Swift @escaping dan Completion Handler

100

Saya mencoba memahami 'Penutupan' Swift dengan lebih tepat.

Tapi @escapingdan Completion Handlerterlalu sulit untuk dimengerti

Saya mencari banyak postingan Swift dan dokumen resmi, tetapi saya merasa itu masih belum cukup.

Ini adalah contoh kode dokumen resmi

var completionHandlers: [()->Void] = []

func someFunctionWithEscapingClosure(completionHandler: @escaping ()->Void){
    completionHandlers.append(completionHandler)
}

func someFunctionWithNoneescapingClosure(closure: ()->Void){
    closure()
}

class SomeClass{
    var x:Int = 10
    func doSomething(){
        someFunctionWithEscapingClosure {
            self.x = 100
            //not excute yet
        }
        someFunctionWithNoneescapingClosure {
            x = 200
        }
    }
}

let instance = SomeClass()
instance.doSomething()
print(instance.x)

completionHandlers.first?() 
print(instance.x)

Saya mendengar bahwa ada dua cara dan alasan yang menggunakan @escaping

Pertama untuk menyimpan closure, kedua untuk tujuan pengoperasian Async.

Berikut adalah pertanyaan saya :

Pertama, jika doSomethingdieksekusi maka someFunctionWithEscapingClosureakan dieksekusi dengan parameter closure dan closure tersebut akan disimpan dalam larik variabel global.

Saya pikir penutupannya adalah {self.x = 100}

Bagaimana selfdi {self.x = 100} yang disimpan dalam variabel global completionHandlersdapat terhubung ke instanceobjek itu SomeClass?

Kedua, saya memahami someFunctionWithEscapingClosureseperti ini.

Untuk menyimpan penutupan variabel lokal completionHandlerke variabel global we usingkata kunci 'completeHandlers @ escaping`!

tanpa pengembalian @escapingkata kunci someFunctionWithEscapingClosure, variabel lokal completionHandlerakan dihapus dari memori

@escaping adalah menyimpan penutupan itu dalam memori

Apakah ini benar?

Terakhir, saya hanya ingin tahu tentang keberadaan tata bahasa ini.

Mungkin ini pertanyaan yang sangat mendasar.

Jika kita ingin beberapa fungsi dijalankan setelah beberapa fungsi tertentu. Mengapa kita tidak memanggil beberapa fungsi setelah panggilan fungsi tertentu?

Apa perbedaan antara menggunakan pola di atas dan menggunakan fungsi panggilan balik pelolosan?

Dongkun Lee
sumber

Jawaban:

123

Swift Completion Handler Escaping & Non-Escaping:

Seperti yang dijelaskan Bob Lee dalam postingan blognya, Completion Handlers in Swift with Bob :

Asumsikan pengguna memperbarui aplikasi saat menggunakannya. Anda pasti ingin memberi tahu pengguna jika sudah selesai. Anda mungkin ingin memunculkan kotak yang bertuliskan, "Selamat, sekarang, Anda dapat menikmati sepenuhnya!"

Jadi, bagaimana Anda menjalankan blok kode hanya setelah unduhan selesai? Selanjutnya, bagaimana Anda menganimasikan objek tertentu hanya setelah pengontrol tampilan dipindahkan ke berikutnya? Nah, kita akan mencari tahu bagaimana mendesainnya seperti bos.

Berdasarkan daftar kosakata saya yang luas, penangan penyelesaian berarti

Lakukan hal-hal ketika sesuatu telah selesai

Postingan Bob memberikan kejelasan tentang penangan penyelesaian (dari sudut pandang pengembang, ini secara tepat mendefinisikan apa yang perlu kita pahami).

@ escape penutupan:

Ketika seseorang melewati closure dalam argumen fungsi, menggunakannya setelah badan fungsi dieksekusi dan mengembalikan compilernya. Saat fungsi berakhir, cakupan dari closure yang diteruskan ada dan ada di memori, sampai closure dijalankan.

Ada beberapa cara untuk keluar dari closure dalam mengandung function:

  • Penyimpanan: Saat Anda perlu menyimpan closure dalam variabel global, properti atau penyimpanan lain yang ada di masa lalu memori dari fungsi pemanggil, dieksekusi dan mengembalikan compiler.

  • Eksekusi Asynchronous: Ketika Anda mengeksekusi closure secara asynchronous pada antrian pengiriman, antrian tersebut akan menahan closure dalam memori untuk Anda, dapat digunakan di masa depan. Dalam hal ini Anda tidak tahu kapan penutupan akan dilaksanakan.

Saat Anda mencoba menggunakan closure dalam skenario ini, kompilator Swift akan menampilkan kesalahan:

tangkapan layar kesalahan

Untuk kejelasan lebih lanjut tentang topik ini, Anda dapat melihat posting ini di Medium .

Menambahkan satu poin lagi, yang perlu dipahami setiap pengembang iOS:

  1. Escaping Closure : Escaping closure adalah penutupan yang dipanggil setelah fungsi yang diteruskannya kembali. Dengan kata lain, itu hidup lebih lama dari fungsi yang diteruskannya.
  2. Penutupan non-pelolosan : Penutupan yang dipanggil di dalam fungsi tempat ia diteruskan, yaitu sebelum ia kembali.
Shobhakar Tiwari
sumber
@shabhakar, bagaimana jika kita menyimpan penutupan tetapi tidak meneleponnya nanti. Atau jika metode dipanggil dua kali tetapi kami memanggil penutupan hanya satu kali. Karena kita tahu hasilnya sama.
user1101733
@ user1101733 Saya rasa Anda berbicara tentang keluar dari penutupan, Penutupan tidak akan dijalankan sampai Anda tidak akan menelepon. Dalam contoh di atas, jika memanggil metode doSomething 2 kali 2 objek completeHandler akan ditambahkan ke array completeHandlers. Jika Anda mengambil objek pertama dari larik penyelesaianHandlers dan memanggilnya akan dieksekusi tetapi jumlah larik penyelesaianHandlers akan tetap sama (2).
Deepak
@ Deepak, Ya tentang penutupan pelarian. misalkan kita tidak menggunakan array dan menggunakan variabel normal untuk menyimpan referensi penutupan karena kita harus menjalankan panggilan terbaru. Akankah beberapa memori ditempati oleh penutupan sebelumnya yang tidak akan pernah dipanggil?
user1101733
1
@ user1101733 Penutupan adalah tipe referensi (seperti kelas), ketika Anda menetapkan penutupan baru ke variabel maka properti / variabel akan mengarah ke penutupan baru sehingga ARC akan membatalkan alokasi memori untuk penutupan sebelumnya.
Deepak
28

Berikut ini kelas kecil contoh yang saya gunakan untuk mengingatkan diri saya sendiri bagaimana @escaping bekerja.

class EscapingExamples: NSObject {

    var closure: (() -> Void)?

    func storageExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because `closure` is outside the scope of this
        //function - it's a class-instance level variable - and so it could be called by any other method at
        //any time, even after this function has completed. We need to tell `completion` that it may remain in memory, i.e. `escape` the scope of this
        //function.
        closure = completion
        //Run some function that may call `closure` at some point, but not necessary for the error to show up.
        //runOperation()
    }

    func asyncExample(with completion: (() -> Void)) {
        //This will produce a compile-time error because the completion closure may be called at any time
        //due to the async nature of the call which precedes/encloses it.  We need to tell `completion` that it should
        //stay in memory, i.e.`escape` the scope of this function.
        DispatchQueue.global().async {
            completion()
        }
    }

    func asyncExample2(with completion: (() -> Void)) {
        //The same as the above method - the compiler sees the `@escaping` nature of the
        //closure required by `runAsyncTask()` and tells us we need to allow our own completion
        //closure to be @escaping too. `runAsyncTask`'s completion block will be retained in memory until
        //it is executed, so our completion closure must explicitly do the same.
        runAsyncTask {
            completion()
        }
    }





    func runAsyncTask(completion: @escaping (() -> Void)) {
        DispatchQueue.global().async {
            completion()
        }
    }

}
JamesK
sumber
2
Kode ini salah. Itu melewatkan @escapingkualifikasi.
Rob
Saya paling suka inii.e. escape the scope of this function.
Gal