美文网首页
iOS(Swift) TaskProtocol异步任务队列

iOS(Swift) TaskProtocol异步任务队列

作者: 简单coder | 来源:发表于2021-07-07 13:14 被阅读0次

    需求前提

    昨天做需求时,一个简单的需求,一个动画展示,在动画展示几秒后,移除动画,然后我在 动画完成时completion写了个 prepareForReuse 方法去重置数据.但是冲突点来了,这个动画是 View 动画,不能提前取消,所以导致 Completion会在下次的 show 动画中被执行,如果还没来的及prepareForReuse就调用 show,这样数据展示就会出问题.

    设计

    利用 OperationQueue 维护任务队列,设置并发数为1,所有任务都由信号量控制并发

    实现

    设计协议

    protocol TaskProtocol: NSObject {
        var taskQueue: OperationQueue { get }
        func addTask(_ task:@escaping (DispatchSemaphore?, CancellableBlockOperation) -> ())
        func cancelAllTask()
    }
    

    队列属性实现

    extension TaskProtocol {
        private var taskQueue: OperationQueue {
            get {
                objc_sync_enter(self); defer { objc_sync_exit(self)}
                if let obj = objc_getAssociatedObject(self, &taskQueueKey) as? OperationQueue {
                    return obj
                } else {
                    let queue: OperationQueue = {
                        let queue = OperationQueue()
                        queue.maxConcurrentOperationCount = 1
                        queue.underlyingQueue = .global(qos: .utility)
                        return queue
                    }()
                    objc_setAssociatedObject(self, &taskQueueKey, queue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
                    return queue
                }
            }
            set {
                objc_setAssociatedObject(self, &taskQueueKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
            }
        }
    }
    

    这个操作队列的实现队列qos 采用utility,这个 UI 任务有可能持续一段时间.

    QOS_CLASS_USER_INTERACTIVE
    最高优先级,即使在争用情况下也可以运行几乎所有可用的系统CPU和I / O带宽。所以使用时应限于与用户的关键交互,例如处理主事件循环上的事件,视图绘制,动画等。
    QOS_CLASS_USER_INITIATED
    低于用户的关键交互,但相对高于系统上的其他工作,使用于持续时间短的操作
    QOS_CLASS_UTILITY
    用于一些可能需要花点时间的任务,这些任务不需要马上返回结果,比如下载任务等
    QOS_CLASS_BACKGROUND
    用于完全不紧急的任务,磁盘 I/O后台备份等用这个,使用此QOS类表明工作应以最节能和最有效的方式运行
    QOS_CLASS_DEFAULT
    优先级介于user-initiated 和 utility,由pthread_create()创建的线程没有指定QOS的属性将默认为QOS_CLASS_DEFAULT, 此值不应用作工作分类,只应在传播或恢复系统提供的QOS类值时进行设置
    

    核心实现

    extension TaskProtocol {
        /// 添加异步 UI任务
        /// - Parameters:
        ///   - task: UI 结束时调用 semaphore.signal()
        func addTask(_ task:@escaping (DispatchSemaphore?, CancellableBlockOperation) -> ()) {
            self.addTask(task, finishBlock: nil, delay: 0, timeout: 0, taskBehavier: .queue(.normal))
        }
        
        /// 添加异步 UI任务
        /// - Parameters:
        ///   - task: UI 结束时调用 semaphore.signal()
        ///   - finishBlock: 完成回调大部分情况不需要
        ///   - delay: 任务延时
        ///   - timeout: 超时
        ///   - taskBehavier: 行为,绝大部分不需要传
        func addTask(_ task:@escaping (DispatchSemaphore?, CancellableBlockOperation) -> (), finishBlock: ((Bool, CancellableBlockOperation) -> ())? = nil, delay: TimeInterval = 0, timeout: TimeInterval = 0, taskBehavier: AsyncTaskBehavior = .queue(.normal)) {
            let operation = CancellableBlockOperation(block: { (sem, op) in
                if delay > 0 {
                    _ = DispatchSemaphore(value: 0).wait(timeout: .now() + delay)
                }
                DispatchQueue.main.async {
                    task(sem, op)
                }
            }, completionBlock: { (isSuccess, op) in
                DispatchQueue.main.async {
                    finishBlock?(isSuccess, op)
                }
            }, timeout: timeout)
            
            switch taskBehavier {
            case let .queue(priority):
                operation.queuePriority = priority
                taskQueue.addOperation(operation)
            case .replaceAll:
    //            let deferredOperations = deferredOperations(queue: queue, filterRunning: false)
                taskQueue.cancelAllOperations()
                operation.queuePriority = .veryHigh
                operation.immediatelyStarted = true
                taskQueue.addOperation(operation)
    //            暂时不处理 ops
    //            for op in deferredOperations {
    //                op.queuePriority = .high
    //            }
            case .replaceCurrent:
                let deferredOperations = deferredOperations(queue: taskQueue, filterRunning: true)
                taskQueue.cancelRunningOperations()
                operation.queuePriority = .veryHigh
                operation.immediatelyStarted = true
                taskQueue.addOperation(operation)
                
                for op in deferredOperations {
                    taskQueue.addOperation(op)
                }
            }
        }
        
        /// 取消所有未执行任务
        func cancelAllTask() {
            taskQueue.cancelAllOperations()
        }
        
        private func deferredOperations(queue: OperationQueue, filterRunning: Bool) -> [CancellableBlockOperation] {
            var deferredOps = [CancellableBlockOperation]()
            for op in queue.operations {
                guard let op = op as? CancellableBlockOperation, !op.isFinished, !op.isCancelled,
                      filterRunning ? (op.isExecuting || op.immediatelyStarted) : true
                else {
                    continue
                }
                deferredOps.append(op)
            }
            return deferredOps
        }
    }
    

    核心逻辑即为用CancellableBlockOperation包装一个operation, 内部用DispatchSemaphore控制该任务的结束时机,利用 operationQueue 并发1的机制,达到异步UI 任务顺序执行的逻辑.

    CancellableBlockOperation核心逻辑

    override func main() {
            autoreleasepool {
                guard self.block != nil else { return }
                if self.isCancelled {
                    self.block = nil
                    self.finishHandler = nil
                    return
                }
                
                var backgroundIdentifier = UIBackgroundTaskIdentifier.invalid
                if self.keepRunningInBackground {
                    backgroundIdentifier = UIApplication.shared.beginBackgroundTask {
                        
                       // UIApplication.shared.endBackgroundTask(backgroundIdentifier)
                    }
                }
                
                self.semaphore = DispatchSemaphore(value: 0)
                self.block?(self.semaphore, self)
                let _ = self.semaphore?.wait(timeout: self.timeout > 0 ? .now() + self.timeout : .distantFuture)
                self.block = nil
                self.finishHandler?(!self._cancelled, self)
                self.finishHandler = nil
                
                if backgroundIdentifier != .invalid {
                    UIApplication.shared.endBackgroundTask(backgroundIdentifier)
                }
            }
        }
    

    因为这个 信号wait ,所以我们需要之前的 undeylingQueue 需要利用.global()

    协议设计完成,可以看看实现
    从现在开始,我们就可以摆脱动画的嵌套地狱了.只要在你想要实现的对象上实现TaskProtocol,即可使用

    
    class DiscoverHeaderCell: CollectionViewCell, TaskProtocol {
        
        
        let box = UIView(.red)
        override func commonInit() {
            super.commonInit()
            
            flexRootContainer.backgroundColor = .gray
            
            box.add(to: contentView)
        }
        
        
        override func layoutSubviews() {
            superview?.layoutSubviews()
            
            box.frame = MakeRect(10, 10, 100, 100)
            // 变蓝, 变圆
            addTask {[weak self] sem, op in
                guard let self = self else { return }
                UIView.animate(withDuration: 2) {
                    self.box.backgroundColor = .blue
                    self.box.cornerRadius = self.box.height * 0.5
                } completion: { _ in
                    sem?.signal()
                }
            }
            // 变宽
            addTask {[weak self] sem, op in
                guard let self = self else { return }
                UIView.animate(withDuration: 1.5) {
                    self.box.width = 200
                } completion: { _ in
                    sem?.signal()
                }
            }
            // 复原
            addTask {[weak self] sem, op in
                guard let self = self else { return }
                let right = self.box.frame.maxX
                UIView.animate(withDuration: 1) {
                    self.box.width = 100
                    self.box.right = right
                } completion: { _ in
                    sem?.signal()
                }
            }
            // 变色,变方
            addTask {[weak self] sem, op in
                guard let self = self else { return }
                UIView.animate(withDuration: 1) {
                    self.box.backgroundColor = .gray
                    self.box.cornerRadius = 0
                } completion: { _ in
                    sem?.signal()
                }
            }
            
        }
    }
    
    

    我设定4个动画逻辑,展示



    如果用UIView 实现,逻辑嵌套会十分的麻烦,而且无法取消,所以,搞起来吧,兄dei~
    异步任务基本也是每个 APP 必备的,以前都是手写 queue,昨天思考了下用协议实现,省去中间商赚差价.
    不过,如非必要,还是不要大规模实现,平时思考下,真的需要异步时再考虑,CAAnimation, GroupAnimation 优先级应该都是要比这个任务队列要高,省性能一定也要考虑

    相关文章

      网友评论

          本文标题:iOS(Swift) TaskProtocol异步任务队列

          本文链接:https://www.haomeiwen.com/subject/adyoultx.html