Objective -C中的懒惰加载 - 我应该从Getter内调用二传器吗?
-
02-10-2019 - |
题
这是一个小细节,但是每次我懒洋洋地加载东西都会陷入困境。这两种方法都可以接受吗?要么更好吗?假设该变量具有保留属性。
方法#1
(AnObject *)theObject{
if (theObject == nil){
theObject = [[AnObject createAnAutoreleasedObject] retain];
}
return theObject;
}
方法#2
(AnObject *)theObject{
if (theObject == nil){
self.theObject = [AnObject createAnAutoreleasedObject];
}
return theObject;
}
首先,我不确定是否可以访问登录器中的另一个访问器功能(尽管不明白为什么不)。但是,如果设置器做一些特别的事情(或者,如果将属性更改为保留且未检查getter,则似乎不经过设置器而不经过设置器的设置似乎同样不好。
解决方案
实际上,两者都非常脆弱,完全没有相同,具体取决于班级的客户在做什么。使它们相同非常容易 - 见下文 - 但是使其脆弱更加困难。这就是懒惰初始化的价格(以及为什么我通常试图以这种方式避免懒惰初始化,而宁愿将子系统的初始化视为整体应用状态管理的一部分)。
使用#1,您避免了二传手,因此,观察变化的任何内容都不会看到更改。通过“观察”,我专门指的是键值观察(包括使用KVO自动更新UI的可可绑定)。
使用#2,您将触发更改通知,更新UI,否则就完全像调用了设置器一样。
在这两种情况下,如果对象的初始化称为Getter,则您都有无限递归的潜力。其中包括任何观察者要求旧值作为变更通知的一部分。不要那样做。
如果要使用两种方法,请仔细考虑后果。一个人有可能将应用程序留在一个不一致的状态下,因为财产的变化没有通知,另一个财产有可能发生僵局。
最好完全避免问题。见下文。
考虑(垃圾收集,标准可可命令行工具:
#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的一部分. 。它太细粒度了。您会更好地管理对象图状态,并且作为其中的一部分,它的状态过渡基本上是“ Yo!我现在要使用此子系统!温暖那个坏男孩! “这可以做到懒惰的初始化。
其他提示
这些方法永远不会相同。第一个是对的,而第二个是 错误的呢getter可能永远不会打电话 will/didChangeValueForKey:
因此,也不是设定器。如果观察到该特性,这将导致无限递归。
此外,何时初始化成员时,没有任何状态更改可观察。您向您的对象询问 theObject
你明白了。创建时,是一个实施细节,对外界无关。
如果您知道属性设置器方法是标准固定设置器,则它们相同。如果没有,您需要确定在该操作期间是否应调用二传手的其他行为。如果您不知道,它最安全的使用设置器,因为它的行为可能很重要。不要流汗。
两者基本上都是相同的,实际上它仅仅由您选择哪种最适合您的情况。您已经真正描述了有关使用属性语法的利弊。