1

I was wondering how to avoid retain cycle in the following scenario:

private func setupDismissCallbacks() {

  // inner func     
  func dismiss() {
     self.videoExporter?.cancel()
     self.rootViewController.dismiss(animated: true, completion: nil)
     self.delegate?.childCoordinatorDidFinish(self)
  }

  // first clousre       
  saveModalViewController.onButtonDismiss = {  [weak self] in
     // not really using `self` here
     guard let self = self else { return }
     dismiss()
  }

  // second clousre  
  saveModalViewController.onDimmedAreaDismiss = { [weak self] in
     // not really using `self` here
     guard let self = self else { return }
     dismiss()
  }

}

I have a function setupDismissCallbacks that listens to two callbacks from a saveModalViewController self property. dismiss() is an inner function inside setupDismissCallbacks that I'm using to access self values.

But inside the closures onButtonDismiss and onDimmedAreaDismiss I don't access self to call dismiss, and I can't add [weak self] into dismiss function because it's a function.

How can I verify that the calls inside dismiss won't cause retain cycle?

Roi Mulia
  • 5,626
  • 11
  • 54
  • 105
  • "I don't access self to call `dismiss`" How do you think the `dismiss()` call knows which instance to operate on? I.e. when it's called, what will its value of `self` be? – Alexander Aug 24 '19 at 13:04
  • @Alexander It's not a class function, calling `self.dismiss()` results in `Value of type 'SaveCoordinator' has no member 'dismiss'` – Roi Mulia Aug 24 '19 at 13:06
  • @Alexander It's inside the function – Roi Mulia Aug 24 '19 at 13:06
  • 1
    Oh I didn't notice that. Your inner function captures self. Your two closures capture that inner function (including the context it encloses over, which includes `self`). Since your two closures are strongly referenced by `saveModalViewController` (which I assume is strongly referenced by `self`), you have a retain cycle. – Alexander Aug 24 '19 at 13:34
  • Is there a way to avoid it? I'm trying to reduce repetitive code. One idea is simply to place dismiss() out and place it in the class-scope, but there are a few dismiss options the class could have, so keeping it inside the function keeps it in context, due creates retain cycle – Roi Mulia Aug 24 '19 at 13:40
  • @Alexander commented above – Roi Mulia Aug 24 '19 at 13:41
  • Store your code in a closure rather than a local function, and use an explicit capture list to capture self weakly. Then assign that closure to both of those properties, rather than pointlessly wrapping it. – Alexander Aug 24 '19 at 13:45

2 Answers2

2

Just assign your closure to a local variable. At that point, extracting out dismiss is pointless, so just inline it:

private func setupDismissCallbacks() {     
    let dismissCallback: () -> Void = {  [weak self] in
        guard let self = self else { return }
        self.videoExporter?.cancel()
        self.rootViewController.dismiss(animated: true, completion: nil)
        self.delegate?.childCoordinatorDidFinish(self)
    }

    saveModalViewController.onButtonDismiss = dismissCallback
    saveModalViewController.onDimmedAreaDismiss = dismissCallback
}
Alexander
  • 59,041
  • 12
  • 98
  • 151
1

@Alexander explained the issue in the comments:

Your inner function captures self. Your two closures capture that inner function (including the context it encloses over, which includes self). Since your two closures are strongly referenced by saveModalViewController (which I assume is strongly referenced by self), you have a retain cycle.

You can break this cycle by not having dismiss capture self. Pass the SaveCoordinator to dismiss:

private func setupDismissCallbacks() {     
  func dismiss(_ coordinator: SaveCoordinator) {
     coordinator.videoExporter?.cancel()
     coordinator(animated: true, completion: nil)
     coordinator.delegate?.childCoordinatorDidFinish(coordinator)
  }

  // first clousre       
  saveModalViewController.onButtonDismiss = {  [weak self] in
     guard let self = self else { return }
     dismiss(self)
  }

  // second clousre  
  saveModalViewController.onDimmedAreaDismiss = { [weak self] in
     guard let self = self else { return }
     dismiss(self)
  }

}
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • DRY. Just extract out that repeated closure to a variable, and assign it twice. – Alexander Aug 24 '19 at 14:49
  • Also thought about that - Thank you for the answer - seems like it's scenario dependent, but Alexander solution suits more to this one. Thank you again! – Roi Mulia Aug 24 '19 at 19:26