Search code examples
icloudnsoperationnsoperationqueueios8cloudkit

How to communicate results between NSOperation dependencies?


The new Cloud Kit framework uses NSOperation extensively for it's CRUD. The results of those operations are returned in blocks. For example:

let fetchOperation = CKFetchRecordsOperation(recordIDs: [recordID1, recordId2])

fetchOperation.fetchRecordsCompletionBlock = {(dict: NSDictionary!, error: NSError!) -> Void in
            // dict contains RecordId -> Record            
            // do something with the records here (if no error)
        }

I want to chain a few of those operations (dependencies), and pass the result of an operation to the next operation in the chain. Simplified example to illustrate this (pseudo code!):

let fetchOperation1 = CKFetchRecordsOperation(recordIDs: [recordID1, recordId2])

fetchOperation1.fetchRecordsCompletionBlock = {(dict: NSDictionary!, error: NSError!) -> Void in
            if error {
              // handle error
            } else {
               // dict contains RecordId -> Record            
               // let's pretend our records contain references to other records
               // that we want to fetch as well
               fetchOperation.operationResult = 
                   dict.allValues().map(
                      { $0.getObject("referencedRecordId"}
               )
            }
        }

let fetchOperation2 = CKFetchRecordsOperation(recordIDs: fetchOperation1.operationResult)

fetchOperation2.fetchRecordsCompletionBlock = {(dict: NSDictionary!, error: NSError!) -> Void in
            if error {
              // handle error
            } else {
              // dosomething
            }
        }

fetchOperation2.addDependency(fetchOperation2)

But above pseudo code can never work, as the fetchOperation1.operationResult is not yet assigned when you init fetchOperation2. You could nest the init of fetchOperation2 in fetchOperation1's completionBlock, but than you ditch the dependency functionality of NSOperation, which I'm trying to use here.

So, I'm looking for a clean, readable, standard (no reactive cocoa and such) solution to work have NSOperation dependencies pass data along in their chain.


Solution

  • I remember when NSOperation was first introduced, and I had to write a introductory article for the ADC site that would first download some photos, and then render them into a poster. I ran into similar issues there: using dependencies to control order, but then finding I had to pass the image filenames to the dependent operation.

    That was many years ago, and at that time I had NSOperation subclasses for each task. I set dependencies between them, and added a delegate to the operations that needed to be passed results from an earlier operation. In the delegate method, the controller object would retrieve the results from a property of the first operation, and set them via a property on the second.

    Perhaps a better solution is to have the relationships between operations explicit, not only in terms of dependencies, but also in terms of the passing of data. So you could create NSOperation subclasses which pass data to the next NSOperation as part of standard operation, or — more elegantly — pull data from a completed operation.

    To make this more concrete: operation B depends on A being complete. A generates resource R which is needed by B to run. You add a property to B that references the A object. When B runs, it simply retrieves R from the A object.

    If you would rather not create operation subclasses, and just want to avoid nesting of blocks, you could consider using a queueing mechanism that gives you a bit more control, such as CDEAsynchronousTaskQueue.