美文网首页
Kingfisher源码分析--下载图片

Kingfisher源码分析--下载图片

作者: oldmonster | 来源:发表于2020-01-16 17:31 被阅读0次
    Kingfisher图片下载.jpg

    我们通过使用的是ImageView通过fingfisher设置图片,因此这边我们以ImageView为例。可以看到在ImageView+Kingfisher中有3个设置图片的方法供外部调用。这3个方法基本一样只是其第一个参数有些许不同

    func setImage(with source: Source?,placeholder: Placeholder? = nil,options: KingfisherOptionsInfo? = nil,progressBlock:DownloadProgressBlock? = nil,completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
    
    func setImage(with resource: Resource?,placeholder: Placeholder? = nil,options: KingfisherOptionsInfo? = nil, progressBlock: DownloadProgressBlock? = nil, completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
    
    func setImage(with provider: ImageDataProvider?, placeholder: Placeholder? = nil,options: KingfisherOptionsInfo? = nil,progressBlock: DownloadProgressBlock? = nil,completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
    
    

    进入到Source的定义类中中,可以看到其是一个枚举,有两个值。可以看到其参数分别对应上述方法中的第一个参数类型。所以其实我们在调用设置图片的方法时最终都会调用到上面的第一个方法中。

        /// The target image should be got from network remotely. The associated `Resource`
        /// value defines detail information like image URL and cache key.
        case network(Resource)
        
        /// The target image should be provided in a data format. Normally, it can be an image
        /// from local storage or in any other encoding format (like Base64).
        case provider(ImageDataProvider)
    

    下面我们仔细分析一下设置图片的核心逻辑。

    public func setImage(
            with source: Source?,
            placeholder: Placeholder? = nil,
            options: KingfisherOptionsInfo? = nil,
            progressBlock: DownloadProgressBlock? = nil,
            completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
        {
            
            //参数合法性判断、设置读取策略和启动动画
            var mutatingSelf = self
            //没有Reource(URL)时直接显示占位图,且加载图片失败
            guard let source = source else {
                mutatingSelf.placeholder = placeholder
                mutatingSelf.taskIdentifier = nil
                //***对错误的枚举处理值得借鉴
                completionHandler?(.failure(KingfisherError.imageSettingError(reason: .emptySource)))
                return nil
            }
    
            
            //加载策略
            /**
                **这边组合默认加载策略的原因是用户可以在外部统一设置图片的默认加载策略,在设置了默认加载策略的时候也可以另行再去设置某一图片的加载策略,此种情况下
                    优先以后面设置的策略为准,因此需要将后续的加载策略放在组合数组的后边,这样在实例化KingfisherParsedOptionsInfo时后边的策略会覆盖前边已经设置过的策略
             **/
            
            let array1 = KingfisherManager.shared.defaultOptions
            let array2 = (options ?? .empty)
            let array3 = array1 + array2
            var options = KingfisherParsedOptionsInfo(array3)
            //当前UIImageView没有显示图片且当前没有显示占位图
    //        let noImageOrPlaceholderSet = base.image == nil && self.placeholder == nil
            //没有图片以及占位图或者非加载时显示当前图片(也就是默认显示占位图)
            if !options.keepCurrentImageWhileLoading {
                // Always set placeholder while there is no image/placeholder yet.
                mutatingSelf.placeholder = placeholder
            }
            
            
            if  base.image == nil && self.placeholder == nil {
                // Always set placeholder while there is no image/placeholder yet.
                mutatingSelf.placeholder = placeholder
            }
            
            
    
            //加载动画
            let maybeIndicator = indicator
            maybeIndicator?.startAnimatingView()
    
            //任务id,取前一任务的下一id为当前任务的id
            let issuedIdentifier = Source.Identifier.next()
            mutatingSelf.taskIdentifier = issuedIdentifier
    
            //预加载动画
            if base.shouldPreloadAllAnimation() {
                options.preloadAllAnimationData = true
            }
            
        
            //进度block
            if let block = progressBlock {
                options.onDataReceived = (options.onDataReceived ?? []) + [ImageLoadingProgressSideEffect(block)]
            }
    
            //进度provider
            if let provider = ImageProgressiveProvider(options, refresh: { image in
                self.base.image = image
            }) {
                options.onDataReceived = (options.onDataReceived ?? []) + [provider]
            }
            
            options.onDataReceived?.forEach {
                $0.onShouldApply = { issuedIdentifier == self.taskIdentifier }
            }
    
            ////从内存、文件或网络获取url对应图片数据
            let task = KingfisherManager.shared.retrieveImage(
                with: source,
                options: options,
                downloadTaskUpdated: { mutatingSelf.imageTask = $0 },
                completionHandler: { result in
                    CallbackQueue.mainCurrentOrAsync.execute {
                        ////在主线程刷新界面
                        maybeIndicator?.stopAnimatingView()
                        guard issuedIdentifier == self.taskIdentifier else {
                            let reason: KingfisherError.ImageSettingErrorReason
                            do {
                                let value = try result.get()
                                reason = .notCurrentSourceTask(result: value, error: nil, source: source)
                            } catch {
                                reason = .notCurrentSourceTask(result: nil, error: error, source: source)
                            }
                            let error = KingfisherError.imageSettingError(reason: reason)
                            completionHandler?(.failure(error))
                            return
                        }
                        
                        mutatingSelf.imageTask = nil
                        mutatingSelf.taskIdentifier = nil
                        
                        switch result {
                        case .success(let value):
                            guard self.needsTransition(options: options, cacheType: value.cacheType) else {
                                mutatingSelf.placeholder = nil
                                //真正的将图片显示出来,strongBase即是UIImageView
                                self.base.image = value.image
                                completionHandler?(result)
                                return
                            }
                            
                            self.makeTransition(image: value.image, transition: options.transition) {
                                completionHandler?(result)
                            }
                            
                        case .failure:
                            if let image = options.onFailureImage {
                                self.base.image = image
                            }
                            completionHandler?(result)
                        }
                    }
                }
            )
            mutatingSelf.imageTask = task
            return task
        }
    
    

    首先对source对判空处理,如果为空直接return,且当前流程结束,调用completionHandler回传错误信息

    然后根据配置的策略转化为配置项对象,这边的策略项都是通过枚举管理,然后在将枚举转化为策略管理类KingfisherParsedOptionsInfo,注意defaultOptions是默认的策略项,默认为空数组,当然我们也可以直接统一设置KingfisherManager.shared.defaultOptions默认的策略。然后获取当前的图片加载的策略,综合之后转化为图片加载的策略。
    接着根据策略处理placeholder的显示。

    后续设置加载动画、图片下载id、进度block处理等。

    接下载就是去处理图片的获取了

    KingfisherManager.swift
    
        private func retrieveImage(
            with source: Source,
            context: RetrievingContext,
            completionHandler: ((Result<RetrieveImageResult, KingfisherError>) -> Void)?) -> DownloadTask?
        {
            
            let options = context.options
            //强制刷新,网络下载图片
            if options.forceRefresh {
                return loadAndCacheImage(
                    source: source,
                    context: context,
                    completionHandler: completionHandler)?.value
                
            } else {
                //是否从内存或者文件中获取图片
                let loadedFromCache = retrieveImageFromCache(
                    source: source,
                    context: context,
                    completionHandler: completionHandler)
                
                //如果否z那么直接返回nil
                if loadedFromCache {
                    return nil
                }
                
                if options.onlyFromCache {
                    let error = KingfisherError.cacheError(reason: .imageNotExisting(key: source.cacheKey))
                    completionHandler?(.failure(error))
                    return nil
                }
                
                return loadAndCacheImage(
                    source: source,
                    context: context,
                    completionHandler: completionHandler)?.value
            }
        }
    

    根据策略判断是否需要从缓存中去图片和是否需要去下载图片。同时会将options以及source配置为context。还可以在发起请求之前处理request对象。
    在通过url从现有的下载任务字典中判断当前同一url的下载任务是否已经存在,如果存在那么直接取出来当前的下载任务。

    之后会在ImageDownloader中下载图片。

    open func downloadImage(
            with url: URL,
            options: KingfisherParsedOptionsInfo,
            completionHandler: ((Result<ImageLoadingResult, KingfisherError>) -> Void)? = nil) -> DownloadTask?
        {
            // Creates default request.
            var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: downloadTimeout)
            request.httpShouldUsePipelining = requestsUsePipelining
    
            //发起请求前重组request对象
            if let requestModifier = options.requestModifier {
                // Modifies request before sending.
                guard let r = requestModifier.modified(for: request) else {
                    options.callbackQueue.execute {
                        completionHandler?(.failure(KingfisherError.requestError(reason: .emptyRequest)))
                    }
                    return nil
                }
                request = r
            }
            
            // There is a possibility that request modifier changed the url to `nil` or empty.
            // In this case, throw an error.
            //校验url是否合法
            guard let url = request.url, !url.absoluteString.isEmpty else {
                options.callbackQueue.execute {
                    completionHandler?(.failure(KingfisherError.requestError(reason: .invalidURL(request: request))))
                }
                return nil
            }
    
            // Wraps `completionHandler` to `onCompleted` respectively.
            
    
            //将completionHandler转为callback
            let onCompleted = completionHandler.map {
                block -> Delegate<Result<ImageLoadingResult, KingfisherError>, Void> in
                //delegate接受Input:Result<ImageLoadingResult, KingfisherError>, 输出Void
                let delegate =  Delegate<Result<ImageLoadingResult, KingfisherError>, Void>()
                delegate.delegate(on: self) { (_, callback) in
                    //这里的call其实就是Result<ImageLoadingResult, KingfisherError>
                    block(callback)
                }
                return delegate
            }
    
            // SessionDataTask.TaskCallback is a wrapper for `onCompleted` and `options` (for processor info)
            let callback = SessionDataTask.TaskCallback(
                onCompleted: onCompleted,
                options: options
            )
    
            // Ready to start download. Add it to session task manager (`sessionHandler`)
    
            //配置下载任务
            let downloadTask: DownloadTask
            //判断当前的url是否已经存在下载任务,如果已存在那么直接取出当前的task组装成新的下载r任务
            if let existingTask = sessionDelegate.task(for: url) {
                downloadTask = sessionDelegate.append(existingTask, url: url, callback: callback)
            } else {
                //否则根据requestx创建下载任务
                let sessionDataTask = session.dataTask(with: request) as URLSessionDataTask
                sessionDataTask.priority = options.downloadPriority
                downloadTask = sessionDelegate.add(sessionDataTask, url: url, callback: callback)
            }
    
            //拿到下载任务对应的会话任务
            let sessionTask = downloadTask.sessionTask
    
            // Start the session task if not started yet.
            //如果会话任务还未开启,那么开启任务
            if !sessionTask.started {
                //这里的done对应的就是Input,即元组(Result,[CallBack]),在图片下载完成后会调用该方法返回图片数据
                sessionTask.onTaskDone.delegate(on: self) { (self, done) in
                    // Underlying downloading finishes.
                    // result: Result<(Data, URLResponse?)>, callbacks: [TaskCallback]
                    let (result, callbacks) = done
    
                    // Before processing the downloaded data.
                    do {
                        let value = try result.get()
                        self.delegate?.imageDownloader(
                            self,
                            didFinishDownloadingImageForURL: url,
                            with: value.1,
                            error: nil
                        )
                    } catch {
                        self.delegate?.imageDownloader(
                            self,
                            didFinishDownloadingImageForURL: url,
                            with: nil,
                            error: error
                        )
                    }
    
                    switch result {
                    // Download finished. Now process the data to an image.
                    case .success(let (data, response)):
                        let processor = ImageDataProcessor(
                            data: data, callbacks: callbacks, processingQueue: options.processingQueue)
                        processor.onImageProcessed.delegate(on: self) { (self, result) in
                            // `onImageProcessed` will be called for `callbacks.count` times, with each
                            // `SessionDataTask.TaskCallback` as the input parameter.
                            // result: Result<Image>, callback: SessionDataTask.TaskCallback
                            let (result, callback) = result
    
                            if let image = try? result.get() {
                                self.delegate?.imageDownloader(self, didDownload: image, for: url, with: response)
                            }
    
                            let imageResult = result.map { ImageLoadingResult(image: $0, url: url, originalData: data) }
                            let queue = callback.options.callbackQueue
                            queue.execute { callback.onCompleted?.call(imageResult) }
                        }
                        //对data数据做处理,转化为Result<Image>
                        processor.process()
    
                    case .failure(let error):
                        callbacks.forEach { callback in
                            let queue = callback.options.callbackQueue
                            queue.execute { callback.onCompleted?.call(.failure(error)) }
                        }
                    }
                }
                delegate?.imageDownloader(self, willDownloadImageForURL: url, with: request)
                sessionTask.resume()
            }
            return downloadTask
        }
    

    注意上边会将completionHandler重组为callback,在后面拿到图片数据后通过callback将图片数据传递回去。

    可以看到sessionTask.started在下载会话未开始的情况下调用sessionTask.resume()开启下载任务,图片的下载处理在类SessionDelegate中实现,里边实现了系统URLSessionDataDelegate数据下载的代理方法,最终在结束的时候调用

    sessionTask.onTaskDone.call((result, sessionTask.callbacks))
    

    将下载的数据回传,也就是我们在上面方法中的block中的获取的done

    sessionTask.onTaskDone.delegate(on: self) { 
      (self, done) in
    
    }
    

    这里的result是swift5中Result<Success,Failure>类型,所以在result.get()时使用了try catch做了容错处理,同时去调了代理方法。
    后面在成功的switch中处理图片数据,转为ImageLoadingResult对象,在通过callback方式将数据回传至KingfisherManager中,然后再去做缓存方面的处理。

    相关文章

      网友评论

          本文标题:Kingfisher源码分析--下载图片

          本文链接:https://www.haomeiwen.com/subject/qeqfzctx.html