我已经读过@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;
}
答案 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
。