传递闭包导致内存泄漏

时间:2016-11-03 18:10:28

标签: ios swift

我有以下功能:

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并未在这些视图中被调用,并且它们在后台非常“活跃”。我的记忆泄漏在哪里,我该如何预防呢?

2 个答案:

答案 0 :(得分:2)

正如Dare和Sealos所暗示的那样,问题很可能是一个强大的参考周期。但是问题中提供的代码单独就不足以导致这样的循环(因为当你解雇editImageViewController时,如果没有更强的引用,则关闭被释放并且强引用周期被打破)。

但有一些观察结果:

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

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

    您可以使用“调试记忆图”工具确定保留对editImageViewController的强引用的内容(参见下面的第3点)。

  2. 如果您确实希望确保attachToComment的{​​{1}}没有对呈现视图控制器的强引用,我会替换:

    EditImageViewController

    使用:

    editImageViewController.attachToComment = attachToComment
    

    这是确保editImageViewController.attachToComment = { [weak self] data, url, type in self?.attachToComment(data, url: url, type: type) } 中的attachToComment闭包不能保持对呈现视图控制器的强引用的最简单方法。

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

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

    • 未在封闭中使用editImageViewController模式(第2点)。

    当我这样做时,我得到了一个类似的记忆图:

    object graph

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

答案 1 :(得分:1)

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

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

  1. 当视图被解除时,无需attachToComment变量。
  2. 编辑由于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()
      
    2. 问题是,这通常是使用弱协议完成的。一个例子是:

      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(...)
          }
      }