このブロックで自己を強くキャプチャすると、保持サイクルにつながる可能性があります


207

xcodeでこの警告を回避するにはどうすればよいですか。これがコードスニペットです。

[player(AVPlayer object) addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
queue:nil usingBlock:^(CMTime time) {
    current+=1;

    if(current==60)
    {
        min+=(current/60);
        current = 0;
    }

    [timerDisp(UILabel) setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];///warning occurs in this line
}];

あるtimerDispクラスのプロパティには?
Tim

はい、@ property(nonatomic、strong)UILabel * timerDisp;
user1845209 2013年

2
これは何ですか:player(AVPlayer object)そしてtimerDisp(UILabel)
Carl Veazey、2013年

AVPlayer * player; UILabel * timerDisp;
user1845209

5
本当の質問は、循環参照が壊れることがわかっている場合(たとえば、ネットワークリクエストの終了時に常に参照をクリアする場合)、自己に不要な弱い参照を付けずにこの警告を抑制する方法です。
Glenn Maynard

回答:


514

ここでのキャプチャはself、の暗黙的なプロパティアクセスで行われself.timerDispます- によって強く保持されるブロック内から、selfまたはselfからプロパティを参照することはできませんself

ブロック内selfにアクセスする前にへの弱参照を作成することで、これを回避できtimerDispます。

__weak typeof(self) weakSelf = self;
[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                     queue:nil
                                usingBlock:^(CMTime time) {
                                                current+=1;

                                                if(current==60)
                                                {
                                                    min+=(current/60);
                                                    current = 0;
                                                }

                                                 [weakSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
                                            }];

13
__unsafe_unretained代わりに使用してみてください。
Tim

63
解決しました。代わりにこれを使用してください:__unsafe_unretained typeof(self)weakSelf = self; @Timのヘルプに感謝
user1845209

1
良い答えですが、「自己によって強く保持されるブロック内から自己または自己のプロパティを参照することはできません」と言って、小さな問題を取り上げます。これは厳密には当てはまりません。以下の私の答えを見てください。「自己について言及する場合十分に注意する必要あります...」
Chris Suter 2013年

8
OPのコードに保持サイクルがありません。ブロックはによって強く保持されるのselfではなく、メインディスパッチキューによって保持されます。私が間違っている?
erikprice 2013年

3
@erikprice:あなたは間違っていません。私は質問を、保持サイクルの実際の存在ではなく、主にXcodeが提示するエラー(「xcodeでこの警告を回避するにはどうすればよいですか」)に関するものと解釈しました。保持サイクルがないことは、提供されたスニペットOPからのみ明らかであると言うのは正しいです。
Tim

52
__weak MyClass *self_ = self; // that's enough
self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
    if (!error) {
       [self_ showAlertWithError:error];
    } else {
       self_.items = [NSArray arrayWithArray:receivedItems];
       [self_.tableView reloadData];
    }
};

そして、覚えておかなければならない非常に重要なことの1つは、ブロックでインスタンス変数を直接使用せず、弱いオブジェクトのプロパティとして使用することです。サンプル:

self.loadingDidFinishHandler = ^(NSArray *receivedItems, NSError *error){
        if (!error) {
           [self_ showAlertWithError:error];
        } else {
           self_.items = [NSArray arrayWithArray:receivedItems];
           [_tableView reloadData]; // BAD! IT ALSO WILL BRING YOU TO RETAIN LOOP
        }
 };

そして、忘れずに:

- (void)dealloc {
    self.loadingCompletionHandler = NULL;
}

誰も保持していないオブジェクトの弱いコピーを渡すと、別の問題が発生する可能性があります。

MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
    [vcToGo_ doSomePrecessing];
};

場合はvcToGo、割り当て解除し、このブロックは解雇され、私はあなたが含まれているゴミ箱に認識されていないセレクタでクラッシュを取得すると信じてvcToGo_、今変数を。それを制御してみてください。


3
これも説明すれば、これはより強力な答えになります。
エリックJ.

43

より良いバージョン

__strong typeof(self) strongSelf = weakSelf;

ブロックの最初の行として、その弱いバージョンへの強い参照を作成します。ブロックが実行を開始したときにまだ自己が存在し、nilにフォールバックしていない場合、この行は、ブロックの実行ライフタイム全体で永続することを保証します。

したがって、全体は次のようになります。

// Establish the weak self reference
__weak typeof(self) weakSelf = self;

[player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(0.1, 100)
                                 queue:nil
                            usingBlock:^(CMTime time) {

    // Establish the strong self reference
    __strong typeof(self) strongSelf = weakSelf;

    if (strongSelf) {
        [strongSelf.timerDisp setText:[NSString stringWithFormat:@"%02d:%02d",min,current]];
    } else {
        // self doesn't exist
    }
}];

私はこの記事を何度も読んだことがあります。これは、Erica Sadunによる、ブロックとNSNotificationCenterを使用するときに問題を回避する方法に関する 優れた記事です。


迅速な更新:

たとえば、swiftでは、successブロックを使用した単純なメソッドは次のようになります。

func doSomeThingWithSuccessBlock(success: () -> ()) {
    success()
}

このメソッドを呼び出しself、成功ブロックで使用する必要がある場合。[weak self]とのguard let機能を使用します。

    doSomeThingWithSuccessBlock { [weak self] () -> () in
        guard let strongSelf = self else { return }
        strongSelf.gridCollectionView.reloadData()
    }

このいわゆる強い弱いダンスは、人気のオープンソースプロジェクトで使用されていAlamofireます。

詳細については、swift-style-guideをご覧ください。


あなたがやったらどうtypeof(self) strongSelf = self;(代わりに__weakの)ブロックの外側には、ブロックの中で述べてstrongSelf = nil;使用後?あなたの例では、ブロックが実行されるまでにweakSelfがnilにならないようにする方法がわかりません。
Matt

保持サイクルの可能性を回避するために、コードでselfを使用するブロックの外部に弱い自己参照を確立します。方法では、ブロックが実行されることを確認する必要があります。urコードの別のブロックは、今まで保持されていたurを解放する責任があります。
Warif Akhand Rishi 2014

@Mattこの例の目的は、weakSelfを保持することではありません。目的は、weakSelfがnilでない場合、ブロック内で強い参照を作成することです。したがって、ブロックがselfで実行を開始すると、ブロック内でselfがnilになることはありません。
Warif Akhand Rishi 2014

15

別の答えでは、ティムは言った:

自分自身によって強く保持されるブロック内から自分自身または自分自身のプロパティを参照することはできません。

これは真実ではありません。ある時点でサイクルを断ち切る限り、これを行うのは問題ありません。たとえば、自分自身を保持するブロックを持つ、起動するタイマーがあり、自分自身でタイマーへの強い参照を保持しているとします。これは、ある時点でタイマーを破棄してサイクルを中断することを常に知っている場合は、まったく問題ありません。

私の場合、ちょうど今、私はそうしたコードに対してこの警告を受けました:

[x setY:^{ [x doSomething]; }];

現在、clangがこの警告を生成するのは、メソッドが「set」で始まることを検出した場合のみです(ここでは触れませんが、他の特別な場合)。私にとっては、保持ループが発生する危険性がないことを知っているので、メソッド名を「useY:」に変更しました。もちろん、すべての場合に適切であるとは限らないため、通常は弱い参照を使用することになりますが、他の人を助けるために私の解決策に注目する価値があると思いました。


4

多くの場合、これは実際には保持サイクルではありません

そうでないことがわかっていれば、無益なweakSelvesを世界に持ち込む必要はありません。

Appleは、APIを使用してこれらの警告を強制しますUIPageViewController。これには、setメソッド(他の場所で言及されているように、これらの警告をトリガーします)を含み、ブロックであるivarに値を設定していると考え、完了ハンドラーブロックあなたは間違いなく自分自身を参照します)。

その1行のコードから警告を削除するためのいくつかのコンパイラディレクティブを次に示します。

#pragma GCC diagnostic push
#pragma clang diagnostic ignored "-Warc-retain-cycles"
    [self.pageViewController setViewControllers:@[newViewController] direction:navigationDirection animated:YES completion:^(BOOL finished) {
        // this warning is caused because "setViewControllers" starts with "set…", it's not a problem
        [self doTheThingsIGottaDo:finished touchThePuppetHead:YES];
    }];
#pragma GCC diagnostic pop

1

精度とスタイルの向上に2セントを追加します。ほとんどの場合self、このブロックではの1つまたはいくつかのメンバーのみを使用します。ほとんどの場合、スライダーを更新するだけです。キャスティングselfはやりすぎです。代わりに、明示的にして、ブロック内で本当に必要なオブジェクトのみをキャストすることをお勧めします。たとえば、それがのインスタンスである場合UISlider*、たとえば、_timeSliderブロック宣言の前に次のことを実行します。

UISlider* __weak slider = _timeSlider;

次にslider、ブロック内で使用します。技術的にはこれがより正確です。保持サイクルの可能性が、内部のすべてのオブジェクトではなく、必要なオブジェクトのみに絞り込まれるためselfです。

完全な例:

UISlider* __weak slider = _timeSlider;
[_embeddedPlayer addPeriodicTimeObserverForInterval:CMTimeMake(1, 1)
     queue:nil
     usingBlock:^(CMTime time){
        slider.value = time.value/time.timescale;
     }
];

さらに、ほとんどの場合、ウィークポインターにキャストされるオブジェクトは内部のウィークポインターselfであり、保持サイクルの可能性を最小限に抑えるか、完全に排除します。上記の例で_timeSliderは、実際には弱参照として格納されているプロパティです。例:

@property (nonatomic, weak) IBOutlet UISlider* timeSlider;

CおよびC ++と同様に、コーディングスタイルの観点から、変数の宣言は右から左に読む方が適切です。SomeType* __weak variableこの順序で宣言すると、次のように右から左に自然に読み取れますvariable is a weak pointer to SomeType


1

私はこの警告に最近遭遇し、少しよく理解したいと思いました。少し試行錯誤した結果、メソッドが「追加」または「保存」で始まることに由来することがわかりました。Objective Cは、「new」、「alloc」などで始まるメソッド名を保持オブジェクトを返すものとして扱いますが、「add」または「save」については何も言及していません(私が見つけることができます)。ただし、この方法でメソッド名を使用した場合:

[self addItemWithCompletionBlock:^(NSError *error) {
            [self done]; }];

[self done]行に警告が表示されます。しかし、これはしません:

[self itemWithCompletionBlock:^(NSError *error) {
    [self done]; }];

私は先に進み、「__ weak __typeof(self)weakSelf = self」の方法を使用してオブジェクトを参照しますが、将来の私や他の開発者を混乱させるため、そうする必要はありません。もちろん、「追加」(または「保存」)を使用することもできませんでしたが、メソッドの意味が失われてしまうため、状況はさらに悪化します。

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