web-dev-qa-db-ja.com

NSOperationの結果を別のNSOperationに渡す

ダウンロードと解析を担当する2つのNSOperationがあります。ダウンロード操作が成功し、NSDataを受け取った後、そのデータを解析操作で使用するデータとして設定します。

init(context: NSManagedObjectContext, completionHandler: Void -> Void) {

    downloadOperation = DownloadActivitiesOperation() { data in
        self.parseOperation.data = data
    }
    parseOperation = ParseActivitiesOperation(context: context)

    let finishOperation = NSBlockOperation(block: completionHandler)

    parseOperation.addDependency(downloadOperation)
    finishOperation.addDependency(parseOperation)

    super.init(operations: [downloadOperation, parseOperation, finishOperation])

    name = "Get Activities"
}

ただし、super.initを呼び出す前に、ダウンロード完了ブロック内でselfを使用しようとしているため、これは機能しません。

私の質問は、ある操作の結果をチェーン内の次の操作に渡そうとする場合の最良のアプローチは何でしょうか?

26
Kyle Decot

NSOperationの各インスタンスには、依存関係の配列が含まれています。終了後、操作はこの配列から削除されません。これらのオブジェクトを使用して、データにアクセスできます。

class DownloadActivitiesOperation: NSOperation {
   var data: NSData?
   ...
   // set self.data when it has downloaded
}

class ParseActivitiesOperation: NSOperation {
    func main() {
      if let downloadOp = self.dependencies.last as? DownloadActivitiesOperation {
          let dataToParse = downloadOp.data
          ...
      }
    }
 }

等々。

18
ilya

ファイルキャッシュを使用する

Advanced NSOperations に関するWWDC2015の講演からGroupOperationのある種の実装を使用しているようです。トークのサンプルコードでは、キャッシュファイルを使用してダウンローダーとパーサーの間でデータを渡します。

GetEarthquakesOperationクラスの次のスニペット:

    
        let cachesFolder = try! NSFileManager.defaultManager().URLForDirectory(.CachesDirectory, inDomain: .UserDomainMask, appropriateForURL: nil, create: true)

        let cacheFile = cachesFolder.URLByAppendingPathComponent("earthquakes.json")

        /*
            This operation is made of three child operations:
            1. The operation to download the JSON feed
            2. The operation to parse the JSON feed and insert the elements into the Core Data store
            3. The operation to invoke the completion handler
        */
        downloadOperation = DownloadEarthquakesOperation(cacheFile: cacheFile)
        parseOperation = ParseEarthquakesOperation(cacheFile: cacheFile, context: context)

メモリキャッシュを使用する

私のプロジェクトの1つでの現在の解決策は、結果をクラスにラップして、両方の操作に渡すことです。


class OperationResultWrapper<T> {
    var result: T?
}

    let wrapper = OperationResultWrapper<NSData>()
    downloadOperation = DownloadEarthquakesOperation(resultWrapper: wrapper)
    parseOperation = ParseEarthquakesOperation(dataWrapper: wrapper, context: context)
7
tidbeck

NSOperationとNSOperationQueuesを使用するために本番コードの大部分を移動し終えたところです。結果を共有するための一般的な解決策(通知、代表者)は面倒で扱いにくいように思われたので、ここに私の解決策があります。

  1. NSOperationQueueをサブクラス化して、スレッドセーフな可変ディクショナリインスタンスプロパティを含めます。ここで公開されているスレッドセーフな可変辞書コードをサブクラスJobOperationQueueに適合させました: https://www.guiguan.net/ggmutabledictionary-thread-safe-nsmutabledictionary/

  2. NSOperationをサブクラス化して、所有/初期JobOperationQueueへの参照を含めます。これにより、コードを別のキューで実行する必要がある場合でも、操作が常に所有者を見つけることができます(これは私が思っていた以上に起こります!)。サブクラスメソッドをJobOperationQueueに追加して、addOperation:またはaddOperations:を介して操作がキューに追加されるたびにこの値を設定します。

  3. 操作プロセスとして、キューのディクショナリに値を追加し、以前のプロセスによってそこに配置された値にアクセスできます。

私はこのアプローチに非常に満足しており、多くの問題を解決しました。

競合状態に注意してください。ある操作に別の操作の値が必要な場合は、操作の順序を確認するために依存関係が明示的に追加されていることを確認してください。

これが私の2つの主要なクラスです。また、双方向の依存関係情報を追加しました。これは、操作が子操作を生成する必要があるが、依存関係ネットワークを維持したい場合に役立ちます。そのような状況では、生成された操作に依存関係を伝播できるように、元の操作に誰が依存しているかを知る必要があります。

//
//  JobOperation.h
//  
//
//  Created by Terry Grossman on 9/17/15.
//

#import <Foundation/Foundation.h>
#import "JobOperationQueue.h"
#import "ThreadSafeMutableDictionary.h"
#import "ThreadSafeMutableArray.h"

@interface JobOperation : NSOperation

@property (strong, atomic) ThreadSafeMutableArray *dependents;    
@property (strong, atomic) NSDate *enqueueDate;
@property (weak, atomic) JobOperationQueue *homeJobQueue;

-(ThreadSafeMutableDictionary *)getJobDict;

@end

//
//  JobOperation.m
// 
//
//  Created by Terry Grossman on 9/17/15.
//

#import "JobOperation.h"

@implementation JobOperation


- (id)init
{
    if((self = [super init])) {
        _dependents = [[ThreadSafeMutableArray alloc] init];
    }

    return self;
}


-(ThreadSafeMutableDictionary *)getJobDict
{
    id owningQueue = self.homeJobQueue;
    if (owningQueue && [owningQueue isKindOfClass:[JobOperationQueue class]])
    {
        return ((JobOperationQueue *)owningQueue).jobDictionary;
    }


    // try to be robust -- handle weird situations
    owningQueue = [NSOperationQueue currentQueue];
    if (owningQueue && [owningQueue isKindOfClass:[JobOperationQueue class]])
    {
        return ((JobOperationQueue *)owningQueue).jobDictionary;
    }

    return nil;
}

-(void) addDependency:(NSOperation *)op
{
    [super addDependency:op];  // this adds op into our list of dependencies

    if ([op isKindOfClass:[JobOperation class]])
    {
        [((JobOperation *)op).dependents addObject:self];  // let the other job op know we are depending on them
    }
}

@end


//
//  JobOperationQueue.h
// 
//
//  Created by Terry Grossman on 9/17/15.
//

#import <Foundation/Foundation.h>
#import "ThreadSafeMutableDictionary.h"

// A subclass of NSOperationQueue
// Adds a thread-safe dictionary that queue operations can read/write
// in order to share operation results with other operations.

@interface JobOperationQueue : NSOperationQueue

// If data needs to be passed to or between job operations
@property (strong, atomic) ThreadSafeMutableDictionary *jobDictionary;


-(void)addOperation:(NSOperation *)operation;
-(void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;

+(BOOL) checkQueue:(JobOperationQueue *)queue hasOpsOlderThan:(NSInteger)secondsThreshold cancelStaleOps:(BOOL)cancelOps;



@end

//
//  JobOperationQueue.m
// 
//
//  Created by Terry Grossman on 9/17/15.
//

#import "JobOperationQueue.h"
#import "JobOperation.h"


@implementation JobOperationQueue


// if this method returns NO, should set the queue to nil and alloc a new one
+(BOOL) checkQueue:(JobOperationQueue *)queue hasOpsOlderThan:(NSInteger)secondsThreshold cancelStaleOps:(BOOL)cancelOps
{
    if (queue == nil) 
    {
        return NO;
    }

    if ([queue operationCount] > 0) 
    {
        NSLog(@"previous share still processing!");

        // recently started or stale?  Check the enqueue date of the first op.
        JobOperation *oldOp = [[queue operations] objectAtIndex:0];

        NSTimeInterval sourceSeconds = [[NSDate date] timeIntervalSinceReferenceDate];
        NSTimeInterval destinationSeconds = [oldOp.enqueueDate timeIntervalSinceReferenceDate];    
        double diff =  fabs( destinationSeconds - sourceSeconds );        

        if (diff > secondsThreshold) 
        {
            // more than three minutes old!  Let's cancel them and tell caller to proceed
            [queue cancelAllOperations];
            return NO;
        }
        else
        {
            return YES;
        }

    }
    return NO;
}


-(id) init;
{
    if((self = [super init])) {
        _jobDictionary = [[ThreadSafeMutableDictionary alloc] initWithCapacity:12];
    }

    return self;
}

-(void)addOperation:(NSOperation *)operation;
{
    if (operation == nil) 
    {
        return;
    }

    if ([operation isKindOfClass:[JobOperation class]]) 
    {
        ((JobOperation *)operation).enqueueDate = [NSDate date];
        //((JobOperation *)operation).homeQueueT = self.underlyingQueue; // dispatch_get_current_queue();
        ((JobOperation *)operation).homeJobQueue = self;
    }

    [super addOperation:operation];    
}

-(void)addOperations:(NSArray<NSOperation *> *)ops waitUntilFinished:(BOOL)wait;
{
    for (NSOperation *operation  in ops) 
    {
        if ([operation isKindOfClass:[JobOperation class]]) 
        {
            ((JobOperation *)operation).enqueueDate = [NSDate date];
            //((JobOperation *)operation).homeQueueT = self.underlyingQueue; //dispatch_get_current_queue();
            ((JobOperation *)operation).homeJobQueue = self;
        }
    }

    [super addOperations:ops waitUntilFinished:wait];
}

@end  
2

最初にNSOperationを使用せずに依存するselfsのチェーンを作成し、次にプロパティを初期化して最後にsuper.initを呼び出すことができます。

init(context: NSManagedObjectContext, completionHandler: () -> Void) {
    let finishOperation = NSBlockOperation(block: completionHandler)

    let parseOperation = ParseActivitiesOperation(context: context)
    finishOperation.addDependency(parseOperation)

    let downloadOperation = DownloadActivitiesOperation() { data in
        parseOperation.data = data
    }
    parseOperation.addDependency(downloadOperation)

    self.parseOperation = parseOperation
    self.downloadOperation = downloadOperation
    self.name = "Get Activities"

    super.init(operations: [downloadOperation, parseOperation, finishOperation])
}

注:downloadOperationparseOperationをプロパティに格納するのは奇妙に思えますandそれらを配列でスーパークラスに渡しますが、私にはわかりません残りのコード。

1
eik

ブロックを開始する前に、自己の弱い変数を作成できます。

ブロックが始まる前に、この行を追加してみてください。

__weak __typeof(self) weakSelf = self;

0
user1478164

回答をくれたilyaのおかげで、依存関係配列を介してOperationサブクラスを介して他の操作にアクセスしたことに気付きました。

結局、私はこの拡張機能を思いついた:

extension Operation{

    func getOperationFromDependancies<T:Operation>(withType operationType:T.Type) -> T?{
        for dependency in self.dependencies {
            if let operation = dependency as? T{
                return operation
            }
        }
        return nil
    }

}

次に、2つの操作を使用するとします。操作1はダウンロードし、操作2はダウンロードしたファイルを処理します。main()には次のようなものが含まれます。

override func main(){
    let downloadOperation = 
    self.getOperationFromDependencies(withType:DownloadOperation.self)
    let downloadedFile = downloadedOperation?.downloadedFile
    //Process file here
}
0
Roni Leshes