提到Kingfisher相信使用swift做日常開發(fā)的同學(xué)應(yīng)該都知道,喵神開源的一個十分強(qiáng)大易用的網(wǎng)絡(luò)圖片加載庫凌盯。但是只提供了單張圖片加載的方法付枫,嵌套加載代碼結(jié)構(gòu)不好看效率也低,使用信號量或者group也會加不少代碼驰怎,這里使用gcd group做了一個簡單的封裝阐滩。
一、老生長談的group enter县忌、group level
由于Kingfisher里圖片加載都是異步操作掂榔,我們希望可以監(jiān)測到所有圖片都加載完畢然后給一個回調(diào),這其實就是多個異步任務(wù)的同步處理操作症杏,而iOS里比較常用的也就是gcd里的信號量和group enter装获、group level了,關(guān)于這些知識網(wǎng)絡(luò)上已經(jīng)有比較多的資料了厉颤,這里就不再贅述了穴豫。廢話不多說直接上代碼。
- 單張圖片加載方法走芋,這里我們使用的是retrieveImage方法绩郎,這個方法會先從內(nèi)存/硬盤緩存里查找圖片,如果找到會直接返回翁逞,找不到則去加載并緩存圖片資源。
static func downloadWith(urlStr: String, complete: ((UIImage?) -> ())? = nil) {
if let url = URL(string: urlStr) {
KingfisherManager.shared.retrieveImage(with: url) { (result) in
switch result {
case .success(let imgResult):
complete?(imgResult.image)
case .failure(let error):
print(error)
complete?(nil)
}
}
} else {
complete?(nil)
}
}
- 今天的多張圖片加載溉仑,這里使用的是dispatchgroup的enter挖函、level來進(jìn)行同步處理的。(我這邊的需求是只要一張下載失敗就直接返回,但是沒有找到提前終止group或者釋放的相關(guān)方法怨喘,這里處理是如果有下載失敗直接回調(diào)津畸,并將閉包置空)
static func downloadWith(urlStrArray: [String], complete: @escaping AIKingfisherDownLoadResultBlock) {
let group = DispatchGroup()
let queue = DispatchQueue.main
var imgDic = [String: UIImage]()
var downLoadCount = 0
var block: AIKingfisherDownLoadResultBlock? = complete
for urlStr in urlStrArray {
group.enter()
queue.async(group: group) {
AIKingfisher.downloadWith(urlStr: urlStr) { (image) in
if let img = image {
imgDic.updateValue(img, forKey: urlStr)
downLoadCount = downLoadCount + 1
} else { //有一個下載失敗就提前終止
block?(nil)
block = nil
}
group.leave()
}
}
}
group.notify(queue: queue) {
if downLoadCount != urlStrArray.count {
block?(nil)
} else {
block?(AIKingfisherDownLoadResult(imgDic: imgDic))
}
}
}
3、另外一種寫法(使用group.wait設(shè)置超時時間必怜,但要注意這個方法會阻塞當(dāng)前線程所以不能放在主線程調(diào)用)
/// Kingfisher多圖下載
/// - Parameter urlStrArray: 圖片鏈接str數(shù)組
/// - Parameter timeout: 超時時間
/// - Parameter complete: 回調(diào)
static func downloadWith(urlStrArray: [String], timeout: TimeInterval = 10, complete: @escaping (AIKingfisherDownLoadResult?) -> ()) {
let group = DispatchGroup()
let queue = DispatchQueue.main
var imgDic = [String: UIImage]()
var downLoadCount = 0
for urlStr in urlStrArray {
group.enter()
queue.async(group: group) {
AIKingfisher.downloadWith(urlStr: urlStr) { (image) in
if let img = image {
imgDic.updateValue(img, forKey: urlStr)
downLoadCount = downLoadCount + 1
}
group.leave()
}
}
}
DispatchQueue.global().async {
let result = group.wait(timeout: DispatchTime.now() + timeout)
DispatchQueue.main.async {
switch result {
case .success:
if downLoadCount != urlStrArray.count {
complete(nil)
} else {
complete(AIKingfisherDownLoadResult(imgDic: imgDic))
}
case .timedOut:
AILog("下載超時")
complete(nil)
}
}
}
}
二肉拓、Kingfisher里的kf是怎么回事
由于swift里是有命名空間的(默認(rèn)是product name),所以大家在swift為了防止命名沖突不再像以前oc里加前綴了梳庆,而是使用kf暖途、rx這種對對象做一層包裝然后調(diào)用方法。(除了命名問題膏执,也很好的做了隔離比如kf.setimage并不是給UIIMageView驻售、UIButton添加了擴(kuò)展方法...)
- 定義包裝類型(添加類型泛型約束以使用所有類型)
包裝后的類型要能獲取到原始類型以做處理,這里定義了成員變量base(被包裝對象實例調(diào)用實例方法)更米,baseType(被包裝對象類型調(diào)用靜態(tài)方法)
struct AIWrapper<Base> {
public let base: Base
static var baseType: Base.Type {
return Base.self
}
public init(_ base: Base) {
self.base = base
}
}
- 定義協(xié)議添加計算型成員變量來實現(xiàn)kf欺栗、rx...
protocol AICompatible : AnyObject {}
extension AICompatible {
public var ai: AIWrapper<Self> {
return AIWrapper(self)
}
static var ai: AIWrapper<Self>.Type {
return AIWrapper<Self>.self
}
}
- 比葫蘆畫瓢其實很容易理解,這里我加了個類型包裝可以調(diào)用靜態(tài)方法征峦。
extension UIImageView: AICompatible {}
extension UIButton: AICompatible {}
extension AIWrapper where Base: UIImageView {
func setImage(imgUrl: String?, placeHolder: UIImage? = nil) {
if let urlStr = imgUrl, let url = URL(string: urlStr) {
self.base.kf.setImage(with: url, placeholder: placeHolder, options: [.transition(.fade(0.2))])
}
}
static func imageViewTestFunc() {
print(baseType)
}
}
使用類似于下面的例子
img.ai.setImage(imgUrl: url1)
UIImageView.ai.imageViewTestFunc()