【问题标题】:Passing result of NSOperation to another NSOperation将 NSOperation 的结果传递给另一个 NSOperation
【发布时间】:2015-12-23 08:38:16
【问题描述】:

我有两个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

我的问题是,当尝试将一个操作的结果传递给链中的下一个操作时,最好的方法是什么?

【问题讨论】:

  • 如果在您的代码中将 parseOperation 实例化为局部变量,您应该能够在下载完成块中使用它。然后,您可以将 self.parseOperation 设置为指向同一个对象 - 因此您应该能够通过对代码进行少量修改来实现所请求的功能。
  • 不管怎样,在这种情况下,我喜欢使用 Bolts 框架。这是一种将并行或串行任务链接在一起的非常简单的方法,并且可以与 NSOperation、GCD 等一起使用。如果您有兴趣,请查看。 github.com/BoltsFramework/Bolts-iOS

标签: ios swift ios9 nsoperation


【解决方案1】:

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
          ...
      }
    }
 }

等等。

【讨论】:

  • 或者,如果您有多个依赖项,您可以过滤列表。即:if let downloadOp = self.dependencies.filter({ $0 is DownloadActivitiesOperation }).last as? DownloadActivitiesOperation { ...
【解决方案2】:

使用文件缓存

看起来您正在使用Advanced NSOperations 上的 WWDC2015 演讲中的某种 GroupOperation 实现。在谈话的示例代码中,他们使用缓存文件在下载器和解析器之间传递数据。

遵循GetEarthquakesOperation 类的 sn-p:

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)

使用内存缓存

我目前在我的一个项目中的解决方案是将结果包装在一个类中并将其传递给两个操作:

class OperationResultWrapper<T> { var result: T? } let wrapper = OperationResultWrapper<NSData>() downloadOperation = DownloadEarthquakesOperation(resultWrapper: wrapper) parseOperation = ParseEarthquakesOperation(dataWrapper: wrapper, context: context)

【讨论】:

    【解决方案3】:

    我刚刚完成了大部分生产代码的移动以使用 NSOperation 和 NSOperationQueues。共享结果(通知、委托)的典型解决方案看起来很麻烦且笨拙,所以这是我的解决方案。

    1. 子类 NSOperationQueue 以包含线程安全的可变字典实例属性。我将此处发布的线程安全可变字典代码改编为我的子类 JobOperationQueue:https://www.guiguan.net/ggmutabledictionary-thread-safe-nsmutabledictionary/

    2. 子类 NSOperation 以包含对其拥有/初始 JobOperationQueue 的引用。这确保了操作始终可以找到它的所有者,即使代码必须在不同的队列上运行(这种情况比我想象的要多!)。将子类方法添加到 JobOperationQueue 以在通过 addOperation: 或 addOperations: 将操作添加到队列时设置此值:

    3. 作为操作进程,它们可以将值添加到队列的字典中,并访问先前进程放置在那里的值。

    我对这种方法非常满意,它解决了很多问题。

    注意竞争条件 - 如果一个操作必须具有来自另一个操作的值,请确保显式添加了依赖项以确保操作顺序。

    这是我的两个主要课程。我还添加了双向依赖信息,我发现这在操作必须产生子操作但想要维护依赖网络的情况下很有用。在这种情况下,您必须知道谁依赖于原始操作,以便您可以将依赖关系传播到衍生的操作上。

    //
    //  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  
    

    【讨论】:

    • 围绕线程处理可变状态可能是个坏主意。此外,此实现不是线程安全的,例如,当一个线程使用 keyEnumerator 而另一个线程正在删除键时,您会遇到麻烦。
    • eik - 因为对键的唯一引用是在线程安全字典链接中,我假设这就是您所指的。我看不出调度障碍是如何允许你描述的情况的。
    【解决方案4】:

    您可以先创建依赖链NSOperations 而不使用self,然后初始化您的属性并最后调用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 并 将它们以数组的形式传递给超类对我来说似乎很奇怪,但我不知道你的其余部分代码。

    【讨论】:

      【解决方案5】:

      您可以在开始块之前创建一个 self 的弱变量。

      尝试在块开始之前添加此行:

      __weak __typeof(self) weakSelf = self;

      【讨论】:

        【解决方案6】:

        感谢 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
            }
        
        }
        

        然后假设您使用两个操作,操作一下载和操作二处理下载的文件,您的 main() 将包含以下内容:

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

        【讨论】:

          【解决方案7】:

          试试这个

          ` 在 Swift 语言中,不允许从其他指定初始化程序中调用指定初始化程序,因此您可以将初始化程序标记为方便。

          有关更多信息,请查看语言指南部分中的 Swift 编程语言的初始化 `

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2014-08-15
            • 1970-01-01
            • 1970-01-01
            • 1970-01-01
            • 2014-03-05
            • 1970-01-01
            • 2016-07-06
            相关资源
            最近更新 更多