私有@property是否创建@private实例变量?

发布于 2024-11-03 14:22:17 字数 594 浏览 4 评论 0原文

我读过 @synthesize会自动为@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;
}

I've read that @synthesize will automatically create corresponding instance variables for @property and that ivars are @protected by default. But, what if I use a class extension (like below) to indicate that the @property methods are to be private?

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

Will the corresponding ivar then be @private? Or should I explicitly declare it as @private like so?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

无所谓啦 2024-11-10 14:22:17

详细说明凯文的答案:

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

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

or

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,但限制更多。

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

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 的 Objective-C 2.0 和最新版本的 Clang/LLVM。

Ellaborating on Kevin’s answer:

When you declare a class, e.g.:

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

the compiler1 decides upon an instance variable layout for that class. This layout determines offsets of instance variables with regard to the address of instances of that class. One possible layout would be:

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

When an instance variable is referenced in code — either in the implementation of the class or in some other part of the code base, the compiler replaces that reference with the corresponding offset of the instance variable with regard to the address of the corresponding instance.

For example, in the implementation of SomeClass,

privateIvar = someObject;

or

self->privateIvar = someValue;

is translated as something like:

*(self + offsetOfPrivateIvar) = someObject;

Similarly, outside of the class,

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

is translated as something like:

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

However, the compiler only allows this according to the visibility of the instance variable:

  • A private instance variable can be referenced only in the implementation of the corresponding class;
  • A protected instance variable can be referenced only in the implementation of the corresponding class and its subclasses;
  • A public instance variable can be referenced anywhere.

When an instance variable is declared in a class extension, e.g.

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

the compiler adds it to the instance variable layout:

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

and any reference to that instance variable is replaced by its corresponding offset with regard to the instance. However, since that instance variable is only known to the implementation file where the class extension has been declared, the compiler won’t allow other files to reference it. An arbitrary source file can only reference instance variables it knows (respecting the visibility rules). If instance variables are declared in a header file that’s imported by a source file, then the source file (or, more accurately, the compiler whilst translating that unit) is aware of them.

On the other hand, an extension variable is only known by the source file where it was declared. We can thus say that instance variables declared in class extensions are hidden from other files. The same reasoning applies to backing instance variables of properties declared in class extensions. It’s similar to @private, but more restrictive.

Note, however, that at runtime visibility rules are not enforced. Using Key-Value Coding, an arbitrary source file can sometimes (the rules are described here) access an instance variable:

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

including an instance variable declared in an extension:

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

Regardless of KVC, access to instance variables be accomplished via the Objective-C runtime 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);

Note that a class can have more than one class extension. However, one class extension cannot declare an instance variable with the same name as another instance variable, including instance variables declared in other class extensions. Since the compiler emits symbols like:

_OBJC_IVAR_$_SomeClass.extensionIvar

for each instance variable, having different extensions declaring instance variables with the same name doesn’t yield a compiler error because a given source file is not aware of another source file, but it does yield a linker error.

1This layout can be changed by the Objective-C runtime. In fact, offsets are computed by the compiler and stored as variables, and the runtime can change them as needed.

PS: Not everything in this answer applies to all compiler/runtime versions. I’ve only considered Objective-C 2.0 with non-fragile ABI and recent versions of Clang/LLVM.

只有一腔孤勇 2024-11-10 14:22:17

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

@private instance variables are a compile-time-only feature. Given that the backing ivars for @property's are already hidden, @private doesn't do anything. So in essence, it's already @private.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文