Почему закрытие требует явного "самого себя", когда все они не выполняются по умолчанию в Swift 3?

Я заметил, что в Swift 2.2 закрытие, помеченное как неэкранированное с помощью @noescape, не требует явного self. В Swift 3 все блокировки по умолчанию не экранируются и теперь требуют, чтобы они были помечены @escaping, если вы хотите, чтобы они могли сбежать.

Учитывая, что все блокировки в Swift 3 по умолчанию не экранируются, почему они требуют явного self?

final class SomeViewController: NSViewController {

    var someClosure: () -> () = { _ in }

    override func viewDidLoad() {
        super.viewDidLoad()

        someClosure = {
            view.layer = CALayer() // ERROR: Implicit use of `self` in closure; use `self.` to make capture semantics explicit
        }
    }
}

Ответы

Ответ 1

В Swift 3 все блокировки по умолчанию не выполняются

Нет, в Swift 3 аргументы функции закрытия (т.е. функциональные входы, которые являются самими функциями) по умолчанию не экранируются (согласно SE-0103). Например:

class A {

    let n = 5
    var bar : () -> Void = {}

    func foo(_ closure: () -> Void) {
        bar = closure // As closure is non-escaping, it is illegal to store it.
    }

    func baz() {
        foo {
            // no explict 'self.' required in order to capture n,
            // as foo closure argument is non-escaping,
            // therefore n is guaranteed to only be captured for the lifetime of foo(_:)
            print(n)
        }
    }
}

Так как closure в приведенном выше примере не является экранирующим, ему запрещено хранить или захватывать, тем самым ограничивая его время жизни на время жизни функции foo(_:). Это означает, что любые значения, которые он фиксирует, гарантированно не остаются захваченными после выхода функции - это означает, что вам не нужно беспокоиться о проблемах, которые могут возникнуть при захвате, например, о циклах сохранения.

Однако закрытое хранимое свойство (например, bar в приведенном выше примере) по определению экранируется (было бы бессмысленно отмечать его @noescape), поскольку его время жизни не ограничивается данной функцией - оно (и поэтому все его захваченные переменные) останется в памяти, пока данный экземпляр останется в памяти. Поэтому это может легко привести к таким проблемам, как сохранение циклов, поэтому вам нужно использовать явный self., чтобы сделать семантику захвата явной.

Фактически, примерный код вашего примера создаст цикл сохранения при вызове viewDidLoad(), поскольку someClosure сильно фиксирует self и self сильно ссылается на someClosure, поскольку это хранимое свойство.


Конечно, одно место, где вы могли бы использовать атрибут @noescape, - это замыкание, в котором есть локальная переменная в функции. Такое закрытие будет иметь прогнозируемое время жизни, если оно не хранится вне функции или не захвачено. Например:

class A {

    let n = 5

    func foo() {

        let f : @noescape () -> Void = {
            print(n)
        }

        f()
    }
}

К сожалению, поскольку @noescape удаляется в Swift 3, это будет невозможно (что интересно в Xcode 8 GM, это возможно, но дает предупреждение об устаревании). Как Jon Shier говорит, ему нужно ждать, пока он будет добавлен к языку, что может или не может произойти.

Ответ 2

Сохраненные закрытия считаются экранированными по умолчанию, даже если они не являются действительно. Нет способа отметить, что они не ускользают, поэтому мы застреваем таким образом, пока они не добавят @noescape обратно на язык, который они могут или не могут делать. См. это обсуждение в списке рассылки быстрой эволюции.