[iOS] Asynchronous Operation

iOS的Operation是synchronous的, 無法做到一些asynchronous的動作如排序下載檔案, 這時候就需要自己寫一個asynchronous的Operation.

FCAsynchronousOperation:

(1.) 繼承Operation

(2.) override isAsynchronous變數, 設為true

(3.) override isReady, isExecuting, isFinished 三個變數

(此例用一個state變數結合KVO來實現)

(4.) override start()與main() function

(5.) 新增一個finish() function 來定義Asynchronous Operation何時結束

(6.) 繼承FCAsynchronousOperation的subclass 需implement main() function

import UIKit

public class FCAsynchronousOperation: Operation {
    
    @objc private enum State: Int {
        case ready
        case executing
        case finished
    }
    
    /// Concurrent queue for synchronizing access to `state`.
    private let stateQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".operationQueue.state", attributes: .concurrent)
    
    /// Private backing stored property for `state`.
    private var _state: State = .ready
    
    /// The state of the operation
    @objc private dynamic var state: State {
        get { return stateQueue.sync { _state } }
        set { stateQueue.sync(flags: .barrier) { _state = newValue } }
    }
    
    // MARK: - Various `Operation` properties
    open override var isAsynchronous: Bool { return true }
    open override var isReady: Bool { return state == .ready && super.isReady }
    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 {
            finish()
            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 isExecuting { state = .finished }
    }
}

Last updated