简体   繁体   中英

Using class extensions in xcode 4.4

Since xcode 4.4 you don't need to @synthesize properties anymore ( see here ), the compiler does it for you. So, why does the compiler complain

use of the undeclared identifier _aVar

in my viewDidLoad method of ViewControllerSubclass :

@interface ViewController : UIViewController
@property (assign, nonatomic) int aVar;
@end

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.aVar = 5;
    NSLog(@"Super value: %d", _aVar);
}
@end

@interface ViewControllerSubclass : ViewController
@end

@interface ViewControllerSubclass ()
@property (assign, nonatomic) int aVar;
@end

@implementation ViewControllerSubclass
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"Subclass value: %d", _aVar);
}
@end

If I move everything to the one file instead of 4 separate files for the respective interfaces and implementations, the compiler instead complains that _aVar is private. But _aVar should have been automatically synthesized in my ViewControllerSubclass .

Still keeping everything in 1 file, if I move the initial property declaration to a class extension:

@interface ViewController ()
@property (assign, nonatomic) int aVar;
@end

The build still fails saying that _aVar is private.

If I go back to the 4 file setup for the respective interfaces and implementations xcode builds without even a warning.

If I then run the code:

[[[ViewControllerSubclass alloc] init] view];

the log statements in the above examples print out the following:

Super value: 0

Subclass value: 5

It makes sense that NSLog(@"Super value: %d", _aVar); produced a result of 0 because this variable is supposed to be private to the superclass. But then, why does NSLog(@"Subclass value: %d", _aVar); produce a result of 5 ??

This is all very odd.

You are confusing several different issues, and I'm somewhat confused when you talk about jumping between files and you don't specify where your errors are happening.

Anyway, there is the issue of instance variable visibility. If you declare your iVars within the interface scope, they are, by default, protected.

@interface Foo : NSObject {
    int protectedInt;
@private
    int privateInt;
@public
    int publicInt;
}
@end

When you synthesize iVars, the instance variables themselves are private, unless you explicitly specify them.

Methods will always fire on the most derived implementation.

Now, when you call this...

[[[ViewControllerSubclass alloc] init] view];

You will allocate a subclass, initialize, and cause the view to be loaded. This code will execute...

@implementation ViewControllerSubclass
- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"Subclass value: %d", _aVar);
}
@end

The first thing it does is call the base class implementation...

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    self.aVar = 5;
    NSLog(@"Super value: %d", _aVar);
}
@end

Of course, it calls super, but that part's not important here. The next line assigns 5 to self.iVar. But, which iVar? It calls the property setter method on this object. What type is this instance? It's a ViewControllerSubclass . Since you have given both your base class and its subclass the same name (and declared the property as part of the class extension), they each have their own private-scope instance variable .

However, a method is called on the most derived implementation. Thus, self.iVar will set the instance variable of the subclass. The instance variable for the base class remains unchanged.

When you NSLog the value, you are accessing the private instance variable of the base class, which has not been changed.

Now, after the base class viewDidLoad finishes, we get the code running for the subclass. It logs the value of its private instance variable, which was changed as a result of the base class calling the property setter. So, it will now print it's value, which is 5.

When you make the superclass declaration public, the compiler won't attempt to re-synthesize the property; it assumes that's been taken care of in the superclass. Thus, _aVar is not in scope anywhere in the subclass. It's private anyway, so even when you put them all in the same file that's why you see those errors.

However when you make the superclass property declaration inside the class extension, the compiler will auto-synthesize the property for both the superclass and the subclass. This ends up with both classes having private instance variables _aVar (with two distinct addresses). However, when the superclass viewDidLoad method sets the property, the method invokes the subclass's accessors, which set the value of the subclass's private _aVar variable, and not the superclass's. So that explains why you see the superclass value not changing.

Hope this helps!

I just tested your setup and could replicate your error. I came to the following conclusion:

You need to declare your @property in a .h file. If you want a private variable, declare it in .m in the category @interface (the one with the parentheses).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM