在不使用自引用的情况下设置 Objective-C 类属性

Posted

技术标签:

【中文标题】在不使用自引用的情况下设置 Objective-C 类属性【英文标题】:Setting an Objective-C class property without using a self reference 【发布时间】:2009-08-18 18:35:48 【问题描述】:

今天早上,我正在开发一个 iPhone 应用程序时遇到了崩溃,虽然我修复了这个错误,但我很好奇这是一个问题的语法原因。

这是我简化为简单元素的代码。我正在使用 NSArray 为项目填充 TableView 中的项目。 NSArray 是一个属性:

@interface FooViewController : UITableViewController 
    NSArray *stuff;


@property (nonatomic, retain) NSArray *stuff;

在我的实现文件中:

@synthesize stuff;

- (void)viewDidLoad      
    NSArray *arr = [[NSArray alloc] initWithObjects:@"", @"Item 1", @"Item 2",   
                                       @"Lorem", @"Ipsum", nil];
    self.stuff = arr;

    [arr release];

现在,当我第一次编写方法时,我不小心遗漏了“自我”。这导致了炸弹。尽管在测试时,它一开始就起作用了。我试过了:

stuff = arr;
NSLog(@"%d", [stuff count]);

但是在其他方法中使用 stuff 就被轰炸了。现在我已经解决了这个问题,我可以在其他地方使用 [stuff count]。

那么为什么我可以在某些地方使用 stuff 而在其他地方我必须使用 self.stuff

【问题讨论】:

【参考方案1】:

当您使用 (self) 和点语法时,根据您定义属性的方式(非原子,保留),将保留 NSArray(东西)。

如果不这样做,您仍在进行分配,但除了通过 alloc+init 隐式保留之外,您没有保留数组 - 并且您立即释放它。

你可以通过“self.stuff = arr”来绕过分配:

stuff = [arr retain];

但是由于您定义了一个属性,您显然希望使用点语法并为您调用保留。

【讨论】:

【参考方案2】:

这也可以正常工作:

- (void)viewDidLoad      
    stuff = [[NSArray alloc] initWithObjects:@"", @"Item 1", @"Item 2",   
                                       @"Lorem", @"Ipsum", nil];

因为数组被alloc保留了。但是,如果您有一个属性并使用自动释放数组创建方法,通常最好坚持使用点表示法,您可以从该属性中“免费”获得保留:

- (void)viewDidLoad      
    NSArray *arr = [NSArray arrayWithObjects:@"", @"Item 1", @"Item 2",   
                                       @"Lorem", @"Ipsum", nil];
    self.stuff = arr;


您可能只是为了简单起见而忽略了它,但您还需要在 dealloc 中释放这个数组:

- (void)dealloc      
  [stuff release]; stuff = nil;

【讨论】:

如果你要使用属性,那么你应该在任何地方使用它们,即dealloc的内容应该简单地写成: self.stuff = nil;避免出错的一种常见方法是将实例变量声明为: NSArray *_stuff;属性声明保持不变,但 synthesize 语句现在应如下所示:@synthesize stuff = _stuff;现在每次你尝试使用没有 self 的东西时,编译器都会标记一个错误。 实际上Apple建议在dealloc中直接释放类变量,而不是使用属性-这是因为如果您使用属性,您可能会触发KVC通知,或者可能会通过覆盖合成来启动其他副作用获取/设置方法。 感谢您的提示。我想知道这变得多么相关,尤其是因为可能不想要 KVO 触发?更不用说在现代运行时我们将必须使用带有合成实例变量的访问器。我想苹果正在确保他们的代码可以防止类似的事情发生。到目前为止,我的代码还没有遇到任何问题,但是现在我已经收到警告了,我会留意的 :-) 不希望 KVO 触发是一个有效的问题,Apple 表示直接在 init 中设置值并直接在 dealloc 中释放它们正是出于这个原因。由于这是在 viewDidLoad 中,我认为它可能更可取......尽管我可以看到想要像 init 一样对待它。如果应该随着现代运行时改变方法,我将不得不自己考虑更多......【参考方案3】:

stuff = ... 直接引用属性的支持字段。它不会增加保留计数。因此,在其他地方释放对象可能会将其保留计数减至零,并在您仍持有对它的引用时将其释放。此外,它可能会导致属性的先前值发生内存泄漏。 有时它看起来像工作的原因是该对象可能尚未被其他人释放。

另一方面,self.stuff = ... 将向属性的 set 访问器发送一条消息,该访问器将处理保留计数。

【讨论】:

【参考方案4】:

做的区别:

stuff=arr;

self.stuff=arr;

是在第二种情况下,您实际上是在调用自动​​合成的 setStuff: 访问器方法,该方法保留了数组。在您发布的代码中,数组是使用 alloc/initWithObjects 创建的,因此它的保留计数已经为 1。

您只需在 viewDidLoad: 方法中更改删除对 [arr release] 的调用,一切都会好起来的:

- (void)viewDidLoad      
    NSArray *arr = [[NSArray alloc] initWithObjects:@"", @"Item 1", @"Item 2",   
                                       @"Lorem", @"Ipsum", nil];
    stuff = arr;

正如您所注意到的,您也可以使用 self.stuff 来“修复”这个问题。我建议不要这样做,因为它掩盖了代码的含义,并添加了大多数情况下不需要的额外工作。一般来说,我建议不要使用“自我”。实例方法中的语法。

【讨论】:

那么,如果@property 有assign 语义,那么在访问前不使用self. 可以吗? 一般来说,是的。正如其他答案中提到的那样,进行直接属性访问也不会触发 KVO 观察者(这可能是可取的,也可能不是可取的,具体取决于)。

以上是关于在不使用自引用的情况下设置 Objective-C 类属性的主要内容,如果未能解决你的问题,请参考以下文章

Objective-C:在不可见的自定义 UITableViewCell 上获取属性的值

如何在不使用位置信息的情况下将 MCC + MNC 映射到 iOS Objective-C 中的用户电话国家代码?

swift - 在不重新加载自定义 UITableViewCell 的情况下设置 UILabel

在不创建新类的情况下访问自定义 UITableViewCell 中的 UITextField?

如何在不使用自动布局的情况下设置 UISegmentedControl 子类的高度?

在不依赖 app.config 的情况下使用 SOAP Web 服务