2
0
Эх сурвалжийг харах

优化options设置, 增加是否模糊处理, 是否等待扫描完成, 是否快速扫描, 扫描最小间隔等设置, 增加Provider类强化封装 重构相关扩展, 并完善Demo页面

lixiang1994 6 жил өмнө
parent
commit
799b4d15f8

+ 44 - 5
Demo/Demo/Kingfisher-Demo/ViewControllers/ProgressiveJPEGViewController.swift

@@ -33,7 +33,11 @@ class ProgressiveJPEGViewController: UIViewController {
     @IBOutlet weak var progressLabel: UILabel!
     
     private var isBlur = true
+    private var isWait = true
+    private var isFastestScan = true
+    
     private let url = URL(string: "https://demo-resources.oss-cn-beijing.aliyuncs.com/progressive.jpg")!
+    private let processor = RoundCornerImageProcessor(cornerRadius: 30)
     
     override func viewDidLoad() {
         super.viewDidLoad()
@@ -45,10 +49,19 @@ class ProgressiveJPEGViewController: UIViewController {
     private func loadImage() {
         progressLabel.text = "- / -"
         
+        let progressive = ImageProgressive(
+            isBlur: isBlur,
+            isWait: isWait,
+            isFastestScan: isFastestScan,
+            scanInterval: 0.1
+        )
+        
         imageView.kf.setImage(
             with: url,
             placeholder: nil,
-            options: [.loadDiskFileSynchronously, .progressiveJPEG(isBlur)],
+            options: [.loadDiskFileSynchronously,
+                      .progressiveJPEG(progressive),
+                      .processor(processor)],
             progressBlock: { receivedSize, totalSize in
                 print("\(receivedSize)/\(totalSize)")
                 self.progressLabel.text = "\(receivedSize) / \(totalSize)"
@@ -62,18 +75,44 @@ class ProgressiveJPEGViewController: UIViewController {
     
     override func alertPopup(_ sender: Any) -> UIAlertController {
         let alert = super.alertPopup(sender)
-        let title = isBlur ? "Close Blur" : "Enabled Blur"
-        alert.addAction(UIAlertAction(title: title, style: .default) { _ in
-            self.isBlur.toggle()
+        
+        func reloadImage() {
+            // Cancel
+            imageView.kf.cancelDownloadTask()
             // Clean cache
             KingfisherManager.shared.cache.removeImage(
                 forKey: self.url.cacheKey,
+                processorIdentifier: self.processor.identifier,
                 callbackQueue: .mainAsync,
                 completionHandler: {
                     self.loadImage()
                 }
             )
-        })
+        }
+        
+        do {
+            let title = isBlur ? "Close Blur" : "Enabled Blur"
+            alert.addAction(UIAlertAction(title: title, style: .default) { _ in
+                self.isBlur.toggle()
+                reloadImage()
+            })
+        }
+        
+        do {
+            let title = isWait ? "Close Wait" : "Enabled Wait"
+            alert.addAction(UIAlertAction(title: title, style: .default) { _ in
+                self.isWait.toggle()
+                reloadImage()
+            })
+        }
+        
+        do {
+            let title = isFastestScan ? "Close Fastest Scan" : "Enabled Fastest Scan"
+            alert.addAction(UIAlertAction(title: title, style: .default) { _ in
+                self.isFastestScan.toggle()
+                reloadImage()
+            })
+        }
         return alert
     }
 }

+ 2 - 0
Kingfisher.xcodeproj/project.pbxproj

@@ -306,6 +306,7 @@
 		6CD5C0134AA4B1C0892E7319 /* Pods-KingfisherTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KingfisherTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-KingfisherTests/Pods-KingfisherTests.release.xcconfig"; sourceTree = "<group>"; };
 		7204D40BEFEA059FA25864C4 /* Pods-KingfisherTests-macOS.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-KingfisherTests-macOS.debug.xcconfig"; path = "Pods/Target Support Files/Pods-KingfisherTests-macOS/Pods-KingfisherTests-macOS.debug.xcconfig"; sourceTree = "<group>"; };
 		C9286406228584EB00257182 /* ImageProgressive.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageProgressive.swift; sourceTree = "<group>"; };
+		C959EEE7228940FE00467A10 /* QuartzCore.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = QuartzCore.framework; path = System/Library/Frameworks/QuartzCore.framework; sourceTree = SDKROOT; };
 		CCDD057F8DA8D24EE701CF98 /* libPods-KingfisherTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-KingfisherTests.a"; sourceTree = BUILT_PRODUCTS_DIR; };
 		D10EC2311C3D632300A4211C /* KingfisherTests-macOS.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "KingfisherTests-macOS.xctest"; sourceTree = BUILT_PRODUCTS_DIR; };
 		D12AB69D215D2BB50013BA68 /* RequestModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RequestModifier.swift; sourceTree = "<group>"; };
@@ -662,6 +663,7 @@
 		EA99D30544BD22799F7A5367 /* Frameworks */ = {
 			isa = PBXGroup;
 			children = (
+				C959EEE7228940FE00467A10 /* QuartzCore.framework */,
 				4B164ACE1B8D554200768EC6 /* CFNetwork.framework */,
 				4B3E714D1B01FEB200F5AAED /* WatchKit.framework */,
 				CCDD057F8DA8D24EE701CF98 /* libPods-KingfisherTests.a */,

+ 33 - 34
Sources/Extensions/ImageView+Kingfisher.swift

@@ -105,34 +105,30 @@ extension KingfisherWrapper where Base: ImageView {
             options.preloadAllAnimationData = true
         }
         
-        let progressive = ImageProgressive(options)
-        
-        let dataUpdate = { (data: Data) in
-            guard
-                let data = progressive.scanning(data),
-                let callbacks = mutatingSelf.imageTask?.sessionTask.callbacks else {
-                return
-            }
-            progressive.decode(data, with: callbacks) { (image) in
-                guard mutatingSelf.imageTask != nil else { return }
-                
+        let progressive = ImageProgressiveProvider(
+            options,
+            isContinue: { () -> Bool in
+                issuedIdentifier == self.taskIdentifier
+            },
+            isFinished: { () -> Bool in
+                mutatingSelf.imageTask == nil
+            },
+            refreshImage: { (image) in
                 self.base.image = image
             }
-        }
+        )
         
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
             receivedBlock: { latest, received in
                 guard issuedIdentifier == self.taskIdentifier else { return }
-                
-                dataUpdate(received)
+                let callbacks = mutatingSelf.imageTask?.sessionTask.callbacks ?? []
+                progressive.update(data: received, with: callbacks)
             },
             progressBlock: { receivedSize, totalSize in
                 guard issuedIdentifier == self.taskIdentifier else { return }
-                if let progressBlock = progressBlock {
-                    progressBlock(receivedSize, totalSize)
-                }
+                progressBlock?(receivedSize, totalSize)
             },
             completionHandler: { result in
                 CallbackQueue.mainCurrentOrAsync.execute {
@@ -149,26 +145,29 @@ extension KingfisherWrapper where Base: ImageView {
                         completionHandler?(.failure(error))
                         return
                     }
-
+                    
                     mutatingSelf.imageTask = nil
-
-                    switch result {
-                    case .success(let value):
-                        guard self.needsTransition(options: options, cacheType: value.cacheType) else {
-                            mutatingSelf.placeholder = nil
-                            self.base.image = value.image
+                    
+                    progressive.finished {
+                        switch result {
+                        case .success(let value):
+                            guard self.needsTransition(options: options, cacheType: value.cacheType) else {
+                                mutatingSelf.placeholder = nil
+                                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)
-                            return
-                        }
-
-                        self.makeTransition(image: value.image, transition: options.transition) {
-                            completionHandler?(result)
-                        }
-                    case .failure:
-                        if let image = options.onFailureImage {
-                            self.base.image = image
                         }
-                        completionHandler?(result)
                     }
                 }
             }

+ 53 - 30
Sources/Extensions/NSButton+Kingfisher.swift

@@ -70,29 +70,27 @@ extension KingfisherWrapper where Base: NSButton {
 
         let issuedIdentifier = Source.Identifier.next()
         mutatingSelf.taskIdentifier = issuedIdentifier
-
-        let progressive = ImageProgressive(options)
         
-        let dataUpdate = { (data: Data) in
-            guard
-                let data = progressive.scanning(data),
-                let callbacks = mutatingSelf.imageTask?.sessionTask.callbacks else {
-                return
-            }
-            progressive.decode(data, with: callbacks) { (image) in
-                guard mutatingSelf.imageTask != nil else { return }
-                
+        let progressive = ImageProgressiveProvider(
+            options,
+            isContinue: { () -> Bool in
+                issuedIdentifier == self.taskIdentifier
+            },
+            isFinished: { () -> Bool in
+                mutatingSelf.imageTask != nil
+            },
+            refreshImage: { (image) in
                 self.base.image = image
             }
-        }
+        )
         
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
             receivedBlock: { latest, received in
                 guard issuedIdentifier == self.taskIdentifier else { return }
-                
-                dataUpdate(received)
+                let callbacks = mutatingSelf.imageTask?.sessionTask.callbacks ?? []
+                progressive.update(data: received, with: callbacks)
             },
             progressBlock: { receivedSize, totalSize in
                 guard issuedIdentifier == self.taskIdentifier else { return }
@@ -115,15 +113,18 @@ extension KingfisherWrapper where Base: NSButton {
 
                     mutatingSelf.imageTask = nil
 
-                    switch result {
-                    case .success(let value):
-                        self.base.image = value.image
-                        completionHandler?(result)
-                    case .failure:
-                        if let image = options.onFailureImage {
-                            self.base.image = image
+                    progressive.finished {
+                        switch result {
+                        case .success(let value):
+                            self.base.image = value.image
+                            completionHandler?(result)
+                            
+                        case .failure:
+                            if let image = options.onFailureImage {
+                                self.base.image = image
+                            }
+                            completionHandler?(result)
                         }
-                        completionHandler?(result)
                     }
                 }
             }
@@ -198,9 +199,28 @@ extension KingfisherWrapper where Base: NSButton {
 
         let issuedIdentifier = Source.Identifier.next()
         mutatingSelf.alternateTaskIdentifier = issuedIdentifier
+        
+        let progressive = ImageProgressiveProvider(
+            options,
+            isContinue: { () -> Bool in
+                issuedIdentifier == self.alternateTaskIdentifier
+            },
+            isFinished: { () -> Bool in
+                mutatingSelf.alternateImageTask != nil
+            },
+            refreshImage: { (image) in
+                self.base.alternateImage = image
+            }
+        )
+        
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
+            receivedBlock: { latest, received in
+                guard issuedIdentifier == self.alternateTaskIdentifier else { return }
+                let callbacks = mutatingSelf.alternateImageTask?.sessionTask.callbacks ?? []
+                progressive.update(data: received, with: callbacks)
+            },
             progressBlock: { receivedSize, totalSize in
                 guard issuedIdentifier == self.alternateTaskIdentifier else { return }
                 progressBlock?(receivedSize, totalSize)
@@ -222,15 +242,18 @@ extension KingfisherWrapper where Base: NSButton {
 
                     mutatingSelf.alternateImageTask = nil
 
-                    switch result {
-                    case .success(let value):
-                        self.base.alternateImage = value.image
-                        completionHandler?(result)
-                    case .failure:
-                        if let image = options.onFailureImage {
-                            self.base.alternateImage = image
+                    progressive.finished {
+                        switch result {
+                        case .success(let value):
+                            self.base.alternateImage = value.image
+                            completionHandler?(result)
+                            
+                        case .failure:
+                            if let image = options.onFailureImage {
+                                self.base.alternateImage = image
+                            }
+                            completionHandler?(result)
                         }
-                        completionHandler?(result)
                     }
                 }
             }

+ 57 - 32
Sources/Extensions/UIButton+Kingfisher.swift

@@ -71,28 +71,26 @@ extension KingfisherWrapper where Base: UIButton {
         let issuedTaskIdentifier = Source.Identifier.next()
         setTaskIdentifier(issuedTaskIdentifier, for: state)
         
-        let progressive = ImageProgressive(options)
-        
-        let dataUpdate = { (data: Data) in
-            guard
-                let data = progressive.scanning(data),
-                let callbacks = mutatingSelf.imageTask?.sessionTask.callbacks else {
-                return
-            }
-            progressive.decode(data, with: callbacks) { (image) in
-                guard mutatingSelf.imageTask != nil else { return }
-                
+        let progressive = ImageProgressiveProvider(
+            options,
+            isContinue: { () -> Bool in
+                issuedTaskIdentifier == self.taskIdentifier(for: state)
+            },
+            isFinished: { () -> Bool in
+                mutatingSelf.imageTask != nil
+            },
+            refreshImage: { (image) in
                 self.base.setImage(image, for: state)
             }
-        }
+        )
         
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
             receivedBlock: { latest, received in
                 guard issuedTaskIdentifier == self.taskIdentifier(for: state) else { return }
-                
-                dataUpdate(received)
+                let callbacks = mutatingSelf.imageTask?.sessionTask.callbacks ?? []
+                progressive.update(data: received, with: callbacks)
             },
             progressBlock: { receivedSize, totalSize in
                 guard issuedTaskIdentifier == self.taskIdentifier(for: state) else { return }
@@ -115,18 +113,22 @@ extension KingfisherWrapper where Base: UIButton {
                     
                     mutatingSelf.imageTask = nil
                     
-                    switch result {
-                    case .success(let value):
-                        self.base.setImage(value.image, for: state)
-                        completionHandler?(result)
-                    case .failure:
-                        if let image = options.onFailureImage {
-                            self.base.setImage(image, for: state)
+                    progressive.finished {
+                        switch result {
+                        case .success(let value):
+                            self.base.setImage(value.image, for: state)
+                            completionHandler?(result)
+                            
+                        case .failure:
+                            if let image = options.onFailureImage {
+                                self.base.setImage(image, for: state)
+                            }
+                            completionHandler?(result)
                         }
-                        completionHandler?(result)
                     }
                 }
-        })
+            }
+        )
         
         mutatingSelf.imageTask = task
         return task
@@ -218,9 +220,28 @@ extension KingfisherWrapper where Base: UIButton {
         var mutatingSelf = self
         let issuedTaskIdentifier = Source.Identifier.next()
         setBackgroundTaskIdentifier(issuedTaskIdentifier, for: state)
+        
+        let progressive = ImageProgressiveProvider(
+            options,
+            isContinue: { () -> Bool in
+                issuedTaskIdentifier == self.backgroundTaskIdentifier(for: state)
+            },
+            isFinished: { () -> Bool in
+                mutatingSelf.backgroundImageTask != nil
+            },
+            refreshImage: { (image) in
+                self.base.setBackgroundImage(image, for: state)
+            }
+        )
+        
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
+            receivedBlock: { latest, received in
+                guard issuedTaskIdentifier == self.backgroundTaskIdentifier(for: state) else { return }
+                let callbacks = mutatingSelf.backgroundImageTask?.sessionTask.callbacks ?? []
+                progressive.update(data: received, with: callbacks)
+            },
             progressBlock: { receivedSize, totalSize in
                 guard issuedTaskIdentifier == self.backgroundTaskIdentifier(for: state) else {
                     return
@@ -245,18 +266,22 @@ extension KingfisherWrapper where Base: UIButton {
                     }
                     mutatingSelf.backgroundImageTask = nil
 
-                    switch result {
-                    case .success(let value):
-                        self.base.setBackgroundImage(value.image, for: state)
-                        completionHandler?(result)
-                    case .failure:
-                        if let image = options.onFailureImage {
-                            self.base.setBackgroundImage(image, for: state)
+                    progressive.finished {
+                        switch result {
+                        case .success(let value):
+                            self.base.setBackgroundImage(value.image, for: state)
+                            completionHandler?(result)
+                            
+                        case .failure:
+                            if let image = options.onFailureImage {
+                                self.base.setBackgroundImage(image, for: state)
+                            }
+                            completionHandler?(result)
                         }
-                        completionHandler?(result)
                     }
                 }
-        })
+            }
+        )
 
         mutatingSelf.backgroundImageTask = task
         return task

+ 25 - 23
Sources/Extensions/WKInterfaceImage+Kingfisher.swift

@@ -71,28 +71,26 @@ extension KingfisherWrapper where Base: WKInterfaceImage {
         let issuedTaskIdentifier = Source.Identifier.next()
         mutatingSelf.taskIdentifier = issuedTaskIdentifier
         
-        let progressive = ImageProgressive(options)
-        
-        let dataUpdate = { (data: Data) in
-            guard
-                let data = progressive.scanning(data),
-                let callbacks = mutatingSelf.imageTask?.sessionTask.callbacks else {
-                    return
-            }
-            progressive.decode(data, with: callbacks) { (image) in
-                guard mutatingSelf.imageTask != nil else { return }
-                
+        let progressive = ImageProgressiveProvider(
+            options,
+            isContinue: { () -> Bool in
+                issuedTaskIdentifier == self.taskIdentifier
+            },
+            isFinished: { () -> Bool in
+                mutatingSelf.imageTask != nil
+            },
+            refreshImage: { (image) in
                 self.base.setImage(image)
             }
-        }
+        )
         
         let task = KingfisherManager.shared.retrieveImage(
             with: source,
             options: options,
             receivedBlock: { latest, received in
                 guard issuedTaskIdentifier == self.taskIdentifier else { return }
-                
-                dataUpdate(received)
+                let callbacks = mutatingSelf.imageTask?.sessionTask.callbacks ?? []
+                progressive.update(data: received, with: callbacks)
             },
             progressBlock: { receivedSize, totalSize in
                 guard issuedTaskIdentifier == self.taskIdentifier else { return }
@@ -115,18 +113,22 @@ extension KingfisherWrapper where Base: WKInterfaceImage {
                     
                     mutatingSelf.imageTask = nil
                     
-                    switch result {
-                    case .success(let value):
-                        self.base.setImage(value.image)
-                        completionHandler?(result)
-                    case .failure:
-                        if let image = options.onFailureImage {
-                            self.base.setImage(image)
+                    progressive.finished {
+                        switch result {
+                        case .success(let value):
+                            self.base.setImage(value.image)
+                            completionHandler?(result)
+                            
+                        case .failure:
+                            if let image = options.onFailureImage {
+                                self.base.setImage(image)
+                            }
+                            completionHandler?(result)
                         }
-                        completionHandler?(result)
                     }
                 }
-        })
+            }
+        )
         
         mutatingSelf.imageTask = task
         return task

+ 4 - 5
Sources/General/KingfisherOptionsInfo.swift

@@ -214,9 +214,8 @@ public enum KingfisherOptionsInfoItem {
     /// blocking the UI, especially if the processor needs a lot of time to run).
     case processingQueue(CallbackQueue)
     
-    /// Enable progressive image loading
-    /// true: Increase blur effect processing, false: No additional processing.
-    case progressiveJPEG(Bool)
+    /// Enable progressive image loading, Kingfisher will use the `ImageProgressive` of
+    case progressiveJPEG(ImageProgressive)
 }
 
 // Improve performance by parsing the input `KingfisherOptionsInfo` (self) first.
@@ -255,7 +254,7 @@ public struct KingfisherParsedOptionsInfo {
     public var memoryCacheExpiration: StorageExpiration? = nil
     public var diskCacheExpiration: StorageExpiration? = nil
     public var processingQueue: CallbackQueue? = nil
-    public var progressiveJPEG: Bool?
+    public var progressiveJPEG: ImageProgressive?
     
     public init(_ info: KingfisherOptionsInfo?) {
         guard let info = info else { return }
@@ -291,7 +290,7 @@ public struct KingfisherParsedOptionsInfo {
             case .memoryCacheExpiration(let expiration): memoryCacheExpiration = expiration
             case .diskCacheExpiration(let expiration): diskCacheExpiration = expiration
             case .processingQueue(let queue): processingQueue = queue
-            case .progressiveJPEG(let isBlur): progressiveJPEG = isBlur
+            case .progressiveJPEG(let value): progressiveJPEG = value
             }
         }
 

+ 192 - 5
Sources/Image/ImageProgressive.swift

@@ -30,17 +30,147 @@ import CoreGraphics
 private let sharedProcessingQueue: CallbackQueue =
     .dispatch(DispatchQueue(label: "com.onevcat.Kingfisher.ImageDownloader.Process"))
 
-final class ImageProgressive {
+public struct ImageProgressive {
+    
+    /// A default `ImageProgressive` could be used across.
+    public static let `default` = ImageProgressive(
+        isBlur: true,
+        isWait: false,
+        isFastestScan: true,
+        scanInterval: 0
+    )
+    
+    /// Whether to enable blur effect processing
+    let isBlur: Bool
+    /// Whether to wait for the scan to complete
+    let isWait: Bool
+    /// Whether to enable the fastest scan
+    let isFastestScan: Bool
+    /// Minimum time interval for each scan
+    let scanInterval: TimeInterval
+    
+    public init(isBlur: Bool,
+                isWait: Bool,
+                isFastestScan: Bool,
+                scanInterval: TimeInterval) {
+        self.isBlur = isBlur
+        self.isWait = isWait
+        self.isFastestScan = isFastestScan
+        self.scanInterval = scanInterval
+    }
+}
+
+final class ImageProgressiveProvider {
+    
+    private let options: KingfisherParsedOptionsInfo
+    private let refreshClosure: (Image) -> Void
+    private let isContinueClosure: () -> Bool
+    private let isFinishedClosure: () -> Bool
+    private let isWait: Bool
+    
+    private let decoder: ImageProgressiveDecoder
+    private let queue = ImageProgressiveSerialQueue(.main)
+    
+    init(_ options: KingfisherParsedOptionsInfo,
+         isContinue: @escaping () -> Bool,
+         isFinished: @escaping () -> Bool,
+         refreshImage: @escaping (Image) -> Void) {
+        self.options = options
+        self.refreshClosure = refreshImage
+        self.isContinueClosure = isContinue
+        self.isFinishedClosure = isFinished
+        self.decoder = ImageProgressiveDecoder(options)
+        self.isWait = options.progressiveJPEG?.isWait ?? false
+    }
+    
+    func update(data: Data, with callbacks: [SessionDataTask.TaskCallback]) {
+        let interval = options.progressiveJPEG?.scanInterval ?? 0
+        let isFastest = options.progressiveJPEG?.isFastestScan ?? true
+        
+        func add(decoder data: Data) {
+            queue.add(minimum: interval) { (completion) in
+                guard self.isContinueClosure() else {
+                    completion()
+                    return
+                }
+                
+                self.decoder.decode(data, with: callbacks) { (image) in
+                    defer { completion() }
+                    guard self.isContinueClosure() else { return }
+                    guard self.isWait || !self.isFinishedClosure() else { return }
+                    guard let image = image else { return }
+                    
+                    self.refreshClosure(image)
+                }
+            }
+        }
+        
+        if isFastest {
+            guard let data = decoder.scanning(data) else { return }
+            
+            add(decoder: data)
+            
+        } else {
+            for data in decoder.scanning(data) {
+                add(decoder: data)
+            }
+        }
+    }
+    
+    func finished(_ closure: @escaping () -> Void) {
+        if queue.count > 0, isWait {
+            queue.notify(closure)
+            
+        } else {
+            queue.clean()
+            closure()
+        }
+    }
+    
+    deinit {
+        print("deinit ImageProgressiveProvider")
+    }
+}
+
+final class ImageProgressiveDecoder {
     
     private let options: KingfisherParsedOptionsInfo
     private(set) var scannedCount: Int = 0
     private var scannedIndex = -1
-    private var lastSOSIndex = 0
     
     init(_ options: KingfisherParsedOptionsInfo) {
         self.options = options
     }
     
+    func scanning(_ data: Data) -> [Data] {
+        guard let _ = options.progressiveJPEG, data.kf.contains(jpeg: .SOF2) else {
+            return []
+        }
+        guard (scannedIndex + 1) < data.count else {
+            return []
+        }
+        
+        var datas: [Data] = []
+        var index = scannedIndex + 1
+        var count = scannedCount
+        
+        while index < (data.count - 1) {
+            scannedIndex = index
+            // 0xFF, 0xDA - Start Of Scan
+            let SOS = ImageFormat.JPEGMarker.SOS.bytes
+            if data[index] == SOS[0], data[index + 1] == SOS[1] {
+                datas.append(data[0 ..< index])
+                count += 1
+            }
+            index += 1
+        }
+        
+        // Found more scans this the previous time
+        guard count > scannedCount else { return [] }
+        scannedCount = count
+        return datas
+    }
+    
     func scanning(_ data: Data) -> Data? {
         guard let _ = options.progressiveJPEG, data.kf.contains(jpeg: .SOF2) else {
             return nil
@@ -51,6 +181,7 @@ final class ImageProgressive {
         
         var index = scannedIndex + 1
         var count = scannedCount
+        var lastSOSIndex = 0
         
         while index < (data.count - 1) {
             scannedIndex = index
@@ -74,8 +205,11 @@ final class ImageProgressive {
         return data[0 ..< lastSOSIndex]
     }
     
-    func decode(_ data: Data, with callbacks: [SessionDataTask.TaskCallback], completion: @escaping (Image) -> Void) {
-        guard let isBlur = options.progressiveJPEG, data.kf.contains(jpeg: .SOF2) else {
+    func decode(_ data: Data,
+                with callbacks: [SessionDataTask.TaskCallback],
+                completion: @escaping (Image?) -> Void) {
+        guard data.kf.contains(jpeg: .SOF2) else {
+            CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
             return
         }
         
@@ -86,7 +220,10 @@ final class ImageProgressive {
                 processingQueue: options.processingQueue
             )
             processor.onImageProcessed.delegate(on: self) { (self, result) in
-                guard let image = try? result.0.get() else { return }
+                guard let image = try? result.0.get() else {
+                    CallbackQueue.mainCurrentOrAsync.execute { completion(nil) }
+                    return
+                }
                 
                 CallbackQueue.mainCurrentOrAsync.execute { completion(image) }
             }
@@ -95,6 +232,8 @@ final class ImageProgressive {
         
         // Blur partial images.
         let count = scannedCount
+        let isBlur = options.progressiveJPEG?.isBlur ?? false
+        
         if isBlur, count < 5 {
             let queue = options.processingQueue ?? sharedProcessingQueue
             queue.execute {
@@ -113,3 +252,51 @@ final class ImageProgressive {
         }
     }
 }
+
+final class ImageProgressiveSerialQueue {
+    typealias ClosureCallback = ((@escaping () -> Void)) -> Void
+    private let queue: DispatchQueue
+    private var items: [DispatchWorkItem] = []
+    private var notify: (() -> Void)?
+    var count: Int {
+        return items.count
+    }
+    
+    init(_ queue: DispatchQueue) {
+        self.queue = queue
+    }
+    
+    func add(minimum interval: TimeInterval, closure: @escaping ClosureCallback) {
+        let completion = {
+            self.queue.async {
+                guard !self.items.isEmpty else { return }
+                
+                self.items.removeFirst()
+                
+                if let next = self.items.first {
+                    self.queue.asyncAfter(deadline: .now() + interval, execute: next)
+                    
+                } else {
+                    self.notify?()
+                    self.notify = nil
+                }
+            }
+        }
+        let item = DispatchWorkItem {
+            closure(completion)
+        }
+        if items.isEmpty {
+            queue.asyncAfter(deadline: .now(), execute: item)
+        }
+        items.append(item)
+    }
+    
+    func notify(_ closure: @escaping () -> Void) {
+        self.notify = closure
+    }
+    
+    func clean() {
+        items.forEach { $0.cancel() }
+        items.removeAll()
+    }
+}