Pertanyaan atribut @noescape di Swift 1.2


Ada atribut baru di Swift 1.2 dengan parameter penutupan dalam fungsi, dan sebagai dokumentasi mengatakan:

Ini menunjukkan bahwa   parameter hanya dipanggil (atau diteruskan sebagai   @   parameter noescape dalam panggilan), yang berarti tidak bisa   hidup lebih lama dari panggilan seumur hidup.

Dalam pemahaman saya, sebelum itu, kita bisa menggunakannya [weak self] untuk tidak membiarkan penutupan memiliki referensi yang kuat untuk misalnya kelasnya, dan diri bisa menjadi nol atau contoh ketika penutupan dijalankan, tapi sekarang, @noescape berarti bahwa penutupan tidak akan pernah bisa dilaksanakan jika kelas dideinalisasi. Apakah saya memahaminya dengan benar?

Dan jika saya benar, mengapa saya menggunakan @noescape penutupan insted dari fungsi biasa, ketika mereka berperilaku sangat mirip?


75
2018-02-10 08:51


asal


Jawaban:


@noescape dapat digunakan seperti ini:

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 () -> ()) {}

Menambah @noescape menjamin bahwa penutupan tidak akan disimpan di suatu tempat, digunakan di lain waktu, atau digunakan secara asinkron.

Dari sudut pandang penelepon, tidak perlu peduli tentang umur variabel yang ditangkap, karena mereka digunakan dalam fungsi yang disebut atau tidak sama sekali. Dan sebagai bonus, kita bisa menggunakan implisit self, menyelamatkan kita dari mengetik 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

Juga, dari sudut pandang kompiler (seperti yang didokumentasikan di lepaskan catatan):

Ini memungkinkan beberapa pengoptimalan kinerja kecil.


143
2018-02-10 09:47



Salah satu cara untuk memikirkannya, adalah bahwa SETIAP variabel di dalam blok @noescape tidak harus kuat (bukan hanya diri).

Ada juga optimisasi yang mungkin karena sekali variabel dialokasikan yang kemudian dibungkus dalam blok, itu tidak bisa hanya biasanya dialokasi pada akhir fungsi. Jadi itu harus dialokasikan pada Heap dan menggunakan ARC untuk mendekonstruksi. Dalam Objective-C, Anda harus menggunakan kata kunci "__block" untuk memastikan bahwa variabel dibuat dengan cara blok yang ramah. Swift secara otomatis akan mendeteksi itu sehingga kata kunci tidak diperlukan, tetapi biayanya sama.

Jika variabel dilewatkan ke blok @nosecape, maka mereka bisa menjadi variabel stack, dan tidak perlu ARC untuk membatalkan alokasi.

Variabel sekarang bahkan tidak perlu mereferensikan variabel lemah (yang lebih mahal daripada pointer tidak aman) karena mereka dijamin akan "hidup" untuk kehidupan blok.

Semua ini menghasilkan kode yang lebih cepat dan lebih optimal. Dan kurangi overhead untuk menggunakan blok @autoclosure (yang sangat berguna).


28
2018-02-10 20:33



(Berkaitan dengan jawaban Michael Gray di atas.)

Tidak yakin apakah ini secara khusus didokumentasikan untuk Swift, atau bahkan jika compiler Swift mengambil keuntungan penuh darinya. Tapi itu desain kompilator standar untuk mengalokasikan penyimpanan untuk sebuah instance di stack jika compiler mengetahui fungsi yang dipanggil tidak akan mencoba untuk menyimpan pointer ke instance itu di heap, dan mengeluarkan kesalahan waktu kompilasi jika fungsi tersebut mencoba untuk melakukannya. .

Ini sangat bermanfaat ketika melewatkan jenis nilai non-skalar (seperti enums, struct, closures) karena menyalinnya secara potensial jauh lebih mahal daripada sekadar mengirimkan pointer ke stack. Pengalokasian instance juga secara signifikan lebih murah (satu instruksi dibandingkan menelepon malloc ()). Jadi ini adalah kemenangan ganda jika kompiler dapat membuat pengoptimalan ini.

Sekali lagi, apakah versi tertentu dari kompilator Swift benar-benar harus dinyatakan oleh tim Swift, atau Anda harus membaca kode sumber ketika mereka membuka-sumbernya. Dari kutipan di atas tentang "pengoptimalan kecil", kedengarannya tidak seperti itu, atau tim Swift menganggapnya "kecil". Saya akan menganggapnya sebagai pengoptimalan yang signifikan.

Agaknya atribut tersebut ada sehingga (setidaknya di masa depan) compiler akan dapat melakukan optimasi ini.


8
2017-10-24 20:31