Tometoyou Tometoyou - 28 days ago 13
iOS Question

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?

Rob Rob
Answer

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 (e.g. 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:

    object graph

    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.

Comments