Как использовать 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

Так что это работает только с выражением без необходимости заключать его в замыкание.

eddie_c
источник
2
На самом деле последний, не работает. Это должно бытьf({2 >1}())
Руи Перес
@JoelFischer Я вижу то же самое, что и @JackyBoy. Звонок f(2 > 1)работает. Вызов f({2 > 1})не удается с error: function produces expected type 'Bool'; did you mean to call it with '()'?. Я проверил это на детской площадке и с Swift REPL.
Оле Бегеманн
Я как-то прочитал ответ от второго к последнему в качестве последнего ответа, мне придется перепроверить, но это имело бы смысл, если бы он потерпел неудачу, поскольку вы, по сути, помещаете замыкание в замыкание, насколько я понимаю.
Джоэл Фишер
3
есть блог о причине они сделали это developer.apple.com/swift/blog/?id=4
Mohamed-Ted
5
Отличное объяснение. Также обратите внимание, что в Swift 1.2 'autoclosure' теперь является атрибутом объявления параметров, поэтому этоfunc f(@autoclosure pred: () -> Bool)
Masa
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()таким образом, завернут в закрытие и не оценивается . Если мы находимся в режиме Release, он никогда не будет оценен, потому item()что не будет вызван. Таким образом, у нас есть версия print, которая не оценивает свои аргументы в режиме Release.

матовый
источник
Я опаздываю на вечеринку, но каково влияние оценки myExpensiveFunction()? Если вместо использования автозаполнения вы передадите функцию для печати print(myExpensiveFunction), как это повлияет? Спасибо.
crom87
11

Описание auto_closure из документов:

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

И вот пример, который Apple использует вместе с ним.

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.")

По сути, это означает, что вы передаете логическое выражение в качестве первого аргумента вместо замыкания, и оно автоматически создает замыкание для вас. Вот почему вы можете передать false в метод, потому что это логическое выражение, но не может передать замыкание.

Коннор
источник
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()
}
onmyway133
источник
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()
}
yoAlex5
источник