Can't make weak reference to closure in Swift

2019-03-18 10:59发布

问题:

Update: I tried writing it without making it weak, and there doesn't seem to be a leak. So maybe the question is no longer necessary.


In Objective-C ARC, when you want to have a closure be able to use itself inside of the closure, the block cannot capture a strong reference to itself, or it will be a retain cycle, so instead you can make the closure capture a weak reference to itself, like so:

// This is a simplified example, but there are real uses of recursive closures
int (^fib)(int);
__block __weak int (^weak_fib)(int);
weak_fib = fib = ^(int n) {
  if (n < 2)
    return n;
  else
    return weak_fib(n-1) + weak_fib(n-2);
};

I tried to translate this to Swift:

var fib: (Int -> Int)?
fib = { [weak fib] (n: Int) in // 'weak' cannot be applied to non-class type 'Int -> Int'
  if n < 2 {
    return n
  } else {
    return fib!(n-1) + fib!(n-2)
  }
}

However, the Swift compiler won't allow me to declare a function to be captured weakly ('weak' cannot be applied to non-class type 'Int -> Int'). [unowned fib] also doesn't work ('unowned' cannot be applied to non-class type '(Int -> Int)?').

I know that functions are not class types in Swift. However, they are reference types and they do participate in reference counting. Therefore, shouldn't there be a way to make them weak or unowned references?

How would I write a recursive closure in Swift that doesn't have a retain cycle?

回答1:

Looks like this isn't possible at the moment; you might want to file a bug.

But you can use an actual function to achieve the same thing:

func fib(n: Int) -> Int {
    if n < 2 {
        return n
    } else {
        return fib(n-1) + fib(n-2)
    }
}

fib(10) // 55

Computer science fun time! For a more direct translation of your code, we can use the Z combinator, with help from Swift's built-in curried function definitions:

func Z<T, U>(f: (T -> U, T) -> U)(x: T) -> U {
    return f(Z(f), x)
}

let fib = Z { (fib: Int -> Int, n: Int) in
    if n < 2 {
        return n
    } else {
        return fib(n-1) + fib(n-2)
    }
}

fib(x: 10) // 55

// (Note the name 'x' should not be required here.
//  It seems seems to be a bug in Beta 3, since the curried function in the
//  Swift guide doesn't work as advertised either.)


回答2:

It looks like there is no way to declare weak/unowned reference to the function; at lest for now. As workaround you can wrap your code in a class definition and have unowned reference to the instance:

class Fib {
    @lazy var calc:(Int) -> Int = {
        [unowned self] (n: Int) -> Int in
        if n < 2 {
            return n
        } else {
            return self.calc(n-1) + self.calc(n-2)
        }
    }
}

Usage:

let f = Fib()
let result = f.calc(6)


回答3:

The problem is well described here:

https://xiliangchen.wordpress.com/2014/08/04/recursive-closure-and-y-combinator-in-swift/

In short:

  1. Recursive closures in Swift do create strong reference cycles.
  2. There is no direct native approach in Swift to solve this problem. Capture lists don't work with closure type.
  3. Still there is a way to solve the problem: Y-combinators


回答4:

You can get weak reference like this

weak var = self // Same as weak, on dealloc property will be set to nil. So var is optional

Or

unowned(unsafe) var weakSelf = self // Same as unretained