前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >AlamofireImage 源码阅读

AlamofireImage 源码阅读

作者头像
用户2215591
发布2018-06-13 14:46:37
1.5K0
发布2018-06-13 14:46:37
举报
文章被收录于专栏:iOSer成长记录iOSer成长记录

在AlamofireImage中一共就只有5个类加一些扩展

代码语言:javascript
复制
// 错误处理类,继承自Error,主要有requestCancelled(请求取消)、imageSerializationFailed(请求失败)两种错误
AFIError
// 定义图片对象,主要用来适配mac(NSImage)和ios(UIImage)平台
Image
// 图片内存缓存对象
ImageCache
// 图片下载对象(下载基于Alamofire)
ImageDownloader
// 图片滤镜对象(CoreGraphics切圆角,CoreImage滤镜)
ImageFilter

一、图片加载过程

AlamofireImage中的扩展定义了很多快速对UI控件设置图片的方法,我挑其中一个来详解AlamofireImage是怎样将图片加载到视图上的

代码语言:javascript
复制
// 该方法是UIImageView的一个扩展方法,其它控件的扩展方法都差不多一样
public func af_setImage(
        withURLRequest urlRequest: URLRequestConvertible,
        placeholderImage: UIImage? = nil,
        filter: ImageFilter? = nil,
        progress: ImageDownloader.ProgressHandler? = nil,
        progressQueue: DispatchQueue = DispatchQueue.main,
        imageTransition: ImageTransition = .noTransition,
        runImageTransitionIfCached: Bool = false,
        completion: ((DataResponse<UIImage>) -> Void)? = nil)
    {
        /* 1.判断ImageView是否正在下载该url图片
         注:Alamofire通过runtime将正在下载图片的请求对象RequestReceipt绑定到af_activeRequestReceipt属性, 在请求完成后再将af_activeRequestReceipt置为nil,
         所以如果ImageView正在下载图片,而你此时再次请求下载图片时,它会进行判断,
         如果两次的url相同,则不需要再次下载了,所有这里会立即返回错误:"取消了一次请求"
        */
        guard !isURLRequestURLEqualToActiveRequestURL(urlRequest) else {
            let error = AFIError.requestCancelled
            let response = DataResponse<UIImage>(request: nil, response: nil, data: nil, result: .failure(error))
            completion?(response)
            return
        }

        // 2.取消之前的所有请求
        af_cancelImageRequest()

        // 3.获取一个下载器
        let imageDownloader = af_imageDownloader ?? UIImageView.af_sharedImageDownloader

        // 4.获取下载器的缓存对象
        let imageCache = imageDownloader.imageCache

        // 5.如果存在缓存
        if  let request = urlRequest.urlRequest,
            let image = imageCache?.image(for: request, withIdentifier: filter?.identifier)
        {
            // 构建一个返回对象
            let response = DataResponse<UIImage>(request: request, response: nil, data: nil, result: .success(image))
            if runImageTransitionIfCached {
                // 如果有图片加载动画,加载图片并执行动画,并执行完成回调闭包
                let tinyDelay = DispatchTime.now() + Double(Int64(0.001 * Float(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
                DispatchQueue.main.asyncAfter(deadline: tinyDelay) {
                    self.run(imageTransition, with: image)
                    completion?(response)
                }
            } else {
                // 如果没有图片加载动画,直接加载图片,并执行完成回调闭包
                self.image = image
                completion?(response)
            }
            return
        }

        // 6.如果没有缓存,则先加载占位图
        if let placeholderImage = placeholderImage { self.image = placeholderImage }

        // 生成唯一的下载标识符,以检查下载在请求中是否发生了更改
        let downloadID = UUID().uuidString

        // 7.开始下载图片
        let requestReceipt = imageDownloader.download(
            urlRequest,
            receiptID: downloadID,
            filter: filter,
            progress: progress,
            progressQueue: progressQueue,
            completion: { [weak self] response in
                // 如果ImageView当前下载图片的url和返回的url相同,并且下载的标识符也相同,说明已经下载过了,所以这里直接返回完成,不需要再做其它了
                guard let strongSelf = self,
                          strongSelf.isURLRequestURLEqualToActiveRequestURL(response.request) &&
                          strongSelf.af_activeRequestReceipt?.receiptID == downloadID else {
                    completion?(response)
                    return
                }

                // 8.下载图片成功,加载图片
                if let image = response.result.value {
                    strongSelf.run(imageTransition, with: image)
                }

                // 9.清除当前的下载票据,回调加载下载完成
                strongSelf.af_activeRequestReceipt = nil
                completion?(response)
            }
        )

        // 存储ImageView正在请求的信息
        af_activeRequestReceipt = requestReceipt
    }

下面是下载类核心方法和注解

代码语言:javascript
复制
@discardableResult
    open func download(
        _ urlRequest: URLRequestConvertible,
        receiptID: String = UUID().uuidString,
        filter: ImageFilter? = nil,
        progress: ProgressHandler? = nil,
        progressQueue: DispatchQueue = DispatchQueue.main,
        completion: CompletionHandler?)
        -> RequestReceipt?
    {
        var request: DataRequest!
        
        // 异步加载图片
        synchronizationQueue.sync {
            // 再次判断该请求是否正在请求,如果是,则在responseHandlers属性中添加本次的回调闭包(多个view同时加载同一张图片的情况)
            // 注:ImageDownloader在responseHandlers属性中,存储正在下载的请求,以防止相同的请求多次发出,
            // 这样不仅浪费流量还会影响加载速度,如果有多个相同的请求时,我们只会发出一个,在完成后一起回调
            let urlID = ImageDownloader.urlIdentifier(for: urlRequest)
            if let responseHandler = self.responseHandlers[urlID] {
                responseHandler.operations.append((receiptID: receiptID, filter: filter, completion: completion))
                request = responseHandler.request
                return
            }

            // (内存缓存)如果允许缓存,再次尝试从缓存加载图像
            if let request = urlRequest.urlRequest {
                switch request.cachePolicy {
                case .useProtocolCachePolicy, .returnCacheDataElseLoad, .returnCacheDataDontLoad:
                    if let image = self.imageCache?.image(for: request, withIdentifier: filter?.identifier) {
                        DispatchQueue.main.async {
                            let response = DataResponse<Image>(
                                request: urlRequest.urlRequest,
                                response: nil,
                                data: nil,
                                result: .success(image)
                            )
                            completion?(response)
                        }
                        return
                    }
                default:
                    break
                }
            }

            // 创建下载器
            request = self.sessionManager.request(urlRequest)
            if let credential = self.credential {
                request.authenticate(usingCredential: credential)
            }

            request.validate()
            if let progress = progress {
                request.downloadProgress(queue: progressQueue, closure: progress)
            }

            // 生成唯一的下载标识符,以检查下载过程中请求是否发生了更改
            let handlerID = UUID().uuidString

            // 开始请求(这其中默认会去获取NSURLCache的缓存,内存缓存和磁盘缓存均有)
            request.response(queue: self.responseQueue, responseSerializer: imageResponseSerializer, completionHandler: { [weak self] response in
                    guard let strongSelf = self, let request = response.request else { return }

                    defer {
                        // 减少当前需要下载的任务数
                        strongSelf.safelyDecrementActiveRequestCount()
                        // 如果还有,则开始下一个下载
                        strongSelf.safelyStartNextRequestIfNecessary()
                    }

                    // 将该请求从正在下载任务中移除
                    let handler = strongSelf.safelyFetchResponseHandler(withURLIdentifier: urlID)
                    guard handler?.handlerID == handlerID else { return }
                    guard let responseHandler = strongSelf.safelyRemoveResponseHandler(withURLIdentifier: urlID) else {
                        return
                    }

                    switch response.result {
                    case .success(let image):
                        var filteredImages: [String: Image] = [:]

                        // 下载完成
                        for (_, filter, completion) in responseHandler.operations {
                            var filteredImage: Image

                            if let filter = filter {
                                if let alreadyFilteredImage = filteredImages[filter.identifier] {
                                    filteredImage = alreadyFilteredImage
                                } else {
                                    filteredImage = filter.filter(image)
                                    filteredImages[filter.identifier] = filteredImage
                                }
                            } else {
                                filteredImage = image
                            }

                            // 内存缓存图片
                            strongSelf.imageCache?.add(filteredImage, for: request, withIdentifier: filter?.identifier)

                            // 返回Image
                            DispatchQueue.main.async {
                                let response = DataResponse<Image>(
                                    request: response.request,
                                    response: response.response,
                                    data: response.data,
                                    result: .success(filteredImage),
                                    timeline: response.timeline
                                )
                                completion?(response)
                            }
                        }
                    case .failure:
                        // 下载失败
                        for (_, _, completion) in responseHandler.operations {
                            DispatchQueue.main.async { completion?(response) }
                        }
                    }
                }
            )

            // 存储正在下载的任务
            let responseHandler = ResponseHandler(
                request: request,
                handlerID: handlerID,
                receiptID: receiptID,
                filter: filter,
                completion: completion
            )
            self.responseHandlers[urlID] = responseHandler

            // 开始任务或者加载到任务队列中
            if self.isActiveRequestCountBelowMaximumLimit() {
                self.start(request)
            } else {
                self.enqueue(request)
            }
        }
        if let request = request {
            return RequestReceipt(request: request, receiptID: receiptID)
        }
        return nil
    }

总结下载过程 1.在内存缓存对象(ImageCache)中获取缓存,如果有则返回图片 2.在NSURLCache中获取缓存(内存缓存+磁盘缓存),如果有则返回图片 3.开始网络下载图片,成功后返回图片 4.缓存图片 5.检查是否使用滤镜、加载动画等加载图片

二、缓存实现

1.ImageCache,实际上它只是一个协议,真正缓存的对象是CachedImage

代码语言:javascript
复制
class CachedImage {
    // 图片
    let image: Image
    // 标识符
    let identifier: String
    // 图片大小
    let totalBytes: UInt64
    // 最后修改时间
    var lastAccessDate: Date 
}

在ImageCache协议中有如下几个方法

代码语言:javascript
复制
/// 通过标识符添加一个缓存图片
func add(_ image: Image, withIdentifier identifier: String)

/// 通过标识符移除一个图片
func removeImage(withIdentifier identifier: String) -> Bool

/// 移除所有图片
func removeAllImages() -> Bool

/// 通过标识符获取图片
func image(withIdentifier identifier: String) -> Image?

实际对图片存储的类是AutoPurgingImageCache,它实现了ImageCache协议,下再来看一下它的一些属性

代码语言:javascript
复制
// 最大内存容量
open let memoryCapacity: UInt64
// 当内存容量达到最大值,清除后的剩余内存(当内存达到最大值时:清除部分 = memoryCapacity - preferredMemoryUsageAfterPurge)
open let preferredMemoryUsageAfterPurge: UInt64
// 操作线程
private let synchronizationQueue: DispatchQueue
// 缓存(说明AlamofireImage的图片缓存是一个字典)
private var cachedImages: [String: CachedImage]
// 当前内存
private var currentMemoryUsage: UInt64

下面是添加缓存的核心函数

代码语言:javascript
复制
open func add(_ image: Image, withIdentifier identifier: String) {
        synchronizationQueue.async(flags: [.barrier]) {
            // 缓存图片,并计算当前的内存缓存大小
            let cachedImage = CachedImage(image, identifier: identifier)
            if let previousCachedImage = self.cachedImages[identifier] {
                self.currentMemoryUsage -= previousCachedImage.totalBytes
            }
            self.cachedImages[identifier] = cachedImage
            self.currentMemoryUsage += cachedImage.totalBytes
        }
        // 这里的barrier,保证了当上面的图片缓存完成之后才会执行下面的代码
        synchronizationQueue.async(flags: [.barrier]) {
            // 当前缓存大于最大缓存时
            if self.currentMemoryUsage > self.memoryCapacity {
                // 计算需要清除的缓存大小
                let bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge
                // 对图片进行时间排序,我们需要清除较早的一些图片
                var sortedImages = self.cachedImages.map { $1 }
                sortedImages.sort {
                    let date1 = $0.lastAccessDate
                    let date2 = $1.lastAccessDate
                    return date1.timeIntervalSince(date2) < 0.0
                }
                // 开始清除缓存
                var bytesPurged = UInt64(0)
                for cachedImage in sortedImages {
                    self.cachedImages.removeValue(forKey: cachedImage.identifier)
                    bytesPurged += cachedImage.totalBytes
                    if bytesPurged >= bytesToPurge {
                        break
                    }
                }
                // 清除缓存完成,设置当前缓存的大小
                self.currentMemoryUsage -= bytesPurged
            }
        }
    }

2.NSURLCache,它我这里就不具体讲了,有兴趣的可以看这篇文章 http://nshipster.cn/nsurlcache/

三、加载动画和滤镜

对于这一部分内容,我自己也没有使用过,所以下面只贴出源码加注释,有兴趣的读者可以自己去研究

1.动画

代码语言:javascript
复制
// 加载动画(是用UIView的动画来实现的)
public func run(_ imageTransition: ImageTransition, with image: Image) {
    UIView.transition(
        with: self,
        duration: imageTransition.duration,
        options: imageTransition.animationOptions,
        animations: { imageTransition.animations(self, image) },
        completion: imageTransition.completion
    )
}
public enum ImageTransition {
    // 所有可选的动画类型
    // 没有动画
    case noTransition
    // 淡入淡出
    case crossDissolve(TimeInterval)
    // 向下翻页
    case curlDown(TimeInterval)
    // 向上翻页
    case curlUp(TimeInterval)
    // 从下、左、右、上翻出
    case flipFromBottom(TimeInterval)
    case flipFromLeft(TimeInterval)
    case flipFromRight(TimeInterval)
    case flipFromTop(TimeInterval)
    // 自定义动画
    case custom(
        duration: TimeInterval,
        animationOptions: UIViewAnimationOptions,
        animations: (UIImageView, Image) -> Void,
        completion: ((Bool) -> Void)?
    )
}

2.滤镜

代码语言:javascript
复制
// 将图片变化到指定大小
public struct ScaledToSizeFilter: ImageFilter, Sizable {
    /// 变换后的大小
    public let size: CGSize
    public init(size: CGSize) {
        self.size = size
    }
    /// 实现函数
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageScaled(to: self.size)
        }
    }
}

/// 拉伸适应
public struct AspectScaledToFitSizeFilter: ImageFilter, Sizable {
    public let size: CGSize
    public init(size: CGSize) {
        self.size = size
    }
    /// 实现函数
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageAspectScaled(toFit: self.size)
        }
    }
}

/// 拉伸铺满
public struct AspectScaledToFillSizeFilter: ImageFilter, Sizable {
    public let size: CGSize
    public init(size: CGSize) {
        self.size = size
    }
    /// 实现函数
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageAspectScaled(toFill: self.size)
        }
    }
}

/// 圆角
public struct RoundedCornersFilter: ImageFilter, Roundable {
    /// 圆角大小 
    public let radius: CGFloat
    /// 圆角的大小需不需要进行缩放radius / scale
    public let divideRadiusByImageScale: Bool
    public init(radius: CGFloat, divideRadiusByImageScale: Bool = false) {
        self.radius = radius
        self.divideRadiusByImageScale = divideRadiusByImageScale
    }
    /// 实现函数
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageRounded(
                withCornerRadius: self.radius,
                divideRadiusByImageScale: self.divideRadiusByImageScale
            )
        }
    }
}

/// 圆形图片
public struct CircleFilter: ImageFilter {
    public init() {}
    /// 实现函数
    public var filter: (Image) -> Image {
        return { image in
            return image.af_imageRoundedIntoCircle()
        }
    }
}

// CoreImage 滤镜
public protocol CoreImageFilter: ImageFilter {
    /// 滤镜名称
    var filterName: String { get }
    /// 滤镜参数
    var parameters: [String: Any] { get }
}

/// 高斯模糊滤镜
public struct BlurFilter: ImageFilter, CoreImageFilter {
    /// 高斯滤镜名称
    public let filterName = "CIGaussianBlur"
    /// 参数
    public let parameters: [String: Any]
    public init(blurRadius: UInt = 10) {
        self.parameters = ["inputRadius": blurRadius]
    }
}

3.CoreImage 滤镜具体实现的核心函数

代码语言:javascript
复制
extension UIImage {
    public func af_imageFiltered(withCoreImageFilter name: String, parameters: [String: Any]? = nil) -> UIImage? {
        var image: CoreImage.CIImage? = ciImage

        if image == nil, let CGImage = self.cgImage {
            image = CoreImage.CIImage(cgImage: CGImage)
        }

        guard let coreImage = image else { return nil }

        let context = CIContext(options: [kCIContextPriorityRequestLow: true])

        var parameters: [String: Any] = parameters ?? [:]
        parameters[kCIInputImageKey] = coreImage

        guard let filter = CIFilter(name: name, withInputParameters: parameters) else { return nil }
        guard let outputImage = filter.outputImage else { return nil }

        let cgImageRef = context.createCGImage(outputImage, from: outputImage.extent)

        return UIImage(cgImage: cgImageRef!, scale: scale, orientation: imageOrientation)
    }
}
本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2018.05.28 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、图片加载过程
  • 二、缓存实现
  • 三、加载动画和滤镜
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档