API'nin bir tamamlama işleyicisi mi yoksa bir çift başarı / başarısızlık bloğu mu sağladığını söyleyebilirim, öncelikle kişisel tercih meselesi.
Her iki yaklaşımın da artıları ve eksileri var, ancak yalnızca marjinal farklılıklar var.
Ayrıca, bir tamamlama işleyicisinin , nihai sonucu veya olası bir hatayı birleştiren yalnızca bir parametreye sahip olabileceği başka değişkenlerin de olduğunu düşünün :
typedef void (^completion_t)(id result);
- (void) taskWithCompletion:(completion_t)completionHandler;
[self taskWithCompletion:^(id result){
if ([result isKindOfError:[NSError class]) {
NSLog(@"Error: %@", result);
}
else {
...
}
}];
Bu imzanın amacı, bir tamamlama işleyicisinin diğer API'lerde genel olarak kullanılabiliyor olmasıdır .
Örneğin, NSArray için Kategori'de, forEachApplyTask:completion:
sırayla her nesne için bir görevi çağıran ve döngü IFF'yi kesen bir hata olduğu bir yöntem var. Bu yöntemin kendisi de asenkron olduğu için bir tamamlama işleyicisine de sahiptir:
typedef void (^completion_t)(id result);
typedef void (^task_t)(id input, completion_t);
- (void) forEachApplyTask:(task_t)task completion:(completion_t);
Aslında, completion_t
yukarıda tanımlandığı gibi, tüm senaryoları ele almak için yeterince genel ve yeterlidir.
Ancak, asenkronize bir görevin, tamamlanma bildirimini çağrı sitesine bildirmesi için başka yollar da vardır:
sözler
“Vadeli İşlemler”, “Ertelenmiş” veya “Gecikmeli” olarak da adlandırılan sözler, zaman uyumsuz bir görevin nihai sonucunu temsil eder (ayrıca bkz: wiki Vadeli İşlemler ve sözler ).
Başlangıçta, bir söz “beklemede” durumda. Yani, “değer” henüz değerlendirilmemiştir ve henüz mevcut değildir.
Objective-C'de Promise, aşağıda gösterildiği gibi asenkronize bir yöntemden döndürülecek sıradan bir nesne olacaktır:
- (Promise*) doSomethingAsync;
! Bir Sözün ilk hali “beklemede” dir.
Bu arada, zaman uyumsuz görevler sonucunu değerlendirmeye başlar.
Ayrıca, tamamlama işleyicisi olmadığını unutmayın. Bunun yerine, Söz, çağrı alanının yakında göreceğimiz zaman uyumsuz görevin nihai sonucunu elde edebileceği daha güçlü bir yöntem sağlayacaktır.
Söz nesnesini oluşturan asenkron görev, sonunda sözünü “çözmelidir”. Yani, bir görev başarılı veya başarısız olabileceğinden, değerlendirilen sonucu geçen bir sözü “yerine getirmeli” veya başarısızlığın nedenini belirten bir hatadan geçen sözü “reddetmelidir”.
! Bir görev sonunda sözünü çözmelidir.
Bir Söz konusu karar verildiğinde, değeri de dahil olmak üzere artık durumunu değiştiremez.
! Bir söz yalnızca bir kez çözülebilir .
Bir söz çözüldükten sonra bir çağrı sitesi sonucu alabilir (başarısız veya başarısız). Bunun nasıl gerçekleştirileceği, söz verinin senkronize mi, yoksa senkronize olmayan tarz mı kullanılarak mı uygulandığına bağlıdır.
Bir Söz, sırasıyla bloke edici olmayan semantiği bloke etmeye yarayan senkron veya asenkron bir tarzda uygulanabilir .
Sözün değerini almak için senkronize bir tarzda, bir çağrı sitesi , söz konusu zamanuyumsuz görev tarafından çözülene ve sonuçta elde edilinceye kadar geçerli konuyu engelleyecek bir yöntem kullanır .
Eşzamansız bir tarzda, çağrı sitesi, söz verildikten hemen sonra çağrılan geri aramaları veya işleyici bloklarını kaydeder.
Eşzamanlı stilin, eşzamansız görevlerin yararlarını etkin bir şekilde ortadan kaldıran çok sayıda dezavantajı olduğu ortaya çıktı. C ++ 11 lib standardındaki “vadeli işlemlerin” şu anki kusurlu uygulamasıyla ilgili ilginç bir makale buradan okunabilir: Broken promises –C ++ 0x futures .
Objective-C'de bir çağrı sitesi sonucu nasıl elde eder?
Muhtemelen birkaç örnek göstermek en iyisidir. Bir Söz Veren birkaç kütüphane vardır (aşağıdaki bağlantılara bakınız).
Ancak, bir sonraki kod parçacıkları için GitHub RXPromise'da bulunan belirli bir Promise kütüphanesinin uygulamasını kullanacağım . RXPromise'un yazarıyım.
Diğer uygulamalar benzer bir API'ye sahip olabilir, ancak sözdiziminde küçük ve muhtemelen ince farklılıklar olabilir. RXPromise, JavaScript'te vaatlerin sağlam ve birlikte çalışabilir uygulamaları için açık bir standardı tanımlayan Promise / A + şartnamesinin Objective-C versiyonudur .
Aşağıda listelenen tüm söz kütüphaneleri eşzamansız stili uygular.
Farklı uygulamalar arasında oldukça önemli farklılıklar vardır. RXPromise dahili olarak lib gönderme yazılımı kullanır, tamamen güvenlidir, son derece hafiftir ve ayrıca iptal etme gibi bir dizi ek kullanışlı özellik sunar.
Bir çağrı sitesi, zaman uyumsuz görevin nihai sonucunu "kayıt" işlemcileri aracılığıyla alır. “Promise / A + şartnamesi” yöntemi tanımlar then
.
Yöntem then
RXPromise ile aşağıdaki gibi görünür:
promise.then(successHandler, errorHandler);
nerede successHandler söz “yerine” olmuştur ve ne zaman çağrılan bir bloktur errorHandler söz “reddedildi” edildiğinde çağrılan bir bloktur.
! then
nihai sonucu elde etmek ve bir başarı veya hata işleyicisi tanımlamak için kullanılır.
RXPromise'da işleyici blokları aşağıdaki imzayı taşır:
typedef id (^success_handler_t)(id result);
typedef id (^error_handler_t)(NSError* error);
Success_handler, asenkron görevin kesin sonucu olan bir parametre sonucuna sahiptir. Aynı şekilde, error_handler de asenkron görev tarafından başarısız olduğunda bildirilen hata olan bir parametre hatasına sahiptir.
Her iki blok da bir dönüş değerine sahiptir. Bu geri dönüş değerinin ne olduğu, yakında belli olacak.
RXPromise olarak, then
a, özelliği , bir blok döndürür. Bu bloğun iki parametresi vardır: başarı işleyici bloğu ve hata işleyici bloğu. İşleyiciler, çağrı sitesi tarafından tanımlanmalıdır.
! İşleyiciler, çağrı sitesi tarafından tanımlanmalıdır.
Yani, ifade promise.then(success_handler, error_handler);
kısa bir şeklidir
then_block_t block promise.then;
block(success_handler, error_handler);
Daha kısa bir kod yazabiliriz:
doSomethingAsync
.then(^id(id result){
…
return @“OK”;
}, nil);
Kod şöyle yazıyor: “Başarılı olduğunda doSomethingAsync'i yürütün, ardından başarı işleyicisini yürütün”.
Burada, hata işleyicisi, nil
bir hata durumunda, bu sözle ele alınmayacağı anlamına gelir.
Bir başka önemli gerçek ise, mülkünden döndürülen bloğu çağırmanın then
bir Söz vermesi:
! then(...)
bir söz verir
Mülkiyetten döndürülen bloğu çağırırken, then
“alıcı” yeni bir Söz, bir çocuk söz veriyor. Alıcı ana vaadi olur .
RXPromise* rootPromise = asyncA();
RXPromise* childPromise = rootPromise.then(successHandler, nil);
assert(childPromise.parent == rootPromise);
Bu ne anlama geliyor?
Bundan dolayı, etkili bir şekilde sırayla yürütülen asenkron işleri “zincirleyebiliriz”.
Ayrıca, her iki işleyicinin geri dönüş değeri, iade edilen sözün “değeri” haline gelecektir. Bu yüzden, eğer görev sonunda “OK” ile sonuçlanırsa, verilen söz “@” ile “çözülür” (“yerine getirilir”) olacaktır:
RXPromise* returnedPromise = asyncA().then(^id(id result){
return @"OK";
}, nil);
...
assert([[returnedPromise get] isEqualToString:@"OK"]);
Benzer şekilde, zaman uyumsuz görev başarısız olduğunda, geri gönderilen söz bir hatayla çözülecektir (“reddedilir”).
RXPromise* returnedPromise = asyncA().then(nil, ^id(NSError* error){
return error;
});
...
assert([[returnedPromise get] isKindOfClass:[NSError class]]);
İşleyici ayrıca başka bir söz verebilir. Örneğin, bu işleyici başka bir zaman uyumsuz görevi yürüttüğünde. Bu mekanizma ile asenkron görevleri “zincirleyebiliriz”:
RXPromise* returnedPromise = asyncA().then(^id(id result){
return asyncB(result);
}, nil);
! Bir işleyici bloğunun dönüş değeri, vaat edilen çocuğun değeri olur.
Çocuk vaadi yoksa geri dönüş değerinin etkisi yoktur.
Daha karmaşık bir örnek:
Burada, yürütmek asyncTaskA
, asyncTaskB
, asyncTaskC
ve asyncTaskD
sırayla - ve sonraki her bir görev girdi olarak önceki görevin sonucunu alır:
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);
Böyle bir “zincir” ayrıca “devam” olarak da adlandırılır.
Hata işleme
Sözler, hataları ele almayı özellikle kolaylaştırır. Ebeveyn vaadinde tanımlanan herhangi bir hata işleyici yoksa, ebeveynden çocuğa hatalar iletilir. Bir çocuk ele alıncaya kadar hata zincirde iletilir. Böylece, yukarıdaki zincire sahip, her yerde ortaya çıkabilir potansiyel bir hata ile fırsatlar başka bir “devamı” ekleyerek sadece hata işleme uygulayabilir yukarıda :
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;
});
Bu istisna işleme ile muhtemelen daha tanıdık bir senkronize tarzı benzer:
try {
id a = A();
id b = B(a);
id c = C(b);
id d = D(c);
// handle d
}
catch (NSError* error) {
NSLog(@“”Error: %@“, error);
}
Genel olarak verilen sözlerin başka faydalı özellikleri de vardır:
Örneğin, bir söze atıfta bulunmak suretiyle, then
biri aracılığıyla dilediğiniz kadar işleyiciyi "kaydedebilir". RXPromise'da, kayıt işleyicileri her zaman ve herhangi bir dişten tam diş güvenliğine sahip olduklarından oluşabilir.
RXPromise, Promise / A + spesifikasyonunun gerektirmediği birkaç kullanışlı fonksiyon özelliğine sahiptir. Birincisi "iptal".
“İptal” in paha biçilemez ve önemli bir özellik olduğu ortaya çıktı. Örneğin, bir söze atıfta bulunan bir çağrı sitesi cancel
, nihayetinde sonuçla artık ilgilenmediğini belirtmek için mesajı gönderebilir .
Web'den bir görüntü yükleyen ve bir görünüm denetleyicide görüntülenecek asenkron bir görevi hayal edin. Kullanıcı geçerli görünüm denetleyicisinden uzaklaşırsa, geliştirici, imagePromise öğesine bir iptal mesajı gönderen bir kod uygulayabilir ; bu da, isteğin iptal edileceği HTTP İsteği İşlemi tarafından tanımlanan hata işleyicisini tetikler.
RXPromise'da, iptal mesajı yalnızca bir ebeveynden çocuklarına iletilir, ancak bunun tersi olmaz. Yani, bir “kök” vaadi tüm çocukların vaatlerini iptal edecektir. Ancak bir çocuk vaadi ancak ebeveynin bulunduğu “şubeyi” iptal edecektir. Bir söz daha önce çözülmüşse iptal mesajı çocuklara da iletilecektir.
Zaman uyumsuz görev olabilir kendisi başkası iptal zaman algılayabilir ve böylece kendi vaadi için işleyici kayıt ve. Daha sonra muhtemel olarak uzun ve masraflı bir görevi yerine getirmeden zamanından önce durabilir.
GitHub’da bulunan Objective-C’de vaat edilen diğer birkaç Uygulama:
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/klaaspieter/Promise
: //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
ve kendi uygulamam: RXPromise .
Bu liste büyük olasılıkla tamamlanmadı!
Projeniz için üçüncü bir kütüphane seçerken, lütfen kütüphane uygulamasının aşağıda listelenen ön koşulları yerine getirip getirmediğini dikkatlice kontrol edin:
Güvenilir bir vaat kütüphane SHALL iş parçacığı güvenli ol!
Tamamen eşzamansız işlemle ilgili ve birden fazla CPU kullanmak ve mümkün olduğunda eşzamanlı olarak farklı iş parçacıkları üzerinde çalışmak istiyoruz. Dikkatli olun, uygulamaların çoğu iş parçacığı güvenli değildir!
Çağrı merkezi ile ilgili olarak, Handlers SHALL asenkron olarak adlandırılır ! Her zaman ve ne olursa olsun!
Herhangi bir düzgün uygulama, asenkron fonksiyonları çağırırken çok katı bir kalıp izlemelidir. Birçok uygulayıcı , işleyicinin kayıt olduğu zaman vaadi çözümlendiğinde , bir işleyicinin eşzamanlı olarak çağrılacağı vakayı "optimize etme" eğilimindedir . Bu her türlü soruna neden olabilir. Bkz. Zalgo'yu bırakma! .
Bir vaadi iptal etmek için bir mekanizma da olmalı.
Zaman uyumsuz bir görevi iptal etme olasılığı, gereksinim analizinde genellikle yüksek önceliğe sahip bir gereksinim haline gelir. Aksi halde, uygulama serbest bırakıldıktan bir süre sonra bir kullanıcıdan bir geliştirme talebi gelecektir. Sebep açık olmalıdır: durması veya bitirmesi çok uzun süren herhangi bir görev kullanıcı tarafından veya zaman aşımı ile iptal edilebilir olmalıdır. İyi bir söz kütüphanesi iptali desteklemeli.