即使在设置了操作的优先级和依赖性之后,操作队列也没有按顺序执行

Operation Queue not executing in order even after setting priority and dependency on Operations

我正在进行三个 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。作为 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.

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

  2. 如果必须严格遵守顺序,则不应使用优先级来管理操作执行的顺序。如queuePrioritydocs say(重点补充):

    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.

    因此,如果您将 100 个高优先级操作和 100 个默认优先级操作排队,您不能保证所有高优先级操作会在低优先级操作开始之前启动 运行。它会倾向于优先考虑它们,但并非严格如此。

  3. 第一点没有实际意义,因为您正在调用异步方法。所以你不能使用简单的 OperationBlockOperation。如果您不希望后续网络请求在前一个网络请求完成之前开始,您将需要将这些网络请求包装在自定义异步 Operation 子类中,其中包含所有需要的特殊 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 编码。此外,您没有传递任何参数。