HuaTham HuaTham - 4 years ago 126
Swift Question

Read-only access for class property in Swift

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 a 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
.

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.

Answer Source

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.)

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download