37

Używam AFNetworking do asynchronicznych wywołań do usługi WWW. Niektóre z tych połączeń muszą być połączone razem, gdy wyniki wywołania A są używane przez wywołanie B używane przez wywołanie C itd.Lepsza asynchroniczna kontrola z blokami Objective-C

AFNetwork obsługuje wyniki asynchronicznych wywołań z blokami sukcesu/awarii ustawionymi w czasie operacji jest tworzone:

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]; 

Powoduje to zagnieżdżone asynchroniczne bloki wywołań, które szybko stają się nieczytelne. Jest to jeszcze bardziej skomplikowane, gdy zadania nie są od siebie zależne i zamiast tego muszą być wykonywane równolegle, a wykonanie zależy od wyników wszystkich operacji.

Wydaje się, że lepszym podejściem byłoby zastosowanie schematu promises w celu oczyszczenia sterowania.

Znalazłem MAFuture, ale nie wiem, jak najlepiej zintegrować go z AFNetworking. Ponieważ połączenia asynchroniczne mogą mieć wiele wyników (sukces/niepowodzenie) i nie mają wartości zwracanej, nie wydaje się idealnym dopasowaniem.

Wszelkie wskazówki i pomysły będą mile widziane.

+0

Dzięki za to pytanie - masz kilka świetnych odpowiedzi.Na początku miałem trochę problemów ze znalezieniem go i dotarłem tutaj, patrząc na obietnice. Ten anty-wzór może się zdarzyć w przypadku dowolnego asynchronicznego interfejsu API wywołania zwrotnego: nie jest on specyficzny dla AFNetworking. Użyłem wyszukiwania typu: "szeregowanie zagnieżdżonych blokowych wywołań zwrotnych". Może trochę więcej tagów może pomóc? To może być tylko ja! :-) – Benjohn

Odpowiedz

10

Jeszcze go nie używałem, ale brzmi to tak, jakby Reactive Cocoa został zaprojektowany tak, by robić to, co opisujesz.

+1

Użyłem go, a Jon ma rację. doskonale nadaje się do tego rodzaju rzeczy. –

+0

Interesujące. Natknąłem się na Reactive Cocoa, ale nie brałem pod uwagę tego scenariusza. Ponieważ operacje AF są zgodne z KVO, mogłem dodać procedury obsługi do kolejki operacji lub poszczególnych operacji. Zaskoczę to. – bromanko

+1

Podoba mi się podejście ReactiveCocoa. Mój [artykuł na blogu] (http://www.techsfo.com/blog/2013/08/managing-nested-asynchronous-callbacks-in-objective-c-using-reactive-cocoa/) wyjaśnia, jak używać ReactiveCocoa do tego cel, powód. –

10

Nie było nic niezwykłego, gdy za pomocą AFNetworking w Gowalla, aby połączenia były powiązane w bloki sukcesu.

Moja rada polega na uwzględnianiu żądań i serializacji sieci w metodach klasowych w modelu. Następnie, w przypadku żądań, które muszą wykonać sub-żądania, możesz wywołać te metody w bloku sukcesu.

Ponadto, jeśli już go nie używasz, AFHTTPClient znacznie upraszcza tego typu złożone interakcje sieciowe.

+0

Dzięki @mattt. Zasadniczo to co teraz robię. Zagnieżdżone bloki mają po prostu zmysłowy zapach. To ten sam zapach, który dostaję z głęboko zagnieżdżoną logiką warunkową. Być może tęsknię za czystością, którą oferuje node.js i inne frameworki JavaScript, aby zapewnić bardziej czytelne funkcjonalne programowanie. – bromanko

+1

Głębokie zagnieżdżanie nie jest nieodłącznym rezultatem tego podejścia - efektywne uwzględnianie wywołań zwrotnych w ich własnych metodach powinno wyglądać bardziej jak łańcuchowanie w języku funkcjonalnym. Konieczność przechodzenia głębiej niż dwa zagnieżdżone wywołania jest zdecydowanie zapachem i prawdopodobnie oznacza to, że powinieneś rozważyć utworzenie nowego wywołania API, aby uzyskać to, czego potrzebujesz od razu (jeśli w ogóle jest to w twojej mocy) – mattt

19

Stworzyłem dla tego lekkie rozwiązanie. Nazywa się Sequencer i jest na github.

Sprawia, że ​​wywoływanie wywołań interfejsu API (lub dowolnego innego kodu asynchronicznego) jest łatwe i proste.

Oto przykład użycia AFNetworking z nim:

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

To miło schludne, proste rozwiązanie. Dziękuję za udostępnienie. –

+0

Wygląda na to, że w przypadku błędu w kroku 1 lub 2 pozostałe kroki nie zostaną wykonane. – fabb

+0

@fabb Uważam, że jest to pożądany rezultat - to z pewnością efekt, który chcę osiągnąć. – Benjohn

4

istnieje implementacja Objective-C CommonJS stylu obiecuje tutaj na Github:

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

Przykład (zaczerpnięte z 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! 

Jeszcze nie t tę bibliotekę, ale wygląda to obiecująco pomimo tego nieco rozczarowującego przykładu. (przepraszam, nie mogłem się oprzeć). Może być przydatne

+0

Wygląda na to, że może być bardzo użyteczny, ale nie jest zgodny z ARC i nie mam środków, aby go tak {westchnąć}. – mpemburn

+1

Ten commit wydaje się być zgodny z ARC: https://github.com/mproberts/objc-promise/commit/9bdeac0d6b1305f00c9c3e4c64bef2743536ed9a – eremzeit

6

PromiseKit. Wydaje się, że jest to jedna z bardziej popularnych implementacji obietnic, a inni pisali kategorie, aby zintegrować ją z bibliotekami, takimi jak AFNetworking, zobacz PromiseKit-AFNetworking.

Powiązane problemy