progammingBeignner
progammingBeignner

Reputation: 936

Weird retain cycle in Swift

so I have written some codes to better under retain cycle.

class AnotherViewController : UIViewController{

var closure : ((() -> Int) -> ())!


override func viewDidLoad() {
    super.viewDidLoad()

    closure = self.someFunctionWithNonescapingClosure

}

func someFunctionWithNonescapingClosure(closure: () -> Int) {
    //        closure()
}

}

So apparently, assigning one of the function in viewController to the property closure, it causes the retain cycle problem.

But I don know how?

Self has a strong reference to Closure But, does assigning a function in the viewController tp Closure, make a strong reference of self to closure?

Thanks

EDIT ------

Apparently, if you try this in a playground by creating the AnotherViewController, initialize it and assign it to a variable, then set the variable to nil, it will successfully deinit the AnotherViewController, but if you try it in an app, the AnotherViewController will not get deinited.

You can try to add a button to AnotherViewController and dismiss it, for convenience, the button code is this

 private func addAButton(){
    let button = UIButton()
    let buttonBounds = CGRect(x: 0, y: 0, width: 200, height: 200)
    let buttonCenter = view.center
    button.bounds = buttonBounds
    button.center = buttonCenter

    view.addSubview(button)

    button.backgroundColor = .red

    button.addTarget(self, action: #selector(goBack), for: .touchUpInside)
}

@objc func goBack(){
    dismiss(animated: true, completion: nil)
}

Upvotes: 0

Views: 923

Answers (1)

CRD
CRD

Reputation: 53000

Your closure is being assigned an instance method, which implicitly captures self, hence the cycle.

Try the following app:

import Cocoa

class Cycle
{
  var closure : ((() -> Int) -> ())!

  init()
  {
     closure = self.someFunctionWithNonescapingClosure
  }

  func someFunctionWithNonescapingClosure(closure: () -> Int)
  {
     print("Hello") // breakpoint on this line
  }
}

@NSApplicationMain
class AppDelegate: NSObject, NSApplicationDelegate
{
  @IBOutlet weak var window: NSWindow!

  func applicationDidFinishLaunching(_ aNotification: Notification)
  {
     let aCycle = Cycle()
     aCycle.closure({ 42 })
  }
}

Add a breakpoint on the print line and run the app.

The app will stop inside someFunctionWithNonescapingClosure() which is called by aCycle.closure({ 42 }).

Look at the variable display, there is a self. This is because every instance method has an implicit self argument.

In your code where does that self come from?

When the line:

closure = self.someFunctionWithNonescapingClosure

is executed Swift captures the current value of self to pass as the implicit argument to someFunctionWithNonescapingClosure(), it must do this as you are creating a closure from an instance method.

And so you have your cycle, the closure assigned to closure contains a reference to self.

To see this note the value of self when the debugger stops and then select the entry for applicationDidFinishLaunching in the stack trace and look at its aCycle variable - it has the same value as the self of someFunctionWithNonescapingClosure - there is your cycle.

In the stack trace you will also see entries like "partial apply" - this is where the supplied argument ({ 42 }) and the implicitly captured self are collected and passed to someFunctionWithNonescapingClosure().

If you change the code to:

  init()
  {
     closure = Cycle.someFunctionWithNonescapingClosure
  }

  static func someFunctionWithNonescapingClosure(closure: () -> Int)
  {
     print("Hello") // breakpoint on this line
  }

that is make someFunctionWithNonescapingClosure a class (static) method instead on an instance one then the current class instance is not captured in the closure and you will not get a cycle.

HTH

Upvotes: 1

Related Questions