Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Overriding a readonly property in subclass

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?

like image 509
wujek Avatar asked Sep 07 '13 12:09

wujek


1 Answers

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;
like image 151
Martin R Avatar answered Sep 23 '22 02:09

Martin R