【问题标题】:Better asynchronous control flow with Objective-C blocks使用 Objective-C 块的更好的异步控制流
【发布时间】:2023-03-24 16:52:01
【问题描述】:

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

任何指针或想法将不胜感激。

【问题讨论】:

  • 感谢您提出这个问题——您已经得到了一些很好的答案。不过,我最初在找到它时遇到了一些麻烦,并通过查看承诺来到了这里。这种反模式可能发生在任何异步回调 API 上:它不是 AFNetworking 特定的。我正在使用类似的搜索:“序列化嵌套块回调”。也许更多的标签会有所帮助?不过也可能是我! :-)

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


【解决方案1】:

我为此创建了一个轻量级的解决方案。它被称为 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步或第2步出错的情况下,其余的步骤将不会执行。
  • @fabb 我相信这是我们想要的结果——这当然是我想要达到的效果。
  • @fabb 在上面的示例代码中,nil 作为失败块传入,因此错误会被静默忽略(但错误 do 会导致排序器停止并被处置,因为没有什么可以调用下一步)。在我的代码中,我有一个可用于运行Sequencer 的函数的错误处理程序块。我传递了这个错误处理程序而不是nil。如果有 is 错误,我不希望运行其余步骤。我希望立即调用错误块,并且定序器将在到达的位置被丢弃。
  • 好的,那么 Sequencer 是 Promise 的“一半”实现?
【解决方案2】:

我还没有使用它,但听起来Reactive Cocoa 的设计目的正是您所描述的。

【讨论】:

  • 我用过,Jon 是对的。它非常适合这类事情。
  • 有趣。我遇到过 Reactive Cocoa,但没有考虑这种情况。由于 AF 操作都符合 KVO,因此我可以将处理程序添加到操作队列或单个操作中。我会搞砸的。
  • 我喜欢 ReactiveCocoa 方法。我的blog article 解释了如何为此目的使用 ReactiveCocoa。
【解决方案3】:

在 Gowalla 中使用 AFNetworking 在成功块中将调用链接在一起的情况并不少见。

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

此外,如果您还没有使用它,AFHTTPClient 可以大大简化这些复杂的网络交互。

【讨论】:

  • 谢谢@mattt。这基本上就是我现在正在做的事情。嵌套块只是有一种代码味道。这与我使用深度嵌套的条件逻辑得到的气味相同。也许我渴望 node.js 和其他 Javascript 框架提供的一些简洁性,以使函数式编程更具可读性。
  • 深度嵌套并不是这种方法的固有结果——通过有效地将回调分解到它们自己的方法中,它应该看起来更像函数式语言中的链接。但是,必须比两个嵌套调用更深入肯定是一种味道,这可能意味着您应该考虑创建一个新的 API 调用来一次获得所需的一切(如果这完全在您的能力范围内)
【解决方案4】:

PromiseKit 可能很有用。它似乎是更流行的 Promise 实现之一,其他人已经编写了类别以将其与 AFNetworking 等库集成,请参阅PromiseKit-AFNetworking

【讨论】:

    【解决方案5】:

    Github 上有一个 CommonJS 风格的 Promise 的 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!
    

    我还没有尝试过这个库,但它看起来“很有前途”,尽管这个例子有点让人印象深刻。 (对不起,我无法抗拒)。

    【讨论】:

    【解决方案6】:

    您可以将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];
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2019-10-21
      • 1970-01-01
      • 2012-11-02
      • 1970-01-01
      • 2017-06-10
      • 1970-01-01
      • 1970-01-01
      • 2014-04-10
      相关资源
      最近更新 更多