序言--感谢好心大神分享

Kingfisher 是由 @onevcat 编写的用于下载和缓存网络图片的轻量级Swift工具库,其中涉及到了包括GCD、Swift高级语法、缓存、硬盘读写、网络编程、图像编码、图形绘制、Gif数据生成和处理、MD5、Associated Objects的使用等大量iOS开发知识。

本文将详尽的对所涉及到的知识点进行讲解,但由于笔者水平有限,失误和遗漏之处在所难免,恳请前辈们批评指正。

一、Kingfisher的架构

Kingfisher.png

Kingfisher 源码中所包含的12个文件及其关系如上图所示,从左至右,由深及浅。
UIImage+Extension 文件内部对 UIImage 以及 NSData 进行了拓展, 包含判定图片类型、图片解码以及Gif数据处理等操作。
String+MD5 负责图片缓存时对文件名进行MD5加密操作。
ImageCache 主要负责将加载过的图片缓存至本地。
ImageDownloader 负责下载网络图片。
KingfisherOptions 内含配置 Kingfisher 行为的部分参数,包括是否设置下载低优先级、是否强制刷新、是否仅缓存至内存、是否允许图像后台解码等设置。
Resource 中的 Resource 结构体记录了图片的下载地址和缓存Key。
ImageTransition 文件中的动画效果将在使用 UIImageView 的拓展 API 时被采用,其底层为UIViewAnimationOptions,此外你也可以自己传入相应地动画操作、完成闭包来配置自己的动画效果。
ThreadHelper 中的 dispatch_async_safely_main_queue 函数接受一个闭包,利用 NSThread.isMainThread 判定并将其放置在主线程中执行。
KingfisherManager 是 Kingfisher 的主控制类,整合了图片下载及缓存操作。
KingfisherOptionsInfoItem 被提供给开发者对 Kingfisher 的各种行为进行控制,包含下载设置、缓存设置、动画设置以及 KingfisherOptions 中的全部配置参数。
UIImage+Kingfisher 以及 UIButton+Kingfisher 对 UIImageView 和 UIButton 进行了拓展,即主要用于提供 Kingfisher 的外部接口。

二、UIImage+Extension

图片格式识别

Magic Number 是用于区分不同文件格式,被放置于文件首的标记数据。Kingfisher 中用它来区分不同的图片格式,如PNG、JPG、GIF。代码如下:

private let pngHeader: [UInt8] = [0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A] private let jpgHeaderSOI: [UInt8] = [0xFF, 0xD8] private let jpgHeaderIF: [UInt8] = [0xFF] private let gifHeader: [UInt8] = [0x47, 0x49, 0x46] // MARK: - Image format enum ImageFormat { case Unknown, PNG, JPEG, GIF } extension NSData { var kf_imageFormat: ImageFormat { var buffer = [UInt8](count: 8, repeatedValue: 0) self.getBytes(&buffer, length: 8) if buffer == pngHeader { return .PNG } else if buffer[0] == jpgHeaderSOI[0] && buffer[1] == jpgHeaderSOI[1] && buffer[2] == jpgHeaderIF[0] { return .JPEG }else if buffer[0] == gifHeader[0] && buffer[1] == gifHeader[1] && buffer[2] == gifHeader[2] { return .GIF } return .Unknown } }

代码上部定义的 imageHeader,就是不同格式图片放置在文件首的对应 Magic Number 数据,我们通过 NSData 的 getBytes: 方法得到图片数据的 Magic Number,通过比对确定图片格式。

图片解码

我们知道 PNG 以及 JPEG 等格式的图片对原图进行了压缩,必须要将其图片数据解码成位图之后才能使用,这是原因,Kingfisher 里提供了用于解码的函数,代码如下:

// MARK: - Decode
extension UIImage {func kf_decodedImage() -> UIImage? {return self.kf_decodedImage(scale: self.scale) } func kf_decodedImage(scale scale: CGFloat) -> UIImage? { let imageRef = self.CGImage let colorSpace = CGColorSpaceCreateDeviceRGB() let bitmapInfo = CGBitmapInfo(rawValue: CGImageAlphaInfo.PremultipliedLast.rawValue).rawValue let contextHolder = UnsafeMutablePointer<Void>() let context = CGBitmapContextCreate(contextHolder, CGImageGetWidth(imageRef), CGImageGetHeight(imageRef), 8, 0, colorSpace, bitmapInfo) if let context = context { let rect = CGRectMake(0, 0, CGFloat(CGImageGetWidth(imageRef)), CGFloat(CGImageGetHeight(imageRef))) CGContextDrawImage(context, rect, imageRef) let decompressedImageRef = CGBitmapContextCreateImage(context) return UIImage(CGImage: decompressedImageRef!, scale: scale, orientation: self.imageOrientation) } else { return nil } } }

这段代码的主要含义是通过 CGBitmapContextCreate 以及 CGContextDrawImage 函数,将被压缩的图片画在 context 上,再通过调用 CGBitmapContextCreateImage 函数,即可完成对被压缩图片的解码。但通过测试后续代码发现,包含 decode 函数的分支从来没被调用过,据本人推测,UIImage 在接收 NSData 数据进行初始化的时候,其本身很可能包含有通过 Magic Number 获知图片格式后,解码并展示的功能,并不需要外部解码。

图片正立

使用 Core Graphics 绘制图片时,图片会倒立显示,Kingfisher 中使用了这个特性创建了一个工具函数来确保图片的正立,虽然该函数并未在后续的文件中使用到,代码如下:

// MARK: - Normalization
extension UIImage {public func kf_normalizedImage() -> UIImage {if imageOrientation == .Up { return self } UIGraphicsBeginImageContextWithOptions(size, false, scale) drawInRect(CGRect(origin: CGPointZero, size: size)) let normalizedImage = UIGraphicsGetImageFromCurrentImageContext() UIGraphicsEndImageContext() return normalizedImage; } }

如果该图片方向为正立,返回自身;否则将其用 Core Graphics 绘制,返回正立后的图片。

GIF数据的保存

我们知道,UIImage 并不能直接保存,需要先将其转化为 NSData 才能写入硬盘以及内存中缓存起来,UIKit 提供了两个 C 语言函数:UIImageJPEGRepresentation 和 UIImagePNGRepresentation,以便于将 JPG 及 PNG 格式的图片转化为 NSData 数据,但却并没有提供相应的 UIImageGIFRepresentation,所以我们需要自己编写这个函数以完成对Gif数据的保存,代码如下:

import ImageIO
import MobileCoreServices// MARK: - GIF func UIImageGIFRepresentation(image: UIImage) -> NSData? { return UIImageGIFRepresentation(image, duration: 0.0, repeatCount: 0) } func UIImageGIFRepresentation(image: UIImage, duration: NSTimeInterval, repeatCount: Int) -> NSData? { guard let images = image.images else { return nil } let frameCount = images.count let gifDuration = duration <= 0.0 ? image.duration / Double(frameCount) : duration / Double(frameCount) let frameProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFDelayTime as String: gifDuration]] let imageProperties = [kCGImagePropertyGIFDictionary as String: [kCGImagePropertyGIFLoopCount as String: repeatCount]] let data = NSMutableData() guard let destination = CGImageDestinationCreateWithData(data, kUTTypeGIF, frameCount, nil) else { return nil } CGImageDestinationSetProperties(destination, imageProperties) for image in images { CGImageDestinationAddImage(destination, image.CGImage!, frameProperties) } return CGImageDestinationFinalize(destination) ? NSData(data: data) : nil }

为实现这个功能,我们首先需要在文件头部添加 ImageIO 和 MobileCoreServices 这两个系统库。
第一部分 guard 语句用于确保GIF数据存在,images 即GIF动图中每一帧的静态图片,类型为 [UIImage]? ;第二部分取得图片总张数以及每一帧的持续时间。
CGImageDestination 对象是对数据写入操作的抽象,Kingfisher 用其实现对GIF数据的保存。
CGImageDestinationCreateWithData 指定了图片数据的保存位置、数据类型以及图片的总张数,最后一个参数现需传入 nil。
CGImageDestinationSetProperties 用于传入包含静态图的通用配置参数,此处传入了Gif动图的重复播放次数。
CGImageDestinationAddImage 用于添加每一张静态图片的数据以及对应的属性(可选),此处添加了每张图片的持续时间。
CGImageDestinationFinalize 需要在所有数据被写入后调用,成功返回 true,失败则返回 false。

GIF数据的展示

我们并不能像其他格式的图片一样直接传入 NSData 给 UIImage 来创建一个GIF动图,而是需要使用 UIImage 的 animatedImageWithImages 方法,但此函数所需的参数是 [UIImage],所以我们需要首先将 NSData 格式的图片数据拆分为每一帧的静态图片,再将其传入上述函数之中,代码如下:

extension UIImage {static func kf_animatedImageWithGIFData(gifData data: NSData) -> UIImage? {return kf_animatedImageWithGIFData(gifData: data, scale: UIScreen.mainScreen().scale, duration: 0.0) } static func kf_animatedImageWithGIFData(gifData data: NSData, scale: CGFloat, duration: NSTimeInterval) -> UIImage? { let options: NSDictionary = [kCGImageSourceShouldCache as String: NSNumber(bool: true), kCGImageSourceTypeIdentifierHint as String: kUTTypeGIF] guard let imageSource = CGImageSourceCreateWithData(data, options) else { return nil } let frameCount = CGImageSourceGetCount(imageSource) var images = [UIImage]() var gifDuration = 0.0 for i in 0 ..< frameCount { guard let imageRef = CGImageSourceCreateImageAtIndex(imageSource, i, options) else { return nil } guard let properties = CGImageSourceCopyPropertiesAtIndex(imageSource, i, nil), gifInfo = (properties as NSDictionary)[kCGImagePropertyGIFDictionary as String] as? NSDictionary, frameDuration = (gifInfo[kCGImagePropertyGIFDelayTime as String] as? NSNumber) else { return nil } gifDuration += frameDuration.doubleValue images.append(UIImage(CGImage: imageRef, scale: scale, orientation: .Up)) } if (frameCount == 1) { return images.first } else { return UIImage.animatedImageWithImages(images, duration: duration <= 0.0 ? gifDuration : duration) } } }

与 CGImageDestination 相对应的,CGImageSource 对象是对数据读出操作的抽象,Kingfisher 用其实现GIF数据的读出。
与 CGImageDestination 的写入操作相类似,这里我们通过 CGImageSourceCreateWithData、CGImageSourceGetCount 以及循环执行相应次数的 CGImageSourceCreateImageAtIndex 来得到 [UIImage],并通过 CGImageSourceCopyPropertiesAtIndex 等相关操作取得每张图片的原持续时间,将其求和,最后将两个对应参数传入 UIImage.animatedImageWithImages 中,即可得到所需的GIF动图。

三、String+MD5

MD5加密

MD5加密在 Kingfisher 中被用于缓存时对文件名的加密,由于其内部实现较为复杂,此处仅提供成品代码以备不时之需,代码如下:

import Foundationextension String {func kf_MD5() -> String {if let data = dataUsingEncoding(NSUTF8StringEncoding) { let MD5Calculator = MD5(data) let MD5Data = MD5Calculator.calculate() let resultBytes = UnsafeMutablePointer<CUnsignedChar>(MD5Data.bytes) let resultEnumerator = UnsafeBufferPointer<CUnsignedChar>(start: resultBytes, count: MD5Data.length) var MD5String = "" for c in resultEnumerator { MD5String += String(format: "%02x", c) } return MD5String } else { return self } } } /** array of bytes, little-endian representation */ func arrayOfBytes<T>(value:T, length:Int? = nil) -> [UInt8] { let totalBytes = length ?? (sizeofValue(value) * 8) let valuePointer = UnsafeMutablePointer<T>.alloc(1) valuePointer.memory = value let bytesPointer = UnsafeMutablePointer<UInt8>(valuePointer) var bytes = [UInt8](count: totalBytes, repeatedValue: 0) for j in 0..<min(sizeof(T),totalBytes) { bytes[totalBytes - 1 - j] = (bytesPointer + j).memory } valuePointer.destroy() valuePointer.dealloc(1) return bytes } extension Int { /** Array of bytes with optional padding (little-endian) */ func bytes(totalBytes: Int = sizeof(Int)) -> [UInt8] { return arrayOfBytes(self, length: totalBytes) } } extension NSMutableData { /** Convenient way to append bytes */ func appendBytes(arrayOfBytes: [UInt8]) { appendBytes(arrayOfBytes, length: arrayOfBytes.count) } } class HashBase { var message: NSData init(_ message: NSData) { self.message = message } /** Common part for hash calculation. Prepare header data. */ func prepare(len:Int = 64) -> NSMutableData { let tmpMessage: NSMutableData = NSMutableData(data: self.message) // Step 1. Append Padding Bits tmpMessage.appendBytes([0x80]) // append one bit (UInt8 with one bit) to message // append "0" bit until message length in bits ≡ 448 (mod 512) var msgLength = tmpMessage.length; var counter = 0; while msgLength % len != (len - 8) { counter++ msgLength++ } let bufZeros = UnsafeMutablePointer<UInt8>(calloc(counter, sizeof(UInt8))) tmpMessage.appendBytes(bufZeros, length: counter) bufZeros.destroy() bufZeros.dealloc(1) return tmpMessage } } func rotateLeft(v:UInt32, n:UInt32) -> UInt32 { return ((v << n) & 0xFFFFFFFF) | (v >> (32 - n)) } class MD5 : HashBase { /** specifies the per-round shift amounts */ private let s: [UInt32] = [7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21] /** binary integer part of the sines of integers (Radians) */ private let k: [UInt32] = [0xd76aa478,0xe8c7b756,0x242070db,0xc1bdceee, 0xf57c0faf,0x4787c62a,0xa8304613,0xfd469501, 0x698098d8,0x8b44f7af,0xffff5bb1,0x895cd7be, 0x6b901122,0xfd987193,0xa679438e,0x49b40821, 0xf61e2562,0xc040b340,0x265e5a51,0xe9b6c7aa, 0xd62f105d,0x2441453,0xd8a1e681,0xe7d3fbc8, 0x21e1cde6,0xc33707d6,0xf4d50d87,0x455a14ed, 0xa9e3e905,0xfcefa3f8,0x676f02d9,0x8d2a4c8a, 0xfffa3942,0x8771f681,0x6d9d6122,0xfde5380c, 0xa4beea44,0x4bdecfa9,0xf6bb4b60,0xbebfbc70, 0x289b7ec6,0xeaa127fa,0xd4ef3085,0x4881d05, 0xd9d4d039,0xe6db99e5,0x1fa27cf8,0xc4ac5665, 0xf4292244,0x432aff97,0xab9423a7,0xfc93a039, 0x655b59c3,0x8f0ccc92,0xffeff47d,0x85845dd1, 0x6fa87e4f,0xfe2ce6e0,0xa3014314,0x4e0811a1, 0xf7537e82,0xbd3af235,0x2ad7d2bb,0xeb86d391] private let h:[UInt32] = [0x67452301, 0xefcdab89, 0x98badcfe, 0x10325476] func calculate() -> NSData { let tmpMessage = prepare() // hash values var hh = h // Step 2. Append Length a 64-bit representation of lengthInBits let lengthInBits = (message.length * 8) let lengthBytes = lengthInBits.bytes(64 / 8) tmpMessage.appendBytes(Array(lengthBytes.reverse())); // Process the message in successive 512-bit chunks: let chunkSizeBytes = 512 / 8 // 64 var leftMessageBytes = tmpMessage.length for (var i = 0; i < tmpMessage.length; i = i + chunkSizeBytes, leftMessageBytes -= chunkSizeBytes) { let chunk = tmpMessage.subdataWithRange(NSRange(location: i, length: min(chunkSizeBytes,leftMessageBytes))) // break chunk into sixteen 32-bit words M[j], 0 ≤ j ≤ 15 var M:[UInt32] = [UInt32](count: 16, repeatedValue: 0) let range = NSRange(location:0, length: M.count * sizeof(UInt32)) chunk.getBytes(UnsafeMutablePointer<Void>(M), range: range) // Initialize hash value for this chunk: var A:UInt32 = hh[0] var B:UInt32 = hh[1] var C:UInt32 = hh[2] var D:UInt32 = hh[3] var dTemp:UInt32 = 0 // Main loop for j in 0..<k.count { var g = 0 var F:UInt32 = 0 switch (j) { case 0...15: F = (B & C) | ((~B) & D) g = j break case 16...31: F = (D & B) | (~D & C) g = (5 * j + 1) % 16 break case 32...47: F = B ^ C ^ D g = (

关于Kingfisher--备用相关推荐

  1. 套用match_再也不用伤脑筋了,分享九个可以直接套用的Excel常用公式,收藏备用...

    编辑|菲菲  来源|烂叶子Office(ID:cnlanyezi) 毫无争议,Excel已经成为日常工作中必不可少的办公软件.无论您身处哪个行业.哪个单位,几乎每天都要跟excel打交道.但是很多人对 ...

  2. 分享一些 WINDOWS 资源站点(备用)

    分享一些 WINDOWS 资源站点(备用) Windows Embedded http://www.10down.net/windows-embedded.php Windows 7 SP1 Upda ...

  3. 问:PC机上的两个dns,是首用dns的查不到再查 备用的,是么?

    问:PC机上的两个dns,是首用dns的查不到再查 备用的,是么? 如果有两个DNS服务器,那么客户机会优先向主DNS服务器发起查询,如果主DNS在规定时间内应答,那么以主DNS的应答为准.如果主DN ...

  4. 华为静态、默认、备用路由配置

    此为华为静态路由及默认路由.备用路由的配置. 了解静态路由.默认路由相对于动态路由的优点 丽姐路由的作用及路由操作的过程 掌握配置静态路由(下一跳为接口)的方法 掌握配置静态路由(下一跳为IP地址)的 ...

  5. 2012r2备域控服务器搭建,Windows2012R2备用域控搭建

    前置操作 域控 主域控的主dns:自己的ip 备域控的主dns:自己的ip,备dns:主域控的ip 客户端 主dns:主域控的ip,备dns:备域控的ip 一般说主和备,主要是指担任PDC放置的角色的 ...

  6. 多DHCP服务器的作用域 及备用DHCP服务器(一)

    http://dev.firnow.com/course/6_system/linux/linuxjq/20100318/200250.html 创建.管理多个DHCP服务器的作用域 及备用DHCP服 ...

  7. swift_035(Swift之第三方库Kingfisher篇)

    [快速学会Swift第三方库] Kingfisher篇 Kingfisher是一个轻量的下载和缓存网络图片库.下载和缓存是异步进行操作,已经下载好的图片会缓存在内存和本地,极大得提高app的体验. ( ...

  8. Eclipse中导入第三方源码的问题和备用解决方案

    Eclipse中导入第三方源码的问题和备用解决方案 参考文章: (1)Eclipse中导入第三方源码的问题和备用解决方案 (2)https://www.cnblogs.com/fjdingsd/p/4 ...

  9. js源生惯性滚动与回弹(备用)

    js源生惯性滚动与回弹(备用) js源生惯性滚动与回弹(备用) <!DOCTYPE html> <html lang="zh-CN"><head> ...

  10. Linux 文件系统错误的修复方法 ddrescue替代dd的恢复软件 备用超级块

    Linux 文件系统错误的修复方法  ddrescue替代dd的恢复软件  备用超级块 http://www.mamicode.com/info-detail-1372846.html 最近处理的一件 ...

最新文章

  1. VIJOS国庆节模拟赛之繁星春水
  2. Java用户修改密码
  3. linux之history使用技巧
  4. java jquery怎么取值_jquery 取值
  5. 语音 识别_语音识别_qq语音识别 - 云+社区 - 腾讯云
  6. Angular.js-开发前笔记
  7. AndroidStudio配置gradle,让App自动签名
  8. 卓有成效的管理者的五个习惯
  9. 高等数学学习笔记——第十三讲——变号级数收敛性判别方法
  10. Maya 2018 for Mac中文破解版永久激活方法含注册机
  11. matlab中princ,基于MATLAB_SIMLINK的Turbo交织器的仿真实现
  12. Mon Dec 31 00:00:00 CST 2012格式的字符串转时间格式
  13. 免费开发工具与IAR工具的比较
  14. 自己写的代码太low?想提升代码质量但是不知道怎么做?IDEA安这个插件~
  15. 谷歌地图谷歌地图_为您的Google地图增添真实感
  16. C语言实现JSON字符串解码与编码(三)源代码
  17. turtle科赫雪花的源码分析
  18. 利用opencv获取网络摄像头数据并显示报错 select() timeout
  19. 【源码】二分法的matlab实现
  20. mysql 创建操作表_mysql-创建和操作表

热门文章

  1. ITK:创建一个点集
  2. VTK:可视化之CameraModel1
  3. VTK:简单操作之FloatingPointExceptions
  4. VTK:图表之TreeBFSIterator
  5. VTK:图表之BreadthFirstDistance
  6. OpenCV使用FacemarkAAM
  7. OpenGL Drawing Commands绘图命令的实例
  8. C语言实现简单而通用的字典算法(附完整源码)
  9. C++智能指针简单介绍
  10. 04_05_06:设置线型风格(设置线型风格)、区域着色 (Shading Regions)、设置Spines