【问题标题】:Performing remove(at:[index]) on UIImage array leads to memory leak对 UIImage 数组执行 remove(at:[index]) 会导致内存泄漏
【发布时间】:2019-08-25 22:21:54
【问题描述】:

当我在 collectionView-didSelectItemAt/UIAlertController 中对 UIImage 数组执行 remove(at: [index]) 时,我最终会出现内存泄漏。 (注意:数组实际上是我定义的一个类的一部分。)

这个问题很难找到,因为它在删除时似乎有效。更具体地说,在我从数组中删除图像并打印内容后,它就消失了,内存看起来还不错。但后来,当我处理完那个对象后,对象的 deinit 方法就再也不会被调用了。

注意:我也尝试在主队列上执行代码。

class ExampleObj {
    var vehicleImages: [UIImage]

    init() {
        self.vehicleImages = []
    }

    deinit() {
        print("Object deinitialized")
    }
}

func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
    let alert = UIAlertController(title: "Delete Image", message: "", preferredStyle: .alert)

    alert.addAction(UIAlertAction(title: "Delete Image", style: .destructive, handler: { (action) in
        alert.dismiss(animated: true, completion: nil)
        // Delete image
        self.exampleObj.vehicleImages.remove(at: indexPath.row)
    }))

    alert.addAction(UIAlertAction(title: "Return", style: .default, handler: { (action) in
        alert.dismiss(animated: true, completion: nil)
    }))
    self.present(alert, animated: true, completion: nil)
}

【问题讨论】:

  • 如果您使用handler: { [weak self] (action) in //code } 是否可以解决您的内存泄漏问题?
  • 我不相信它会;我删除了操作中的所有代码并尝试运行,一切正常。 @DanO'Leary
  • 我认为没有必要添加 deinit,我的错误。而且我不确定如何解释泄漏的性质......当我完成该对象时,对象 deinit 方法永远不会被调用,因此该对象不会被释放 - 泄漏。奇怪的是,如果我将 (self.exampleObj.vehicleImages.remove(at: indexPath.row)) 移动到警报控制器块之外(并保持其他所有内容相同),我看不到泄漏@Rob
  • 在您的示例代码中,不应调用 ExampleObj.deinit。您不会删除 ExampleObj。您正在删除包含在 ExampleObj 中的图像。您认为这里应该发生什么行为,确实发生了什么行为(以及您如何确定这种行为发生)?
  • @RobNapier 请仔细阅读帖子:“在我从数组中删除图像并打印内容后,它消失了,内存看起来很好。~但是后来,当我完成那个对象,~对象的deinit方法永远不会被调用。”所以我的意思是当我说“稍后,当我完成那个对象”时,我的意思是我已经在创建对象的 VC 中完成了我需要的一切,然后我返回(关闭所说的 VC)对象(和VC) 应该被释放,但它们不是。

标签: swift memory-leaks uialertcontroller


【解决方案1】:

问题是在您的 UIAlertAction 闭包中存在对 alert 的引用:

alert.addAction(UIAlertAction(title: "Delete Image", style: .destructive, handler: { (action) in
    alert.dismiss(animated: true, completion: nil)
}))

这不仅不是必需的(警报会自动解除,因此自己调用dismiss 是多余的并且可能存在问题),而且在此闭包中对alert 的引用也在警报控制器之间引入了强引用循环,警报动作,关闭,然后返回警报控制器。

更糟糕的是,当您在其中添加对 self 的引用时:

alert.addAction(UIAlertAction(title: "Delete Image", style: .destructive, handler: { (action) in
    alert.dismiss(animated: true, completion: nil)
    self.exampleObj.vehicleImages.remove(at: indexPath.row)
}))

现在,警报控制器与其警报操作之间的强引用循环现在引入了对您的视图控制器 self 的强引用,这将防止其属性被释放。

因此,只需从闭包中删除这些 alert 引用,问题就会消失:

alert.addAction(UIAlertAction(title: "Delete Image", style: .destructive) { _ in
    self.exampleObj.vehicleImages.remove(at: indexPath.row)
})

alert.addAction(UIAlertAction(title: "Return", style: .default))

如果您愿意,也可以将 self 设为 weak 参考:

alert.addAction(UIAlertAction(title: "Delete Image", style: .destructive) { [weak self] _ in
    self?.exampleObj.vehicleImages.remove(at: indexPath.row)
})

这样,即使你不小心在警报控制器和它的动作之间引入了一个强引用循环,至少self 不会陷入那个强引用循环。 (但值得注意的是,[weak self] 不足以解决强引用循环,而只是让self 不参与其中。您确实想删除那些alert 引用以避免首先出现强引用循环。 )


FWIW,您可以使用“Debug Memory Graph” 功能来查找对您的对象保持强引用的原因,追溯它,最终如果您使用“Malloc 堆栈”功能,您可以准确地看到这个根强引用的位置建立了。在这种情况下,我展示了一个警报控制器(在闭包中强烈引用alertself),然后关闭有问题的视图控制器,点击“Debug Memory Graph”按钮,它向我们展示了ExampleObj、视图控制器、警报控制器和警报操作都还在内存中:

确实很难遵循该图,但我专注于我创建的对象,我可以将其从 ExampleObj 追溯到视图控制器到警报操作,甚至可以追溯到 @987654345 所在的代码行@ 创建了对视图控制器的强引用。我添加了这些箭头来显示我是如何追踪它的。诚然,所有这些并没有告诉您如何解决问题,但它可以让您追溯问题,此时您可以抢先诊断问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-19
    • 2021-03-23
    • 2021-09-25
    • 2014-12-08
    • 1970-01-01
    相关资源
    最近更新 更多