Невозможно сделать ссылку на закрытие в Swift

Обновление: Я пробовал писать его, не делая его слабым, и, похоже, утечки нет. Поэтому, возможно, вопрос больше не нужен.


В Objective-C ARC, когда вы хотите иметь закрытие, чтобы иметь возможность использовать себя внутри замыкания, блок не может фиксировать сильную ссылку на себя, или это будет цикл сохранения, так что вместо этого вы можете сделать замыкание фиксирует слабую ссылку на себя, например:

// 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);
};

Я попытался перевести это на 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)
  }
}

Однако компилятор Swift не позволит мне объявить, что функция будет зафиксирована слабо ('weak' cannot be applied to non-class type 'Int -> Int'). [unowned fib] также не работает ('unowned' cannot be applied to non-class type '(Int -> Int)?').

Я знаю, что функции не являются типами классов в Swift. Однако они являются ссылочными типами, и они участвуют в подсчете ссылок. Следовательно, не должно быть способа сделать их слабыми или неназванными ссылками?

Как мне написать рекурсивное замыкание в Swift, которое не имеет цикла сохранения?

Ответ 1

Похоже, на данный момент это невозможно. вы можете захотеть указать ошибку.

Но вы можете использовать фактическое func для достижения того же:

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

fib(10) // 55

Время для компьютерных наук! Для более прямого перевода вашего кода мы можем использовать комбинатор Z, с помощью встроенного Swift - в определениях карри:

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

Похоже, что нет способа объявить слабую/незаслуженную ссылку на функцию; на данный момент. В качестве обходного пути вы можете обернуть свой код в определении класса и иметь ссылку unowned к экземпляру:

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)
        }
    }
}

Использование:

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

Ответ 3

Проблема хорошо описана здесь:

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

Короче:

  • Рекурсивные замыкания в Swift действительно создают сильные ссылочные циклы.
  • В Swift нет прямого родного подхода для решения этой проблемы. Списки захвата не работают с типом закрытия.
  • Есть еще способ решить проблему: Y-комбинаторы

Ответ 4

Вы можете получить слабую ссылку, подобную этой

слабый var = self//То же, что и слабый, для свойства dealloc будет установлено значение nil. Поэтому var необязателен

или

unowned (небезопасный) var weakSelf = self//То же, что и незавершенный