3

I discovered a strange behaviour when working with a property, that was inherited as readonly and than redeclared in the inherited class as readwrite

In A.h

@interface A : NSObject

@property (nonatomic, strong, readonly) NSObject * someProperty;

@end

In B.h

@interface B : A

// no matter if here
// @property (nonatomic, strong, readwrite) NSObject * someProperty;

- (void)foo;

@end

In B.m

@interface B()

// no matter if here
@property (nonatomic, strong, readwrite) NSObject * someProperty;

@end

@implementation B

- (void)foo {

    NSLog(@"%@", self.someProperty);

    // crash here with unrecognized selector setSomeProperty:
    self.someProperty = [NSObject new];
}

@end

calling

self.someProperty = [NSObject new];

causes the code to crash on unrecognized selector "setSomeProperty:"

investigation showed, that it looks like the setter did not get synthetized, even when declared as readwrite

Why is this happening? The compiler didnt indicate any warning for this to happen, nor i found anywhere this behaviour documented

Peter Lapisu
  • 19,915
  • 16
  • 123
  • 179

2 Answers2

2

Add a @synthesize directive to the B.m file and the crash will go away:

@synthesize someProperty = _someProperty;

The problem is that since in the parent class you declared the property as readonly there is no setter synthesized for it. And the subclasses inherit this behavior. Even if you redeclare the property to be readwrite in the subclass. The @synthesize command will instruct the compiler to generate the accessor methods for class B again.

Hope this helps!

enter image description here

LuisCien
  • 6,362
  • 4
  • 34
  • 42
  • Did you try this? I get "error: property 'someProperty' attempting to use instance variable '_someProperty' declared in super class 'A'" – Martin R Jun 19 '13 at 18:21
  • Yes I tried it. If I don't add the synthesize directive to the B.m file I get: "unrecognized selector sent to instance 0xec74a00". If then I add it and re-run the app the error goes away. – LuisCien Jun 19 '13 at 18:24
  • Strange, my results are different, perhaps I made some error ... Did you define the superclass with the property in the same way as in the question? – Martin R Jun 19 '13 at 18:27
  • Maybe there's something else going on... I'll investigate further – LuisCien Jun 19 '13 at 18:30
  • 1
    This is a subtle issue where the compiler is letting the OP down. This doesn't solve the problem, it *masks* it. @MartinR is correct in his reasoning - the compiler cannot (given the design of Obj-C) synthesise a setter to match the inherited getter. When you add an `@synthesize` to `B` the compiler implements both setter *and* a getter - and that getter does not call the inherited version. Now depending on how the compiler implements the backing store for a synthesised property and on how the inherited getter is written then it may work, you may get errors, or it may just malfunction. – CRD Jun 20 '13 at 20:05
2

I cannot give you an official reference, but this is what I experienced: For a property inherited from a superclass, the compiler does not generate any accessor methods.

In your case, the property is declared as readonly in class A, so that the compiler creates only a getter method. The re-declaration in class B does not create any additional accessor methods. The reason might be that class B does not know "how" the property is realised in class A (it need not be an instance variable).

So the property declaration in the subclass is only a "promise" to the compiler that getter and setter functions will be available at runtime (similar to a @dynamic declaration). If there is no setter then you will get a runtime exception.

So a use case of re-declaring the property in the subclass would be if the superclass declares the property as read-only in the public interface, but as read-write in the (private) class extension:

// A.h
@interface A : NSObject
@property (nonatomic, strong, readonly) NSObject * someProperty;
@end

// A.m
@interface A()
@property (nonatomic, strong, readwrite) NSObject * someProperty;
@end

@implementation A
@end

In this case, both setter and getter are created in the class A, and class B can re-declare the property as read-write in its implementation.

Martin R
  • 529,903
  • 94
  • 1,240
  • 1,382
  • This reasoning makes sense. Further as the compiler knows that it doesn't know how the property is realised it could issue a warning to the effect that a setter must be implemented or inherited. Maybe one of you should submit a radar? – CRD Jun 19 '13 at 22:06
  • @CRD: That is a good suggestion and thanks for the feedback. But since my answer and LuisCien's answer somehow contradict each other, I will wait for some feedback from the OP first. – Martin R Jun 20 '13 at 08:42
  • Martin, your reasoning does make sense. I've just run a test to confirm that the compiler (I used Xcode 4.6.3) is not being over clever and using information it shouldn't have access to by the design of the language - and it isn't. (Try setting `_someProperty` directly in `A`'s `init` and logging it in `B`'s `init` when there is an `@synthesize` - or use "Generate Assembler".) It's a subtle corner where the compiler is letting the OP down. – CRD Jun 20 '13 at 20:10