Search code examples
objective-cinheritancepropertiesredeclaration

Property Declaration - ivar and getter values don't match


I have a doubt regarding property redeclaration

Overview:

  • class "A" is the parent class with a readonly property int n1;
  • class "B" is the subclass, which redeclares the property as read write
  • using the setter of class "B" the property value is set as 20
  • when i print the value using the getter and the instance variable I seem to get different values

Points to note: - Memory management = ARC (Automatic Reference Counting)

Question:

  • When I print the values of self.n1 and _n1 why do I get different values ?
  • My expected behavior and actual behavior don't match why (Pls scroll down to see the actual vs expected) ?

Code: (in separate files)

A.h

#import<Foundation/Foundation.h>

@interface A : NSObject

@property (readonly) int n1;

- (void) display;

@end

A.m

#import "A.h"

@implementation A

@synthesize n1 = _n1;

- (void) display
{
    printf("_n1     = %i\n", _n1);                  //I expected _n1 and self.n1 to display the same value
    printf("self.n1 = %i\n\n", self.n1);            //but they seem to display different values
}

@end

B.h

#import"A.h"

@interface B : A

@property (readwrite) int n1;

@end

B.m

#import"B.h"

@implementation B

@synthesize n1 = _n1;

@end

test.m

#import"B.h"

int main()
{
    system("clear");

    B* b1 = [[B alloc] init];

    b1.n1 = 20;

    [b1 display];   //Doubt - my expected behavior is different from actual behavior


    return(0);
}

Expected Behavior:

_n1     = 20
self.n1 = 20

Actual Behavior:

_n1     = 0
self.n1 = 20

Solution

  • There are two ways of going about this. In neither case do you call @synthesize in the subclass. I'm surprised that compiles for you. I would expect an error like "Property 'n1' attempting to use ivar '_n1' declared in superclass 'A'". In any case it's definitely not something you can really do, which is why you're seeing strange behavior. (I remembered why you aren't seeing this error; it's because of the separate compile units. You're just winding up with different ivars.)

    First, you need to understand @dyanmic. This is a way of telling the compiler "yes, I know you don't see an implementation for the required method here; I promise it'll be there at runtime." In the subclass, you will use @dynamic to let the compiler know that it's ok to inherit n1.

    @implementation B
    @dynamic n1;
    @end
    

    Now, you need to provide the setN1: method. IMO, subclasses shouldn't go messing with their superclass's ivars, so I approve of the fact that synthesized ivars are marked @private. In a second, I'll tell you how to undo that, but for now let's deal with my preferred solution:

    • Implement setN1: as a private method in A.
    • Expose it in B.

    A.h

    @interface A : NSObject
    @property (readonly) int n1;
    - (void) display;
    @end
    

    A.m

    #import "A.h"
    
    @interface A () // Private class extension, causes setN1: to be created but not exposed.
    @property (readwrite) int n1;
    @end
    @implementation A
    
    @synthesize n1 = _n1;
    
    - (void) display {
       ...
    }
    @end
    

    B.h

    #import "A.h"    
    @interface B : A
    @property (readwrite) int n1; // Tell the world about setN1:
    @end
    

    B.m

    #import "B.h"
    @implementation B
    @dynamic n1; // Yes compiler, setN1: exists. I promise.
    @end
    

    Now, some people think it's fine for subclasses to mess with their superclass's ivars. Those people are wrong (ok, IMHO...) but it is possible in ObjC. You just need to declare the ivar @protected. This is the default when you declare ivars directly in the @interface (one of many reasons you shouldn't do this anymore). It would look like this:

    A.h

    @interface A : NSObject {
      int _n1;
    }
    ...
    

    A.m -- remove the extra class extension that makes n1 writable in the superclass.

    B.h -- no change

    B.m

    @implementation B
    @dynamic n1;
    
    - (void)setN1:(int)n1 {
      _n1 = n1;
    }
    @end