使用Objective-C块实现更好的异步控制流程

时间:2012-06-06 14:09:37

标签: objective-c design-patterns afnetworking objective-c-blocks

我正在使用AFNetworking来异步调用Web服务。其中一些呼叫必须链接在一起,其中呼叫A的结果由呼叫B使用,由呼叫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集成。由于异步调用可能有多个结果(成功/失败)并且没有返回值,因此它似乎不是理想的选择。

任何指针或想法都会受到赞赏。

6 个答案:

答案 0 :(得分:19)

我为此创造了一个轻量级的解决方案。它被称为Sequencer,它出现在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 :(得分:10)

我还没有使用它,但听起来像Reactive Cocoa的目的只是为了你所描述的。

答案 2 :(得分:10)

在Gowalla中使用AFNetworking在成功阻止中将呼叫链接在一起并不罕见。

我的建议是尽可能将网络请求和序列化分解为模型中的类方法。然后,对于需要进行子请求的请求,您可以在成功块中调用这些方法。

此外,如果您尚未使用它,AFHTTPClient可以极大地简化这些复杂的网络交互。

答案 3 :(得分:6)

PromiseKit可能有用。它似乎是更受欢迎的承诺实现之一,其他人已编写类别将其与AFNetworking等库集成,请参阅PromiseKit-AFNetworking

答案 4 :(得分:4)

Github上有一个CommonJS风格承诺的Objective-C实现:

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!

我还没有尝试过这个库,但它看起来“很有希望”,尽管这个例子略显不足。 (对不起,我无法抗拒)。

答案 5 :(得分:0)

您可以将NSBlockOperationsemaphore结合使用来实现它:

- (void)loadDataByOrderSuccess:(void (^)(void))success failure:(void (^)(void))failure {
    // first,load data1
    NSBlockOperation * operation1 = [NSBlockOperation blockOperationWithBlock:^{
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
        [self loadData1Success:^{
            dispatch_semaphore_signal(sema);
        } failure:^{
            !failure ?: failure();
        }];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    }];
    // then,load data2
    NSBlockOperation * operation2 = [NSBlockOperation blockOperationWithBlock:^{
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
        [self loadData2Success:^{
            dispatch_semaphore_signal(sema);
        } failure:^{
            !failure ?: failure();
        }];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
    }];
    // finally,load data3
    NSBlockOperation * operation3 = [NSBlockOperation blockOperationWithBlock:^{
        dispatch_semaphore_t sema = dispatch_semaphore_create(0);
        [self loadData3Success:^{
            dispatch_semaphore_signal(sema);
        } failure:^{
            !failure ?: failure();
        }];
        dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
        !success ?: success();
    }];
    [operation2 addDependency:operation1];
    [operation3 addDependency:operation2];
    NSOperationQueue * queue = [[NSOperationQueue alloc] init];
    [queue addOperations:@[operation1, operation2, operation3] waitUntilFinished:NO];
}