From 54da484423ca69d1842903054445579c82c669d3 Mon Sep 17 00:00:00 2001 From: Riley Testut Date: Wed, 15 Mar 2017 13:07:34 -0600 Subject: [PATCH] =?UTF-8?q?Refactors=20loading=20operations=20to=20inherit?= =?UTF-8?q?=20from=20Roxas=E2=80=99=20RSTLoadOperation?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Delta.xcodeproj/project.pbxproj | 6 +- .../LoadControllerSkinImageOperation.swift | 33 ++++++- .../Loading/LoadImageOperation.swift | 69 --------------- .../Loading/LoadImageURLOperation.swift | 85 +++++++++++++++++-- .../SaveStatesViewController.swift | 4 +- .../ControllerSkinsViewController.swift | 6 +- External/Roxas | 2 +- 7 files changed, 116 insertions(+), 89 deletions(-) delete mode 100644 Delta/Components/Loading/LoadImageOperation.swift diff --git a/Delta.xcodeproj/project.pbxproj b/Delta.xcodeproj/project.pbxproj index dafb3cd..72de1c4 100644 --- a/Delta.xcodeproj/project.pbxproj +++ b/Delta.xcodeproj/project.pbxproj @@ -47,7 +47,6 @@ BF3540081C5DAFAD00C1184C /* PauseTransitionCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */; }; BF59425C1E09BB810051894B /* Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942581E09BB810051894B /* Action.swift */; }; BF5942641E09BBB10051894B /* LoadControllerSkinImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */; }; - BF5942651E09BBB10051894B /* LoadImageOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942621E09BBB10051894B /* LoadImageOperation.swift */; }; BF5942661E09BBB10051894B /* LoadImageURLOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */; }; BF59426A1E09BBD00051894B /* GridCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */; }; BF59426B1E09BBD00051894B /* GridCollectionViewLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */; }; @@ -153,7 +152,6 @@ BF3540071C5DAFAD00C1184C /* PauseTransitionCoordinator.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = PauseTransitionCoordinator.swift; path = "Pause Menu/Segues/PauseTransitionCoordinator.swift"; sourceTree = ""; }; BF5942581E09BB810051894B /* Action.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Action.swift; path = Components/Action.swift; sourceTree = ""; }; BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadControllerSkinImageOperation.swift; path = Components/Loading/LoadControllerSkinImageOperation.swift; sourceTree = ""; }; - BF5942621E09BBB10051894B /* LoadImageOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadImageOperation.swift; path = Components/Loading/LoadImageOperation.swift; sourceTree = ""; }; BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = LoadImageURLOperation.swift; path = Components/Loading/LoadImageURLOperation.swift; sourceTree = ""; }; BF5942681E09BBD00051894B /* GridCollectionViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridCollectionViewCell.swift; path = "Components/Collection View/GridCollectionViewCell.swift"; sourceTree = ""; }; BF5942691E09BBD00051894B /* GridCollectionViewLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = GridCollectionViewLayout.swift; path = "Components/Collection View/GridCollectionViewLayout.swift"; sourceTree = ""; }; @@ -306,9 +304,8 @@ BF5942601E09BBA80051894B /* Loading */ = { isa = PBXGroup; children = ( - BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */, - BF5942621E09BBB10051894B /* LoadImageOperation.swift */, BF5942631E09BBB10051894B /* LoadImageURLOperation.swift */, + BF5942611E09BBB10051894B /* LoadControllerSkinImageOperation.swift */, ); name = Loading; sourceTree = ""; @@ -752,7 +749,6 @@ BF13A7561D5D29B0000BB055 /* PreviewGameViewController.swift in Sources */, BF6866171DCAC8B900BF2D06 /* ControllerSkin+Configuring.swift in Sources */, BF59427D1E09BC830051894B /* ControllerSkin.swift in Sources */, - BF5942651E09BBB10051894B /* LoadImageOperation.swift in Sources */, BFCEA67E1D56FF640061A534 /* UIViewControllerContextTransitioning+Conveniences.swift in Sources */, BF1173501DA32CF600047DF8 /* ControllersSettingsViewController.swift in Sources */, BFFC461E1D59823500AF2CC6 /* GamesPresentationController.swift in Sources */, diff --git a/Delta/Components/Loading/LoadControllerSkinImageOperation.swift b/Delta/Components/Loading/LoadControllerSkinImageOperation.swift index 10ffd96..2168fd3 100644 --- a/Delta/Components/Loading/LoadControllerSkinImageOperation.swift +++ b/Delta/Components/Loading/LoadControllerSkinImageOperation.swift @@ -10,6 +10,17 @@ import UIKit import DeltaCore +import Roxas + +extension LoadControllerSkinImageOperation +{ + enum Error: Swift.Error + { + case doesNotExist + case unsupportedTraits + } +} + class ControllerSkinImageCacheKey: NSObject { let controllerSkin: ControllerSkin @@ -36,7 +47,7 @@ class ControllerSkinImageCacheKey: NSObject } } -class LoadControllerSkinImageOperation: LoadImageOperation +class LoadControllerSkinImageOperation: RSTLoadOperation { let controllerSkin: ControllerSkin let traits: DeltaCore.ControllerSkin.Traits @@ -52,9 +63,23 @@ class LoadControllerSkinImageOperation: LoadImageOperation UIImage? + override func loadResult(completion: @escaping (UIImage?, Swift.Error?) -> Void) { - let image = self.controllerSkin.image(for: self.traits, preferredSize: self.size) - return image + guard self.controllerSkin.supports(self.traits) else { + completion(nil, Error.unsupportedTraits) + return + } + + guard let image = self.controllerSkin.image(for: self.traits, preferredSize: self.size) else { + completion(nil, Error.doesNotExist) + return + } + + // Force decompression of image + UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), true, 1.0) + image.draw(at: CGPoint.zero) + UIGraphicsEndImageContext() + + completion(image, nil) } } diff --git a/Delta/Components/Loading/LoadImageOperation.swift b/Delta/Components/Loading/LoadImageOperation.swift deleted file mode 100644 index 420342b..0000000 --- a/Delta/Components/Loading/LoadImageOperation.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// LoadImageOperation.swift -// Delta -// -// Created by Riley Testut on 2/26/16. -// Copyright © 2016 Riley Testut. All rights reserved. -// - -import Foundation -import ImageIO - -import Roxas - -class LoadImageOperation: RSTOperation -{ - var completionHandler: ((UIImage?) -> Void)? { - didSet { - self.completionBlock = { - rst_dispatch_sync_on_main_thread() { - self.completionHandler?(self.image) - } - } - } - } - - var imageCache: NSCache? { - didSet { - // Ensures if an image is cached, it will be returned immediately, to prevent temporary flash of placeholder image - self.isImmediate = self.imageCache?.object(forKey: self.cacheKey) != nil - } - } - - private let cacheKey: CacheKeyType - private var image: UIImage? - - init(cacheKey: CacheKeyType) - { - self.cacheKey = cacheKey - - super.init() - } - - override func main() - { - guard !self.isCancelled else { return } - - if let cachedImage = self.imageCache?.object(forKey: self.cacheKey) - { - self.image = cachedImage - return - } - - guard let loadedImage = self.loadImage() else { return } - - // Force decompression of image - UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), true, 1.0) - loadedImage.draw(at: CGPoint.zero) - UIGraphicsEndImageContext() - - self.imageCache?.setObject(loadedImage, forKey: self.cacheKey) - - self.image = loadedImage - } - - func loadImage() -> UIImage? - { - return nil - } -} diff --git a/Delta/Components/Loading/LoadImageURLOperation.swift b/Delta/Components/Loading/LoadImageURLOperation.swift index 888a5ed..090b9f6 100644 --- a/Delta/Components/Loading/LoadImageURLOperation.swift +++ b/Delta/Components/Loading/LoadImageURLOperation.swift @@ -9,25 +9,100 @@ import UIKit import ImageIO +import SDWebImage + import Roxas -class LoadImageURLOperation: LoadImageOperation +extension LoadImageURLOperation { - public let url: URL + enum Error: Swift.Error + { + case doesNotExist + case invalid + case downloadFailed(Swift.Error) + } +} + +class LoadImageURLOperation: RSTLoadOperation +{ + let url: URL + + override var isAsynchronous: Bool { + return !self.url.isFileURL + } + + private var downloadOperation: SDWebImageOperation? init(url: URL) { self.url = url + super.init(cacheKey: url as NSURL) } - override func loadImage() -> UIImage? + override func cancel() + { + super.cancel() + + self.downloadOperation?.cancel() + } + + override func loadResult(completion: @escaping (UIImage?, Swift.Error?) -> Void) + { + let callback = { (image: UIImage?, error: Error?) in + + if let image = image, !self.isCancelled + { + // Force decompression of image + UIGraphicsBeginImageContextWithOptions(CGSize(width: 1, height: 1), true, 1.0) + image.draw(at: CGPoint.zero) + UIGraphicsEndImageContext() + } + + completion(image, error) + } + + if self.url.isFileURL + { + self.loadLocalImage(completion: callback) + } + else + { + self.loadRemoteImage(completion: callback) + } + } + + private func loadLocalImage(completion: @escaping (UIImage?, Error?) -> Void) { let options: NSDictionary = [kCGImageSourceShouldCache as NSString: true] - guard let imageSource = CGImageSourceCreateWithURL(self.url as CFURL, options), let quartzImage = CGImageSourceCreateImageAtIndex(imageSource, 0, options) else { return nil } + guard let imageSource = CGImageSourceCreateWithURL(self.url as CFURL, options) else { + completion(nil, .doesNotExist) + return + } + + guard let quartzImage = CGImageSourceCreateImageAtIndex(imageSource, 0, options) else { + completion(nil, .invalid) + return + } let image = UIImage(cgImage: quartzImage) - return image + completion(image, nil) + } + + private func loadRemoteImage(completion: @escaping (UIImage?, Error?) -> Void) + { + let manager = SDWebImageManager.shared() + + self.downloadOperation = manager?.downloadImage(with: self.url, options: [.retryFailed, .continueInBackground], progress: nil, completed: { (image, error, cacheType, finished, imageURL) in + if let error = error + { + completion(nil, .downloadFailed(error)) + } + else + { + completion(image, nil) + } + }) } } diff --git a/Delta/Pause Menu/Save States/SaveStatesViewController.swift b/Delta/Pause Menu/Save States/SaveStatesViewController.swift index 26a2c9f..60fbe19 100644 --- a/Delta/Pause Menu/Save States/SaveStatesViewController.swift +++ b/Delta/Pause Menu/Save States/SaveStatesViewController.swift @@ -236,8 +236,8 @@ private extension SaveStatesViewController if !ignoreOperations { let imageOperation = LoadImageURLOperation(url: saveState.imageFileURL) - imageOperation.imageCache = self.imageCache - imageOperation.completionHandler = { image in + imageOperation.resultsCache = self.imageCache + imageOperation.resultHandler = { (image, error) in if let image = image { diff --git a/Delta/Settings/Controller Skins/ControllerSkinsViewController.swift b/Delta/Settings/Controller Skins/ControllerSkinsViewController.swift index ed8930a..4d4a9b2 100644 --- a/Delta/Settings/Controller Skins/ControllerSkinsViewController.swift +++ b/Delta/Settings/Controller Skins/ControllerSkinsViewController.swift @@ -95,8 +95,8 @@ private extension ControllerSkinsViewController let size = UIScreen.main.defaultControllerSkinSize let imageOperation = LoadControllerSkinImageOperation(controllerSkin: controllerSkin, traits: self.traits, size: size) - imageOperation.imageCache = self.imageCache - imageOperation.completionHandler = { image in + imageOperation.resultsCache = self.imageCache + imageOperation.resultHandler = { (image, error) in guard let image = image else { return } @@ -159,7 +159,7 @@ extension ControllerSkinsViewController: UITableViewDataSourcePrefetching let size = UIScreen.main.defaultControllerSkinSize let imageOperation = LoadControllerSkinImageOperation(controllerSkin: controllerSkin, traits: self.traits, size: size) - imageOperation.imageCache = self.imageCache + imageOperation.resultsCache = self.imageCache self.imageOperationQueue.addOperation(imageOperation, forKey: indexPath as NSCopying) } diff --git a/External/Roxas b/External/Roxas index ecb29e0..0057bde 160000 --- a/External/Roxas +++ b/External/Roxas @@ -1 +1 @@ -Subproject commit ecb29e0050fe9335845698c896c4cd8ce60df0da +Subproject commit 0057bde6337f90b5747eec1baf94a1360e78c23e