168

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

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

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

ただの代わりに

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

私が知りたいのは次のことです:これが本当なら、醜さを避ける方法はありますか(GCを使用する以外に)?

4

9 に答える 9

170

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

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

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


補遺: ARC としてコンパイルする場合、__blockブレークはもはや循環を保持しません。ARC 用にコンパイルする場合は、代わりに__weakorを使用する必要があります。__unsafe_unretained

于 2010-12-04T08:14:24.070 に答える
68

使用するだけです:

__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;
于 2012-08-10T10:49:09.003 に答える
23

これは明らかかもしれませんがself、保持サイクルが発生することがわかっている場合にのみ、醜いエイリアスを実行する必要があります。ブロックが 1 回限りのものである場合は、 keep on を安全に無視できると思います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、長い目で見れば、エイリアシングがベスト プラクティスとして出てくるかもしれません。

于 2010-12-04T08:45:17.940 に答える
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;
} 
于 2011-08-18T14:01:25.390 に答える
9

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

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

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

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

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

そして最後に、このパターンはアラームを鳴らす必要があります。

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

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

于 2011-01-02T07:39:05.307 に答える
1

__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;
}];
于 2012-11-27T09:48:27.590 に答える
0

これはどう?

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

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

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

于 2012-05-16T13:32:38.930 に答える
-1

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

于 2012-11-02T03:10:08.920 に答える