美文网首页iOS 专题
Kingfisher源码解析之使用

Kingfisher源码解析之使用

作者: lkkwxy | 来源:发表于2019-12-07 23:22 被阅读0次

    Kingfisher源码解析系列,由于水平有限,哪里有错,肯请不吝赐教

    1. 基本使用

    1.1 通过Resource设置图片

    Kingfisher中内置的ImageResource和URL实现了Resource协议,ImageResource和URL的区别是ImageResource可自定义cacheKey。

    • URL设置图片
    let url = URL(string: "https://test/image.jpg")!
    imageView.kf.setImage(with: url)
    
    • ImageResource
    let imageResource = ImageResource(downloadURL: url, cacheKey: "custom_cache_key")
    imageView.kf.setImage(with: imageResource)
    
    1.2 通过ImageDataProvider设置图片

    Kingfisher内置了LocalFileImageDataProvider,Base64ImageDataProvider,RawImageDataProvider三种ImageDataProvider。

    • LocalFileImageDataProvider
    let fileUrl = Bundle.main.url(forResource: "image", withExtension: "jpg")!
    let imageDataProvider =  LocalFileImageDataProvider(fileURL: fileUrl)
    imageView.kf.setImage(with: imageDataProvider)
    
    • Base64ImageDataProvider
    let base64String = "...."
    let base64ImageDataProvider = Base64ImageDataProvider(base64String: base64String, cacheKey: "base64_cache_key")
    imageView.kf.setImage(with: base64ImageDataProvider)
    
    • RawImageDataProvider
    let data = Data()
    let dataImageDataProvider = RawImageDataProvider(data: data, cacheKey: "data_cache_key")
    imageView.kf.setImage(with: dataImageDataProvider)
    
    • 自定义ImageDataProvider
    //定义
    public struct FileNameImageDataProvider : ImageDataProvider {
        public let cacheKey: String
        public let fileName: String
        public init(fileName: String, cacheKey: String? = nil) {
            self.fileName = fileName
            self.cacheKey = cacheKey ?? fileName
        }
        
        public func data(handler: @escaping (Result<Data, Error>) -> Void) {
            if let fileURL = Bundle.main.url(forResource: fileName, withExtension: "") {
                handler(Result(catching: { try Data(contentsOf: fileURL) }))
            }else {
                let error = NSError(domain: "文件不存在", code: -1, userInfo: ["fileName":fileName])
                handler(.failure(error))
            }
        }
    }
    //使用
    let fileNameImageDataProvider = FileNameImageDataProvider(fileName: "image.jpg")
    imageView.kf.setImage(with: fileNameImageDataProvider)
    
    1.3 展示placeholder
    • 使用UIImage设置placeholder
    let placeholderImage = UIImage(named: "placeholder.png")
    imageView.kf.setImage(with: url, placeholder: placeholderImage)
    
    • 通过自定义View设置placeholder
    // 定义
    // 需要使自定义View遵循Placeholder协议
    // 可以什么都不实现,是因为当Placeholder为UIview的时候有默认实现
    class PlaceholderView: UIView, Placeholder {
    }
    // 使用
    let placeholderView = PlaceholderView()
    imageView.kf.setImage(with: url, placeholder: placeholderView)
    
    1.4 加载GIF图
    • 通过UIImageView加载GIF图
    let url = URL(string: "https://test/image.gif")!
    imageView.kf.setImage(with: url)
    
    • 通过AnimatedImageView加载GIF图
    let url = URL(string: "https://test/image.gif")!
    animatedImageView.kf.setImage(with: url)
    

    上面二者的区别请参考Kingfisher源码解析之加载动图

    1.5 设置指示器
    • 不使用指示器
    imageView.kf.indicatorType = .none
    
    • 使用UIActivityIndicatorView作为指示器
    imageView.kf.indicatorType = .activity
    
    • 使用图片作为指示器
    let path = Bundle.main.path(forResource: "loader", ofType: "gif")!
    let data = try! Data(contentsOf: URL(fileURLWithPath: path))
    imageView.kf.indicatorType = .image(imageData: data)
    
    • 使用自定义View作为指示器
    // 定义
    struct CustomIndicator: Indicator {
        let view: UIView = UIView()
        func startAnimatingView() { view.isHidden = false }
        func stopAnimatingView() { view.isHidden = true }
        init() {
            view.backgroundColor = .red
        }
    }
    // 使用
    let indicator = CustomIndicator()
    imageView.kf.indicatorType = .custom(indicator: indicator)
    
    1.6 设置transition

    transition用于图片加载完成之后的展示动画,有以下类型

    public enum ImageTransition {
        // 无动画
        case none 
        // 相当于UIView.AnimationOptions.transitionCrossDissolve
        case fade(TimeInterval)
        // 相当于UIView.AnimationOptions.transitionFlipFromLeft
        case flipFromLeft(TimeInterval)
        // 相当于UIView.AnimationOptions.transitionFlipFromRight
        case flipFromRight(TimeInterval)
        // 相当于UIView.AnimationOptions.transitionFlipFromTop
        case flipFromTop(TimeInterval)
        // 相当于UIView.AnimationOptions.transitionFlipFromBottom
        case flipFromBottom(TimeInterval)
        // 自定义动画
        case custom(duration: TimeInterval,
                     options: UIView.AnimationOptions,
                  animations: ((UIImageView, UIImage) -> Void)?,
                  completion: ((Bool) -> Void)?)
    }
    

    使用方式

    imageView.kf.setImage(with: url, options: [.transition(.fade(0.2))])
    

    2. Processor

    2.1 DefaultImageProcessor

    将下载的数据转换为相应的UIImage。支持PNG,JPEG和GIF格式。

    2.2 BlendImageProcessor

    修改图片的混合模式(这里不知道这么描述对不对),核心实现如下

    1. 首先利用DefaultImageProcessor把Data转成image,然后去绘制
    2. 获取上下文
    3. 为上下文填充背景色
    4. 调用image.draw函数设置混合模式
    5. 从上下文中获取图片为processedImage
    6. 释放上下文,并返回processedImage
    let image = 处理之前的图片
    UIGraphicsBeginImageContextWithOptions(size, false, scale)
    let context = UIGraphicsGetCurrentContext()
    let rect = CGRect(origin: .zero, size: size)
    backgroundColor.setFill()
    UIRectFill(rect)
    image.draw(in: rect, blendMode: blendMode, alpha: alpha)
    let cgImage = context.makeImage()
    let processedImage = UIImage(cgImage: cgImage, scale: scale, orientation: image.orientation)
    UIGraphicsEndImageContext()
    return processedImage
    
    2.3 OverlayImageProcessor

    在image上添加一层覆盖,其本质也是混合模式,逻辑大致同上

    2.4 BlurImageProcessor

    给图片添加高斯模糊,用vimage实现

    2.5 RoundCornerImageProcessor

    给图片添加圆角,支持四个角进行相互组合,使用方式如下

    // 设置四个角的圆角
    imageView.kf.setImage(with: url, options: [.processor(RoundCornerImageProcessor(cornerRadius: 20))])
    // 给最上角和右下角设置圆角
    imageView.kf.setImage(with: url, options: [.processor(RoundCornerImageProcessor(cornerRadius: 20 
                                                ,roundingCorners: [.topLeft, .bottomRight]))])
    

    实现方式:利用贝塞尔曲线设置一下带圆角的圆角矩形,然后对图片进行裁剪

    let path = UIBezierPath(
                    roundedRect: rect,
                    byRoundingCorners: corners.uiRectCorner,//此参数表示是哪个圆角
                    cornerRadii: CGSize(width: radius, height: radius)
                )
    context.addPath(path.cgPath)
    context.clip()
    image.draw(in: rect)
    
    2.6 TintImageProcessor

    用颜色给图像主色,实现方式是利用CoreImage中的CIFilter,使用了这2个CIFilter(name: "CIConstantColorGenerator")CIFilter(name: "CISourceOverCompositing")

    2.7 ColorControlsProcessor

    修改图片的对比度,曝光度,亮度,饱和度,实现方式是利用CoreImage中的CIFilter,使用了这2个CIColorControlsCIExposureAdjust

    2.8 BlackWhiteProcessor

    使图像灰度化,是ColorControlsProcessor的特例

    2.9 CroppingImageProcessor

    对图片进行裁剪

    2.10 DownsamplingImageProcessor

    对图片下采样,一般在较小的imageView展示较大的高清图
    核心实现:

    public static func downsampledImage(data: Data, to pointSize: CGSize, scale: CGFloat) -> KFCrossPlatformImage? {
        let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
        guard let imageSource = CGImageSourceCreateWithData(data as CFData, imageSourceOptions) else {
            return nil
        }
        
        let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
        let downsampleOptions = [
            kCGImageSourceCreateThumbnailFromImageAlways: true,
            kCGImageSourceShouldCacheImmediately: true,
            kCGImageSourceCreateThumbnailWithTransform: true,
            kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
        guard let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions) else {
            return nil
        }
        return KingfisherWrapper.image(cgImage: downsampledImage, scale: scale, refImage: nil)
    }
    
    2.11 GeneralProcessor

    用于组合多个已有的Processor,使用方式如下,最终都会转成GeneralProcessor

    // 使用方式1
    let processor1 = BlurImageProcessor(blurRadius: 5)
    let processor2 = RoundCornerImageProcessor(cornerRadius: 20)
    let generalProcessor = GeneralProcessor(identifier: "123") { (item, options) -> KFCrossPlatformImage? in
       if let image = processor1.process(item: item, options: options) {
           return processor2.process(item: .image(image), options: options)
       }
       return nil
    }
    // 使用方式2,此方法是Processor的扩展
    let generalProcessor = BlurImageProcessor(blurRadius: 5).append(RoundCornerImageProcessor(cornerRadius: 20)_
    // 使用方式3,自定义的操作符,调用了append方法
    let generalProcessor = BlurImageProcessor(blurRadius: 5) |> RoundCornerImageProcessor(cornerRadius: 20)
    
    2.12 自定义Processor

    参考Kingfisher源码解析之Processor和CacheSerializer

    3 缓存

    3.1 使用自定义的cacheKey

    通常情况下,会直接通过URL去加载图片,这个时候cacheKey是URL.absoluteString,也可使用ImageResource自定义cacheKey

    3.2 通过cacheKey判断是否缓存,以及缓存的类型

    cacheType是一个枚举,有三个case:.none 未缓存,.memory 存在内存缓存,.disk存在磁盘缓存。
    需要说明的是cacheKey+processor.identifier才是缓存的唯一标识符,只是DefaultImageProcessor的identifier为空字符串,若是在加载的时候指定了非DefaultImageProcessor的Processor,则在查找的时候需要指定processorIdentifier

    let cache = ImageCache.default
    let isCached = cache.isCached(forKey: cacheKey)
    let cacheType = cache.imageCachedType(forKey: cacheKey)
    // 若是指定了Processor,可使用此方法查找缓存
    cache.isCached(forKey: cacheKey, processorIdentifier: processor.identifier)
    
    3.3 通过cacheKey,从缓存中获取图片
    cache.retrieveImage(forKey: "cacheKey") { result in
        switch result {
        case .success(let value):
            print(value.cacheType)
            print(value.image)
        case .failure(let error):
            print(error)
        }
    }
    
    3.4 设置缓存的配置
    3.4.1 设置内存缓存的容量限制(默认值设置物理内存的四分之一)
    cache.memoryStorage.config.totalCostLimit = 100 * 1024 * 1024
    
    3.4.2 设置内存缓存的个数限制
    cache.memoryStorage.config.countLimit = 150
    
    3.4.3 设置内存缓存的过期时间(默认值是300秒)
    cache.memoryStorage.config.expiration = .seconds(300)
    

    也可指定某一个图片的内存缓存

    imageView.kf.setImage(with: url, options:[.memoryCacheExpiration(.never)])
    
    3.4.4 设置内存缓存的过期时间更新策略

    更新策略是一个枚举,有三个case,.none 过期时间不更新,.cacheTime 在当前时间上加上过期时间,.expirationTime(_ expiration: StorageExpiration)过期时间更新为指定多久之后过期。默认值是.cacheTime,使用方式如下

    imageView.kf.setImage(with: url, options:[.memoryCacheAccessExtendingExpiration(.cacheTime)])
    
    3.4.5 设置内存缓存清除过期内存的时间间隔(此值是不可变的,只可在初始化时赋值)
    cache.memoryStorage.config.cleanInterval = 120
    
    3.4.6 设置磁盘缓存的容量
    cache.diskStorage.config.sizeLimit =  = 500 * 1024 * 1024
    
    3.4.7 设置磁盘缓存的过期时间和过期时间更新策略

    同内存缓存

    3.5手动的缓存图片
    // 普通缓存
    let image: UIImage = //...
    cache.store(image, forKey: cacheKey)
    
    // 缓存原始数据
    let data: Data = //...
    let image: UIImage = //...
    cache.store(image, original: data, forKey: cacheKey)
    
    3.6 清除缓存
    3.6.1 删除指定的缓存
        forKey: cacheKey,
        processorIdentifier: processor.identifier, 
        fromMemory: false,//是否才能够内存缓存中删除
        fromDisk: true //是否从磁盘缓存中删除){}
    
    3.6.2 清空内存缓存,清空过期的内存缓存
    // 清空内存缓存
    cache.clearMemoryCache()
    // 清空过期的内存缓存
    cache.cleanExpiredMemoryCache()
    
    3.6.3 清空磁盘缓存,清空过期的磁盘缓存和超过磁盘容量限制的缓存
    // 清空磁盘缓存
    cache.clearDiskCache()
    // 清空过期的磁盘缓存和超过磁盘容量限制的缓存
    cache.cleanExpiredDiskCache()
    
    3.7 获取磁盘缓存大小
    cache.calculateDiskStorageSize()
    

    5. 下载

    5.1 手动下载图片
    let downloader = ImageDownloader.default
    downloader.downloadImage(with: url) { result in
        switch result {
        case .success(let value):
            print(value.image)
        case .failure(let error):
            print(error)
        }
    }
    
    5.2 在发送请求之前,修改Request
    // 定义一个requestModifier
    let modifier = AnyModifier { request in
        var r = request
        r.setValue("abc", forHTTPHeaderField: "Access-Token")
        return r
    }
    // 在手动下载时设置
    downloader.downloadImage(with: url, options: [.requestModifier(modifier)]) { 
    }
    // 在imageView的setImage的options里设置
    imageView.kf.setImage(with: url, options: [.requestModifier(modifier)])
    
    5.3 设置超时时间
    downloader.downloadTimeout = 60
    
    5.4 处理重定向
    // 定义一个重定向的处理逻辑
    let anyRedirectHandler = AnyRedirectHandler { (task, resp, req, completionHandler) in
            completionHandler(req)
    }
    // 在手动下载时设置
    downloader.downloadImage(with: url, options: [.redirectHandler(anyRedirectHandler)])
    // 在imageView的setImage的options里设置
    imageView.kf.setImage(with: url,  options: [.redirectHandler(anyRedirectHandler)])
    
    5.5 取消下载
    // 取消手动下载
    let task = downloader.downloadImage(with: url) { result in
    }
    task?.cancel()
    
    // 取消imageView的下载
    let task = imageView.kf.set(with: url)
    task?.cancel()
    

    6. 预加载

    使用方式如下,具体可参考Kingfisher源码解析之ImagePrefetcher

    let urls = ["https://example.com/image1.jpg", "https://example.com/image2.jpg"]
               .map { URL(string: $0)! }
    let prefetcher = ImagePrefetcher(urls: urls)
    prefetcher.start()
    

    7. 一些有用的options

    • loadDiskFileSynchronously 从磁盘中加载时,是否同步的去加载
    • onlyFromCache 是否只从缓存中加载
    • cacheMemoryOnly 是否只使用内存缓存
    • forceRefresh 是否强制刷新,若值为true,则每次都会重新下载
    • backgroundDecode 是否在子线程去解码
    • ...其他配置请参考Kingfisher源码解析之Options解释

    相关文章

      网友评论

        本文标题:Kingfisher源码解析之使用

        本文链接:https://www.haomeiwen.com/subject/liupgctx.html