Can cocoa framework hide an ivar declaration in their public header file?
Is this possible ?
My colleague told me that naming a ivar with prefix underscore like what apple mostly does may cause some problem. He said that apple can declare an ivar in a class and doesn't let it be in the public header file. So if i accidentally name an ivar that get collide with that "secret ivar" which is not in the header file , this will cause a problem.
I don't know much about how frameworks works so I'm not sure about his explanation. I know apple only reserve the use of prefix underscore for only method name in there code guideline and many people do use prefix underscore when name an ivar and they said it's perfectly safe because if the name get collide the compiler would generate a error. I know this is true if the name of the ivar is in the header file. But what about that kind of "secret ivar" which is not in the public header file?
My colleague's another evidence for this is that if you dump an apple's application framework's header file and compare it with apple's public header file, you would find they don't match for many declaration of both methods 开发者_如何学Goand ivars.
I'm really confused by this question and hope someone can provide some professional answer and if you can provide some reliable reference about this question , that would be great help.
(I don't know whether I have explain my question clearly enough... Forgive my poor english...)
Your colleague might be referring to instance variables in class extensions. They do not show up in the (public) interface of the class. For instance,
// SomeClass.h -- this is a publicly distributed file
@interface SomeClass : NSObject
@end
// SomeClass.m -- this is known only to the framework developer
@interface SomeClass() {
NSString *someIvar;
}
@end
@implementation SomeClass
- (id)init {
self = [super init];
if (self) someIvar = @"Default";
return self;
}
@end
Note that someone that has access to the public headers only won’t know that SomeClass
has an instance variable called someIvar
by reading the header file that declares that class. Also, it won’t be possible to access that instance variable in the same way as accessing an instance variable declared in the public interface. In fact, there won’t be a name collision in case a developer subclasses SomeClass
and declares an instance variable named someIvar
. For instance, this is valid:
// SomeSubclass.h
@interface SomeSubclass : SomeClass {
NSString *someIvar;
}
@end
In this case, any reference to someIvar
in the implementation of SomeSubclass
will refer to the instance variable declared in SomeSubclass
only, and this instance variable is different from the one declared in the (private) class extension of SomeClass
.
The compiler emits different symbol names for them:
_OBJC_IVAR_$_SomeClass.someIvar
_OBJC_IVAR_$_SomeSubclass.someIvar
and, upon instantiating an object of type SomeSubclass
, both instance variables are located in different memory addresses.
While an inspection of the binary might show that SomeClass
has an instance variable called someIvar
even though it’s not listed in the public header file, there won’t be a name collision in subclasses.
That said, it might be possible that Apple have a set of public header files that they distribute with the SDK but use alternative, private header files that declare additional instance variables. I find that rather unlikely and there might be potential for binary incompatibility if that actually happens. I guess only Apple would be able to provide a definitive answer.
I'm going to invoke @bbum again hoping that he'll get the message and come and correct this answer.... But I'll do my best here.
In the old ABI, you could not hide your ivars. The compiler will catch the kinds of conflicts you're discussing in any case where they would matter. That said, I've found it's probably better to just avoid the problem by using some other naming convention. This way, when you find that there is already a _data
in your superclass (as I have), you don't have to come up with some other random name, or accidentally access the superclass's private data when you didn't mean to. Despite my original dislike, Google's trailing underscore has grown on me since I used it in a large project.
The reason you couldn't hide your ivars (and if you did somehow, you wouldn't get name collisions anyway) is that ivars used to be just struct offsets. Hampster Emporium has a nice short post explaining this. At runtime, there is no _window
, there is only offset 20. (Again, this is the old ABI I'm talking about).
This is not true for methods. They can collide. This is a bad thing when it happens. You don't get a warning. It really annoys me that Apple has private methods that do not have leading underscores on them. I have collided with them and the behavior is undefined. They reserved underscore; they should use it. (NSMutableArray
had a private -pop
method in a category that was implemented backwards of how I implemented my category with the same name. The resulting bugs in UINavigationController
were entertaining to say the least.)
That digression to the side, with the new ABI, it is now possible to have "hidden" ivars. You just declare them as synthesized properties in a private class extension. But through the magic of the new ABI (ok, not magic, just pointers), the ivars are safe even if they collide. But the synthesized methods aren't safe from collision. Note how directly accessing the ivar below gives you the expected behavior, but using the property gives a surprising result. I haven't worked out how a developer should best avoid or even detect this kind of situation.
EDIT Based on my discussion with @Bavarious, I moved this code to the version of clang that comes with Xcode 4 (most of my boxes are still on Xcode 3). It complains with all the kinds of loud errors you'd hope for. Particularly you get "Property someIvar
is already implemented." So that bodes very well for using @property
the way you'd want to.
#import <Foundation/Foundation.h>
// SomeClass.h
@interface SomeClass : NSObject
@end
// SomeClass.m
@interface SomeClass ()
@property (copy) NSString *someIvar;
@end
@implementation SomeClass
@synthesize someIvar;
- (id)init {
self = [super init];
if (self) someIvar = @"SomeClass";
return self;
}
- (void)print
{
NSLog(@"Superclass=%@:%@", someIvar, [self someIvar]);
}
@end
// SubClass.h
@interface SubClass : SomeClass
{
NSString *someIvar;
}
@property (copy) NSString *someIvar;
@end
// SubClass.m
@implementation SubClass
@synthesize someIvar;
- (id)init {
self = [super init];
if (self) someIvar = @"SubClass";
return self;
}
- (void)print
{
[super print];
NSLog(@"Subclass=%@:%@", someIvar, [self someIvar]);
}
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
SubClass *subclass = [[SubClass alloc] init];
[subclass print];
[pool drain];
return 0;
}
Superclass=SomeClass:SubClass <== Note the mismatch
Subclass=SubClass:SubClass
The compiler will let you know if there is a name clash with ivars, but not with method names.
From Apple's Developer's Guide:
Avoid the use of the underscore character as a prefix meaning private, especially in methods. Apple reserves the use of this convention. Use by third parties could result in name-space collisions; they might unwittingly override an existing private method with one of their own, with disastrous consequences. See “Private Methods” for suggestions on conventions to follow for private API.
精彩评论