Як користуватися 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.