Consider a C++ API like const T* foo()
. This clearly documents the supported mutability and use of the API: OK, we'll let you look at T, but please don't change it. You can still mutate it, but you have to explicitly use const_cast
to indicate your intention to not follow the API.
A good portion of Objective-C API's are comprised of property declarations. How is a user of an API supposed to interpret: @property (readonly) T foo
? (Assume T isn't an immutable type)
foo
isn't mean to be replaced.foo
. Is it safe to mutate foo? (Clearly I can)NOTE: I'm not asking about the language specs. I'm asking about what the conventional interpretation of an API like this is within the Objective-C community.
As matt said, the fact that you've got a pointer to the object does not mean that the object itself is mutable. Objective-C uses the behavior of the class, not the pointer, to enforce immutability. So in general you should be seeing read-only properties that return, e.g., NSString
rather than NSMutableString
.
I took a look through Apple's iOS framework headers to verify this:
grep -rn "@property.*readonly.*Mutable" /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS.sdk/*.h
(The pattern for class names in Cocoa is to call the mutable "version" of the class $PREFIXMutable$CLASSNAME
: NSString
/NSMutableString
, NSDictionary
/NSMutableDictionary
.)
./System/Library/Frameworks/AVFoundation.framework/Headers/AVComposition.h:133:@property (nonatomic, readonly) NSArray<AVMutableCompositionTrack *> *tracks; ./System/Library/Frameworks/CoreData.framework/Headers/NSManagedObjectContext.h:149:@property (nonatomic, readonly, strong) NSMutableDictionary *userInfo NS_AVAILABLE(10_7, 5_0); ./System/Library/Frameworks/Foundation.framework/Headers/NSAttributedString.h:54:@property (readonly, retain) NSMutableString *mutableString; ./System/Library/Frameworks/Foundation.framework/Headers/NSExpression.h:127:@property (readonly, copy) id (^expressionBlock)(id __nullable, NSArray *, NSMutableDictionary * __nullable) NS_AVAILABLE(10_6, 4_0); ./System/Library/Frameworks/Foundation.framework/Headers/NSThread.h:24:@property (readonly, retain) NSMutableDictionary *threadDictionary; ./System/Library/Frameworks/GameplayKit.framework/Headers/GKRuleSystem.h:54:@property (nonatomic, retain, readonly) NSMutableDictionary *state; ./System/Library/Frameworks/ModelIO.framework/Headers/MDLMesh.h:137:@property (nonatomic, readonly, retain) NSMutableArray *submeshes;
Only seven results, and the one in NSExpression
doesn't count because the "Mutable" that the search found is an argument to the Block that is actually the property's value.
For the others, I think you'll find that the appropriate class reference doc tells you what you can and can't do with the values.
For example, the documentation for threadDictionary
has this to say:
You can use the returned dictionary to store thread-specific data.[...]You may define your own keys for the dictionary.
A mutable dictionary is returned precisely so that you can mutate it. The thread object doesn't let you set it, however, so that it can also store things there.
The hit in NSAttributedString.h is actually in the NSMutableAttributedString
class, and those docs note:
The receiver tracks changes to this string and keeps its attribute mappings up to date.
Since NSAttributedString
is pretty explicitly* just an NSString
packaged up with a bunch of attributes, the design of the class exposes the wrapped string directly; the mutable version follows suit.
UIButton
was mentioned in the comments, because there you have a read-only label whose own properties are modifiable. And there again, the docs are explicit:
Although this property is read-only, its own properties are read/write. Use these properties primarily to configure the text of the button.
and
Do not use the label object to set the text color or the shadow color.
In summary, there's no way in Objective-C at the language level to create or enforce mutability restrictions. As you've noted, a property marked readonly
simply means there's no way for you to set the value to something else.** And there's no equivalent of const_cast
ing the value to be mutable so that you can change it: you will end up with a new value that the vendor object knows nothing about.
The Cocoa convention, then, is to secondarily enforce the property's status by using immutable classes. (In some cases you might even be getting an immutable copy of data that the class internally retains as mutable.) If the API gives you a mutable object, you can assume that you may mutate it, but the documentation should tell you exactly how you can use it.
*Its class description says: "An NSAttributedString
object manages character strings and associated sets of attributes (for example, font and kerning) that apply to individual characters or ranges of characters in the string."
**There is KVC, but that's again at the framework level, and framework convention would indicate that you're asking for trouble doing that.