Атрибут @noescape в Swift 1.2

В Swift 1.2 есть новый атрибут с параметрами закрытия в функциях, и в документации говорится:

Это означает, что параметр только когда-либо называется (или передается как @ noescape в вызове), что означает, что он не может продлите время жизни вызова.

В моем понимании, до этого мы могли бы использовать [weak self], чтобы не дать замыканию иметь сильную ссылку, например, его класс и self могут быть nil или экземпляром, когда выполняется закрытие, но теперь @noescape означает, что закрытие никогда не будет выполнено, если класс деинициализирован. Правильно ли я понимаю?

И если я прав, зачем использовать закрытие @noescape для регулярной функции, когда они ведут себя очень похоже?

Ответ 1

@noescape можно использовать следующим образом:

func doIt(code: @noescape () -> ()) {
    /* what we CAN */

    // just call it
    code()
    // pass it to another function as another `@noescape` parameter
    doItMore(code)
    // capture it in another `@noescape` closure
    doItMore {
        code()
    }

    /* what we CANNOT do *****

    // pass it as a non-`@noescape` parameter
    dispatch_async(dispatch_get_main_queue(), code)
    // store it
    let _code:() -> () = code
    // capture it in another non-`@noescape` closure
    let __code = { code() }

    */
}

func doItMore(code: @noescape () -> ()) {}

Добавление @noescape гарантирует, что закрытие не будет храниться где-нибудь, используется позднее или используется асинхронно.

С точки зрения звонящего, нет необходимости заботиться о времени жизни захваченных переменных, поскольку они используются в вызываемой функции или вообще отсутствуют. И в качестве бонуса мы можем использовать неявный self, сохраняя нас от ввода self..

func doIt(code: @noescape () -> ()) {
    code()
}

class Bar {
    var i = 0
    func some() {
        doIt {
            println(i)
            //      ^ we don't need `self.` anymore!
        }
    }
}

let bar = Bar()
bar.some() // -> outputs 0

Кроме того, с точки зрения компилятора (как описано в примечания к выпуску):

Это позволяет немного оптимизировать производительность.

Ответ 2

Один из способов подумать об этом заключается в том, что каждая переменная внутри блока @noescape не должна быть сильной (а не только самой).

Возможны также оптимизации, поскольку после выделения переменной, которая затем завершается в блок, ее нельзя просто освободить в конце функции. Поэтому он должен быть выделен в кучу и использовать ARC для деконструирования. В Objective-C вам нужно использовать ключевое слово "__block", чтобы гарантировать, что переменная создается дружественным блоком. Swift автоматически обнаружит, что ключевое слово не требуется, но стоимость одинаков.

Если переменные передаются в блок @nosecape, они могут быть переменными стека и не нуждаются в ARC для освобождения.

Теперь переменным не требуется даже нулевая ссылка на слабые переменные (которые стоят дороже, чем небезопасные указатели), поскольку они гарантированно будут "живыми" для жизни блока.

Все это приводит к более быстрому и более оптимальному коду. И уменьшает накладные расходы для использования блоков @autoclosure (которые очень полезны).

Ответ 3

(Относительно ответа Майкла Грея выше).

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

Это особенно полезно при передаче нескалярных типов значений (например, перечислений, структур, закрытий), поскольку их копирование потенциально намного дороже, чем просто передача указателя на стек. Выделение экземпляра также значительно дешевле (одна инструкция против вызова malloc()). Таким образом, это двойной выигрыш, если компилятор может сделать эту оптимизацию.

Опять же, действительно ли данная версия компилятора Swift действительно должна быть заявлена ​​командой Swift, или вам придется читать исходный код, когда они с открытым исходным кодом. Из приведенной выше цитаты о "незначительной оптимизации" это звучит так, как будто это не так, или команда Swift считает ее "незначительной". Я бы счел это значительной оптимизацией.

Предположительно, атрибут существует так, что (по крайней мере в будущем) компилятор сможет выполнить эту оптимизацию.