37

我正在使用AFNetworking來異步調用Web服務。其中一些呼叫必須鏈接在一起,其中呼叫A的結果由呼叫B使用,由呼叫C使用,等等。使用Objective-C塊更好的異步控制流程

AFNetworking在操作時處理設置了成功/失敗塊的異步呼叫的結果被創建:

NSURL *url = [NSURL URLWithString:@"http://api.twitter.com/1/statuses/public_timeline.json"]; 
NSURLRequest *request = [NSURLRequest requestWithURL:url]; 
AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) { 
    NSLog(@"Public Timeline: %@", JSON); 
} failure:nil]; 
[operation start]; 

這會導致嵌套的異步調用塊迅速變得不可讀。當任務不相互依賴,而是必須並行執行並且執行取決於所有操作的結果時,情況會更加複雜。

看來,一個更好的方法是利用一個promises框架清理控制流。

我遇到MAFuture,但無法弄清楚如何最好地將其與AFNetworking整合。由於異步調用可能有多個結果(成功/失敗),並且沒有返回值,因此它看起來不太合適。

任何指針或想法,將不勝感激。

+0

感謝這個問題 - 你有一些很好的答案。儘管我最初找到了一點麻煩,但通過查看承諾來到這裏。這種反模式可能發生在任何異步回調API上:它不是AFNetworking特定的。我正在使用搜索:「序列化嵌套塊回調」。也許更多的標籤可以幫助?但它可能只是我! :-) – Benjohn

回答

10

我還沒有使用它,但它聽起來像是Reactive Cocoa旨在只是你的描述做。

+1

我已經使用它了,喬恩是對的。這對於這種事情來說很好。 –

+0

有趣。我遇到了Reactive Cocoa,但沒有考慮到這種情況。由於AF操作都符合KVO標準,因此我可以將處理程序添加到操作隊列或單個操作中。我會搞砸的。 – bromanko

+1

我喜歡ReactiveCocoa方法。我的[博客文章](http://www.techsfo.com/blog/2013/08/managing-nested-asynchronous-callbacks-in-objective-c-using-reactive-cocoa/)解釋瞭如何爲此使用ReactiveCocoa目的。 –

10

在Gowalla中使用AFNetworking將呼叫鏈接在成功塊中時,並不罕見。

我的建議是儘可能將網絡請求和序列化分解爲模型中的類方法。然後,對於需要創建子請求的請求,可以在成功塊中調用這些方法。

此外,如果你不使用它已經,AFHTTPClient大大簡化了這類複雜的網絡交互。

+0

謝謝@mattt。這基本上是我現在正在做的。嵌套塊只是具有代碼味道的感覺。這與我深入嵌套的條件邏輯具有相同的氣味。也許我渴望node.js和其他Javascript框架提供的一些清潔,以提高可讀性的功能性編程。 – bromanko

+1

深層嵌套不是這種方法的固有結果 - 通過有效地將回調因素分解到他們自己的方法中,它應該看起來更像是一種函數式語言中的鏈接。然而,必須深入到兩個嵌套調用之中絕對是一種嗅覺,這可能意味着您應該考慮創建一個新的API調用,以便一次獲得您所需的所有內容(如果這完全在您的能力範圍內) – mattt

19

我爲此創建了一個輕量級解決方案。這就是所謂的音序器,它在github上。

它使鏈接API調用(或任何其他異步代碼)容易和直接。

以下是使用它AFNetworking的例子:

Sequencer *sequencer = [[Sequencer alloc] init]; 

[sequencer enqueueStep:^(id result, SequencerCompletion completion) { 
    NSURL *url = [NSURL URLWithString:@"https://alpha-api.app.net/stream/0/posts/stream/global"]; 
    NSURLRequest *request = [NSURLRequest requestWithURL:url]; 
    AFJSONRequestOperation *operation = [AFJSONRequestOperation JSONRequestOperationWithRequest:request success:^(NSURLRequest *request, NSHTTPURLResponse *response, id JSON) { 
     completion(JSON); 
    } failure:nil]; 
    [operation start]; 
}]; 

[sequencer enqueueStep:^(NSDictionary *feed, SequencerCompletion completion) { 
    NSArray *data = [feed objectForKey:@"data"]; 
    NSDictionary *lastFeedItem = [data lastObject]; 
    NSString *cononicalURL = [lastFeedItem objectForKey:@"canonical_url"]; 

    NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:cononicalURL]]; 
    AFHTTPRequestOperation *operation = [[AFHTTPRequestOperation alloc] initWithRequest:request]; 
    [operation setCompletionBlockWithSuccess:^(AFHTTPRequestOperation *operation, id responseObject) { 
     completion(responseObject); 
    } failure:nil]; 
    [operation start]; 
}]; 

[sequencer enqueueStep:^(NSData *htmlData, SequencerCompletion completion) { 
    NSString *html = [[NSString alloc] initWithData:htmlData encoding:NSUTF8StringEncoding]; 
    NSLog(@"HTML Page: %@", html); 
    completion(nil); 
}]; 

[sequencer run]; 
+1

這真是一個不錯的選擇整潔,簡單的解決方案。感謝分享。 –

+0

看起來像在步驟1或2中出現錯誤,其餘步驟將不會執行。 – fabb

+0

@fabb我相信這是所期望的結果 - 這當然是我想達到的效果。 – Benjohn

4

還有就是CommonJS的風格的一個Objective-C語言實現的承諾這裏Github上:

https://github.com/mproberts/objc-promise

例(取自Readme.md)

Deferred *russell = [Deferred deferred]; 
Promise *promise = [russell promise]; 

[promise then:^(NSString *hairType){ 
    NSLog(@"The present King of France is %@!", hairType); 
}]; 

[russell resolve:@"bald"]; 

// The present King of France is bald! 

我還沒有t出了這個圖書館,但它看起來'很有前途',儘管這個例子有些微不足道。 (對不起,我無法抗拒)。

+0

看起來它可能非常有用,但它不符合ARC標準,而且我也沒有足夠的資金來實現它。 – mpemburn

+1

此提交似乎已使其符合ARC:https://github.com/mproberts/objc-promise/commit/9bdeac0d6b1305f00c9c3e4c64bef2743536ed9a – eremzeit

6

PromiseKit可能是有用的。它似乎是更受歡迎的承諾實現之一,而其他人已經編寫了類別以將其與像AFNetworking這樣的庫集成,請參閱PromiseKit-AFNetworking