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
}];
ここでの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]];
}];
__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_
変数を含むゴミ箱への認識されていないセレクターでクラッシュするでしょう。それを制御してみてください。
__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 をご覧ください。
別の答えでは、ティムは言った:
selfによって強く保持されるブロック内からselfまたはselfのプロパティを参照することはできません。
これはまったく真実ではありません。ある時点でサイクルを中断する限り、これを行うことは問題ありません。たとえば、自己を保持するブロックを持つタイマーが起動し、自己のタイマーへの強い参照も保持しているとします。ある時点でタイマーを破壊し、サイクルを中断することを常に知っている場合、これはまったく問題ありません。
今の私の場合、次のようなコードに対してこの警告が表示されました。
[x setY:^{ [x doSomething]; }];
メソッドが「set」で始まることを検出した場合にのみclangがこの警告を生成することがわかりました(そして、ここで言及しない他の特別なケース)。私にとっては、保持ループが存在する危険性がないことを知っているので、メソッド名を「useY:」に変更しました。もちろん、それはすべての場合に適切ではなく、通常は弱い参照を使用しますが、他の人に役立つ場合は、自分の解決策に注目する価値があると思いました。
多くの場合、これは実際には保持サイクルではありません。
そうでないことを知っていれば、実を結ばない弱い自己を世界に持ち込む必要はありません。
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
精度とスタイルの改善に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
のように、右から左に自然に読み取れます。
私は最近この警告に遭遇し、それをもう少しよく理解したかったです。少し試行錯誤を繰り返した結果、「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」の方法を使用してオブジェクトを参照しますが、実際に参照するのは好きではありません。もちろん、「追加」(または「保存」)を使用することもできませんでしたが、メソッドの意味を取り除いてしまうため、それはさらに悪いことです。