diff --git a/CHANGELOG.md b/CHANGELOG.md index a05edf7f..577003d3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +### 0.8.12 +- **Add**: `UserDefaults+Codable` is back. Now with generic subscript support. + ### 0.8.11 - **Change**: `NumberFormattingService.computedFormatters` computed var reverted to static. diff --git a/LeadKit.podspec b/LeadKit.podspec index 244a3671..cde7f6b7 100644 --- a/LeadKit.podspec +++ b/LeadKit.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "LeadKit" - s.version = "0.8.11" + s.version = "0.8.12" s.summary = "iOS framework with a bunch of tools for rapid development" s.homepage = "https://github.com/TouchInstinct/LeadKit" s.license = "Apache License, Version 2.0" diff --git a/LeadKit.xcodeproj/project.pbxproj b/LeadKit.xcodeproj/project.pbxproj index f261a0d6..5b04b3fb 100644 --- a/LeadKit.xcodeproj/project.pbxproj +++ b/LeadKit.xcodeproj/project.pbxproj @@ -321,6 +321,13 @@ 67274790206CD88600725163 /* DateFormattingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6727478E206CD88600725163 /* DateFormattingService.swift */; }; 67274791206CD88600725163 /* DateFormattingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6727478E206CD88600725163 /* DateFormattingService.swift */; }; 67274792206CD88600725163 /* DateFormattingService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6727478E206CD88600725163 /* DateFormattingService.swift */; }; + 6732F23F214C09F900B446F2 /* UserDefaults+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6732F23E214C09F900B446F2 /* UserDefaults+Codable.swift */; }; + 6732F240214C09F900B446F2 /* UserDefaults+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6732F23E214C09F900B446F2 /* UserDefaults+Codable.swift */; }; + 6732F241214C09F900B446F2 /* UserDefaults+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6732F23E214C09F900B446F2 /* UserDefaults+Codable.swift */; }; + 6732F242214C09F900B446F2 /* UserDefaults+Codable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6732F23E214C09F900B446F2 /* UserDefaults+Codable.swift */; }; + 6732F243214C189000B446F2 /* Single+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F8BB171F5DDED100C1061B /* Single+DeferredJust.swift */; }; + 6732F244214C189100B446F2 /* Single+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F8BB171F5DDED100C1061B /* Single+DeferredJust.swift */; }; + 6732F245214C189100B446F2 /* Single+DeferredJust.swift in Sources */ = {isa = PBXBuildFile; fileRef = 82F8BB171F5DDED100C1061B /* Single+DeferredJust.swift */; }; 673564F12068C2AD00F0CBED /* NumberFormattingService+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 673564F02068C2AD00F0CBED /* NumberFormattingService+DefaultImplementation.swift */; }; 673564F22068C2AD00F0CBED /* NumberFormattingService+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 673564F02068C2AD00F0CBED /* NumberFormattingService+DefaultImplementation.swift */; }; 673564F32068C2AD00F0CBED /* NumberFormattingService+DefaultImplementation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 673564F02068C2AD00F0CBED /* NumberFormattingService+DefaultImplementation.swift */; }; @@ -856,6 +863,7 @@ 6727477E206CD3BD00725163 /* ViewText+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ViewText+Extensions.swift"; sourceTree = ""; }; 67274789206CD83600725163 /* DateFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormat.swift; sourceTree = ""; }; 6727478E206CD88600725163 /* DateFormattingService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateFormattingService.swift; sourceTree = ""; }; + 6732F23E214C09F900B446F2 /* UserDefaults+Codable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UserDefaults+Codable.swift"; sourceTree = ""; }; 673564F02068C2AD00F0CBED /* NumberFormattingService+DefaultImplementation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NumberFormattingService+DefaultImplementation.swift"; sourceTree = ""; }; 673564F52068C68D00F0CBED /* NumberFormat.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NumberFormat.swift; sourceTree = ""; }; 6737CFA2207220960063E056 /* SeparatorConfiguration+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SeparatorConfiguration+Extensions.swift"; sourceTree = ""; }; @@ -1150,6 +1158,7 @@ 671461DA1EB3396E00EAB194 /* Extensions */ = { isa = PBXGroup; children = ( + 6732F23C214C09DF00B446F2 /* Foundation */, 671461DB1EB3396E00EAB194 /* Alamofire */, EFBE57CE1EC35ED90040E00A /* Array */, 67A1FF921EBCA64A00D6C89F /* CABasicAnimation */, @@ -1592,6 +1601,22 @@ path = UIImage; sourceTree = ""; }; + 6732F23C214C09DF00B446F2 /* Foundation */ = { + isa = PBXGroup; + children = ( + 6732F23D214C09E900B446F2 /* UserDefaults */, + ); + path = Foundation; + sourceTree = ""; + }; + 6732F23D214C09E900B446F2 /* UserDefaults */ = { + isa = PBXGroup; + children = ( + 6732F23E214C09F900B446F2 /* UserDefaults+Codable.swift */, + ); + path = UserDefaults; + sourceTree = ""; + }; 673564EF2068C29100F0CBED /* NumberFormattingService */ = { isa = PBXGroup; children = ( @@ -3109,6 +3134,7 @@ 677452A9206263360024EEEF /* CursorType+RxDataSourceDefaultImplementation.swift in Sources */, 677B06B221186C14006C947D /* Completable+DeferredJust.swift in Sources */, 671462501EB3396E00EAB194 /* StaticCursor.swift in Sources */, + 6732F23F214C09F900B446F2 /* UserDefaults+Codable.swift in Sources */, 67990AE6213EB4080040D195 /* ConfigurableView+Extensions.swift in Sources */, 6741C40F20EAC88800418D08 /* GeneralDataLoadingViewModel+Extensions.swift in Sources */, 67EB7FC7206148D000BDD9FB /* TotalCountCursorListingResult.swift in Sources */, @@ -3171,6 +3197,7 @@ 671463621EB3396E00EAB194 /* SupportProtocol.swift in Sources */, 678D26A220692BFF00B05B93 /* TextFieldViewEvents.swift in Sources */, 671462861EB3396E00EAB194 /* CGContext+Initializers.swift in Sources */, + 6732F244214C189100B446F2 /* Single+DeferredJust.swift in Sources */, 6774527B206252020024EEEF /* DataLoadingState.swift in Sources */, 67E3525D2119B5A50035BDDB /* BaseTextAttributes.swift in Sources */, 6714634E1EB3396E00EAB194 /* ReuseIdentifierProtocol.swift in Sources */, @@ -3238,6 +3265,7 @@ 67386A8E206CF3F6004EDA6C /* DateFormattingService+DefaultImplementation.swift in Sources */, 671462961EB3396E00EAB194 /* CGSize+CGContextSize.swift in Sources */, 671463661EB3396E00EAB194 /* ViewHeightProtocol.swift in Sources */, + 6732F241214C09F900B446F2 /* UserDefaults+Codable.swift in Sources */, 67EB7FD120615B8900BDD9FB /* TotalCountCursorConfiguration.swift in Sources */, 678D267B20691D8200B05B93 /* DataModelFieldBinding.swift in Sources */, 673CF40D2063AB7C00C329F6 /* GeneralDataLoadingViewModel.swift in Sources */, @@ -3387,6 +3415,7 @@ B84CB06B20B702260090DB91 /* Encodable+Extensions.swift in Sources */, 671462731EB3396E00EAB194 /* CursorError.swift in Sources */, 6741CED020E243F800FEC4D9 /* BaseCustomViewController.swift in Sources */, + 6732F242214C09F900B446F2 /* UserDefaults+Codable.swift in Sources */, 677B06B521186C14006C947D /* Completable+DeferredJust.swift in Sources */, 6727478D206CD83600725163 /* DateFormat.swift in Sources */, 67EB7FDD20615D5B00BDD9FB /* ResettableRxCursorDataSource.swift in Sources */, @@ -3424,6 +3453,7 @@ 6774529520625D170024EEEF /* GeneralDataLoadingModel.swift in Sources */, 6713C23A20AF0C4D00875921 /* NetworkOperationState.swift in Sources */, 6774529D20625E5B0024EEEF /* PaginationDataLoadingState.swift in Sources */, + 6732F245214C189100B446F2 /* Single+DeferredJust.swift in Sources */, 6714632F1EB3396E00EAB194 /* ConfigurableController.swift in Sources */, 67990ACD213EA5B70040D195 /* ContentLoadingViewModel.swift in Sources */, 67EB7FF42061682F00BDD9FB /* TotalCountCursorListingResult+DefaultTotalCountCursorListingResult.swift in Sources */, @@ -3505,6 +3535,7 @@ 671463111EB3396E00EAB194 /* UIViewController+DefaultXibName.swift in Sources */, 67153E41207DFBA80049D8C0 /* FloatingPoint+DegreesRadiansConvertion.swift in Sources */, 67990AE7213EB4080040D195 /* ConfigurableView+Extensions.swift in Sources */, + 6732F240214C09F900B446F2 /* UserDefaults+Codable.swift in Sources */, 671462911EB3396E00EAB194 /* CGImage+Crop.swift in Sources */, 6760DC4E212F351700020BAE /* UIView+AddSubviews.swift in Sources */, 67E902582125B66E008EDF45 /* UIImageView+ExpandCollapseDisclosure.swift in Sources */, @@ -3638,6 +3669,7 @@ 67990ACB213EA5B70040D195 /* ContentLoadingViewModel.swift in Sources */, EFBE57D11EC35EF20040E00A /* Array+Extensions.swift in Sources */, 676B22A3206A626D002E9F8A /* NSAttributedString+Extensions.swift in Sources */, + 6732F243214C189000B446F2 /* Single+DeferredJust.swift in Sources */, 671462D91EB3396E00EAB194 /* TimeInterval+DateComponents.swift in Sources */, 3622F5DD20E253F1009DED94 /* TableDirector+Extensions.swift in Sources */, 6714638D1EB3396E00EAB194 /* SolidFillDrawingOperation.swift in Sources */, diff --git a/Podfile.lock b/Podfile.lock index 2a50693a..797ddbe5 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -8,8 +8,8 @@ PODS: - RxCocoa (4.2.0): - RxSwift (~> 4.0) - RxSwift (4.2.0) - - SwiftDate (5.0.4) - - SwiftLint (0.26.0) + - SwiftDate (5.0.7) + - SwiftLint (0.27.0) - TableKit (2.7.0) - UIScrollView-InfiniteScroll (1.1.0) @@ -38,8 +38,8 @@ SPEC CHECKSUMS: RxAlamofire: 87a9c588541210cc3e4a1f843ccc3ecf3eb98b31 RxCocoa: 0b54909c902e1e581212a03e690bbd94032d8baa RxSwift: 99e10317ddfcc7fbe01356aafd118fde4a0be104 - SwiftDate: d9827f0e7edfeb8be52882beb67e75c773b634c3 - SwiftLint: f6b83e8d95ee1e91e11932d843af4fdcbf3fc764 + SwiftDate: f053fb89250c59af5eff777f3b832d2b9dd57403 + SwiftLint: 3207c1faa2240bf8973b191820a116113cd11073 TableKit: 506650573ed96ec007649b655559ecd43f9fd505 UIScrollView-InfiniteScroll: 3ef456bcbe759c19f510a383cff96e6647c98c98 diff --git a/Sources/Extensions/Foundation/UserDefaults/UserDefaults+Codable.swift b/Sources/Extensions/Foundation/UserDefaults/UserDefaults+Codable.swift new file mode 100644 index 00000000..d9982fc9 --- /dev/null +++ b/Sources/Extensions/Foundation/UserDefaults/UserDefaults+Codable.swift @@ -0,0 +1,108 @@ +import RxSwift + +public enum UserDefaultsError: Error { + + case noSuchValue(key: String) + case unableToDecode(decodingError: Error) + +} + +public extension UserDefaults { + + /// Returns the object with specified type associated with the first occurrence of the specified default. + /// + /// - Parameters: + /// - key: A key in the current user's defaults database. + /// - decoder: JSON decoder to decode stored data. + /// - Returns: The object with specified type associated with the specified key, + /// or throw exception if the key was not found. + /// - Throws: One of cases in UserDefaultsError + func object(forKey key: String, decoder: JSONDecoder = JSONDecoder()) throws -> T { + guard let storedData = data(forKey: key) else { + throw UserDefaultsError.noSuchValue(key: key) + } + + do { + return try decoder.decode(T.self, from: storedData) + } catch { + throw UserDefaultsError.unableToDecode(decodingError: error) + } + } + + /// Returns the object with specified type associated with the first occurrence of the specified default. + /// + /// - Parameters: + /// - key: A key in the current user's defaults database. + /// - defaultValue: A default value which will be used if there is no such value for specified key, + /// or if error occurred during mapping + /// - decoder: JSON decoder to decode stored data. + /// - Returns: The object with specified type associated with the specified key, or passed default value + /// if there is no such value for specified key or if error occurred during mapping. + func object(forKey key: String, defaultValue: T, decoder: JSONDecoder = JSONDecoder()) -> T { + return (try? object(forKey: key, decoder: decoder)) ?? defaultValue + } + + /// Set or remove the value of the specified default key in the standard application domain. + /// + /// - Parameters: + /// - object: The object with specified type to store or nil to remove it from the defaults database. + /// - key: The key with which to associate with the value. + /// - encoder: JSON encoder to encode to encode passed object. + /// - Throws: EncodingError if error is occured during passed object encoding. + func set(object: T?, forKey key: String, encoder: JSONEncoder = JSONEncoder()) throws { + if let object = object { + set(try encoder.encode(object), forKey: key) + } else { + set(nil, forKey: key) + } + } + + subscript(key: String) -> T? { + get { + return try? object(forKey: key) + } + set { + try? set(object: newValue, forKey: key) + } + } + +} + +public extension Reactive where Base: UserDefaults { + + /// Reactive version of object(forKey:decoder:) -> T. + /// + /// - Parameters: + /// - key: A key in the current user's defaults database. + /// - decoder: JSON decoder to decode stored data. + /// - Returns: Single of specified model type. + func object(forKey key: String, decoder: JSONDecoder = JSONDecoder()) -> Single { + return .deferredJust { try self.base.object(forKey: key, decoder: decoder) } + } + + /// Reactive version of object(forKey:defaultValue:decoder:) -> T. + /// + /// - Parameters: + /// - key: A key in the current user's defaults database. + /// - defaultValue: A default value which will be used if there is no such value for specified key, + /// or if error occurred during mapping + /// - decoder: JSON decoder to decode stored data. + /// - Returns: Single of specified model type. + func object(forKey key: String, defaultValue: T, decoder: JSONDecoder = JSONDecoder()) -> Single { + return .deferredJust { self.base.object(forKey: key, defaultValue: defaultValue, decoder: decoder) } + } + + /// Reactive version of set(object:forKey:encoder:). + /// + /// - Parameters: + /// - object: The object with specified type to store in the defaults database. + /// - key: The key with which to associate with the value. + /// - encoder: JSON encoder to encode to encode passed object. + /// - Returns: Completable. + func set(object: T?, forKey key: String, encoder: JSONEncoder = JSONEncoder()) -> Completable { + return .deferredJust { + try self.base.set(object: object, forKey: key, encoder: encoder) + } + } + +} diff --git a/Sources/Extensions/Rx/PrimitiveSequence/Completable/Completable+DeferredJust.swift b/Sources/Extensions/Rx/PrimitiveSequence/Completable/Completable+DeferredJust.swift index 3c7f6ff3..d324d1c9 100644 --- a/Sources/Extensions/Rx/PrimitiveSequence/Completable/Completable+DeferredJust.swift +++ b/Sources/Extensions/Rx/PrimitiveSequence/Completable/Completable+DeferredJust.swift @@ -29,7 +29,7 @@ public extension PrimitiveSequence where Trait == CompletableTrait { /// - Parameter workUnit: Element factory function to invoke for each observer /// that subscribes to the resulting sequence. /// - Returns: A single whose observers trigger an invocation of the given element factory function. - static func deferredJust(_ workUnit: @escaping () throws -> Void) -> Completable { + static func deferredJust(_ workUnit: @escaping ThrowableVoidBlock) -> Completable { return .create { observer in do { try workUnit() diff --git a/Sources/Extensions/TableKit/TableDirector/TableDirector+Extensions.swift b/Sources/Extensions/TableKit/TableDirector/TableDirector+Extensions.swift index 8a828716..020a2beb 100644 --- a/Sources/Extensions/TableKit/TableDirector/TableDirector+Extensions.swift +++ b/Sources/Extensions/TableKit/TableDirector/TableDirector+Extensions.swift @@ -127,7 +127,7 @@ public extension TableDirector { at indexPath: IndexPath, with animation: UITableViewRowAnimation, manualBeginEndUpdates: Bool = false) { - + sections[indexPath.section].insert(rows: rows, at: indexPath.row) let indexPaths: [IndexPath] = rows.indices.map { IndexPath(row: indexPath.row + $0, section: indexPath.section) diff --git a/Sources/Functions/VoidBlock.swift b/Sources/Functions/VoidBlock.swift index f064c486..e7cf2f7c 100644 --- a/Sources/Functions/VoidBlock.swift +++ b/Sources/Functions/VoidBlock.swift @@ -20,7 +20,8 @@ // THE SOFTWARE. // -import Foundation - /// Closure that takes no arguments and return Void. public typealias VoidBlock = () -> Void + +/// Closure that takes no arguments, may throw error and return Void. +public typealias ThrowableVoidBlock = () throws -> Void