Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Can't make weak reference to closure in Swift

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?

like image 361
user102008 Avatar asked Jul 12 '14 21:07

user102008


People also ask

How to declare a weak reference in Swift?

Note: The declaration of a property is strong by default. To declare a weak reference we use the weak keyword. In Swift, whenever we create an instance of a class, a reference count value is increased from 0 to 1. Similarly, if we dellocate the instance the count is decreased to 0. Let's see an example,

What is a closure in Swift?

In Swift, a closure captures its context. This means it strongly references anything referred to inside of it. If you have a closure that belongs to a class, and you call self inside that closure, you create a strong reference cycle.

What is a strong reference cycle in Swift?

In Swift, a closure captures its context. This means it strongly references anything referred to inside of it. If you have a closure that belongs to a class, and you call self inside that closure, you create a strong reference cycle. The closure strongly references to self. And self (the class) strongly references to the closure.

Why do we use weak self in Swift?

In Swift, [weak self] prevents closures from causing memory leaks in your application. This is because when you use [weak self], you tell the compiler to create a weak reference to self. In other words, the ARC can release self from memory when necessary.


Video Answer


3 Answers

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.)
like image 83
jtbandes Avatar answered Oct 19 '22 04:10

jtbandes


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)
like image 44
Keenle Avatar answered Oct 19 '22 03:10

Keenle


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
like image 31
Igor Vasilev Avatar answered Oct 19 '22 03:10

Igor Vasilev