başarı: / başarısız: bloklar vs tamamlanma: blok


23

Objective-C'de bloklar için iki ortak kalıp görüyorum. Biri bir çift başarıdır: / failure: blocks, diğeri ise tek bir tamamlamadır: block.

Örneğin, bir nesneyi eşzamansız olarak döndürecek bir görevim olduğunu ve bu görevin başarısız olabileceğini varsayalım. İlk örnek olan -taskWithSuccess:(void (^)(id object))success failure:(void (^)(NSError *error))failure. İkinci desen -taskWithCompletion:(void (^)(id object, NSError *error))completion.

başarı: / hatası:

[target taskWithSuccess:^(id object) {
    // W00t! I've got my object
} failure:^(NSError *error) {
    // Oh noes! report the failure.
}];

tamamlama:

[target taskWithCompletion:^(id object, NSError *error) {
    if (object) {
        // W00t! I've got my object
    } else {
        // Oh noes! report the failure.
    }
}];

Tercih edilen kalıp hangisidir? Güçlü ve zayıf yönleri nelerdir? Birini diğerinden ne zaman kullanırsın?


Objective-C'nin fırlatma / yakalama ile özel durum oluşturduğundan eminim, bunu kullanamamanızın bir nedeni var mı?
SinirliFormsDesigner'la

Bunlardan herhangi biri, istisnalar size verilmeyen eşzamansız aramaları zincirlemeye izin verir.
Frank Shearar

5
@FrustratedWithFormsDesigner: stackoverflow.com/a/3678556/2289 - deyimsel objc akış kontrolü için try / catch kullanmıyor.
Ant

1
Lütfen cevabınızı sorudan cevaba taşımayı düşünün ... sonuçta, bu bir cevap (ve kendi sorularınızı cevaplayabilirsiniz).

1
Sonunda akran baskısına bastım ve cevabımı gerçek bir cevaba taşıdım.
Jeffery Thomas

Yanıtlar:


8

Tamamlama geri çağrısı (başarı / başarısızlık çiftinin aksine) daha geneldir. Dönüş durumu ile ilgilenmeden önce bir bağlam hazırlamanız gerekiyorsa, bunu sadece "if (object)" cümlesinden önce yapabilirsiniz. Başarı / başarısızlık durumunda bu kodu kopyalamanız gerekir. Tabii ki bu geri arama anlambilimine bağlı.


Orijinal soru hakkında yorum yapamıyorum ... İstisnalar, c-objede geçerli bir akış kontrolü değildir (iyi, kakao) ve böyle kullanılmamalıdır. Atılan istisna yalnızca incelikle sonlandırmak için yakalanmalıdır.

Evet görebiliyorum. Eğer -task…nesneyi döndürmek, ancak nesne doğru durumda değil olabilir, o zaman yine başarı koşulu hata işlemeye gerekir.
Jeffery Thomas,

Evet, ve blok yerinde değilse, ancak denetleyicinize argüman olarak iletilirse, iki blok atmanız gerekir. Geri aramanın birçok katmandan geçirilmesi gerektiğinde bu sıkıcı olabilir. Yine de her zaman bölebilirsiniz.

Tamamlama işleyicisinin nasıl daha genel olduğunu anlamıyorum. Tamamlama, temelde çoklu metot paramlarını bire dönüştürür - blok param şeklinde. Ayrıca, genel daha iyi anlama geliyor mu? MVC’de çoğu zaman görünüm denetleyicisinde yinelenen kod vardır, bu endişelerin ayrılması nedeniyle gerekli bir kötülüktür. Bunun MVC'den uzak durmasının bir nedeni olduğunu sanmıyorum.
Boon

@Boon Tek işleyicinin daha genel olarak görülmesinin bir nedeni, bir işlemin başarılı veya başarısız olduğunu belirlemek için callee / işleyici / bloğun kendisini seçmeyi tercih ettiğiniz durumlar içindir. Kısmi verili bir nesneye sahip olduğunuz ve hata nesnenizin tüm verilerin döndürülmediğini belirten bir hata olduğu muhtemelen kısmi başarı durumlarını düşünün. Blok, verilerin kendisini inceleyebilir ve yeterli olup olmadığını kontrol edebilir. İkili başarı / başarısızlık geri arama senaryosunda bu mümkün değildir.
Travis,

8

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_tyukarı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.

! thennihai 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, thena, ö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, nilbir 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 thenbir 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, asyncTaskCve 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, thenbiri 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.


1
Bu şimdiye kadar cevap verilmeyen en uzun cevap için ödül alır. Ama çaba için A :-)
Travelling Man

3

Bunun eski bir soru olduğunun farkındayım ama cevabı diğerlerinden farklı olduğu için cevaplamak zorundayım.

Kişisel tercih meselesi olduğunu söyleyenler için aynı fikirde değilim. Birini diğerine tercih etmenin iyi, mantıklı bir nedeni var.

Tamamlama durumunda, bloğunuz iki nesneye verilir, biri başarıyı temsil ederken diğeri başarısızlığı temsil eder ... Öyleyse her ikisi de sıfırsa ne yaparsınız? Her ikisinin de değeri varsa ne yaparsınız? Bunlar derleme zamanında kaçınılması gereken sorulardır ve olması gerektiği gibi. İki ayrı blok alarak bu soruları önlersiniz.

Ayrı başarı ve başarısızlık bloklarına sahip olmak kodunuzu statik olarak doğrulanabilir kılar.


Swift ile her şeyin değiştiğini unutmayın. İçinde, bir Eitherenum kavramını uygulayabiliriz, böylece tek tamamlama bloğunun bir nesneye veya bir hataya sahip olmasını garanti altına alır ve tam olarak bunlardan birine sahip olmalıdır. Yani Swift için, tek bir blok daha iyidir.


1

Sanırım kişisel tercih olarak sona erecek ...

Ancak ayrı başarı / başarısızlık bloklarını tercih ederim. Başarı / başarısızlık mantığını ayırmayı seviyorum. İç içe geçmiş başarı / başarısızlıklarınız olsaydı, daha okunaklı olacak bir şeyle karşılaşırdınız (bence).

Bu tür yuvalamanın nispeten aşırı bir örneği olarak, işte bu Ruby'yi gösteren bazı Ruby .


1
İkisinin de iç içe zincirlerini gördüm. Sanırım ikisi de korkunç görünüyor, ama bu benim kişisel görüşüm.
Jeffery Thomas

1
Ama başka nasıl zaman uyumsuz aramalar zincir olabilir?
Frank Shearar

Bilmiyorum dostum… Bilmiyorum. Sormamın bir nedeni de, zaman uyumsuz kodumun görünüşünden hoşlanmadığımdır.
Jeffery Thomas

Emin. Kodunuzu, son derece şaşırtıcı olmayan bir şekilde yazmaya devam edersiniz. (Haskell'in tam da bu sebepten ötürü gösterimi vardır: görünüşte doğrudan bir stilde yazmanıza izin verir.)
Frank Shearar

Bu objc Sözler uygulanmasında ilginizi çekebilir: github.com/couchdeveloper/RXPromise
e1985

0

Bu tam bir koparma gibi geliyor, ama burada doğru bir cevap olduğunu sanmıyorum. Tamamlama bloğuyla gittim, çünkü başarı / başarısızlık blokları kullanılırken başarı durumunda hata işlemenin hala yapılması gerekebilir.

Bence son kod gibi gözükecek

[target taskWithCompletion:^(id object, NSError *error) {
    if (error) {
        // Oh noes! report the failure.
    } else if (![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
    } else {
        // W00t! I've got my object
    }
}];

ya da sadece

[target taskWithCompletion:^(id object, NSError *error) {
    if (error || ![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
        return;
    }

    // W00t! I've got my object
}];

En iyi kod parçası ve iç içe geçme durumu daha da kötüleşiyor

[target taskWithCompletion:^(id object, NSError *error) {
    if (error || ![target validateObject:&object error:&error]) {
        // Oh noes! report the failure.
        return;
    }

    [object objectTaskWithCompletion:^(id object2, NSError *error) {
        if (error || ![object validateObject2:&object2 error:&error]) {
            // Oh noes! report the failure.
            return;
        }

        // W00t! I've got object and object 2
    }];
}];

Sanırım bir süre paspaslayacağım.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.