先来放一下调用实例看看,算是比较优雅的链式操作
ImageDownloader(url: "http://www.iforworld.com/bizhi/4k/pic/20160203/002.JPG")
.progress { progress in
print(progress)
}.then { image in
print(image)
}.catch { error in
print(error)
}.start()
一、自定义Operation部分
1:要支持异步操作,首先要重载其中的属性
override var isExecuting: Bool {
return _isExecuting
}
override var isFinished: Bool {
return _isFinished
}
override var isAsynchronous: Bool {
return true
}
2:自定义异步需要我们自己通过KVO来通知Operation的isExecuting(是否正在进行中),以及isFinished(是否已经完成)
willChangeValue(forKey: "isExecuting")
_isExecuting = true/false
didChangeValue(forKey: "isExecuting")
willChangeValue(forKey: "isFinished")
_isFinished = true/false
didChangeValue(forKey: "isFinished")
当然我们可以使用swift的属性观察器来简化操作
private var _isExecuting: Bool {
willSet { willChangeValue(forKey: "isExecuting") }
didSet { didChangeValue(forKey: "isExecuting") }
}
private var _isFinished: Bool {
willSet { willChangeValue(forKey: "isFinished") }
didSet { didChangeValue(forKey: "isFinished") }
}
在需要改变的状态的时候,直接设置_isExecuting/_isFinished的值
3:因为Operation有个cancel方法来取消操作,而且我们并不知道在何时取消,所以我们需要在一下几个地方需要注意是否取消了操作
a:再重载start方法的时候我们检查一下是否cancel。如果取消了,我们要设置_isFinished的值来通知意见完成操作,同时立即return,不进行下一步操作;没取消的话才进行下一步才做;注意在这里我们需要加锁,因为可以在多线程操作的
override func start() {
semap.wait()
defer {
semap.signal()
}
if isCancelled {
wrongHandler?(.cancel)
_isFinished = true
return
}
Thread.detachNewThreadSelector(#selector(main), toTarget: self, with: nil)
_isExecuting = true
}
b:在比较耗时的操作之后都要检查一下isCancelled。比如URLSessionDataDelegate、把接收到的data转成uiimage等等......
func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
guard !isCanceled() else { return }
imageData?.append(data)
if let imageData = imageData {
let progress = Float(imageData.count) / Float(expectedSize)
DispatchQueue.main.async { [weak self] in
self?.progressBlock?(progress)
}
}
}
4:重载cancel方法。1、在这里我们需要设置finished和executing的状态;2、因为在这里我是用URLSession进行下载的,需要在这里把人物cancel掉;3、同时重置下对象中的其他属性
override func cancel() {
semap.wait()
defer {
semap.signal()
}
guard !isFinished else { return }
super.cancel()
task?.cancel()
if isExecuting {
_isExecuting = false
}
if !isFinished {
_isFinished = true
}
reset()
}
5:关于链式操作:只是简单的对回调属性进行赋值,并返回self
@discardableResult
func progress(_ progress: @escaping (Float) -> ()) -> Self {
self.progressBlock = progress
return self
}
@discardableResult
func then(_ completed: @escaping (UIImage) -> ()) -> Self {
self.completed = completed
return self
}
@discardableResult
func `catch`(_ error: @escaping (ImageDownloadError) -> ()) -> Self {
self.wrongHandler = error
return self
}
二、管理下载的工具类manager,这里并没有采用单例模式(比较简单的下载图片demo,并不是sd/kingfisher之类的大型框架),在这里使用OperationQueue来管理上面封装的operation,同时,我们还可以简单的设置最大并发下载数量
init() {
queue = OperationQueue()
queue.maxConcurrentOperationCount = maxConcurrentCount
}
下载图片方法就有下面一个
func download(_ url: String, progressBlock: ((Float) -> Void)? = nil, result: @escaping (ImageDownloadResult) -> Void) {
let op = ImageDownloader(url: url)
op.downloadTimeout = downloadTimeout
op.progress { progress in
if op.taskUrl == url {
progressBlock?(progress)
}
}.then { img in
if op.taskUrl == url {
result(.success(img))
}
}.catch { error in
if op.taskUrl == url {
result(.failure(error))
}
}.completionBlock = { [weak self] in
if op.taskUrl == url {
self?.operations.removeValue(forKey: url)
}
}
operations.updateValue(op, forKey: url)
queue.addOperation(op)
}
同时还有取消下载的两个方法
func cancelLoadingUrl(_ url: String) {
guard let op = operations[url], !op.isFinished else { return }
if op.taskUrl == url {
op.cancel()
}
}
func cancelAll() {
queue.cancelAllOperations()
}
最后来一个在tableview中下载并显示下载进度的图像(不会制作gif)