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:
typedef void (^completion_t)(id result);
- (void) taskWithCompletion:(completion_t)completionHandler;
[self taskWithCompletion:^(id result){
if ([result isKindOfError:[NSError class]) {
NSLog(@"Error: %@", result);
}
else {
...
}
}];
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:
typedef void (^completion_t)(id result);
typedef void (^task_t)(id input, completion_t);
- (void) forEachApplyTask:(task_t)task completion:(completion_t);
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:
- (Promise*) doSomethingAsync;
! Keadaan awal suatu Janji adalah "tertunda".
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.
! Suatu tugas pada akhirnya harus menyelesaikan janjinya.
Ketika Janji telah diselesaikan, itu tidak dapat mengubah statusnya lagi, termasuk nilainya.
! Janji hanya bisa diselesaikan satu kali .
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:
promise.then(successHandler, errorHandler);
di mana successHandler adalah blok yang dipanggil ketika janji telah "dipenuhi" dan errorHandler adalah blok yang dipanggil ketika janji telah "ditolak".
! then
digunakan untuk mendapatkan hasil akhirnya dan untuk menentukan sukses atau penangan kesalahan.
Dalam RXPromise, blok pawang memiliki tanda tangan berikut:
typedef id (^success_handler_t)(id result);
typedef id (^error_handler_t)(NSError* error);
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.
! Penangan harus ditentukan oleh situs panggilan.
Jadi, ungkapan itu promise.then(success_handler, error_handler);
adalah bentuk singkat dari
then_block_t block promise.then;
block(success_handler, error_handler);
Kami bahkan dapat menulis kode yang lebih ringkas:
doSomethingAsync
.then(^id(id result){
…
return @“OK”;
}, nil);
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:
! then(...)
mengembalikan Janji
Saat memanggil blok yang dikembalikan dari properti then
, "penerima" mengembalikan Janji baru , janji anak . Penerima menjadi janji orang tua .
RXPromise* rootPromise = asyncA();
RXPromise* childPromise = rootPromise.then(successHandler, nil);
assert(childPromise.parent == rootPromise);
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":
RXPromise* returnedPromise = asyncA().then(^id(id result){
return @"OK";
}, nil);
...
assert([[returnedPromise get] isEqualToString:@"OK"]);
Demikian juga, ketika tugas asinkron gagal, janji yang dikembalikan akan diselesaikan (yaitu "ditolak") dengan kesalahan.
RXPromise* returnedPromise = asyncA().then(nil, ^id(NSError* error){
return error;
});
...
assert([[returnedPromise get] isKindOfClass:[NSError class]]);
Pawang juga dapat mengembalikan janji lain. Misalnya ketika pawang itu menjalankan tugas asinkron lainnya. Dengan mekanisme ini, kita dapat "rantai" tugas tidak sinkron:
RXPromise* returnedPromise = asyncA().then(^id(id result){
return asyncB(result);
}, nil);
! Nilai kembali blok pawang menjadi nilai janji anak.
Jika tidak ada janji anak, nilai kembali tidak berpengaruh.
Contoh yang lebih kompleks:
Di sini, kita mengeksekusi asyncTaskA
, asyncTaskB
, asyncTaskC
dan asyncTaskD
berurutan - dan masing-masing tugas berikutnya mengambil hasil dari tugas sebelumnya sebagai masukan:
asyncTaskA()
.then(^id(id result){
return asyncTaskB(result);
}, nil)
.then(^id(id result){
return asyncTaskC(result);
}, nil)
.then(^id(id result){
return asyncTaskD(result);
}, nil)
.then(^id(id result){
// handle result
return nil;
}, nil);
"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 :
asyncTaskA()
.then(^id(id result){
return asyncTaskB(result);
}, nil)
.then(^id(id result){
return asyncTaskC(result);
}, nil)
.then(^id(id result){
return asyncTaskD(result);
}, nil)
.then(^id(id result){
// handle result
return nil;
}, nil);
.then(nil, ^id(NSError*error) {
NSLog(@“”Error: %@“, error);
return nil;
});
Ini mirip dengan gaya sinkron yang mungkin lebih akrab dengan penanganan pengecualian:
try {
id a = A();
id b = B(a);
id c = C(b);
id d = D(c);
// handle d
}
catch (NSError* error) {
NSLog(@“”Error: %@“, error);
}
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.