【问题标题】:How can an NSOperationQueue wait for two async operations?NSOperationQueue 如何等待两个异步操作?
【发布时间】:2015-01-31 07:10:15
【问题描述】:

如何让 NSOperationQueue(或其他任何东西)等待两个带有回调的异步网络调用?流程需要看起来像这样

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
}

这是我目前所拥有的。

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

【问题讨论】:

  • 我认为NSOperationQueue 可以很好地封装 同步 操作。在您的情况下,它只会使事情变得不必要地复杂。 NSBlockOperation 可以被 NSOperationQueue 视为已完成,而您的请求仍在进行中,因此以这种方式排队请求并不是很有用。

标签: ios nsoperationqueue


【解决方案1】:

AsyncOperation 保持其状态 w.r.t 到 Operation 自己的状态。 由于操作是异步的。我们需要明确定义我们的操作状态。 因为异步操作的执行在它被调用后立即返回。 因此,在您的 AsyncOperation 子类中,您只需在完成处理程序中将操作状态设置为已完成

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

现在从你自己的 Operation 类调用 if

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

【讨论】:

  • 什么是请求?
  • request 是包含您的 URL 和其他元数据的对象。 developer.apple.com/documentation/foundation/urlrequest
  • 所示的异步代码应该放在 main 的覆盖中。请注意,在重写的 start 函数中调用了 main。这是一个很好的可重复使用的解决方案。
【解决方案2】:

使用 Grand Central Dispatch 和 DispatchGroup

使用 Swift 3,在您不需要对任务状态进行细粒度控制的最简单情况下,您可以使用 Grand Central Dispatch 和 DispatchGroup。下面的 Playground 代码展示了它是如何工作的:

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

一旦两个异步任务都完成,前面的代码将打印"Task Three finished"


使用OperationQueueOperation

OperationQueueOperation 用于您的请求任务需要更多样板代码,但提供了许多优势,例如kvoed 状态和依赖性。

1.创建一个将充当抽象类的Operation 子类

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.创建您的操作

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.用法

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)

使用此代码,operation3 保证总是最后执行。


你可以在GitHub repo找到这个游乐场。

【讨论】:

  • 这是一个很好的解决方案,但是有一个错误。在 state set(newState) 闭包中,布尔测试应为 if _state != .isFinished 否则不会发生状态更改 同样在 Swift 5 上,var state 必须以 @objc 为前缀以允许代码编译
【解决方案3】:

你必须使用NSOperation Queue吗?以下是使用调度组的方法:

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

【讨论】:

  • 这是最优雅的解决方案,+1
  • 我不想随意使用GCD。这似乎是最好的解决方案,所以我会这样做
猜你喜欢
  • 2023-03-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-12-26
  • 1970-01-01
  • 2013-05-08
  • 2015-10-16
  • 2018-06-03
相关资源
最近更新 更多