如何使用ReactiveCocoa实现promise模式?

时间:2013-09-16 11:42:54

标签: cocoa-touch reactive-cocoa

我是来自EmberJS的JS背景的iOS开发新手。我想将我的EmberJS应用程序移植到iOS应用程序。因此,我想在我的iOS应用程序中使用类似的结构。由于EmberJS大量使用承诺,我搜索了类似于iOS的东西,偶然发现了ReactiveCocoa。 在ReactiveCocoa的介绍中说,这个框架可以用来实现Promises。我试了但是它没有正常工作。我想从一个非常简单的例子开始:

  • 发出异步网络请求(填充UITableViewController)。从这个方法中返回一个承诺。
  • 订阅此承诺并在完成后重新加载TableView。

我想这样做,因为我必须在数据加载成功后执行几项操作。 我的方法基本上是,但我遇到了以下问题:

  • 请求完成后,我的TableView不会立即重新加载。
  • 我在请求完成后立即看到subscribeCompleted中的日志语句。但是TableView保持空白。
  • TableView在等待几秒钟后加载数据。
  • 如果我在看到Log输出后开始滚动TableView,则会突然加载TableView。

我怀疑这可能发生,因为我在后台线程中获取数据。 我认为承诺(subscribeCompleted)的决心也可能在后台线程中发生,而Cocoa Touch可能不喜欢这样。我是对的吗?但如果是这种情况,我该如何实施承诺?

我希望你能帮助我开始使用ReactiveCocoa。谢谢! : - )

更新 我设法通过将reloadData包裹在dispatch_async(dispatch_get_main_queue(), ^{...中来解决这个问题但是我仍然不确定这是最好的方法还是ReactiveCocoa的建议。 所以我仍然热衷于听取一些答案 :-)

// this method wants to use the promise
- (void) loadDataAndPerformActionsAfterwards{
    RACSignal *signal = [self fetchObjects];
    [signal subscribeCompleted:^{
        NSLog(@"Entered subscribeCompleted block signal!");
        NSLog(@"Number of objects: %i", self.objects.count);
        [self.tableView reloadData];
    }];
}

// this method returns a promise. I omitted some parts but it shows basically how i go about resolving the promise.
- (RACSignal*) fetchMoviesForCurrentFormState{

    return [RACSignal createSignal:^RACDisposable*(id<RACSubscriber> subscriber) {
        NSLog(@"RAC createSignal Block called");

        NSString *requestURL = @"...";
        NSURL *urlObj = [NSURL URLWithString: requestURL];

        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            NSData* data = [NSData dataWithContentsOfURL: urlObj];
            if(data){
                [self performSelectorOnMainThread:@selector(fetchedData:)
                                       withObject:data waitUntilDone:YES];
                [subscriber sendCompleted];
            }else{
                // Not implemented yet: handle the error case
                [subscriber sendCompleted];
            }
        });
        // actually i do not know yet what i should return here. Copied from a basic example.
        return nil;
    }];
}

2 个答案:

答案 0 :(得分:3)

你是对的,这是线程问题。但是,您无需降低到GCD级别。

信号可以“传递”到另一个线程上,该线程只调用那里的任何订阅回调:

- (void) loadDataAndPerformActionsAfterwards {
    [[[self
        fetchObjects]
        deliverOn:RACScheduler.mainThreadScheduler]
        subscribeCompleted:^{
            NSLog(@"Entered subscribeCompleted block signal!");
            NSLog(@"Number of objects: %i", self.objects.count);
            [self.tableView reloadData];
        }];
}

答案 1 :(得分:1)

您可以查看RXPromise。它是Promises/A+ specification的Objective-C实现,具有更多功能。 (我是作者)。

使用RXPromise库的解决方案如下所示:

- (void) loadDataAndPerformActionsAfterwards {
    [self fetchMovie]
    .thenOn(dispatch_queue_get_main(), ^id(id fetchedMovie) {
        self.model = fetchedObjects;
        [self.tableView reloadData];
    }, nil);
}

这假定,方法fetchMovie返回Promise。

你怎么得到这个?好吧,您可以轻松地将任何异步方法或操作包装到返回Promise的方法或操作中。这适用于任何信号方法:完成块,回调函数,委托,KVO,通知等。

例如,NSURLConnection的异步便利类方法的简化实现(在实践中,您应该检查响应并执行更好的错误处理):

- (RXPromise*) fetchMovie {
    RXPromise* promise = [[RXPromise alloc] init];
    NSMutableRequest* request = ...;    
    [NSURLConnection sendAsynchronousRequest:request 
                                       queue:networkQueue 
                           completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
                               if (error) {
                                    [promise rejectWithReason:error];    
                               }
                               else {
                                    [promise fulfillWithValue:data];
                               }
                           }];
    return promise;
}

您可能希望使用NSURLConnection委托的方法,或使用NSOperation子类的方法。这使您可以实现取消

- (RXPromise*) fetchObjects {
    RXPromise* promise = [[RXPromise alloc] init];
    NSMutableRequest* request = ...;    
    HTTPOperation* op = 
        [[HTTPOperation alloc] initWithRequest:request 
                                         queue:networkQueue 
                             completionHandler:^(NSURLResponse* response, NSData* data, NSError* error){
                             if (error) {
                                 [promise rejectWithReason:error];    
                             }
                             else {
                                 [promise fulfillWithValue:data];
                            }
                        }];

    promise.then(nil, ^id(NSError* error){
        [op cancel];
        return nil;
    });                             
    [op start];
    return promise;
}

这里,HTTPOperation对象将听取自己对错误信号的承诺。如果它收到一个,例如从另一个对象发送到promise的取消消息,则处理程序然后将cancel消息“转发”到该操作。

例如,View Controller现在可以取消正在运行的HTTPOperation,如下所示:

- (void) viewWillDisappear:(BOOL)animate {
    [super viewWillDisappear:animate];

    [self.fetchObjectsPromise cancel];
    self.fetchObjectPromise = nil;
}