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

Posted

技术标签:

【中文标题】在此块中强烈捕获自我可能会导致保留周期【英文标题】:capturing self strongly in this block is likely to lead to a retain cycle 【发布时间】:2013-01-11 11:18:13 【问题描述】:

如何避免在 xcode 中出现此警告。这是代码sn-p:

[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 是类的属性吗? 是的,@property(nonatomic,strong)UILabel *timerDisp; 这是什么:player(AVPlayer object)timerDisp(UILabel) AVPlayer *player; UILabel *timerDisp; 真正的问题是当你知道循环引用将被破坏时(例如,如果你总是清除引用当网络请求完成时)。 【参考方案1】:

这里对self 的捕获是与self.timerDisp 的隐式属性访问一起出现的——你不能在@987654325 强烈保留的块内引用selfself 上的属性@。

您可以通过在块内访问 timerDisp 之前创建对 self 的弱引用来解决此问题:

__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]];
                                            ];

【讨论】:

尝试改用__unsafe_unretained 已解决。改用这个: __unsafe_unretained typeof(self) weakSelf = self;感谢@Tim的帮助 很好的答案,但我对你说的小问题不以为然:“你不能在一个将被 self 强烈保留的块中引用 self 或 self 上的属性。”严格来说,这不是真的。请看下面我的回答。最好说,“如果你提到自己,你必须非常小心……” 我在 OP 的代码中看不到保留周期。该块未被self 强保留,它由主调度队列保留。我错了吗? @erikprice:你没看错。我将这个问题解释为主要是关于 Xcode 出现的错误(“如何在 xcode 中避免这个警告”),而不是关于保留周期的实际存在。您说得对,仅从提供的 sn-p OP 来看,没有明显的保留周期。【参考方案2】:
__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;

如果您传递不被任何对象保留的弱副本,则会出现另一个问题:

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

如果vcToGo 将被释放,然后这个块被触发,我相信你会因为无法识别的选择器崩溃到现在包含vcToGo_ 变量的垃圾箱。试着控制它。

【讨论】:

如果你也解释一下,这将是一个更有力的答案。【参考方案3】:

更好的版本

__strong typeof(self) strongSelf = weakSelf;

创建对该弱版本的强引用作为块中的第一行。如果当块开始执行时 self 仍然存在并且没有回退为零,则此行确保它在整个块的执行生命周期中持续存在。

所以整个事情会是这样的:

// 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 中,一个带有成功块的简单方法是:

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 不为零。 为了避免可能的保留循环,我们在代码中使用 self 的任何块之外建立弱自引用。以您的方式,您必须确保执行该块。你的另一块代码现在负责释放你之前保留的内存。 @Matt 这个例子的目的不是让weakSelf被保留。目的是,如果weakSelf 不为零,则在块内进行强引用。因此,一旦块开始使用 self 执行,self 不会在块内变为 nil。【参考方案4】:

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

您不能在将被 self 强烈保留的块内引用 self 或 self 上的属性。

这并不完全正确。只要您在某个时候打破循环,您就可以这样做。例如,假设您有一个触发的计时器,它有一个保留 self 的块,并且您还在 self 中保留了对计时器的强引用。如果你总是知道你会在某个时候破坏计时器并打破循环,那就太好了。

就我刚才的情况,我收到了以下代码警告:

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

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

【讨论】:

【参考方案5】:

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

如果你知道它不是,你就不需要把毫无结果的弱者带到这个世界上。

Apple 甚至通过他们的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

【讨论】:

【参考方案6】:

在提高精度和风格方面增加两分钱。在大多数情况下,您只会在此块中使用 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 实际上是一个存储为弱引用的属性,例如:

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

就编码风格而言,与 C 和 C++ 一样,变量声明最好从右向左阅读。按此顺序声明SomeType* __weak variable 从右到左更自然地读为:variable is a weak pointer to SomeType

【讨论】:

【参考方案7】:

我最近遇到了这个警告,想更好地理解它。经过一番反复试验,我发现它源于一个方法以“添加”或“保存”开头。 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”方式来引用我的对象,但我真的不喜欢这样做,因为它会混淆未来的我和/或其他开发人员。当然,我也不能使用“添加”(或“保存”),但这更糟,因为它带走了方法的含义。

【讨论】:

以上是关于在此块中强烈捕获自我可能会导致保留周期的主要内容,如果未能解决你的问题,请参考以下文章

实现 API 时如何避免在块中捕获自我?

更新到 iOS 6.1 后出现 ARC Retain Cycle

用强弱自我打破保留周期

在嵌套块中引用弱自我

重新夺回你自己,做个强烈自我意识的人

异常捕获