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


207

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

timerDisp在类的属性?
蒂姆(Tim)

是的,@ property(nonatomic,strong)UILabel * timerDisp;
user1845209

2
这是什么:player(AVPlayer object)timerDisp(UILabel)
Carl Veazey 2013年

AVPlayer *播放器;UILabel * timerDisp;
user1845209

5
真正的问题是,当您知道循环引用将被破坏时(例如,如果在网络请求完成时始终清除引用),如何在自身上没有不必要的弱引用的情况下使此警告静音。
Glenn Maynard 2014年

Answers:


514

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

13
尝试__unsafe_unretained改用。
蒂姆(Tim)

63
解决。改用它:__unsafe_unretained typeof(self)weakSelf = self; 谢谢fo的帮助@Tim
2013年

1
好的答案,但我对您说的不多:“您不能从一个将被自身强烈保留的块中引用自身或自身的属性。” 严格来说并非如此。请在下面查看我的答案。最好说:“ 如果提及自我,则必须格外小心……”
克里斯·苏特

8
我在OP的代码中看不到保留周期。该块不是由强烈保留的self,而是由主调度队列保留的。我错了吗?
erikprice

3
@erikprice:你没看错。我将问题解释为主要与Xcode出现的错误有关(“如何避免在xcode中出现此警告”),而不是与保留周期的实际存在有关。您说对了,仅从提供的片段OP中就没有明显的保留周期,这是正确的。

52
__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_现在包含变量。尝试控制它。


3
如果您也解释一下,这将是一个更强有力的答案。
Eric J.

43

更好的版本

__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时避免出现问题


快速更新:

例如,快速地使用成功块的简单方法是:

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不为零。
马特(Matt)

为了避免可能的保留周期,我们在代码中使用self的任何块之外建立一个弱self引用。用您的方式,您必须确保执行该块。现在,您的另一个代码块负责释放以前保留的内存。
Warif Akhand Rishi 2014年

@Matt此示例的目的不是保留weakSelf。目的是,如果weakSelf不为零,请在块内进行强引用。因此,一旦块开始使用self执行,self就不会在块内变为nil。
Warif Akhand Rishi 2014年

15

蒂姆在另一个回答中说:

您不能从将由self强烈保留的块中引用self或self的属性。

这不是真的。只要您在某个时候中断周期,就可以这样做。例如,假设您有一个触发的计时器,该计时器具有一个保留self的块,并且您对self中的计时器也有很强的引用。如果您始终知道自己会在某个时候破坏计时器并中断周期,那将是非常好的方法。

就我而言,对于以下代码,我有以下警告:

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

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


4

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

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

Apple甚至通过API将这些警告强加给我们UIPageViewController,包括一个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

1

在提高精度和样式上加2美分。在大多数情况下,您仅使用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


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”),但这更糟,因为它消除了方法的含义。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.