Saya melihat dua pola umum untuk blok di Objective-C. Salah satunya adalah sepasang keberhasilan: / kegagalan: blok, yang lain adalah penyelesaian tunggal: blok.
Misalnya, katakanlah saya memiliki tugas yang akan mengembalikan objek secara tidak sinkron dan tugas itu mungkin gagal. Pola pertama adalah -taskWithSuccess:(void (^)(id object))success failure:(void (^)(NSError *error))failure
. Pola kedua adalah -taskWithCompletion:(void (^)(id object, NSError *error))completion
.
berhasil: / gagal:
[target taskWithSuccess:^(id object) {
// W00t! I've got my object
} failure:^(NSError *error) {
// Oh noes! report the failure.
}];
penyelesaian:
[target taskWithCompletion:^(id object, NSError *error) {
if (object) {
// W00t! I've got my object
} else {
// Oh noes! report the failure.
}
}];
Pola mana yang disukai? Apa kelebihan dan kekurangannya? Kapan Anda akan menggunakan satu di atas yang lain?
design-patterns
objective-c
Jeffery Thomas
sumber
sumber
Jawaban:
Callback penyelesaian (berlawanan dengan pasangan sukses / gagal) lebih umum. Jika Anda perlu menyiapkan beberapa konteks sebelum berurusan dengan status pengembalian, Anda dapat melakukannya tepat sebelum klausa "jika (objek)". Dalam kasus sukses / gagal Anda harus menduplikasi kode ini. Ini tergantung pada semantik panggilan balik, tentu saja.
sumber
-task…
bisa mengembalikan objek, tetapi objek tidak dalam kondisi yang benar, maka Anda masih perlu penanganan kesalahan dalam kondisi sukses.Saya akan mengatakan, apakah API menyediakan satu handler penyelesaian atau sepasang blok sukses / gagal, pada dasarnya adalah masalah preferensi pribadi.
Kedua pendekatan memiliki pro dan kontra, meskipun hanya ada sedikit perbedaan.
Pertimbangkan bahwa ada juga varian lebih lanjut, misalnya di mana satu selesai handler mungkin hanya satu parameter menggabungkan hasil akhirnya atau kesalahan yang potensial:
Tujuan dari tanda tangan ini adalah bahwa handler penyelesaian dapat digunakan secara umum di API lain.
Sebagai contoh dalam Kategori untuk NSArray ada metode
forEachApplyTask:completion:
yang secara berurutan memanggil tugas untuk setiap objek dan memecah loop IFF ada kesalahan. Karena metode ini sendiri juga tidak sinkron, ia memiliki penangan penyelesaian juga:Faktanya,
completion_t
sebagaimana didefinisikan di atas cukup umum dan cukup untuk menangani semua skenario.Namun, ada cara lain untuk tugas asinkron untuk mengirimkan notifikasi penyelesaiannya ke situs panggilan:
Janji
Janji, juga disebut "Berjangka", "Ditangguhkan" atau "Tertunda" merupakan hasil akhirnya dari tugas asinkron (lihat juga: wiki Futures dan janji ).
Awalnya, sebuah janji berada dalam kondisi "tertunda". Artinya, "nilainya" belum dievaluasi dan belum tersedia.
Di Objective-C, sebuah Janji akan menjadi objek biasa yang akan dikembalikan dari metode asinkron seperti yang ditunjukkan di bawah ini:
Sementara itu, tugas asinkron mulai mengevaluasi hasilnya.
Perhatikan juga, bahwa tidak ada handler penyelesaian. Sebaliknya, Janji akan memberikan cara yang lebih kuat di mana situs panggilan dapat memperoleh hasil akhirnya dari tugas asinkron, yang akan segera kita lihat.
Tugas asinkron, yang menciptakan objek janji, HARUS akhirnya "menyelesaikan" janjinya. Itu berarti, karena suatu tugas dapat berhasil atau gagal, itu HARUS "memenuhi" janji lewat itu hasil yang dievaluasi, atau HARUS "menolak" janji lewat itu kesalahan yang menunjukkan alasan kegagalan.
Ketika Janji telah diselesaikan, itu tidak dapat mengubah statusnya lagi, termasuk nilainya.
Setelah janji telah diselesaikan, situs panggilan dapat memperoleh hasilnya (apakah gagal atau berhasil). Bagaimana ini dilakukan tergantung pada apakah janji diimplementasikan menggunakan gaya sinkron atau asinkron.
Sebuah Janji dapat diimplementasikan dalam sinkron atau gaya asynchronous yang mengarah ke salah blocking masing-masing non-blocking semantik.
Dalam gaya sinkron untuk mengambil nilai janji, situs panggilan akan menggunakan metode yang akan memblokir utas saat ini sampai setelah janji telah diselesaikan oleh tugas asinkron dan hasil akhirnya tersedia.
Dalam gaya yang tidak sinkron, situs panggilan akan mendaftarkan panggilan balik atau blok penangan yang dipanggil segera setelah janji telah diselesaikan.
Ternyata gaya sinkron memiliki sejumlah kelemahan signifikan yang secara efektif mengalahkan kelebihan tugas asinkron. Artikel menarik tentang implementasi "futures" yang saat ini cacat dalam standar C ++ 11 lib dapat dibaca di sini: Patah janji – C ++ 0x futures .
Bagaimana, di Objective-C, sebuah situs panggilan akan mendapatkan hasilnya?
Yah, mungkin yang terbaik untuk menunjukkan beberapa contoh. Ada beberapa perpustakaan yang menerapkan Janji (lihat tautan di bawah).
Namun, untuk cuplikan kode berikutnya, saya akan menggunakan implementasi tertentu dari perpustakaan Promise, tersedia di GitHub RXPromise . Saya penulis RXPromise.
Implementasi lain mungkin memiliki API yang serupa, tetapi mungkin ada perbedaan kecil dan mungkin perbedaan dalam sintaksis. RXPromise adalah versi Objective-C dari spesifikasi Promise / A + yang mendefinisikan standar terbuka untuk implementasi yang kuat dan interoperable dari janji dalam JavaScript.
Semua perpustakaan janji yang tercantum di bawah ini menerapkan gaya asinkron.
Ada perbedaan yang cukup signifikan di antara implementasi yang berbeda. RXPromise secara internal menggunakan lib pengiriman, sepenuhnya aman dari benang, sangat ringan, dan juga menyediakan sejumlah fitur bermanfaat lainnya, seperti pembatalan.
Situs panggilan mendapatkan hasil akhirnya dari tugas asinkron melalui penangan “pendaftaran”. "Janji / spesifikasi A +" mendefinisikan metode
then
.Metode
then
Dengan RXPromise tampilannya sebagai berikut:
di mana successHandler adalah blok yang dipanggil ketika janji telah "dipenuhi" dan errorHandler adalah blok yang dipanggil ketika janji telah "ditolak".
Dalam RXPromise, blok pawang memiliki tanda tangan berikut:
Success_handler memiliki hasil parameter yang jelas merupakan hasil akhirnya dari tugas asinkron. Demikian juga, error_handler memiliki kesalahan parameter yang merupakan kesalahan yang dilaporkan oleh tugas asinkron ketika gagal.
Kedua blok memiliki nilai pengembalian. Tentang nilai pengembalian ini, akan menjadi jelas segera.
Di RXPromise,
then
adalah properti yang mengembalikan blok. Blok ini memiliki dua parameter, blok penangan sukses dan blok penangan kesalahan. Penangan harus ditentukan oleh situs panggilan.Jadi, ungkapan itu
promise.then(success_handler, error_handler);
adalah bentuk singkat dariKami bahkan dapat menulis kode yang lebih ringkas:
Kode tersebut berbunyi: "Jalankan doSomethingAsync, ketika berhasil, kemudian jalankan penangan sukses".
Di sini, penangan kesalahan adalah
nil
yang berarti, jika terjadi kesalahan, itu tidak akan ditangani dalam janji ini.Fakta penting lainnya adalah bahwa memanggil blok yang dikembalikan dari properti
then
akan mengembalikan Janji:Saat memanggil blok yang dikembalikan dari properti
then
, "penerima" mengembalikan Janji baru , janji anak . Penerima menjadi janji orang tua .Apa artinya?
Nah, karena ini kita dapat "rantai" tugas-tugas tidak sinkron yang secara efektif dieksekusi secara berurutan.
Selain itu, nilai pengembalian dari salah satu pawang akan menjadi "nilai" dari janji yang dikembalikan. Jadi, jika tugas berhasil dengan hasil akhirnya @ "OK", janji yang dikembalikan akan "diselesaikan" (yaitu "terpenuhi") dengan nilai @ "OK":
Demikian juga, ketika tugas asinkron gagal, janji yang dikembalikan akan diselesaikan (yaitu "ditolak") dengan kesalahan.
Pawang juga dapat mengembalikan janji lain. Misalnya ketika pawang itu menjalankan tugas asinkron lainnya. Dengan mekanisme ini, kita dapat "rantai" tugas tidak sinkron:
Jika tidak ada janji anak, nilai kembali tidak berpengaruh.
Contoh yang lebih kompleks:
Di sini, kita mengeksekusi
asyncTaskA
,asyncTaskB
,asyncTaskC
danasyncTaskD
berurutan - dan masing-masing tugas berikutnya mengambil hasil dari tugas sebelumnya sebagai masukan:"Rantai" seperti itu juga disebut "kelanjutan".
Menangani kesalahan
Janji membuatnya sangat mudah untuk menangani kesalahan. Kesalahan akan "diteruskan" dari orangtua ke anak jika tidak ada penangan kesalahan yang ditentukan dalam janji orangtua. Kesalahan akan diteruskan ke rantai sampai seorang anak menanganinya. Dengan demikian, dengan memiliki rantai di atas, kita dapat menerapkan penanganan kesalahan hanya dengan menambahkan "kelanjutan" lain yang berhubungan dengan kesalahan potensial yang mungkin terjadi di mana saja di atas :
Ini mirip dengan gaya sinkron yang mungkin lebih akrab dengan penanganan pengecualian:
Janji secara umum memiliki fitur berguna lainnya:
Misalnya, memiliki referensi ke janji, melalui
then
seseorang dapat "mendaftar" sebanyak penangan yang diinginkan. Di RXPromise, pendaftaran penangan dapat terjadi kapan saja dan dari utas apa pun karena sepenuhnya aman dari benang.RXPromise memiliki beberapa fitur fungsional yang lebih bermanfaat, tidak diharuskan oleh spesifikasi Promise / A +. Salah satunya adalah "pembatalan".
Ternyata "pembatalan" adalah fitur yang tak ternilai dan penting. Misalnya situs panggilan yang memegang referensi janji dapat mengirimkannya
cancel
pesan untuk menunjukkan bahwa itu tidak lagi tertarik pada hasil akhirnya.Bayangkan saja tugas asinkron yang memuat gambar dari web dan yang akan ditampilkan di view controller. Jika pengguna menjauh dari pengontrol tampilan saat ini, pengembang dapat menerapkan kode yang mengirim pesan pembatalan ke imagePromise , yang pada gilirannya memicu penangan kesalahan yang ditentukan oleh Operasi Permintaan HTTP di mana permintaan akan dibatalkan.
Di RXPromise, pesan pembatalan hanya akan diteruskan dari orangtua ke anak-anaknya, tetapi tidak sebaliknya. Artinya, janji "root" akan membatalkan semua janji anak-anak. Tetapi janji anak hanya akan membatalkan "cabang" di mana itu adalah orang tua. Pesan yang dibatalkan juga akan diteruskan ke anak-anak jika janji telah diselesaikan.
Tugas asinkron itu sendiri dapat mendaftarkan handler untuk janjinya sendiri, dan dengan demikian dapat mendeteksi ketika orang lain membatalkannya. Mungkin kemudian secara prematur berhenti melakukan tugas yang mungkin panjang dan mahal.
Berikut adalah beberapa implementasi lain dari Janji dalam Objective-C yang ditemukan di GitHub:
https://github.com/Schoonology/aplus-objc
https://github.com/affablebloke/deferred-objective-c
https://github.com/bww/FutureKit
https://github.com/jkubicek/JKPromises
https://github.com/Strilanc/ObjC-CollapsingFutures
https://github.com/b52/OMPromises
https://github.com/mproberts/objc-promise
https://github.com/klaaspieter/Promise
https: //github.com/jameswomack/Promise
https://github.com/nilfs/promise-objc
https://github.com/mxcl/PromiseKit
https://github.com/apleshkov/promises-aplus
https: // github.com/KptainO/Rebelle
dan implementasi saya sendiri: RXPromise .
Daftar ini kemungkinan tidak lengkap!
Saat memilih perpustakaan ketiga untuk proyek Anda, harap periksa dengan cermat apakah implementasi perpustakaan mengikuti prasyarat yang tercantum di bawah ini:
Sebuah perpustakaan janji yang dapat diandalkan HARUS aman!
Ini semua tentang pemrosesan yang tidak sinkron, dan kami ingin menggunakan banyak CPU dan mengeksekusi di utas yang berbeda secara bersamaan bila memungkinkan. Hati-hati, sebagian besar implementasi tidak aman utas!
Penangan AKAN disebut asinkron yang berkenaan dengan situs panggilan! Selalu, dan apa pun yang terjadi!
Setiap implementasi yang layak juga harus mengikuti pola yang sangat ketat ketika menjalankan fungsi asinkron. Banyak pelaksana yang cenderung "mengoptimalkan" kasing, di mana pawang akan dipanggil secara serempak ketika janji sudah diselesaikan ketika pawang akan terdaftar. Ini dapat menyebabkan segala macam masalah. Lihat Jangan lepaskan Zalgo! .
Seharusnya juga ada mekanisme untuk membatalkan janji.
Kemungkinan untuk membatalkan tugas asinkron sering menjadi persyaratan dengan prioritas tinggi dalam analisis kebutuhan. Jika tidak, pasti akan diajukan permintaan tambahan dari pengguna beberapa waktu kemudian setelah aplikasi dirilis. Alasannya harus jelas: tugas apa pun yang mungkin terhenti atau terlalu lama untuk diselesaikan, harus dibatalkan oleh pengguna atau dengan batas waktu. Perpustakaan janji yang layak harus mendukung pembatalan.
sumber
Saya menyadari ini adalah pertanyaan lama tetapi saya harus menjawabnya karena jawaban saya berbeda dari yang lain.
Bagi mereka yang mengatakan itu masalah preferensi pribadi, saya harus tidak setuju. Ada alasan yang baik, logis, untuk memilih satu daripada yang lain ...
Dalam kasus penyelesaian, blok Anda diserahkan dua objek, satu mewakili kesuksesan sementara yang lain mewakili kegagalan ... Jadi apa yang Anda lakukan jika keduanya nihil? Apa yang Anda lakukan jika keduanya memiliki nilai? Ini adalah pertanyaan yang dapat dihindari pada waktu kompilasi dan karena itu seharusnya. Anda menghindari pertanyaan-pertanyaan ini dengan memiliki dua blok terpisah.
Memiliki blok keberhasilan dan kegagalan yang terpisah membuat kode Anda diverifikasi secara statis.
Perhatikan bahwa segalanya berubah dengan Swift. Di dalamnya, kita dapat menerapkan gagasan
Either
enum sehingga blok penyelesaian tunggal dijamin memiliki objek atau kesalahan, dan harus memiliki salah satunya. Jadi untuk Swift, satu blok lebih baik.sumber
Saya menduga itu akan berakhir menjadi preferensi pribadi ...
Tapi saya lebih suka blok kesuksesan / kegagalan yang terpisah. Saya suka memisahkan logika keberhasilan / kegagalan. Jika Anda telah menyarangkan kesuksesan / kegagalan, Anda akan berakhir dengan sesuatu yang akan lebih mudah dibaca (menurut saya setidaknya).
Sebagai contoh yang relatif ekstrim dari sarang seperti ini, berikut adalah beberapa Ruby yang menunjukkan pola ini.
sumber
Ini terasa seperti solusi lengkap, tapi saya rasa tidak ada jawaban yang tepat di sini. Saya pergi dengan blok penyelesaian hanya karena penanganan kesalahan mungkin masih perlu dilakukan dalam kondisi sukses ketika menggunakan blok sukses / gagal.
Saya pikir kode akhir akan terlihat seperti
atau sederhana
Bukan potongan kode terbaik dan bersarang akan semakin buruk
Saya pikir saya akan pergi mope untuk sementara waktu.
sumber