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

ma1*_*w28 7 properties private objective-c instance-variables

我已经读过,@synthesize它将自动创建相应的实例变量,@property@protected默认情况下 ivars .但是,如果我使用类扩展(如下所示)来指示@property方法是私有的呢?

// Photo.m
@interface Photo ()
@property (nonatomic, retain) NSMutableData *urlData;
@end
Run Code Online (Sandbox Code Playgroud)

相应的ivar会是@private吗?或者我应该明确地声明它是@private这样的吗?

// Photo.h
@interface Photo : Resource {
@private
    NSMutableData *urlData;
}
Run Code Online (Sandbox Code Playgroud)

小智 31

对Kevin的回答进行了阐述:

当您声明一个类时,例如:

@interface SomeClass : NSObject {
@public
    id publicIvar;
@protected
    id protectedIvar;
@private
    id privateIvar;
}
@end
Run Code Online (Sandbox Code Playgroud)

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

        +--> publicIvar address = instance address + offsetOfPublicIvar
        |
        |
+-----+------------+-----+---------------+-----+-------------+-----+
| ... | publicIvar | ... | protectedIvar | ... | privateIvar | ... |
+-----+------------+-----+---------------+-----+-------------+-----+
|
|
+--> instance address
Run Code Online (Sandbox Code Playgroud)

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

例如,在SomeClass的实现中,

privateIvar = someObject;
Run Code Online (Sandbox Code Playgroud)

要么

self->privateIvar = someValue;
Run Code Online (Sandbox Code Playgroud)

被翻译为:

*(self + offsetOfPrivateIvar) = someObject;
Run Code Online (Sandbox Code Playgroud)

同样,在课堂之外,

SomeClass *obj = [SomeClass new];
obj->publicIvar = someObject;
Run Code Online (Sandbox Code Playgroud)

被翻译为:

SomeClass *obj = [SomeClass new];
*(obj + offsetOfPublicIvar) = someObject;
Run Code Online (Sandbox Code Playgroud)

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

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

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

@interface SomeClass () {
    id extensionIvar;
}
@end
Run Code Online (Sandbox Code Playgroud)

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

+-----+------------+-----+---------------+
| ... | otherIvars | ... | extensionIvar |
+-----+------------+-----+---------------+
Run Code Online (Sandbox Code Playgroud)

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

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

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

SomeClass *obj = [SomeClass new];
id privateValue = [obj valueForKey:@"privateIvar"];
Run Code Online (Sandbox Code Playgroud)

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

id extensionValue = [obj valueForKey:@"extensionIvar"];
Run Code Online (Sandbox Code Playgroud)

无论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);
Run Code Online (Sandbox Code Playgroud)

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

_OBJC_IVAR_$_SomeClass.extensionIvar
Run Code Online (Sandbox Code Playgroud)

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

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

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


Lil*_*ard 12

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

  • ....并且没有"匿名类别"这样的东西.这是OP问题中的"阶级延伸"; 非常不同的野兽而不是一个类别(虽然在特征的静脉中相似). (7认同)