简体   繁体   中英

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. This shows the camera and once the image is taken, passes the attachToComment function to the next view controller – EditImageViewController . Inside ContainerNavViewController I have the following code:

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:

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

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. Is editImageViewController a local variable, or is it a property? 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).

    You can identify what is keeping a strong reference to editImageViewController using the "Debug Memory Graph" tool (see point 3, below).

  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:

     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.

  3. You might want to use the "Debug Memory Graph" tool in Xcode 8 to identify the ownership of the 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); and

    • Didn't use the weak pattern in the closure (point 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.

There are 2 ways you can solve this:

  1. When the views are dismissed, nil the attachToComment variable.

EDIT Because of Rob's correction, instead of capturing an instance, get back a closure with a weak self-pointer:

  1. Use a weak self-pointer in the attachToComment variable. 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(...)
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

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