简体   繁体   中英

Closure recursion and retain cycles

My closure retains itself. It causes capturing all other objects inside. I can pass such objects using weak reference, but it doesn't solve the problem of retain cycle. What's the right way to do recursion with closures without retain cycles?

class Foo {
  var s = "Bar"
  deinit {
    print("deinit") // Won't be executed!
  }
}

class TestVC: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    let foo = Foo() // Weak works, but not the right solution.
    var closure: () -> Void = { return }
    closure = {
      print(foo.s)
      if true {
        return
      } else {
        closure()
      }
    }
  }

}

You have an unusual setup where your closure retains itself. Note that Swift doesn't allow you to create a weak reference to a closure.

To break the retain cycle, set closure to { } in the base case of the recursion. Here's a test macOS command-line program:

func test() {
    var closure: ((Int) -> ()) = { _ in }
    closure = { i in
        if i < 10 {
            closure(i + 1)
        } else {
            // Comment out this line for unbounded memory consumption.
            closure = { _ in }
        }
    }
    closure(0)
}

while true {
    test()
}

If you run this, its memory consumption is flat.

If you comment out the line in the base case that resets closure , its memory consumption grows without bound.

Your closure is holding foo instance reference. foo will be released as soon as the closure is released.

closure is calling itself. If we pass weak self inside closure then that should be fine. OR by resetting closure

below code should work fine.

var closure: () -> Void = { return }

override func viewDidLoad() {
    super.viewDidLoad()

    let foo = Foo()

    closure = { [weak self] in
        print(foo.s)
        if true {
            return
        } else {
            self?.closure()
        }
    }
}

OR initialize foo inside closure

override func viewDidLoad() {
    super.viewDidLoad()

    var closure: () -> Void = { return }
    closure = { [weak self] in
        let foo = Foo()
        print(foo.s)
        if true {
            return
        } else {
            self?.closure()
        }
    }
}

Turn your closure into a nested function :

class Foo {
  var s = "Bar"
  deinit {
    print("deinit")
  }
}

class TestVC: UIViewController {

  override func viewDidLoad() {
    super.viewDidLoad()

    let foo = Foo()
    func nestedFunction() {
      print(foo.s)
      if true {
        return
      } else {
        nestedFunction()
      }
    }
    nestedFunction()
  }

}

In Swift nested functions can refer to themselves synchronously (recursive functions) or asynchronously (typically for asynchronous iteration), can do so without any reference cycle, and can capture variables just as well as closures do. You can even have mutually recursive nested functions.

You could instead reset the closure-containing variable to a dummy closure once done, I am not saying this does not work, but this is very error-prone, especially when the closure calls itself asynchronously: the reset has to be done asynchronously as well in that case. Better have the lack of a reference cycle be ensured statically, as can be done most everywhere else in Swift.

(The concept used to have a bad rap due to an implementation in the C language by gcc that introduced security holes as a result of attempting to squeeze a closure reference into a C function pointer ie a code address, but Swift nested functions have nothing to do with that)

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