objective-c - 私有(private)@property 是否创建@private 实例变量?

标签 objective-c properties private instance-variables

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

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

那么对应的ivar会不会是@private?或者我应该像这样将它显式声明为 @private 吗?

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

最佳答案

详细阐述凯文的回答:

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

@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;

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

  • 私有(private)实例变量只能在相应类的实现中被引用;
  • protected 实例变量只能在相应类及其子类的实现中被引用;
  • 可以在任何地方引用公共(public)实例变量。

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

@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。

关于objective-c - 私有(private)@property 是否创建@private 实例变量?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/5784875/

相关文章:

ios - 从 iOS 照片库 uri 获取 base64 照片

iOS iPhone 6 自动布局 - 全宽和全高

vb.net - VB.NET的表达主体成员吗?

c# - => 运算符在属性中意味着什么?

vb.net - 直接给对象赋值

php - PHP中可以使用私有(private)常量吗?

c++ - 类私有(private)成员 - 数组访问失败

ios - 如何在 GMSMapView 上实现 GMSMarker 拖放?

iphone - UIView 帧序列动画

swift - 如何使用私有(private)集扩展协议(protocol)?