美文网首页
Kingfisher3.x的分析与学习(三)

Kingfisher3.x的分析与学习(三)

作者: hoggenWang | 来源:发表于2017-03-27 16:47 被阅读135次

    本文的内容主要以代码为主,分析和学习了主要的类,在学习过程中添加相关的中文注释,重要且利于理解的英文注释也保留;如果有理解不对的地方,请告诉我。

    ImageCache

    相关属性

    //Memory
        fileprivate let memoryCache = NSCache<NSString, AnyObject>()
        ///最大内存缓存,默认不限
        open var maxMemoryCost: UInt = 0 {
            didSet {
                self.memoryCache.totalCostLimit = Int(maxMemoryCost)
            }
        }
        
        //Disk
        //ioQueue:dispatch_queue_t 为单独的硬盘操作队列,由于硬盘存取操作极为耗时,使其与主线程并行执行以免造成阻塞。
        fileprivate let ioQueue: DispatchQueue
        //文件管理
        fileprivate var fileManager: FileManager!
        
        ///The disk cache location.
        //路径
        open let diskCachePath: String
      
        /// The default file extension appended to cached files.
        open var pathExtension: String?
        
        /// The longest time duration in second of the cache being stored in disk. 
        /// Default is 1 week (60 * 60 * 24 * 7 seconds).
        /// Setting this to a negative value will make the disk cache never expiring.
        //默认最长存储时间
        open var maxCachePeriodInSecond: TimeInterval = 60 * 60 * 24 * 7 //Cache exists for 1 week
        
        /// The largest disk size can be taken for the cache. It is the total 
        /// allocated size of cached files in bytes.
        /// Default is no limit.
        open var maxDiskCacheSize: UInt = 0
        //执行图片的 decode 操作
        fileprivate let processQueue: DispatchQueue
        
        /// The default cache.单例
        public static let `default` = ImageCache(name: "default")
    

    图片的存取与移除

        // MARK: - Store & Remove
    
        /**
        Store an image to cache. It will be saved to both memory and disk. It is an async operation.
        
        - parameter image:             The image to be stored.
        - parameter original:          The original data of the image.
                                       Kingfisher will use it to check the format of the image and optimize cache size on disk.
                                       If `nil` is supplied, the image data will be saved as a normalized PNG file.
                                       It is strongly suggested to supply it whenever possible, to get a better performance and disk usage.
        - parameter key:               Key for the image.
        - parameter identifier:        The identifier of processor used. If you are using a processor for the image, pass the identifier of
                                       processor to it.
                                       This identifier will be used to generate a corresponding key for the combination of `key` and processor.
        - parameter toDisk:            Whether this image should be cached to disk or not. If false, the image will be only cached in memory.
        - parameter completionHandler: Called when store operation completes.
        */
        open func store(_ image: Image,
                          original: Data? = nil,
                          forKey key: String,
                          processorIdentifier identifier: String = "",
                          cacheSerializer serializer: CacheSerializer = DefaultCacheSerializer.default,
                          toDisk: Bool = true,
                          completionHandler: (() -> Void)? = nil)
        {
            //内存缓存
            let computedKey = key.computedKey(with: identifier)
            memoryCache.setObject(image, forKey: computedKey as NSString, cost: image.kf.imageCost)
    
            func callHandlerInMainQueue() {
                if let handler = completionHandler {
                    DispatchQueue.main.async {
                        handler()
                    }
                }
            }
            
            if toDisk {
                ioQueue.async {
                    if let data = serializer.data(with: image, original: original) {
                        //不存在 self.diskCachePath文件,即创建
                        if !self.fileManager.fileExists(atPath: self.diskCachePath) {
                            do {
            
                                try self.fileManager.createDirectory(atPath: self.diskCachePath, withIntermediateDirectories: true, attributes: nil)
                            } catch _ {}
                        }
                        
                        self.fileManager.createFile(atPath: self.cachePath(forComputedKey: computedKey), contents: data, attributes: nil)
                    }
                    callHandlerInMainQueue()
                }
            } else {
                callHandlerInMainQueue()
            }
        }
        
        
            open func removeImage(forKey key: String,
                              processorIdentifier identifier: String = "",
                              fromDisk: Bool = true,
                              completionHandler: (() -> Void)? = nil)
        {
            let computedKey = key.computedKey(with: identifier)
            memoryCache.removeObject(forKey: computedKey as NSString)
            
            func callHandlerInMainQueue() {
                if let handler = completionHandler {
                    DispatchQueue.main.async {
                        handler()
                    }
                }
            }
            
            if fromDisk {
                ioQueue.async{
                    do {
                        try self.fileManager.removeItem(atPath: self.cachePath(forComputedKey: computedKey))
                    } catch _ {}
                    callHandlerInMainQueue()
                }
            } else {
                callHandlerInMainQueue()
            }
        }
    
    

    从内存或磁盘中获取image

       open func retrieveImage(forKey key: String,
                                   options: KingfisherOptionsInfo?,
                         completionHandler: ((Image?, CacheType) -> ())?) -> RetrieveImageDiskTask?
        {
            // No completion handler. Not start working and early return.
            guard let completionHandler = completionHandler else {
                return nil
            }
            //类似于dispatch_block_t
            var block: RetrieveImageDiskTask?
            let options = options ?? KingfisherEmptyOptionsInfo
            
            if let image = self.retrieveImageInMemoryCache(forKey: key, options: options) {
                options.callbackDispatchQueue.safeAsync {
                    completionHandler(image, .memory)
                }
            } else {
                var sSelf: ImageCache! = self
                block = DispatchWorkItem(block: {
                    // Begin to load image from disk
                    if let image = sSelf.retrieveImageInDiskCache(forKey: key, options: options) {
                        //获取到image
                        if options.backgroundDecode {
                            sSelf.processQueue.async {
                                let result = image.kf.decoded(scale: options.scaleFactor)
                                
                                sSelf.store(result,
                                            forKey: key,
                                            processorIdentifier: options.processor.identifier,
                                            cacheSerializer: options.cacheSerializer,
                                            toDisk: false,
                                            completionHandler: nil)
                                
                                options.callbackDispatchQueue.safeAsync {
                                    completionHandler(result, .memory)
                                    sSelf = nil
                                }
                            }
                        } else {
                            sSelf.store(image,
                                        forKey: key,
                                        processorIdentifier: options.processor.identifier,
                                        cacheSerializer: options.cacheSerializer,
                                        toDisk: false,
                                        completionHandler: nil
                            )
                            options.callbackDispatchQueue.safeAsync {
                                completionHandler(image, .disk)
                                sSelf = nil
                            }
                        }
                    } else {
                        // No image found from either memory or disk
                        options.callbackDispatchQueue.safeAsync {
                            completionHandler(nil, .none)
                            sSelf = nil
                        }
                    }
                })
                
                sSelf.ioQueue.async(execute: block!)
            }
        
            return block
        }
    

    清除磁盘缓存

        open func clearDiskCache(completion handler: (()->())? = nil) {
            ioQueue.async {
                do {
                    try self.fileManager.removeItem(atPath: self.diskCachePath)
                    try self.fileManager.createDirectory(atPath: self.diskCachePath, withIntermediateDirectories: true, attributes: nil)
                } catch _ { }
                
                if let handler = handler {
                    DispatchQueue.main.async {
                        handler()
                    }
                }
            }
        }
        
    

    清楚过期或者超出存储范围的缓存files

        /**
        Clean expired disk cache. This is an async operation.//清除过期磁盘上信息
        */
        @objc fileprivate func cleanExpiredDiskCache() {
            cleanExpiredDiskCache(completion: nil)
        }
        
        /**
        Clean expired disk cache. This is an async operation.
        
        - parameter completionHandler: Called after the operation completes.
        */
        open func cleanExpiredDiskCache(completion handler: (()->())? = nil) {
            
            // Do things in cocurrent io queue
            ioQueue.async {
                // 拿到过期需要删除的urlsToDelete数组,diskCacheSize磁盘缓存大小和cachedFiles字典
                var (URLsToDelete, diskCacheSize, cachedFiles) = self.travelCachedFiles(onlyForCacheSize: false)
                
                for fileURL in URLsToDelete {
                    do {
                        try self.fileManager.removeItem(at: fileURL)
                    } catch _ { }
                }
                // 磁盘缓存大小超过自定义最大缓存
                if self.maxDiskCacheSize > 0 && diskCacheSize > self.maxDiskCacheSize {
                    //计划清除到最大缓存的一半
                    let targetSize = self.maxDiskCacheSize / 2
                        
                    // Sort files by last modify date. We want to clean from the oldest files.
                    //清除最老未改变的files
                    //降序
                    let sortedFiles = cachedFiles.keysSortedByValue {
                        resourceValue1, resourceValue2 -> Bool in
                        
                        if let date1 = resourceValue1.contentAccessDate,
                           let date2 = resourceValue2.contentAccessDate
                        {
                            return date1.compare(date2) == .orderedAscending
                        }
                        
                        // Not valid date information. This should not happen. Just in case.
                        return true
                    }
                    //删除files直到小于最大缓存的一半结束
                    for fileURL in sortedFiles {
                        
                        do {
                            try self.fileManager.removeItem(at: fileURL)
                        } catch { }
                            
                        URLsToDelete.append(fileURL)
                        
                        if let fileSize = cachedFiles[fileURL]?.totalFileAllocatedSize {
                            diskCacheSize -= UInt(fileSize)
                        }
                        
                        if diskCacheSize < targetSize {
                            break
                        }
                    }
                }
                    
                DispatchQueue.main.async {
                    
                    if URLsToDelete.count != 0 {
                        let cleanedHashes = URLsToDelete.map { $0.lastPathComponent }
                        NotificationCenter.default.post(name: .KingfisherDidCleanDiskCache, object: self, userInfo: [KingfisherDiskCacheCleanedHashKey: cleanedHashes])
                    }
                    
                    handler?()
                }
            }
        }
        //获取过期的URL数组,磁盘缓存大小和缓存文件字典, 进行缓存删除操作。 通过FileManager的enumerator方法遍历出所有缓存文件,如果文件最后一次访问日期比当前时间减去一周时间还要早,将该文件fileUrl添加到urlsToDelete数组。计算缓存文件大小,以fileUrl为key,resourceValues为value,存入 cachedFiles
        fileprivate func travelCachedFiles(onlyForCacheSize: Bool) -> (urlsToDelete: [URL], diskCacheSize: UInt, cachedFiles: [URL: URLResourceValues]) {
            
            let diskCacheURL = URL(fileURLWithPath: diskCachePath)
            let resourceKeys: Set<URLResourceKey> = [.isDirectoryKey, .contentAccessDateKey, .totalFileAllocatedSizeKey]
            //过期日期
            let expiredDate: Date? = (maxCachePeriodInSecond < 0) ? nil : Date(timeIntervalSinceNow: -maxCachePeriodInSecond)
            //// 缓存字典 URL : ResourceValue
            var cachedFiles = [URL: URLResourceValues]()
            var urlsToDelete = [URL]()
            var diskCacheSize: UInt = 0
    
            for fileUrl in (try? fileManager.contentsOfDirectory(at: diskCacheURL, includingPropertiesForKeys: Array(resourceKeys), options: .skipsHiddenFiles)) ?? [] {
    
                do {
                    let resourceValues = try fileUrl.resourceValues(forKeys: resourceKeys)
                    // If it is a Directory. Continue to next file URL.
                    if resourceValues.isDirectory == true {
                        continue
                    }
    
                    // If this file is expired, add it to URLsToDelete
                    if !onlyForCacheSize,
                        let expiredDate = expiredDate,
                        let lastAccessData = resourceValues.contentAccessDate,
                        (lastAccessData as NSDate).laterDate(expiredDate) == expiredDate
                    {
                        ////添加过期URL到删除数组
                        urlsToDelete.append(fileUrl)
                        continue
                    }
    
                    if let fileSize = resourceValues.totalFileAllocatedSize {
                        //
                        diskCacheSize += UInt(fileSize)
                        if !onlyForCacheSize {
                            //构建字典
                            cachedFiles[fileUrl] = resourceValues
                        }
                    }
                } catch _ { }
            }
    
            return (urlsToDelete, diskCacheSize, cachedFiles)
        }
    

    imageDownloader

        //作用ImageDownloader往往要处理多个URL的下载任务,它的fetchLoads属性是一个[URL: ImageFetchLoad]类型的字典,存储不同 URL 及其 ImageFetchLoad 之间的对应关系
        class ImageFetchLoad {
            //嵌套包含进度和完成回调
            var contents = [(callback: CallbackPair, options: KingfisherOptionsInfo)]()
            //数据存储
            var responseData = NSMutableData()
    
            var downloadTaskCount = 0
            var downloadTask: RetrieveImageDownloadTask?
        }
        
           //根据URL获取ImageFetchLoad 的方法
        func fetchLoad(for url: URL) -> ImageFetchLoad? {
            var fetchLoad: ImageFetchLoad?
            barrierQueue.sync { fetchLoad = fetchLoads[url] }
            return fetchLoad
        }
        
    

    这是外部调用ImageDownloader最常用的方法 配置好请求参数:Time 、URL、 URLRequest ,确保请求的前提条件 主要是setup方法

        func downloadImage(with url: URL,
                  retrieveImageTask: RetrieveImageTask?,
                            options: KingfisherOptionsInfo?,
                      progressBlock: ImageDownloaderProgressBlock?,
                  completionHandler: ImageDownloaderCompletionHandler?) -> RetrieveImageDownloadTask?
        {
            if let retrieveImageTask = retrieveImageTask, retrieveImageTask.cancelledBeforeDownloadStarting {
                completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.downloadCancelledBeforeStarting.rawValue, userInfo: nil), nil, nil)
                return nil
            }
            
            let timeout = self.downloadTimeout == 0.0 ? 15.0 : self.downloadTimeout
            
            // We need to set the URL as the load key. So before setup progress, we need to ask the `requestModifier` for a final URL.
            var request = URLRequest(url: url, cachePolicy: .reloadIgnoringLocalCacheData, timeoutInterval: timeout)
            request.httpShouldUsePipelining = requestsUsePipeling
    
            if let modifier = options?.modifier {
                guard let r = modifier.modified(for: request) else {
                    completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.downloadCancelledBeforeStarting.rawValue, userInfo: nil), nil, nil)
                    return nil
                }
                request = r
            }
            
            // There is a possiblility that request modifier changed the url to `nil` or empty.
            guard let url = request.url, !url.absoluteString.isEmpty else {
                completionHandler?(nil, NSError(domain: KingfisherErrorDomain, code: KingfisherError.invalidURL.rawValue, userInfo: nil), nil, nil)
                return nil
            }
            
            var downloadTask: RetrieveImageDownloadTask?
            ////根据传过来的fetchLoad 是否开启下载任务。若没有根据session 生成 dataTask,在进一步包装成RetrieveImageDownloadTask,传给fetchLoad的downloadTask属性 配置好任务优先级,开启下载任务,如果已开启下载,下载次数加1,设置传给外部的retrieveImageTask的downloadTask
            setup(progressBlock: progressBlock, with: completionHandler, for: url, options: options) {(session, fetchLoad) -> Void in
                if fetchLoad.downloadTask == nil {
                    let dataTask = session.dataTask(with: request)
                    ////设置下载任务
                    fetchLoad.downloadTask = RetrieveImageDownloadTask(internalTask: dataTask, ownerDownloader: self)
                     //设置下载任务优先级
                    dataTask.priority = options?.downloadPriority ?? URLSessionTask.defaultPriority
                    ////开启下载任务
                    dataTask.resume()
                    
                    // Hold self while the task is executing.
                    //下载期间确保sessionHandler 持有 ImageDownloader
                    self.sessionHandler.downloadHolder = self
                }
                //下载次数加1
                fetchLoad.downloadTaskCount += 1
                downloadTask = fetchLoad.downloadTask
                
                retrieveImageTask?.downloadTask = downloadTask
            }
            return downloadTask
        }
        
            func setup(progressBlock: ImageDownloaderProgressBlock?, with completionHandler: ImageDownloaderCompletionHandler?, for url: URL, options: KingfisherOptionsInfo?, started: ((URLSession, ImageFetchLoad) -> Void)) {
            //首先barrierQueue.sync 确保ImageFetchLoad 读写安全,根据传入的URL获取对应的ImageFetchLoad 设置callbackPair并更新contents ,开启下载
            barrierQueue.sync(flags: .barrier) {
                let loadObjectForURL = fetchLoads[url] ?? ImageFetchLoad()
                let callbackPair = (progressBlock: progressBlock, completionHandler: completionHandler)
                
                loadObjectForURL.contents.append((callbackPair, options ?? KingfisherEmptyOptionsInfo))
                
                fetchLoads[url] = loadObjectForURL
                
                if let session = session {
                    started(session, loadObjectForURL)
                }
            }
        }
    

    取消下载

        func cancelDownloadingTask(_ task: RetrieveImageDownloadTask) {
            barrierQueue.sync {
                if let URL = task.internalTask.originalRequest?.url, let imageFetchLoad = self.fetchLoads[URL] {
                    //更新下载次数
                    imageFetchLoad.downloadTaskCount -= 1
                    if imageFetchLoad.downloadTaskCount == 0 {
                        task.internalTask.cancel()
                    }
                }
            }
        }
        
    

    NSURLSessionDataDelegate,下载生命周期操作

        //下载过程中接收Response
        func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive response: URLResponse, completionHandler: @escaping (URLSession.ResponseDisposition) -> Void) {
            ////下载过程中确保ImageDownloader 一直持有
            guard let downloader = downloadHolder else {
                completionHandler(.cancel)
                return
            }
            ////返回状态码判断
            if let statusCode = (response as? HTTPURLResponse)?.statusCode,
               let url = dataTask.originalRequest?.url,
                !(downloader.delegate ?? downloader).isValidStatusCode(statusCode, for: downloader)
            {
                let error = NSError(domain: KingfisherErrorDomain,
                                    code: KingfisherError.invalidStatusCode.rawValue,
                                    userInfo: [KingfisherErrorStatusCodeKey: statusCode, NSLocalizedDescriptionKey: HTTPURLResponse.localizedString(forStatusCode: statusCode)])
                ////返回错误 首先清除ImageFetchLoad
                callCompletionHandlerFailure(error: error, url: url)
            }
            ////继续请求数据
            completionHandler(.allow)
        }
        //下载过程中接收到数据
        func urlSession(_ session: URLSession, dataTask: URLSessionDataTask, didReceive data: Data) {
    
            guard let downloader = downloadHolder else {
                return
            }
            //添加数据到指定ImageFetchLoad
            if let url = dataTask.originalRequest?.url, let fetchLoad = downloader.fetchLoad(for: url) {
                fetchLoad.responseData.append(data)
                //下载进度回调
                if let expectedLength = dataTask.response?.expectedContentLength {
                    for content in fetchLoad.contents {
                        DispatchQueue.main.async {
                            content.callback.progressBlock?(Int64(fetchLoad.responseData.length), expectedLength)
                        }
                    }
                }
            }
        }
        //下载结束
        func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) {
            //// URL 一致性判断
            guard let url = task.originalRequest?.url else {
                return
            }
            // error 判断
            guard error == nil else {
                callCompletionHandlerFailure(error: error!, url: url)
                return
            }
            //图片处理
            processImage(for: task, url: url)
        }
        
        /**
        This method is exposed since the compiler requests. Do not call it.
        */
        //会话需要认证
        func urlSession(_ session: URLSession, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
            guard let downloader = downloadHolder else {
                return
            }
            
            downloader.authenticationChallengeResponder?.downloader(downloader, didReceive: challenge, completionHandler: completionHandler)
        }
        
        private func cleanFetchLoad(for url: URL) {
            guard let downloader = downloadHolder else {
                return
            }
            
            downloader.clean(for: url)
            
            if downloader.fetchLoads.isEmpty {
                downloadHolder = nil
            }
        }
        //返回错误信息
        private func callCompletionHandlerFailure(error: Error, url: URL) {
            guard let downloader = downloadHolder, let fetchLoad = downloader.fetchLoad(for: url) else {
                return
            }
            
            // We need to clean the fetch load first, before actually calling completion handler.
            cleanFetchLoad(for: url)
            
            for content in fetchLoad.contents {
                content.options.callbackDispatchQueue.safeAsync {
                    content.callback.completionHandler?(nil, error as NSError, url, nil)
                }
            }
        }
    

    图片数据处理

        private func processImage(for task: URLSessionTask, url: URL) {
    
            guard let downloader = downloadHolder else {
                return
            }
            
            // We are on main queue when receiving this.
            downloader.processQueue.async {
                
                guard let fetchLoad = downloader.fetchLoad(for: url) else {
                    return
                }
                //首先清除ImageDownloader
                self.cleanFetchLoad(for: url)
                
                let data = fetchLoad.responseData as Data
                
                // Cache the processed images. So we do not need to re-process the image if using the same processor.
                // Key is the identifier of processor.
                var imageCache: [String: Image] = [:]
                for content in fetchLoad.contents {
                    
                    let options = content.options
                    let completionHandler = content.callback.completionHandler
                    let callbackQueue = options.callbackDispatchQueue
                    
                    let processor = options.processor
                    
                    var image = imageCache[processor.identifier]
                    if image == nil {
                        //合成图片
                        image = processor.process(item: .data(data), options: options)
                        
                        // Add the processed image to cache. 
                        // If `image` is nil, nothing will happen (since the key is not existing before).
                        imageCache[processor.identifier] = image
                    }
                    
                    if let image = image {
                        
                        downloader.delegate?.imageDownloader(downloader, didDownload: image, for: url, with: task.response)
                        //后台编码
                        if options.backgroundDecode {
                            let decodedImage = image.kf.decoded(scale: options.scaleFactor)
                            callbackQueue.safeAsync { completionHandler?(decodedImage, nil, url, data) }
                        } else {
                            callbackQueue.safeAsync { completionHandler?(image, nil, url, data) }
                        }
                        
                    } else {
                         // 304 状态码 没有图像数据下载
                        if let res = task.response as? HTTPURLResponse , res.statusCode == 304 {
                            let notModified = NSError(domain: KingfisherErrorDomain, code: KingfisherError.notModified.rawValue, userInfo: nil)
                            completionHandler?(nil, notModified, url, nil)
                            continue
                        }
                         //返回不是图片数据 或者数据被破坏
                        let badData = NSError(domain: KingfisherErrorDomain, code: KingfisherError.badData.rawValue, userInfo: nil)
                        callbackQueue.safeAsync { completionHandler?(nil, badData, url, nil) }
                    }
                }
            }
        }
    }
    

    相关文章

      网友评论

          本文标题:Kingfisher3.x的分析与学习(三)

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