在Objective-C中延迟加载 – 我应该从getter中调用setter吗?

这是一个小细节,但每次我懒得加载一些东西,我会抓住它.这两种方法都可以接受吗?还是更好吗?假设变量具有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).

解决方法

两者实际上都非常脆弱,并且根本不相同,这取决于班级的客户正在做什么.使它们相同是很容易的 – 见下文 – 但使它不那么脆弱更难.这就是延迟初始化的代价(为什么我通常会尝试以这种方式避免延迟初始化,更喜欢将子系统的初始化视为整个应用程序状态管理的一部分).

使用#1,您可以避开setter,因此,观察到更改的任何内容都不会看到更改.通过“观察”,我特别指的是键值观察(包括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 %@",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:%@",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的一部分.它太精细了.在更高级别管理对象图状态会好得多,作为其中一部分,状态转换基本上是“哟!我现在要使用这个子系统!温暖那个坏孩子! “这是懒惰的初始化.

相关文章

本程序的编译和运行环境如下(如果有运行方面的问题欢迎在评...
水了一学期的院选修,万万没想到期末考试还有比较硬核的编程...
补充一下,先前文章末尾给出的下载链接的完整代码含有部分C&...
思路如标题所说采用模N取余法,难点是这个除法过程如何实现。...
本篇博客有更新!!!更新后效果图如下: 文章末尾的完整代码...
刚开始学习模块化程序设计时,估计大家都被形参和实参搞迷糊...