Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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.

like image 839
Ward Bekker Avatar asked Jun 25 '14 09:06

Ward Bekker


3 Answers

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.

like image 108
Drew McCormack Avatar answered Oct 31 '22 03:10

Drew McCormack


Here you can find 4 different approaches to pass the data between two Operations in Swift.

4 ways to pass data between operations in swift

like image 22
Alessandro Avatar answered Oct 31 '22 05:10

Alessandro


Just declare the second operation above the first block so you can set the recordIDs to fetch on it, as demonstrated with this edit to your example:

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

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
               fetchOperation2.recordIDs = 
                   dict.allValues().map(
                      { $0.getObject("referencedRecordId"}
               )
            }
        }

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

fetchOperation2.addDependency(fetchOperation1)

Also if you get an error in your first block, you should cancel all operations on the queue. Each block will still be called but it will have the NSError set to cancelled. So you don't need any special final block as you might have with custom operations.

like image 26
malhal Avatar answered Oct 31 '22 05:10

malhal