提到Kingfisher相信使用swift做日常开发的同学应该都知道,喵神开源的一个十分强大易用的网络图片加载库。但是只提供了单张图片加载的方法,嵌套加载代码结构不好看效率也低,使用信号量或者group也会加不少代码,这里使用gcd group做了一个简单的封装。
一、老生长谈的group enter、group level
由于Kingfisher里图片加载都是异步操作,我们希望可以监测到所有图片都加载完毕然后给一个回调,这其实就是多个异步任务的同步处理操作,而iOS里比较常用的也就是gcd里的信号量和group enter、group level了,关于这些知识网络上已经有比较多的资料了,这里就不再赘述了。废话不多说直接上代码。
- 单张图片加载方法,这里我们使用的是retrieveImage方法,这个方法会先从内存/硬盘缓存里查找图片,如果找到会直接返回,找不到则去加载并缓存图片资源。
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来进行同步处理的。(我这边的需求是只要一张下载失败就直接返回,但是没有找到提前终止group或者释放的相关方法,这里处理是如果有下载失败直接回调,并将闭包置空)
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设置超时时间,但要注意这个方法会阻塞当前线程所以不能放在主线程调用)
/// Kingfisher多图下载
/// - Parameter urlStrArray: 图片链接str数组
/// - Parameter timeout: 超时时间
/// - Parameter complete: 回调
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里是有命名空间的(默认是product name),所以大家在swift为了防止命名冲突不再像以前oc里加前缀了,而是使用kf、rx这种对对象做一层包装然后调用方法。(除了命名问题,也很好的做了隔离比如kf.setimage并不是给UIIMageView、UIButton添加了扩展方法...)
- 定义包装类型(添加类型泛型约束以使用所有类型)
包装后的类型要能获取到原始类型以做处理,这里定义了成员变量base(被包装对象实例调用实例方法),baseType(被包装对象类型调用静态方法)
struct AIWrapper<Base> {
public let base: Base
static var baseType: Base.Type {
return Base.self
}
public init(_ base: Base) {
self.base = base
}
}
- 定义协议添加计算型成员变量来实现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
}
}
- 比葫芦画瓢其实很容易理解,这里我加了个类型包装可以调用静态方法。
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()
网友评论