Read-only access for class-type property

I posted this question originally on StackOverflow but it doesn't seem to be a feature yet supported by Swift 3, so I am reposting my question here.


I am aware of `private(set)` as discussed here and here and here but it doesn't seem to work for my situation where I have class-type instance property.


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 `Point`, allowing it to be the only one who can modify `origin`, 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`.



Note1: (Since someone on SO asked why I didn't use `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.


Note2: An answer in SO 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;

}

Accepted Reply

The traditional Objective-C approach to this is to make mutable and immutable variants of your

Point
class.
Sprite
can then export the immutable variant, which clients can’t (legally) modify.

The Swift-y approach would be to do this with a struct. I understand that that’s challenging in your case, but I think you should go back and reconsider that option. One possibility would be to split

Point
into two parts, a struct holding the state for which value semantics is appropriate and a class for the state where reference semantics makes sense. It may even be possible to glom these together by having the struct reference the class.

There may be a better answer to this conundrum but it’s hard to say without more info about the “other design constraints” that require you to use a class.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Replies

The traditional Objective-C approach to this is to make mutable and immutable variants of your

Point
class.
Sprite
can then export the immutable variant, which clients can’t (legally) modify.

The Swift-y approach would be to do this with a struct. I understand that that’s challenging in your case, but I think you should go back and reconsider that option. One possibility would be to split

Point
into two parts, a struct holding the state for which value semantics is appropriate and a class for the state where reference semantics makes sense. It may even be possible to glom these together by having the struct reference the class.

There may be a better answer to this conundrum but it’s hard to say without more info about the “other design constraints” that require you to use a class.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

I simply would like to be able to keep the reference to the property from somewhere outside of the owner of that property, not being able to write to it but able to keep an up-to-date data inside that property. I finally decided that I can solve this problem with a struct and a delegation pattern. Thank you for your answer.