web-dev-qa-db-ja.com

NSOperationの非同期メソッド

Facebook Connectから(FBConnect Objective-C 2.0フレームワークを使用して)いくつかのデータをフェッチしており、NSOperationですべてを実行しています。他にもいくつかの操作が実行されているため、NSOperationにあります。これはそのうちの1つです。

問題は、すべてのFBConnect呼び出しが非同期であるということです。このため、NSOperationのmainメソッドはすぐに終了し、操作は完了としてマークされます。

これを克服する方法はありますか? FBConnectには同期オプションがないように見えます!

どうもありがとう、

マイク

33

以下は完全な例です。サブクラスでは、asyncメソッドが完了した後、[self completeOperation]を呼び出して終了状態に移行します。

@interface AsynchronousOperation()
// 'executing' and 'finished' exist in NSOperation, but are readonly
@property (atomic, assign) BOOL _executing;
@property (atomic, assign) BOOL _finished;
@end

@implementation AsynchronousOperation

- (void) start;
{
    if ([self isCancelled])
    {
        // Move the operation to the finished state if it is canceled.
        [self willChangeValueForKey:@"isFinished"];
        self._finished = YES;
        [self didChangeValueForKey:@"isFinished"];
        return;
    }

    // If the operation is not canceled, begin executing the task.
    [self willChangeValueForKey:@"isExecuting"];
    [NSThread detachNewThreadSelector:@selector(main) toTarget:self withObject:nil];
    self._executing = YES;
    [self didChangeValueForKey:@"isExecuting"];

}

- (void) main;
{
    if ([self isCancelled]) {
        return;
    }

}

- (BOOL) isAsynchronous;
{
    return YES;
}

- (BOOL)isExecuting {
    return self._executing;
}

- (BOOL)isFinished {
    return self._finished;
}

- (void)completeOperation {
    [self willChangeValueForKey:@"isFinished"];
    [self willChangeValueForKey:@"isExecuting"];

    self._executing = NO;
    self._finished = YES;

    [self didChangeValueForKey:@"isExecuting"];
    [self didChangeValueForKey:@"isFinished"];
}

@end
24
Jason Moore

FBConnect呼び出しを 'start'ではなく 'main'に配置し、 'isFinished''isExecuting'プロパティを管理します。 (そして 'YES'に対してisConcurrentを返します)

詳細については、Appleの書き込みに関するドキュメント concurrent NSOperations を参照してください。

6
Roy Lovejoy

他に何もない場合は、これを理解してください。NSOperationの動作に魔法はありません。 NSOperationQueueKey ValueObservationを使用して操作を監視します。これが非常に簡単ではない唯一の理由は、使用されるキーがObjective-C 2.0の規則で定められているものと同じではないため、標準の合成セッターが機能しないことです。

その結果、NSOperationサブクラスを定義するときに、asynchronousexecuting、およびfinishedを指定する必要があります。そして、これらの最後の2つは、正しく機能するためにあなたの側で少し助けが必要です。

複雑に聞こえますか?そうではなく、ただdetailsです。途中の各ステップは単純で理にかなっていますが、すべてを正しく理解するまで実際には機能しません。

まず、ヘッダー:

//
//  MyOperation.h

#import <Foundation/Foundation.h>

@interface MyOperation : NSOperation

@property(readonly, getter=isAsynchronous) BOOL asynchronous;
@property(readonly, getter=isExecuting) BOOL executing;
@property(readonly, getter=isFinished) BOOL finished;

@end

もちろん、ここではexecutingfinishedreadwriteとして定義できるので、実装でそれらをreadwriteとして再定義する必要はありません。しかし、自分の操作だけが状態を変更できることを知りたいです。

今実装。ここにはいくつかの手順があります。

  • finishedおよびexecutingプロパティを読み取り/書き込みとして再定義します。
  • 正しいKVOメッセージングを手動で提供するexecutingおよびfinishedの実装を完全に提供します(つまり、isExecutingsetExecuting:isFinished、およびsetFinished:)。
  • @synthesizeを使用して、executingおよびfinishedivarのストレージを提供します。
  • asynchronousの実装を提供します

(このコードはおそらく少しスクロールすることに注意してください。)

//
//  MyOperation.m

#import "MyOperation.h"

@interface MyOperation()
@property(readwrite) BOOL executing;
@property(readwrite) BOOL finished;
@end

@implementation MyOperation

// Provide your own start.

- (void)start {
    if (self.cancelled) {
        self.finished = YES;
        return;
    }
    NSLog(@"Starting %@", self);
    self.executing = YES;
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
        NSLog(@"Finished %@", self);
        self.executing = NO;
        self.finished = YES;
    });
}

// The rest of this is boilerplate.

- (BOOL)isAsynchronous {
    return YES;
}

@synthesize executing = _executing;

- (BOOL)isExecuting {
    @synchronized(self) {
        return _executing;
    }
}

- (void)setExecuting:(BOOL)executing {
    @synchronized(self) {
        if (executing != _executing) {
            [self willChangeValueForKey:@"isExecuting"];
            _executing = executing;
            [self didChangeValueForKey:@"isExecuting"];
        }
    }
}

@synthesize finished = _finished;

- (BOOL)isFinished {
    @synchronized(self) {
        return _finished;
    }
}

- (void)setFinished:(BOOL)finished {
    @synchronized(self) {
        if (finished != _finished) {
            [self willChangeValueForKey:@"isFinished"];
            _finished = finished;
            [self didChangeValueForKey:@"isFinished"];
        }
    }
}


@end

セッターで(たとえば)executing != _executingをチェックする必要はありません。 willChangeValueForKeyを呼び出し、値を盲目的に変更してからdidChangeValueForKeyを呼び出すことにより、正しい動作が自動的に提供されます。ただし、この条件は、割り当てにブレークポイントを設定し、値が変更されたときにのみ停止できることを意味します。これは、実際の操作のデバッグに非常に役立つことがわかりました。

また、executingプロパティとfinishedプロパティの上にカスタム状態を提供することでこれが実装されていることも確認しました。もちろん、これは完全にうまく機能し、いくつかの点で優れています…しかし、この例よりもKVOの知識が必要であり、これで十分です。

最後に、操作開始後のキャンセルのサポートを追加していないことに注意してください。これを行うには、cancelをオーバーライドして(または、より正確には、isCancelledの値を観察して)それを処理する必要があります。それは私の単純なstartの例を非常に複雑にするでしょう。

コマンドラインコンソールアプリで、maxConcurrentOperationCountが5のキューに15の操作を追加し、キューがwaitUntilAllOperationsAreFinishedの使用を終了するのを待つことで、このコードを実行しました(これがバックグラウンドキューを使用した理由です)私のstartdispatch_afterの場合)。これは出力です:

2019-01-22 13:29:32.897893-0800 test[86762:4812871] Starting <MyOperation: 0x10058d2d0>
2019-01-22 13:29:32.897893-0800 test[86762:4812872] Starting <MyOperation: 0x10058d710>
2019-01-22 13:29:32.897903-0800 test[86762:4812873] Starting <MyOperation: 0x100589930>
2019-01-22 13:29:32.898161-0800 test[86762:4812871] Starting <MyOperation: 0x10058edc0>
2019-01-22 13:29:32.898166-0800 test[86762:4812873] Starting <MyOperation: 0x10058ed50>
2019-01-22 13:29:37.898487-0800 test[86762:4812872] Finished <MyOperation: 0x100589930>
2019-01-22 13:29:37.898489-0800 test[86762:4812870] Finished <MyOperation: 0x10058ed50>
2019-01-22 13:29:37.898548-0800 test[86762:4812874] Finished <MyOperation: 0x10058edc0>
2019-01-22 13:29:37.898797-0800 test[86762:4812870] Starting <MyOperation: 0x100590000>
2019-01-22 13:29:37.899160-0800 test[86762:4812870] Finished <MyOperation: 0x10058d710>
2019-01-22 13:29:37.899651-0800 test[86762:4812870] Starting <MyOperation: 0x1005901a0>
2019-01-22 13:29:37.899933-0800 test[86762:4812874] Starting <MyOperation: 0x100590340>
2019-01-22 13:29:37.900133-0800 test[86762:4812871] Finished <MyOperation: 0x10058d2d0>
2019-01-22 13:29:37.900504-0800 test[86762:4812871] Starting <MyOperation: 0x100590680>
2019-01-22 13:29:37.900583-0800 test[86762:4812874] Starting <MyOperation: 0x1005904e0>
2019-01-22 13:29:42.899325-0800 test[86762:4812871] Finished <MyOperation: 0x100590000>
2019-01-22 13:29:42.899541-0800 test[86762:4812874] Starting <MyOperation: 0x100590820>
2019-01-22 13:29:43.393291-0800 test[86762:4812871] Finished <MyOperation: 0x1005901a0>
2019-01-22 13:29:43.393298-0800 test[86762:4812874] Finished <MyOperation: 0x100590340>
2019-01-22 13:29:43.394531-0800 test[86762:4812874] Finished <MyOperation: 0x1005904e0>
2019-01-22 13:29:43.395380-0800 test[86762:4812874] Finished <MyOperation: 0x100590680>
2019-01-22 13:29:43.396359-0800 test[86762:4812874] Starting <MyOperation: 0x1005909c0>
2019-01-22 13:29:43.397440-0800 test[86762:4812872] Starting <MyOperation: 0x100590b60>
2019-01-22 13:29:43.397891-0800 test[86762:4812874] Starting <MyOperation: 0x100590d00>
2019-01-22 13:29:43.399711-0800 test[86762:4812872] Starting <MyOperation: 0x100590ea0>
2019-01-22 13:29:47.900058-0800 test[86762:4812984] Finished <MyOperation: 0x100590820>
2019-01-22 13:29:48.892953-0800 test[86762:4812872] Finished <MyOperation: 0x100590d00>
2019-01-22 13:29:48.892970-0800 test[86762:4812871] Finished <MyOperation: 0x100590b60>
2019-01-22 13:29:48.893019-0800 test[86762:4813163] Finished <MyOperation: 0x100590ea0>
2019-01-22 13:29:48.893562-0800 test[86762:4812984] Finished <MyOperation: 0x1005909c0>
Program ended with exit code: 0
0
Steven Fisher