Objective-C 中的属性和实例变量

Posted

技术标签:

【中文标题】Objective-C 中的属性和实例变量【英文标题】:Properties and Instance Variables in Objective-C 【发布时间】:2010-12-05 20:03:54 【问题描述】:

我对 感到很困惑。

Aaron Hillegass 的“Mac OS X 的 Cocoa 编程”已经完成了一半,一切都顺理成章。你可以像这样声明一个类:

@class Something;

@interface MyClass : NSObject 
    NSString *name;
    NSArray *items;

    Something *something;

    IBOutlet NSTextField *myTextField;


@property (nonatomic, retain) NSString *name;
@property (nonatomic, retain) NSArray *items;

由于其他对象需要操作我们的nameitems 实例变量,我们使用@property/@synthesize 为它们生成访问器/修改器。在我们的类中,我们不使用访问器/修改器——我们只是直接与实例变量交互。

something 只是我们将在我们的类中使用的一个实例变量,因为没有其他人需要使用它,所以我们不会为它创建一对访问器和修改器。

我们需要与 UI 中的文本字段进行交互,因此我们为它声明一个 IBOutlet,连接它,我们就完成了。

一切都很合乎逻辑。

但是,在 iPhone 世界中,情况似乎有所不同。人们为每个实例变量声明属性,为IBOutlets 声明属性,并使用访问器/修改器与类 的实例变量进行交互(例如,他们会写[self setName:@"Test"] 而不是name = @"Test") .

为什么?到底是怎么回事?这些差异是 iPhone 特有的吗?为所有实例变量声明属性、为IBOutlets 声明属性以及在您自己的类中使用访问器/修改器有什么优势?

【问题讨论】:

对于处于类似情况的任何人:除了下面的答案,请参阅 ***.com/questions/1221516/… 和 ***.com/questions/1250518/… 了解为什么要将 IBOutlets 声明为属性。 【参考方案1】:

在 iPhone 世界中,没有可用的垃圾收集器。您必须使用引用计数仔细管理内存。考虑到这一点,请考虑以下之间的区别:

name = @"Test";

self.name = @"Test";
// which is equivalent to:
[self setName: @"Test"];

如果您直接设置实例变量,而不事先考虑,您将丢失对先前值的引用并且您无法调整其保留计数(您应该手动设置released)。如果您通过属性访问它,它将自动为您处理,同时增加新分配对象的保留计数。

基本概念并非特定于 iPhone,但在没有垃圾收集器的环境中变得至关重要。

【讨论】:

...但是,在“Mac OS X 的 Cocoa 编程”中,Aaron Hillegass 不使用垃圾收集器...我们手动管理内存... 史蒂夫:我没看过指南。无论如何,如果您不使用属性并将其分配给实例变量anywhere,无论它是否在类中,您都必须注意引用计数。除此之外,Mac OS X 的框架比 iPhone 的框架更老。当 iPhone SDK 出现时,Objective-C 2.0 已经存在,导致属性的使用比 OS X 开发人员习惯的要多。总的来说,这主要是风格问题,而不是严格的规则。 @Mehrdad:好的,这是有道理的。谢谢! 只是古玩,现在我们有 ARC 也是这样吗? @meaning 确实如此。但是当我写答案时它没有。【参考方案2】:

属性用于为实例变量生成访问器,不会发生什么神奇的事情。

您可以手动实现相同的访问器。

您可以在 Aaron Hillegass 的书中找到成员变量的 3 种内存管理策略示例。他们是assign/copy/retain。您可以根据给定变量的需要选择其中之一。

我假设你了解 Objective-c 中的内存管理...

访问器隐藏了每个变量的内存管理的复杂性和差异。

例如:

name = @"Test"

是一个简单的赋值,name 现在持有对NSString @"Test" 的引用。但是,您可以决定使用copyretain。无论您选择哪个版本的内存管理访问器都隐藏了复杂性,并且您始终使用(或类似)访问变量:

[self setName:@"Test"] 
[self name]

现在setName: 可能会使用assign/copy or retain,您不必担心。

我的猜测是,iPhone 教程使用属性使新开发人员更容易跳过内存管理(尽管使用属性生成适当的访问器而不是每次都手动实现它们很方便)。

【讨论】:

好的,这也是有道理的。谢谢!【参考方案3】:

但是,在 iPhone 世界中,情况似乎有所不同。人们为每个实例变量声明属性,为 IBOutlets 声明属性,并使用访问器/修改器与类中的实例变量进行交互(例如,他们会写 [self setName:@"Test"] 而不是 name = @"Test")。

这不是 iPhone 特有的。除了在init 方法和dealloc 方法中,始终使用您的访问器是一个好习惯。主要的好处,尤其是在 Mac(带有 Cocoa 绑定)上,是使用访问器意味着免费的 KVO 通知。

人们“为每个实例变量声明属性”的原因很可能是他们所有的实例变量都是他们想要作为属性公开的东西。如果他们有想要保密的东西,他们不会在头文件中声明它的属性。 (但是,他们可能会在实现文件的类扩展中为其创建一个属性,以便获得上述免费的 KVO 通知。)

在我看来,为 outlets 声明属性太过分了。我看不出有什么意义。如果您不创建属性,nib 加载器将通过直接实例变量访问来设置出口,这对于该任务来说很好。

【讨论】:

根据developer.apple.com/mac/library/documentation/Cocoa/Conceptual/…,如果在 iPhone 上加载 nib 后不保留***对象,这些对象将被释放。在 OS X 桌面上,***对象的保留计数为 1。但使用属性(或至少 setter/getter)可以更清楚地了解正在发生的事情,这在 iPhone 上尤其是一个好主意。 我同意它明确保留,但在这种情况下,我认为没有必要——请记住,出口始终是一种拥有关系。 @Peter Hosey:谢谢!现在一切都开始变得有意义了。但是有一个问题:为什么不应该在 init 方法中使用访问器/修改器方法? Steve:因为你还没有完成初始化对象。因此,向自己发送访问器消息就是向部分初始化的对象发送消息。 dealloc 中的相同处理,只是相反:您已经开始解除分配对象,因此您将向部分解除分配的对象发送消息。 @Peter Hosey:是的,这是有道理的。谢谢!【参考方案4】:

我认为现代开发已经做出了非常强烈的尝试来识别、定义和应用最佳实践。

在这些最佳实践中,我们发现了连续性和一致性。

除了争论在initdealloc 方法中使用访问器之外,访问器通常应该一直使用(在类内部和外部),因为它们提供的好处包括encapsulation、多态var 实现(它们都允许抽象和重构)并促进那些连续性和一致性的最佳实践。当以这种方式做事并充分利用语言的能力时,面向对象语言的基本优势就会发挥作用。始终保持编码的一致性是一个经常被忽视的好处,任何高级程序员通常都会证明这一点。

【讨论】:

【参考方案5】:

你可以这样写

//MyClass.h

@class Something;

@interface MyClass : NSObject 

@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) NSArray *items;

@end 

//MyClass.m
@interface MyClass() 

@property (nonatomic, strong) IBOutlet NSTextField *myTextField;
@property (nonatomic, strong) Something *something;

@end

【讨论】:

以上是关于Objective-C 中的属性和实例变量的主要内容,如果未能解决你的问题,请参考以下文章

objective-C学习笔记数据成员:属性与实例变量

iOS 开发:成员变量(属性,实例变量)的相关知识

如何将实例变量或属性添加到 XCTestCase?

Objective-C 中的 switch 语句和实例变量分配

objective-C属性也定义为实例变量

知识梳理Objective-C中的@property