diff --git a/.gitignore b/.gitignore index d53404493..b012ddbf5 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ ## Build generated build/ DerivedData/ +.swiftpm ## Various settings *.pbxuser diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 5186d0706..000000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -4.0 diff --git a/.travis.yml b/.travis.yml index 45c345e8c..bed3ebfdc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ -osx_image: xcode9 -language: objective-c +osx_image: xcode11.5 +language: swift stage: Build env: matrix: diff --git a/Package.swift b/Package.swift index c9afde243..538401d2c 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:4.0 +// swift-tools-version:5.0 import PackageDescription diff --git a/Parse.xcworkspace/contents.xcworkspacedata b/Parse.xcworkspace/contents.xcworkspacedata index a1620fe6d..d347cb931 100644 --- a/Parse.xcworkspace/contents.xcworkspacedata +++ b/Parse.xcworkspace/contents.xcworkspacedata @@ -2,6 +2,6 @@ + location = "container:ParseSwift.xcodeproj"> diff --git a/ParseSwift.playground/Pages/1- Your first Object.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/1- Your first Object.xcplaygroundpage/Contents.swift index 64f7b1114..11f608a5f 100644 --- a/ParseSwift.playground/Pages/1- Your first Object.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/1- Your first Object.xcplaygroundpage/Contents.swift @@ -10,11 +10,6 @@ PlaygroundPage.current.needsIndefiniteExecution = true initializeParse() -ParseSwift.initialize(applicationId: "applicationId", - clientKey: "clientKey", - masterKey: "masterKey", - serverURL: URL(string: "http://localhost:1337/1")!) - struct GameScore: ParseSwift.ObjectType { //: Those are required for Object var objectId: String? @@ -33,18 +28,18 @@ struct GameScore: ParseSwift.ObjectType { var score = GameScore(score: 10) -guard let score = try? score.save() else { fatalError() } -assert(score.objectId != nil) -assert(score.createdAt != nil) -assert(score.updatedAt != nil) -assert(score.score == 10) +guard let savedScore = try? score.save() else { fatalError() } +assert(savedScore.objectId != nil) +assert(savedScore.createdAt != nil) +assert(savedScore.updatedAt != nil) +assert(savedScore.score == 10) // Need to make it a var as Value Types -var changedScore = score +var changedScore = savedScore changedScore.score = 200 -guard let savedScore = try? changedScore.save() else { fatalError() } -assert(score.score == 10) -assert(score.objectId == changedScore.objectId) +guard let savedChangedScore = try? changedScore.save() else { fatalError() } +assert(savedChangedScore.score == 200) +assert(savedChangedScore.objectId == changedScore.objectId) let score2 = GameScore(score: 3) guard let results = try? GameScore.saveAll(score, score2) else { fatalError() } diff --git a/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift b/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift index c7578568e..3ba81a5fd 100644 --- a/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift +++ b/ParseSwift.playground/Pages/3- Users.xcplaygroundpage/Contents.swift @@ -1,4 +1,5 @@ //: [Previous](@previous) + import PlaygroundSupport import Foundation PlaygroundPage.current.needsIndefiniteExecution = true @@ -54,10 +55,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.playground/contents.xcplayground b/ParseSwift.playground/contents.xcplayground index 2efd0263e..5deb0f128 100644 --- a/ParseSwift.playground/contents.xcplayground +++ b/ParseSwift.playground/contents.xcplayground @@ -1,5 +1,5 @@ - + diff --git a/ParseSwift.podspec b/ParseSwift.podspec index e4b49d585..052c0a755 100644 --- a/ParseSwift.podspec +++ b/ParseSwift.podspec @@ -10,9 +10,11 @@ Pod::Spec.new do |s| :git => "#{s.homepage}.git", :tag => "#{s.version}", } + s.pod_target_xcconfig = { 'SWIFT_VERSION' => '5.0' } s.ios.deployment_target = "8.0" s.osx.deployment_target = "10.10" s.tvos.deployment_target = "9.0" + s.swift_versions = ['5.0'] s.source_files = "Sources/ParseSwift/**/*.swift" s.license = { :type => "MIT", @@ -23,4 +25,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..cbe504ea8 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, ); }; }; + 709075C724B9117500B95310 /* AnyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709075C424B9117400B95310 /* AnyCodable.swift */; }; + 709075C824B9117500B95310 /* AnyCodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709075C424B9117400B95310 /* AnyCodable.swift */; }; + 709075C924B9117500B95310 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709075C524B9117500B95310 /* AnyDecodable.swift */; }; + 709075CA24B9117500B95310 /* AnyDecodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709075C524B9117500B95310 /* AnyDecodable.swift */; }; + 709075CB24B9117500B95310 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709075C624B9117500B95310 /* AnyEncodable.swift */; }; + 709075CC24B9117500B95310 /* AnyEncodable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 709075C624B9117500B95310 /* 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 = ""; }; + 709075C424B9117400B95310 /* AnyCodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyCodable.swift; sourceTree = ""; }; + 709075C524B9117500B95310 /* AnyDecodable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AnyDecodable.swift; sourceTree = ""; }; + 709075C624B9117500B95310 /* 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 */ @@ -253,6 +268,7 @@ 4AA8076C1F794C1C008CD551 /* ParseSwiftTests */ = { isa = PBXGroup; children = ( + 7FFF552A2217E729007C3B4E /* AnyCodableTests */, 4AA8076D1F794C1C008CD551 /* Info.plist */, 4AA8076E1F794C1C008CD551 /* KeychainStoreTests.swift */, 4AA8076F1F794C1C008CD551 /* ParseSwiftTests.swift */, @@ -291,6 +307,7 @@ 4A2F14901F4A41B900A7A7EF /* Asynchronous.swift */, 4AC397761F4895A200DEA9D3 /* Objects Protocols */, 4A82B7FD1F25691B0063D731 /* Types */, + 709075CD24B9829600B95310 /* AnyCodable */, 4A2F149C1F4A604900A7A7EF /* Encoder */, 4A99A46A1F2650E200D72A59 /* Mutations */, 4AC397771F4895C000DEA9D3 /* API */, @@ -332,6 +349,26 @@ path = "ParseSwift-macOS"; sourceTree = ""; }; + 709075CD24B9829600B95310 /* AnyCodable */ = { + isa = PBXGroup; + children = ( + 709075C424B9117400B95310 /* AnyCodable.swift */, + 709075C524B9117500B95310 /* AnyDecodable.swift */, + 709075C624B9117500B95310 /* 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,25 +472,26 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0900; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1010; ORGANIZATIONNAME = Parse; TargetAttributes = { 4AA807571F794242008CD551 = { CreatedOnToolsVersion = 9.0; + LastSwiftMigration = 1130; ProvisioningStyle = Automatic; }; 4AB8B4F31F254AE10070F682 = { CreatedOnToolsVersion = 9.0; - LastSwiftMigration = 0900; + LastSwiftMigration = 1130; }; 4AB8B4FC1F254AE10070F682 = { CreatedOnToolsVersion = 9.0; - LastSwiftMigration = 0900; + LastSwiftMigration = 1130; TestTargetID = 4AA807571F794242008CD551; }; 4AFDA7111F26D9A5002AE4FC = { CreatedOnToolsVersion = 9.0; - LastSwiftMigration = 0900; + LastSwiftMigration = 1130; }; }; }; @@ -557,6 +595,7 @@ buildActionMask = 2147483647; files = ( 4A2F14961F4A5F2900A7A7EF /* Responses.swift in Sources */, + 709075C724B9117500B95310 /* AnyCodable.swift in Sources */, 4AC397741F488FF900DEA9D3 /* API.swift in Sources */, 4A82B7F71F254CCE0063D731 /* ObjectType.swift in Sources */, 4A82B7F51F254CCE0063D731 /* GeoPoint.swift in Sources */, @@ -571,6 +610,7 @@ 4A82B7FF1F256A8F0063D731 /* Query.swift in Sources */, 4AF85BD81F78144900665264 /* URLSession+sync.swift in Sources */, 4A2F14981F4A5F6900A7A7EF /* ObjectType+Batch.swift in Sources */, + 709075CB24B9117500B95310 /* AnyEncodable.swift in Sources */, 4A82B7F41F254CCE0063D731 /* File.swift in Sources */, 4AF85BD31F78011100665264 /* ParseError.swift in Sources */, 4AF85BDA1F781ED000665264 /* Asynchronous.swift in Sources */, @@ -580,6 +620,7 @@ 4AEBA54D1F26523800628B17 /* DeleteOperation.swift in Sources */, 4AF85BDD1F783B9800665264 /* API+Commands.swift in Sources */, 4A82B7F81F254CCE0063D731 /* Pointer.swift in Sources */, + 709075C924B9117500B95310 /* AnyDecodable.swift in Sources */, 4AEBA5491F26519B00628B17 /* AddOperation.swift in Sources */, 4A99A46C1F2650FF00D72A59 /* AddUniqueOperation.swift in Sources */, 4AEBA54B1F2651D900628B17 /* RemoveOperation.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 */, ); @@ -600,6 +644,7 @@ buildActionMask = 2147483647; files = ( 4AFDA72B1F26DAE1002AE4FC /* ObjectType.swift in Sources */, + 709075C824B9117500B95310 /* AnyCodable.swift in Sources */, 4AFDA7301F26DAE1002AE4FC /* DeleteOperation.swift in Sources */, 4AFDA7361F26DAE1002AE4FC /* ObjectType+Query.swift in Sources */, 4AFDA7341F26DAE1002AE4FC /* Pointer.swift in Sources */, @@ -614,6 +659,7 @@ 4AFDA7311F26DAE1002AE4FC /* AddUniqueOperation.swift in Sources */, 4AF85BD91F78145C00665264 /* URLSession+sync.swift in Sources */, 4A2F149B1F4A5FBA00A7A7EF /* ObjectType+Batch.swift in Sources */, + 709075CC24B9117500B95310 /* AnyEncodable.swift in Sources */, 4A6511521F48E406005237DF /* GeoPoint.swift in Sources */, 4AF85BD61F7803C100665264 /* ParseError.swift in Sources */, 4AF85BDB1F781ED100665264 /* Asynchronous.swift in Sources */, @@ -623,6 +669,7 @@ 4AFDA7321F26DAE1002AE4FC /* IncrementOperation.swift in Sources */, 4AF85BDE1F783BB400665264 /* API+Commands.swift in Sources */, 4AFDA72D1F26DAE1002AE4FC /* ParseMutationContainer.swift in Sources */, + 709075CA24B9117500B95310 /* AnyDecodable.swift in Sources */, 4AFDA72C1F26DAE1002AE4FC /* UserType.swift in Sources */, 4A6511511F48E406005237DF /* ACL.swift in Sources */, 4AFDA72F1F26DAE1002AE4FC /* RemoveOperation.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 = 5.0; 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 = 5.0; 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 = 5.0; 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 = 5.0; 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 = 5.0; 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 = 5.0; 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 = 5.0; }; name = Debug; }; @@ -919,7 +970,7 @@ SDKROOT = macosx; SKIP_INSTALL = YES; SWIFT_SWIFT3_OBJC_INFERENCE = Off; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/ParseSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/ParseSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 000000000..18d981003 --- /dev/null +++ b/ParseSwift.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + 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/ParseSwift/API/API+Commands.swift b/Sources/ParseSwift/API/API+Commands.swift index 86a458d28..60e79a02a 100644 --- a/Sources/ParseSwift/API/API+Commands.swift +++ b/Sources/ParseSwift/API/API+Commands.swift @@ -9,8 +9,8 @@ import Foundation internal extension API { - internal struct Command: Encodable where T: Encodable { - typealias ReturnType = U + struct Command: Encodable where T: Encodable { + typealias ReturnType = U // swiftlint:disable:this nesting let method: API.Method let path: API.Endpoint let body: T? @@ -63,7 +63,7 @@ internal extension API { internal extension API.Command { // MARK: Saving - internal static func saveCommand(_ object: T) -> API.Command where T: ObjectType { + static func saveCommand(_ object: T) -> API.Command where T: ObjectType { if object.isSaved { return updateCommand(object) } @@ -92,7 +92,7 @@ internal extension API.Command { } // MARK: Fetching - internal static func fetchCommand(_ object: T) throws -> API.Command where T: ObjectType { + static func fetchCommand(_ object: T) throws -> API.Command where T: ObjectType { guard object.isSaved else { throw ParseError(code: .unknownError, message: "Cannot Fetch an object without id") } @@ -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/API/API.swift b/Sources/ParseSwift/API/API.swift index dac960380..b559d5901 100644 --- a/Sources/ParseSwift/API/API.swift +++ b/Sources/ParseSwift/API/API.swift @@ -56,20 +56,16 @@ public struct API { case installationId(String) // use HashValue so we can use in a sets - public var hashValue: Int { + public func hash(into hasher: inout Hasher) { switch self { case .useMasterKey: - return 1 + hasher.combine(1) case .sessionToken: - return 2 + hasher.combine(2) case .installationId: - return 3 + hasher.combine(3) } } - - public static func == (lhs: API.Option, rhs: API.Option) -> Bool { - return lhs.hashValue == rhs.hashValue - } } internal static func getHeaders(options: API.Options) -> [String: String] { diff --git a/Sources/ParseSwift/API/URLSession+sync.swift b/Sources/ParseSwift/API/URLSession+sync.swift index 5fb058841..2e97956c5 100644 --- a/Sources/ParseSwift/API/URLSession+sync.swift +++ b/Sources/ParseSwift/API/URLSession+sync.swift @@ -23,7 +23,7 @@ extension URLSession { semaphore.wait() guard let responseData = data else { guard let error = error else { - throw NSError(domain: "unknown", code: -1, userInfo: ["response": response!]) + throw ParseError(code: .unknownError, message: "Unable to sync data: \(response!).") } throw error } diff --git a/Sources/ParseSwift/AnyCodable/AnyCodable.swift b/Sources/ParseSwift/AnyCodable/AnyCodable.swift new file mode 100755 index 000000000..e73a93c14 --- /dev/null +++ b/Sources/ParseSwift/AnyCodable/AnyCodable.swift @@ -0,0 +1,100 @@ +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` + + Source: https://github.com/Flight-School/AnyCodable + */ +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 { // swiftlint:disable:this cyclomatic_complexity line_length + 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 {} +extension AnyCodable: ExpressibleByBooleanLiteral {} +extension AnyCodable: ExpressibleByIntegerLiteral {} +extension AnyCodable: ExpressibleByFloatLiteral {} +extension AnyCodable: ExpressibleByStringLiteral {} +extension AnyCodable: ExpressibleByArrayLiteral {} +extension AnyCodable: ExpressibleByDictionaryLiteral {} diff --git a/Sources/ParseSwift/AnyCodable/AnyDecodable.swift b/Sources/ParseSwift/AnyCodable/AnyDecodable.swift new file mode 100755 index 000000000..50a4df79d --- /dev/null +++ b/Sources/ParseSwift/AnyCodable/AnyDecodable.swift @@ -0,0 +1,136 @@ +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 { // swiftlint:disable:this cyclomatic_complexity line_length + 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/ParseSwift/AnyCodable/AnyEncodable.swift b/Sources/ParseSwift/AnyCodable/AnyEncodable.swift new file mode 100755 index 000000000..6c373bab5 --- /dev/null +++ b/Sources/ParseSwift/AnyCodable/AnyEncodable.swift @@ -0,0 +1,232 @@ +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) + + Source: https://github.com/Flight-School/AnyCodable + */ +public struct AnyEncodable: Encodable { + public let value: Any + public init(_ value: T?) { + self.value = value ?? () + } +} + +@usableFromInline +protocol _AnyEncodable { + var value: Any { get } + init(_ value: T?) +} + +extension AnyEncodable: _AnyEncodable {} + +// MARK: - Encodable + +extension _AnyEncodable { + public func encode(to encoder: Encoder) throws { // swiftlint:disable:this cyclomatic_complexity function_body_length line_length + 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) + } + } + private func encode(nsnumber: NSNumber, into container: inout SingleValueEncodingContainer) throws { // swiftlint:disable:this cyclomatic_complexity line_length + switch CFNumberGetType(nsnumber) { + case .charType: + try container.encode(nsnumber.boolValue) + case .sInt8Type: + try container.encode(nsnumber.int8Value) + case .sInt16Type: + try container.encode(nsnumber.int16Value) + case .sInt32Type: + try container.encode(nsnumber.int32Value) + case .sInt64Type: + try container.encode(nsnumber.int64Value) + case .shortType: + try container.encode(nsnumber.uint16Value) + case .longType: + try container.encode(nsnumber.uint32Value) + case .longLongType: + try container.encode(nsnumber.uint64Value) + case .intType, .nsIntegerType, .cfIndexType: + try container.encode(nsnumber.intValue) + case .floatType, .float32Type: + try container.encode(nsnumber.floatValue) + case .doubleType, .float64Type, .cgFloatType: + try container.encode(nsnumber.doubleValue) + @unknown default: + fatalError() + } + } +} + +extension AnyEncodable: Equatable { + public static func == (lhs: AnyEncodable, rhs: AnyEncodable) -> Bool { // swiftlint:disable:this cyclomatic_complexity line_length + 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 {} +extension AnyEncodable: ExpressibleByBooleanLiteral {} +extension AnyEncodable: ExpressibleByIntegerLiteral {} +extension AnyEncodable: ExpressibleByFloatLiteral {} +extension AnyEncodable: ExpressibleByStringLiteral {} +extension AnyEncodable: ExpressibleByArrayLiteral {} +extension AnyEncodable: 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([AnyHashable: Any](elements, uniquingKeysWith: { (first, _) in first })) + } +} diff --git a/Sources/ParseSwift/Asynchronous.swift b/Sources/ParseSwift/Asynchronous.swift index 03a284abc..cfcf8aa8a 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) } } } @@ -47,16 +47,16 @@ extension Querying { } public extension ObjectType { - public static func saveAll(options: API.Options = [], - _ objects: Self..., - callback: @escaping ([(Self, ParseError?)]?, Error?) -> Void) { + static func saveAll(options: API.Options = [], + _ objects: Self..., + callback: @escaping ([(Self, ParseError?)]?, Error?) -> Void) { objects.saveAll(options: options, callback: callback) } } public extension Sequence where Element: ObjectType { - public func saveAll(options: API.Options = [], - callback: @escaping ([(Element, ParseError?)]?, Error?) -> Void) { + func saveAll(options: API.Options = [], + callback: @escaping ([(Element, ParseError?)]?, Error?) -> Void) { runAsync(options: options, function: self.saveAll, callback: callback) } } diff --git a/Sources/ParseSwift/Encoder/ParseEncoder.swift b/Sources/ParseSwift/Encoder/ParseEncoder.swift index 76f6d4635..9a17fe778 100644 --- a/Sources/ParseSwift/Encoder/ParseEncoder.swift +++ b/Sources/ParseSwift/Encoder/ParseEncoder.swift @@ -841,7 +841,7 @@ fileprivate extension EncodingError { /// - parameter value: The value that was invalid to encode. /// - parameter path: The path of `CodingKey`s taken to encode this value. /// - returns: An `EncodingError` with the appropriate path and debug description. - fileprivate static func _invalidFloatingPointValue(_ value: T, at codingPath: [CodingKey]) -> EncodingError { + static func _invalidFloatingPointValue(_ value: T, at codingPath: [CodingKey]) -> EncodingError { let valueDescription: String if value == T.infinity { valueDescription = "\(T.self).infinity" diff --git a/Sources/ParseSwift/Objects Protocols/ObjectType+Batch.swift b/Sources/ParseSwift/Objects Protocols/ObjectType+Batch.swift index 9b38b9bac..8b2823ecc 100644 --- a/Sources/ParseSwift/Objects Protocols/ObjectType+Batch.swift +++ b/Sources/ParseSwift/Objects Protocols/ObjectType+Batch.swift @@ -9,7 +9,7 @@ import Foundation public extension ObjectType { - public static func saveAll(_ objects: Self...) throws -> [(Self, ParseError?)] { + static func saveAll(_ objects: Self...) throws -> [(Self, ParseError?)] { return try objects.saveAll() } } diff --git a/Sources/ParseSwift/Objects Protocols/ObjectType+Query.swift b/Sources/ParseSwift/Objects Protocols/ObjectType+Query.swift index 03fe8d1d5..c963094d3 100644 --- a/Sources/ParseSwift/Objects Protocols/ObjectType+Query.swift +++ b/Sources/ParseSwift/Objects Protocols/ObjectType+Query.swift @@ -9,15 +9,15 @@ import Foundation public extension ObjectType { - public static func find() throws -> [Self] { + static func find() throws -> [Self] { return try query().find() } - public static func query() -> Query { + static func query() -> Query { return Query() } - public static func query(_ constraints: QueryConstraint...) -> Query { + static func query(_ constraints: QueryConstraint...) -> Query { return Query(constraints) } } diff --git a/Sources/ParseSwift/Objects Protocols/ObjectType.swift b/Sources/ParseSwift/Objects Protocols/ObjectType.swift index 4693850a5..aecca686b 100644 --- a/Sources/ParseSwift/Objects Protocols/ObjectType.swift +++ b/Sources/ParseSwift/Objects Protocols/ObjectType.swift @@ -43,7 +43,7 @@ public protocol ObjectType: Fetching, Saving, CustomDebugStringConvertible, Equa } internal extension ObjectType { - internal func getEncoder() -> ParseEncoder { + func getEncoder() -> ParseEncoder { return getParseEncoder() } } @@ -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 @@ -102,10 +102,10 @@ let dateEncodingStrategy: JSONEncoder.DateEncodingStrategy = .custom({ (date, en }) internal extension Date { - internal func parseFormatted() -> String { + func parseFormatted() -> String { return dateFormatter.string(from: self) } - internal var parseRepresentation: [String: String] { + var parseRepresentation: [String: String] { return ["__type": "Date", "iso": parseFormatted()] } } @@ -115,13 +115,13 @@ 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)! } } - throw NSError(domain: "", code: -1, userInfo: nil) + throw ParseError(code: .unknownError, message: "unable to decode") }) func getJSONEncoder() -> JSONEncoder { @@ -137,7 +137,7 @@ func getParseEncoder() -> ParseEncoder { encoder.dateEncodingStrategy = parseDateEncodingStrategy encoder.shouldEncodeKey = { (key, path) -> Bool in if path.count == 0 // top level - && forbiddenKeys.index(of: key) != nil { + && forbiddenKeys.firstIndex(of: key) != nil { return false } return true @@ -161,11 +161,11 @@ func getDecoder() -> JSONDecoder { } public extension ObjectType { - public func save(options: API.Options) throws -> Self { + func save(options: API.Options) throws -> Self { return try saveCommand().execute(options: options) } - public func fetch(options: API.Options) throws -> Self { + func fetch(options: API.Options) throws -> Self { return try fetchCommand().execute(options: options) } diff --git a/Sources/ParseSwift/Storage/KeychainStore.swift b/Sources/ParseSwift/Storage/KeychainStore.swift index 896c9c90e..20a0feccd 100644 --- a/Sources/ParseSwift/Storage/KeychainStore.swift +++ b/Sources/ParseSwift/Storage/KeychainStore.swift @@ -10,7 +10,7 @@ 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 +25,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) } @@ -42,28 +42,37 @@ struct KeychainStore: SecureStorage { }) else { return nil } - return NSKeyedUnarchiver.unarchiveObject(with: data) as? T + do { + let object = try JSONDecoder().decode(T.self, from: data) + return object + } catch { + return nil + } } func set(object: T?, forKey key: String) -> Bool where T: Encodable { guard let object = object else { return removeObject(forKey: key) } - let data = NSKeyedArchiver.archivedData(withRootObject: object) - let query = keychainQuery(forKey: key) - let update = [ - kSecValueData as String: data - ] - - let status = synchronizationQueue.sync(flags: .barrier) { () -> OSStatus in - if self.data(forKey: key) != nil { - return SecItemUpdate(query as CFDictionary, update as CFDictionary) + do { + let data = try JSONEncoder().encode(object) + let query = keychainQuery(forKey: key) + let update = [ + kSecValueData as String: data + ] + + let status = synchronizationQueue.sync(flags: .barrier) { () -> OSStatus in + if self.data(forKey: key) != nil { + return SecItemUpdate(query as CFDictionary, update as CFDictionary) + } + let mergedQuery = query.merging(update) { (_, otherValue) -> Any in otherValue } + return SecItemAdd(mergedQuery as CFDictionary, nil) } - let mergedQuery = query.merging(update) { (_, otherValue) -> Any in otherValue } - return SecItemAdd(mergedQuery as CFDictionary, nil) - } - return status == errSecSuccess + return status == errSecSuccess + } catch { + return false + } } subscript(key: String) -> T? where T: Codable { 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/File.swift b/Sources/ParseSwift/Types/File.swift index c57e93c5c..4d933021a 100644 --- a/Sources/ParseSwift/Types/File.swift +++ b/Sources/ParseSwift/Types/File.swift @@ -20,7 +20,7 @@ public struct File: Saving, Fetching { public func encode(to encoder: Encoder) throws { if data == nil && url == nil { - throw NSError(domain: "cannot encode file", code: -1, userInfo: nil) + throw ParseError(code: .unknownError, message: "cannot encode file") } var container = encoder.container(keyedBy: CodingKeys.self) if let url = url { 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/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/AnyCodableTests/AnyCodableTests.swift b/Tests/ParseSwiftTests/AnyCodableTests/AnyCodableTests.swift new file mode 100755 index 000000000..b53555b29 --- /dev/null +++ b/Tests/ParseSwiftTests/AnyCodableTests/AnyCodableTests.swift @@ -0,0 +1,93 @@ +import XCTest +@testable import ParseSwift + +class AnyCodableTests: XCTestCase { + func testJSONDecoding() { + guard 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) else { + XCTFail("Should unrap data as utf8") + return + } + let decoder = JSONDecoder() + do { + let dictionary = try decoder.decode([String: AnyCodable].self, from: json) + XCTAssertEqual(dictionary["boolean"]?.value as? Bool, true) + XCTAssertEqual(dictionary["integer"]?.value as? Int, 1) + guard let doubleValue = dictionary["double"]?.value as? Double else { + XCTFail("Should unrap value as Double") + return + } + XCTAssertEqual(doubleValue, 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"]) + } catch { + XCTFail(error.localizedDescription) + } + } + 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" + ] + ] + do { + let encoder = JSONEncoder() + let json = try encoder.encode(dictionary) + guard let encodedJSONObject = + try JSONSerialization.jsonObject(with: json, options: []) as? NSDictionary else { + XCTFail("Should unrap JSON serialized object") + return + } + guard 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) else { + XCTFail("Should unrap data to utf8") + return + } + guard let expectedJSONObject = + try JSONSerialization.jsonObject(with: expected, options: []) as? NSDictionary else { + XCTFail("Should unrap JSON serialized object") + return + } + XCTAssertEqual(encodedJSONObject, expectedJSONObject) + } catch { + XCTFail(error.localizedDescription) + } + } + static var allTests = [ + ("testJSONDecoding", testJSONDecoding), + ("testJSONEncoding", testJSONEncoding) + ] +} diff --git a/Tests/ParseSwiftTests/AnyCodableTests/AnyDecodableTests.swift b/Tests/ParseSwiftTests/AnyCodableTests/AnyDecodableTests.swift new file mode 100755 index 000000000..f24c2cd51 --- /dev/null +++ b/Tests/ParseSwiftTests/AnyCodableTests/AnyDecodableTests.swift @@ -0,0 +1,44 @@ +import XCTest +@testable import ParseSwift + +class AnyDecodableTests: XCTestCase { + func testJSONDecoding() { + guard 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) else { + XCTFail("Should unrap data as utf8") + return + } + do { + 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) + guard let doubleValue = dictionary["double"]?.value as? Double else { + XCTFail("Should unrap data as Double") + return + } + XCTAssertEqual(doubleValue, 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"]) + } catch { + XCTFail(error.localizedDescription) + } + } + static var allTests = [ + ("testJSONDecoding", testJSONDecoding) + ] +} diff --git a/Tests/ParseSwiftTests/AnyCodableTests/AnyEncodableTests.swift b/Tests/ParseSwiftTests/AnyCodableTests/AnyEncodableTests.swift new file mode 100755 index 000000000..2667d1d3f --- /dev/null +++ b/Tests/ParseSwiftTests/AnyCodableTests/AnyEncodableTests.swift @@ -0,0 +1,56 @@ +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" + ] + ] + do { + let encoder = JSONEncoder() + let json = try encoder.encode(dictionary) + guard let encodedJSONObject = + try JSONSerialization.jsonObject(with: json, options: []) as? NSDictionary else { + XCTFail("Should encode JSON object") + return + } + guard 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) else { + XCTFail("Should unrap data to utf8") + return + } + guard let expectedJSONObject = + try JSONSerialization.jsonObject(with: expected, options: []) as? NSDictionary else { + XCTFail("Should unrap serialized json object") + return + } + XCTAssertEqual(encodedJSONObject, expectedJSONObject) + } catch { + XCTFail(error.localizedDescription) + } + } + static var allTests = [ + ("testJSONEncoding", testJSONEncoding) + ] +} diff --git a/Tests/ParseSwiftTests/KeychainStoreTests.swift b/Tests/ParseSwiftTests/KeychainStoreTests.swift index 560320ff2..2c982ad9a 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() { @@ -31,7 +30,10 @@ class KeychainStoreTests: XCTestCase { let key = "yarrKey" let value = "yarrValue" testStore[key] = value - let storedValue: String = testStore.object(forKey: key)! + guard let storedValue: String = testStore.object(forKey: key) else { + XCTFail("Should unwrap to String") + return + } XCTAssertEqual(storedValue, value, "Values should be equal after get") } @@ -39,7 +41,11 @@ class KeychainStoreTests: XCTestCase { let key = "yarrKey" let value = "yarrValue" testStore[key] = value - XCTAssertEqual(testStore[key]!, value, "Values should be equal after get") + guard let storedValue: String = testStore[key] else { + XCTFail("Should unwrap to String") + return + } + XCTAssertEqual(storedValue, value, "Values should be equal after get") } func testGetObjectStringTypedSubscript() { @@ -53,7 +59,7 @@ class KeychainStoreTests: XCTestCase { let key = "yarrKey" let value = 1 testStore[key] = value - XCTAssertNil(testStore[string: key], "Values should be equal after get") + XCTAssertNil(testStore[string: key], "Values should be nil after get") } func testGetObjectBoolTypedSubscript() { @@ -67,43 +73,48 @@ class KeychainStoreTests: XCTestCase { let key = "yarrKey" let value = "Yo!" testStore[key] = value - XCTAssertNil(testStore[bool: key], "Values should be equal after get") + XCTAssertNil(testStore[bool: key], "Values should be nil after get") + } + + func testGetAnyCodableObject() { + let key = "yarrKey" + let value: AnyCodable = "yarrValue" + testStore[key] = value + guard let storedValue: AnyCodable = testStore.object(forKey: key) else { + XCTFail("Should unwrap to AnyCodable") + return + } + XCTAssertEqual(storedValue, value, "Values should be equal after get") } func testSetComplextObject() { - let complexObject: [Any] = [["key": "value"], "string2", 1234, NSNull()] + let complexObject: [AnyCodable] = [["key": "value"], "string2", 1234] 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) + XCTAssertTrue(retrievedObject.count == 3) retrievedObject.enumerated().forEach { (offset, retrievedValue) in - let value = complexObject[offset] + let value = complexObject[offset].value switch offset { case 0: guard let dict = value as? [String: String], - let retrivedDict = retrievedValue as? [String: String] else { + let retrievedDictionary = retrievedValue.value as? [String: String] else { return XCTFail("Should be both dictionaries") } - XCTAssertTrue(dict["key"] == retrivedDict["key"]) + XCTAssertTrue(dict == retrievedDictionary) case 1: guard let string = value as? String, - let retrievedString = retrievedValue as? String else { + let retrievedString = retrievedValue.value as? String else { return XCTFail("Should be both strings") } XCTAssertTrue(string == retrievedString) case 2: guard let int = value as? Int, - let retrievedInt = retrievedValue as? Int else { + let retrievedInt = retrievedValue.value as? Int else { return XCTFail("Should be both ints") } XCTAssertTrue(int == retrievedInt) - case 3: - guard let retrieved = retrievedValue as? NSNull else { - return XCTFail("Should be both ints") - } - XCTAssertTrue(retrieved == NSNull()) default: break } } @@ -111,7 +122,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 +130,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 +139,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"]