ブロックで「自己」のサイクルを保持


167

この質問はかなり基本的なものだと思いますが、ブロックに入っている多くのObjective-Cプログラマーに関係があると思います。

私が聞いたことは、ブロックはそれらとして参照されるローカル変数をconstコピーとしてキャプチャselfするため、ブロック内で使用すると、そのブロックがコピーされた場合に保持サイクルが発生する可能性があるということです。したがって、__blockブロックをselfコピーするのではなく、ブロックを強制的に直接処理するために使用することになっています。

__block typeof(self) bself = self;
[someObject messageWithBlock:^{ [bself doSomething]; }];

ただの代わりに

[someObject messageWithBlock:^{ [self doSomething]; }];

私が知りたいのは次のとおりです。これがtrueの場合、醜さを回避できる方法はありますか(GCの使用は別として)?


3
物事をselfひっくり返すためthisだけにプロキシを呼び出すのが好きです。JavaScriptでは、thisクロージャーを呼び出しますself。そのため、快適でバランスが取れています。:)
devios1 2013

私がSwiftブロックを使用している場合、同等のアクションを実行する必要があるのでしょうか
Ben Lu

@BenLu絶対に!Swiftクロージャー(および自分自身について暗黙的または明示的に言及されて渡される関数)では、自分自身を保持します。これが望ましい場合もあれば、サイクルが作成される場合もあります(クロージャ自体が自己によって所有される(または何かが所有するため)。これが発生する主な理由は、ARCが原因です
イーサン

1
問題を回避するために、ブロックで使用される 'self'を定義する適切な方法は、 '__ typeof(self)__weak weakSelf = self;'です。弱い参照を持つために。
XLE_22 2017

回答:


169

厳密に言えば、それがconstコピーであるという事実は、この問題とは何の関係もありません。ブロックは、作成時にキャプチャされたobj-c値を保持します。const-copyの問題の回避策は、保持の問題の回避策と同じです。つまり、__block変数のストレージクラスを使用します。

いずれにせよ、あなたの質問に答えるために、ここには本当の選択肢はありません。独自のブロックベースのAPIを設計していて、それを行うことが理にかなっている場合は、ブロックにself引数としてin の値を渡すことができます。残念ながら、これはほとんどのAPIには意味がありません。

ivarを参照してもまったく同じ問題があることに注意してください。ブロック内のivarを参照する必要がある場合は、代わりにプロパティを使用するか、を使用してくださいbself->ivar


補遺:ARCとしてコンパイルするとき、__block保持サイクルが中断されなくなりました。ARC用にコンパイルする場合は、__weakまたはを使用する必要があります__unsafe_unretained


問題ない!これでご満足いただけると思いますので、正解としてお答えいただければ幸いです。そうでない場合は、私があなたの質問によりよく答えられる方法を教えてください。
Lily Ballard

4
問題ありません、ケビン。SOは質問に対する回答の選択をすぐに遅らせるので、少し後で戻ってくる必要がありました。乾杯。
ジョナサンスターリング

__unsafe_unretained id bself = self;
caleb

4
@JKLaiho:もちろん__weakです。ブロックが呼び出されたときにオブジェクトがスコープの外に出てはいけないという事実を知っている場合__unsafe_unretainedは、少し速くなりますが、一般的には違いはありません。を使用する場合は__weak、それを__strongローカル変数にスローし、nil何かを実行する前に、それがnon- かどうかをテストしてください。
リリーバラード

2
@Rpranata:はい。__blockの保持と解放を行わないことによる副作用は、純粋にそれを適切に推論できないことによるものです。ARCを使用すると、コンパイラーはその機能を獲得し、__block現在は保持およびリリースされます。それを回避する必要がある場合は__unsafe_unretained、を使用する必要があります。これは、変数の値に対して保持または解放を実行しないようにコンパイラーに指示します。
リリーバラード

66

ただ使用する:

__weak id weakSelf = self;

[someObject someMethodWithBlock:^{
    [weakSelf someOtherMethod];
}];

詳細:WWDC 2011- Blocks and Grand Central Dispatch in Practice

https://developer.apple.com/videos/wwdc/2011/?id=308

注:それがうまくいかない場合は、試すことができます

__weak typeof(self)weakSelf = self;

2
そして、あなたはたまたまそれを見つけましたか:)?
Tieme

2
あなたはここに動画をチェックすることができます- developer.apple.com/videos/wwdc/2011/...
nanjunda

「someOtherMethod」内で自分自身を参照できますか?その時点で弱点を自己参照するか、それとも保持サイクルを作成するか?
オーレン2015年

こんにちは@Oren、「someOtherMethod」内で自分自身を参照しようとすると、Xcode警告が表示されます。私のアプローチは、自己の弱参照を作るだけです。
3lvis 2015年

1
ブロック内で自分自身を直接参照する場合にのみ警告が表示されました。someOtherMethod内に自己を配置しても、警告は発生しませんでした。それはxcodeが十分にスマートではないためですか、それとも問題ではありませんか?someOtherMethod内でselfを参照すると、weakSelfが参照されますか?
Oren

22

これは明白かもしれませんが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エイリアシングは長期的にはベストプラクティスとして出てくるかもしれません。


6
いい視点ね。自己がブロックを存続させている場合、それは保持サイクルにすぎません。決してコピーされないブロック、または保証された期間が制限されているブロック(たとえば、UIViewアニメーションの完了ブロック)の場合、それについて心配する必要はありません。
Lily Ballard、

6
原則として、あなたは正しいです。ただし、例のコードを実行すると、クラッシュします。ブロックプロパティは常にではcopyなくとして宣言する必要がありますretain。それらが単にretainである場合、それらがスタックから移動される保証はありません。つまり、実行しようとすると、それはもう存在しません。(そしてコピーと既にコピーされたブロックは保持のために最適化されています)
Dave DeLong

確かに、タイプミスです。私はretain少し前にフェーズを通過しましたが、あなたの言っていることがすぐにわかりました:)ありがとう!
zoul

私はretainブロックについては完全に無視されると確信しています(それらがすでにスタックから移動していない限りcopy)。
スティーブンフィッシャー

@Daveデロング、ノー@propertyは(保持)のみオブジェクト参照ではなく、ブロックに使用されているので、それがクラッシュしないでしょう...ここにすべてで使用するコピーの必要はありません...
DeniziOS

20

これも私にとって問題だったので、別の回答を投稿します。私は元々、ブロック内に自己参照がある場所ではblockSelfを使用する必要があると思っていました。これは当てはまりません。オブジェクト自体にブロックがある場合のみです。実際、これらの場合にblockSelfを使用すると、ブロックから結果が返される前にオブジェクトの割り当てが解除され、それを呼び出そうとするとオブジェクトがクラッシュするため、応答までselfを保持する必要があることは明らかです。戻ってくる。

最初のケースは、ブロックで参照されるブロックが含まれているために保持サイクルが発生する場合を示しています。

#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この修正を過度に適用したためではない可能性があるためです。これは、投稿してくれてありがとう、非ARCコードで保持サイクルを回避する良い例です。
Carl Veazey、2013

9

また、ブロックが別のオブジェクトを参照し、そのオブジェクトが保持する場合、保持サイクルが発生する可能性があることも覚えておいてくださいself

ガベージコレクションがこれらの保持サイクルに役立つかどうかはわかりません。ブロックを保持しているオブジェクト(サーバーオブジェクトと呼びます)が存続selfする場合(クライアントオブジェクト)、参照selfブロック内は、保持するオブジェクト自体が解放されるまで循環とは見なされません。サーバーオブジェクトがそのクライアントよりも長生きする場合は、重大なメモリリークがある可能性があります。

クリーンな解決策はないので、次の回避策をお勧めします。問題を解決するために、1つ以上を自由に選択してください。

  • ブロックは完了のみに使用し、制限のないイベントには使用しないでください。たとえば、のようなメソッドにはブロックを使用しdoSomethingAndWhenDoneExecuteThisBlock:setNotificationHandlerBlock:。完了に使用されるブロックには明確な有効期限があり、評価後にサーバーオブジェクトによって解放される必要があります。これにより、保持サイクルが発生しても、保持サイクルが長くなりすぎるのを防ぎます。
  • あなたが説明したその弱参照ダンスをしてください。
  • リリース前にオブジェクトをクリーンアップするメソッドを提供します。これにより、オブジェクトへの参照を保持している可能性のあるサーバーオブジェクトからオブジェクトが「切断」されます。オブジェクトのリリースを呼び出す前にこのメソッドを呼び出します。このメソッドは、オブジェクトにクライアントが1つしかない(またはコンテキスト内のシングルトンである)場合は完全に問題ありませんが、クライアントが複数ある場合は機能しなくなります。ここでは基本的に、保持カウントメカニズムを無効にします。これはではdeallocなくを呼び出すのと同じですrelease

サーバーオブジェクトを記述している場合は、完了のためだけにブロック引数をとります。などのコールバックのブロック引数を受け入れないでくださいsetEventHandlerBlock:。代わりに、古典的なデリゲートパターンに戻ります。正式なプロトコルを作成し、setEventDelegate:メソッドをアドバタイズします。デリゲートを保持しないでください。正式なプロトコルを作成したくない場合でも、セレクターをデリゲートコールバックとして受け入れます。

そして最後に、このパターンはアラームを鳴らすはずです:

-(void)dealloc {
    [myServerObject releaseCallbackBlocksForObject:self];
    ...
}

self内部から参照する可能性のあるブロックのフックを解除しようとしている場合はdealloc、既に問題が発生しています。deallocブロック内の参照によって引き起こされる保持サイクルのために呼び出されることはありません。つまり、サーバーオブジェクトが割り当て解除されるまでオブジェクトはリークするだけです。


GCは、__weak適切に使用すれば役立ちます。
tc。

もちろん、ガベージコレクションのトレースでは、保持サイクルを処理できます。唯一の参照カウント環境のための問題であるサイクルを維持する
newacctは

誰もが知っているように、ガベージコレクションはOS X v10.8で廃止され、自動参照カウント(ARC)が採用されたため、OS Xの将来のバージョン(developer.apple.com/library/mac/#releasenotesで削除される予定です)/ ObjectiveC /…)。
Ricardo Sanchez-Saez 2013

1

__block __unsafe_unretainedKevinの投稿で提案されている修飾子は 、別のスレッドでブロックが実行された場合に、不正アクセス例外を引き起こす可能性があります。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;
}];

変数の使用後に変数をnillする必要をなくすために、__ blockの代わりに__weakを使用する方が本当に安全ではないでしょうか?つまり、このソリューションは、他のタイプのサイクルを中断したい場合に最適ですが、「自己」保持サイクルの特定の利点は確かにわかりません。
ale0xB 2012年

プラットフォームターゲットがiOS 4.xの場合、__ weakは使用できません。また、ブロック内のコードがnilではなく有効なオブジェクトに対して実行されている必要がある場合もあります。
b1gbr0

1

libextobjcライブラリを使用できます。それは非常に人気があり、たとえばReactiveCocoaで使用されます。 https://github.com/jspahrsummers/libextobjc

@weakifyと@strongifyの2つのマクロが用意されているため、次のことが可能です。

@weakify(self)
[someObject messageWithBlock:^{
   @strongify(self)
   [self doSomething]; 
}];

これは直接の強い参照を防ぐので、自己の保持サイクルに入りません。また、途中でnilになるのを防ぎますが、保持カウントは適切に減分されます。このリンクの詳細:http : //aceontech.com/objc/ios/2014/01/10/weakify-a-more-elegant-solution-to-weakself.html


1
簡略化されたコードを表示する前に、背後にあるものを知っておくとよいでしょう。誰もが実際の2行のコードを知っている必要があります。
Alex Cio 2015

0

これはどう?

- (void) foo {
     __weak __block me = self;

     myBlock = ^ {
        [[me someProp] someMessage];
     }
     ...
 }

コンパイラの警告が表示されなくなりました。


-1

ブロック:ブロックで参照されるブロックが含まれているため、保持サイクルが発生します。ブロックコピーを作成してメンバー変数を使用すると、自分自身が保持されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.