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

发布于 2024-09-19 00:21:26 字数 574 浏览 12 评论 0原文

这是一个小细节,但每次我延迟加载一些东西时,我都会陷入其中。这两种方法都可以接受吗?哪个更好?假设该变量具有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 做了一些特殊的事情(或者如果属性被更改为保留之外的东西并且 getter 没有被检查),那么在不通过 setter 的情况下设置类变量似乎同样糟糕。

This is a small detail but everytime I lazy load something I get caught up on it. Are both of these methods acceptable? Is either better? Assume that the variable has the retain property.

Method #1

(AnObject *)theObject{
    if (theObject == nil){
        theObject = [[AnObject createAnAutoreleasedObject] retain];
    }
    return theObject;
}

Method #2

(AnObject *)theObject{
    if (theObject == nil){
        self.theObject = [AnObject createAnAutoreleasedObject];
    }
    return theObject;
}

First, I'm not sure if it's OK to access another accessor function within an accessor (don't see why not, though). But it seems like setting the class variable without going through the setter could be equally bad if the setter does something special (or if the property is changed to something besides retain and the getter isn't checked).

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

爱给你人给你 2024-09-26 00:21:26

两者实际上都非常脆弱,而且完全不一样,具体取决于课程的客户在做什么。让它们完全相同很容易——见下文——但让它不那么脆弱就更难了。这就是延迟初始化的代价(也是为什么我通常会尝试以这种方式避免延迟初始化,而更喜欢将子系统的初始化视为整个应用程序状态管理的一部分)。

使用#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 的一部分,它的粒度太细了。级别,并且作为其中的一部分,有一个状态转换,基本上是“哟!我现在要使用这个子系统!让那个坏男孩暖起来!”。

Both are actually quite fragile and not at all identical, depending on what clients of the class are doing. Making them identical is easy enough -- see below -- but making it less fragile is harder. Such is the price of lazy initialization (and why I generally try to avoid lazy initialization in this fashion, preferring to treat initialization of subsystems as a part of overall application state management).

With #1, you are avoiding the setter and, thus, anything observing the change won't see the change. By "observing", I'm specifically referring to key-value observation (including Cocoa Bindings, which uses KVO to update the UI automatically).

With #2, you will trigger the change notification, updating the UI and otherwise exactly as if the setter was called.

In both cases, you have a potential for infinite recursion if the initialization of the object calls the getter. That includes if any observer asks for the old value as a part of the change notification. Don't do that.

If you are going to use either method, consider carefully the consequences. One has the potential to leave the app in an inconsistent state because a state change of a property did not notify and the other has the potential for deadlock.

Better to avoid the issue entirely. See below.


Consider (garbage collection on, standard Cocoa command line tool:

#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;
}

This does not hang. It spews:

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;
}

If you were to add NSKeyValueObservingOptionOld to the list of options for observation, it very much does hang.

Getting back to a comment I made earlier; the best solution is to not do lazy initialization as a part of your getter/setter. It is too fine grained. You are far better off managing your object graph state at a higher level and, as a part of that, have a state transition that is basically of the "Yo! I'm going to use this subsystem now! Warm that bad boy up!" that does the lazy initialization.

青春如此纠结 2024-09-26 00:21:26

这些方法从来都不相同。第一个是正确的,而第二个是错误! getter 可能永远不会调用 will/didChangeValueForKey: ,因此也不会调用 setter。如果观察到该属性,这将导致无限递归。

此外,在初始化成员时,无需观察任何状态更改。你向你的对象询问theObject并且你得到了它。当它被创建时是一个实现细节,与外界无关。

Those methods are never identical. The first one is right, while the second one is wrong! A getter may never call will/didChangeValueForKey: and therefore also not the setter. This will lead to infinite recursion if that property is observed.

And besides, there is no state change to observe when the member is initialized. You ask your object for the theObject and you get it. When this gets created is an implementation detail and no concern to the outside world.

城歌 2024-09-26 00:21:26

如果您知道属性设置器方法是标准保留设置器,那么它们是相同的。如果没有,您需要决定在该操作期间是否应该调用 setter 的其他行为。如果您不知道,使用 setter 是最安全的,因为它的行为可能很重要。别出汗。

If you know the property setter method is a standard retaining setter, they're identical. If not, you need to decide whether the setter's other behavior should be invoked during that operation. If you don't know, it's safest to use the setter, since its behavior may be important. Don't sweat it.

叫嚣ゝ 2024-09-26 00:21:26

两者基本相同,您只需选择最适合您的情况即可。您已经真正描述了使用属性语法的优点/缺点。

Both of those are basically identical, its really just up to you to choose which one is best for your case. You already really described the pros/cons about using the property syntax.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文