Objective-C中的延迟加载-我应该从getter中调用setter吗?
这是一个小细节,但每次我懒洋洋地加载一些东西时,我都会被它抓住。这两种方法都可以接受吗?要么更好?假设变量具有retain属性 方法#1 方法#2Objective-C中的延迟加载-我应该从getter中调用setter吗?,objective-c,memory-management,lazy-loading,lazy-initialization,Objective C,Memory Management,Lazy Loading,Lazy Initialization,这是一个小细节,但每次我懒洋洋地加载一些东西时,我都会被它抓住。这两种方法都可以接受吗?要么更好?假设变量具有retain属性 方法#1 方法#2 首先,我不确定是否可以访问访问器中的另一个访问器函数(不过,我不知道为什么不能)。但是,如果setter做了一些特殊的事情(或者如果属性更改为除retain之外的其他属性,并且没有检查getter),那么在不经过setter的情况下设置类变量似乎同样糟糕。这两个属性基本上是相同的,这实际上取决于您选择哪一个最适合您的情况。您已经详细描述了使用属性语法
首先,我不确定是否可以访问访问器中的另一个访问器函数(不过,我不知道为什么不能)。但是,如果setter做了一些特殊的事情(或者如果属性更改为除retain之外的其他属性,并且没有检查getter),那么在不经过setter的情况下设置类变量似乎同样糟糕。这两个属性基本上是相同的,这实际上取决于您选择哪一个最适合您的情况。您已经详细描述了使用属性语法的优缺点。如果您知道属性setter方法是标准的保留setter,那么它们是相同的。如果没有,则需要决定是否在该操作期间调用setter的其他行为。如果您不知道,使用setter是最安全的,因为它的行为可能很重要。别担心。这两个类实际上都很脆弱,完全不同,这取决于类的客户机在做什么。使它们完全相同很容易——见下文——但使其不那么脆弱却很难。这就是延迟初始化的代价(也是为什么我通常试图以这种方式避免延迟初始化,更愿意将子系统的初始化作为整个应用程序状态管理的一部分) 使用#1时,您避开了setter,因此,任何观察到变化的人都不会看到变化。通过“观察”,我特别指的是键值观察(包括Cocoa绑定,它使用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;
}
如果要将NSKeyValueObservingOptionOld
添加到观察选项列表中,它确实会挂起
回到我前面的评论;最好的解决方案是不要将惰性初始化作为getter/setter的一部分。这太细粒度了。您最好在更高的级别上管理对象图状态,并且作为其中的一部分,有一个基本上是相同的状态转换“哟!我现在就要用这个子系统了!把那个坏孩子热起来!“这就是延迟初始化。这些方法从来都不相同。第一个方法是正确的,而第二个方法是错误的。!getter可能永远不会调用
will/didChangeValueForKey:
,因此也不会调用setter。如果观察到该属性,这将导致无限递归。”
此外,当成员初始化时,没有要观察的状态更改。您向对象请求
对象
,您就得到了它。创建该对象时是一个实现细节,与外部世界无关。实际上,在懒洋洋地初始化对象时,没有要观察的更改,所以这个答案是不正确的。H呃?它是否是惰性初始化的并不重要。如果我对theObject
设置了一个KVO观察,并将self.theObject=…
放在getter中,那么KV观察将触发。“惰性初始化”只是一个模式的名称;编译器和运行时都不知道它。是的,观察将触发并以infinte递归结束。setter在更改任何内容之前调用willChangeValueForKey:
。willChangeValueForKey:
将再次调用setter,后者(因为ivar仍然为零)将再次调用setter,以此类推。这是错误的技术原因。但从概念上讲,期望调用setter并生成通知也是错误的。这种惰性初始化的要点是,从外部代码的角度来看,对象总是存在的。不完全如此。您只需要如果你在观察器中请求旧值,则在无限递归中递增。从内存管理的角度来看,这可能是相同的。但是setter不仅仅负责内存管理,还需要考虑KVO。这使得方法#2错了!如果在调用getter之前存在观察器,那么就有了ge在不触发观察通知的情况下修改状态将使应用程序处于不一致的状态。不,设置绑定将始终调用getter。没有办法使观察者处于不一致的状态。啊--是的--我明白你的意思。但是仍然不一致,因为缺少通知意味着更改后的状态不会触发与观察相关的任何行为。UI可能永远不会不一致,但除了UI更新之外,可能还有其他操作会出现问题。就外部世界而言,状态没有更改。如果对象不是惰性初始化的,而是在-init
方法中创建的,则也会通知观察者。不,就外部世界而言,这不是一个状态更改。只有该类本身才能知道ivar是nil
。就任何其他人而言,此属性永远不会nil
。如果其他代码只是假设它没有
(AnObject *)theObject{
if (theObject == nil){
self.theObject = [AnObject createAnAutoreleasedObject];
}
return theObject;
}
#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;
}