The Objective-C runtime keeps a list of declared properties as meta-data with a Class object. The meta-data includes property name, type, and attributes. The runtime library also provides a couple of functions to retrieve these information. It means a declared property is more than a pair of accessor methods (getter/setter). My first question is: Why we (or the runtime) need the meta-data?
As is well known, a declared property cannot be overridden in subclasses (except readwrite vs. readonly). But I have a scenario that guarantees that needs:
@interface MyClass : MySuperClass <NSCopying, NSMutableCopying>
@property (nonatomic, copy, readonly) NSString *string;
- (id)initWithString:(NSString *)aString;
@end
@interface MyMutableClass : MyClass
@property (nonatomic, strong, readwrite) NSMutableString *string;
- (id)initWithString:(NSString *)aString;
@end
Of course, the compiler won't let the above code pass through. My solution is to substitute the declared property with a pair of accessor methods (with the readonly case, just the getter):
@interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
NSString *_string;
}
- (id)initWithString:(NSString *)aString;
- (NSString *)string;
@end
@implementation MyClass
- (id)initWithString:(NSString *)aString {
self = [super init...];
if (self) {
_string = [aString copy];
}
return self;
}
- (NSString *)string {
return _string;
}
- (id)copyWithZone:(NSZone *)zone {
return self;
}
- (id)mutableCopyWithZone:(NSZone *)zone {
return [[MyMutableClass alloc] initWithString:self.string];
}
@end
@interface MyMutableClass : MyClass
- (id)initWithString:(NSString *)aString;
- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;
- (void)didMutateString;
@end
@implementation MyMutableClass
- (id)initWithString:(NSString *)aString {
self = [super init...];
if (self) {
_string = [aString mutableCopy];
}
return self;
}
- (NSMutableString *)string {
return (NSMutableString *)_string;
}
- (void)setString:(NSMutableString *)aMutableString {
_string = aMutableString;
// Inform other parts that `string` has been changed (as a whole).
// ...
}
- (void)didMutateString {
// The content of `string` has been changed through the interface of
// NSMutableString, beneath the accessor method.
// ...
}
- (id)copyWithZone:(NSZone *)zone {
return [[MyClass alloc] initWithString:self.string];
}
@end
Property string needs to be mutable because it is modified incrementally and potentially frequently. I know the constraint that methods with the same selector should share the same return and parameter types. But I think the above solution is appropriate both semantically and technically. For the semantic aspect, a mutable object is a immutable object. For the technical aspect, the compiler encodes all objects as id's. My second question is: Does the above solution make sense? Or it's just odd?
I can also take a hybrid approach, as follows:
@interface MyClass : MySuperClass <NSCopying, NSMutableCopying> {
NSString *_string;
}
@property (nonatomic, copy, readonly) NSString *string;
- (id)initWithString:(NSString *)aString;
@end
@interface MyMutableClass: MyClass
- (id)initWithString:(NSString *)aString;
- (NSMutableString *)string;
- (void)setString:(NSMutableString *)aMutableString;
- (void)didMutateString;
@end
However, when I access the property using the dot syntax like myMutableObject.string, the compiler warns that the return type of the accessor method does not match the type of the declared property. It's OK to use the message form as [myMutableObject string]. That suggests another aspect where a declared property is more than a pair of accessor methods, that is, more static type checking, although it is undesirable here. My third question is: Is it common to use getter/setter pair instead of declared property when it is intended to be overridden in subclasses?