这是一个小细节,但每次我懒得加载一些东西,我都会抓住它。这两种方法都可以接受吗?要么更好吗?假设变量具有retain属性。
方法#1
(AnObject *)theObject{
if (theObject == nil){
theObject = [[AnObject createAnAutoreleasedObject] retain];
}
return theObject;
}
方法#2
(AnObject *)theObject{
if (theObject == nil){
self.theObject = [AnObject createAnAutoreleasedObject];
}
return theObject;
}
首先,我不确定是否可以访问访问者中的另一个访问者功能(但不知道为什么不这样做)。但似乎设置类变量而不通过setter可能同样糟糕,如果setter执行一些特殊操作(或者如果属性更改为除了retain之外的某些内容并且未检查getter)。
答案 0 :(得分:17)
两者实际上都非常脆弱,并且根本不相同,这取决于班级的客户正在做什么。使它们相同是很容易的 - 见下文 - 但使它不那么脆弱更难。这就是延迟初始化的代价(为什么我通常会尝试以这种方式避免延迟初始化,更喜欢将子系统的初始化视为整个应用程序状态管理的一部分)。
使用#1,您将避开设置器,因此,观察到更改的任何内容都不会看到更改。通过“观察”,我特别指的是键值观察(包括Cocoa Bindings,它使用KVO自动更新UI)。
使用#2,您将触发更改通知,更新UI,否则就像调用setter一样。
在这两种情况下,如果对象的初始化调用getter,则可能会无限递归。这包括如果任何观察者要求将旧值作为更改通知的一部分。不要那样做。
如果您要使用任何一种方法,请仔细考虑后果。一个人有可能使应用程序处于不一致状态,因为属性的状态更改未通知而另一个可能存在死锁。
最好完全避免这个问题。见下文。
考虑(垃圾收集,标准Cocoa命令行工具:
#import <Foundation/Foundation.h>
@interface Foo : NSObject
{
NSString *bar;
}
@property(nonatomic, retain) NSString *bar;
@end
@implementation Foo
- (NSString *) bar
{
if (!bar) {
NSLog(@"[%@ %@] lazy setting", NSStringFromClass([self class]), NSStringFromSelector(_cmd));
[self willChangeValueForKey: @"bar"];
bar = @"lazy value";
[self didChangeValueForKey: @"bar"];
}
return bar;
}
- (void) setBar: (NSString *) aString
{
NSLog(@"[%@ %@] setting value %@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), aString);
bar = aString;
}
@end
@interface Bar:NSObject
@end
@implementation Bar
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context;
{
NSLog(@"[%@ %@] %@ changed\n\tchange:%@", NSStringFromClass([self class]), NSStringFromSelector(_cmd), keyPath, change);
}
@end
int main (int argc, const char * argv[]) {
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
Foo *foo = [Foo new];
Bar *observer = [Bar new];
CFRetain(observer);
[foo addObserver:observer forKeyPath:@"bar"
options: NSKeyValueObservingOptionPrior | NSKeyValueObservingOptionNew
context:NULL];
foo.bar;
foo.bar = @"baz";
CFRelease(observer);
[pool drain];
return 0;
}
这不会挂起。它喷出:
2010-09-15 12:29:18.377 foobar[27795:903] [Foo bar] lazy setting
2010-09-15 12:29:18.396 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed
change:{
kind = 1;
notificationIsPrior = 1;
}
2010-09-15 12:29:18.397 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed
change:{
kind = 1;
new = "lazy value";
}
2010-09-15 12:29:18.400 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed
change:{
kind = 1;
notificationIsPrior = 1;
}
2010-09-15 12:29:18.400 foobar[27795:903] [Foo setBar:] setting value baz
2010-09-15 12:29:18.401 foobar[27795:903] [Bar observeValueForKeyPath:ofObject:change:context:] bar changed
change:{
kind = 1;
new = baz;
}
如果你要将NSKeyValueObservingOptionOld
添加到观察选项列表中,它就会挂起。
回到我之前发表的评论;最好的解决方案是不要将懒惰初始化作为getter / setter的一部分。它太精细了。最好在更高级别管理对象图状态,并且作为其中一部分,状态转换基本上是“哟!我现在要使用这个子系统!温暖那个坏孩子! “这是懒惰的初始化。
答案 1 :(得分:3)
这些方法永远不会完全相同。第一个是正确的,而第二个是错误! getter可能永远不会调用will/didChangeValueForKey:
,因此也不会调用setter。如果观察到该属性,这将导致无限递归。
此外,成员初始化时没有状态变化。你向对象询问theObject
并得到它。创建它时是一个实现细节,而不关心外部世界。
答案 2 :(得分:1)
如果您知道属性setter方法是标准的保留setter,它们是相同的。如果没有,您需要决定是否应该在该操作期间调用setter的其他行为。如果您不知道,使用setter是最安全的,因为它的行为可能很重要。不要出汗。
答案 3 :(得分:0)
这两者基本相同,它真的由您决定哪一个最适合您的情况。您已经真正描述了使用属性语法的优缺点。