简体   繁体   English

传递闭包导致内存泄漏

[英]Passing a closure causing a memory leak

I have the following function: 我有以下功能:

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

This is passed a reference to a View Controller that is presented modally: 这是传递给模态呈现的视图控制器的引用:

containerNavViewController.attachToComment = attachToComment

ContainerNavViewController is a UINavigationController that has a container view controller inside it. ContainerNavViewController是一个UINavigationController ,里面有一个容器视图控制器。 This shows the camera and once the image is taken, passes the attachToComment function to the next view controller – EditImageViewController . 这显示了摄像头,一旦拍摄了图像, attachToComment函数传递给下一个视图控制器 - EditImageViewController Inside ContainerNavViewController I have the following code: 在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)

Inside EditImageViewController I have the following code that calls the closure: 在EditImageViewController里面我有以下代码调用闭包:

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

This then calls the original function and dismisses the modal view controller. 然后调用原始函数并解除模态视图控制器。 However, deinit is not called on these views, and they're very much "living" in the background. 然而,deinit并未在这些视图中被调用,并且它们在后台非常“活跃”。 Where is my memory leak and how can I prevent it? 我的内存泄漏在哪里?我该如何预防?

As Dare and Sealos both suggest, the problem is likely a strong reference cycle. 正如Dare和Sealos所暗示的那样,问题很可能是一个强大的参考周期。 But the code provided in the question, alone, is not enough to cause such a cycle (because when you dismiss editImageViewController , if there are no more strong references to it, the closure is released and the strong reference cycle is broken). 但是问题中提供的代码,单独来说,还不足以导致这样的循环(因为当你解除editImageViewController ,如果没有更强的引用,那么闭包就会被释放并且强引用循环被破坏)。

A few observations, though: 但是,有一些观察结果:

  1. I was unable to reproduce your strong reference cycle solely using the provided code. 我无法仅使用提供的代码重现您的强引用周期。

    But I can induce a strong reference cycle if the view controller that presented editImageViewController is keeping a strong reference to it, preventing it from being released. 但是如果呈现editImageViewController的视图控制器保持editImageViewController的强引用,则可以引发强引用循环,从而阻止它被释放。 Is editImageViewController a local variable, or is it a property? editImageViewController是局部变量,还是属性? If a property, change it to a local variable and that may resolve the issue. 如果是属性,请将其更改为本地变量,这可能会解决问题。 Or perhaps something else is keeping a strong reference to editImageViewController (eg a repeating timer or something like that). 或许其他东西保持对editImageViewController的强引用(例如重复计时器或类似的东西)。

    You can identify what is keeping a strong reference to editImageViewController using the "Debug Memory Graph" tool (see point 3, below). 您可以使用“调试内存图”工具来识别对editImageViewController的强引用(参见下面的第3点)。

  2. If you did want to make sure that the attachToComment of EditImageViewController didn't keep a strong reference to the presenting view controller, I would replace: 如果你确实想确保attachToCommentEditImageViewController没有保持对呈现视图控制器的强引用,我会替换:

     editImageViewController.attachToComment = attachToComment 

    with: 有:

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

    That's the simplest way to make sure the attachToComment closure in editImageViewController doesn't maintain a strong reference to the presenting view controller. 这确保了最简单的方法attachToComment关闭在editImageViewController不保持强大的参考呈现视图控制器。

  3. You might want to use the "Debug Memory Graph" tool in Xcode 8 to identify the ownership of the EditImageViewController . 您可能希望使用Xcode 8中的“调试内存图”工具来识别EditImageViewController的所有权。 For example, I did an example where I both: 例如,我做了一个例子,我都是:

    • Incorrectly stored editImageViewController as a property of the presenting view controller rather than keeping it as a local variable (point 1); editImageViewController错误地存储为呈现视图控制器的属性,而不是将其保存为局部变量(第1点); and

    • Didn't use the weak pattern in the closure (point 2). 没有在封闭中使用weak模式(第2点)。

    When I did that, I got an memory graph like so: 当我这样做时,我得到了一个像这样的记忆图:

    对象图

    From that graph, I don't have to guess what the source of the problem is. 从该图表中,我不必猜测问题的根源是什么。 I can see not only that the strong reference cycle exists, but what specifically is causing the problem. 我不仅可以看到强引用周期存在,而且具体是导致问题的原因。 If I fix either of the two above issues (or both), though, this cycle disappears. 但是,如果我修复上述两个问题中的任何一个(或两者),这个循环就会消失。

The issue is when you assign attachToComment, it captures the self pointer of the view controller, and that prevents the release. 问题是当您分配attachToComment时,它会捕获视图控制器的自指针,并阻止发布。

There are 2 ways you can solve this: 有两种方法可以解决这个问题:

  1. When the views are dismissed, nil the attachToComment variable. 当视图被解除时,没有attachToComment变量。

EDIT Because of Rob's correction, instead of capturing an instance, get back a closure with a weak self-pointer: 编辑由于Rob的更正,而不是捕获实例,请使用弱自指针获取闭包:

  1. Use a weak self-pointer in the attachToComment variable. 在attachToComment变量中使用弱自指针。 Like so: 像这样:

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

The thing is, this is usually done using weak protocols. 问题是,这通常使用弱协议来完成。 An example of that would be: 一个例子是:

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

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

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