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

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

Kingfisher source Reading Notes (1) continuation.

Multithreading processes cached data

Kingfisher uses maybeCached to save the cached file path for the image when determining whether it already exists locally. The special maybeCachedCheckingQueue is used when modifying maybeCached.

var maybeCached : Set<String>?
let maybeCachedCheckingQueue = DispatchQueue(label: "com.onevcat.Kingfisher.maybeCachedCheckingQueue")

// Use asynchronous operations to add cached content
maybeCachedCheckingQueue.async {
    self.maybeCached?.insert(fileURL.lastPathComponent)
}

// Use synchronization when retrieving cached content
let fileMaybeCached = maybeCachedCheckingQueue.sync {
    return maybeCached?.contains(fileURL.lastPathComponent) ?? true
}
Copy the code

When working with cached data that does not involve UI changes, consider using a dedicated custom serial queue to retrieve and modify cached data to provide performance.

In addition, the last access time of the file is updated each time the cache file information is read, and this operation is also placed in a separate queue.

let metaChangingQueue: DispatchQueue
metaChangingQueue = DispatchQueue(label: creation.cacheName)
metaChangingQueue.async {
    meta.extendExpiration(with: fileManager, extendingExpiration: extendingExpiration)
}
Copy the code

Exception handling

Kingfisher defines all possible exceptions thrown in KingfisherError. Each member (case) represents a class of errors, and the specific error information is placed in reason of case association.

public enum KingfisherError: Error {

    public enum RequestErrorReason {
        // Specific error
    }
    
    public enum ResponseErrorReason {
        // Specific error
    }
    // Other specific errors
    
    // MARK: Member Cases
    
    case requestError(reason: RequestErrorReason)
    case responseError(reason: ResponseErrorReason)
    // Other error types...
    
    // Other methods
}
Copy the code

When an exception occurs, it is converted to an exception corresponding to a KingfisherError, and the specific underlying exception is also carried into the error message. For example, when an exception occurs while storing image data to a file, an exception of type cacheError is issued, detailed as cannotCreateCacheFile, and lower-level errors are placed in the associated error.

do {
    try data.write(to: fileURL)
} catch {
    throw KingfisherError.cacheError(
        reason: .cannotCreateCacheFile(fileURL: fileURL, key: key, data: data, error: error)
    )
}
Copy the code

When creating the framework, consider this approach:

  1. All possible exceptions are classified and concentrated in one place, so that users can see the exceptions that may be thrown at a glance.
  2. Encapsulate the underlying exceptions into more abstract ones, while preserving the underlying exceptions, rather than just throwing them up.

In addition, Kingfisher considers NSError in objectiv-c. The CustomNSError protocol is required to fully convert an Error in Swift into an NSError in objectiv-c. For more information, see Swift 3 must see: Error vs. NSError.

Break all external circular references that can be created

When you need to use an external passed in closure, do everything you can to eliminate circular references. For example: Backend uses a strong reference to config, which must be cleared immediately after the cachePathBlock closure of Config is used.

 public class Backend<T: DataTransformable> {

    public var config: Config

    init(noThrowConfig config: Config.creatingDirectory: Bool) {
        var config = config

        // the 'cachePathBlock' closure of 'config' generates a directoryURL
        self.directoryURL = creation.cachePathBlock(""."")

        // Break any possible retain cycle set by outside.
        config.cachePathBlock = nil
        self.config = config
    }
}
Copy the code

Config’s cachePathBlock property is specifically designed to implicitly resolve optional types:

public struct Config {
    var cachePathBlock: ((_ directory: URL._ cacheName: String) - >URL)! = {
            (directory, cacheName) in
        return directory.appendingPathComponent(cacheName, isDirectory: true)}}Copy the code