From 1e0fd5fc74d62d40568e6e8e757d7e643bb73681 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Tue, 17 Apr 2018 01:16:25 +0700 Subject: [PATCH] Use the `Defaults` module (#32) --- .swiftlint.yml | 4 +- Gifski.xcodeproj/project.pbxproj | 16 ++- Gifski/AppDelegate.swift | 9 +- Gifski/MainWindowController.swift | 2 +- Gifski/SavePanelAccessoryViewController.swift | 4 +- Gifski/Vendor/Defaults.swift | 127 ++++++++++++++++++ Gifski/util.swift | 3 - 7 files changed, 149 insertions(+), 16 deletions(-) create mode 100644 Gifski/Vendor/Defaults.swift diff --git a/.swiftlint.yml b/.swiftlint.yml index 50a1daee..c0b4e4e8 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -30,7 +30,6 @@ opt_in_rules: - trailing_closure - block_based_kvo - discouraged_direct_init - - strict_fileprivate - joined_default_parameter - pattern_matching_keywords - contains_over_first_not_nil @@ -42,6 +41,9 @@ opt_in_rules: - yoda_condition - required_enum_case - discouraged_optional_boolean + - empty_string + - untyped_error_in_catch + - discouraged_optional_collection force_cast: warning force_unwrapping: warning number_separator: diff --git a/Gifski.xcodeproj/project.pbxproj b/Gifski.xcodeproj/project.pbxproj index e42a6648..cc75e7f8 100644 --- a/Gifski.xcodeproj/project.pbxproj +++ b/Gifski.xcodeproj/project.pbxproj @@ -10,6 +10,7 @@ 5A1FDC6F203F0B050065E0F5 /* libgifski.a in Frameworks */ = {isa = PBXBuildFile; fileRef = E3FD619F201BD29E0087160A /* libgifski.a */; }; C2040B8920435871004EE259 /* GifskiWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2040B8820435871004EE259 /* GifskiWrapper.swift */; }; C2AFA91D204FFEFD00FC5A7F /* MainWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C2AFA91B204FFEFD00FC5A7F /* MainWindowController.swift */; }; + E30557F0207E521F003401A1 /* Defaults.swift in Sources */ = {isa = PBXBuildFile; fileRef = E30557EF207E521F003401A1 /* Defaults.swift */; }; E317FF132057E24700A80A18 /* CircularProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = E317FF122057E24700A80A18 /* CircularProgress.swift */; }; E317FF1C20583E9800A80A18 /* DockProgress.swift in Sources */ = {isa = PBXBuildFile; fileRef = E317FF1B20583E9800A80A18 /* DockProgress.swift */; }; E339F011203820ED003B78FB /* Gifski.swift in Sources */ = {isa = PBXBuildFile; fileRef = E339F010203820ED003B78FB /* Gifski.swift */; }; @@ -64,6 +65,7 @@ /* Begin PBXFileReference section */ C2040B8820435871004EE259 /* GifskiWrapper.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = GifskiWrapper.swift; sourceTree = ""; usesTabs = 1; }; C2AFA91B204FFEFD00FC5A7F /* MainWindowController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = MainWindowController.swift; sourceTree = ""; usesTabs = 1; }; + E30557EF207E521F003401A1 /* Defaults.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Defaults.swift; sourceTree = ""; }; E317FF122057E24700A80A18 /* CircularProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CircularProgress.swift; sourceTree = ""; }; E317FF1B20583E9800A80A18 /* DockProgress.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DockProgress.swift; sourceTree = ""; }; E339F010203820ED003B78FB /* Gifski.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = Gifski.swift; sourceTree = ""; usesTabs = 1; }; @@ -111,6 +113,7 @@ children = ( E317FF122057E24700A80A18 /* CircularProgress.swift */, E317FF1B20583E9800A80A18 /* DockProgress.swift */, + E30557EF207E521F003401A1 /* Defaults.swift */, ); path = Vendor; sourceTree = ""; @@ -295,15 +298,16 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - E339F011203820ED003B78FB /* Gifski.swift in Sources */, - C2AFA91D204FFEFD00FC5A7F /* MainWindowController.swift in Sources */, - E3CB1DD71F7E4CBC00D79BFC /* VideoDropView.swift in Sources */, + E30557F0207E521F003401A1 /* Defaults.swift in Sources */, + E317FF1C20583E9800A80A18 /* DockProgress.swift in Sources */, E317FF132057E24700A80A18 /* CircularProgress.swift in Sources */, - C2040B8920435871004EE259 /* GifskiWrapper.swift in Sources */, - E3AE62871E5CD2F300035A2F /* AppDelegate.swift in Sources */, E3D08F6E1E5D7BFD00F465DF /* util.swift in Sources */, - E317FF1C20583E9800A80A18 /* DockProgress.swift in Sources */, + E339F011203820ED003B78FB /* Gifski.swift in Sources */, + C2040B8920435871004EE259 /* GifskiWrapper.swift in Sources */, + E3CB1DD71F7E4CBC00D79BFC /* VideoDropView.swift in Sources */, E3DF3E88203BD2B900055855 /* SavePanelAccessoryViewController.swift in Sources */, + C2AFA91D204FFEFD00FC5A7F /* MainWindowController.swift in Sources */, + E3AE62871E5CD2F300035A2F /* AppDelegate.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Gifski/AppDelegate.swift b/Gifski/AppDelegate.swift index 32d23728..f2bc790f 100644 --- a/Gifski/AppDelegate.swift +++ b/Gifski/AppDelegate.swift @@ -4,6 +4,10 @@ extension NSColor { static let appTheme = NSColor(named: NSColor.Name("Theme"))! } +extension Defaults.Keys { + static let outputQuality = Defaults.Key("outputQuality", default: 1) +} + @NSApplicationMain final class AppDelegate: NSObject, NSApplicationDelegate { lazy var mainWindowController = MainWindowController() @@ -11,10 +15,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate { var urlsToConvertOnLaunch: URL! func applicationWillFinishLaunching(_ notification: Notification) { - defaults.register(defaults: [ + UserDefaults.standard.register(defaults: [ "NSApplicationCrashOnExceptions": true, - "NSFullScreenMenuItemEverywhere": false, - "outputQuality": 1 + "NSFullScreenMenuItemEverywhere": false ]) NSAppearance.app = .dark diff --git a/Gifski/MainWindowController.swift b/Gifski/MainWindowController.swift index 780dc2e7..f4a616ec 100644 --- a/Gifski/MainWindowController.swift +++ b/Gifski/MainWindowController.swift @@ -110,7 +110,7 @@ final class MainWindowController: NSWindowController { let conversion = Gifski.Conversion( input: inputUrl, output: outputUrl, - quality: defaults["outputQuality"] as! Double, + quality: defaults[.outputQuality], dimensions: self.choosenDimensions, frameRate: self.choosenFrameRate ) diff --git a/Gifski/SavePanelAccessoryViewController.swift b/Gifski/SavePanelAccessoryViewController.swift index 113033e5..5779818c 100644 --- a/Gifski/SavePanelAccessoryViewController.swift +++ b/Gifski/SavePanelAccessoryViewController.swift @@ -48,7 +48,7 @@ final class SavePanelAccessoryViewController: NSViewController { } qualitySlider.onAction = { _ in - defaults["outputQuality"] = self.qualitySlider.doubleValue + defaults[.outputQuality] = self.qualitySlider.doubleValue estimateFileSize() } @@ -57,7 +57,7 @@ final class SavePanelAccessoryViewController: NSViewController { frameRateSlider.maxValue = Double(frameRate) frameRateSlider.integerValue = frameRate frameRateSlider.triggerAction() - qualitySlider.doubleValue = defaults["outputQuality"] as! Double + qualitySlider.doubleValue = defaults[.outputQuality] qualitySlider.triggerAction() } } diff --git a/Gifski/Vendor/Defaults.swift b/Gifski/Vendor/Defaults.swift new file mode 100644 index 00000000..08fd36c2 --- /dev/null +++ b/Gifski/Vendor/Defaults.swift @@ -0,0 +1,127 @@ +// Vendored from: https://github.com/sindresorhus/Defaults +import Foundation + +public final class Defaults { + public class Keys { + fileprivate init() {} + } + + public final class Key: Keys { + fileprivate let name: String + fileprivate let defaultValue: T + + public init(_ key: String, default defaultValue: T) { + self.name = key + self.defaultValue = defaultValue + } + } + + public final class OptionalKey: Keys { + fileprivate let name: String + + public init(_ key: String) { + self.name = key + } + } + + public subscript(key: Defaults.Key) -> T { + get { + return UserDefaults.standard[key] + } + set { + UserDefaults.standard[key] = newValue + } + } + + public subscript(key: Defaults.OptionalKey) -> T? { + get { + return UserDefaults.standard[key] + } + set { + UserDefaults.standard[key] = newValue + } + } + + public func clear() { + for key in UserDefaults.standard.dictionaryRepresentation().keys { + UserDefaults.standard.removeObject(forKey: key) + } + } +} + +// Has to be `defaults` lowercase until Swift supports static subscripts… +public let defaults = Defaults() + +public extension UserDefaults { + private func _get(_ key: String) -> T? { + if isNativelySupportedType(T.self) { + return object(forKey: key) as? T + } + + guard let text = string(forKey: key), + let data = "[\(text)]".data(using: .utf8) else { + return nil + } + + do { + return (try JSONDecoder().decode([T].self, from: data)).first + } catch { + print(error) + } + + return nil + } + + private func _set(_ key: String, to value: T) { + if isNativelySupportedType(T.self) { + set(value, forKey: key) + return + } + + do { + // Some codable values like URL and enum are encoded as a top-level + // string which JSON can't handle, so we need to wrap it in an array + // We need this: https://forums.swift.org/t/allowing-top-level-fragments-in-jsondecoder/11750 + let data = try JSONEncoder().encode([value]) + let string = String(data: data, encoding: .utf8)?.dropFirst().dropLast() + set(string, forKey: key) + } catch { + print(error) + } + } + + public subscript(key: Defaults.Key) -> T { + get { + return _get(key.name) ?? key.defaultValue + } + set { + _set(key.name, to: newValue) + } + } + + public subscript(key: Defaults.OptionalKey) -> T? { + get { + return _get(key.name) + } + set { + if let value = newValue { + _set(key.name, to: value) + } + } + } + + private func isNativelySupportedType(_ type: T.Type) -> Bool { + switch type { + case is Bool.Type, + is String.Type, + is Int.Type, + is Double.Type, + is Float.Type, + is Date.Type, + is Data.Type: + return true + default: + return false + } + } +} diff --git a/Gifski/util.swift b/Gifski/util.swift index cdb5ea43..043d69b6 100644 --- a/Gifski/util.swift +++ b/Gifski/util.swift @@ -2,9 +2,6 @@ import Cocoa import AVFoundation -let defaults = UserDefaults.standard - - /** Convenience function for initializing an object and modifying its properties