私有的@property是否创建了一个@private实例变量?

时间:2011-04-26 01:04:27

标签: objective-c properties private instance-variables

我已经读过@synthesize will automatically create corresponding instance variables for @property,而且ivars默认为@protected。但是,如果我使用类扩展(如下所示)来指示@property方法是私有的呢?

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

那么相应的ivar会@private吗?或者我应该明确地将其声明为@private吗?

// Photo.h
@interface Photo : Resource {
@private
    NSMutableData *urlData;
}

2 个答案:

答案 0 :(得分:31)

对Kevin的回答进行阐述:

声明一个类时,例如:

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

编译器 1 决定该类的实例变量布局。此布局确定实例变量的偏移量,该偏移量与该类的实例地址有关。一种可能的布局是:

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

当在代码中引用实例变量时 - 无论是在类的实现中还是在代码库的某个其他部分中,编译器都会将该引用替换为实例变量的相应偏移量,而不是相应的地址。实例

例如,在SomeClass的实现中,

privateIvar = someObject;

self->privateIvar = someValue;

翻译为:

*(self + offsetOfPrivateIvar) = someObject;

同样,在课堂之外,

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

翻译为:

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

但是,编译器只根据实例变量的可见性来允许它:

  • 私有实例变量只能在相应类的实现中引用;
  • 受保护的实例变量只能在相应类及其子类的实现中引用;
  • 可以在任何地方引用公共实例变量。

在类扩展中声明实例变量时,例如

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

编译器将其添加到实例变量布局:

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

并且对该实例变量的任何引用都将替换为与实例相关的相应偏移量。但是,由于该实例变量仅对已声明类扩展的实现文件已知,因此编译器不允许其他文件引用它。任意源文件只能引用它知道的实例变量(遵守可见性规则)。如果实例变量是在源文件导入的头文件中声明的,那么源文件(或者更准确地说,是翻译该单元时的编译器)就会知道它们。

另一方面,扩展变量仅由声明它的源文件所知。因此,我们可以说在类扩展中声明的实例变量对其他文件是隐藏的。相同的推理适用于支持类扩展中声明的属性的实例变量。它类似于@private,但限制性更强。

但请注意,不会强制执行运行时可见性规则。使用键值编码,有时(规则描述为here)任意源文件访问实例变量:

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

包括在扩展名中声明的实例变量:

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

无论KVC如何,都可以通过Objective-C运行时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);

请注意,一个类可以有多个类扩展。但是,一个类扩展不能声明与另一个实例变量具有相同名称的实例变量,包括在其他类扩展中声明的实例变量。由于编译器发出如下符号:

_OBJC_IVAR_$_SomeClass.extensionIvar

对于每个实例变量,具有不同的扩展名,声明具有相同名称的实例变量不会产生编译器错误,因为给定的源文件不知道另一个源文件,但它确实会产生链接器错误。

1 可以通过Objective-C运行时更改此布局。实际上,偏移量由编译器计算并存储为变量,运行时可以根据需要更改它们。

PS:此答案中的所有内容都不适用于所有编译器/运行时版本。我只考虑过非脆弱的ABI和最新版本的Clang / LLVM的Objective-C 2.0。

答案 1 :(得分:12)

@private实例变量是仅编译时功能。鉴于@property的支持ivars已被隐藏,@private没有做任何事情。所以从本质上讲,它已经是@private