Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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
like image 820
user1046037 Avatar asked Dec 07 '11 03:12

user1046037


2 Answers

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
like image 110
Rob Napier Avatar answered Sep 25 '22 20:09

Rob Napier


I copied your code and verified the behavior that you are getting. I can explain the mechanics of it, but not the logic behind it.

Here is what's going on: each of the two @synthesize directives produces a hidden variable _n in its corresponding class. In addition, the directive synthesizes a getter for n1 in A, and a getter/setter pair in B. The getter of n1 in B overrides the getter of n1 in A; the setter does not, because there is nothing to override.

At this point, A's _n1 in B becomes orphaned: neither the getter of n1 nor its setter reference it. The setter references B's _n1, not A's. That's why you are seeing different values printed in the display method of A. Putting the method in B behaves the way that you would expect.

EDIT:

Naturally, the next question is how to make the behavior that you want. It turns out to be simple: do not synthesize the property in B, and implement a setter of _n1 in A's implementation file (without putting it in the interface, so that it remains read-only to the clients of your interface).

// This goes in A.m without a declaration in A.h
- (void) setN1:(int)n1 {
    _n1 = n1;
}
like image 42
Sergey Kalinichenko Avatar answered Sep 21 '22 20:09

Sergey Kalinichenko