在不使用自引用的情况下设置 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?