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


75

У Swift 1.2 є новий атрибут з параметрами закриття у функціях, і як сказано в документації:

Це вказує на те, що параметр викликається лише коли-небудь (або передається як параметр @ noescape у виклику), що означає, що він не може пережити термін дії виклику.

У моєму розумінні, до цього ми могли б використовувати, [weak self]щоб не дозволити закриттю мати чітке посилання, наприклад, на свій клас, а self може бути нульовим або екземпляр, коли закриття виконується, але тепер це @noescapeозначає, що закриття ніколи не буде виконано якщо клас деініталізований. Чи правильно я це розумію?

І якщо я @noescapeправ , чому б я використовував закриття, передбачене регулярною функцією, коли вони поводяться дуже схоже?

Відповіді:


143

@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

Крім того, з точки зору компілятора (як зафіксовано в примітках до випуску ):

Це дозволяє здійснити незначні оптимізації продуктивності.


5
Ключовий біт для мене: " @noescapeгарантує, що закриття не буде ... використовуватися асинхронно". А це означає, що ви не можете використовувати його з мережевим кодом, який спрацьовує асинхронно.
Девід Джеймс

28

Одним із способів подумати про це є те, що КОЖНА змінна всередині блоку @noescape не повинна бути сильною (не лише самості).

Також можливі оптимізації, оскільки після виділення змінної, яка потім загортається в блок, її неможливо просто нормально звільнити в кінці функції. Отже, його потрібно розподілити в кучі та використовувати ARC для деконструкції. У Objective-C вам потрібно використовувати ключове слово "__block", щоб переконатися, що змінна створюється зручним для блоків способом. Swift автоматично визначить, що ключове слово не потрібне, але вартість однакова.

Якщо змінні передаються в блок @nosecape, тоді вони можуть бути змінними стеку, і їм не потрібно ARC для звільнення.

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

Все це призводить до швидшого та оптимальнішого коду. І зменшує накладні витрати на використання блоків @autoclosure (що дуже корисно).


Чи можете ви дати посилання, де я можу прочитати про те, що ви сказали тут, мене б дуже зацікавили ці деталі.
Даніель Надь

8

(Посилаючись на відповідь Майкла Грея вище.)

Не впевнений, чи це спеціально задокументовано для Swift, чи навіть компілятор Swift цим користується в повній мірі. Але це стандартна конструкція компілятора, щоб виділити пам'ять для екземпляра в стеку, якщо компілятор знає, що функція, що викликається, не буде намагатися зберегти вказівник на цей екземпляр у купі і видасть помилку під час компіляції, якщо функція намагається це зробити .

Це особливо вигідно при передачі нескалярних типів значень (таких як перерахування, структури, закриття), оскільки їх копіювання потенційно набагато дорожче, ніж просто передача вказівника на стек. Виділення екземпляра також значно дешевше (одна інструкція проти виклику malloc ()). Отже, подвійний виграш, якщо компілятор може здійснити цю оптимізацію.

Знову ж таки, незалежно від того, чи справді дана версія компілятора Swift дійсно повинна бути вказана командою Swift, або вам доведеться читати вихідний код, коли вони відкривають його. З цитати вище про "незначну оптимізацію" це звучить як ні, або команда Свіфта вважає це "незначною". Я вважав би це значною оптимізацією.

Імовірно, атрибут є там, щоб (принаймні в майбутньому) компілятор міг виконати цю оптимізацію.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.