Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
menu search
person
Welcome To Ask or Share your Answers For Others

Categories

I've read that @synthesize will automatically create corresponding instance variables for @property and that ivars are @protected by default. But, what if I use a class extension (like below) to indicate that the @property methods are to be private?

// Photo.m
@interface Photo ()
@property (nonatomic, retain) NSMutableData *urlData;
@end

Will the corresponding ivar then be @private? Or should I explicitly declare it as @private like so?

// Photo.h
@interface Photo : Resource {
@private
    NSMutableData *urlData;
}
See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
322 views
Welcome To Ask or Share your Answers For Others

1 Answer

Ellaborating on Kevin’s answer:

When you declare a class, e.g.:

@interface SomeClass : NSObject {
@public
    id publicIvar;
@protected
    id protectedIvar;
@private
    id privateIvar;
}
@end

the compiler1 decides upon an instance variable layout for that class. This layout determines offsets of instance variables with regard to the address of instances of that class. One possible layout would be:

        +--> publicIvar address = instance address + offsetOfPublicIvar
        |
        |
+-----+------------+-----+---------------+-----+-------------+-----+
| ... | publicIvar | ... | protectedIvar | ... | privateIvar | ... |
+-----+------------+-----+---------------+-----+-------------+-----+
|
|
+--> instance address

When an instance variable is referenced in code — either in the implementation of the class or in some other part of the code base, the compiler replaces that reference with the corresponding offset of the instance variable with regard to the address of the corresponding instance.

For example, in the implementation of SomeClass,

privateIvar = someObject;

or

self->privateIvar = someValue;

is translated as something like:

*(self + offsetOfPrivateIvar) = someObject;

Similarly, outside of the class,

SomeClass *obj = [SomeClass new];
obj->publicIvar = someObject;

is translated as something like:

SomeClass *obj = [SomeClass new];
*(obj + offsetOfPublicIvar) = someObject;

However, the compiler only allows this according to the visibility of the instance variable:

  • A private instance variable can be referenced only in the implementation of the corresponding class;
  • A protected instance variable can be referenced only in the implementation of the corresponding class and its subclasses;
  • A public instance variable can be referenced anywhere.

When an instance variable is declared in a class extension, e.g.

@interface SomeClass () {
    id extensionIvar;
}
@end

the compiler adds it to the instance variable layout:

+-----+------------+-----+---------------+
| ... | otherIvars | ... | extensionIvar |
+-----+------------+-----+---------------+

and any reference to that instance variable is replaced by its corresponding offset with regard to the instance. However, since that instance variable is only known to the implementation file where the class extension has been declared, the compiler won’t allow other files to reference it. An arbitrary source file can only reference instance variables it knows (respecting the visibility rules). If instance variables are declared in a header file that’s imported by a source file, then the source file (or, more accurately, the compiler whilst translating that unit) is aware of them.

On the other hand, an extension variable is only known by the source file where it was declared. We can thus say that instance variables declared in class extensions are hidden from other files. The same reasoning applies to backing instance variables of properties declared in class extensions. It’s similar to @private, but more restrictive.

Note, however, that at runtime visibility rules are not enforced. Using Key-Value Coding, an arbitrary source file can sometimes (the rules are described here) access an instance variable:

SomeClass *obj = [SomeClass new];
id privateValue = [obj valueForKey:@"privateIvar"];

including an instance variable declared in an extension:

id extensionValue = [obj valueForKey:@"extensionIvar"];

Regardless of KVC, access to instance variables be accomplished via the Objective-C runtime API:

Ivar privateIvar = class_getInstanceVariable([SomeClass class],
                                             "privateIvar");
Ivar extensionIvar = class_getInstanceVariable([SomeClass class],
                                               "extensionIvar");

id privateValue = object_getIvar(obj, privateIvar);
id extensionValue = object_getIvar(obj, extensionIvar);

Note that a class can have more than one class extension. However, one class extension cannot declare an instance variable with the same name as another instance variable, including instance variables declared in other class extensions. Since the compiler emits symbols like:

_OBJC_IVAR_$_SomeClass.extensionIvar

for each instance variable, having different extensions declaring instance variables with the same name doesn’t yield a compiler error because a given source file is not aware of another source file, but it does yield a linker error.

1This layout can be changed by the Objective-C runtime. In fact, offsets are computed by the compiler and stored as variables, and the runtime can change them as needed.

PS: Not everything in this answer applies to all compiler/runtime versions. I’ve only considered Objective-C 2.0 with non-fragile ABI and recent versions of Clang/LLVM.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
thumb_up_alt 0 like thumb_down_alt 0 dislike
Welcome to ShenZhenJia Knowledge Sharing Community for programmer and developer-Open, Learning and Share
...