web-dev-qa-db-ja.com

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

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
}];
202
user1845209

ここでの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]];
                                            }];
504
Tim
__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];
    }
};

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

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_変数を含むゴミ箱への認識されていないセレクターでクラッシュするでしょう。それを制御してみてください。

51
iiFreeman

より良いバージョン

__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では、成功ブロックを持つ単純なメソッドは次のようになります。

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 をご覧ください。

40

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

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

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

今の私の場合、次のようなコードに対してこの警告が表示されました。

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

メソッドが「set」で始まることを検出した場合にのみclangがこの警告を生成することがわかりました(そして、ここで言及しない他の特別なケース)。私にとっては、保持ループが存在する危険性がないことを知っているので、メソッド名を「useY:」に変更しました。もちろん、それはすべての場合に適切ではなく、通常は弱い参照を使用しますが、他の人に役立つ場合は、自分の解決策に注目する価値があると思いました。

15
Chris Suter

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

そうでないことを知っていれば、実を結ばない弱い自己を世界に持ち込む必要はありません。

Appleは、UIPageViewControllerのAPIを使用してこれらの警告を強制します。これには、setメソッドが含まれます(他の箇所で述べたように、 block)および完了ハンドラーブロック(間違いなく自分自身を参照します)。

その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
3
bshirley

精度とスタイルの改善に2セントを追加します。ほとんどの場合、このブロックではselfの1つまたは2、3のメンバーのみを使用します。ほとんどの場合、スライダーを更新するだけです。 selfのキャストは過剰です。代わりに、ブロック内で本当に必要なオブジェクトを明示的にキャストしてonlyとする方が適切です。たとえば、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
Luis Artola

私は最近この警告に遭遇し、それをもう少しよく理解したかったです。少し試行錯誤を繰り返した結果、「add」または「save」で始まるメソッドを持つことに起因することがわかりました。 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」の方法を使用してオブジェクトを参照しますが、実際に参照するのは好きではありません。もちろん、「追加」(または「保存」)を使用することもできませんでしたが、メソッドの意味を取り除いてしまうため、それはさらに悪いことです。

1
Ray M.