在AlamofireImage中一共就只有5個(gè)類加一些擴(kuò)展
// 錯誤處理類,繼承自Error限煞,主要有requestCancelled(請求取消)、imageSerializationFailed(請求失敗)兩種錯誤
AFIError
// 定義圖片對象,主要用來適配mac(NSImage)和ios(UIImage)平臺
Image
// 圖片內(nèi)存緩存對象
ImageCache
// 圖片下載對象(下載基于Alamofire)
ImageDownloader
// 圖片濾鏡對象(CoreGraphics切圓角振劳,CoreImage濾鏡)
ImageFilter
一、圖片加載過程
AlamofireImage中的擴(kuò)展定義了很多快速對UI控件設(shè)置圖片的方法油狂,我挑其中一個(gè)來詳解AlamofireImage是怎樣將圖片加載到視圖上的
// 該方法是UIImageView的一個(gè)擴(kuò)展方法,其它控件的擴(kuò)展方法都差不多一樣
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正在下載圖片味咳,而你此時(shí)再次請求下載圖片時(shí)槽驶,它會進(jìn)行判斷,
如果兩次的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.獲取一個(gè)下載器
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)
{
// 構(gòu)建一個(gè)返回對象
let response = DataResponse<UIImage>(request: request, response: nil, data: nil, result: .success(image))
if runImageTransitionIfCached {
// 如果有圖片加載動畫蛋济,加載圖片并執(zhí)行動畫渡处,并執(zhí)行完成回調(diào)閉包
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 {
// 如果沒有圖片加載動畫祟辟,直接加載圖片旧困,并執(zhí)行完成回調(diào)閉包
self.image = image
completion?(response)
}
return
}
// 6.如果沒有緩存,則先加載占位圖
if let placeholderImage = placeholderImage { self.image = placeholderImage }
// 生成唯一的下載標(biāo)識符僚纷,以檢查下載在請求中是否發(fā)生了更改
let downloadID = UUID().uuidString
// 7.開始下載圖片
let requestReceipt = imageDownloader.download(
urlRequest,
receiptID: downloadID,
filter: filter,
progress: progress,
progressQueue: progressQueue,
completion: { [weak self] response in
// 如果ImageView當(dāng)前下載圖片的url和返回的url相同怖竭,并且下載的標(biāo)識符也相同痊臭,說明已經(jīng)下載過了广匙,所以這里直接返回完成鸦致,不需要再做其它了
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.清除當(dāng)前的下載票據(jù)鳍寂,回調(diào)加載下載完成
strongSelf.af_activeRequestReceipt = nil
completion?(response)
}
)
// 存儲ImageView正在請求的信息
af_activeRequestReceipt = requestReceipt
}
下面是下載類核心方法和注解
@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屬性中添加本次的回調(diào)閉包(多個(gè)view同時(shí)加載同一張圖片的情況)
// 注:ImageDownloader在responseHandlers屬性中盗扇,存儲正在下載的請求疗隶,以防止相同的請求多次發(fā)出斑鼻,
// 這樣不僅浪費(fèi)流量還會影響加載速度坚弱,如果有多個(gè)相同的請求時(shí)荒叶,我們只會發(fā)出一個(gè)拒垃,在完成后一起回調(diào)
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
}
// (內(nèi)存緩存)如果允許緩存悼瓮,再次嘗試從緩存加載圖像
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
}
}
// 創(chuàng)建下載器
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)
}
// 生成唯一的下載標(biāo)識符横堡,以檢查下載過程中請求是否發(fā)生了更改
let handlerID = UUID().uuidString
// 開始請求(這其中默認(rèn)會去獲取NSURLCache的緩存,內(nèi)存緩存和磁盤緩存均有)
request.response(queue: self.responseQueue, responseSerializer: imageResponseSerializer, completionHandler: { [weak self] response in
guard let strongSelf = self, let request = response.request else { return }
defer {
// 減少當(dāng)前需要下載的任務(wù)數(shù)
strongSelf.safelyDecrementActiveRequestCount()
// 如果還有食听,則開始下一個(gè)下載
strongSelf.safelyStartNextRequestIfNecessary()
}
// 將該請求從正在下載任務(wù)中移除
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
}
// 內(nèi)存緩存圖片
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) }
}
}
}
)
// 存儲正在下載的任務(wù)
let responseHandler = ResponseHandler(
request: request,
handlerID: handlerID,
receiptID: receiptID,
filter: filter,
completion: completion
)
self.responseHandlers[urlID] = responseHandler
// 開始任務(wù)或者加載到任務(wù)隊(duì)列中
if self.isActiveRequestCountBelowMaximumLimit() {
self.start(request)
} else {
self.enqueue(request)
}
}
if let request = request {
return RequestReceipt(request: request, receiptID: receiptID)
}
return nil
}
總結(jié)下載過程
1.在內(nèi)存緩存對象(ImageCache)中獲取緩存迹蛤,如果有則返回圖片
2.在NSURLCache中獲取緩存(內(nèi)存緩存+磁盤緩存),如果有則返回圖片
3.開始網(wǎng)絡(luò)下載圖片陋桂,成功后返回圖片
4.緩存圖片
5.檢查是否使用濾鏡嗜历、加載動畫等加載圖片
二秸脱、緩存實(shí)現(xiàn)
1.ImageCache摊唇,實(shí)際上它只是一個(gè)協(xié)議巷查,真正緩存的對象是CachedImage
class CachedImage {
// 圖片
let image: Image
// 標(biāo)識符
let identifier: String
// 圖片大小
let totalBytes: UInt64
// 最后修改時(shí)間
var lastAccessDate: Date
}
在ImageCache協(xié)議中有如下幾個(gè)方法
/// 通過標(biāo)識符添加一個(gè)緩存圖片
func add(_ image: Image, withIdentifier identifier: String)
/// 通過標(biāo)識符移除一個(gè)圖片
func removeImage(withIdentifier identifier: String) -> Bool
/// 移除所有圖片
func removeAllImages() -> Bool
/// 通過標(biāo)識符獲取圖片
func image(withIdentifier identifier: String) -> Image?
實(shí)際對圖片存儲的類是AutoPurgingImageCache,它實(shí)現(xiàn)了ImageCache協(xié)議崇败,下再來看一下它的一些屬性
// 最大內(nèi)存容量
open let memoryCapacity: UInt64
// 當(dāng)內(nèi)存容量達(dá)到最大值后室,清除后的剩余內(nèi)存(當(dāng)內(nèi)存達(dá)到最大值時(shí):清除部分 = memoryCapacity - preferredMemoryUsageAfterPurge)
open let preferredMemoryUsageAfterPurge: UInt64
// 操作線程
private let synchronizationQueue: DispatchQueue
// 緩存(說明AlamofireImage的圖片緩存是一個(gè)字典)
private var cachedImages: [String: CachedImage]
// 當(dāng)前內(nèi)存
private var currentMemoryUsage: UInt64
下面是添加緩存的核心函數(shù)
open func add(_ image: Image, withIdentifier identifier: String) {
synchronizationQueue.async(flags: [.barrier]) {
// 緩存圖片岸霹,并計(jì)算當(dāng)前的內(nèi)存緩存大小
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贡避,保證了當(dāng)上面的圖片緩存完成之后才會執(zhí)行下面的代碼
synchronizationQueue.async(flags: [.barrier]) {
// 當(dāng)前緩存大于最大緩存時(shí)
if self.currentMemoryUsage > self.memoryCapacity {
// 計(jì)算需要清除的緩存大小
let bytesToPurge = self.currentMemoryUsage - self.preferredMemoryUsageAfterPurge
// 對圖片進(jìn)行時(shí)間排序,我們需要清除較早的一些圖片
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
}
}
// 清除緩存完成掖蛤,設(shè)置當(dāng)前緩存的大小
self.currentMemoryUsage -= bytesPurged
}
}
}
2.NSURLCache坠七,它我這里就不具體講了,有興趣的可以看這篇文章 http://nshipster.cn/nsurlcache/
三蝇恶、加載動畫和濾鏡
對于這一部分內(nèi)容惶桐,我自己也沒有使用過姚糊,所以下面只貼出源碼加注釋贸辈,有興趣的讀者可以自己去研究
1.動畫
// 加載動畫(是用UIView的動畫來實(shí)現(xiàn)的)
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.濾鏡
// 將圖片變化到指定大小
public struct ScaledToSizeFilter: ImageFilter, Sizable {
/// 變換后的大小
public let size: CGSize
public init(size: CGSize) {
self.size = size
}
/// 實(shí)現(xiàn)函數(shù)
public var filter: (Image) -> Image {
return { image in
return image.af_imageScaled(to: self.size)
}
}
}
/// 拉伸適應(yīng)
public struct AspectScaledToFitSizeFilter: ImageFilter, Sizable {
public let size: CGSize
public init(size: CGSize) {
self.size = size
}
/// 實(shí)現(xiàn)函數(shù)
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
}
/// 實(shí)現(xiàn)函數(shù)
public var filter: (Image) -> Image {
return { image in
return image.af_imageAspectScaled(toFill: self.size)
}
}
}
/// 圓角
public struct RoundedCornersFilter: ImageFilter, Roundable {
/// 圓角大小
public let radius: CGFloat
/// 圓角的大小需不需要進(jìn)行縮放radius / scale
public let divideRadiusByImageScale: Bool
public init(radius: CGFloat, divideRadiusByImageScale: Bool = false) {
self.radius = radius
self.divideRadiusByImageScale = divideRadiusByImageScale
}
/// 實(shí)現(xiàn)函數(shù)
public var filter: (Image) -> Image {
return { image in
return image.af_imageRounded(
withCornerRadius: self.radius,
divideRadiusByImageScale: self.divideRadiusByImageScale
)
}
}
}
/// 圓形圖片
public struct CircleFilter: ImageFilter {
public init() {}
/// 實(shí)現(xiàn)函數(shù)
public var filter: (Image) -> Image {
return { image in
return image.af_imageRoundedIntoCircle()
}
}
}
// CoreImage 濾鏡
public protocol CoreImageFilter: ImageFilter {
/// 濾鏡名稱
var filterName: String { get }
/// 濾鏡參數(shù)
var parameters: [String: Any] { get }
}
/// 高斯模糊濾鏡
public struct BlurFilter: ImageFilter, CoreImageFilter {
/// 高斯濾鏡名稱
public let filterName = "CIGaussianBlur"
/// 參數(shù)
public let parameters: [String: Any]
public init(blurRadius: UInt = 10) {
self.parameters = ["inputRadius": blurRadius]
}
}
3.CoreImage 濾鏡具體實(shí)現(xiàn)的核心函數(shù)
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)
}
}