在这个区块中强烈捕获自我可能会导致保留周期

时间:2013-01-28 06:30:24

标签: objective-c cocoa-touch automatic-ref-counting avplayer retain

如何在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
}];

7 个答案:

答案 0 :(得分:504)

此处self的捕获是您self.timerDisp的隐式属性访问 - 您无法在块中引用selfself上的属性这将由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]];
                                            }];

答案 1 :(得分:51)

__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_变量的垃圾箱。试着控制它。

答案 2 :(得分:40)

更好的版本

__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 的精彩文章 How To Avoid Issues When Using Blocks And NSNotificationCenter


Swift更新:

例如,在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

答案 3 :(得分:15)

在另一个答案中,蒂姆说:

  

你不能在自我强烈保留的区块内引用自我或自我属性。

这不是真的。只要你在某个时刻打破循环,你就可以这样做。例如,假设您有一个触发计时器,该计时器具有保留自身的块,您还可以自我保持对计时器的强引用。如果你总是知道你会在某个时刻破坏计时器并打破循环,那就完全没问题了。

就我刚才的情况而言,我对代码做了这个警告:

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

现在我碰巧知道clang只会在检测到方法以“set”(以及我在这里不提及的另一个特殊情况)开始时才会产生此警告。对我来说,我知道没有保留循环的危险,所以我将方法名称更改为“useY:”当然,这可能不适合所有情况,通常你会想要使用弱引用,但是我认为值得注意我的解决方案,以防它帮助他人。

答案 4 :(得分:3)

很多时候,这实际上不是保留周期

如果您知道并非如此,则无需将毫无结果的弱者带入世界。

Apple甚至通过API将UIPageViewController上的这些警告强加给我们,其中包括设置方法(触发这些警告(如其他地方所述),认为您正在将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

答案 5 :(得分:1)

在提高精度和风格方面增加两美分。在大多数情况下,您只能在此块中使用一个或几个content成员,很可能只是更新滑块。施放self是过度的。相反,最好是明确并且强制您在块内真正需要的对象。例如,如果它是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

就编码风格而言,与C和C ++一样,从右到左更好地读取变量声明。按此顺序声明@property (nonatomic, weak) IBOutlet UISlider* timeSlider; 从右到左更自然地读取:SomeType* __weak variable

答案 6 :(得分:1)

我最近遇到了这个警告,想更好地理解它。经过一番尝试和错误,我发现它起源于以“ add”或“ save”开头的方法。目标C将以“ new”,“ alloc”等开头的方法名称视为返回保留的对象,但未提及(我可以找到)有关“ add”或“ save”的任何内容。但是,如果我以这种方式使用方法名称:

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

我会在[完成]行中看到警告。但是,这不会:

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

我将继续使用“ __weak __typeof(self)weakSelf = self”方法来引用我的对象,但实际上不喜欢这样做,因为这会使我和/或其他开发者感到困惑。当然,我也不能使用“ add”(或“ save”),但这更糟,因为它消除了方法的含义。