How can an NSOperationQueue wait for two async operations?

痴心易碎 提交于 2019-12-18 12:34:59

问题


How can I make an NSOperationQueue (or anything else) wait for two async network calls with callbacks? The flow needs to look like this

Block Begins {
    Network call with call back/block begins {
        first network call is done 
    }
}
Second Block Begins {
    Network call with call back/block begins {
        second network call is done 
    }
} 

Only run this block once the NETWORK CALLS are done {
    blah
}

Here's what I have so far.

NSOperationQueue *queue = [[NSOperationQueue alloc] init];
__block NSString *var;


[queue addOperation:[NSBlockOperation blockOperationWithBlock:^{
   [AsyncReq get:^{
       code
    } onError:^(NSError *error) {
       code
    }];
}]];

[queue addOperation:[NSBlockOperation blockOperationWithBlock:^{
   [AsyncReq get:^{
       code
    } onError:^(NSError *error) {
       code
    }];
}]];
[queue waitUntilAllOperationsAreFinished];
//do something with both of the responses

回答1:


Do you have to use NSOperation Queue? Here's how you can do it w/ a dispatch group:

dispatch_group_t group = dispatch_group_create();

dispatch_group_enter(group);
[AsyncReq get:^{
    code
    dispatch_group_leave(group); 
} onError:^(NSError *error) {
    code
    dispatch_group_leave(group);
}];


dispatch_group_enter(group);
[AsyncReq get:^{
    code
    dispatch_group_leave(group); 
} onError:^(NSError *error) {
    code
    dispatch_group_leave(group);
}];

dispatch_group_notify(group, dispatch_get_main_queue(), ^{
    NSLog(@"Both operations completed!")
});



回答2:


Using Grand Central Dispatch and DispatchGroup

With Swift 3, in the simplest cases where you don't need fine grained control on tasks states, you can use Grand Central Dispatch and DispatchGroup. The following Playground code shows how it works:

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

let group = DispatchGroup()

group.enter()
// Perform some asynchronous operation
let queue1 = DispatchQueue(label: "com.example.imagetransform")
queue1.async {
    print("Task One finished")
    group.leave()
}

group.enter()
// Perform some asynchronous operation
let queue2 = DispatchQueue(label: "com.example.retrievedata")
queue2.async {
    print("Task Two finished")
    group.leave()
}

group.notify(queue: DispatchQueue.main, execute: { print("Task Three finished") })

The previous code will print "Task Three finished" once the two asynchronous tasks are both finished.


Using OperationQueue and Operation

Using OperationQueue and Operation for your request tasks requires more boilerplate code but offers many advantages like kvoed state and dependency.

1. Create an Operation subclass that will act as an abstract class

import Foundation

/**
 NSOperation documentation:
 Operation objects are synchronous by default.
 At no time in your start method should you ever call super.
 When you add an operation to an operation queue, the queue ignores the value of the asynchronous property and always calls the start method from a separate thread.
 If you are creating a concurrent operation, you need to override the following methods and properties at a minimum:
 start, asynchronous, executing, finished.
 */

open class AbstractOperation: Operation {

    @objc enum State: Int {
        case isReady, isExecuting, isFinished

        func canTransition(toState state: State) -> Bool {
            switch (self, state) {
            case (.isReady, .isExecuting):      return true
            case (.isReady, .isFinished):       return true
            case (.isExecuting, .isFinished):   return true
            default:                            return false
            }
        }
    }

    // use the KVO mechanism to indicate that changes to `state` affect other properties as well
    class func keyPathsForValuesAffectingIsReady() -> Set<NSObject> {
        return [#keyPath(state) as NSObject]
    }

    class func keyPathsForValuesAffectingIsExecuting() -> Set<NSObject> {
        return [#keyPath(state) as NSObject]
    }

    class func keyPathsForValuesAffectingIsFinished() -> Set<NSObject> {
        return [#keyPath(state) as NSObject]
    }

    // A lock to guard reads and writes to the `_state` property
    private let stateLock = NSLock()

    private var _state = State.isReady
    var state: State {
        get {
            stateLock.lock()
            let value = _state
            stateLock.unlock()
            return value
        }
        set (newState) {
            // Note that the KVO notifications MUST NOT be called from inside the lock. If they were, the app would deadlock.
            willChangeValue(forKey: #keyPath(state))

            stateLock.lock()
            if _state == .isFinished {
                assert(_state.canTransition(toState: newState), "Performing invalid state transition from \(_state) to \(newState).")
                _state = newState
            }
            stateLock.unlock()

            didChangeValue(forKey: #keyPath(state))
        }
    }

    override open var isExecuting: Bool {
        return state == .isExecuting
    }

    override open var isFinished: Bool {
        return state == .isFinished
    }

    var hasCancelledDependencies: Bool {
        // Return true if this operation has any dependency (parent) operation that is cancelled
        return dependencies.reduce(false) { $0 || $1.isCancelled }
    }

    override final public func start() {
        // If any dependency (parent operation) is cancelled, we should also cancel this operation
        if hasCancelledDependencies {
            finish()
            return
        }

        if isCancelled {
            finish()
            return
        }

        state = .isExecuting
        main()
    }

    open override func main() {
        fatalError("This method has to be overriden and has to call `finish()` at some point")
    }

    open func didCancel() {
        finish()
    }

    open func finish() {
        state = .isFinished
    }

}

2. Create your operations

import Foundation

open class CustomOperation1: AbstractOperation {

    override open func main() {
        if isCancelled {
            finish()
            return
        }

        // Perform some asynchronous operation
        let queue = DispatchQueue(label: "com.app.serialqueue1")
        let delay = DispatchTime.now() + .seconds(5)
        queue.asyncAfter(deadline: delay) {
            self.finish()
            print("\(self) finished")
        }
    }

}
import Foundation

open class CustomOperation2: AbstractOperation {

    override open func main() {
        if isCancelled {
            finish()
            return
        }

        // Perform some asynchronous operation
        let queue = DispatchQueue(label: "com.app.serialqueue2")
        queue.async {
            self.finish()
            print("\(self) finished")
        }
    }

}

3. Usage

import Foundation
import PlaygroundSupport

PlaygroundPage.current.needsIndefiniteExecution = true

// Declare operations
let operation1 = CustomOperation1()
let operation2 = CustomOperation2()
let operation3 = CustomOperation1()

// Set operation3 to perform only after operation1 and operation2 have finished
operation3.addDependency(operation2)
operation3.addDependency(operation1)

// Launch operations
let queue = OperationQueue()
queue.addOperations([operation2, operation3, operation1], waitUntilFinished: false)

With this code, operation3 is guaranteed to always be performed last.


You can find this Playground on this GitHub repo.




回答3:


AsyncOperation maintains its state w.r.t to Operation own state. Since the operations are of asynchronous nature. We need to explicitly define the state of our operation. Because the execution of async operation returns immediately after it's called. So in your subclass of AsyncOperation you just have to set the state of the operation as finished in your completion handler

class AsyncOperation: Operation {

public enum State: String {
   case ready, executing, finished

    //KVC of Operation class are
    // isReady, isExecuting, isFinished
    var keyPath: String {
        return "is" + rawValue.capitalized
    }
}

//Notify KVO properties of the new/old state
public var state = State.ready {
    willSet {
        willChangeValue(forKey: newValue.keyPath)
        willChangeValue(forKey: state.keyPath)
    }
    didSet{
        didChangeValue(forKey: oldValue.keyPath)
        didChangeValue(forKey: state.keyPath)
    }
  }
}



extension AsyncOperation {

//have to make sure the operation is ready to maintain dependancy with other operation
//hence check with super first
override open var isReady: Bool {
    return super.isReady && state == .ready
}

override open var isExecuting: Bool {
    return state == .executing
}

override open var isFinished: Bool {
    return state == .finished
}

override open func start() {
    if isCancelled {
        state = .finished
        return
    }

    main()
    state = .executing
}

override open func cancel() {
    super.cancel()
    state = .finished
}  }

Now to call if from your own Operation class

Class MyOperation: AsyncOperation {


     request.send() {success: { (dataModel) in
      //waiting for success closure to be invoked before marking the state as completed
           self.state = .finished
     } }


来源:https://stackoverflow.com/questions/27259570/how-can-an-nsoperationqueue-wait-for-two-async-operations

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!