2

私は、EmberJS の JS バックグラウンドから来た iOS 開発の初心者です。EmberJS アプリを iOS アプリに移植したいと考えています。したがって、iOS アプリで同様の構造を使用したいと考えています。EmberJS は Promise を多用するため、iOS 用に似たものを探していたところ、ReactiveCocoa にたどり着きました。ReactiveCocoa の紹介で、このフレームワークを使用して Promises を実装できると言われています。やってみたのですが、うまくいきません。私は非常に単純な例から始めたかった:

  • 非同期ネットワーク リクエストを作成します (UITableViewController を満たすため)。このメソッドから promise を返します。
  • この promise にサブスクライブし、終了したら TableView をリロードします。

データが正常にロードされた後、いくつかのことを実行する必要があるため、この方法で実行したいと考えています。私のアプローチは基本的に機能しますが、次の問題が発生しています。

  • リクエストが終了した直後に TableView がリロードされません。
  • subscribeCompletedリクエストが終了した直後にログ ステートメントが表示されます。ただし、TableView は空白のままです。
  • TableView は、数秒待ってからデータをロードします。
  • ログ出力を見た後に TableView のスクロールを開始すると、TableView が突然読み込まれます。

バックグラウンドスレッドでデータをフェッチしているため、これが発生する可能性があると思われます。promise ( subscribeCompleted) の解決はバックグラウンド スレッドでも発生する可能性があり、Cocoa Touch がこれを気に入らない可能性があると思います。私は正しいですか?しかし、これが事実である場合、どのように約束を実装する必要がありますか?

ReactiveCocoa を使い始めるのを手伝ってくれることを願っています。どうも!:-)

更新:reloadData toを 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これにより、 cancelを実装できます。

- (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 をリッスンします。たとえば、別のオブジェクトから promise に送信されたキャンセル メッセージを受信した場合、ハンドラーはそのcancelメッセージを操作に「転送」します。

たとえば、View Controller は、次のように実行中の HTTPOperation をキャンセルできるようになりました。

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

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