操作队列在设置优先级和对操作的依赖关系后,也不会按顺序执行



我正在进行三个 api 调用,并希望 API1 应该首先执行,完成后 API2 应该执行,然后是 API3。 我为此使用了操作队列,并添加了对操作的依赖关系。我也尝试设置优先级,但没有按顺序收到 api 调用。帮助我如何正确制作它。

代码是这样的:

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)

我将API调用方法放在DispatchQueue.main.sync中,如下所示:

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. 如果尝试管理操作之间的依赖项,则不能将操作的completionBlock用于依赖项所依赖的代码。直到操作完成之后才会调用完成块(从而破坏任何依赖项的目的)。

    因此,以下内容将无法按预期工作:

    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)
    

    但是,如果您像这样定义操作,它将起作用:

    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")
    }
    

    (但这仅有效,因为我重新定义了同步操作。请参阅下面的第 3 点。

    值得注意的是,通常您从不直接使用Operation。正如文档所说:

    一个抽象类,表示与单个任务关联的代码和数据。

    因为Operation类是一个抽象类,所以你不直接使用它,而是使用子类或使用系统定义的子类(NSInvocationOperationBlockOperation)之一来执行实际任务。

    因此,使用上面的BlockOperation或子类化,如下面的第3点所示。

  2. 如果必须严格遵守顺序,则不应使用优先级来管理操作执行的顺序。正如queuePriority文档所说(着重号是加的):

    此值用于影响操作取消排队和执行的顺序...

    应仅根据需要使用优先级值对非依赖操作的相对优先级进行分类。优先级值不应用于实现不同操作对象之间的依赖关系管理。如果需要在操作之间建立依赖关系,请改用addDependency(_:)方法。

    因此,如果将 100 个高优先级操作和 100 个默认优先级操作排队,则无法保证所有高优先级操作都将在低优先级操作开始运行之前启动。它倾向于优先考虑它们,但不是严格意义上的。

  3. 第一点是没有意义的,因为您正在调用异步方法。所以你不能使用简单的OperationBlockOperation.如果您不希望后续网络请求在前一个网络请求完成之前启动,则需要将这些网络请求包装在自定义异步Operation子类中,其中包含所有特殊的 KVO,这些 KVO 需要:

    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()
    }
    }
    

    然后你可以做:

    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)
    

    由于它使用的是AsynchronousOperation子类(如下所示),因此在异步请求完成之前,操作不会完成。

    /// 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 }
    }
    }
    
  4. 作为非常小的观察,您的代码使用 JSON 参数指定了 GET 请求。这说不通。GET请求没有可以包含JSON的正文。GET 请求仅使用 URL 编码。此外,您没有传递任何参数。

最新更新