繁体   English   中英

为什么在performBatchUpdates中对父UIViewController的强引用会泄漏活动?

[英]Why does a strong reference to parent UIViewController in performBatchUpdates leak an activity?

我刚刚调试了一个非常讨厌的UIViewController泄漏,这样即使在调用dismissViewControllerAnimated之后UIViewController也没有被dismissViewControllerAnimated

我将问题跟踪到以下代码块:

    self.dataSource.doNotAllowUpdates = YES;

    [self.collectionView performBatchUpdates:^{
        [self.collectionView reloadItemsAtIndexPaths:@[indexPath]];
    } completion:^(BOOL finished) {
        self.dataSource.doNotAllowUpdates = NO;
    }];

基本上,如果我调用performBatchUpdates然后立即调用dismissViewControllerAnimated ,UIViewController就会泄露,并且永远不会调用该UIViewControllerdealloc方法。 UIViewController永远挂起。

有人可以解释这种行为吗? 我假设performBatchUpdates在一段时间间隔内运行,比如500毫秒,所以我假设在上述间隔之后,它会调用这些方法,然后触发dealloc。

修复似乎是这样的:

    self.dataSource.doNotAllowUpdates = YES;

    __weak __typeof(self)weakSelf = self;

    [self.collectionView performBatchUpdates:^{
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        if (strongSelf) {
            [strongSelf.collectionView reloadItemsAtIndexPaths:@[indexPath]];
        }
    } completion:^(BOOL finished) {
        __strong __typeof(weakSelf)strongSelf = weakSelf;

        if (strongSelf) {
            strongSelf.dataSource.doNotAllowUpdates = NO;
        }
    }];

请注意, BOOL成员变量doNotAllowUpdates是我添加的变量,它在执行performBatchUpdates调用时阻止任何类型的dataSource / collectionView更新。

我在网上搜索关于我们是否应该在performBatchUpdates使用weakSelf / strongSelf模式的讨论,但没有找到关于这个问题的具体内容。

我很高兴我能够找到这个bug的底部,但我希望一个更聪明的iOS开发人员能够向我解释我所看到的这种行为。

这似乎是UICollectionView的一个错误。 API用户不应期望在执行任务之后保留单个运行块参数,因此防止参考周期不应成为问题。

UICollectionView应该在完成批量更新过程后清除对块的任何引用,或者批量更新过程中断(例如,通过从屏幕中删除集合视图)。

您已经亲眼看到,即使在更新过程中将集合视图移出屏幕,也会调用完成块,因此集合视图应该将其对该完成块的任何引用取消 - 它永远不会被调用再次,无论集合视图的当前状态如何。

如您所知,当不使用weak时,会创建保留周期。

保留周期是由self强烈引用collectionView引起的,而collectionView现在具有对self的强引用。

必须始终假设在执行异步块之前, self可能已被释放。 要安全地处理这个问题,必须做两件事:

  1. 总是使用对self的弱引用(或ivar本身)
  2. 在将它作为nunnull参数传递之前,始终确认weakSelf存在

更新:

performBatchUpdates周围进行一些日志记录确认了很多:

- (void)logPerformBatchUpdates {
    [self.collectionView performBatchUpdates:^{
        NSLog(@"starting reload");
        [self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
        NSLog(@"finishing reload");
    } completion:^(BOOL finished) {
        NSLog(@"completed");
    }];

    NSLog(@"exiting");
}

打印:

starting reload
finishing reload
exiting
completed

这表明在离开当前作用域后触发了完成块,这意味着它将异步调度回主线程。

您提到在执行批量更新后立即关闭视图控制器。 我认为这是你问题的根源:

经过一些测试,我能够重新创建内存泄漏的唯一方法是在解雇之前调度工作。 这是一个很长的镜头,但你的代码是偶然的吗?:

- (void)breakIt {
    // dispatch causes the view controller to get dismissed before the enclosed block is executed
    dispatch_async(dispatch_get_main_queue(), ^{
        [self.collectionView performBatchUpdates:^{
            [self.collectionView reloadItemsAtIndexPaths:[self.collectionView indexPathsForVisibleItems]];
        } completion:^(BOOL finished) {
            NSLog(@"completed: %@", self);
        }];
    });
    [self.presentationController.presentingViewController dismissViewControllerAnimated:NO completion:nil];
}

上面的代码导致在视图控制器上不调用dealloc

如果您使用现有代码并简单地调度(或执行dismissViewController :after :) dismissViewController调用,您也可能会解决此问题。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM