常に自己の弱い参照をARCのブロックに渡しますか?


252

Objective-Cでのブロックの使用について少し混乱しています。私は現在ARCを使用しており、アプリには非常に多くのブロックがあり、現在は常にself弱い参照ではなく参照しています。これが、これらのブロックselfが割り当て解除されないように保持および保持する原因となる可能性がありますか?問題は、ブロック内のweak参照を常に使用する必要selfがあるかどうかです。

-(void)handleNewerData:(NSArray *)arr
{
    ProcessOperation *operation =
    [[ProcessOperation alloc] initWithDataToProcess:arr
                                         completion:^(NSMutableArray *rows) {
        dispatch_async(dispatch_get_main_queue(), ^{
            [self updateFeed:arr rows:rows];
        });
    }];
    [dataProcessQueue addOperation:operation];
}

ProcessOperation.h

@interface ProcessOperation : NSOperation
{
    NSMutableArray *dataArr;
    NSMutableArray *rowHeightsArr;
    void (^callback)(NSMutableArray *rows);
}

ProcessOperation.m

-(id)initWithDataToProcess:(NSArray *)data completion:(void (^)(NSMutableArray *rows))cb{

    if(self =[super init]){
        dataArr = [NSMutableArray arrayWithArray:data];
        rowHeightsArr = [NSMutableArray new];
        callback = cb;
    }
    return self;
}

- (void)main {
    @autoreleasepool {
        ...
        callback(rowHeightsArr);
    }
}

あなたは、このトピックの深さ談話で読みたい場合はdhoerl.wordpress.com/2013/04/23/...を
デビッド・H

回答:


721

ディスカッションの一部strongまたはweak一部に焦点を当てないことが役立ちます。代わりに、サイクル部分に焦点を当てます。

保持サイクルは、オブジェクトAがオブジェクトBを保持し、場合に発生ループでありそしていずれかのオブジェクトが解放された場合、オブジェクトBは、そのような状況ではオブジェクトAを保持します。

  • オブジェクトBはオブジェクトAへの参照を保持しているため、オブジェクトAは割り当て解除されません。
  • ただし、オブジェクトAがオブジェクトBを参照している限り、オブジェクトBは割り当て解除されません。
  • ただし、オブジェクトBはオブジェクトAへの参照を保持しているため、オブジェクトAは割り当て解除されません。
  • 無限

したがって、これらの2つのオブジェクトは、プログラムが正常に動作している場合は、割り当てを解除する必要がありますが、プログラムの存続期間中はメモリ内でハングします。

したがって、私たちが心配しているのは、保持サイクルであり、これらのサイクルを作成するブロック自体については何もありません。たとえば、これは問題ではありません。

[myArray enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop){
   [self doSomethingWithObject:obj];
}];

ブロックは保持されますがself、ブロックは保持されselfません。どちらかが解放された場合、サイクルは作成されず、すべての割り当てが解除されます。

問題が発生する場所は次のようなものです。

//In the interface:
@property (strong) void(^myBlock)(id obj, NSUInteger idx, BOOL *stop);

//In the implementation:
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [self doSomethingWithObj:obj];     
}];

これで、オブジェクト(self)はstrongブロックへの明示的な参照を持ちます。また、ブロックにはへの暗黙の強い参照がありselfます。これはサイクルであり、どちらのオブジェクトも適切に割り当て解除されます。

このような状況では、self 定義によりすでにstrongブロックへの参照が含まれているため、通常self、ブロックを使用するために明示的に弱い参照を作成することで解決するのが最も簡単です。

__weak MyObject *weakSelf = self;
[self setMyBlock:^(id obj, NSUInteger idx, BOOL *stop) {
  [weakSelf doSomethingWithObj:obj];     
}];

しかし、これは、を呼び出すブロックを処理するときに従うデフォルトのパターンであってはなりませんself。これは、他の方法では自己とブロックの間の保持サイクルを壊すためにのみ使用する必要があります。どこにでもこのパターンを採用すると、self割り当て解除後に実行されたものにブロックを渡すリスクが発生します。

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not retained!
  [weakSelf doSomething];
}];

2
AがBを保持、BがAを保持して無限サイクルを実行するかどうかはわかりません。参照カウントの観点から見ると、AとBの参照カウントは1です。この状況で保持サイクルが発生するのは、外部にAとBの強い参照を持つ他のグループがない場合です。つまり、これらの2つのオブジェクトに到達できません( Aを制御してBを解放したり、その逆を行ったりすることはできません)。
Danyun Liu 2013

@Danyun これらのオブジェクトへの他のすべての参照が解放されるまで、AとBの間の保持サイクルが回復不能ではないことは事実ですが、それによってサイクルが短くなることはありません。逆に、特定のサイクルが回復可能であるからといって、それをコードに含めても問題ないということではありません。保持サイクルは悪いデザインの匂いです。
jemmons 2013年

@jemmonsはい、できる限り保持サイクル設計を常に避ける必要があります。
Danyun Liu 2013

1
@マスター言うことは不可能です。-setCompleteionBlockWithSuccess:failure:メソッドの実装に完全に依存します。しかし、paginatorが所有していてViewController、これらのブロックViewControllerが解放された後に呼び出されない場合、__weak参照を使用すると安全に移動できます(selfブロックを所有するものを所有しているため、ブロックがそれを呼び出すときにまだ存在する可能性が高いため)彼らはそれを保持していませんが)。しかし、それは多くの「if」です。それは実際にこれが何をすることになっているかに依存します。
jemmons 2014年

1
@Jaiいいえ、これはブロック/クロージャのメモリ管理問題の中心にあります。オブジェクトは、何も所有していないときに割り当て解除されます。MyObjectそして、SomeOtherObjectの両方がブロックを所有しています。ブロックのリファレンスバックので、しかしMyObjectであるweak、ブロックはしません自分MyObject。そのため、ブロックがどちらか MyObject または SomeOtherObject存在するMyObject限り存在することが保証されていますが、ブロックが存在する限り存在する保証はありません。MyObject完全に割り当て解除できSomeOtherObject、ブロックがまだ存在している限り、ブロックはそのまま残ります。
jemmons 2015

26

常に弱い参照を使用する必要はありません。ブロックが保持されず、実行されてから破棄される場合、保持サイクルが作成されないため、自己を強くキャプチャできます。場合によっては、ブロックが完了するまでブロックを自己保持して、早期に割り当てを解除しないようにすることもできます。ただし、ブロックを強くキャプチャし、内部で自己をキャプチャすると、保持サイクルが作成されます。


さて、私はブロックをコールバックとして実行するだけで、自分自身の割り当てを解除したくありません。しかし、問題のビューコントローラーの割り当てが解除されないため、保持サイクルを作成しているように見えます...
the_critic

1
たとえば、View Controllerによって保持されるバーボタン項目があり、そのブロックで自分を強くキャプチャすると、保持サイクルが発生します。
レオナタン

1
@MartinE。コードサンプルで質問を更新してください。レオはまったく正しい(+1)で、必ずしも強い参照サイクルを発生させるわけではありませんが、これらのブロックの使用方法によっては発生することがあります。コードスニペットを提供していただくと、サポートが簡単になります。
Rob

@LeoNatan私は保持サイクルの概念を理解していますが、ブロックで何が起こるかよくわからないので、少し混乱します
the_critic

上記のコードでは、操作が完了してブロックが解放されると、セルフインスタンスが解放されます。ブロックがどのように機能し、何をいつキャプチャするかを確認する必要があります。
レオナタン

26

@jemmonsに完全に同意します。

しかし、これは、selfを呼び出すブロックを処理するときに従うデフォルトのパターンであってはなりません!これは、他の方法では自己とブロックの間の保持サイクルを壊すためにのみ使用されるべきです。どこにでもこのパターンを採用すると、自分自身の割り当てが解除された後に実行されたものにブロックを渡すリスクが発生します。

//SUSPICIOUS EXAMPLE:
__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  //By the time this gets called, "weakSelf" might be nil because it's not  retained!
  [weakSelf doSomething];
}];

この問題を克服するためweakSelfに、ブロックの内側に強い参照を定義することができます:

__weak MyObject *weakSelf = self;
[[SomeOtherObject alloc] initWithCompletion:^{
  MyObject *strongSelf = weakSelf;
  [strongSelf doSomething];
}];

3
strongSelfはweakSelfの参照カウントを増分しませんか?したがって、保持サイクルを作成しますか?
mskw

12
保持サイクルは、オブジェクトの静的状態で存在する場合にのみ重要です。コードが実行され、その状態が流動的である間、複数の、場合によっては冗長な保持が問題ありません。とにかく、このパターンに関しては、強い参照をキャプチャしても、ブロックが実行される前に自己割り当てが解除される場合は何も起こりません。ブロックの実行中に自分自身が割り当て解除されないようにします。これは、ブロック自体が非同期操作を実行し、そのためのウィンドウを提供する場合に重要です。
ピエールヒューストン

@smallduck、あなたの説明は素晴らしいです。今、私はこれをよりよく理解しています。本はこれをカバーしませんでした、ありがとう。
Huibin Zhang

5
これはstrongSelfの良い例ではありません。strongSelfの明示的な追加は、ランタイムがとにかく実行することとまったく同じであるためです。doSomething行では、メソッド呼び出しの間、強い参照が行われます。weakSelfがすでに無効化されている場合、strong refはnilであり、メソッド呼び出しは何もしません。strongSelfが役立つのは、一連の操作がある場合、またはメンバーフィールド(->)にアクセスしている場合で、実際に有効な参照が確実に取得され、操作のセット全体で継続的に保持されるようにします。例if ( strongSelf ) { /* several operations */ }
Ethan

19

Leoが指摘するように、あなたがあなたの質問に追加したコードは強い参照サイクル(別名、保持サイクル)を示唆しません。強い参照サイクルを引き起こす可能性がある操作関連の問題の1つは、操作が解放されない場合です。コードスニペットは、操作が並行であると定義していないことを示していますが、そうである場合isFinished、投稿したことがない場合、または循環依存関係がある場合などは解放されません。また、操作が解放されない場合、ビューコントローラーも解放されません。ブレークポイントを追加するかNSLog、オペレーションのdeallocメソッドに追加して、それが呼び出されることを確認することをお勧めします。

あなたが言った:

保持サイクルの概念は理解していますが、ブロックで何が発生するかはよくわからないので、少し混乱します

ブロックで発生する保持サイクル(強い参照サイクル)の問題は、よく知っている保持サイクルの問題と同じです。ブロックは、ブロック内に出現するオブジェクトへの強い参照を維持し、ブロック自体が解放されるまでそれらの強い参照を解放しません。したがって、ブロック参照がself、またはのインスタンス変数だけを参照する場合でもself、それが自身への強い参照を維持しますが、それはブロックが解放されるまで(またはこの場合はNSOperationサブクラスが解放されるまで)解決されません。

詳細については、「Objective-Cを使用したプログラミング:ブロックの操作」ドキュメントの「セルフキャプチャ時に強い参照サイクルを回避する」セクションを参照してください

それでもビューコントローラーが解放されない場合は、未解決の強参照が存在する場所を特定するだけです(NSOperation割り当てが解除されていることを確認した場合)。一般的な例は、繰り返しの使用ですNSTimer。または、delegate誤ってstrong参照を維持しているカスタムオブジェクトまたはその他のオブジェクト。Instrumentを使用して、オブジェクトが強い参照を取得している場所を追跡できます。例:

Xcode 6のレコード参照カウント

またはXcode 5では:

Xcode 5のレコード参照カウント


1
別の例としては、操作がブロッククリエーターに保持され、終了後に解放されない場合があります。素敵な記事を+1してください!
レオナタン

@LeoNatan同意。ただし、コードスニペットは、ARCを使用している場合に解放されるローカル変数としてそれを表します。しかし、あなたはまったく正しいです!
Robは

1
はい、OPが他の回答で要求したように、私は単に例を挙げていました。
レオナタン2013年

ちなみに、Xcode 8には「デバッグメモリグラフ」があります。これは、リリースされていないオブジェクトへの強い参照を見つけるためのさらに簡単な方法です。stackoverflow.com/questions/30992338/…を参照してください。
Rob

0

一部の説明では、保持サイクルに関する条件を無視します[オブジェクトのグループが強い関係のサークルによって接続されている場合、グループの外部からの強い参照がなくても、オブジェクトは互いに存続します。]詳細については、ドキュメントを参照してください。


-2

これは、ブロック内で自己を使用する方法です。

//ブロックの呼び出し

 NSString *returnedText= checkIfOutsideMethodIsCalled(self);

NSString* (^checkIfOutsideMethodIsCalled)(*)=^NSString*(id obj)
{
             [obj MethodNameYouWantToCall]; // this is how it will call the object 
            return @"Called";


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