This is the 14th day of my participation in Gwen Challenge

This article is received at the time of reading the Kingfisher source.

Other series

Kingfisher (1)

Kingfisher source code reading Notes (2)

Kingfisher source code reading Notes (3)

digression

I made a pull request to Kingfisher a few days ago, and I was named by the cat god. I feel happy for a whole year.

Excellent open source framework, after many people repeatedly verify, view, constantly improve. As a newbie, it’s hard to make an outstanding contribution. However, we can start small and gradually participate in the maintenance of open source frameworks. For example, if we start with code comments and see the wrong or missing comments, submit a pull Request. Don’t think these things are easy and unimportant. These are the small things that we can do to increase our influence over time and further into the maintenance of open source frameworks.

The callback queue

When creating a utility class or framework, you sometimes need to let the user set which queue (thread) to call the completion closure or proxy method. Previously I always passed the specified queue (thread) in each method.

That is, until I saw Kingfisher’s solution.

Kingfisher uses enum to represent all situations, which can be divided into four categories:

  1. Asynchronously add tasks to the main queue;
  2. If the current thread is not the main thread, the main thread asynchronously adds the task to the main queue, otherwise directly runs the task.
  3. Do not change the call queue;
  4. Runs the callback on the specified queue.
/// 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() }
        }
    }
}
Copy the code

With the CallbackQueue above, specifying work queues and callback threads is surprisingly simple:

func doSomething(callbackQueue: CallbackQueue = .untouch,
               completionHandler: @escaping (Result<Void.Error- > >)Void) {
    // The worker thread
    let loadingQueue: CallbackQueue = .mainAsync
    loadingQueue.execute {
        do {
            // Operation that may throw an exception
            
            // Callback thread
            callbackQueue.execute { completionHandler(.success(())) }
        } catch {
            callbackQueue.execute { completionHandler(.failure(error)) }
        }
    }
}
Copy the code

LoadingQueue is the worker thread and callbackQueue is the callback thread.

Main thread and main queue

CallbackQueue. MainCurrentOrAsync mainly not only determine whether the current Thread Thread. IsMainThread, and judge whether the main queue DispatchQueue. Main.

Queues are not threads. Queues are used to organize tasks. When we add tasks to queues, the system decides whether to create new threads to process tasks in queues based on resources.

In iOS, UI-related actions must be placed in the main thread and must be in the main queue. For more information, see: RxSwift determines if it is a “primary queue”.