阅读 698

Kingfisher 源码阅读笔记(4)

这是我参与更文挑战的第14天,活动详情查看: 更文挑战

本文为在阅读 Kingfisher 源码时的收货。

其他系列文章

Kingfisher 源码阅读笔记(1)

Kingfisher 源码阅读笔记(2)

Kingfisher 源码阅读笔记(3)

题外话

前几天给 Kingfisher 提了一个 pull request,居然被喵神点名了,感觉可以开心一整年了。

image.png

优秀的开源框架,经过很多人的反复验证、查看,不断地趋于完善。作为一个新手,如果想要做出突出的贡献是很难的了。但是,我们可以从小处着手,逐渐的参与到开源框架的维护当中。比如,我们可以先从代码注释入手,看到错误的、缺少的注释,那就提交一个 pull request 吧。不要认为这些事情简单、不值得一提。我们完全可以从这些小事上逐渐提高自己的影响力,深入到开源框架的维护当中。

回调队列

在制作工具类、framework 时,有时候需要让使用者去设置在哪个队列(线程)调用完成闭包或者代理方法。之前我总是将指定的队列(线程)在各个方法中传递。

直到看到了 Kingfisher 的解决方案,让我眼前一亮。

Kingfisher 中使用 enum 来表示所有的情况,一共分成了四类:

  1. 在主队列中异步添加任务;
  2. 如果当前不是主线程,主线程,则向主队列中异步添加任务,否则直接运行;
  3. 不改变调用队列;
  4. 在指定的队列上运行回调。
/// Represents callback queue behaviors when an calling of closure be dispatched.
///
/// - asyncMain: Dispatch the calling to `DispatchQueue.main` with an `async` behavior.
/// - currentMainOrAsync: Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not
///                       `.main`. Otherwise, call the closure immediately in current main queue.
/// - untouch: Do not change the calling queue for closure.
/// - dispatch: Dispatches to a specified `DispatchQueue`.
public enum CallbackQueue {
    /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior.
    case mainAsync
    /// Dispatch the calling to `DispatchQueue.main` with an `async` behavior if current queue is not
    /// `.main`. Otherwise, call the closure immediately in current main queue.
    case mainCurrentOrAsync
    /// Do not change the calling queue for closure.
    case untouch
    /// Dispatches to a specified `DispatchQueue`.
    case dispatch(DispatchQueue)
    
    public func execute(_ block: @escaping () -> Void) {
        switch self {
        case .mainAsync:
            DispatchQueue.main.async { block() }
        case .mainCurrentOrAsync:
            DispatchQueue.main.safeAsync { block() }
        case .untouch:
            block()
        case .dispatch(let queue):
            queue.async { block() }
        }
    }

    var queue: DispatchQueue {
        switch self {
        case .mainAsync: return .main
        case .mainCurrentOrAsync: return .main
        case .untouch: return OperationQueue.current?.underlyingQueue ?? .main
        case .dispatch(let queue): return queue
        }
    }
}

extension DispatchQueue {
    // This method will dispatch the `block` to self.
    // If `self` is the main queue, and current thread is main thread, the block
    // will be invoked immediately instead of being dispatched.
    func safeAsync(_ block: @escaping () -> Void) {
        if self === DispatchQueue.main && Thread.isMainThread {
            block()
        } else {
            async { block() }
        }
    }
}
复制代码

有了上面的 CallbackQueue,在指定工作队列和回调线程就会异常的简单:

func doSomething(callbackQueue: CallbackQueue = .untouch,
               completionHandler: @escaping (Result<Void, Error>) -> Void) {
    // 工作线程
    let loadingQueue: CallbackQueue = .mainAsync
    loadingQueue.execute {
        do {
            // 可能会抛出异常的操作
            
            // 回调线程
            callbackQueue.execute { completionHandler(.success(())) }
        } catch {
            callbackQueue.execute { completionHandler(.failure(error)) }
        }
    }
}
复制代码

其中 loadingQueue 为工作线程,callbackQueue 为回调线程。

主线程和主队列

CallbackQueue.mainCurrentOrAsync 不仅判断了当前是否为主线程 Thread.isMainThread,而且判断了是否为主队列 DispatchQueue.main

队列不是线程,队列时用来组织任务的,我们将任务添加到队列中,系统会根据资源决定是否创建新的线程去处理队列中的任务。

iOS 中,UI 相关的操作必须放在主线程,而且必须是主队列中。更多信息可以查看: RxSwift 判断是否为“主队列”

文章分类
iOS