问题
I am making three api calls and want that API1 should execute first, once completed API2 should execute followed by API3. I used operation queue for this with adding dependency over operations. I tried setting priority as well but not getting api calls in order. Help me out how to make it properly.
Code is like this :
let op1 = Operation()
op1.completionBlock = {
self.APICall(urlString: self.url1)
}
op1.queuePriority = .veryHigh
let op2 = Operation()
op2.completionBlock = {
self.APICall(urlString: self.url2)
}
op2.queuePriority = .high
let op3 = Operation()
op3.completionBlock = {
self.APICall(urlString: self.url3)
}
op3.queuePriority = .normal
op2.addDependency(op1)
op3.addDependency(op2)
queue.addOperations([op1, op2, op3], waitUntilFinished: false)
I put the API Call Method in DispatchQueue.main.sync like this:
func APICall(urlString: String) {
let headers: HTTPHeaders = [
"Accept": "text/html"
]
print(urlString)
DispatchQueue.main.sync {
Alamofire.request(urlString.addingPercentEncoding(withAllowedCharacters: CharacterSet.urlQueryAllowed)!, method: .get, parameters: nil, encoding: JSONEncoding.default, headers: headers).responseJSON {
response in
// self.stopActivityIndicator()
print(response.result.value)
switch response.result {
case .success:
break
case .failure(let error):
break
}
}
}
}
回答1:
There are several issues:
If you’re trying to manage dependencies between operations, you cannot use the operation’s
completionBlock
for the code that the dependencies rely upon. The completion block isn't called until after the operation is complete (and thus defeating the purpose of any dependencies).So the following will not work as intended:
let queue = OperationQueue() let op1 = Operation() op1.completionBlock = { print("starting op1") Thread.sleep(forTimeInterval: 1) print("finishing op1") } let op2 = Operation() op2.completionBlock = { print("starting op2") Thread.sleep(forTimeInterval: 1) print("finishing op2") } op2.addDependency(op1) queue.addOperations([op1, op2], waitUntilFinished: false)
But if you define the operations like so, it will work:
let op1 = BlockOperation() { print("starting op1") Thread.sleep(forTimeInterval: 1) print("finishing op1") } let op2 = BlockOperation { print("starting op2") Thread.sleep(forTimeInterval: 1) print("finishing op2") }
(But this only works because I redefined operations that were synchronous. See point 3 below.)
It’s worth noting generally you never use
Operation
directly. As the docs say:An abstract class that represents the code and data associated with a single task. ...
Because the
Operation
class is an abstract class, you do not use it directly but instead subclass or use one of the system-defined subclasses (NSInvocationOperation or BlockOperation) to perform the actual task.Hence the use of
BlockOperation
, above, or subclassing it as shown below in point 3.One should not use priorities to manage the order that operations execute if the order must be strictly honored. As the
queuePriority
docs say (emphasis added):This value is used to influence the order in which operations are dequeued and executed...
You should use priority values only as needed to classify the relative priority of non-dependent operations. Priority values should not be used to implement dependency management among different operation objects. If you need to establish dependencies between operations, use the
addDependency(_:)
method instead.So, if you queue 100 high priority operations and 100 default priority operations, you are not guaranteed that all of the high priority ones will start before the lower priority ones start running. It will tend to prioritize them, but not strictly so.
The first point is moot, as you are calling asynchronous methods. So you can’t use simple
Operation
orBlockOperation
. If you don’t want a subsequent network request to start until the prior one finishes, you’ll want to wrap these network request in custom asynchronousOperation
subclass with all of the special KVO that entails:class NetworkOperation: AsynchronousOperation { var request: DataRequest static var sessionManager: SessionManager = { let manager = Alamofire.SessionManager(configuration: .default) manager.startRequestsImmediately = false return manager }() init(urlString: String, parameters: [String: String]? = nil, completion: @escaping (Result<Any>) -> Void) { let headers: HTTPHeaders = [ "Accept": "text/html" ] let string = urlString.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)! let url = URL(string: string)! request = NetworkOperation.sessionManager.request(url, parameters: parameters, headers: headers) super.init() request.responseJSON { [weak self] response in completion(response.result) self?.finish() } } override func main() { request.resume() } override func cancel() { request.cancel() } }
Then you can do:
let queue = OperationQueue() let op1 = NetworkOperation(urlString: ...) { result in ... } let op2 = NetworkOperation(urlString: ...) { result in ... } let op3 = NetworkOperation(urlString: ...) { result in ... } op2.addDependency(op1) op3.addDependency(op2) queue.addOperations([op1, op2, op3], waitUntilFinished: false)
And because that’s using
AsynchronousOperation
subclass (shown below), the operations won’t complete until the asynchronous request is done./// Asynchronous operation base class /// /// This is abstract to class performs all of the necessary KVN of `isFinished` and /// `isExecuting` for a concurrent `Operation` subclass. You can subclass this and /// implement asynchronous operations. All you must do is: /// /// - override `main()` with the tasks that initiate the asynchronous task; /// /// - call `completeOperation()` function when the asynchronous task is done; /// /// - optionally, periodically check `self.cancelled` status, performing any clean-up /// necessary and then ensuring that `finish()` is called; or /// override `cancel` method, calling `super.cancel()` and then cleaning-up /// and ensuring `finish()` is called. public class AsynchronousOperation: Operation { /// State for this operation. @objc private enum OperationState: Int { case ready case executing case finished } /// Concurrent queue for synchronizing access to `state`. private let stateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".rw.state", attributes: .concurrent) /// Private backing stored property for `state`. private var _state: OperationState = .ready /// The state of the operation @objc private dynamic var state: OperationState { get { stateQueue.sync { _state } } set { stateQueue.sync(flags: .barrier) { _state = newValue } } } // MARK: - Various `Operation` properties open override var isReady: Bool { return state == .ready && super.isReady } public final override var isAsynchronous: Bool { return true } public final override var isExecuting: Bool { return state == .executing } public final override var isFinished: Bool { return state == .finished } // KVN for dependent properties open override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> { if ["isReady", "isFinished", "isExecuting"].contains(key) { return [#keyPath(state)] } return super.keyPathsForValuesAffectingValue(forKey: key) } // Start public final override func start() { if isCancelled { state = .finished return } state = .executing main() } /// Subclasses must implement this to perform their work and they must not call `super`. The default implementation of this function throws an exception. open override func main() { fatalError("Subclasses must implement `main`.") } /// Call this function to finish an operation that is currently executing public final func finish() { if !isFinished { state = .finished } } }
As very minor observation, your code specified GET request with JSON parameters. That doesn’t make sense. GET requests have no body in which JSON could be included. GET requests only use URL encoding. Besides you’re not passing any parameters.
来源:https://stackoverflow.com/questions/57246580/operation-queue-not-executing-in-order-even-after-setting-priority-and-dependenc