There is a class that looks like this (I'm omitting the imports for brevity):
Base.h:
@interface Base : NSObject
@property (strong, readonly) NSString *something;
- (id)initWithSomething:(NSString *)something;
@end
Base.m:
@implementation Base
- (id)initWithSomething:(NSString *)something {
self = [super init];
if (self) _something = something;
return self;
}
@end
As you see, the 'something' property is readonly. Now I want to create a subclass that overrides that property to be writable as well:
Sub.h:
@interface Sub : Base
@property (strong) NSString *something;
@end
Sub.m:
@implementation Sub
@end
And the code:
main.c:
int main(int argc, const char * argv[]) {
@autoreleasepool {
Sub *o = [Sub new];
o.something = @"foo";
NSLog(@"%@", o.something);
}
return 0;
}
This code results in:
2013-09-07 13:58:36.970 ClilTest[3094:303] *** Terminating app due to uncaught
exception 'NSInvalidArgumentException', reason: '-[Sub setSomething:]: unrecognized
selector sent to instance 0x100109ff0'
Why is that? Why doesn't it find the setSelector?
When I do this in the subclass instead:
Sub.m:
@implementation Sub
@synthesize something = _something;
@end
it all works. Does this mean the subclass' property is not synthesized by default even though it is defined as @property
in the @interface
? Does the compile somehow 'see' the automatically generated getter from Base and doesn't generate the setter? And why, I think the setter should be generated as it doesn't exist yet. I'm using Xcode 4.6.2 and the project is a Cli Tool (type Foundation), but the same happens in my actual project which is an iPhone app.
Background: I have a heavy object (instance of Base) that requires a Bluetooth connection to some equipment and I am supposed to create a view controller for some functionality. For easy testing I don't want to be connected to BT (actually, I would need a physical device and test the code on it), I would like to be able to test it in the simulator.
What I came up with is that I simply create a subclass (Sub) that stubs a few methods / properties and use it instead, and when the code is ready I just remove the code for the subclass, replace its instance with the correct one, test in with a device, commit and push. It actually works fine, except for the weird thing with @property
above.
Could somebody tell me what is going on with property overriding?
For a readonly property, only a getter method is synthesized, but no setter method.
And when compiling the subclass, the compiler does not know how the property is realized in the base class (it could be a custom getter instead of a backing instance variable). So it cannot just create a setter method in the subclass.
If you want to have write access to the same instance variable from the subclass,
you have to declare it as @protected
in the base class
(so that it is accessible in the subclass), re-declare the property
as read-write in the subclass, and provide a setter method:
Base.h:
@interface Base : NSObject {
@protected
NSString *_something;
}
@property (strong, readonly) NSString *something;
- (id)initWithSomething:(NSString *)something;
@end
Sub.h:
@interface Sub : Base
@property (strong, readwrite) NSString *something;
@end
Sub.m:
@implementation Sub
-(void)setSomething:(NSString *)something
{
_something = something;
}
@end
Your solution
@synthesize something = _something;
generates getter and setter method in the subclass, using a separate instance
variable _something
in the subclass (which is different
from _something
in the base class).
This works as well, you just should be aware that self.something
refers to
different instance variables in the base class and in the subclass. To make that
more obvious, you could use a different instance variable in the subclass:
@synthesize something = _somethingElse;
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With