代码之家  ›  专栏  ›  技术社区  ›  Jesse Anderson

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

  •  8
  • Jesse Anderson  · 技术社区  · 15 年前

    这是一个小细节,但每次我偷懒加载的东西,我会赶上它。这两种方法都可以接受吗?要么更好?假设变量具有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做了一些特殊的事情(或者属性被更改为retain以外的内容,而getter没有被检查),那么在不经过setter的情况下设置类变量似乎也同样糟糕。

    4 回复  |  直到 14 年前
        1
  •  17
  •   bbum    15 年前

    这两个类实际上都非常脆弱,完全不同,这取决于类的客户机在做什么。使它们完全相同很容易——见下文——但使其不那么脆弱却很难。这就是延迟初始化的代价(也是为什么我通常试图以这种方式避免延迟初始化,而更愿意将子系统的初始化作为整个应用程序状态管理的一部分)。

    有了#1,你就避免了设置者,因此,任何观察到变化的人都看不到变化。通过“观察”,我特别指的是键值观察(包括Cocoa绑定,它使用KVO自动更新UI)。

    在这两种情况下,如果对象的初始化调用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 在观察的选项列表中,它确实非常重要。

    回到我之前的评论;最好的解决办法是 . 太细了。你最好在更高的层次上管理你的对象图状态,作为其中的一部分,有一个基本上是“哟!我要用这个子系统了!把那个坏孩子热起来!”这就完成了延迟初始化。

        2
  •  3
  •   Sven    15 年前

    这些方法从不相同。第一个是对的,而第二个是对的 错误的 ! 一个能干的人可能永远不会打电话来 will/didChangeValueForKey: 因此也不是设定者。如果观察到该属性,这将导致无限递归。

    此外,在初始化成员时,没有要观察的状态更改。你要求你的目标 theObject 你明白了。当这个被创建的时候,它只是一个实现细节,与外界无关。

        3
  •  1
  •   David M.    15 年前

        4
  •  0
  •   Joshua Weinberg    15 年前

    这两个基本上是相同的,它真的只是由你来选择哪一个是最适合你的情况。您已经详细描述了使用属性语法的优缺点。