2

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

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

我想这样做,因为在成功加载数据后我将不得不执行几件事。我的方法基本上有效,但我遇到了以下问题:

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

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

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

更新: 我设法通过将 to 包装reloadData在 a 中来修复它,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;
    }];
}
4

2 回答 2

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];
        }];
}
于 2013-09-16T20:56:31.637 回答
1

你可以看看RXPromise它是Promises/A+ 规范的 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、通知等。

例如,async 便利类方法的简化实现 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消息“转发”到操作。

例如,视图控制器现在可以取消正在运行的 HTTPOperation,如下所示:

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

    [self.fetchObjectsPromise cancel];
    self.fetchObjectPromise = nil;
}
于 2013-09-20T00:04:02.210 回答