【问题标题】:Does a private @property create an @private instance variable?私有@property 是否创建@private 实例变量?
【发布时间】:2011-08-12 16:41:10
【问题描述】:

我读过 @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;
}

【问题讨论】:

    标签: objective-c properties private instance-variables


    【解决方案1】:

    详细阐述凯文的回答:

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

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

    【讨论】:

    • 虽然不鼓励使用这些类型的 cmets:感谢您提供这个信息丰富、简洁且令人大开眼界的答案
    【解决方案2】:

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

    【讨论】:

    • .... 并且没有“匿名类别”之类的东西。这将是 OP 问题中的“类扩展”;与一个类别非常不同的野兽(尽管特征相似)。
    猜你喜欢
    • 2017-12-05
    • 2012-05-16
    • 2011-01-23
    • 2012-04-10
    • 2013-07-07
    • 1970-01-01
    • 1970-01-01
    • 2012-12-03
    • 1970-01-01
    相关资源
    最近更新 更多