ブロックで「自己」のサイクルを保持する
この質問はかなり基本的なものではないかと思いますが、ブロックに入る多くのObjective-Cプログラマーに関係があると思います。
私が聞いたのは、ブロック内で参照されるローカル変数をconst
コピーとしてキャプチャするため、ブロック内でself
を使用すると、そのブロックがコピーされると保持サイクルが発生する可能性があるということです。したがって、__block
を使用して、ブロックをコピーする代わりにself
を直接処理するように強制する必要があります。
__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];
ただの代わりに
[someObject messageWithBlock:^{ [self doSomething]; }];
私が知りたいのは次のとおりです:これが本当であれば、さを回避する方法はありますか(GCを使用する以外に)?
厳密に言えば、それがconstコピーであるという事実は、この問題とは何の関係もありません。ブロックは、作成時にキャプチャされたobj-c値を保持します。 const-copyの問題の回避策は、retainの問題の回避策と同じです。つまり、変数に__block
ストレージクラスを使用します。
いずれにせよ、あなたの質問に答えるために、ここに本当の選択肢はありません。独自のブロックベースのAPIを設計していて、そうすることが理にかなっている場合、ブロックにself
の値を引数として渡すことができます。残念ながら、これはほとんどのAPIには意味がありません。
Ivarの参照にもまったく同じ問題があることに注意してください。ブロック内でivarを参照する必要がある場合は、代わりにプロパティを使用するか、bself->ivar
を使用します。
補遺:ARCとしてコンパイルする場合、__block
は保持サイクルを中断しなくなりました。 ARC用にコンパイルする場合は、代わりに__weak
または__unsafe_unretained
を使用する必要があります。
ただ使用する:
__weak id weakSelf = self;
[someObject someMethodWithBlock:^{
[weakSelf someOtherMethod];
}];
詳細情報:WWDC 2011-ブロックとGrand Central Dispatch in Practice。
https://developer.Apple.com/videos/wwdc/2011/?id=308
注:うまくいかない場合は試してください
__weak typeof(self)weakSelf = self;
これは明らかなことかもしれませんが、cycleいself
エイリアスを実行する必要があるのは、保持サイクルが得られることがわかっている場合だけです。ブロックが1回限りの場合は、self
の保持を安全に無視できると思います。たとえば、ブロックをコールバックインターフェイスとして使用している場合が悪いケースです。ここみたいに:
typedef void (^BufferCallback)(FullBuffer* buffer);
@interface AudioProcessor : NSObject {…}
@property(copy) BufferCallback bufferHandler;
@end
@implementation AudioProcessor
- (id) init {
…
[self setBufferCallback:^(FullBuffer* buffer) {
[self whatever];
}];
…
}
ここでは、APIはあまり意味がありませんが、たとえば、スーパークラスと通信する場合には意味があります。バッファハンドラを保持し、バッファハンドラが保持します。次のようなものと比較してください。
typedef void (^Callback)(void);
@interface VideoEncoder : NSObject {…}
- (void) encodeVideoAndCall: (Callback) block;
@end
@interface Foo : NSObject {…}
@property(retain) VideoEncoder *encoder;
@end
@implementation Foo
- (void) somewhere {
[encoder encodeVideoAndCall:^{
[self doSomething];
}];
}
これらの状況では、self
エイリアシングは行いません。保持サイクルはありますが、操作は短命であり、ブロックは最終的にメモリから抜け出し、サイクルを中断します。しかし、ブロックに関する私の経験は非常に小さく、self
エイリアシングが長期的にはベストプラクティスとして出てくる可能性があります。
これも私にとって問題だったので、別の回答を投稿します。もともと、ブロック内に自己参照がある場所ではblockSelfを使用しなければならないと考えていました。これは当てはまりません。オブジェクト自体にブロックが含まれている場合のみです。実際、これらの場合にblockSelfを使用すると、ブロックから結果を取得する前にオブジェクトがdeallocされ、呼び出しを試みるとクラッシュするため、明らかに応答まで自己を保持する必要があります戻ってくる。
最初のケースは、ブロック内で参照されるブロックが含まれているために保持サイクルが発生するタイミングを示しています。
#import <Foundation/Foundation.h>
typedef void (^MyBlock)(void);
@interface ContainsBlock : NSObject
@property (nonatomic, copy) MyBlock block;
- (void)callblock;
@end
@implementation ContainsBlock
@synthesize block = _block;
- (id)init {
if ((self = [super init])) {
//__block ContainsBlock *blockSelf = self; // to fix use this.
self.block = ^{
NSLog(@"object is %@", self); // self retain cycle
};
}
return self;
}
- (void)dealloc {
self.block = nil;
NSLog (@"ContainsBlock"); // never called.
[super dealloc];
}
- (void)callblock {
self.block();
}
@end
int main() {
ContainsBlock *leaks = [[ContainsBlock alloc] init];
[leaks callblock];
[leaks release];
}
2番目の場合、blockSelfは必要ありません。呼び出し元のオブジェクトには、selfを参照するときに保持サイクルを引き起こすブロックが含まれていないためです。
#import <Foundation/Foundation.h>
typedef void (^MyBlock)(void);
@interface BlockCallingObject : NSObject
@property (copy, nonatomic) MyBlock block;
@end
@implementation BlockCallingObject
@synthesize block = _block;
- (void)dealloc {
self.block = nil;
NSLog(@"BlockCallingObject dealloc");
[super dealloc];
}
- (void)callblock {
self.block();
}
@end
@interface ObjectCallingBlockCallingObject : NSObject
@end
@implementation ObjectCallingBlockCallingObject
- (void)doneblock {
NSLog(@"block call complete");
}
- (void)dealloc {
NSLog(@"ObjectCallingBlockCallingObject dealloc");
[super dealloc];
}
- (id)init {
if ((self = [super init])) {
BlockCallingObject *myobj = [[BlockCallingObject alloc] init];
myobj.block = ^() {
[self doneblock]; // block in different object than this object, no retain cycle
};
[myobj callblock];
[myobj release];
}
return self;
}
@end
int main() {
ObjectCallingBlockCallingObject *myObj = [[ObjectCallingBlockCallingObject alloc] init];
[myObj release];
return 0;
}
ブロックが参照している場合、保持サイクルが発生する可能性があることも忘れないでください 別の self
を保持するオブジェクト。
Garbage Collectionがこれらの保持サイクルに役立つかどうかはわかりません。ブロックを保持するオブジェクト(サーバーオブジェクトと呼びます)がself
(クライアントオブジェクト)よりも長い場合、ブロック内のself
への参照は、保持オブジェクト自体が循環するまで循環と見なされません。リリース。サーバーオブジェクトがそのクライアントよりはるかに長い場合、重大なメモリリークが発生する可能性があります。
クリーンなソリューションはないため、次の回避策をお勧めします。問題を解決するためにそれらを1つ以上選択してください。
- ブロックを使用するのは 完了、および無制限のイベント用ではありません。たとえば、
doSomethingAndWhenDoneExecuteThisBlock:
などのメソッドにはブロックを使用し、setNotificationHandlerBlock:
などのメソッドにはブロックを使用しません。完了に使用されるブロックには、明確な寿命があり、評価後にサーバーオブジェクトによって解放される必要があります。これにより、保持サイクルが発生した場合でも、保持サイクルが長すぎないようにします。 - あなたが説明したその弱参照ダンスをしてください。
- オブジェクトが解放される前にオブジェクトをクリーンアップするメソッドを提供します。このメソッドは、オブジェクトへの参照を保持しているサーバーオブジェクトからオブジェクトを「切断」します。オブジェクトのリリースを呼び出す前にこのメソッドを呼び出します。オブジェクトにクライアントが1つしかない場合(または特定のコンテキスト内でシングルトンである場合)、このメソッドはまったく問題ありませんが、複数のクライアントがある場合は機能しなくなります。ここでは、基本的に保持カウントメカニズムを無効にしています。これは、
dealloc
の代わりにrelease
を呼び出すことに似ています。
サーバーオブジェクトを作成している場合は、完了のためだけにブロック引数を取ります。 setEventHandlerBlock:
などのコールバックのブロック引数を受け入れないでください。代わりに、古典的なデリゲートパターンにフォールバックします。正式なプロトコルを作成し、setEventDelegate:
メソッドをアドバタイズします。デリゲートを保持しないでください。正式なプロトコルを作成したくない場合は、デリゲートコールバックとしてセレクターを受け入れます。
そして最後に、このパターンはアラームを鳴らします:
-(void)dealloc { [myServerObject releaseCallbackBlocksForObject:self]; ... }
self
内からdealloc
を参照している可能性のあるブロックをアンフックしようとしている場合、すでに問題が発生しています。 dealloc
は、ブロック内の参照に起因する保持サイクルのために呼び出されることはありません。つまり、サーバーオブジェクトが割り当て解除されるまで、オブジェクトは単純にリークします。
Libextobjcライブラリを使用できます。これは非常に人気があり、たとえばReactiveCocoaで使用されています。 https://github.com/jspahrsummers/libextobjc
@weakifyと@strongifyの2つのマクロが用意されているため、次のことができます。
@weakify(self)
[someObject messageWithBlock:^{
@strongify(self)
[self doSomething];
}];
これにより、直接的な強い参照が防止されるため、自己保持サイクルに陥ることはありません。また、自己が途中でゼロになることを防ぎますが、それでも適切に保持カウントを減らします。このリンクの詳細: http://aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html
Kevinの投稿 で提案されている__block __unsafe_unretained
修飾子は、異なるスレッドで実行されたブロックの場合、不正アクセス例外を引き起こす可能性があります。 temp変数には__ block修飾子のみを使用し、使用後はnilにすることをお勧めします。
__block SomeType* this = self;
[someObject messageWithBlock:^{
[this doSomething]; // here would be BAD_ACCESS in case of __unsafe_unretained with
// multithreading and self was already released
this = nil;
}];
これはどう?
- (void) foo {
__weak __block me = self;
myBlock = ^ {
[[me someProp] someMessage];
}
...
}
コンパイラの警告はもう表示されません。