Як користуватися Swift @autoclosure


148

Я помітив, коли писав assertу Swift, що перше значення вводиться як

@autoclosure() -> Bool

з перевантаженим методом повернути загальне Tзначення, перевірити існування через LogicValue protocol.

Однак суворо дотримуйтесь питання. Схоже, потрібно @autoclosureповернути а Bool.

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

assert({() -> Bool in return false}(), "No user has been set", file: __FILE__, line: __LINE__)

Однак просто проходження Bool працює:

assert(false, "No user has been set", file: __FILE__, line: __LINE__)

Отже, що відбувається? Що таке @autoclosure?

Редагувати: @auto_closure було перейменовано@autoclosure

Відповіді:


269

Розглянемо функцію, яка бере один аргумент, просте закриття, яке не бере аргументу:

func f(pred: () -> Bool) {
    if pred() {
        print("It's true")
    }
}

Щоб викликати цю функцію, ми повинні пройти закриття

f(pred: {2 > 1})
// "It's true"

Якщо ми опустимо дужки, ми передаємо вираз і це помилка:

f(pred: 2 > 1)
// error: '>' produces 'Bool', not the expected contextual result type '() -> Bool'

@autoclosureстворює автоматичне закриття навколо виразу. Тож, коли абонент записує такий вираз 2 > 1, він автоматично загортається в замикання, щоб стати до того, {2 > 1}як воно передається f. Отже, якщо ми застосуємо це до функції f:

func f(pred: @autoclosure () -> Bool) {
    if pred() {
        print("It's true")
    }
}

f(pred: 2 > 1)
// It's true

Таким чином, це працює просто з виразом, без необхідності загортати його в закриття.


2
Насправді останній, не працює. Це повинно бутиf({2 >1}())
Rui Peres

@JoelFischer Я бачу те саме, що і @JackyBoy. Виклик f(2 > 1)працює. f({2 > 1})Помилка дзвінка з error: function produces expected type 'Bool'; did you mean to call it with '()'?. Я протестував це на ігровому майданчику та за допомогою SWIPft REPL.
Оле Бегеман

Я якось читаю другий за останнім відповідь як останню відповідь, мені доведеться ще раз перевірити, але було б сенс, якби це не вдалося, оскільки ви в основному ставите закриття всередині закриття, з того, що я розумію.
Джоель Фішер

3
є повідомлення в блозі про причину того, що вони зробили, що developer.apple.com/swift/blog/?id=4
mohamed-ted

5
Чудове пояснення. Зауважте також, що в Swift 1.2 'автозакриття' тепер є атрибутом декларації параметрів, тому цеfunc f(@autoclosure pred: () -> Bool)
Маса,

30

Ось практичний приклад - моя printпереоцінка (це Swift 3):

func print(_ item: @autoclosure () -> Any, separator: String = " ", terminator: String = "\n") {
    #if DEBUG
    Swift.print(item(), separator:separator, terminator: terminator)
    #endif
}

Коли ви говорите print(myExpensiveFunction()), моя printпереоцінка затьмарює Свіфт printі називається. myExpensiveFunction()таким чином загорнутий у закриття і не оцінюється . Якщо ми перебуваємо в режимі випуску, він ніколи не буде оцінюватися, оскільки item()не буде викликаний. Таким чином, у нас є версія print, яка не оцінює його аргументи в режимі випуску.


Я спізнююсь на партію, але який вплив має оцінювання myExpensiveFunction()?. Якщо замість використання автоматичного закриття ви передаєте функцію для друку, як print(myExpensiveFunction), який би це був вплив? Дякую.
crom87

11

Опис автоматичного закриття з документів:

Ви можете застосувати атрибут auto_closure до типу функції, що має тип параметра () і який повертає тип виразу (див. Атрибути типу). Функція автоматичного закриття фіксує неявне закриття над вказаним виразом замість самого виразу. У наступному прикладі використовується атрибут auto_closure для визначення дуже простої функції затвердження:

І ось приклад, який яблуко використовує разом з ним.

func simpleAssert(condition: @auto_closure () -> Bool, message: String) {
    if !condition() {
        println(message)
    }
}
let testNumber = 5
simpleAssert(testNumber % 2 == 0, "testNumber isn't an even number.")

По суті, це означає, що ви передаєте булевий вираз як перший аргумент замість закриття, і він автоматично створює закриття з нього для вас. Ось чому ви можете передати помилковий метод, оскільки це булевий вираз, але не може пройти закриття.


15
Зауважте, що вам насправді не потрібно @auto_closureтут використовувати . Код працює відмінно без нього: func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }. Використовуйте, @auto_closureколи вам потрібно неодноразово оцінювати аргумент (наприклад, якщо ви реалізували функцію while-подобний) або вам потрібно затримати оцінку аргументу (наприклад, якщо ви реалізували коротке замикання &&).
Натан

1
@nathan Привіт, Натане. Чи не могли б ви процитувати мені зразок щодо використання autoclosureз while-like функції? Я, здається, не розумію цього. Дякую заздалегідь.
Unheilig

@connor Можливо, ви захочете оновити свою відповідь на Swift 3.
jarora

4

Це показує корисний випадок @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

Тепер умовний вираз, переданий як перший параметр до, автоматично не згортається в вираз закриття і його можна викликати кожен раз навколо циклу

func until<L: LogicValue>(pred: @auto_closure ()->L, block: ()->()) {
    while !pred() {
        block()
    }
}

// doSomething until condition becomes true
until(condition) {
    doSomething()
}

2

Це просто спосіб позбутися від фігурних брекетів у виклику закриття, простий приклад:

    let nonAutoClosure = { (arg1: () -> Bool) -> Void in }
    let non = nonAutoClosure( { 2 > 1} )

    let autoClosure = { (arg1: @autoclosure () -> Bool) -> Void in }
    var auto = autoClosure( 2 > 1 ) // notice curly braces omitted

0

@autoclosure- це параметр функції, який приймає приготовану функцію (або повертається тип), тим часом як загальний closureприймає необроблену функцію

  • Параметр типу аргументу @autoclosure повинен бути '()'
    @autoclosure ()
  • @autoclosure приймає будь-яку функцію лише з відповідним повернутим типом
  • Результат закриття обчислюється попитом

Давайте розглянемо приклад

func testClosures() {

    //closures
    XCTAssertEqual("fooWithClosure0 foo0", fooWithClosure0(p: foo0))
    XCTAssertEqual("fooWithClosure1 foo1 1", fooWithClosure1(p: foo1))
    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: foo2))

    XCTAssertEqual("fooWithClosure2 foo2 3", fooWithClosure2(p: { (i1, i2) -> String in
        return "fooWithClosure2 " + "foo2 " + String(i1 + i2)
    }))

    //@autoclosure
    XCTAssertEqual("fooWithAutoClosure HelloWorld", fooWithAutoClosure(a: "HelloWorld"))

    XCTAssertEqual("fooWithAutoClosure foo0", fooWithAutoClosure(a: foo0()))
    XCTAssertEqual("fooWithAutoClosure foo1 1", fooWithAutoClosure(a: foo1(i1: 1)))
    XCTAssertEqual("fooWithAutoClosure foo2 3", fooWithAutoClosure(a: foo2(i1: 1, i2: 2)))

}

//functions block
func foo0() -> String {
    return "foo0"
}

func foo1(i1: Int) -> String {
    return "foo1 " + String(i1)
}

func foo2(i1: Int, i2: Int) -> String {
    return "foo2 " + String(i1 + i2)
}

//closures block
func fooWithClosure0(p: () -> String) -> String {
    return "fooWithClosure0 " + p()
}

func fooWithClosure1(p: (Int) -> String) -> String {
    return "fooWithClosure1 " + p(1)
}

func fooWithClosure2(p: (Int, Int) -> String) -> String {
    return "fooWithClosure2 " + p(1, 2)
}

//@autoclosure
func fooWithAutoClosure(a: @autoclosure () -> String) -> String {
    return "fooWithAutoClosure " + a()
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.