【问题标题】:Passing a closure causing a memory leak传递闭包导致内存泄漏
【发布时间】:2016-11-04 05:38:33
【问题描述】:

我有以下功能:

func attachToComment(_ data: Data, url: URL?, type: MediaType) {
    self.dismiss(animated: true, completion: nil)
    let image = UIImage(data: data)!
    model.attachmentData = data
    model.attachmentVideoURL = url
    model.attachmentType = type

    toolbar.attachImage(image, withType: type)
}

这是传递给以模态方式呈现的视图控制器的引用:

containerNavViewController.attachToComment = attachToComment

ContainerNavViewController 是一个UINavigationController,里面有一个容器视图控制器。这显示了相机,一旦拍摄了图像,将 attachToComment 函数传递给下一个视图控制器 - EditImageViewController。在 ContainerNavViewController 我有以下代码:

var attachToComment: ((_ data: Data, _ url: URL?, _ type: MediaType) -> ())?
var model = CameraModel()
....

editImageViewController.attachToComment = self.attachToComment
editImageViewController.model = model
self.navigationController?.pushViewController(editImageViewController, animated: false)

在 EditImageViewController 内部,我有以下调用闭包的代码:

attachToComment(model.imageData, model.videoURL, model.mediaType)

然后调用原始函数并关闭模态视图控制器。但是,这些视图不会调用 deinit,它们在后台非常“活跃”。我的内存泄漏在哪里,我该如何预防?

【问题讨论】:

  • 看起来您可能正在进行一个保留周期。 Self 强烈拥有 attachToComment(闭包是一流的对象),在闭包内您引用 self 和 self 拥有的变量(模型、工具栏和函数解除)。结果,self 拥有闭包,闭包强烈地捕获了 self,现在两者都不会释放,因为它们相互保留。

标签: ios swift


【解决方案1】:

正如 Dare 和 Sealos 都建议的那样,问题很可能是一个强大的参考循环。但是仅凭问题中提供的代码并不足以引起这样的循环(因为当您关闭editImageViewController时,如果没有更多的强引用,则释放闭包并打破强引用循环)。

不过有一些观察:

  1. 仅使用提供的代码,我无法重现您的强大参考周期。

    但是,如果呈现editImageViewController 的视图控制器保持对它的强引用,阻止它被释放,我可以诱导一个强引用循环。 editImageViewController 是局部变量还是属性?如果是属性,请将其更改为局部变量,这可能会解决问题。或者可能是其他东西对editImageViewController 保持强烈引用(例如重复计时器或类似的东西)。

    您可以使用“调试内存图”工具(请参阅下面的第 3 点)确定是什么保持对 editImageViewController 的强引用。

  2. 如果您确实想确保 EditImageViewControllerattachToComment 没有对呈现视图控制器保持强引用,我会替换:

    editImageViewController.attachToComment = attachToComment
    

    与:

    editImageViewController.attachToComment = { [weak self] data, url, type in
        self?.attachToComment(data, url: url, type: type)
    }
    

    这是确保editImageViewController 中的attachToComment 闭包不会保持对呈现视图控制器的强引用的最简单方法。

  3. 您可能希望使用 Xcode 8 中的“调试内存图”工具来识别 EditImageViewController 的所有权。例如,我做了一个例子,我两者都有:

    • 错误地将editImageViewController 存储为当前视图控制器的属性,而不是将其保存为局部变量(第 1 点);和

    • 没有在闭包中使用 weak 模式(第 2 点)。
       

    当我这样做时,我得到了一个这样的内存图:

    从该图表中,我不必猜测问题的根源是什么。我不仅可以看到存在强引用循环,而且可以看到具体是什么导致了问题。但是,如果我解决了上述两个问题中的任何一个(或两个),这个循环就会消失。

【讨论】:

  • 这里的每个人都给出了很好的答案!第1点是我的答案。我强烈引用了containerNavViewController,这是一个属性(它又强烈引用了其他控制器)。在解雇时将其设置为 nil 可以正确定义所有内容。干杯!
【解决方案2】:

问题是当您分配 attachToComment 时,它会捕获视图控制器的自身指针,从而阻止释放。

有两种方法可以解决这个问题:

  1. 当视图被关闭时,将 attachToComment 变量设为 nil。

编辑由于 Rob 的更正,而不是捕获实例,而是返回带有弱自指针的闭包:

  1. 在 attachToComment 变量中使用弱自指针。像这样:

    typealias ReturnType = (Data, URL?, MediaType) -> ()
    func attachToComment() -> ReturnType {
    
        return { [weak self] in
            self?.dismiss(animated: true, completion: nil)
            let image = UIImage(data: data)!
            self?.model.attachmentData = data
            self?.model.attachmentVideoURL = url
            self?.model.attachmentType = type
    
            self?.toolbar.attachImage(image, withType: type)
        }
    }
    
    editImageViewController.attachToComment = containerNavController.attachToComment()
    

问题是,这通常使用弱协议来完成。一个例子是:

protocol EditImageProtocol: class {
    func attachToComment(_ data: Data, url: URL?, type: MediaType)
}

class ContainerNavController: EditImageProtocol {
    func attachToComment(_ data: Data, url: URL?, type: MediaType) {
        self.dismiss(animated: true, completion: nil)
        let image = UIImage(data: data)!
        model.attachmentData = data
        model.attachmentVideoURL = url
        model.attachmentType = type

        toolbar.attachImage(image, withType: type)
    }
}

class EditImageViewController {
    weak var delegate: EditImageProtocol?

    func someFunction() {
        delegate?.attachToComment(...)
    }
}

【讨论】:

  • @Rob 非常感谢,现在我在提交之前已经实际测试过了!
猜你喜欢
  • 2021-08-02
  • 1970-01-01
  • 2019-11-19
  • 1970-01-01
  • 2012-02-07
  • 1970-01-01
  • 2015-07-06
  • 2014-06-07
  • 2013-11-20
相关资源
最近更新 更多