diff --git a/.travis.yml b/.travis.yml index 45c345e8c..18c2f4115 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,4 @@ -osx_image: xcode9 +osx_image: xcode10 language: objective-c stage: Build env: diff --git a/Gemfile b/Gemfile new file mode 100644 index 000000000..0f5107ec2 --- /dev/null +++ b/Gemfile @@ -0,0 +1,3 @@ +source 'https://rubygems.org' + +gem 'cocoapods', '~> 1.6.0.beta.2' diff --git a/Gemfile.lock b/Gemfile.lock new file mode 100644 index 000000000..d4cf0c3bc --- /dev/null +++ b/Gemfile.lock @@ -0,0 +1,76 @@ +GEM + remote: https://rubygems.org/ + specs: + CFPropertyList (3.0.0) + activesupport (4.2.11) + i18n (~> 0.7) + minitest (~> 5.1) + thread_safe (~> 0.3, >= 0.3.4) + tzinfo (~> 1.1) + atomos (0.1.3) + claide (1.0.2) + cocoapods (1.6.0.beta.2) + activesupport (>= 4.0.2, < 5) + claide (>= 1.0.2, < 2.0) + cocoapods-core (= 1.6.0.beta.2) + cocoapods-deintegrate (>= 1.0.2, < 2.0) + cocoapods-downloader (>= 1.2.2, < 2.0) + cocoapods-plugins (>= 1.0.0, < 2.0) + cocoapods-search (>= 1.0.0, < 2.0) + cocoapods-stats (>= 1.0.0, < 2.0) + cocoapods-trunk (>= 1.3.1, < 2.0) + cocoapods-try (>= 1.1.0, < 2.0) + colored2 (~> 3.1) + escape (~> 0.0.4) + fourflusher (~> 2.0.1) + gh_inspector (~> 1.0) + molinillo (~> 0.6.6) + nap (~> 1.0) + ruby-macho (~> 1.3, >= 1.3.1) + xcodeproj (>= 1.7.0, < 2.0) + cocoapods-core (1.6.0.beta.2) + activesupport (>= 4.0.2, < 6) + fuzzy_match (~> 2.0.4) + nap (~> 1.0) + cocoapods-deintegrate (1.0.2) + cocoapods-downloader (1.2.2) + cocoapods-plugins (1.0.0) + nap + cocoapods-search (1.0.0) + cocoapods-stats (1.0.0) + cocoapods-trunk (1.3.1) + nap (>= 0.8, < 2.0) + netrc (~> 0.11) + cocoapods-try (1.1.0) + colored2 (3.1.2) + concurrent-ruby (1.1.4) + escape (0.0.4) + fourflusher (2.0.1) + fuzzy_match (2.0.4) + gh_inspector (1.1.3) + i18n (0.9.5) + concurrent-ruby (~> 1.0) + minitest (5.11.3) + molinillo (0.6.6) + nanaimo (0.2.6) + nap (1.1.0) + netrc (0.11.0) + ruby-macho (1.3.1) + thread_safe (0.3.6) + tzinfo (1.2.5) + thread_safe (~> 0.1) + xcodeproj (1.7.0) + CFPropertyList (>= 2.3.3, < 4.0) + atomos (~> 0.1.3) + claide (>= 1.0.2, < 2.0) + colored2 (~> 3.1) + nanaimo (~> 0.2.6) + +PLATFORMS + ruby + +DEPENDENCIES + cocoapods (~> 1.6.0.beta.2) + +BUNDLED WITH + 1.16.2 diff --git a/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift index c7578568e..810eba077 100644 --- a/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift @@ -54,10 +54,8 @@ do { acl?.publicWrite = true loggedIn.ACL = acl try loggedIn.save() -} catch let e { - e - e.localizedDescription - fatalError("\(e.localizedDescription)") +} catch let error { + fatalError("\(error.localizedDescription)") } //var acl = ACL() diff --git a/ParseSwift.podspec b/ParseSwift.podspec index e4b49d585..88808f292 100644 --- a/ParseSwift.podspec +++ b/ParseSwift.podspec @@ -10,6 +10,7 @@ Pod::Spec.new do |s| :git => "#{s.homepage}.git", :tag => "#{s.version}", } + s.pod_target_xcconfig = { 'SWIFT_VERSION' => '4.2' } s.ios.deployment_target = "8.0" s.osx.deployment_target = "10.10" s.tvos.deployment_target = "9.0" @@ -23,4 +24,4 @@ Pod::Spec.new do |s| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. LICENSE } -end \ No newline at end of file +end diff --git a/ParseSwift.xcodeproj/project.pbxproj b/ParseSwift.xcodeproj/project.pbxproj index d3e86ef69..fd477fd3c 100644 --- a/ParseSwift.xcodeproj/project.pbxproj +++ b/ParseSwift.xcodeproj/project.pbxproj @@ -70,6 +70,15 @@ 4AFDA7351F26DAE1002AE4FC /* Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B7FE1F256A8F0063D731 /* Query.swift */; }; 4AFDA7361F26DAE1002AE4FC /* ObjectType+Query.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4A82B8001F256B330063D731 /* ObjectType+Query.swift */; }; 4AFDA7391F26DAF8002AE4FC /* Parse.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AB8B4F71F254AE10070F682 /* Parse.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 7FFF55242217E6EF007C3B4E /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF55212217E6EF007C3B4E /* AnyDecodable.swift */; }; + 7FFF55252217E6EF007C3B4E /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF55212217E6EF007C3B4E /* AnyDecodable.swift */; }; + 7FFF55262217E6EF007C3B4E /* AnyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF55222217E6EF007C3B4E /* AnyCodable.swift */; }; + 7FFF55272217E6EF007C3B4E /* AnyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF55222217E6EF007C3B4E /* AnyCodable.swift */; }; + 7FFF55282217E6EF007C3B4E /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF55232217E6EF007C3B4E /* AnyEncodable.swift */; }; + 7FFF55292217E6EF007C3B4E /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF55232217E6EF007C3B4E /* AnyEncodable.swift */; }; + 7FFF552E2217E72A007C3B4E /* AnyEncodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF552B2217E729007C3B4E /* AnyEncodableTests.swift */; }; + 7FFF552F2217E72A007C3B4E /* AnyCodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF552C2217E729007C3B4E /* AnyCodableTests.swift */; }; + 7FFF55302217E72A007C3B4E /* AnyDecodableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7FFF552D2217E729007C3B4E /* AnyDecodableTests.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -134,6 +143,12 @@ 4AF85BDC1F783B9800665264 /* API+Commands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "API+Commands.swift"; sourceTree = ""; }; 4AFDA7121F26D9A5002AE4FC /* ParseSwift.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = ParseSwift.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4AFDA7151F26D9A5002AE4FC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 7FFF55212217E6EF007C3B4E /* AnyDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = ""; }; + 7FFF55222217E6EF007C3B4E /* AnyCodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCodable.swift; sourceTree = ""; }; + 7FFF55232217E6EF007C3B4E /* AnyEncodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyEncodable.swift; sourceTree = ""; }; + 7FFF552B2217E729007C3B4E /* AnyEncodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyEncodableTests.swift; sourceTree = ""; }; + 7FFF552C2217E729007C3B4E /* AnyCodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCodableTests.swift; sourceTree = ""; }; + 7FFF552D2217E729007C3B4E /* AnyDecodableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDecodableTests.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -180,6 +195,7 @@ 4A5EE45F1F49E9E000D3CAE3 /* Sources */ = { isa = PBXGroup; children = ( + 7FFF55202217E6EF007C3B4E /* AnyCodable */, 4AB8B4F61F254AE10070F682 /* ParseSwift */, ); path = Sources; @@ -188,6 +204,7 @@ 4A5EE4601F49EA0600D3CAE3 /* Tests */ = { isa = PBXGroup; children = ( + 7FFF552A2217E729007C3B4E /* AnyCodableTests */, 4AA8076C1F794C1C008CD551 /* ParseSwiftTests */, 4A1120BF1F49FC3300E32D94 /* LinuxMain.swift */, ); @@ -332,6 +349,26 @@ path = "ParseSwift-macOS"; sourceTree = ""; }; + 7FFF55202217E6EF007C3B4E /* AnyCodable */ = { + isa = PBXGroup; + children = ( + 7FFF55212217E6EF007C3B4E /* AnyDecodable.swift */, + 7FFF55222217E6EF007C3B4E /* AnyCodable.swift */, + 7FFF55232217E6EF007C3B4E /* AnyEncodable.swift */, + ); + path = AnyCodable; + sourceTree = ""; + }; + 7FFF552A2217E729007C3B4E /* AnyCodableTests */ = { + isa = PBXGroup; + children = ( + 7FFF552B2217E729007C3B4E /* AnyEncodableTests.swift */, + 7FFF552C2217E729007C3B4E /* AnyCodableTests.swift */, + 7FFF552D2217E729007C3B4E /* AnyDecodableTests.swift */, + ); + path = AnyCodableTests; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -435,11 +472,12 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0900; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = Parse; TargetAttributes = { 4AA807571F794242008CD551 = { CreatedOnToolsVersion = 9.0; + LastSwiftMigration = 1010; ProvisioningStyle = Automatic; }; 4AB8B4F31F254AE10070F682 = { @@ -564,12 +602,15 @@ 4A82B7F61F254CCE0063D731 /* Parse.swift in Sources */, 4AEBA54F1F265A0D00628B17 /* UserType.swift in Sources */, 4A99A4691F2650CA00D72A59 /* ParseMutationContainer.swift in Sources */, + 7FFF55262217E6EF007C3B4E /* AnyCodable.swift in Sources */, 4AC397701F48778900DEA9D3 /* ACL.swift in Sources */, 4A2D8C811F48B3A900EE1FCC /* ParseEncoder.swift in Sources */, 4A99A46E1F26512100D72A59 /* IncrementOperation.swift in Sources */, 4A82B8011F256B330063D731 /* ObjectType+Query.swift in Sources */, + 7FFF55242217E6EF007C3B4E /* AnyDecodable.swift in Sources */, 4A82B7FF1F256A8F0063D731 /* Query.swift in Sources */, 4AF85BD81F78144900665264 /* URLSession+sync.swift in Sources */, + 7FFF55282217E6EF007C3B4E /* AnyEncodable.swift in Sources */, 4A2F14981F4A5F6900A7A7EF /* ObjectType+Batch.swift in Sources */, 4A82B7F41F254CCE0063D731 /* File.swift in Sources */, 4AF85BD31F78011100665264 /* ParseError.swift in Sources */, @@ -590,6 +631,9 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 7FFF552E2217E72A007C3B4E /* AnyEncodableTests.swift in Sources */, + 7FFF55302217E72A007C3B4E /* AnyDecodableTests.swift in Sources */, + 7FFF552F2217E72A007C3B4E /* AnyCodableTests.swift in Sources */, 4AA807701F794C31008CD551 /* KeychainStoreTests.swift in Sources */, 4AA807711F794C33008CD551 /* ParseSwiftTests.swift in Sources */, ); @@ -607,12 +651,15 @@ 4A6511501F48E400005237DF /* BatchUtils.swift in Sources */, 4AFDA72A1F26DAE1002AE4FC /* Parse.swift in Sources */, 4AFDA7351F26DAE1002AE4FC /* Query.swift in Sources */, + 7FFF55272217E6EF007C3B4E /* AnyCodable.swift in Sources */, 4A2F14991F4A5FB500A7A7EF /* Responses.swift in Sources */, 4A2F149A1F4A5FBA00A7A7EF /* ObjectType+Equatable.swift in Sources */, 4A65114F1F48E3F3005237DF /* ParseEncoder.swift in Sources */, 4AFDA72E1F26DAE1002AE4FC /* AddOperation.swift in Sources */, + 7FFF55252217E6EF007C3B4E /* AnyDecodable.swift in Sources */, 4AFDA7311F26DAE1002AE4FC /* AddUniqueOperation.swift in Sources */, 4AF85BD91F78145C00665264 /* URLSession+sync.swift in Sources */, + 7FFF55292217E6EF007C3B4E /* AnyEncodable.swift in Sources */, 4A2F149B1F4A5FBA00A7A7EF /* ObjectType+Batch.swift in Sources */, 4A6511521F48E406005237DF /* GeoPoint.swift in Sources */, 4AF85BD61F7803C100665264 /* ParseError.swift in Sources */, @@ -673,7 +720,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.parse.TestHost; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -687,7 +734,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.parse.TestHost; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -706,6 +753,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -713,6 +761,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -747,7 +796,7 @@ SDKROOT = iphoneos; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; }; @@ -767,6 +816,7 @@ CLANG_WARN_BOOL_CONVERSION = YES; CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; + CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; CLANG_WARN_EMPTY_BODY = YES; @@ -774,6 +824,7 @@ CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; @@ -800,7 +851,7 @@ MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; VALIDATE_PRODUCT = YES; VERSIONING_SYSTEM = "apple-generic"; VERSION_INFO_PREFIX = ""; @@ -823,7 +874,7 @@ PRODUCT_NAME = ParseSwift; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Debug; @@ -844,7 +895,7 @@ PRODUCT_NAME = ParseSwift; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; }; name = Release; @@ -857,7 +908,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwiftTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestHost.app/TestHost"; }; @@ -871,7 +922,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.parse.ParseSwiftTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TARGETED_DEVICE_FAMILY = "1,2"; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/TestHost.app/TestHost"; }; @@ -896,7 +947,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -919,7 +970,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (iOS).xcscheme b/ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (iOS).xcscheme index 8e4acb301..bb7f8f688 100644 --- a/ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (iOS).xcscheme +++ b/ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (iOS).xcscheme @@ -1,6 +1,6 @@ + codeCoverageEnabled = "YES" + shouldUseLaunchSchemeArgsEnv = "YES"> @@ -57,7 +56,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (macOS).xcscheme b/ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (macOS).xcscheme index 6eb84aa9a..326ba783e 100644 --- a/ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (macOS).xcscheme +++ b/ParseSwift.xcodeproj/xcshareddata/xcschemes/ParseSwift (macOS).xcscheme @@ -1,6 +1,6 @@ @@ -37,7 +36,6 @@ buildConfiguration = "Debug" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" - language = "" launchStyle = "0" useCustomWorkingDirectory = "NO" ignoresPersistentStateOnLaunch = "NO" diff --git a/Sources/AnyCodable/AnyCodable.swift b/Sources/AnyCodable/AnyCodable.swift new file mode 100755 index 000000000..fead04158 --- /dev/null +++ b/Sources/AnyCodable/AnyCodable.swift @@ -0,0 +1,93 @@ +import Foundation + +/** + A type-erased `Codable` value. + + The `AnyCodable` type forwards encoding and decoding responsibilities + to an underlying value, hiding its specific underlying type. + + You can encode or decode mixed-type values in dictionaries + and other collections that require `Encodable` or `Decodable` conformance + by declaring their contained type to be `AnyCodable`. + + - SeeAlso: `AnyEncodable` + - SeeAlso: `AnyDecodable` + */ +public struct AnyCodable: Codable { + public let value: Any + + public init(_ value: T?) { + self.value = value ?? () + } +} + +extension AnyCodable: _AnyEncodable, _AnyDecodable {} + +extension AnyCodable: Equatable { + public static func ==(lhs: AnyCodable, rhs: AnyCodable) -> Bool { + switch (lhs.value, rhs.value) { + case is (Void, Void): + return true + case let (lhs as Bool, rhs as Bool): + return lhs == rhs + case let (lhs as Int, rhs as Int): + return lhs == rhs + case let (lhs as Int8, rhs as Int8): + return lhs == rhs + case let (lhs as Int16, rhs as Int16): + return lhs == rhs + case let (lhs as Int32, rhs as Int32): + return lhs == rhs + case let (lhs as Int64, rhs as Int64): + return lhs == rhs + case let (lhs as UInt, rhs as UInt): + return lhs == rhs + case let (lhs as UInt8, rhs as UInt8): + return lhs == rhs + case let (lhs as UInt16, rhs as UInt16): + return lhs == rhs + case let (lhs as UInt32, rhs as UInt32): + return lhs == rhs + case let (lhs as UInt64, rhs as UInt64): + return lhs == rhs + case let (lhs as Float, rhs as Float): + return lhs == rhs + case let (lhs as Double, rhs as Double): + return lhs == rhs + case let (lhs as String, rhs as String): + return lhs == rhs + case (let lhs as [String: AnyCodable], let rhs as [String: AnyCodable]): + return lhs == rhs + case (let lhs as [AnyCodable], let rhs as [AnyCodable]): + return lhs == rhs + default: + return false + } + } +} + +extension AnyCodable: CustomStringConvertible { + public var description: String { + switch value { + case is Void: + return String(describing: nil as Any?) + case let value as CustomStringConvertible: + return value.description + default: + return String(describing: value) + } + } +} + +extension AnyCodable: CustomDebugStringConvertible { + public var debugDescription: String { + switch value { + case let value as CustomDebugStringConvertible: + return "AnyCodable(\(value.debugDescription))" + default: + return "AnyCodable(\(self.description))" + } + } +} + +extension AnyCodable: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {} diff --git a/Sources/AnyCodable/AnyDecodable.swift b/Sources/AnyCodable/AnyDecodable.swift new file mode 100755 index 000000000..6e8a0d6e2 --- /dev/null +++ b/Sources/AnyCodable/AnyDecodable.swift @@ -0,0 +1,137 @@ +import Foundation + +/** + A type-erased `Decodable` value. + + The `AnyDecodable` type forwards decoding responsibilities + to an underlying value, hiding its specific underlying type. + + You can decode mixed-type values in dictionaries + and other collections that require `Decodable` conformance + by declaring their contained type to be `AnyDecodable`: + + let json = """ + { + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": { + "a": "alpha", + "b": "bravo", + "c": "charlie" + } + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + let dictionary = try! decoder.decode([String: AnyCodable].self, from: json) + */ +public struct AnyDecodable: Decodable { + public let value: Any + + public init(_ value: T?) { + self.value = value ?? () + } +} + +protocol _AnyDecodable { + var value: Any { get } + init(_ value: T?) +} + +extension AnyDecodable: _AnyDecodable {} + +extension _AnyDecodable { + public init(from decoder: Decoder) throws { + let container = try decoder.singleValueContainer() + + if container.decodeNil() { + self.init(()) + } else if let bool = try? container.decode(Bool.self) { + self.init(bool) + } else if let int = try? container.decode(Int.self) { + self.init(int) + } else if let uint = try? container.decode(UInt.self) { + self.init(uint) + } else if let double = try? container.decode(Double.self) { + self.init(double) + } else if let string = try? container.decode(String.self) { + self.init(string) + } else if let array = try? container.decode([AnyCodable].self) { + self.init(array.map { $0.value }) + } else if let dictionary = try? container.decode([String: AnyCodable].self) { + self.init(dictionary.mapValues { $0.value }) + } else { + throw DecodingError.dataCorruptedError(in: container, debugDescription: "AnyCodable value cannot be decoded") + } + } +} + +extension AnyDecodable: Equatable { + public static func ==(lhs: AnyDecodable, rhs: AnyDecodable) -> Bool { + switch (lhs.value, rhs.value) { + case is (Void, Void): + return true + case let (lhs as Bool, rhs as Bool): + return lhs == rhs + case let (lhs as Int, rhs as Int): + return lhs == rhs + case let (lhs as Int8, rhs as Int8): + return lhs == rhs + case let (lhs as Int16, rhs as Int16): + return lhs == rhs + case let (lhs as Int32, rhs as Int32): + return lhs == rhs + case let (lhs as Int64, rhs as Int64): + return lhs == rhs + case let (lhs as UInt, rhs as UInt): + return lhs == rhs + case let (lhs as UInt8, rhs as UInt8): + return lhs == rhs + case let (lhs as UInt16, rhs as UInt16): + return lhs == rhs + case let (lhs as UInt32, rhs as UInt32): + return lhs == rhs + case let (lhs as UInt64, rhs as UInt64): + return lhs == rhs + case let (lhs as Float, rhs as Float): + return lhs == rhs + case let (lhs as Double, rhs as Double): + return lhs == rhs + case let (lhs as String, rhs as String): + return lhs == rhs + case (let lhs as [String: AnyDecodable], let rhs as [String: AnyDecodable]): + return lhs == rhs + case (let lhs as [AnyDecodable], let rhs as [AnyDecodable]): + return lhs == rhs + default: + return false + } + } +} + +extension AnyDecodable: CustomStringConvertible { + public var description: String { + switch value { + case is Void: + return String(describing: nil as Any?) + case let value as CustomStringConvertible: + return value.description + default: + return String(describing: value) + } + } +} + +extension AnyDecodable: CustomDebugStringConvertible { + public var debugDescription: String { + switch value { + case let value as CustomDebugStringConvertible: + return "AnyDecodable(\(value.debugDescription))" + default: + return "AnyDecodable(\(self.description))" + } + } +} diff --git a/Sources/AnyCodable/AnyEncodable.swift b/Sources/AnyCodable/AnyEncodable.swift new file mode 100755 index 000000000..55653bb16 --- /dev/null +++ b/Sources/AnyCodable/AnyEncodable.swift @@ -0,0 +1,198 @@ +import Foundation + +/** + A type-erased `Encodable` value. + + The `AnyEncodable` type forwards encoding responsibilities + to an underlying value, hiding its specific underlying type. + + You can encode mixed-type values in dictionaries + and other collections that require `Encodable` conformance + by declaring their contained type to be `AnyEncodable`: + + let dictionary: [String: AnyEncodable] = [ + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": [ + "a": "alpha", + "b": "bravo", + "c": "charlie" + ] + ] + + let encoder = JSONEncoder() + let json = try! encoder.encode(dictionary) + */ +public struct AnyEncodable: Encodable { + public let value: Any + + public init(_ value: T?) { + self.value = value ?? () + } +} + +protocol _AnyEncodable { + var value: Any { get } + init(_ value: T?) +} + + +extension AnyEncodable: _AnyEncodable {} + +// MARK: - Encodable + +extension _AnyEncodable { + public func encode(to encoder: Encoder) throws { + var container = encoder.singleValueContainer() + + switch self.value { + case is Void: + try container.encodeNil() + case let bool as Bool: + try container.encode(bool) + case let int as Int: + try container.encode(int) + case let int8 as Int8: + try container.encode(int8) + case let int16 as Int16: + try container.encode(int16) + case let int32 as Int32: + try container.encode(int32) + case let int64 as Int64: + try container.encode(int64) + case let uint as UInt: + try container.encode(uint) + case let uint8 as UInt8: + try container.encode(uint8) + case let uint16 as UInt16: + try container.encode(uint16) + case let uint32 as UInt32: + try container.encode(uint32) + case let uint64 as UInt64: + try container.encode(uint64) + case let float as Float: + try container.encode(float) + case let double as Double: + try container.encode(double) + case let string as String: + try container.encode(string) + case let date as Date: + try container.encode(date) + case let url as URL: + try container.encode(url) + case let array as [Any?]: + try container.encode(array.map { AnyCodable($0) }) + case let dictionary as [String: Any?]: + try container.encode(dictionary.mapValues { AnyCodable($0) }) + default: + let context = EncodingError.Context(codingPath: container.codingPath, debugDescription: "AnyCodable value cannot be encoded") + throw EncodingError.invalidValue(self.value, context) + } + } +} + +extension AnyEncodable: Equatable { + public static func ==(lhs: AnyEncodable, rhs: AnyEncodable) -> Bool { + switch (lhs.value, rhs.value) { + case is (Void, Void): + return true + case let (lhs as Bool, rhs as Bool): + return lhs == rhs + case let (lhs as Int, rhs as Int): + return lhs == rhs + case let (lhs as Int8, rhs as Int8): + return lhs == rhs + case let (lhs as Int16, rhs as Int16): + return lhs == rhs + case let (lhs as Int32, rhs as Int32): + return lhs == rhs + case let (lhs as Int64, rhs as Int64): + return lhs == rhs + case let (lhs as UInt, rhs as UInt): + return lhs == rhs + case let (lhs as UInt8, rhs as UInt8): + return lhs == rhs + case let (lhs as UInt16, rhs as UInt16): + return lhs == rhs + case let (lhs as UInt32, rhs as UInt32): + return lhs == rhs + case let (lhs as UInt64, rhs as UInt64): + return lhs == rhs + case let (lhs as Float, rhs as Float): + return lhs == rhs + case let (lhs as Double, rhs as Double): + return lhs == rhs + case let (lhs as String, rhs as String): + return lhs == rhs + case (let lhs as [String: AnyEncodable], let rhs as [String: AnyEncodable]): + return lhs == rhs + case (let lhs as [AnyEncodable], let rhs as [AnyEncodable]): + return lhs == rhs + default: + return false + } + } +} + +extension AnyEncodable: CustomStringConvertible { + public var description: String { + switch value { + case is Void: + return String(describing: nil as Any?) + case let value as CustomStringConvertible: + return value.description + default: + return String(describing: value) + } + } +} + +extension AnyEncodable: CustomDebugStringConvertible { + public var debugDescription: String { + switch value { + case let value as CustomDebugStringConvertible: + return "AnyEncodable(\(value.debugDescription))" + default: + return "AnyEncodable(\(self.description))" + } + } +} + +extension AnyEncodable: ExpressibleByNilLiteral, ExpressibleByBooleanLiteral, ExpressibleByIntegerLiteral, ExpressibleByFloatLiteral, ExpressibleByStringLiteral, ExpressibleByArrayLiteral, ExpressibleByDictionaryLiteral {} + +extension _AnyEncodable { + public init(nilLiteral: ()) { + self.init(nil as Any?) + } + + public init(booleanLiteral value: Bool) { + self.init(value) + } + + public init(integerLiteral value: Int) { + self.init(value) + } + + public init(floatLiteral value: Double) { + self.init(value) + } + + public init(extendedGraphemeClusterLiteral value: String) { + self.init(value) + } + + public init(stringLiteral value: String) { + self.init(value) + } + + public init(arrayLiteral elements: Any...) { + self.init(elements) + } + + public init(dictionaryLiteral elements: (AnyHashable, Any)...) { + self.init(Dictionary(elements, uniquingKeysWith: { (first, _) in first })) + } +} diff --git a/Sources/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index 86a458d28..cc31a7291 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -111,7 +111,7 @@ extension API.Command where T: ObjectType { } static func batch(commands: [API.Command]) -> RESTBatchCommandType { - let commands = commands.flatMap { (command) -> API.Command? in + let commands = commands.compactMap { (command) -> API.Command? in let path = ParseConfiguration.mountPath + command.path.urlComponent guard let body = command.body else { return nil @@ -119,7 +119,7 @@ extension API.Command where T: ObjectType { return API.Command(method: command.method, path: .any(path), body: body, mapper: command.mapper) } - let bodies = commands.flatMap { (command) -> T? in + let bodies = commands.compactMap { (command) -> T? in return command.body } let mapper = { (data: Data) -> [(T, ParseError?)] in diff --git a/Sources/ParseSwift/Asynchronous.swift b/Sources/ParseSwift/Asynchronous.swift index 03a284abc..539b47d17 100644 --- a/Sources/ParseSwift/Asynchronous.swift +++ b/Sources/ParseSwift/Asynchronous.swift @@ -16,8 +16,8 @@ private func runAsync(options: API.Options, queue.async { do { callback(try function(options), nil) - } catch let e { - callback(nil, e) + } catch let error { + callback(nil, error) } } } diff --git a/Sources/ParseSwift/Objects Protocols/ObjectType.swift b/Sources/ParseSwift/Objects Protocols/ObjectType.swift index 4693850a5..8efcbab68 100644 --- a/Sources/ParseSwift/Objects Protocols/ObjectType.swift +++ b/Sources/ParseSwift/Objects Protocols/ObjectType.swift @@ -51,8 +51,8 @@ internal extension ObjectType { extension ObjectType { // Parse ClassName inference public static var className: String { - let t = "\(type(of: self))" - return t.components(separatedBy: ".").first! // strip .Type + let classType = "\(type(of: self))" + return classType.components(separatedBy: ".").first! // strip .Type } public var className: String { return Self.className @@ -115,7 +115,7 @@ let dateDecodingStrategy: JSONDecoder.DateDecodingStrategy = .custom({ (dec) -> let container = try dec.singleValueContainer() let decodedString = try container.decode(String.self) return dateFormatter.date(from: decodedString)! - } catch let e { + } catch let error { let container = try dec.container(keyedBy: DateEncodingKeys.self) if let decoded = try container.decodeIfPresent(String.self, forKey: .iso) { return dateFormatter.date(from: decoded)! diff --git a/Sources/ParseSwift/Storage/KeychainStore.swift b/Sources/ParseSwift/Storage/KeychainStore.swift index 896c9c90e..f49df8145 100644 --- a/Sources/ParseSwift/Storage/KeychainStore.swift +++ b/Sources/ParseSwift/Storage/KeychainStore.swift @@ -7,10 +7,9 @@ // import Foundation - func getKeychainQueryTemplate(forService service: String) -> [String: String] { var query = [String: String]() - if service.characters.count > 0 { + if service.count > 0 { query[kSecAttrService as String] = service } query[kSecClass as String] = kSecClassGenericPassword as String @@ -25,7 +24,7 @@ struct KeychainStore: SecureStorage { synchronizationQueue = DispatchQueue(label: "com.parse.keychain.\(service)", qos: .default, attributes: .concurrent, - autoreleaseFrequency:.inherit, + autoreleaseFrequency: .inherit, target: nil) keychainQueryTemplate = getKeychainQueryTemplate(forService: service) } @@ -35,7 +34,6 @@ struct KeychainStore: SecureStorage { query[kSecAttrAccount as String] = key return query } - func object(forKey key: String) -> T? where T: Decodable { guard let data = synchronizationQueue.sync(execute: { () -> Data? in return self.data(forKey: key) diff --git a/Sources/ParseSwift/Storage/SecureStorage.swift b/Sources/ParseSwift/Storage/SecureStorage.swift index a78725ccd..baa9f8d7b 100644 --- a/Sources/ParseSwift/Storage/SecureStorage.swift +++ b/Sources/ParseSwift/Storage/SecureStorage.swift @@ -10,9 +10,8 @@ import Foundation protocol SecureStorage { init(service: String) - func object(forKey: String) -> T? where T: Decodable + func object(forKey key: String) -> T? where T: Decodable func set(object: T?, forKey: String) -> Bool where T: Encodable - subscript (key: String) -> T? where T: Codable { get } func removeObject(forKey: String) -> Bool func removeAllObjects() -> Bool diff --git a/Sources/ParseSwift/Types/ACL.swift b/Sources/ParseSwift/Types/ACL.swift index a7af08cc1..ecefee848 100644 --- a/Sources/ParseSwift/Types/ACL.swift +++ b/Sources/ParseSwift/Types/ACL.swift @@ -114,7 +114,7 @@ extension ACL { try container.nestedContainer(keyedBy: Access.self, forKey: scope)) }.flatMap { pair -> [(String, Access, Bool)] in let (scope, accessValues) = pair - return try accessValues.allKeys.flatMap { (access) -> (String, Access, Bool)? in + return try accessValues.allKeys.compactMap { (access) -> (String, Access, Bool)? in guard let value = try accessValues.decodeIfPresent(Bool.self, forKey: access) else { return nil } diff --git a/Sources/ParseSwift/Types/Query.swift b/Sources/ParseSwift/Types/Query.swift index 2eb2a236d..8a952379c 100644 --- a/Sources/ParseSwift/Types/Query.swift +++ b/Sources/ParseSwift/Types/Query.swift @@ -112,10 +112,10 @@ internal struct QueryWhere: Encodable { public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: RawCodingKey.self) try _constraints.forEach { (key, value) in - var c = container.nestedContainer(keyedBy: QueryConstraint.Comparator.self, + var nestedContainer = container.nestedContainer(keyedBy: QueryConstraint.Comparator.self, forKey: .key(key)) try value.forEach { (constraint) in - try constraint.encode(to: c.superEncoder(forKey: constraint.comparator)) + try constraint.encode(to: nestedContainer.superEncoder(forKey: constraint.comparator)) } } } diff --git a/Tests/AnyCodableTests/AnyCodableTests.swift b/Tests/AnyCodableTests/AnyCodableTests.swift new file mode 100755 index 000000000..ea58f654c --- /dev/null +++ b/Tests/AnyCodableTests/AnyCodableTests.swift @@ -0,0 +1,74 @@ +import XCTest +@testable import ParseSwift + +class AnyCodableTests: XCTestCase { + func testJSONDecoding() { + let json = """ + { + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": { + "a": "alpha", + "b": "bravo", + "c": "charlie" + } + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + let dictionary = try! decoder.decode([String: AnyCodable].self, from: json) + + XCTAssertEqual(dictionary["boolean"]?.value as! Bool, true) + XCTAssertEqual(dictionary["integer"]?.value as! Int, 1) + XCTAssertEqual(dictionary["double"]?.value as! Double, 3.14159265358979323846, accuracy: 0.001) + XCTAssertEqual(dictionary["string"]?.value as! String, "string") + XCTAssertEqual(dictionary["array"]?.value as! [Int], [1, 2, 3]) + XCTAssertEqual(dictionary["nested"]?.value as! [String: String], ["a": "alpha", "b": "bravo", "c": "charlie"]) + } + + func testJSONEncoding() { + let dictionary: [String: AnyCodable] = [ + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": [ + "a": "alpha", + "b": "bravo", + "c": "charlie" + ] + ] + + let encoder = JSONEncoder() + + let json = try! encoder.encode(dictionary) + let encodedJSONObject = try! JSONSerialization.jsonObject(with: json, options: []) as! NSDictionary + + let expected = """ + { + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": { + "a": "alpha", + "b": "bravo", + "c": "charlie" + } + } + """.data(using: .utf8)! + let expectedJSONObject = try! JSONSerialization.jsonObject(with: expected, options: []) as! NSDictionary + + XCTAssertEqual(encodedJSONObject, expectedJSONObject) + } + + static var allTests = [ + ("testJSONDecoding", testJSONDecoding), + ("testJSONEncoding", testJSONEncoding), + ] +} diff --git a/Tests/AnyCodableTests/AnyDecodableTests.swift b/Tests/AnyCodableTests/AnyDecodableTests.swift new file mode 100755 index 000000000..82c3d0a4e --- /dev/null +++ b/Tests/AnyCodableTests/AnyDecodableTests.swift @@ -0,0 +1,35 @@ +import XCTest +@testable import ParseSwift + +class AnyDecodableTests: XCTestCase { + func testJSONDecoding() { + let json = """ + { + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": { + "a": "alpha", + "b": "bravo", + "c": "charlie" + } + } + """.data(using: .utf8)! + + let decoder = JSONDecoder() + let dictionary = try! decoder.decode([String: AnyDecodable].self, from: json) + + XCTAssertEqual(dictionary["boolean"]?.value as! Bool, true) + XCTAssertEqual(dictionary["integer"]?.value as! Int, 1) + XCTAssertEqual(dictionary["double"]?.value as! Double, 3.14159265358979323846, accuracy: 0.001) + XCTAssertEqual(dictionary["string"]?.value as! String, "string") + XCTAssertEqual(dictionary["array"]?.value as! [Int], [1, 2, 3]) + XCTAssertEqual(dictionary["nested"]?.value as! [String: String], ["a": "alpha", "b": "bravo", "c": "charlie"]) + } + + static var allTests = [ + ("testJSONDecoding", testJSONDecoding), + ] +} diff --git a/Tests/AnyCodableTests/AnyEncodableTests.swift b/Tests/AnyCodableTests/AnyEncodableTests.swift new file mode 100755 index 000000000..994aed204 --- /dev/null +++ b/Tests/AnyCodableTests/AnyEncodableTests.swift @@ -0,0 +1,47 @@ +import XCTest +@testable import ParseSwift + +class AnyEncodableTests: XCTestCase { + + func testJSONEncoding() { + let dictionary: [String: AnyEncodable] = [ + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": [ + "a": "alpha", + "b": "bravo", + "c": "charlie" + ] + ] + + let encoder = JSONEncoder() + + let json = try! encoder.encode(dictionary) + let encodedJSONObject = try! JSONSerialization.jsonObject(with: json, options: []) as! NSDictionary + + let expected = """ + { + "boolean": true, + "integer": 1, + "double": 3.14159265358979323846, + "string": "string", + "array": [1, 2, 3], + "nested": { + "a": "alpha", + "b": "bravo", + "c": "charlie" + } + } + """.data(using: .utf8)! + let expectedJSONObject = try! JSONSerialization.jsonObject(with: expected, options: []) as! NSDictionary + + XCTAssertEqual(encodedJSONObject, expectedJSONObject) + } + + static var allTests = [ + ("testJSONEncoding", testJSONEncoding), + ] +} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index ceec89c4b..62e83af7a 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -2,5 +2,6 @@ import XCTest @testable import ParseSwiftTests XCTMain([ - testCase(ParseSwiftTests.allTests) + testCase(ParseSwiftTests.allTests), + testCase(AnyCodableTests.allTests) ]) diff --git a/Tests/ParseSwiftTests/KeychainStoreTests.swift b/Tests/ParseSwiftTests/KeychainStoreTests.swift index 560320ff2..69433a356 100644 --- a/Tests/ParseSwiftTests/KeychainStoreTests.swift +++ b/Tests/ParseSwiftTests/KeychainStoreTests.swift @@ -10,7 +10,6 @@ import Foundation import XCTest @testable import ParseSwift - class KeychainStoreTests: XCTestCase { var testStore: KeychainStore! override func setUp() { @@ -71,12 +70,11 @@ class KeychainStoreTests: XCTestCase { } func testSetComplextObject() { - let complexObject: [Any] = [["key": "value"], "string2", 1234, NSNull()] + let complexObject: [AnyCodable] = [["key": "value"], "string2", 1234, nil] testStore["complexObject"] = complexObject - guard let retrievedObject: [Any] = testStore["complexObject"] else { + guard let retrievedObject: [AnyCodable] = testStore["complexObject"] else { return XCTFail("Should retrieve the object") } - XCTAssertTrue(retrievedObject.count == 4) retrievedObject.enumerated().forEach { (offset, retrievedValue) in let value = complexObject[offset] @@ -111,7 +109,7 @@ class KeychainStoreTests: XCTestCase { func testRemoveObject() { testStore["key1"] = "value1" - XCTAssertNotNil(testStore["key1"]!, "The value should be set") + XCTAssertNotNil(testStore[string: "key1"], "The value should be set") _ = testStore.removeObject(forKey: "key1") let key1Val: String? = testStore["key1"] XCTAssertNil(key1Val, "There should be no value after removal") @@ -119,7 +117,7 @@ class KeychainStoreTests: XCTestCase { func testRemoveObjectSubscript() { testStore["key1"] = "value1" - XCTAssertNotNil(testStore["key1"]!, "The value should be set") + XCTAssertNotNil(testStore[string: "key1"], "The value should be set") testStore[string: "key1"] = nil let key1Val: String? = testStore["key1"] XCTAssertNil(key1Val, "There should be no value after removal") @@ -128,8 +126,8 @@ class KeychainStoreTests: XCTestCase { func testRemoveAllObjects() { testStore["key1"] = "value1" testStore["key2"] = "value2" - XCTAssertNotNil(testStore["key1"]!, "The value should be set") - XCTAssertNotNil(testStore["key2"]!, "The value should be set") + XCTAssertNotNil(testStore[string: "key1"], "The value should be set") + XCTAssertNotNil(testStore[string: "key2"], "The value should be set") _ = testStore.removeAllObjects() let key1Val: String? = testStore["key1"] let key2Val: String? = testStore["key1"]