如何声明实例变量和方法在类实例外部不可见或不可用?

13

我浏览了关于这个主题的一堆帖子,也许我没有找到“那一个”,但有人会指引我。问题很简单,答案可能也很简单。

如果你有两个实例变量,“public_ivar”和“private_ivar”,那么你应该在哪里/如何声明它们,以使公共部分是公共的,并且私有部分不会以任何方式暴露给查看头文件的任何人?

同样的问题也适用于“public_method”和“private_method”。

我喜欢干净的头文件(在其他语言中),只公开我希望别人看到的方法和实例变量。你应该能够发布你的头文件,并不会遇到有人访问它们不应该访问的内容的危险。如何在Objective-C中实现这一点呢?

例如,假设我决定使用一个实例变量来跟踪某些数据,计数器或类似的东西,在各种需要访问此信息的类方法之间。如果在@interface下通常声明这个实例变量,则它的存在被公开广告,并且可以被创建该类实例的任何人使用。理想情况是,这个实例变量在类实现之外根本不可见。

3个回答

17

你可以在类扩展中声明实例变量或已声明的属性。由于类扩展是在实现文件中声明的(即不在头文件中),因此它们不会对检查头文件的人可见。例如,在头文件中:

@interface SomeClass : NSObject
@end

在实现文件中:

@interface SomeClass ()
@property (nonatomic, assign) int privateInt;
@end

@implementation SomeClass

@synthesize privateInt;
…
@end

或者

@interface SomeClass () {
    int privateInt;
}
@end

@implementation SomeClass@end

请注意,运行时可以访问私有/类扩展实例变量(或在类扩展中声明的属性的访问器方法)。我曾经在Stack Overflow上回答另一个问题时写过一篇非常详细的文章:私有@property是否创建@private实例变量? 编辑:类扩展中的实例变量在WWDC 2010会议144中进行了介绍。 编辑:“使用Clang/LLVM 2.0编译器,您还可以在类扩展中声明属性和实例变量。”

http://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjectiveC/Chapters/ocCategories.html#//apple_ref/doc/uid/TP30001163-CH20-SW1


好的,我会看一下这个。我正在使用遗传算法和神经网络进行一些工作。类最终会有大量需要在内部使用的变量。其中很少有变量需要在头文件级别上可见。你的第一个实现文件示例是针对iOS4+的吗(因为你在@property语句之前没有声明int privateInt;)? - martin's
参考文献明确表示“类别中不允许使用ivars”,并在单独讨论类扩展时对此问题保持沉默。因此,可以安全地假设官方上是不允许在扩展中使用ivars的。然而,实际上似乎是可以的,这听起来很不错。@ughoavgfhw - jscs
@Josh @ugho 类扩展中的实例变量在WWDC 2010会议第144场演讲中被介绍,所以我想这是官方认可的。 - user557219
好的,我会去看一下。谢谢! - jscs
声明属性并不等同于声明实例变量。即使不建议使用实例变量,而是建议使用属性,但实例变量仍然可以在 @implementation {type var;} 中声明。 - Ekkstein
显示剩余10条评论

4
使用class extensions将其添加到实现文件中的类中。类扩展基本上是一个没有名称的类别,具有一些额外的优势:在其中声明的属性可以被合成,并且其中声明的任何内容都必须在主要实现中,因此编译器可以检查以确保您没有漏掉任何实现。必须将类扩展放在实现之前。您不能直接在类扩展中添加实例变量,但可以添加属性。当您为没有对应实例变量的属性合成访问器时,新的运行时(os x 10.5及更高版本和所有版本的iOS,我相信)将自动创建实例变量。这意味着您无法创建自己的访问器,除非您将实例变量放在头文件中。私有方法可以随意添加到类扩展中,但正如Anomie所指出的那样,如果您知道它们被称为什么,并且使用class-dump,则技术上可以使用它们,而且没有什么是安全的。
类扩展的用法示例:
@interface MyClass ()
@property (retain) id privateIvar;
@property (readwrite) id readonlyProperty; // bonus! class extensions can be used to make a property that is publicly readonly and privately readwrite
- (void)privateMethod;
@end
@implementation MyClass
@synthesize privateIvar; // the runtime will create the actual ivar, and we just access it through the property
- (void)privateMethod {
    ...
}
...

另一种在不将它们放在头文件或使用属性的情况下创建“实例变量”的方法是使用关联引用,它们在运行时向对象添加数据。它们并不严格等同于实例变量,并且它们的语法更加复杂。由于它们还需要新的运行时,因此您只有两个原因真正想要使用它们:您想在类别中添加实例变量(超出了本问题的范围),或者您需要它是非常非常私有。关联引用不会在编译代码中创建任何方法或添加到类的定义中,因此如果您不为它们创建包装器,则无法在添加数据后找到它们。请参见我链接的页面底部以获取完整的使用示例。


那么,如果你需要为一个通过此种方式“隐藏”的实例变量编写定制的 Setter 或 Getter,而不暴露该实例变量在头文件中,该怎么办呢? - martin's
@Martin:正如Bavarious所说,并且在评论中讨论过的,ivars现在在类扩展中得到支持,因此您可以在那里声明它,而不是在头文件中声明。您还可以使用关联引用并编写包装器作为访问器。 - ughoavgfhw

2
您可以使用@private来指定实例变量为私有。但是,没有办法使方法成为私有的。即使该方法未在头文件中列出,如果有人知道名称和参数,则可以调用它。

据我所了解,如果你有@property声明(设置器和访问器),@private实际上并没有起到什么作用。我甚至不希望ivar或私有成员函数出现在头文件中。基于这个假设,即使没有人知道它们的存在,它们也应该保持不可见(只是试图让诚实的人保持诚实,如果你想深入研究,你可以发现任何东西)。我只是认为在头文件中包含与应用程序的其他部分完全无关的所有这些东西是“肮脏”的。 - martin's
1
@Martin:属性不是实例变量。至于在头文件中隐藏实例变量,我可以想到两个选项:你可以只有一个指向其他地方声明的结构体的实例变量,或者你可以让所有实例都成为私有子类(类似于苹果使用类簇的方式)。 - Anomie
懒惰使用语言是有罪的。我知道@property只是创建基础的setter和getter代码。它还有其他作用吗? - martin's
@Martin:@property甚至不会创建底层的setter和getter。它告诉编译器foo.property语法应该使用getter和setter方法,并允许@synthesize工作。还有一些簿记工作,这样class_getProperty和其他低级方法才能发挥作用。 - Anomie

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接