Objective -Cの怠zyな読み込み - ゲッター内からセッターに電話する必要がありますか?
-
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;
}
まず、アクセサ内の別のアクセサラ機能にアクセスしても問題ないかどうかはわかりません(ただし、なぜそうではないのかわかりません)。しかし、セッターを通過せずにクラス変数を設定することは、セッターが何か特別なことをしている場合(またはプロパティが保持され、ゲッターがチェックされない以外のものに変更された場合)場合も同様に悪い可能性があるようです。
解決
どちらも実際には非常に脆弱であり、クラスのクライアントが何をしているかに応じて、まったく同一ではありません。それらを同じようにすることは十分に簡単です - 以下を参照 - しかし、それを壊れやすくすることは難しくなります。これが怠zyな初期化の価格です(そして、なぜ私は一般的にこの方法で怠zyな初期化を避けようとしているのです。
#1を使用すると、セッターを避けているため、変更を観察するものはすべて変更が見られません。 「観察」により、私は特にキー価値の観察(KVOを使用してUIを自動的に更新するココアバインディングを含む)について言及しています。
#2を使用すると、変更通知をトリガーし、UIを更新し、その他の場合はセッターが呼び出されたかのようになります。
どちらの場合も、オブジェクトの初期化がゲッターを呼び出す場合、無限の再帰の可能性があります。これには、オブザーバーが変更通知の一部として古い価値を要求する場合が含まれます。それをしないでください。
どちらの方法も使用する場合は、結果を慎重に検討してください。 1つは、プロパティの状態の変更が通知されず、もう1つはデッドロックの可能性があるため、アプリを一貫性のない状態にしたままにする可能性があります。
問題を完全に避ける方が良いです。下記参照。
検討してください(ごみ収集、標準のココアコマンドラインツール:
#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
観察のオプションのリストには、非常に垂れ下がっています。
私が以前に行ったコメントに戻る。最良の解決策は次のとおりです あなたのゲッター/セッターの一部として怠zyな初期化をしないでください. 。それはあまりにも細かい粒子です。オブジェクトグラフ状態をより高いレベルで管理する方がはるかに優れています。その一部として、基本的に「このサブシステムを使用する予定の状態移行があります! 「それは怠zyな初期化を行います。
他のヒント
これらの方法は決して同一ではありません。最初のものは正しいです、2番目のものは 間違い!ゲッターは決して電話をかけないかもしれません will/didChangeValueForKey:
したがって、セッターでもありません。そのプロパティが観察された場合、これは無限の再帰につながります。
また、メンバーが初期化されたときに観察する状態の変更はありません。あなたはあなたのオブジェクトをに尋ねます theObject
そして、あなたはそれを手に入れます。これが作成されるときは、実装の詳細であり、外の世界には関心がありません。
プロパティセッターメソッドが標準保持セッターであることがわかっている場合、それらは同じです。そうでない場合は、その操作中にセッターの他の動作を呼び出すべきかどうかを判断する必要があります。わからない場合は、セッターを使用するのが最も安全です。その動作は重要かもしれないからです。汗をかかないでください。
どちらも基本的に同一であり、あなたのケースに最適なものを選択するのは本当にあなた次第です。プロパティ構文の使用に関する長所/短所については、すでに説明しています。