如何在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或self上的属性。
这并不完全正确。只要你在某个时候打破了这个循环,你就可以这样做。例如,假设你有一个触发的定时器,它有一个保留self的块,你也在self中保留一个对定时器的强引用。如果你总是知道你会在某个时候破坏计时器并打破循环,这是完全没问题的。
在我刚才的例子中,我有这样的代码警告:
[x setY:^{ [x doSomething]; }];
现在我碰巧知道clang只有在检测到以“set”开头的方法时才会产生这个警告(还有一种特殊情况,我不会在这里提到)。对我来说,我知道存在保留循环的危险,所以我将方法名称更改为“useY:”当然,这可能不适用于所有情况,通常您会希望使用弱引用,但我认为值得注意我的解决方案,以防它有助于其他人。
很多时候,这实际上不是一个保留周期。
如果你知道它不是,你就不需要把没有结果的软弱自我带到这个世界上。
苹果甚至通过他们的UIPageViewController的API将这些警告强加给我们,其中包括一个set方法(触发这些警告-正如在其他地方提到的-认为你正在设置一个值为一个ivar,这是一个块)和一个完成处理程序块(其中你无疑会提到你自己)。
下面是一些编译器指令,可以从这一行代码中删除警告:
#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
这里对自我的捕捉是伴随着你对自我的隐式属性访问。timerDisp -你不能从一个将被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];
}
};
有一件非常重要的事情要记住:
不要直接在block中使用实例变量,使用它作为弱对象的属性,示例:
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;
}
如果传递not retain by anyone对象的弱副本,则会出现另一个问题:
MyViewController *vcToGo = [[MyViewCOntroller alloc] init];
__weak MyViewController *vcToGo_ = vcToGo;
self.loadingCompletion = ^{
[vcToGo_ doSomePrecessing];
};
如果vcToGo将被释放,然后这个块被发射,我相信你会崩溃,无法识别的选择器到一个垃圾,其中包含vcToGo_变量现在。试着控制它。
在另一个回答中,蒂姆说:
您不能从将被self强保留的块中引用self或self上的属性。
这并不完全正确。只要你在某个时候打破了这个循环,你就可以这样做。例如,假设你有一个触发的定时器,它有一个保留self的块,你也在self中保留一个对定时器的强引用。如果你总是知道你会在某个时候破坏计时器并打破循环,这是完全没问题的。
在我刚才的例子中,我有这样的代码警告:
[x setY:^{ [x doSomething]; }];
现在我碰巧知道clang只有在检测到以“set”开头的方法时才会产生这个警告(还有一种特殊情况,我不会在这里提到)。对我来说,我知道存在保留循环的危险,所以我将方法名称更改为“useY:”当然,这可能不适用于所有情况,通常您会希望使用弱引用,但我认为值得注意我的解决方案,以防它有助于其他人。
更好的版本
__strong typeof(self) strongSelf = weakSelf;
创建弱版本的强引用,作为块中的第一行。如果self在块开始执行时仍然存在,并且没有回落到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时。我们将使用[弱自我]和守护let特性。
doSomeThingWithSuccessBlock { [weak self] () -> () in
guard let strongSelf = self else { return }
strongSelf.gridCollectionView.reloadData()
}
这种所谓的强弱舞蹈被流行的开源项目Alamofire所使用。
更多信息请查看swift-style-guide