Cara menggunakan Swift @autoclosure

148

Saya perhatikan ketika menulis assertdi Swift bahwa nilai pertama diketikkan sebagai

@autoclosure() -> Bool

dengan metode kelebihan beban untuk mengembalikan Tnilai generik , untuk menguji keberadaan melalui LogicValue protocol.

Namun berpegang erat pada pertanyaan yang ada. Tampaknya ingin @autoclosureyang mengembalikan a Bool.

Menulis penutupan yang sebenarnya tanpa parameter dan mengembalikan Bool tidak berfungsi, ia ingin saya memanggil penutupan untuk membuatnya dikompilasi, seperti:

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

Namun demikian, hanya melewati karya Bool:

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

Jadi apa yang terjadi? Apa @autoclosure?

Sunting: @auto_closure diubah namanya@autoclosure

Joel Fischer
sumber

Jawaban:

269

Pertimbangkan fungsi yang mengambil satu argumen, penutupan sederhana yang tidak menggunakan argumen:

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

Untuk memanggil fungsi ini, kita harus memberi penutupan

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

Jika kami menghilangkan kawat gigi, kami menyampaikan ekspresi dan itu adalah kesalahan:

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

@autoclosuremembuat penutupan otomatis di sekitar ekspresi. Jadi ketika penelepon menulis ekspresi seperti 2 > 1, itu secara otomatis dibungkus menjadi menjadi {2 > 1}sebelum dilewatkan f. Jadi jika kita menerapkan ini pada fungsi f:

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

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

Jadi itu berfungsi hanya dengan ekspresi tanpa harus membungkusnya dengan penutupan.

eddie_c
sumber
2
Sebenarnya yang terakhir, tidak berhasil. Seharusnyaf({2 >1}())
Rui Peres
@ JoelFischer Saya melihat hal yang sama dengan @JackyBoy. Memanggil f(2 > 1)berhasil. Panggilan f({2 > 1})gagal dengan error: function produces expected type 'Bool'; did you mean to call it with '()'?. Saya mengujinya di taman bermain dan dengan Swift REPL.
Ole Begemann
Saya entah bagaimana membaca jawaban kedua hingga terakhir sebagai jawaban terakhir, saya harus memeriksa ulang, tetapi akan masuk akal jika gagal, karena Anda pada dasarnya meletakkan penutup di dalam penutup, dari apa yang saya mengerti.
Joel Fischer
3
ada posting blog tentang alasan mereka melakukan itu developer.apple.com/swift/blog/?id=4
mohamed-ted
5
Penjelasan yang bagus. Perhatikan juga bahwa dalam Swift 1.2 'autoclosure' sekarang merupakan atribut dari deklarasi parameter, jadifunc f(@autoclosure pred: () -> Bool)
Masa
30

Inilah contoh praktis - printpenggantian saya (ini Swift 3):

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

Ketika Anda mengatakan print(myExpensiveFunction()), printoverride saya menaungi Swift printdan dipanggil. myExpensiveFunction()dengan demikian dibungkus dalam penutupan dan tidak dievaluasi . Jika kita dalam mode Release, itu tidak akan pernah dievaluasi, karena item()tidak akan dipanggil. Jadi kami memiliki versi printyang tidak mengevaluasi argumennya dalam mode Rilis.

matt
sumber
Saya terlambat ke pesta, tetapi apa dampak dari evaluasi myExpensiveFunction()? Jika alih-alih menggunakan autoclosure, Anda melewatkan fungsi untuk mencetak print(myExpensiveFunction), apa dampaknya? Terima kasih.
crom87
11

Deskripsi auto_closure dari dokumen:

Anda dapat menerapkan atribut auto_closure ke tipe fungsi yang memiliki tipe parameter () dan yang mengembalikan tipe ekspresi (lihat Jenis Atribut). Fungsi autoclosure menangkap penutupan tersirat atas ekspresi yang ditentukan, bukan ekspresi itu sendiri. Contoh berikut menggunakan atribut auto_closure dalam mendefinisikan fungsi penegasan yang sangat sederhana:

Dan inilah contoh yang digunakan apel bersama dengannya.

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

Pada dasarnya apa yang Anda maksudkan adalah Anda memberikan ekspresi boolean sebagai argumen pertama alih-alih penutupan dan secara otomatis membuat penutupan untuk Anda. Itu sebabnya Anda bisa memasukkan false ke dalam metode karena itu adalah ekspresi boolean, tetapi tidak dapat melewati penutupan.

Connor
sumber
15
Perhatikan bahwa Anda sebenarnya tidak perlu menggunakannya di @auto_closuresini. Kode berfungsi dengan baik tanpa itu: func simpleAssert(condition: Bool, message: String) { if !condition { println(message) } }. Gunakan @auto_closureketika Anda perlu mengevaluasi argumen berulang kali (misalnya, jika Anda menerapkan whilefungsi-like) atau Anda perlu menunda evaluasi argumen (misalnya, jika Anda menerapkan hubungan arus pendek &&).
nathan
1
@nathan Hai, nathan. Bisakah Anda memberi saya contoh tentang penggunaan autoclosuredengan whilefungsi seperti? Saya sepertinya tidak memahaminya. Terima kasih banyak sebelumnya.
Unheilig
@connor Anda mungkin ingin memperbarui jawaban Anda untuk Swift 3.
jarora
4

Ini menunjukkan kasus bermanfaat dari @autoclosure https://airspeedvelocity.net/2014/06/28/extending-the-swift-language-is-cool-but-be-careful/

Sekarang, ekspresi kondisional dilewatkan sebagai parameter pertama hingga akan secara otomatis dibungkus menjadi ekspresi penutupan dan dapat dipanggil setiap kali di sekitar loop

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

// doSomething until condition becomes true
until(condition) {
    doSomething()
}
onmyway133
sumber
2

Ini hanya cara untuk menghilangkan kurung kurawal dalam panggilan penutupan, contoh sederhana:

    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
Polisi
sumber
0

@autoclosureadalah parameter fungsi yang menerima fungsi yang dimasak (atau tipe yang dikembalikan) sementara seorang umum closuremenerima fungsi mentah

  • @auto jenis parameter argumen harus '()'
    @autoclosure ()
  • @autoclosure menerima fungsi apa pun dengan hanya tipe yang dikembalikan sesuai
  • Hasil penutupan dihitung berdasarkan permintaan

Mari kita lihat contohnya

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
sumber