This question came up in an IRC channel the other day: “What’s the best way to set up a property that’s read-only externally, but modifiable inside of the class, so I can use properties or KVC to change it?”
TL;DR: Make readonly in a public
readwrite in a class extension.
Properties by default are read/write. This property:
@property (assign) NSInteger frobulationThreshold;
States that (semantically)
frobulationThreshold is a mutable property. You can query it. You can change it, with reasonable expectation that you’ll get the same value back next time you query it unless somebody else changes it.
What’s happening under the hood is the compiler is acting as if it had seen these declarations:
- (NSInteger) frobulationThreshold; - (void) setFrobulationThreshold: (NSInteger) frobThresh;
You can declare a property to be read-only, say to get the number of elements in a collection.
@property (readonly) NSUInteger count;
All this does is have the compiler fantasize that it has seen this declaration:
- (NSUInteger) count;
Outside of that, nothing happens.
Things get more interesting when you
@synthesize one of these properties. The
@property just tells the compiler to pretend that it saw method declarations of a certain form.
@synthesize (whether explicitly written, or implicitly performed with newer Xcodes) takes the various attributes of the
@property declaration, and emits object code for any of the accessors that don’t otherwise exist. Given the
frobulationThreshold property declaration above, if there were no explicitly implemented setters or getters, the compiler would emit an implementation that used atomic access (the default), to retrieve and change an instance variable. Given the
@property, the compiler would emit a similar single method’s implementation.
So now the actual answer to the question. Say you wanted to make
frobulationThreshold a read-only property that was modifiable inside of the implementation.
In the header file, you’ll want a property that’ll make the compiler think it’s only seen
- (NSInteger) frobulationThreshold;
And not the setter:
@property (readonly) NSInteger frobulationThreshold;
Is sufficient. Because of the
-frobulationThreshold is considered to exist, but not
-setFrobulationThreshold: Now everyone who
#imports this header will see the
readonly property, and if they try to change it, the compiler will complain with a “I have not seen a declaration for a method called
-setFrobulationThreshold:. I shall now warn in your general direction.” (And you are on top of fixing your Warnings right?)
Inside of the implementation file, though, you can create a class extension that redeclares the property as
@interface Frobinizer () @property (assign, readwrite) NSInteger frobulationThreshold; @end // extension
readwrite is actually optional in this case, but I like adding it to make it explicit that this is a property declaration that exists to turn a read-only property into a read/write one
When the compiler sees this, it adjusts its concept of method declarations it thinks it has seen. Before processing the
@property above, the compiler thought it only saw
-frobulationThreshold thanks to the readonly property in the header file. Now with this property redeclaration, the compiler thinks it has seen now seen both
-setFrobulationThreshold:. Et voila, the property is now read/write, as far as any code after this class extension is concerned. Code can happily call
self.frobulationThreshold = 23;, or
[self setFrobulationThreshold: 23]; to change that property.
Of course not, this is Objective-C. Because there will exist a
setFrobulationThreshold: (either explicitly written, or synthesized) as a method of the class, anyone who is aware of it can call it. That’s just the nature of Objective-C. There are no private methods.
Using read-only properties does help “keep honest people honest”. You have to do some work to call
setFrobulationThreshold from outside of its implementation file, and if you are jumping through those hoops, you should feel dirty enough to reconsider what you’re doing, or to lobby to have the property changed to be publicly read/write.