我是这样学习拥有18k⭐️的Kingfisher优秀代码的--亲自实战优化项目代码和结构【物超所值】

6,137 阅读13分钟

前序

Kingfisher是由onevcat编写的用于下载和缓存网络图片的轻量级Swift工具库,目前在github收获的star已经达到了18k了。其内容包括了GCD、Swift的高级语法、缓存、硬盘读写、网络编程、图形绘制等大量iOS开发知识。

本篇博客不再简述Kingfisher源码的解析了,而是上了一个层次,当你看完或者了解过Kingfisher的时候,会发现别人写的代码真好,或者怎一个好字了得。本人也是看了Kingfisher的很多遍源码,每次看一遍都会得到不同的感受和体验。抽出这个时间,本人将Kingfisher一些好的代码思想和风格运用到自己目前的项目中或者新建的Demo中,供大家查看和思考。

踏踏实实提高技术才是硬道理【具备Craftsmasspirit工匠精神\color{#FF3030}{Craftsma's spirit---工匠精神}】。

目录

阅读Kingfisher源码的一些收获

下面我们一一来剖析Kingfisher以及本人是如何在项目中使用的。

kf前缀命名空间所得

当大家使用Kingfisher的时候,肯定会对一个现象感到好奇,为什么要用.kf.的方式,那么我们今天就来探究一下以及如果在自己项目中使用这种?

imageView.kf.setImage(with: url, placeholder: image)

对于这种带有前缀的写法:可以很好避免与系统方法冲突,也可以宣传属于自己的style,废话少说,今天都是干货,主要讲述代码思想和项目如何使用。

1.1 Kingfisher实现

打开Kingfisher源码的Kingfisher.swift文件

import UIKit
public typealias KFCrossPlatformImage = UIImage
public typealias KFCrossPlatformColor = UIColor
#if !os(watchOS)
public typealias KFCrossPlatformImageView = UIImageView
public typealias KFCrossPlatformView = UIView
public typealias KFCrossPlatformButton = UIButton
#endif

public struct KingfisherWrapper<Base> {
    public let base: Base
    public init(_ base: Base) {
        self.base = base
    }
}

public protocol KingfisherCompatible: AnyObject { }

public protocol KingfisherCompatibleValue {}

extension KingfisherCompatible {
    public var kf: KingfisherWrapper<Self> {
        get { return KingfisherWrapper(self) }
        set { }
    }
}

extension KingfisherCompatibleValue {
    public var kf: KingfisherWrapper<Self> {
        get { return KingfisherWrapper(self) }
        set { }
    }
}

extension KFCrossPlatformImage: KingfisherCompatible { }
#if !os(watchOS)
extension KFCrossPlatformImageView: KingfisherCompatible { }
extension KFCrossPlatformButton: KingfisherCompatible { }
extension NSTextAttachment: KingfisherCompatible { }
#endif

上面代码的逻辑:

  1. 首先定义了一个KingfisherWrapper\color{#A43030}{KingfisherWrapper}结构体,有一个泛型属性base;
  2. 定义一个KingfisherCompatible\color{#A43030}{KingfisherCompatible}协议;
  3. KingfisherCompatible\color{#A43030}{KingfisherCompatible}定义了一个只读的kf关联属性,指定关联类型为KingfisherWrapper<Self>\color{#A43030}{KingfisherWrapper<Self>},这里的Self理解为协议约束,需要遵守KingfisherCompatible\color{#A43030}{KingfisherCompatible}协议的类型;
  4. ImageView遵守KingfisherCompatible\color{#A43030}{KingfisherCompatible}协议,所以imageView可以用.kf.

1.2 项目中使用

1.2.1 常规做法

需求:利用协议实现前缀【统计字符串有几个数字出现,例如1234dafdaf1234,应该返回数字8?】

如果说仅仅是这个需求,用一个方法就可以实现的,如下【大多数人第一反应就是下面方面】

extension String {
    ///计算属性===方法,下面两种完全等价
    //方法
    func numberCount() -> Int {
        var count = 0
        for c in self where("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    
    //计算属性
    var numberCount1: Int {
        var count = 0
        for c in self where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
    
}
print("1234dafdaf1234".numberCount())

1.2.2 进阶版本1

但是如果想进一步凸显封装和代码的可读性,可以这样做:

struct ZXY {
    var string: String
    init(_ str: String) {
        self.string = str
    }
    var numberCount: Int {
        var count = 0
        for c in string where ("0"..."9").contains(c) {
            count += 1
        }
        return count
    }
}

extension String {
    var zxy: ZXY {return ZXY(self)}//传值self字符串
}

print("1234dafdaf1234".zxy.numberCount)

上面已经完成对字符串的拓展功能的系列,也已经很好的很优雅的解决了问题,但是如果相对字符串拓展一个功能的话,这就OK啦! 但是如果想对数组进行拓展一个类似的方法,还要在ZXY里面增加array属性和初始化以及拓展Array功能,就会发现冗余代码太多,且不够封装,不够通用。

1.2.3 进阶版本2

这时候泛型的作用就来啦,如下:

struct ZXY<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

extension String {
    var zxy: ZXY<String> {ZXY(self)}
}

class Person{}
extension Person {
    var zxy: ZXY<Person> {ZXY(self)}
}

extension ZXY where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where("0"..."9").contains(c){
            count += 1
        }
        return count
    }
}

extension ZXY where Base == Person {
    func run() {
        print("run")
    }
}

"1234dafdaf1234".zxy.numberCount
Person().zxy.run()

1.2.4 最终版本

上面实现了通过类的对象调用,可不可以实现通过类本身来调用呢,因为我在使用类.zxy的时候,并不想出现类对象的属性,只想出现类型本身的方法和属性,这就需要用到static\color{#FF3030}{static}来修饰。

这些代码,增加其他,会导致代码还是会有点冗余,这样就发现了POP的好处是面向协议编程\color{#FF3030}{POP的好处-是面向协议编程},将公共的地方抽出来(协议只能声明一些东西,想扩充一些东西,就是在extension加入)

///前缀类型
struct ZXY<Base> {
    var base: Base
    init(_ base: Base) {
        self.base = base
    }
}

///利用协议扩展前缀属性
protocol ZXYCompatible {}
extension ZXYCompatible {
    var zxy: ZXY<Self> {ZXY(self)}
    static var zxy: ZXY<Self>.Type {ZXY<Self>.self}
}

///给字符串扩展功能
//让String拥有前缀属性
extension String: ZXYCompatible {}
//给string.zxy以及String().zxy前缀扩展功能
extension ZXY where Base == String {
    var numberCount: Int {
        var count = 0
        for c in base where("0"..."9").contains(c){
            count += 1
        }
        return count
    }
    static func test() {
        print("test")
    }
}
 
class Person{}
extension Person: ZXYCompatible{}
class Dog{}
extension Dog: ZXYCompatible{}
extension ZXY where Base == Person {
    func run() {
        print("run")
    }
}

枚举的使用

Kingfisher使用了大量的枚举,以前认为枚举就是为了区分状态,以提高代码的可读性,现在的理解是枚举定义了含义相同,但行为策略可能不同的一组值。

2.1 Kingfisher使用案例

本模块讲述Kingfisher的KingfisherError\color{#FF3030}{KingfisherError}【定义错误枚举】和StorageExpiration\color{#FF3030}{StorageExpiration}【定义更新时间】。

2.1.1 KingfisherError案例

首先看下源码如何实现

public enum KingfisherError: Error {
    
    // MARK: Member Cases
    case requestError(reason: RequestErrorReason)

    case responseError(reason: ResponseErrorReason)
   
    // MARK: Helper Properties & Methods
    public var isTaskCancelled: Bool {
        if case .requestError(reason: .taskCancelled) = self {
            return true
        }
        return false
    }
    
    ///请求失败原因
    public enum RequestErrorReason {
        
        case emptyRequest

        case invalidURL(request: URLRequest)
        
        case taskCancelled(task: SessionDataTask, token: SessionDataTask.CancelToken)
    }
    
    ///响应失败原因
    public enum ResponseErrorReason {
        
        case invalidURLResponse(response: URLResponse)
        
        case invalidHTTPStatusCode(response: HTTPURLResponse)
        
        case URLSessionError(error: Error)
        
        case dataModifyingFailed(task: SessionDataTask)

        case noURLResponse(task: SessionDataTask)
    }
}

// MARK: - LocalizedError Conforming
extension KingfisherError: LocalizedError {
    
    /// A localized message describing what error occurred.
    public var errorDescription: String? {
        switch self {
        case .requestError(let reason):
            return reason.errorDescription
            
        case .responseError(let reason):
            return reason.errorDescription
        }
    }
}

extension KingfisherError.RequestErrorReason {
    var errorDescription: String? {
        switch self {
        case .emptyRequest:
            return "The request is empty or `nil`."
        case .invalidURL(let request):
            return "The request contains an invalid or empty URL. Request: \(request)."
        case .taskCancelled(let task, let token):
            return "The session task was cancelled. Task: \(task), cancel token: \(token)."
        }
    }
    
    var errorCode: Int {
        switch self {
        case .emptyRequest: return 1001
        case .invalidURL: return 1002
        case .taskCancelled: return 1003
        }
    }
}

extension KingfisherError.ResponseErrorReason {
    var errorDescription: String? {
        switch self {
        case .invalidURLResponse(let response):
            return "The URL response is invalid: \(response)"
        case .invalidHTTPStatusCode(let response):
            return "The HTTP status code in response is invalid. Code: \(response.statusCode), response: \(response)."
        case .URLSessionError(let error):
            return "A URL session error happened. The underlying error: \(error)"
        case .dataModifyingFailed(let task):
            return "The data modifying delegate returned `nil` for the downloaded data. Task: \(task)."
        case .noURLResponse(let task):
            return "No URL response received. Task: \(task),"
        }
    }
    
    var errorCode: Int {
        switch self {
        case .invalidURLResponse: return 2001
        case .invalidHTTPStatusCode: return 2002
        case .URLSessionError: return 2003
        case .dataModifyingFailed: return 2004
        case .noURLResponse: return 2005
        }
    }
}

通过上面的KingfisherError定义了Kingfisher的所有错误类型【上面仅仅是请求错误和响应错误】在代码中如何使用呢? KingfisherError调用枚举类型,然后将真正错误枚举类型传入其中\color{#FF3030}{KingfisherError调用枚举类型,然后将真正错误枚举类型传入其中}

guard let httpResponse = response as? HTTPURLResponse else {
            let error = KingfisherError.responseError(reason: .invalidURLResponse(response: response))
            onCompleted(task: dataTask, result: .failure(error))
            completionHandler(.cancel)
            return
}

2.1.2 StorageExpiration案例

Storage.swift定义了缓存日期的规范。通过TimeConstants定义常量,然后通过StorageExpiration定义缓存过期枚举。

/// Constants for some time intervals
struct TimeConstants {
    static let secondsInOneMinute = 60
    static let minutesInOneHour = 60
    static let hoursInOneDay = 24
    static let secondsInOneDay = 86_400
}

public enum StorageExpiration {
    /// The item never expires.
    case never
    /// The item expires after a time duration of given seconds from now.
    case seconds(TimeInterval)
    /// The item expires after a time duration of given days from now.
    case days(Int)
    /// The item expires after a given date.
    case date(Date)
    /// Indicates the item is already expired. Use this to skip cache.
    case expired

    func estimatedExpirationSince(_ date: Date) -> Date {
        switch self {
        case .never: return .distantFuture
        case .seconds(let seconds):
            return date.addingTimeInterval(seconds)
        case .days(let days):
            let duration: TimeInterval = TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days)
            return date.addingTimeInterval(duration)
        case .date(let ref):
            return ref
        case .expired:
            return .distantPast
        }
    }
    
    var estimatedExpirationSinceNow: Date {
        return estimatedExpirationSince(Date())
    }
    
    var isExpired: Bool {
        return timeInterval <= 0
    }

    var timeInterval: TimeInterval {
        switch self {
        case .never: return .infinity
        case .seconds(let seconds): return seconds
        case .days(let days): return TimeInterval(TimeConstants.secondsInOneDay) * TimeInterval(days)
        case .date(let ref): return ref.timeIntervalSinceNow
        case .expired: return -(.infinity)
        }
    }
}

2.2 项目使用案例

项目中使用到的HttpRequestError,定义了无网络、网络异常;网络超时;服务器异常;请求已取消等枚举,并定义属性错误提示文案。

public enum HttpRequestError {
    /// 无网络、网络异常
    case netless(String)
    /// 网络超时
    case timeout(String)
    /// 服务器异常
    case serviceException(String)
    /// 请求已取消
    case cancelled(String)
    
    /// 错误提示文案
    public var errorMessage: String {
        switch self {
        case .netless(let msg):
            return msg
        case .timeout(let msg):
            return msg
        case .serviceException(let msg):
            return msg
        case .cancelled(let msg):
            return msg
        }
    }
}

在项目中使用网络封装:

func put(request: ZXYRequestEntity, success: @escaping ((ZXYResponseEntity) -> Void), failure: @escaping ((HttpRequestError) -> Void)) -> ZXYRequestCancleable? {
        return self.request(method: .put, request: request, success: success, failure: failure)
    }

封装使用:

ZXYHttpManager.shared.put(request: request) { [weak self] (response) in
            if isAESApi {
                response.bodyMessage = response.bodyMessage?.decrypt()
            }
            self?.handleResponse(response, success: success, failure: failure)
        } failure: {(_ message) in
            failure(message.errorMessage)
        }

协议增加拓展性

协议是定义了某种能力,由协议遵循者去实现这些能力,但是由于Swift中协议扩展的存在,就可以让协议自己就提供某些能力,只要让协议遵循者去遵循协议,就能自动获取这些能力,减少了遵循协议的复杂性。并且协议仅仅定义了某种能力,不涉及具体类型,更方面的去扩展。

我们先从一个小的技能点来说:在Kingfisher中大量使用了协议的功能,

面试题:swift中将协议部分方法设为可选,该怎么实现?

import UIkit

protocol OptionalProtocol {
    func optionalMethod()
    func mustMethods()
    func anotherOptionalMethod()
}

extension OptionalProtocol {
    func optionalMethod() {
        print("一个可选方法")
    }
    
    func anotherOptionalMethod() {
        print("另一个可选方法")
    }
    
}

class MyClass: OptionalProtocol {
    func mustMethods() {
        print("必须要实现的方法")
    }
}

3.1 Resource案例

Resource.swift定义了协议Resource,里面有两个计算属性cacheKey和downloadURL,并且在Resource的拓展中定义方法【遵守协议的就不需要实现该方法】,然后ImageResource遵守协议,然后有自身有初始化方法,当然还有URL遵守协议

public protocol Resource {
    
    /// The key used in cache.
    var cacheKey: String { get }
    
    /// The target image URL.
    var downloadURL: URL { get }
    
}

extension Resource {

    public func convertToSource(overrideCacheKey: String? = nil) -> Source {
        return downloadURL.isFileURL ?
            .provider(LocalFileImageDataProvider(fileURL: downloadURL, cacheKey: overrideCacheKey ?? cacheKey)) :
            .network(ImageResource(downloadURL: downloadURL, cacheKey: overrideCacheKey ?? cacheKey))
    }
}

public struct ImageResource: Resource {

    public init(downloadURL: URL, cacheKey: String? = nil) {
        self.downloadURL = downloadURL
        self.cacheKey = cacheKey ?? downloadURL.absoluteString
    }

    // MARK: Protocol Conforming
    /// The key used in cache.
    public let cacheKey: String

    /// The target image URL.
    public let downloadURL: URL
}

extension URL: Resource {
    public var cacheKey: String { return absoluteString }
    public var downloadURL: URL { return self }
}

3.2 项目框架使用

项目中最原始使用CTMediator的target-action方式来实现,后期团队讨论如何改善CTMediator的硬编码,所以讨论使用协议来拓展,解决了硬编码问题

如下:

1.定义公共协议,为每个模块创建基本的协议

public protocol Routable {
    // 公共协议
}

2.创建模块协议遵守基本协议,定义模块方法

public protocol Broker_Routable: Routable {
    
    /// 详情页
    /// - Parameters:
    ///   - brokerId: id
    ///   - collectionHandle: 收藏操作(取消或者添加)
    func brokerDetailVC(brokerId: Int, collectionHandle: @escaping ((_ isCandel: Bool) -> Void)) -> UIViewController
    
}

3. 因为要Router.broker.方法,使用share为了遵守各个模块的协议的单例

public class Router {
    static let shared: Router = Router()
    private init() {}
    
    /// 交易商模块
    public static var broker: Broker_Routable { shared as! Broker_Routable }
    
}

4. 实现协议的方法

extension Router: Broker_Routable {
    public func brokerDetailVC(brokerId: Int, collectionHandle: @escaping ((Bool) -> Void)) -> UIViewController {
        let (vc, input) = BrokerDetailModuleBuilder.setupModule()
        input.configBrokerDetailScene(brokerId: brokerId)
        input.configCancelCollectionCallback(collectionHandle)
        return vc
    }
}

调用如下

let vc = Router.broker.brokerDetailVC(brokerId: id) { (_) in}
self.navigationController?.pushViewController(vc, animated: true)

上面仅仅是一个交易商模块,如果对于整个项目而言,broker通过Router.broker,Grade通过 Router.grade方式,complainCenter通过Router.complainCenter方式……使用share的单例遵守各个子模块的协议方法,就可以达到share为grade、complainCenter等

public class Router {
    static let shared: Router = Router()
    private init() {}
    

    public static var home: Home_Routable { shared as! Home_Routable}
    
    public static var broker: Broker_Routable { shared as! Broker_Routable }
    
    public static var grade: Grade_Routable { shared as! Grade_Routable }
    
    public static var complainCenter: ComplainCenter_Routable { shared as! ComplainCenter_Routable }
    
    public static var mine: Mine_Routable { shared as! Mine_Routable }
    
    public static var account: Account_Routable { shared as! Account_Routable }
    
}

使用协议解决了硬编码的CTMediator的方式,也很好的使用了swift的编程思想。

defer对代码简洁的好处

defer block 里的代码会在函数 return 之前执行,无论函数是从哪个分支 return 的,还是有 throw,还是自然而然走到最后一行。

4.1 SessionDelegate的defer

private func cancelTask(_ dataTask: URLSessionDataTask) {
        lock.lock()
        defer { lock.unlock() }
        dataTask.cancel()
    }

swift的defer简单的使用场景

跟swift文档举的例子类似,defer一个很适合的使用场景就是用来做清理工作。文件操作就是一个很好的例子: 关闭文件

func foo() {
  let fileDescriptor = open(url.path, O_EVTONLY)
  defer {
    close(fileDescriptor)
  }
  // use fileDescriptor...
}

这样就不怕哪个分支忘了写,或者中间 throw 个 error,导致 fileDescriptor 没法正常关闭。

项目中画圆圈【消息红点】

class MessageCenterCircleDot: UIView {
    
    fileprivate var fillColor: UIColor = UIColor.rgbColor(254, 98, 98)
    
    convenience init(frame: CGRect, fillColor: UIColor) {
        self.init()
        self.fillColor = fillColor
        self.backgroundColor = UIColor.clear
    }
    
    override func draw(_ rect: CGRect) {
        super.draw(rect)

        let size = rect.size.width
        let context = UIGraphicsGetCurrentContext()!
        context.setFillColor((self.backgroundColor?.cgColor ?? UIColor.white.cgColor)!)
        context.fill(rect)
        
        context.saveGState()
        defer { context.restoreGState() }
        let path = UIBezierPath.init(roundedRect: rect, cornerRadius: size * 0.5)

        context.addPath(path.cgPath)
        context.closePath()

        context.setFillColor(self.fillColor.cgColor)
        context.fillPath()
    }
}

关联Associate封装

想到要如何为所有的对象增加实例变量吗?使用Category可以很方便地为现有的类增加方法,但却无法直接增加实例变量。后来,系统提供了Associative References,这个问题就很容易解决了。这种方法也就是所谓的关联【association】,可以在runtime期间动态地添加任意多的属性,并且随时读取。

5.1 Kingfisher关于关联的封装

使用泛型代表为不同对象增加属性

func getAssociatedObject<T>(_ object: Any, _ key: UnsafeRawPointer) -> T? {
    return objc_getAssociatedObject(object, key) as? T
}

func setRetainedAssociatedObject<T>(_ object: Any, _ key: UnsafeRawPointer, _ value: T) {
    objc_setAssociatedObject(object, key, value, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}

Kingfisher使用封装的关联对KFCrossPlatformImage=UIImage的拓展动画数据

private var animatedImageDataKey: Void?

    private(set) var animatedImageData: Data? {
        get { return getAssociatedObject(base, &animatedImageDataKey) }
        set { setRetainedAssociatedObject(base, &animatedImageDataKey, newValue) }
    }

5.2 项目使用封装

private var recordedUserInfo: UserInfoEntity? {
        get { return getAssociatedObject(self, &recordedUserInfoKey) }
        set { setRetainedAssociatedObject(self, &recordedUserInfoKey, newValue) }
    }

通知的另类写法

通知中心【NSNotificationCenter】实际是在程序内部提供了一种广播机制。把接收到的消息,根据内部的消息转发表,将消息转发给需要的对象。这句话其实已经很明显的告诉我们要如何使用通知了。第一步:在需要的地方注册要观察的通知,第二步:在某地方发送通知。第三步:在合适时机移除通知

6.1 Kingfisher通知

let notifications: [(Notification.Name, Selector)]
        #if !os(macOS) && !os(watchOS)
        #if swift(>=4.2)
        notifications = [
            (UIApplication.didReceiveMemoryWarningNotification, #selector(clearMemoryCache)),
            (UIApplication.willTerminateNotification, #selector(cleanExpiredDiskCache)),
            (UIApplication.didEnterBackgroundNotification, #selector(backgroundCleanExpiredDiskCache))
        ]
        #else
        notifications = [
            (NSNotification.Name.UIApplicationDidReceiveMemoryWarning, #selector(clearMemoryCache)),
            (NSNotification.Name.UIApplicationWillTerminate, #selector(cleanExpiredDiskCache)),
            (NSNotification.Name.UIApplicationDidEnterBackground, #selector(backgroundCleanExpiredDiskCache))
        ]
        #endif
        #elseif os(macOS)
        notifications = [
            (NSApplication.willResignActiveNotification, #selector(cleanExpiredDiskCache)),
        ]
        #else
        notifications = []
        #endif
        notifications.forEach {
            NotificationCenter.default.addObserver(self, selector: $0.1, name: $0.0, object: nil)
        }

Kingfisher通过数组和元祖的配合,数组notifications包含元祖的集合,元祖中的元素是通知名称和方法【上面的太多是加入了平台的原因】 上面是针对多个通知的使用

6.2 本项目使用

func addObserverForNoti() {
        let notifications: [(Notification.Name, Selector)]
        
        notifications = [
            (NewsTableView.newsScrollTopNotification, #selector(onRecvNewsScrollTopNotification(_:))),
            (Notification.Name.init(Notification.nextUpdateAppNotification), #selector(onRecvClickNextUpdateNotification(_:))),
            (Notification.Name.init(Notification.userAgreenmentAppNotification), #selector(onRecvClickUserAgreenmentNotification(_:)))
        ]
        
        notifications.forEach {
            NotificationCenter.default.addObserver(self, selector: $0.1, name: $0.0, object: nil)
        }
                
    }

判断图片格式的原理

一般图片格式的都在data的前几个字节里,只要按对应的规则去取,然后进行判断就行了

Kingfisher判断图片格式

public enum ImageFormat {
    /// The format cannot be recognized or not supported yet.
    case unknown
    /// PNG image format.
    case PNG
    /// JPEG image format.
    case JPEG
    /// GIF image format.
    case GIF
    
    struct HeaderData {
        static var PNG: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A]
        static var JPEG_SOI: [UInt8] = [0xFF, 0xD8]
        static var JPEG_IF: [UInt8] = [0xFF]
        static var GIF: [UInt8] = [0x47, 0x49, 0x46]
    }
        
}

extension Data: KingfisherCompatibleValue {}

// MARK: - Misc Helpers
extension KingfisherWrapper where Base == Data {
    /// Gets the image format corresponding to the data.
    public var imageFormat: ImageFormat {
        guard base.count > 8 else { return .unknown }
        
        var buffer = [UInt8](repeating: 0, count: 8)
        base.copyBytes(to: &buffer, count: 8)
        
        if buffer == ImageFormat.HeaderData.PNG {
            
            return .PNG
            
        } else if buffer[0] == ImageFormat.HeaderData.JPEG_SOI[0],
            buffer[1] == ImageFormat.HeaderData.JPEG_SOI[1],
            buffer[2] == ImageFormat.HeaderData.JPEG_IF[0]{
            
            return .JPEG
            
        } else if buffer[0] == ImageFormat.HeaderData.GIF[0],
            buffer[1] == ImageFormat.HeaderData.GIF[1],
            buffer[2] == ImageFormat.HeaderData.GIF[2] {
            
            return .GIF
        }
        
        return .unknown
    }
    
}

总结

本篇文章主要讲述Kingfisher的部分优秀思想,以及在合适的地方,用之,并慢慢优化本人项目中的代码。在此,希望大家也能尝试用之,慢慢提高自己写代码的可移植性和可拓展封装等性。【毕竟工作了一些年份,已经过了业务书写的能力,而更具备更高层次的代码能力】

有一句心灵鸡汤送给此时想提高自己的你:“总有人要赢,那么反问一下自己,为什么不可能是我呢?”赢有点大了,换句话可能更好点:很多人都能进心仪的公司,那反问一下自己,为什么不可以是我呢?

踏踏实实提高技术才是硬道理【Follow\color{#FF3030}{Follow} your\color{#FF3030}{your} heart\color{#FF3030}{heart}】!!!

感谢大家❤️

  1. 如果你觉得这篇内容对你挺有有帮助的话: 点赞支持下吧,让更多的人也能看到这篇内容,本人会不断更新优质博客内容。
  2. 欢迎在留言区与我分享你的想法,也欢迎你在留言区记录你的思考过程。
  3. 觉得不错的话,也可以关注本人其他的有关iOS底层、Flutter及小程序方面的文章(感谢掘友的鼓励与支持🌹🌹🌹)

机会❤️❤️❤️🌹🌹🌹

如果想和我一起共建抖音,成为一名bytedancer,Come on。期待你的加入!!!

截屏2022-06-08 下午6.09.11.png