Reputation: 7596
I am aware of private(set)
as discussed here and here but it doesn't seem to work for my situation where I have instance variable with internal properties.
I have an instance property where I would like it to have read-only access. The following code shows some concrete example.
class Point {
var x: Int
var y: Int
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
}
class Sprite {
private(set) var origin = Point(0,0)
func incrementX() {
origin.x += 1
}
func incrementY() {
origin.y += 1
}
}
Here, my intent is to make Sprite
the sole owner of origin
, allowing it to be the only one who can modify it, otherwise making origin
read-only to the public. However:
var sprite = Sprite()
sprite.origin = Point(100, 200) // Error: setter is inaccessibie. Expected behavior.
sprite.origin.x = 150 // No error. The origin.x will be set to 150.
Apparently I can still modify origin
by modifying internal x
and y
directly, but this is not my intent.
How do I make origin
truly read-only? Am I missing something?
Modifying Point
to private var x: Int
or fileprivate var x: Int
won't work for me because I want to be able to let an external class like Sprite
modify Point
.
Edit: From comment below, this is not my actual scenario, since someone comments that I should be using CGPoint
. The code serves as as example to make my question more concrete. I actually have a complex class as instance property in another class, not this simple Point
. I also can't use it as struct
due to other design constraints.
Edit2: An answer below points out that you cannot prevent the reference from being mutable. That's a limitation in Swift I just learned, because apparently, I can achieve exactly what I wanted in C++ using Point const &
:
class Point {
public:
int x;
int y;
Point(int _x, int _y) {
x = _x;
y = _y;
}
Point & operator=(const Point & rhs) {
x = rhs.x;
y = rhs.y;
return *this;
}
...
};
class Sprite {
Point origin;
public:
Sprite()
: origin(0,0)
{}
// HERE
Point const & getOrigin() const { return origin; }
void incrementX() { origin.x += 1; }
void incrementY() { origin.y += 1; }
};
int main(int argc, const char * argv[]) {
Sprite sprite = Sprite();
Point const & o = sprite.getOrigin();
cout << "o: " << o << endl; // (0,0)
// o.x = 10; // Error: Cannot assign to const-qualified type.
// o = Point (100, 200); // Error: no viable overlaod =.
sprite.incrementX();
cout << "o: " << o << endl; // (1,0). Swift can't get this behavior with `struct Point`, but it won't prevent some writing attempts with `class Point`.
cout << "sprite.origin: " << sprite.getOrigin() << endl; // (1,0).
return 0;
}
Upvotes: 3
Views: 3197
Reputation: 2071
It is impossible to get your desired result by exposing Point directly, it is a class and will be passed by reference. You can use a protocol to only expose the parts of Point you desire.
protocol ReadablePoint {
var x: Int { get }
var y: Int { get }
}
class Point: ReadablePoint {
var x: Int
var y: Int
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
}
class Sprite {
private var _origin = Point(0,0)
var origin: ReadablePoint { return _origin }
func incrementX() {
_origin.x += 1
}
func incrementY() {
_origin.y += 1
}
}
Which results in:
var sprite = Sprite()
sprite.origin = Point(100, 200) // error: cannot assign to property: 'origin' is a get-only property
sprite.origin.x = 150 // error: cannot assign to property: 'x' is a get-only property
Upvotes: 1
Reputation: 759
class Point {
private var x: Int
private var y: Int
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
func incrementX() {
x += 1
}
func incrementY() {
y += 1
}
func setX(_ value : Int){
x = value
}
func setY(_ value : Int){
y = value
}
}
class Sprite {
private (set) var origin = Point(0,0)
}
if you need to get the x
and y
value you should add getter in your Point
class like this:
func getX()->Int{
return x
}
so you can try this:
let sprite = Sprite()
sprite.origin.incrementX()
let point = Point(10,20)
point.setX(12)
point.setY(100)
so you can have origin
read-only, in my opinion you have to set x
and y
private.
Upvotes: 1
Reputation: 534885
Make origin
completely private
. The only public thing should be a read-only computed property. Your public incrementX
and incrementY
methods will still work as desired, because they are allowed to access origin
.
If Point is a struct, then it is sufficient for this read-only property to return origin
, because it will be a copy:
var currentOrigin : Point {
return self.origin
}
But if you insist on making Point a class rather than a struct, then you are vending a reference to the same Point instance you are already retaining, and you cannot prevent it from being mutable. Therefore you will have to produce a copy of self.origin
yourself, so that you are not vending a mutable reference to your own private instance:
var currentOrigin : Point {
return Point(self.origin.x,self.origin.y)
}
(The way Objective-C Cocoa typically solves this sort of problem is by implementing a class cluster of immutable/mutable pairs; for example, we maintain an NSMutableString but we vend an NSString copy, so that our mutable string cannot be mutated behind our back. However, I think you are being very foolish to reject the use a struct, since this is one of Swift's huge advantages over Objective-C, i.e. it solves the very problem you are having.)
Upvotes: 2
Reputation: 736
try this :
class Point {
var x: Int
var y: Int
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
}
class Sprite {
private var privatePoint = Point(0,0)
var origin:Point{
get{
return privatePoint
}
}
}
so you can do this :
var sprite = Sprite()
sprite.origin = Point(100, 200) // Error: setter is inaccessibie. Expected behavior.
sprite.origin.x = 150 // No error. The origin.x will be set to 150.
Upvotes: 0
Reputation: 59496
Just make Point
a struct
struct Point {
var x: Int
var y: Int
init(_ x: Int, _ y: Int) {
self.x = x
self.y = y
}
}
class Sprite {
private(set) var origin = Point(0,0)
func incrementX() {
origin.x += 1
}
func incrementY() {
origin.y += 1
}
}
And try again
var sprite = Sprite()
sprite.origin.x = 1
error: cannot assign to property: 'origin' setter is inaccessible
BTW: Why don't you simply use
CGPoint
instead of defining a new type?
Upvotes: 4