From d76f6c8b0767f0717968c1ae87cb625e1d1f90da Mon Sep 17 00:00:00 2001 From: Dima Zen Date: Fri, 26 May 2017 18:22:09 +0300 Subject: [PATCH 01/47] Fix core data concurrency violation during saving --- DBClient/CoreData/CoreDataDBClient.swift | 4 +- .../NSManagedObjectContext+Extension.swift | 44 +++++++++++++++++++ 2 files changed, 45 insertions(+), 3 deletions(-) create mode 100644 DBClient/CoreData/NSManagedObjectContext+Extension.swift diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index 9d0aa7e..b616c05 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -245,9 +245,7 @@ public class CoreDataDBClient { let context = writeManagedContext context.perform { closure(context) { - try context.save() - try self.mainContext.save() - try self.rootContext.save() + try context.save(includingParent: true) } } } diff --git a/DBClient/CoreData/NSManagedObjectContext+Extension.swift b/DBClient/CoreData/NSManagedObjectContext+Extension.swift new file mode 100644 index 0000000..1ed12b1 --- /dev/null +++ b/DBClient/CoreData/NSManagedObjectContext+Extension.swift @@ -0,0 +1,44 @@ +// +// NSManagedObjectContext+Extension.swift +// DBClient +// +// Copyright © 2016 Yalantis. All rights reserved. +// + +import Foundation +import CoreData + +extension NSManagedObjectContext { + + func save(includingParent: Bool) throws { + guard hasChanges else { + return + } + + try save() + + if includingParent, let parent = parent { + try performAndWait { + try parent.save(includingParent: true) + } + } + } + + func performAndWait(_ block: @escaping (Void) throws -> Void) throws { + var outError: Error? + + performAndWait { + do { + try block() + } catch { + outError = error + } + } + + // fake rethrowing + if let outError = outError { + throw outError + } + } + +} From 0621abb36b02bef8751e4a07a9c9db31b34cc592 Mon Sep 17 00:00:00 2001 From: Dima Zen Date: Fri, 26 May 2017 18:29:01 +0300 Subject: [PATCH 02/47] Typo fix --- DBClient/CoreData/NSManagedObjectContext+Extension.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DBClient/CoreData/NSManagedObjectContext+Extension.swift b/DBClient/CoreData/NSManagedObjectContext+Extension.swift index 1ed12b1..1d45b0d 100644 --- a/DBClient/CoreData/NSManagedObjectContext+Extension.swift +++ b/DBClient/CoreData/NSManagedObjectContext+Extension.swift @@ -18,7 +18,7 @@ extension NSManagedObjectContext { try save() if includingParent, let parent = parent { - try performAndWait { + try parent.performAndWait { try parent.save(includingParent: true) } } From 02d24d241d353c2f43f8e2e581c5b13a814cd0e8 Mon Sep 17 00:00:00 2001 From: Dima Zen Date: Fri, 26 May 2017 18:38:42 +0300 Subject: [PATCH 03/47] Update Pod dependencies --- Example/Podfile.lock | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 0ae39de..b5068c5 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,10 +1,10 @@ PODS: - Bolts-Swift (1.3.0) - - DBClient/Core (0.4): + - DBClient/Core (0.4.2): - Bolts-Swift (~> 1.3.0) - - DBClient/CoreData (0.4): + - DBClient/CoreData (0.4.2): - DBClient/Core - - DBClient/Realm (0.4): + - DBClient/Realm (0.4.2): - DBClient/Core - RealmSwift (~> 2.1.1) - Realm (2.1.2): @@ -19,14 +19,14 @@ DEPENDENCIES: EXTERNAL SOURCES: DBClient: - :path: ../ + :path: "../" SPEC CHECKSUMS: Bolts-Swift: fa98d1b59fc1acea9b21a21306dcdca1c85e3737 - DBClient: a9404be1db68eff3a9d04d1ea61043e00e39081d + DBClient: 6b80ee2856d2ac0174bdeec7874e3a0af8aaa6e4 Realm: efe855f4d977c8ce5a82d3116d9f1ff155a6550c RealmSwift: 17d6ee30b6f9df86364408c2197492e33bfea567 PODFILE CHECKSUM: e1b66d1226f9ec10a94bb2534dbf6b4ca65549de -COCOAPODS: 1.2.1.beta.1 +COCOAPODS: 1.2.1 From a02f53784877336b41fab30543a6b64bca7201fc Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Thu, 21 Sep 2017 21:20:15 +0300 Subject: [PATCH 04/47] [Fix] warnings --- DBClient.podspec | 4 +- DBClient/Core/DBClient.swift | 4 +- DBClient/CoreData/CoreDataDBClient.swift | 4 +- .../NSManagedObjectContext+Extension.swift | 4 +- DBClient/Realm/RealmDBClient.swift | 8 ++-- Example/DBClientTests/DBClientTest.swift | 2 +- .../Interface/ExecuteTests.swift | 2 +- Example/Example.xcodeproj/project.pbxproj | 42 +++++++++++++++++-- Example/Example/DBClientInjector.swift | 4 +- Example/Podfile.lock | 28 ++++++------- 10 files changed, 69 insertions(+), 33 deletions(-) diff --git a/DBClient.podspec b/DBClient.podspec index 2ae503b..c07bd98 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DBClient" - s.version = "0.4.2" + s.version = "0.5" s.requires_arc = true s.summary = "CoreData & Realm wrapper written on Swift" s.homepage = "" @@ -29,7 +29,7 @@ Pod::Spec.new do |s| s.subspec "Realm" do |spec| spec.dependency "DBClient/Core" spec.source_files = ['DBClient/Realm/*.swift'] - spec.dependency "RealmSwift", "~> 2.1.1" + spec.dependency "RealmSwift", "~> 2.10.1" end end diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index 6db9398..b4b3a55 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -27,13 +27,13 @@ public protocol DBClient { /// /// - Parameter request: request to execute /// - Returns: `Task` with array of objects or error in case of failude. - func execute(_ request: FetchRequest) -> Task<[T]> + func execute(_ request: FetchRequest) -> Task<[T]> /// Creates observable request from given `FetchRequest`. /// /// - Parameter request: fetch request to be observed /// - Returns: observable of for given request. - func observable(for request: FetchRequest) -> RequestObservable + func observable(for request: FetchRequest) -> RequestObservable /// Inserts objects to database. /// diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index b616c05..3d9b5f9 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -263,11 +263,11 @@ public class CoreDataDBClient { extension CoreDataDBClient: DBClient { - public func observable(for request: FetchRequest) -> RequestObservable { + public func observable(for request: FetchRequest) -> RequestObservable { return CoreDataObservable(request: request, context: mainContext) } - public func execute(_ request: FetchRequest) -> Task<[T]> { + public func execute(_ request: FetchRequest) -> Task<[T]> { let coreDataModelType = checkType(T.self) let taskCompletionSource = TaskCompletionSource<[T]>() diff --git a/DBClient/CoreData/NSManagedObjectContext+Extension.swift b/DBClient/CoreData/NSManagedObjectContext+Extension.swift index 1d45b0d..442fa85 100644 --- a/DBClient/CoreData/NSManagedObjectContext+Extension.swift +++ b/DBClient/CoreData/NSManagedObjectContext+Extension.swift @@ -18,13 +18,13 @@ extension NSManagedObjectContext { try save() if includingParent, let parent = parent { - try parent.performAndWait { + try parent.safePerformAndWait { try parent.save(includingParent: true) } } } - func performAndWait(_ block: @escaping (Void) throws -> Void) throws { + func safePerformAndWait(_ block: @escaping () throws -> Void) throws { var outError: Error? performAndWait { diff --git a/DBClient/Realm/RealmDBClient.swift b/DBClient/Realm/RealmDBClient.swift index eb273b1..8029564 100644 --- a/DBClient/Realm/RealmDBClient.swift +++ b/DBClient/Realm/RealmDBClient.swift @@ -52,7 +52,7 @@ public class RealmDBClient { extension RealmDBClient: DBClient { /// Executes given request. Fetches all entities and then applies all given restrictions - public func execute(_ request: FetchRequest) -> Task<[T]> { + public func execute(_ request: FetchRequest) -> Task<[T]> { let modelType = checkType(T.self) let taskCompletionSource = TaskCompletionSource<[T]>() @@ -145,7 +145,7 @@ extension RealmDBClient: DBClient { return taskCompletionSource.task } - public func observable(for request: FetchRequest) -> RequestObservable { + public func observable(for request: FetchRequest) -> RequestObservable { checkType(T.self) return RealmObservable(request: request, realm: realm) @@ -193,10 +193,10 @@ private extension RealmDBClient { internal extension FetchRequest { - func applyTo(realmObjects: Results) -> Results { + func applyTo(realmObjects: Results) -> Results { var objects: Results = realmObjects if let sortDescriptor = sortDescriptor, let key = sortDescriptor.key { - objects = realmObjects.sorted(byProperty: key, ascending: sortDescriptor.ascending) + objects = realmObjects.sorted(byKeyPath: key, ascending: sortDescriptor.ascending) } if let predicate = predicate { objects = objects.filter(predicate) diff --git a/Example/DBClientTests/DBClientTest.swift b/Example/DBClientTests/DBClientTest.swift index 3860fae..ffa7f87 100644 --- a/Example/DBClientTests/DBClientTest.swift +++ b/Example/DBClientTests/DBClientTest.swift @@ -52,7 +52,7 @@ class DBClientTest: XCTestCase { } } waitForExpectations(timeout: expectationTimeout) { (error) in - XCTAssert(error == nil, "\(error)") + XCTAssert(error == nil, String(describing: error)) } } diff --git a/Example/DBClientTests/Interface/ExecuteTests.swift b/Example/DBClientTests/Interface/ExecuteTests.swift index 1564157..2bd7756 100644 --- a/Example/DBClientTests/Interface/ExecuteTests.swift +++ b/Example/DBClientTests/Interface/ExecuteTests.swift @@ -40,7 +40,7 @@ final class ExecuteTests: DBClientTest { .execute(request) .continueWith { task in guard let users = task.result else { - XCTFail("\(task.error)") + XCTFail(String(describing: task.error)) return } diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 79c484a..ab78207 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -265,7 +265,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0820; - LastUpgradeCheck = 0820; + LastUpgradeCheck = 0900; ORGANIZATIONNAME = Yalantis; TargetAttributes = { B8275AFC1E4B6D2500232EE4 = { @@ -325,9 +325,18 @@ files = ( ); inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-DBClientTests/Pods-DBClientTests-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Bolts-Swift/BoltsSwift.framework", + "${BUILT_PRODUCTS_DIR}/DBClient/DBClient.framework", + "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", + "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BoltsSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DBClient.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -355,9 +364,18 @@ files = ( ); inputPaths = ( + "${SRCROOT}/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks.sh", + "${BUILT_PRODUCTS_DIR}/Bolts-Swift/BoltsSwift.framework", + "${BUILT_PRODUCTS_DIR}/DBClient/DBClient.framework", + "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", + "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/BoltsSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DBClient.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -370,13 +388,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-Example-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; 80407DAF4353353A748FD4DC /* [CP] Copy Pods Resources */ = { @@ -400,13 +421,16 @@ files = ( ); inputPaths = ( + "${PODS_PODFILE_DIR_PATH}/Podfile.lock", + "${PODS_ROOT}/Manifest.lock", ); name = "[CP] Check Pods Manifest.lock"; outputPaths = ( + "$(DERIVED_FILE_DIR)/Pods-DBClientTests-checkManifestLockResult.txt", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n"; + shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; /* End PBXShellScriptBuildPhase section */ @@ -514,7 +538,9 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -522,7 +548,11 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; @@ -564,7 +594,9 @@ CLANG_CXX_LIBRARY = "libc++"; CLANG_ENABLE_MODULES = YES; CLANG_ENABLE_OBJC_ARC = YES; + CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; CLANG_WARN_BOOL_CONVERSION = YES; + CLANG_WARN_COMMA = YES; CLANG_WARN_CONSTANT_CONVERSION = YES; CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_DOCUMENTATION_COMMENTS = YES; @@ -572,7 +604,11 @@ CLANG_WARN_ENUM_CONVERSION = YES; CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; + CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; + CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; + CLANG_WARN_STRICT_PROTOTYPES = YES; CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; diff --git a/Example/Example/DBClientInjector.swift b/Example/Example/DBClientInjector.swift index 2fa98bb..36ecb08 100644 --- a/Example/Example/DBClientInjector.swift +++ b/Example/Example/DBClientInjector.swift @@ -32,8 +32,8 @@ extension DBClientInjectable { var dbClient: DBClient { get { - return DBClientInjector.coreDataClient -// return DBClientInjector.realmClient +// return DBClientInjector.coreDataClient + return DBClientInjector.realmClient } } diff --git a/Example/Podfile.lock b/Example/Podfile.lock index b5068c5..79e8ff1 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,17 +1,17 @@ PODS: - Bolts-Swift (1.3.0) - - DBClient/Core (0.4.2): + - DBClient/Core (0.5): - Bolts-Swift (~> 1.3.0) - - DBClient/CoreData (0.4.2): + - DBClient/CoreData (0.5): - DBClient/Core - - DBClient/Realm (0.4.2): + - DBClient/Realm (0.5): - DBClient/Core - - RealmSwift (~> 2.1.1) - - Realm (2.1.2): - - Realm/Headers (= 2.1.2) - - Realm/Headers (2.1.2) - - RealmSwift (2.1.2): - - Realm (= 2.1.2) + - RealmSwift (~> 2.10.1) + - Realm (2.10.1): + - Realm/Headers (= 2.10.1) + - Realm/Headers (2.10.1) + - RealmSwift (2.10.1): + - Realm (= 2.10.1) DEPENDENCIES: - DBClient/CoreData (from `../`) @@ -19,14 +19,14 @@ DEPENDENCIES: EXTERNAL SOURCES: DBClient: - :path: "../" + :path: ../ SPEC CHECKSUMS: Bolts-Swift: fa98d1b59fc1acea9b21a21306dcdca1c85e3737 - DBClient: 6b80ee2856d2ac0174bdeec7874e3a0af8aaa6e4 - Realm: efe855f4d977c8ce5a82d3116d9f1ff155a6550c - RealmSwift: 17d6ee30b6f9df86364408c2197492e33bfea567 + DBClient: 4afbd8f0134bdd82de826a228cd0dcc98b20f28e + Realm: fc7a317a5c2c9ba91f5f235ede4e2ea76e9eba0c + RealmSwift: 505ed6c15942a2e76f5cfa78a8667cfa997ee75b PODFILE CHECKSUM: e1b66d1226f9ec10a94bb2534dbf6b4ca65549de -COCOAPODS: 1.2.1 +COCOAPODS: 1.3.1 From 26c8b7372d6fc78dae16d78fedba6e60ac9f1a7c Mon Sep 17 00:00:00 2001 From: Artem Havriushov Date: Mon, 25 Sep 2017 14:51:12 +0300 Subject: [PATCH 05/47] [Change] Migrated DBClient and Example project to Swift 4 --- Example/DBClientTests/Interface/ExecuteTests.swift | 6 +++--- Example/Example.xcodeproj/project.pbxproj | 14 ++++++++++---- Example/Example/Models/Realm/ObjectUser.swift | 4 ++-- 3 files changed, 15 insertions(+), 9 deletions(-) diff --git a/Example/DBClientTests/Interface/ExecuteTests.swift b/Example/DBClientTests/Interface/ExecuteTests.swift index 2bd7756..0e874b4 100644 --- a/Example/DBClientTests/Interface/ExecuteTests.swift +++ b/Example/DBClientTests/Interface/ExecuteTests.swift @@ -72,7 +72,7 @@ final class ExecuteTests: DBClientTest { let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) let order: ComparisonResult = sortDescriptor.ascending ? .orderedAscending : .orderedDescending - let randomUsers = createRandomUsers(10).sorted { $0.0.name.compare($0.1.name) == order } + let randomUsers = createRandomUsers(10).sorted { $0.name.compare($1.name) == order } let request = FetchRequest(sortDescriptor: sortDescriptor) execute { expectation in @@ -113,7 +113,7 @@ final class ExecuteTests: DBClientTest { let randomUsers = createRandomUsers(10) let offset = 5 let limit = 2 - let sortedUsers = randomUsers.sorted { $0.0.name.compare($0.1.name) == order } + let sortedUsers = randomUsers.sorted { $0.name.compare($1.name) == order } let shiftedUsers = Array(sortedUsers[offset..(sortDescriptor: sortDescriptor, fetchOffset: offset, fetchLimit: limit) @@ -139,7 +139,7 @@ final class ExecuteTests: DBClientTest { let offset = 2 let limit = 5 var users = randomUsers.filter { $0.id.hasPrefix(arg) } - users = users.sorted { $0.0.name.compare($0.1.name) == order } + users = users.sorted { $0.name.compare($1.name) == order } users = Array(users[offset..( diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index ab78207..6f87c56 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -270,11 +270,13 @@ TargetAttributes = { B8275AFC1E4B6D2500232EE4 = { CreatedOnToolsVersion = 8.2.1; + LastSwiftMigration = 0900; ProvisioningStyle = Automatic; TestTargetID = C53372551E26155D004ECBCF; }; C53372551E26155D004ECBCF = { CreatedOnToolsVersion = 8.2.1; + LastSwiftMigration = 0900; ProvisioningStyle = Automatic; }; }; @@ -510,7 +512,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.DBClientTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; }; name = Debug; @@ -524,7 +527,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.DBClientTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; }; name = Release; @@ -643,7 +647,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.dbclient.Example; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Debug; }; @@ -656,7 +661,8 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.dbclient.Example; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 3.0; + SWIFT_SWIFT3_OBJC_INFERENCE = Default; + SWIFT_VERSION = 4.0; }; name = Release; }; diff --git a/Example/Example/Models/Realm/ObjectUser.swift b/Example/Example/Models/Realm/ObjectUser.swift index 1efbe5d..ad3b715 100644 --- a/Example/Example/Models/Realm/ObjectUser.swift +++ b/Example/Example/Models/Realm/ObjectUser.swift @@ -15,7 +15,7 @@ class ObjectUser: Object { return #keyPath(ObjectUser.id) } - dynamic var id: String = "" - dynamic var name: String = "" + @objc dynamic var id: String = "" + @objc dynamic var name: String = "" } From 90b7981215159d3a2e2b4742ea9a0b56d67409dd Mon Sep 17 00:00:00 2001 From: Artem Havriushov Date: Mon, 25 Sep 2017 15:13:30 +0300 Subject: [PATCH 06/47] [Change] podspec source, readme --- DBClient.podspec | 2 +- README.md | 20 ++++++++++++++------ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/DBClient.podspec b/DBClient.podspec index c07bd98..f637751 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -6,7 +6,7 @@ Pod::Spec.new do |s| s.homepage = "" s.license = 'MIT' s.author = { "Yalantis" => "mail@yalantis.com" } - s.source = { :git => "https://git.yalantis.com/roman.kyrylenko/DBClient.git" } + s.source = { :git => "https://github.com/Yalantis/DBClient.git", :tag => s.version } s.social_media_url = 'https://yalantis.com/' s.homepage = 'https://yalantis.com/' diff --git a/README.md b/README.md index 278b4a3..956b275 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ ## Requirements -- Xcode 8 -- Swift 3 +- Xcode 9 +- Swift 4 - iOS 9+ ## Installation @@ -15,17 +15,25 @@ There're 3 podspecs: Core, common classes for any database: ```ruby -pod 'DBClient', '~> 0.3' +pod 'DBClient', :git => 'https://github.com/Yalantis/DBClient.git' ``` Wrapper for CoreData: ```ruby -pod 'DBClient/CoreData', '~> 0.3' +pod 'DBClient/CoreData', :git => 'https://github.com/Yalantis/DBClient.git' ``` Wrapper for Realm: ```ruby -pod 'DBClient/Realm', '~> 0.3' -``` \ No newline at end of file +pod 'DBClient/Realm', :git => 'https://github.com/Yalantis/DBClient.git' +``` + +## Xcode 8 and Swift 3.2 support + +To support old version of DBClient for Xcode 8 and Swift 3.2 need add previous version tag ```ruby 0.4.2 ```: + +```ruby +pod 'DBClient', :git => 'https://github.com/Yalantis/DBClient.git', :tag => '0.4.2' +``` From c11cf9a2b612ca137e1c5d2b2a9a9b2b53263378 Mon Sep 17 00:00:00 2001 From: Sergey Date: Tue, 31 Oct 2017 09:51:49 +0200 Subject: [PATCH 07/47] Create github folder --- .github/ISSUE_TEMPLATE.md | 44 +++++++++++++++++++++++++++++ .github/contributing.md | 59 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE.md create mode 100644 .github/contributing.md diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 0000000..7a6ea55 --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,44 @@ +# Report + +> The more information you provide, the faster we can help you. + +⚠️ Select what you want - **a feature request** or **report a bug**. Please remove the section you aren't interested in. + +## A feature request + +### What do you want to add? + +> Please describe what you want to add to the component. + +### How should it look like? + +> Please add images. + +## Report a bug + +### What did you do? + +> Please replace this with what you did. + +### What did you expect to happen? + +> Please replace this with what you expected to happen. + +### What happened instead? + +> Please replace this with what happened instead. + +### Your Environment + +- Version of the component: _insert here_ +- Swift version: _insert here_ +- iOS version: _insert here_ +- Device: _insert here_ +- Xcode version: _insert here_ +- If you use Cocoapods: _run `pod env | pbcopy` and insert here_ +- If you use Carthage: _run `carthage version | pbcopy` and insert here_ + +### Project that demonstrates the bug + +> Please add a link to a project we can download that reproduces the bug. + diff --git a/.github/contributing.md b/.github/contributing.md new file mode 100644 index 0000000..89acd87 --- /dev/null +++ b/.github/contributing.md @@ -0,0 +1,59 @@ +## How to contribute to DBClient + +#### **Did you find a bug?** + +* **Ensure the bug was not already reported** by searching under [Issues](https://github.com/Yalantis/DBClient/issues). + +* If you're unable to find an open issue addressing the problem, [open a new one](https://github.com/Yalantis/DBClient/issues/new). Be sure to include a **title and clear description**, as much relevant information as possible, and a **code sample** or an **example project** demonstrating the expected behavior that is not occurring. + +* Fill appropriate section in issue template and remove the section you aren't interested in. + +#### **Did you write a patch that fixes a bug?** + +* Open a new GitHub pull request with the patch. + +* Ensure the PR description clearly describes the problem and solution. Include the relevant issue number if applicable. + +* Ensure the PR doesn't extend the number of existing issues. + +#### **Did you fix whitespace, format code, or make a purely cosmetic patch?** + +* Changes that are **cosmetic** in nature and **do not add anything substantial** to the stability or functionality of DBClient will generally **not be accepted**. + +#### **Did you write patch that extends functionality?** + +* Ensure the functionality you trying to add needed not only for your case. + +#### Each issue will be labeled by it's `type`, `priority` and `status`. + +**Issue types:** +* Bug +* Enhancement + +**These are the available priority labels:** +* Critical +* High +* Medium +* Low + +**Status label will be assigned to your issue to keep the issue tracker easy to follow:** +* Queued (will be reviewed soon) +* Reviewed (assignee has read it) +* Pending (will work on it soon) +* Work in progress (is working on it now) +* On hold +* Invalid (if bug it's not reproducible) +* Need feedback (signal to get people to read and comment or provide help) + +#### **Coding Style** + +* Most importantly, match the existing code style as much as possible. + +#### **Do you have a question?** + +For any usage questions that are not specific to the project itself, please ask on [Stack Overflow](https://stackoverflow.com/). By doing so, you'll be more likely to quickly solve your problem, and you'll allow anyone else with the same question to find the answer. This also allows maintainers to focus on improving the project for others. + +## Thank you! + +#### [![Yalantis](https://raw.githubusercontent.com/Yalantis/PullToMakeSoup/master/PullToMakeSoupDemo/Resouces/badge_dark.png)](https://Yalantis.com/?utm_source=github) + From 31e48684643e14a28adca884da6c8c787e7bdaed Mon Sep 17 00:00:00 2001 From: Sergey <> Date: Fri, 27 Apr 2018 14:17:52 +0300 Subject: [PATCH 08/47] Fix segmentation fault error --- DBClient/CoreData/CoreDataObservable.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/DBClient/CoreData/CoreDataObservable.swift b/DBClient/CoreData/CoreDataObservable.swift index 08914a9..c4ad07d 100644 --- a/DBClient/CoreData/CoreDataObservable.swift +++ b/DBClient/CoreData/CoreDataObservable.swift @@ -129,7 +129,9 @@ private class FetchedResultsControllerDelegate: NSObject, NS insertions: inserted, modifications: updated ) - observer?(.change(mappedChange)) + if let observer = observer { + observer(.change(mappedChange)) + } batchChanges = [] } From 28064441fee418010a43159e11347e81c765e957 Mon Sep 17 00:00:00 2001 From: Sergey D Date: Wed, 2 May 2018 14:12:01 +0300 Subject: [PATCH 09/47] Update DBClient.podspec --- DBClient.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DBClient.podspec b/DBClient.podspec index f637751..e7f4c0b 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DBClient" - s.version = "0.5" + s.version = "0.6" s.requires_arc = true s.summary = "CoreData & Realm wrapper written on Swift" s.homepage = "" From 458f48e1cd04a26b3f9249e795601604b9ea108d Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 11 Sep 2018 12:58:24 +0300 Subject: [PATCH 10/47] [Update] bolts dependency --- .swift-version | 2 +- DBClient.podspec | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.swift-version b/.swift-version index f398a20..389f774 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -3.0 \ No newline at end of file +4.0 \ No newline at end of file diff --git a/DBClient.podspec b/DBClient.podspec index e7f4c0b..f2528e0 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DBClient" - s.version = "0.6" + s.version = "0.7" s.requires_arc = true s.summary = "CoreData & Realm wrapper written on Swift" s.homepage = "" @@ -16,7 +16,7 @@ Pod::Spec.new do |s| s.subspec "Core" do |spec| spec.source_files = ['DBClient/Core/*.swift'] - spec.dependency "Bolts-Swift", "~> 1.3.0" + spec.dependency "Bolts-Swift", "~> 1.4" spec.frameworks = ['Foundation'] end From 0a39b6e5664ac96068b6cd11090d5117698f02e5 Mon Sep 17 00:00:00 2001 From: Anton Vodolazkyi Date: Wed, 26 Sep 2018 12:11:03 +0300 Subject: [PATCH 11/47] remove Bolts --- DBClient.podspec | 1 - DBClient/Core/DBClient.swift | 105 +++++++++++---------- DBClient/CoreData/CoreDataDBClient.swift | 66 +++++-------- DBClient/Realm/RealmDBClient.swift | 72 ++++++-------- Example/DBClientTests/DBClientTest.swift | 1 - Example/Example.xcodeproj/project.pbxproj | 36 ------- Example/Example/DetailViewController.swift | 4 +- Example/Example/MasterViewController.swift | 6 +- Example/Podfile.lock | 34 +++---- Result.swift | 83 ++++++++++++++++ 10 files changed, 217 insertions(+), 191 deletions(-) create mode 100644 Result.swift diff --git a/DBClient.podspec b/DBClient.podspec index f2528e0..9bd7583 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -16,7 +16,6 @@ Pod::Spec.new do |s| s.subspec "Core" do |spec| spec.source_files = ['DBClient/Core/*.swift'] - spec.dependency "Bolts-Swift", "~> 1.4" spec.frameworks = ['Foundation'] end diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index b4b3a55..1e4fa42 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -6,7 +6,12 @@ // Copyright © 2016 Yalantis. All rights reserved. // -import BoltsSwift +import Foundation + +public enum DBClientError: Error { + case noPrimaryKey + case noData +} /// Protocol for transaction restrictions in `DBClient`. /// Used for transactions of all type. @@ -23,11 +28,12 @@ public protocol Stored { /// Describes abstract database transactions, common for all engines. public protocol DBClient { - /// Executes given request and returns result wrapped in `Task`. + /// Executes given request and calls completion result wrapped in `Result`. /// - /// - Parameter request: request to execute - /// - Returns: `Task` with array of objects or error in case of failude. - func execute(_ request: FetchRequest) -> Task<[T]> + /// - Parameters: + /// - request: request to execute + /// - completion: `Result` with array of objects or error in case of failude. + func execute(_ request: FetchRequest, completion: @escaping (Result<[T]>) -> Void) /// Creates observable request from given `FetchRequest`. /// @@ -37,31 +43,31 @@ public protocol DBClient { /// Inserts objects to database. /// - /// - Parameter objects: list of objects to be inserted - /// - Returns: `Task` with inserted objects or appropriate error in case of failure. - @discardableResult - func insert(_ objects: [T]) -> Task<[T]> + /// - Parameters: + /// - objects: list of objects to be inserted + /// - completion: `Result` with inserted objects or appropriate error in case of failure. + func insert(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) /// Updates changed performed with objects to database. /// - /// - Parameter objects: list of objects to be updated - /// - Returns: `Task` with updated objects or appropriate error in case of failure. - @discardableResult - func update(_ objects: [T]) -> Task<[T]> + /// - Parameters: + /// - objects: list of objects to be updated + /// - completion: `Result` with updated objects or appropriate error in case of failure. + func update(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) /// Deletes objects from database. /// - /// - Parameter objects: list of objects to be deleted - /// - Returns: `Task` with appropriate error in case of failure. - @discardableResult - func delete(_ objects: [T]) -> Task + /// - Parameters: + /// - objects: list of objects to be deleted + /// - completion: `Result` with appropriate error in case of failure. + func delete(_ objects: [T], completion: @escaping (Result<()>) -> Void) /// Iterates through given objects and updates existing in database instances or creates them /// - /// - Parameter objects: objects to be worked with - /// - Returns: A `Task` with inserted and updated instances - @discardableResult - func upsert(_ objects: [T]) -> Task<(updated: [T], inserted: [T])> + /// - Parameters: + /// - objects: objects to be worked with + /// - completion: `Result` with inserted and updated instances. + func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) } @@ -69,9 +75,9 @@ public extension DBClient { /// Fetch all entities from database /// - /// - Returns: Task with array of objects - func findAll() -> Task<[T]> { - return execute(FetchRequest()) + /// - Parameter completion: `Result` with array of objects + func findAll(completion: @escaping (Result<[T]>) -> Void) { + execute(FetchRequest(), completion: completion) } /// Finds first element with given value as primary. @@ -81,10 +87,11 @@ public extension DBClient { /// - type: type of object to search for /// - primaryValue: the value of primary key field to search for /// - predicate: predicate for request - /// - Returns: `Task` with found object or nil. - func findFirst(_ type: T.Type, primaryValue: String, predicate: NSPredicate? = nil) -> Task { + /// - completion: `Result` with found object or nil + func findFirst(_ type: T.Type, primaryValue: String, predicate: NSPredicate? = nil, completion: @escaping (Result) -> Void) { guard let primaryKey = type.primaryKeyName else { - return Task(nil) + completion(.failure(DBClientError.noPrimaryKey)) + return } let primaryKeyPredicate = NSPredicate(format: "\(primaryKey) == %@", primaryValue) @@ -96,45 +103,43 @@ public extension DBClient { } let request = FetchRequest(predicate: fetchPredicate, fetchLimit: 1) - return execute(request).continueWithTask { task -> Task in - return Task(task.result?.first) + execute(request) { result in + result.map({ $0.first }) } } /// Inserts object to database. /// - /// - Parameter object: object to be inserted - /// - Returns: `Task` with inserted object or appropriate error in case of failure. - @discardableResult func insert(_ object: T) -> Task { - return convertArrayTaskToSingleObject(insert([object])) + /// - Parameters: + /// - object: object to be inserted + /// - completion: `Result` with inserted object or appropriate error in case of failure. + func insert(_ object: T, completion: @escaping (Result) -> Void) { + insert([object], completion: { $0.next(self.convertArrayTaskToSingleObject) }) } /// Updates changed performed with object to database. /// - /// - Parameter object: object to be updated - /// - Returns: `Task` with updated object or appropriate error in case of failure. - @discardableResult func update(_ object: T) -> Task { - return convertArrayTaskToSingleObject(update([object])) + /// - Parameters: + /// - object: object to be updated + /// - completion: `Result` with updated object or appropriate error in case of failure. + func update(_ object: T, completion: @escaping (Result) -> Void) { + update([object], completion: { $0.next(self.convertArrayTaskToSingleObject) }) } /// Deletes object from database. /// - /// - Parameter object: object to be deleted - /// - Returns: `Task` with appropriate error in case of failure. - @discardableResult func delete(_ object: T) -> Task { - return delete([object]) + /// - Parameters: + /// - object: object to be deleted + /// - completion: `Result` with appropriate error in case of failure. + func delete(_ object: T, completion: @escaping (Result<()>) -> Void) { + delete([object], completion: completion) } - private func convertArrayTaskToSingleObject(_ task: Task<[T]>) -> Task { - return task.continueWithTask { task -> Task in - if let objects = task.result, let object = objects.first { - return Task(object) - } else if let error = task.error { - return Task(error: error) - } else { // no objects returned - return Task.cancelledTask() - } + private func convertArrayTaskToSingleObject(_ array: [T]) -> Result { + guard let first = array.first else { + return .failure(DBClientError.noData) } + return .success(first) } } diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index 3d9b5f9..a0d4c17 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -7,7 +7,6 @@ // import CoreData -import BoltsSwift /// Describes type of model for CoreData database client. /// Model should conform to CoreDataModelConvertible protocol @@ -267,35 +266,31 @@ extension CoreDataDBClient: DBClient { return CoreDataObservable(request: request, context: mainContext) } - public func execute(_ request: FetchRequest) -> Task<[T]> { + public func execute(_ request: FetchRequest, completion: @escaping (Result<[T]>) -> Void) where T : Stored { let coreDataModelType = checkType(T.self) - - let taskCompletionSource = TaskCompletionSource<[T]>() - + performReadTask { context in let fetchRequest = self.fetchRequest(for: coreDataModelType) fetchRequest.predicate = request.predicate - fetchRequest.sortDescriptors = [request.sortDescriptor].flatMap { $0 } + fetchRequest.sortDescriptors = [request.sortDescriptor].compactMap { $0 } fetchRequest.fetchLimit = request.fetchLimit fetchRequest.fetchOffset = request.fetchOffset do { let result = try context.fetch(fetchRequest) as! [NSManagedObject] - let resultModels = result.flatMap { coreDataModelType.from($0) as? T } - taskCompletionSource.set(result: resultModels) + let resultModels = result.compactMap { coreDataModelType.from($0) as? T } + + completion(.success(resultModels)) } catch let error { - taskCompletionSource.set(error: error) + completion(.failure(error)) } } - - return taskCompletionSource.task } /// Insert given objects into context and save it /// If appropriate object already exists in DB it will be ignored and nothing will be inserted - public func insert(_ objects: [T]) -> Task<[T]> { + public func insert(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T : Stored { checkType(T.self) - - let taskCompletionSource = TaskCompletionSource<[T]>() + performWriteTask { context, savingClosure in var insertedObjects = [T]() let foundObjects = self.find(objects: objects, in: context) @@ -307,23 +302,21 @@ extension CoreDataDBClient: DBClient { _ = object.upsertManagedObject(in: context, existedInstance: nil) insertedObjects.append(object as! T) } - + do { try savingClosure() - taskCompletionSource.set(result: insertedObjects) + completion(.success(insertedObjects)) } catch let error { - taskCompletionSource.set(error: error) + completion(.failure(error)) } } - return taskCompletionSource.task } /// Method to update existed in DB objects /// if there is no such object in db nothing will happened - public func update(_ objects: [T]) -> Task<[T]> { + public func update(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T : Stored { checkType(T.self) - - let taskCompletionSource = TaskCompletionSource<[T]>() + performWriteTask { context, savingClosure in var updatedObjects = [T]() @@ -339,19 +332,17 @@ extension CoreDataDBClient: DBClient { do { try savingClosure() - taskCompletionSource.set(result: updatedObjects) + completion(.success(updatedObjects)) } catch let error { - taskCompletionSource.set(error: error) + completion(.failure(error)) } } - return taskCompletionSource.task } /// Update object if it exists or insert new one otherwise - public func upsert(_ objects: [T]) -> Task<(updated: [T], inserted: [T])> { + public func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) where T : Stored { checkType(T.self) - - let taskCompletionSource = TaskCompletionSource<(updated: [T], inserted: [T])>() + performWriteTask { context, savingClosure in var updatedObjects = [T]() var insertedObjects = [T]() @@ -368,34 +359,29 @@ extension CoreDataDBClient: DBClient { do { try savingClosure() - taskCompletionSource.set(result: (updated: updatedObjects, inserted: insertedObjects)) + completion(.success((updated: updatedObjects, inserted: insertedObjects))) } catch let error { - taskCompletionSource.set(error: error) + completion(.failure(error)) } } - - return taskCompletionSource.task } /// For each element in collection: /// After all deletes try to save context - public func delete(_ objects: [T]) -> Task { + public func delete(_ objects: [T], completion: @escaping (Result<()>) -> Void) where T : Stored { checkType(T.self) - - let taskCompletionSource = TaskCompletionSource() + performWriteTask { context, savingClosure in let foundObjects = self.find(objects, in: context) foundObjects.forEach { context.delete($0) } do { try savingClosure() - taskCompletionSource.set(result: ()) + completion(.success(())) } catch let error { - taskCompletionSource.set(error: error) + completion(.failure(error)) } } - - return taskCompletionSource.task } } @@ -426,7 +412,7 @@ private extension CoreDataDBClient { return [] } - let ids = objects.flatMap { $0.valueOfPrimaryKey } + let ids = objects.compactMap { $0.valueOfPrimaryKey } let fetchRequest = self.fetchRequest(for: coreDataModelType) fetchRequest.predicate = NSPredicate(format: "\(primaryKeyName) IN %@", ids) guard let result = try? context.fetch(fetchRequest), let storedObjects = result as? [NSManagedObject] else { @@ -459,7 +445,7 @@ private extension CoreDataDBClient { func convert(objects: [T]) -> [CoreDataModelConvertible] { checkType(T.self) - return objects.flatMap { $0 as? CoreDataModelConvertible } + return objects.compactMap { $0 as? CoreDataModelConvertible } } } diff --git a/DBClient/Realm/RealmDBClient.swift b/DBClient/Realm/RealmDBClient.swift index 8029564..a172d70 100644 --- a/DBClient/Realm/RealmDBClient.swift +++ b/DBClient/Realm/RealmDBClient.swift @@ -7,7 +7,6 @@ // import Foundation -import BoltsSwift import RealmSwift /// Describes protocol to be implemented by model for `RealmDBClient` @@ -52,97 +51,84 @@ public class RealmDBClient { extension RealmDBClient: DBClient { /// Executes given request. Fetches all entities and then applies all given restrictions - public func execute(_ request: FetchRequest) -> Task<[T]> { + public func execute(_ request: FetchRequest, completion: @escaping (Result<[T]>) -> Void) { let modelType = checkType(T.self) - - let taskCompletionSource = TaskCompletionSource<[T]>() let neededType = modelType.realmClass() let objects = request .applyTo(realmObjects: realm.objects(neededType)) .map { $0 } .get(offset: request.fetchOffset, limit: request.fetchLimit) - .flatMap { modelType.from($0) as? T } - taskCompletionSource.set(result: objects) + .compactMap { modelType.from($0) as? T } - return taskCompletionSource.task + completion(.success(objects)) } /// Inserts new objects to database. If object with such `primaryKeyValue` already exists Realm'll throw an error - public func insert(_ objects: [T]) -> Task<[T]> { + public func insert(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T : Stored { checkType(T.self) - - let taskCompletionSource = TaskCompletionSource<[T]>() - - let realmObjects = objects.flatMap { ($0 as? RealmModelConvertible)?.toRealmObject() } + + let realmObjects = objects.compactMap { ($0 as? RealmModelConvertible)?.toRealmObject() } + do { realm.beginWrite() realm.add(realmObjects) try realm.commitWrite() - taskCompletionSource.set(result: objects) + completion(.success(objects)) } catch let error { - taskCompletionSource.set(error: error) + completion(.failure(error)) } - - return taskCompletionSource.task } /// Updates objects which are already in db. - public func update(_ objects: [T]) -> Task<[T]> { + public func update(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T : Stored { checkType(T.self) - - let taskCompletionSource = TaskCompletionSource<[T]>() + let realmObjects = separate(objects: objects) .present - .flatMap { ($0 as? RealmModelConvertible)?.toRealmObject() } + .compactMap { ($0 as? RealmModelConvertible)?.toRealmObject() } do { realm.beginWrite() realm.add(realmObjects, update: true) try realm.commitWrite() - taskCompletionSource.set(result: objects) + + completion(.success(objects)) } catch let error { - taskCompletionSource.set(error: error) + completion(.failure(error)) } - - return taskCompletionSource.task } /// Removes objects by it `primaryKeyValue`s - public func delete(_ objects: [T]) -> Task { + public func delete(_ objects: [T], completion: @escaping (Result<()>) -> Void) where T : Stored { let type = checkType(T.self) - - let taskCompletionSource = TaskCompletionSource() + let realmType = type.realmClass() do { - let primaryValues = objects.flatMap { $0.valueOfPrimaryKey } - let realmObjects = primaryValues.flatMap { realm.object(ofType: realmType, forPrimaryKey: $0) } + let primaryValues = objects.compactMap { $0.valueOfPrimaryKey } + let realmObjects = primaryValues.compactMap { realm.object(ofType: realmType, forPrimaryKey: $0) } realm.beginWrite() realm.delete(realmObjects) try realm.commitWrite() - taskCompletionSource.set(result: ()) + + completion(.success(())) } catch let error { - taskCompletionSource.set(error: error) + completion(.failure(error)) } - - return taskCompletionSource.task } - - public func upsert(_ objects: [T]) -> Task<(updated: [T], inserted: [T])> { + + public func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) where T : Stored { checkType(T.self) - - let taskCompletionSource = TaskCompletionSource<(updated: [T], inserted: [T])>() + let separatedObjects = separate(objects: objects) - let realmObjects = objects.flatMap { ($0 as? RealmModelConvertible)?.toRealmObject() } + let realmObjects = objects.compactMap { ($0 as? RealmModelConvertible)?.toRealmObject() } do { realm.beginWrite() realm.add(realmObjects, update: true) try realm.commitWrite() - taskCompletionSource.set(result: (updated: separatedObjects.present, inserted: separatedObjects.new)) + completion(.success((updated: separatedObjects.present, inserted: separatedObjects.new))) } catch let error { - taskCompletionSource.set(error: error) + completion(.failure(error)) } - - return taskCompletionSource.task } public func observable(for request: FetchRequest) -> RequestObservable { @@ -223,7 +209,7 @@ private extension Array { lim = offset + limit } - return (off.. /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; - 80407DAF4353353A748FD4DC /* [CP] Copy Pods Resources */ = { - isa = PBXShellScriptBuildPhase; - buildActionMask = 2147483647; - files = ( - ); - inputPaths = ( - ); - name = "[CP] Copy Pods Resources"; - outputPaths = ( - ); - runOnlyForDeploymentPostprocessing = 0; - shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DBClientTests/Pods-DBClientTests-resources.sh\"\n"; - showEnvVarsInLog = 0; - }; BF151D4AF0AD5B6D29F45416 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; diff --git a/Example/Example/DetailViewController.swift b/Example/Example/DetailViewController.swift index 7fc937b..e235a26 100644 --- a/Example/Example/DetailViewController.swift +++ b/Example/Example/DetailViewController.swift @@ -23,8 +23,8 @@ class DetailViewController: UIViewController, DBClientInjectable { @IBAction private func saveButtonAction() { detailItem.name = userNameTextField.text ?? "" - dbClient.update(detailItem).continueOnSuccessWith(.mainThread) { _ in - self.navigationController?.popViewController(animated: true) + dbClient.update(detailItem) { [weak self] result in + self?.navigationController?.popViewController(animated: true) } } diff --git a/Example/Example/MasterViewController.swift b/Example/Example/MasterViewController.swift index 5aab29b..10a2bdb 100644 --- a/Example/Example/MasterViewController.swift +++ b/Example/Example/MasterViewController.swift @@ -44,7 +44,9 @@ class MasterViewController: UITableViewController, DBClientInjectable { // MARK: - Actions @IBAction private func addObject(_ sender: Any) { - dbClient.insert(User.createRandom()) + dbClient.insert(User.createRandom()) { result in + print(result) + } } private func observeChanges(_ changeSet: ObservableChange) { @@ -102,7 +104,7 @@ extension MasterViewController { } let user = objects[indexPath.row] - dbClient.delete(user) + dbClient.delete(user, completion: { _ in }) } } diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 79e8ff1..49b4eee 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,32 +1,34 @@ PODS: - - Bolts-Swift (1.3.0) - - DBClient/Core (0.5): - - Bolts-Swift (~> 1.3.0) - - DBClient/CoreData (0.5): + - DBClient/Core (0.7) + - DBClient/CoreData (0.7): - DBClient/Core - - DBClient/Realm (0.5): + - DBClient/Realm (0.7): - DBClient/Core - RealmSwift (~> 2.10.1) - - Realm (2.10.1): - - Realm/Headers (= 2.10.1) - - Realm/Headers (2.10.1) - - RealmSwift (2.10.1): - - Realm (= 2.10.1) + - Realm (2.10.2): + - Realm/Headers (= 2.10.2) + - Realm/Headers (2.10.2) + - RealmSwift (2.10.2): + - Realm (= 2.10.2) DEPENDENCIES: - DBClient/CoreData (from `../`) - DBClient/Realm (from `../`) +SPEC REPOS: + https://github.com/cocoapods/specs.git: + - Realm + - RealmSwift + EXTERNAL SOURCES: DBClient: - :path: ../ + :path: "../" SPEC CHECKSUMS: - Bolts-Swift: fa98d1b59fc1acea9b21a21306dcdca1c85e3737 - DBClient: 4afbd8f0134bdd82de826a228cd0dcc98b20f28e - Realm: fc7a317a5c2c9ba91f5f235ede4e2ea76e9eba0c - RealmSwift: 505ed6c15942a2e76f5cfa78a8667cfa997ee75b + DBClient: 3d8e3884fe65ea068edb6453f4d70ec00bb0c8e3 + Realm: 0ef72b837fb67e9f4b098bac771ddd72c7fdbb69 + RealmSwift: 07a9ae0505091eda6b2ee7c190c3786d6e90a7b0 PODFILE CHECKSUM: e1b66d1226f9ec10a94bb2534dbf6b4ca65549de -COCOAPODS: 1.3.1 +COCOAPODS: 1.5.3 diff --git a/Result.swift b/Result.swift new file mode 100644 index 0000000..9e6f675 --- /dev/null +++ b/Result.swift @@ -0,0 +1,83 @@ +// +// Result.swift +// DBClient +// +// Created by Vodolazkyi Anton on 9/19/18. +// + +import Foundation + +public enum Result { + + case success(T) + case failure(Error) +} + +extension Result { + + public var value: T? { + switch self { + case .success(let result): return result + case .failure: return nil + } + } + + public var error: Error? { + switch self { + case .success: return nil + case .failure(let error): return error + } + } +} + +extension Result { + + @discardableResult + public func map(_ f: (T) -> U) -> Result { + switch self { + case .success(let t): return .success(f(t)) + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func map(_ f: () -> U) -> Result { + switch self { + case .success: return .success(f()) + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func next(_ f: (T) -> Result) -> Result { + switch self { + case .success(let t): return f(t) + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func next(_ f: () -> Result) -> Result { + switch self { + case .success: return f() + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func onError(_ f: (Error) -> Error) -> Result { + switch self { + case .success(let value): return .success(value) + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func require() -> T { + switch self { + case .success(let value): return value + case .failure(let error): fatalError("Value is required: \(error)") + } + } + +} From 70f4502c1e78df0d519ae8ef508f3c6516759f85 Mon Sep 17 00:00:00 2001 From: Anton Vodolazkyi Date: Wed, 26 Sep 2018 20:09:05 +0300 Subject: [PATCH 12/47] update tests --- DBClient/Core/DBClient.swift | 8 +- DBClient/Core/Result.swift | 83 ++++++++ Example/DBClientTests/DBClientTest.swift | 73 ++----- .../DBClientTests/Interface/CreateTests.swift | 58 ++--- .../DBClientTests/Interface/DeleteTests.swift | 87 +++----- .../Interface/ExecuteTests.swift | 199 ++++++++---------- .../DBClientTests/Interface/FetchTests.swift | 66 +++--- .../Interface/ObservableTests.swift | 141 +++++-------- .../DBClientTests/Interface/UpdateTests.swift | 121 ++--------- .../DBClientTests/Interface/UpsertTests.swift | 28 +-- 10 files changed, 338 insertions(+), 526 deletions(-) create mode 100644 DBClient/Core/Result.swift diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index 1e4fa42..1a64a5e 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -104,7 +104,7 @@ public extension DBClient { let request = FetchRequest(predicate: fetchPredicate, fetchLimit: 1) execute(request) { result in - result.map({ $0.first }) + completion(result.map({ $0.first })) } } @@ -113,8 +113,8 @@ public extension DBClient { /// - Parameters: /// - object: object to be inserted /// - completion: `Result` with inserted object or appropriate error in case of failure. - func insert(_ object: T, completion: @escaping (Result) -> Void) { - insert([object], completion: { $0.next(self.convertArrayTaskToSingleObject) }) + func insert(_ object: T, completion: @escaping (Result) -> Void) { + insert([object], completion: { completion($0.next(self.convertArrayTaskToSingleObject)) }) } /// Updates changed performed with object to database. @@ -123,7 +123,7 @@ public extension DBClient { /// - object: object to be updated /// - completion: `Result` with updated object or appropriate error in case of failure. func update(_ object: T, completion: @escaping (Result) -> Void) { - update([object], completion: { $0.next(self.convertArrayTaskToSingleObject) }) + update([object], completion: { completion($0.next(self.convertArrayTaskToSingleObject)) }) } /// Deletes object from database. diff --git a/DBClient/Core/Result.swift b/DBClient/Core/Result.swift new file mode 100644 index 0000000..8c403e5 --- /dev/null +++ b/DBClient/Core/Result.swift @@ -0,0 +1,83 @@ +// +// Result.swift +// APIClient +// +// Created by Vodolazkyi Anton on 9/19/18. +// + +import Foundation + +public enum Result { + + case success(T) + case failure(Error) +} + +extension Result { + + public var value: T? { + switch self { + case .success(let result): return result + case .failure: return nil + } + } + + public var error: Error? { + switch self { + case .success: return nil + case .failure(let error): return error + } + } +} + +extension Result { + + @discardableResult + public func map(_ f: (T) -> U) -> Result { + switch self { + case .success(let t): return .success(f(t)) + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func map(_ f: () -> U) -> Result { + switch self { + case .success: return .success(f()) + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func next(_ f: (T) -> Result) -> Result { + switch self { + case .success(let t): return f(t) + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func next(_ f: () -> Result) -> Result { + switch self { + case .success: return f() + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func onError(_ f: (Error) -> Error) -> Result { + switch self { + case .success(let value): return .success(value) + case .failure(let error): return .failure(error) + } + } + + @discardableResult + public func require() -> T { + switch self { + case .success(let value): return value + case .failure(let error): fatalError("Value is required: \(error)") + } + } + +} diff --git a/Example/DBClientTests/DBClientTest.swift b/Example/DBClientTests/DBClientTest.swift index afbbdb4..fc8f5c5 100644 --- a/Example/DBClientTests/DBClientTest.swift +++ b/Example/DBClientTests/DBClientTest.swift @@ -33,28 +33,6 @@ class DBClientTest: XCTestCase { } }() - var expectationTimeout: TimeInterval { - return 25 - } - - // execute given closure asynchronously with expectation - func execute(_ closure: @escaping (XCTestExpectation) -> ()) { - let exp = expectation(description: "DBClientTestExpectation") - switch storageType { - // because realm transactions should be perfomrmed in the same thread where realm created - case .realm: - closure(exp) - - default: - DispatchQueue.global(qos: .background).async { - closure(exp) - } - } - waitForExpectations(timeout: expectationTimeout) { (error) in - XCTAssert(error == nil, String(describing: error)) - } - } - override func setUp() { super.setUp() @@ -70,48 +48,21 @@ class DBClientTest: XCTestCase { // removes all objects from the database func cleanUpDatabase() { print("[DBClientTest]: Cleaning database") - var count = 0 - let request: Task<[User]> = dbClient.findAll() - execute { (expectation) in - request - .continueOnSuccessWithTask { users -> Task in - count = users.count - return self.dbClient.delete(users) - } - .continueOnSuccessWith { objects in - print("[DBClientTest]: Removed \(count) objects") - expectation.fulfill() - } - .waitUntilCompleted() - } - } - - @discardableResult func createRandomUser() -> User { - let randomUser = User.createRandom() - execute { expectation in - self.dbClient - .insert(randomUser) - .continueOnSuccessWith { _ in - expectation.fulfill() - } - .waitUntilCompleted() - } + let expectationDeleletion = expectation(description: "Deletion") + var isDeleted = false - return randomUser - } - - @discardableResult func createRandomUsers(_ count: Int) -> [User] { - let randomUsers = (0..) in + if let objects = result.value { + self.dbClient.delete(objects, completion: { result in + isDeleted = true + expectationDeleletion.fulfill() + }) + } } - return randomUsers + waitForExpectations(timeout: 1) { _ in + XCTAssert(isDeleted) + } } } diff --git a/Example/DBClientTests/Interface/CreateTests.swift b/Example/DBClientTests/Interface/CreateTests.swift index 7ea89f2..a5023b5 100644 --- a/Example/DBClientTests/Interface/CreateTests.swift +++ b/Example/DBClientTests/Interface/CreateTests.swift @@ -7,52 +7,40 @@ // import XCTest -import BoltsSwift @testable import Example final class CreateTests: DBClientTest { - func testSingleInsertion() { + func test_SingleInsertion_WhenSuccessful_ReturnsObject() { let randomUser = User.createRandom() - execute { expectation in - self.dbClient - .insert(randomUser) - .continueOnSuccessWith { savedUser in - XCTAssertEqual(randomUser, savedUser) - expectation.fulfill() - } - .waitUntilCompleted() + let expectationObject = expectation(description: "Object") + var expectedObject: User? + + self.dbClient.insert(randomUser, completion: { result in + expectedObject = result.value + expectationObject.fulfill() + }) + + waitForExpectations(timeout: 1) { _ in + XCTAssertNotNil(expectedObject) } } - func testBulkInsertions() { + func test_BulkInsertion_WhenSuccessful_ReturnsBulk() { let randomUsers: [User] = (0...100).map { _ in User.createRandom() } - execute { expectation in - self.dbClient - .insert(randomUsers) - .continueOnSuccessWith { savedUsers in - XCTAssertEqual(randomUsers, savedUsers) - expectation.fulfill() - } - .waitUntilCompleted() - } - } - - func testAsyncInsertions() { - let randomUsers: [User] = (0...100).map { _ in User.createRandom() } - var tasks: [Task] = [] - execute { expectation in - for user in randomUsers { - tasks.append(self.dbClient.insert(user)) - } - Task.whenAll(tasks) - .continueOnSuccessWith { createdTasks in - expectation.fulfill() - } - .waitUntilCompleted() + let expectationObjects = expectation(description: "Objects") + var expectedObjectsCount = 0 + + self.dbClient.insert(randomUsers, completion: { result in + expectedObjectsCount = result.value?.count ?? 0 + expectationObjects.fulfill() + }) + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedObjectsCount, randomUsers.count) } } - + } diff --git a/Example/DBClientTests/Interface/DeleteTests.swift b/Example/DBClientTests/Interface/DeleteTests.swift index 5dc6181..d1835d7 100644 --- a/Example/DBClientTests/Interface/DeleteTests.swift +++ b/Example/DBClientTests/Interface/DeleteTests.swift @@ -7,73 +7,46 @@ // import XCTest -import BoltsSwift @testable import Example final class DeleteTests: DBClientTest { - func testSingleDeletion() { - let randomUser = createRandomUser() - // remove user from db - execute { expectation in - self.dbClient - .delete(randomUser) - .continueOnSuccessWith { _ in - expectation.fulfill() - } - .waitUntilCompleted() - } - // check if it has been removed - execute { expectation in - self.dbClient - .findFirst(User.self, primaryValue: randomUser.id) - .continueOnSuccessWith { user in - XCTAssert(user == nil) - expectation.fulfill() - } - .waitUntilCompleted() - } - } - - func testBulkDeletions() { - let randomUsers: [User] = createRandomUsers(100) - // remove users from db - execute { expectation in - self.dbClient - .delete(randomUsers) - .continueOnSuccessWith { _ in - expectation.fulfill() - } - .waitUntilCompleted() - } - // check if they have been removed - execute { expectation in - let request: Task<[User]> = self.dbClient.findAll() - request - .continueOnSuccessWith { users in - XCTAssert(users.isEmpty) - expectation.fulfill() - } - .waitUntilCompleted() + func test_SingleDeletion_WhenSuccessful_ReturnsNil() { + let randomUser = User.createRandom() + let expectationHit = expectation(description: "Object") + var isDeleted = false + + self.dbClient.insert(randomUser, completion: { result in + if let object = result.value { + self.dbClient.delete(object, completion: { result in + isDeleted = result.value != nil + expectationHit.fulfill() + }) + } + }) + + waitForExpectations(timeout: 1) { _ in + XCTAssert(isDeleted) } } - func testAsyncDeletions() { - let randomUsers: [User] = createRandomUsers(100) - - var tasks: [Task] = [] + func test_BulkDeletion_WhenSuccessful_ReturnsNil() { + let randomUsers: [User] = (0...100).map { _ in User.createRandom() } + let expectationHit = expectation(description: "Object") + var isDeleted = false - execute { expectation in - for user in randomUsers { - tasks.append(self.dbClient.delete(user)) + self.dbClient.insert(randomUsers, completion: { result in + if let objects = result.value { + self.dbClient.delete(objects, completion: { result in + isDeleted = result.value != nil + expectationHit.fulfill() + }) } - Task.whenAll(tasks) - .continueOnSuccessWith { createdTasks in - expectation.fulfill() - } - .waitUntilCompleted() + }) + + waitForExpectations(timeout: 1) { _ in + XCTAssert(isDeleted) } } } - diff --git a/Example/DBClientTests/Interface/ExecuteTests.swift b/Example/DBClientTests/Interface/ExecuteTests.swift index 0e874b4..9ac7cf6 100644 --- a/Example/DBClientTests/Interface/ExecuteTests.swift +++ b/Example/DBClientTests/Interface/ExecuteTests.swift @@ -7,156 +7,123 @@ // import XCTest -import BoltsSwift import DBClient @testable import Example final class ExecuteTests: DBClientTest { - func testNakedExecute() { - let user = createRandomUser() + func test_SingleExecute_WhenSuccessful_ReturnsCount() { + let randomUser = User.createRandom() + let expectationObject = expectation(description: "Object") + var expectedCount = 0 - let request = FetchRequest() - execute { expectation in - self.dbClient - .execute(request) - .continueOnSuccessWith { users in - XCTAssertEqual(users.count, 1) - XCTAssertEqual(user, users.first!) - expectation.fulfill() - } - .waitUntilCompleted() + self.dbClient.insert(randomUser, completion: { result in + if let _ = result.value { + let request = FetchRequest() + self.dbClient.execute(request, completion: { result in + expectedCount = result.value?.count ?? 0 + expectationObject.fulfill() + }) + } + }) + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedCount, 1) } } - func testOffset() { - let randomUsers = createRandomUsers(10) + func test_ExecuteWithOffset_WhenSuccessful_ReturnsCount() { + let randomUsers: [User] = (0...10).map { _ in User.createRandom() } let offset = 5 let shiftedUsers = Array(randomUsers[offset..(fetchOffset: offset) + self.dbClient.execute(request, completion: { result in + expectedCount = result.value?.count ?? 0 + expectationObjects.fulfill() + }) + } + }) - let request = FetchRequest(fetchOffset: offset) - execute { expectation in - self.dbClient - .execute(request) - .continueWith { task in - guard let users = task.result else { - XCTFail(String(describing: task.error)) - return - } - - // check only count of arrays beacause we haven't specified sorting - XCTAssertEqual(shiftedUsers.count, users.count) - expectation.fulfill() - } - .waitUntilCompleted() + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedCount, shiftedUsers.count) } } - func testLimit() { - createRandomUsers(10) + func test_ExecuteWithLimit_WhenSuccessful_ReturnsCount() { + let randomUsers: [User] = (0...10).map { _ in User.createRandom() } let limit = 3 + + let expectationObjects = expectation(description: "Object") + var expectedCount = 0 - let request = FetchRequest(fetchLimit: limit) - execute { expectation in - self.dbClient - .execute(request) - .continueOnSuccessWith { users in - XCTAssertEqual(limit, users.count) - expectation.fulfill() - } - .waitUntilCompleted() + self.dbClient.insert(randomUsers, completion: { result in + if let _ = result.value { + let request = FetchRequest(fetchLimit: limit) + self.dbClient.execute(request, completion: { result in + expectedCount = result.value?.count ?? 0 + expectationObjects.fulfill() + }) + } + }) + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedCount, limit) } } - func testOrder() { + func test_ExecuteWithSortDescriptor_WhenSuccessful_ReturnsCount() { let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) let order: ComparisonResult = sortDescriptor.ascending ? .orderedAscending : .orderedDescending + let randomUsers: [User] = (0...10).map { _ in User.createRandom() } + let sortedUsers = randomUsers.sorted { $0.name.compare($1.name) == order } + let expectationObjects = expectation(description: "Object") + var expectedUsers = [User]() - let randomUsers = createRandomUsers(10).sorted { $0.name.compare($1.name) == order } + self.dbClient.insert(randomUsers, completion: { result in + if let _ = result.value { + let request = FetchRequest(sortDescriptor: sortDescriptor) + + self.dbClient.execute(request, completion: { result in + expectedUsers = result.value ?? [] + expectationObjects.fulfill() + }) + } + }) - let request = FetchRequest(sortDescriptor: sortDescriptor) - execute { expectation in - self.dbClient - .execute(request) - .continueOnSuccessWith { users in - XCTAssertEqual(randomUsers, users) - expectation.fulfill() - } - .waitUntilCompleted() + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUsers, sortedUsers) } } - func testPredicate() { + func test_ExecuteWithPredicate_WhenSuccessful_ReturnsCount() { let arg = "1" let predicate = NSPredicate(format: "SELF.id ENDSWITH %@", arg) - - let randomUsers = createRandomUsers(10).filter { - $0.id.hasSuffix(arg) + let randomUsers: [User] = (0...10).map { _ in User.createRandom() } + let preicatedUsers = randomUsers.filter { + $0.id.hasSuffix(arg) } + let expectationObjects = expectation(description: "Object") + var expectedUsers = [User]() - let request = FetchRequest(predicate: predicate) - execute { expectation in - self.dbClient - .execute(request) - .continueOnSuccessWith { users in - XCTAssertEqual(randomUsers, users) - expectation.fulfill() - } - .waitUntilCompleted() - } - } - - func testOffsetWithOrder() { - let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) - let order: ComparisonResult = sortDescriptor.ascending ? .orderedAscending : .orderedDescending + self.dbClient.insert(randomUsers, completion: { result in + if let _ = result.value { + let request = FetchRequest(predicate: predicate) - let randomUsers = createRandomUsers(10) - let offset = 5 - let limit = 2 - let sortedUsers = randomUsers.sorted { $0.name.compare($1.name) == order } - let shiftedUsers = Array(sortedUsers[offset..(sortDescriptor: sortDescriptor, fetchOffset: offset, fetchLimit: limit) - execute { expectation in - self.dbClient - .execute(request) - .continueOnSuccessWith { users in - XCTAssertEqual(shiftedUsers, users) - expectation.fulfill() - } - .waitUntilCompleted() + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUsers, preicatedUsers) } } - - func testCombinedRequest() { - let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) - let order: ComparisonResult = sortDescriptor.ascending ? .orderedAscending : .orderedDescending - let arg = "1" - let predicate = NSPredicate(format: "SELF.id BEGINSWITH %@", arg) - - let randomUsers = createRandomUsers(50) - let offset = 2 - let limit = 5 - var users = randomUsers.filter { $0.id.hasPrefix(arg) } - users = users.sorted { $0.name.compare($1.name) == order } - users = Array(users[offset..( - predicate: predicate, - sortDescriptor: sortDescriptor, - fetchOffset: offset, - fetchLimit: limit - ) - execute { expectation in - self.dbClient - .execute(request) - .continueOnSuccessWith { fetchedUsers in - XCTAssertEqual(users, fetchedUsers) - expectation.fulfill() - } - .waitUntilCompleted() - } - } - } diff --git a/Example/DBClientTests/Interface/FetchTests.swift b/Example/DBClientTests/Interface/FetchTests.swift index a4eb305..6086245 100644 --- a/Example/DBClientTests/Interface/FetchTests.swift +++ b/Example/DBClientTests/Interface/FetchTests.swift @@ -7,56 +7,44 @@ // import Foundation -import BoltsSwift import XCTest +import DBClient @testable import Example class FetchTests: DBClientTest { - func testSingleFetch() { - let user = createRandomUser() - // check if it has been successfully saved - execute { expectation in - self.dbClient - .findFirst(User.self, primaryValue: user.id) - .continueOnSuccessWith { fetchedUser in - XCTAssertEqual(user, fetchedUser) - expectation.fulfill() - } - } - } - - func testBulkFetch() { - let randomUsers: [User] = createRandomUsers(10).sorted() + func test_SingleFetch_WhenSuccessful_ReturnsObject() { + let randomUser = User.createRandom() + let expectationObject = expectation(description: "Object") + var expectedObject: User? - // check if generated users have been successfully saved - let request: Task<[User]> = dbClient.findAll() - execute { expectation in - request - .continueOnSuccessWith { fetchedUsers in - // use sort to match users order - XCTAssert(randomUsers == fetchedUsers.sorted()) - expectation.fulfill() - } - .waitUntilCompleted() + self.dbClient.insert(randomUser, completion: { result in + self.dbClient.findFirst(User.self, primaryValue: randomUser.id, completion: { result in + expectedObject = result.require() + expectationObject.fulfill() + }) + }) + + waitForExpectations(timeout: 5) { _ in + XCTAssertNotNil(expectedObject) } } - func testAsyncFetches() { - let randomUsers: [User] = createRandomUsers(100) - let userIds: [String] = randomUsers.map { $0.id } - var tasks: [Task] = [] + func test_BulkFetch_WhenSuccessful_ReturnsBulk() { + let randomUsers: [User] = (0...100).map { _ in User.createRandom() } + + let expectationObjects = expectation(description: "Objects") + var expectedObjectsCount = 0 - // async fetch them - execute { expectation in - for userId in userIds { - tasks.append(self.dbClient.findFirst(User.self, primaryValue: userId)) + self.dbClient.insert(randomUsers, completion: { result in + self.dbClient.findAll { (result: Result<[User]>) in + expectedObjectsCount = result.value?.count ?? 0 + expectationObjects.fulfill() } - Task.whenAll(tasks) - .continueOnSuccessWith { - expectation.fulfill() - } - .waitUntilCompleted() + }) + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedObjectsCount, randomUsers.count) } } diff --git a/Example/DBClientTests/Interface/ObservableTests.swift b/Example/DBClientTests/Interface/ObservableTests.swift index 527af50..253b044 100644 --- a/Example/DBClientTests/Interface/ObservableTests.swift +++ b/Example/DBClientTests/Interface/ObservableTests.swift @@ -1,137 +1,92 @@ -// -// ObservableTests.swift -// DBClient-Example -// -// Created by Roman Kyrylenko on 2/13/17. -// Copyright © 2017 Yalantis. All rights reserved. -// +//// +//// ObservableTests.swift +//// DBClient-Example +//// +//// Created by Roman Kyrylenko on 2/13/17. +//// Copyright © 2017 Yalantis. All rights reserved. +//// -import BoltsSwift import XCTest import DBClient @testable import Example final class ObservableTests: DBClientTest { - func testInsertionObservations() { + func test_InsertionObservation_WhenSuccessful_InvokesChnages() { let request = FetchRequest() let observable = dbClient.observable(for: request) - - let objectsToCreate = 50 + let objectsToCreate: [User] = (0...100).map { _ in User.createRandom() } + let expectationObject = expectation(description: "Object") + var expectedInsertedObjects = [User]() observable.observe { (change: ObservableChange) in switch change { - case .change(let change): - XCTAssertEqual(change.insertions.count, objectsToCreate) - XCTAssertEqual(change.objects.count, objectsToCreate) - - case .initial(let objects): - XCTAssert(objects.isEmpty) - - case .error(let error): - XCTFail("\(error)") + expectedInsertedObjects = change.insertions.map { $0.element } + expectationObject.fulfill() + default: break } } - createRandomUsers(objectsToCreate) + dbClient.insert(objectsToCreate) { _ in } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedInsertedObjects, objectsToCreate) + } } - func testUpdationObservations() { + func test_UpdationObservation_WhenSuccessful_InvokesChnages() { let request = FetchRequest() let observable = dbClient.observable(for: request) - - let numberOfUsers = 50 - createRandomUsers(numberOfUsers) - let numberOfUsersToUpdate = 10 + let objectsToCreate: [User] = (0...100).map { _ in User.createRandom() } + let expectationObject = expectation(description: "Object") + var expectedUpdatedObjects = [User]() observable.observe { (change: ObservableChange) in switch change { - case .change(let change): - XCTAssertEqual(change.objects.count, numberOfUsers) - XCTAssertEqual(change.modifications.count, numberOfUsersToUpdate) - - case .initial(let objects): - XCTAssertEqual(objects.count, numberOfUsers) - - case .error(let error): - XCTFail("\(error)") + if !change.modifications.isEmpty { + expectedUpdatedObjects = change.modifications.map { $0.element } + expectationObject.fulfill() + } + default: break } } - execute { expectation in - let request = FetchRequest(fetchLimit: numberOfUsersToUpdate) - self.dbClient.execute(request) - .continueOnSuccessWithTask { users -> Task<[User]> in - return self.dbClient.update(users) - } - .continueOnSuccessWith { _ in - expectation.fulfill() - } + dbClient.insert(objectsToCreate) { _ in + self.dbClient.update(objectsToCreate, completion: { _ in }) + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUpdatedObjects, objectsToCreate) } } - func testDeletionObservations() { + func test_DeletionObservation_WhenSuccessful_InvokesChnages() { let request = FetchRequest() let observable = dbClient.observable(for: request) - - let numberOfUsers = 50 - createRandomUsers(numberOfUsers) - let numberOfUsersToDelete = 10 + let objectsToCreate: [User] = (0...100).map { _ in User.createRandom() } + let expectationObject = expectation(description: "Object") + var expectedDeletedObjectsCount = 0 observable.observe { (change: ObservableChange) in switch change { - case .change(let change): - XCTAssertEqual(change.objects.count, numberOfUsers - numberOfUsersToDelete) - XCTAssertEqual(change.deletions.count, numberOfUsersToDelete) - - case .initial(let objects): - XCTAssertEqual(objects.count, numberOfUsers) - - case .error(let error): - XCTFail("\(error)") - } - } - - execute { expectation in - let request = FetchRequest(fetchLimit: numberOfUsersToDelete) - self.dbClient.execute(request) - .continueOnSuccessWithTask { users -> Task in - return self.dbClient.delete(users) + if !change.deletions.isEmpty { + expectedDeletedObjectsCount = change.deletions.count + expectationObject.fulfill() } - .continueOnSuccessWith { _ in - expectation.fulfill() + default: break } } - } - - func testComplexRequestObservations() { - let users = createRandomUsers(100) - let offset = 5 - let suffix = "1" - let numberOfMatchedUsers = users.filter { $0.id.hasSuffix(suffix) }.count - let request = FetchRequest( - predicate: NSPredicate(format: "SELF.id ENDSWITH %@", suffix), - fetchOffset: offset - ) - let observable = dbClient.observable(for: request) + dbClient.insert(objectsToCreate) { _ in + self.dbClient.delete(objectsToCreate, completion: { _ in }) + } - observable.observe { (change: ObservableChange) in - switch change { - - case .change(let change): - XCTAssertEqual(numberOfMatchedUsers - offset, change.objects.count) - - case .initial(let objects): - XCTAssertEqual(objects.count, numberOfMatchedUsers - offset) - - case .error(let error): - XCTFail("\(error)") - } + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedDeletedObjectsCount, objectsToCreate.count) } } - + } diff --git a/Example/DBClientTests/Interface/UpdateTests.swift b/Example/DBClientTests/Interface/UpdateTests.swift index baecca4..c92cb97 100644 --- a/Example/DBClientTests/Interface/UpdateTests.swift +++ b/Example/DBClientTests/Interface/UpdateTests.swift @@ -7,119 +7,26 @@ // import XCTest -import BoltsSwift @testable import Example final class UpdateTests: DBClientTest { - func testSingleUpdate() { - let randomUser = createRandomUser() - let userId = randomUser.id - let userName = "named \(randomUser.name)" - // update user's name - execute { expectation in - self.dbClient - .findFirst(User.self, primaryValue: userId) - .continueOnSuccessWithTask { user -> Task in - XCTAssert(user != nil) - user?.name = userName - return self.dbClient.update(user!) - } - .continueOnSuccessWith { _ in - expectation.fulfill() - } - .waitUntilCompleted() - } - // check it - execute { expectation in - self.dbClient - .findFirst(User.self, primaryValue: userId) - .continueOnSuccessWith { user in - XCTAssert(user != nil) - XCTAssert(user!.name == userName) - expectation.fulfill() - } - } - } - - func testPrimaryValueUpdate() { - let randomUser = createRandomUser() - let userId = randomUser.id - let newUserId = "n\(userId)" - // update user's id - execute { expectation in - self.dbClient - .findFirst(User.self, primaryValue: userId) - .continueOnSuccessWithTask { user -> Task in - XCTAssert(user != nil) - user?.id = newUserId - return self.dbClient.update(user!) - } - .continueWith { _ in - expectation.fulfill() - } - .waitUntilCompleted() - } - // check if it exists - execute { expectation in - self.dbClient - .findFirst(User.self, primaryValue: userId) - .continueOnSuccessWith { user in - // old value should exists - XCTAssert(user != nil) - expectation.fulfill() - } - } - execute { expectation in - self.dbClient - .findFirst(User.self, primaryValue: newUserId) - .continueOnSuccessWith { user in - // new value shouldn't - XCTAssert(user == nil) - expectation.fulfill() - } - } - - } - - func testBulkUpdates() { - // sort users by id to be sure that each arrays contains the same user at the same index - let randomUsers: [User] = createRandomUsers(100).sorted() - let userNames: [String] = randomUsers.map { "awesome \($0.name)" } + func test_UpdateUserName_WhenSuccessful_SetsCorrectName() { + let randomUser = User.createRandom() + let expectationObject = expectation(description: "Object") + var expectedUser: User? - // fetch and update them - execute { expectation in - let task: Task<[User]> = self.dbClient.findAll() - task - .continueOnSuccessWithTask { users -> Task<[User]> in - let sortedUsers = users.sorted() - let updatedUsers = (0.. User in - let user = sortedUsers[index] - user.name = userNames[index] - return user - } - - return self.dbClient.update(updatedUsers) - } - .continueOnSuccessWith { _ in - expectation.fulfill() - } - .waitUntilCompleted() - } + self.dbClient.insert(randomUser, completion: { result in + randomUser.name = "Bob" + self.dbClient.update(randomUser, completion: { result in + expectedUser = result.value + expectationObject.fulfill() + }) + }) - // check - execute { expectation in - let task: Task<[User]> = self.dbClient.findAll() - task - .continueOnSuccessWith { users in - let fetchedUserNames = users - .sorted() - .map { $0.name } - XCTAssertEqual(userNames, fetchedUserNames) - expectation.fulfill() - } - .waitUntilCompleted() + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUser?.name, "Bob") } } - + } diff --git a/Example/DBClientTests/Interface/UpsertTests.swift b/Example/DBClientTests/Interface/UpsertTests.swift index 89d59e5..1e6b006 100644 --- a/Example/DBClientTests/Interface/UpsertTests.swift +++ b/Example/DBClientTests/Interface/UpsertTests.swift @@ -7,26 +7,26 @@ // import XCTest -import BoltsSwift @testable import Example final class UpsertTests: DBClientTest { - func testUpsert() { - let savedUsers = createRandomUsers(10) + func test_UpsertUsers_WhenSuccessful_ReturnsUpsertedUsers() { let newUsers: [User] = (0...5).map { _ in User.createRandom() } - var combinedUsers = savedUsers - combinedUsers.append(contentsOf: newUsers) + let savedUsers: [User] = (0...5).map { _ in User.createRandom() } + let expectationObjects = expectation(description: "Object") + var expectedUsers = [User]() + let combinedUsers = savedUsers + newUsers + + self.dbClient.insert(savedUsers) { _ in + self.dbClient.upsert(combinedUsers, completion: { result in + expectedUsers = result.require().updated + result.require().inserted + expectationObjects.fulfill() + }) + } - execute { expectation in - self.dbClient - .upsert(combinedUsers) - .continueOnSuccessWith { upsertions in - XCTAssertEqual(savedUsers.sorted(), upsertions.updated.sorted()) - XCTAssertEqual(newUsers.sorted(), upsertions.inserted.sorted()) - expectation.fulfill() - } - .waitUntilCompleted() + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUsers, combinedUsers) } } From 943d2b7f201d25a3804575608e2d6888ddd2b726 Mon Sep 17 00:00:00 2001 From: Anton Vodolazkyi Date: Wed, 26 Sep 2018 22:12:55 +0300 Subject: [PATCH 13/47] update --- DBClient.podspec | 2 +- Example/DBClientTests/DBClientTest.swift | 4 +- .../DBClientTests/Interface/CreateTests.swift | 9 +- .../DBClientTests/Interface/DeleteTests.swift | 16 ++-- .../Interface/ExecuteTests.swift | 50 +++++------ .../DBClientTests/Interface/FetchTests.swift | 12 +-- .../Interface/ObservableTests.swift | 4 +- .../DBClientTests/Interface/UpdateTests.swift | 8 +- .../DBClientTests/Interface/UpsertTests.swift | 8 +- Example/DBClientTests/User+Comparable.swift | 8 +- Example/DBClientTests/User+Equtable.swift | 2 +- Example/Example.xcodeproj/project.pbxproj | 40 ++++++--- Example/Example/DBClientInjector.swift | 15 ++-- Example/Example/DetailViewController.swift | 2 +- Example/Example/MasterViewController.swift | 4 +- .../CoreData/ManagedUser+CoreDataClass.swift | 1 - .../ManagedUser+CoreDataProperties.swift | 3 +- Example/Podfile | 4 +- Example/Podfile.lock | 4 +- Result.swift | 83 ------------------- 20 files changed, 105 insertions(+), 174 deletions(-) delete mode 100644 Result.swift diff --git a/DBClient.podspec b/DBClient.podspec index 9bd7583..00bc4d6 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -10,7 +10,7 @@ Pod::Spec.new do |s| s.social_media_url = 'https://yalantis.com/' s.homepage = 'https://yalantis.com/' - s.ios.deployment_target = "9.0" + s.ios.deployment_target = "10.0" s.default_subspec = "Core" diff --git a/Example/DBClientTests/DBClientTest.swift b/Example/DBClientTests/DBClientTest.swift index fc8f5c5..5f6c26b 100644 --- a/Example/DBClientTests/DBClientTest.swift +++ b/Example/DBClientTests/DBClientTest.swift @@ -53,10 +53,10 @@ class DBClientTest: XCTestCase { dbClient.findAll { (result: Result<[User]>) in if let objects = result.value { - self.dbClient.delete(objects, completion: { result in + self.dbClient.delete(objects) { _ in isDeleted = true expectationDeleletion.fulfill() - }) + } } } diff --git a/Example/DBClientTests/Interface/CreateTests.swift b/Example/DBClientTests/Interface/CreateTests.swift index a5023b5..7f7548d 100644 --- a/Example/DBClientTests/Interface/CreateTests.swift +++ b/Example/DBClientTests/Interface/CreateTests.swift @@ -16,10 +16,10 @@ final class CreateTests: DBClientTest { let expectationObject = expectation(description: "Object") var expectedObject: User? - self.dbClient.insert(randomUser, completion: { result in + self.dbClient.insert(randomUser) { result in expectedObject = result.value expectationObject.fulfill() - }) + } waitForExpectations(timeout: 1) { _ in XCTAssertNotNil(expectedObject) @@ -32,10 +32,10 @@ final class CreateTests: DBClientTest { let expectationObjects = expectation(description: "Objects") var expectedObjectsCount = 0 - self.dbClient.insert(randomUsers, completion: { result in + self.dbClient.insert(randomUsers) { result in expectedObjectsCount = result.value?.count ?? 0 expectationObjects.fulfill() - }) + } waitForExpectations(timeout: 1) { _ in XCTAssertEqual(expectedObjectsCount, randomUsers.count) @@ -43,4 +43,3 @@ final class CreateTests: DBClientTest { } } - diff --git a/Example/DBClientTests/Interface/DeleteTests.swift b/Example/DBClientTests/Interface/DeleteTests.swift index d1835d7..b34489e 100644 --- a/Example/DBClientTests/Interface/DeleteTests.swift +++ b/Example/DBClientTests/Interface/DeleteTests.swift @@ -16,14 +16,14 @@ final class DeleteTests: DBClientTest { let expectationHit = expectation(description: "Object") var isDeleted = false - self.dbClient.insert(randomUser, completion: { result in + self.dbClient.insert(randomUser) { result in if let object = result.value { - self.dbClient.delete(object, completion: { result in + self.dbClient.delete(object) { result in isDeleted = result.value != nil expectationHit.fulfill() - }) + } } - }) + } waitForExpectations(timeout: 1) { _ in XCTAssert(isDeleted) @@ -35,14 +35,14 @@ final class DeleteTests: DBClientTest { let expectationHit = expectation(description: "Object") var isDeleted = false - self.dbClient.insert(randomUsers, completion: { result in + self.dbClient.insert(randomUsers) { result in if let objects = result.value { - self.dbClient.delete(objects, completion: { result in + self.dbClient.delete(objects) { result in isDeleted = result.value != nil expectationHit.fulfill() - }) + } } - }) + } waitForExpectations(timeout: 1) { _ in XCTAssert(isDeleted) diff --git a/Example/DBClientTests/Interface/ExecuteTests.swift b/Example/DBClientTests/Interface/ExecuteTests.swift index 9ac7cf6..e71ee3b 100644 --- a/Example/DBClientTests/Interface/ExecuteTests.swift +++ b/Example/DBClientTests/Interface/ExecuteTests.swift @@ -17,15 +17,15 @@ final class ExecuteTests: DBClientTest { let expectationObject = expectation(description: "Object") var expectedCount = 0 - self.dbClient.insert(randomUser, completion: { result in - if let _ = result.value { + self.dbClient.insert(randomUser) { result in + if result.value != nil { let request = FetchRequest() - self.dbClient.execute(request, completion: { result in + self.dbClient.execute(request) { result in expectedCount = result.value?.count ?? 0 expectationObject.fulfill() - }) + } } - }) + } waitForExpectations(timeout: 1) { _ in XCTAssertEqual(expectedCount, 1) @@ -39,15 +39,15 @@ final class ExecuteTests: DBClientTest { let expectationObjects = expectation(description: "Object") var expectedCount = 0 - self.dbClient.insert(randomUsers, completion: { result in - if let _ = result.value { + self.dbClient.insert(randomUsers) { result in + if result.value != nil { let request = FetchRequest(fetchOffset: offset) - self.dbClient.execute(request, completion: { result in + self.dbClient.execute(request) { result in expectedCount = result.value?.count ?? 0 expectationObjects.fulfill() - }) + } } - }) + } waitForExpectations(timeout: 1) { _ in XCTAssertEqual(expectedCount, shiftedUsers.count) @@ -61,15 +61,15 @@ final class ExecuteTests: DBClientTest { let expectationObjects = expectation(description: "Object") var expectedCount = 0 - self.dbClient.insert(randomUsers, completion: { result in - if let _ = result.value { + self.dbClient.insert(randomUsers) { result in + if result.value != nil { let request = FetchRequest(fetchLimit: limit) - self.dbClient.execute(request, completion: { result in + self.dbClient.execute(request) { result in expectedCount = result.value?.count ?? 0 expectationObjects.fulfill() - }) + } } - }) + } waitForExpectations(timeout: 1) { _ in XCTAssertEqual(expectedCount, limit) @@ -84,16 +84,16 @@ final class ExecuteTests: DBClientTest { let expectationObjects = expectation(description: "Object") var expectedUsers = [User]() - self.dbClient.insert(randomUsers, completion: { result in - if let _ = result.value { + self.dbClient.insert(randomUsers) { result in + if result.value != nil { let request = FetchRequest(sortDescriptor: sortDescriptor) - self.dbClient.execute(request, completion: { result in + self.dbClient.execute(request) { result in expectedUsers = result.value ?? [] expectationObjects.fulfill() - }) + } } - }) + } waitForExpectations(timeout: 1) { _ in XCTAssertEqual(expectedUsers, sortedUsers) @@ -110,16 +110,16 @@ final class ExecuteTests: DBClientTest { let expectationObjects = expectation(description: "Object") var expectedUsers = [User]() - self.dbClient.insert(randomUsers, completion: { result in - if let _ = result.value { + self.dbClient.insert(randomUsers) { result in + if result.value != nil { let request = FetchRequest(predicate: predicate) - self.dbClient.execute(request, completion: { result in + self.dbClient.execute(request) { result in expectedUsers = result.value ?? [] expectationObjects.fulfill() - }) + } } - }) + } waitForExpectations(timeout: 1) { _ in XCTAssertEqual(expectedUsers, preicatedUsers) diff --git a/Example/DBClientTests/Interface/FetchTests.swift b/Example/DBClientTests/Interface/FetchTests.swift index 6086245..b587745 100644 --- a/Example/DBClientTests/Interface/FetchTests.swift +++ b/Example/DBClientTests/Interface/FetchTests.swift @@ -18,12 +18,12 @@ class FetchTests: DBClientTest { let expectationObject = expectation(description: "Object") var expectedObject: User? - self.dbClient.insert(randomUser, completion: { result in - self.dbClient.findFirst(User.self, primaryValue: randomUser.id, completion: { result in + self.dbClient.insert(randomUser) { result in + self.dbClient.findFirst(User.self, primaryValue: randomUser.id) { result in expectedObject = result.require() expectationObject.fulfill() - }) - }) + } + } waitForExpectations(timeout: 5) { _ in XCTAssertNotNil(expectedObject) @@ -36,12 +36,12 @@ class FetchTests: DBClientTest { let expectationObjects = expectation(description: "Objects") var expectedObjectsCount = 0 - self.dbClient.insert(randomUsers, completion: { result in + self.dbClient.insert(randomUsers) { result in self.dbClient.findAll { (result: Result<[User]>) in expectedObjectsCount = result.value?.count ?? 0 expectationObjects.fulfill() } - }) + } waitForExpectations(timeout: 1) { _ in XCTAssertEqual(expectedObjectsCount, randomUsers.count) diff --git a/Example/DBClientTests/Interface/ObservableTests.swift b/Example/DBClientTests/Interface/ObservableTests.swift index 253b044..a6e1b4c 100644 --- a/Example/DBClientTests/Interface/ObservableTests.swift +++ b/Example/DBClientTests/Interface/ObservableTests.swift @@ -54,7 +54,7 @@ final class ObservableTests: DBClientTest { } dbClient.insert(objectsToCreate) { _ in - self.dbClient.update(objectsToCreate, completion: { _ in }) + self.dbClient.update(objectsToCreate) { _ in } } waitForExpectations(timeout: 1) { _ in @@ -81,7 +81,7 @@ final class ObservableTests: DBClientTest { } dbClient.insert(objectsToCreate) { _ in - self.dbClient.delete(objectsToCreate, completion: { _ in }) + self.dbClient.delete(objectsToCreate) { _ in } } waitForExpectations(timeout: 1) { _ in diff --git a/Example/DBClientTests/Interface/UpdateTests.swift b/Example/DBClientTests/Interface/UpdateTests.swift index c92cb97..e6ad4dd 100644 --- a/Example/DBClientTests/Interface/UpdateTests.swift +++ b/Example/DBClientTests/Interface/UpdateTests.swift @@ -16,13 +16,13 @@ final class UpdateTests: DBClientTest { let expectationObject = expectation(description: "Object") var expectedUser: User? - self.dbClient.insert(randomUser, completion: { result in + self.dbClient.insert(randomUser) { result in randomUser.name = "Bob" - self.dbClient.update(randomUser, completion: { result in + self.dbClient.update(randomUser) { result in expectedUser = result.value expectationObject.fulfill() - }) - }) + } + } waitForExpectations(timeout: 1) { _ in XCTAssertEqual(expectedUser?.name, "Bob") diff --git a/Example/DBClientTests/Interface/UpsertTests.swift b/Example/DBClientTests/Interface/UpsertTests.swift index 1e6b006..4d1e68d 100644 --- a/Example/DBClientTests/Interface/UpsertTests.swift +++ b/Example/DBClientTests/Interface/UpsertTests.swift @@ -11,18 +11,18 @@ import XCTest final class UpsertTests: DBClientTest { - func test_UpsertUsers_WhenSuccessful_ReturnsUpsertedUsers() { + func test_UpsertUsers_WhenSuccessful_ReturnsUpsertedUsers() { let newUsers: [User] = (0...5).map { _ in User.createRandom() } let savedUsers: [User] = (0...5).map { _ in User.createRandom() } let expectationObjects = expectation(description: "Object") var expectedUsers = [User]() let combinedUsers = savedUsers + newUsers - + self.dbClient.insert(savedUsers) { _ in - self.dbClient.upsert(combinedUsers, completion: { result in + self.dbClient.upsert(combinedUsers) { result in expectedUsers = result.require().updated + result.require().inserted expectationObjects.fulfill() - }) + } } waitForExpectations(timeout: 1) { _ in diff --git a/Example/DBClientTests/User+Comparable.swift b/Example/DBClientTests/User+Comparable.swift index e6dddb9..9e87ec7 100644 --- a/Example/DBClientTests/User+Comparable.swift +++ b/Example/DBClientTests/User+Comparable.swift @@ -11,19 +11,19 @@ // allows us to use `.sorted()` on the array of `User objects extension User: Comparable { - public static func <(lhs: User, rhs: User) -> Bool { + public static func < (lhs: User, rhs: User) -> Bool { return lhs.id < rhs.id } - public static func <=(lhs: User, rhs: User) -> Bool { + public static func <= (lhs: User, rhs: User) -> Bool { return lhs.id <= rhs.id } - public static func >=(lhs: User, rhs: User) -> Bool { + public static func >= (lhs: User, rhs: User) -> Bool { return lhs.id >= rhs.id } - public static func >(lhs: User, rhs: User) -> Bool { + public static func > (lhs: User, rhs: User) -> Bool { return rhs.id > rhs.id } diff --git a/Example/DBClientTests/User+Equtable.swift b/Example/DBClientTests/User+Equtable.swift index 01c4567..374c77e 100644 --- a/Example/DBClientTests/User+Equtable.swift +++ b/Example/DBClientTests/User+Equtable.swift @@ -11,7 +11,7 @@ // allows us to use `XCAssertEqual` on `User` objects extension User: Equatable { - public static func ==(lhs: User, rhs: User) -> Bool { + public static func == (lhs: User, rhs: User) -> Bool { return lhs.id == rhs.id && lhs.name == rhs.name } diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index bf5c411..a462198 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -246,6 +246,7 @@ C53372531E26155D004ECBCF /* Frameworks */, C53372541E26155D004ECBCF /* Resources */, 5DD0265501157C9DFB18D487 /* [CP] Embed Pods Frameworks */, + 8839DF70215C08D90021A85A /* ShellScript */, ); buildRules = ( ); @@ -263,18 +264,18 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0820; - LastUpgradeCheck = 0900; + LastUpgradeCheck = 1000; ORGANIZATIONNAME = Yalantis; TargetAttributes = { B8275AFC1E4B6D2500232EE4 = { CreatedOnToolsVersion = 8.2.1; - LastSwiftMigration = 0900; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; TestTargetID = C53372551E26155D004ECBCF; }; C53372551E26155D004ECBCF = { CreatedOnToolsVersion = 8.2.1; - LastSwiftMigration = 0900; + LastSwiftMigration = 1000; ProvisioningStyle = Automatic; }; }; @@ -381,6 +382,23 @@ shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; showEnvVarsInLog = 0; }; + 8839DF70215C08D90021A85A /* ShellScript */ = { + isa = PBXShellScriptBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/sh; + shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + }; BF151D4AF0AD5B6D29F45416 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; buildActionMask = 2147483647; @@ -476,8 +494,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.DBClientTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; }; name = Debug; @@ -491,8 +508,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.DBClientTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; }; name = Release; @@ -510,6 +526,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; @@ -517,6 +534,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; @@ -566,6 +584,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; @@ -573,6 +592,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; @@ -611,8 +631,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.dbclient.Example; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Debug; }; @@ -625,8 +644,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.dbclient.Example; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_SWIFT3_OBJC_INFERENCE = Default; - SWIFT_VERSION = 4.0; + SWIFT_VERSION = 4.2; }; name = Release; }; diff --git a/Example/Example/DBClientInjector.swift b/Example/Example/DBClientInjector.swift index 36ecb08..977074d 100644 --- a/Example/Example/DBClientInjector.swift +++ b/Example/Example/DBClientInjector.swift @@ -11,9 +11,9 @@ import DBClient import RealmSwift private struct DBClientInjector { - + static let coreDataClient: DBClient = CoreDataDBClient(forModel: "Users") - + static let realmClient: DBClient = { let realm: Realm do { @@ -23,18 +23,15 @@ private struct DBClientInjector { } return RealmDBClient(realm: realm) }() - + } protocol DBClientInjectable {} extension DBClientInjectable { - + var dbClient: DBClient { - get { -// return DBClientInjector.coreDataClient - return DBClientInjector.realmClient - } + // return DBClientInjector.coreDataClient + return DBClientInjector.realmClient } - } diff --git a/Example/Example/DetailViewController.swift b/Example/Example/DetailViewController.swift index e235a26..aaf4903 100644 --- a/Example/Example/DetailViewController.swift +++ b/Example/Example/DetailViewController.swift @@ -23,7 +23,7 @@ class DetailViewController: UIViewController, DBClientInjectable { @IBAction private func saveButtonAction() { detailItem.name = userNameTextField.text ?? "" - dbClient.update(detailItem) { [weak self] result in + dbClient.update(detailItem) { [weak self] _ in self?.navigationController?.popViewController(animated: true) } } diff --git a/Example/Example/MasterViewController.swift b/Example/Example/MasterViewController.swift index 10a2bdb..0a61aca 100644 --- a/Example/Example/MasterViewController.swift +++ b/Example/Example/MasterViewController.swift @@ -98,13 +98,13 @@ extension MasterViewController { return true } - override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) { + override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) { guard editingStyle == .delete else { return } let user = objects[indexPath.row] - dbClient.delete(user, completion: { _ in }) + dbClient.delete(user) { _ in } } } diff --git a/Example/Example/Models/CoreData/ManagedUser+CoreDataClass.swift b/Example/Example/Models/CoreData/ManagedUser+CoreDataClass.swift index 15ede89..1c40091 100644 --- a/Example/Example/Models/CoreData/ManagedUser+CoreDataClass.swift +++ b/Example/Example/Models/CoreData/ManagedUser+CoreDataClass.swift @@ -13,4 +13,3 @@ import CoreData public class ManagedUser: NSManagedObject { } - diff --git a/Example/Example/Models/CoreData/ManagedUser+CoreDataProperties.swift b/Example/Example/Models/CoreData/ManagedUser+CoreDataProperties.swift index 49fd4d8..e449d79 100644 --- a/Example/Example/Models/CoreData/ManagedUser+CoreDataProperties.swift +++ b/Example/Example/Models/CoreData/ManagedUser+CoreDataProperties.swift @@ -11,7 +11,8 @@ import CoreData extension ManagedUser { - @nonobjc class func fetchRequest() -> NSFetchRequest { + @nonobjc + class func fetchRequest() -> NSFetchRequest { return NSFetchRequest(entityName: User.entityName) } diff --git a/Example/Podfile b/Example/Podfile index 6fc1b17..93b7041 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -1,4 +1,4 @@ -platform :ios, '9.0' +platform :ios, '10.0' use_frameworks! target 'Example' do @@ -13,4 +13,4 @@ target 'DBClientTests' do pod 'DBClient/Realm', :path => '../' pod 'DBClient/CoreData', :path => '../' -end \ No newline at end of file +end diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 49b4eee..9310b2d 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -25,10 +25,10 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - DBClient: 3d8e3884fe65ea068edb6453f4d70ec00bb0c8e3 + DBClient: 0ffb4b6203663236b7d6ea1b75e121e5166a5a26 Realm: 0ef72b837fb67e9f4b098bac771ddd72c7fdbb69 RealmSwift: 07a9ae0505091eda6b2ee7c190c3786d6e90a7b0 -PODFILE CHECKSUM: e1b66d1226f9ec10a94bb2534dbf6b4ca65549de +PODFILE CHECKSUM: 25c038a30b752fe0113957b0d196c0fc91287f73 COCOAPODS: 1.5.3 diff --git a/Result.swift b/Result.swift deleted file mode 100644 index 9e6f675..0000000 --- a/Result.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// Result.swift -// DBClient -// -// Created by Vodolazkyi Anton on 9/19/18. -// - -import Foundation - -public enum Result { - - case success(T) - case failure(Error) -} - -extension Result { - - public var value: T? { - switch self { - case .success(let result): return result - case .failure: return nil - } - } - - public var error: Error? { - switch self { - case .success: return nil - case .failure(let error): return error - } - } -} - -extension Result { - - @discardableResult - public func map(_ f: (T) -> U) -> Result { - switch self { - case .success(let t): return .success(f(t)) - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func map(_ f: () -> U) -> Result { - switch self { - case .success: return .success(f()) - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func next(_ f: (T) -> Result) -> Result { - switch self { - case .success(let t): return f(t) - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func next(_ f: () -> Result) -> Result { - switch self { - case .success: return f() - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func onError(_ f: (Error) -> Error) -> Result { - switch self { - case .success(let value): return .success(value) - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func require() -> T { - switch self { - case .success(let value): return value - case .failure(let error): fatalError("Value is required: \(error)") - } - } - -} From 1f86dcac7116ba34970f2d08f26536bd48619604 Mon Sep 17 00:00:00 2001 From: Anton Vodolazkyi Date: Wed, 3 Oct 2018 18:32:06 +0300 Subject: [PATCH 14/47] update dependencies --- .swift-version | 1 - DBClient.podspec | 2 + DBClient/Core/DBClient.swift | 3 + DBClient/Core/Result.swift | 83 ----------------------- Example/Example.xcodeproj/project.pbxproj | 7 +- Example/Podfile.lock | 10 ++- 6 files changed, 19 insertions(+), 87 deletions(-) delete mode 100644 .swift-version delete mode 100644 DBClient/Core/Result.swift diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 389f774..0000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -4.0 \ No newline at end of file diff --git a/DBClient.podspec b/DBClient.podspec index 00bc4d6..fbc181b 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -12,6 +12,8 @@ Pod::Spec.new do |s| s.ios.deployment_target = "10.0" + s.dependency 'YALResult', '1.0' + s.default_subspec = "Core" s.subspec "Core" do |spec| diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index 1a64a5e..eb2c5d2 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -7,6 +7,9 @@ // import Foundation +import YALResult + +public typealias Result = YALResult.Result public enum DBClientError: Error { case noPrimaryKey diff --git a/DBClient/Core/Result.swift b/DBClient/Core/Result.swift deleted file mode 100644 index 8c403e5..0000000 --- a/DBClient/Core/Result.swift +++ /dev/null @@ -1,83 +0,0 @@ -// -// Result.swift -// APIClient -// -// Created by Vodolazkyi Anton on 9/19/18. -// - -import Foundation - -public enum Result { - - case success(T) - case failure(Error) -} - -extension Result { - - public var value: T? { - switch self { - case .success(let result): return result - case .failure: return nil - } - } - - public var error: Error? { - switch self { - case .success: return nil - case .failure(let error): return error - } - } -} - -extension Result { - - @discardableResult - public func map(_ f: (T) -> U) -> Result { - switch self { - case .success(let t): return .success(f(t)) - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func map(_ f: () -> U) -> Result { - switch self { - case .success: return .success(f()) - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func next(_ f: (T) -> Result) -> Result { - switch self { - case .success(let t): return f(t) - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func next(_ f: () -> Result) -> Result { - switch self { - case .success: return f() - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func onError(_ f: (Error) -> Error) -> Result { - switch self { - case .success(let value): return .success(value) - case .failure(let error): return .failure(error) - } - } - - @discardableResult - public func require() -> T { - switch self { - case .success(let value): return value - case .failure(let error): fatalError("Value is required: \(error)") - } - } - -} diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index a462198..108ad4f 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -330,12 +330,14 @@ "${BUILT_PRODUCTS_DIR}/DBClient/DBClient.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", + "${BUILT_PRODUCTS_DIR}/YALResult/YALResult.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DBClient.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YALResult.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -352,12 +354,14 @@ "${BUILT_PRODUCTS_DIR}/DBClient/DBClient.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", + "${BUILT_PRODUCTS_DIR}/YALResult/YALResult.framework", ); name = "[CP] Embed Pods Frameworks"; outputPaths = ( "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/DBClient.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Realm.framework", "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/RealmSwift.framework", + "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/YALResult.framework", ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; @@ -397,7 +401,8 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "if which swiftlint >/dev/null; then\nswiftlint\nelse\necho \"warning: SwiftLint not installed, download from https://github.com/realm/SwiftLint\"\nfi\n"; + shellScript = " +"; }; BF151D4AF0AD5B6D29F45416 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 9310b2d..f2eeda9 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,15 +1,19 @@ PODS: - - DBClient/Core (0.7) + - DBClient/Core (0.7): + - YALResult (= 1.0) - DBClient/CoreData (0.7): - DBClient/Core + - YALResult (= 1.0) - DBClient/Realm (0.7): - DBClient/Core - RealmSwift (~> 2.10.1) + - YALResult (= 1.0) - Realm (2.10.2): - Realm/Headers (= 2.10.2) - Realm/Headers (2.10.2) - RealmSwift (2.10.2): - Realm (= 2.10.2) + - YALResult (1.0) DEPENDENCIES: - DBClient/CoreData (from `../`) @@ -19,15 +23,17 @@ SPEC REPOS: https://github.com/cocoapods/specs.git: - Realm - RealmSwift + - YALResult EXTERNAL SOURCES: DBClient: :path: "../" SPEC CHECKSUMS: - DBClient: 0ffb4b6203663236b7d6ea1b75e121e5166a5a26 + DBClient: f932742894913327efd48fb5312c6746145d9fe5 Realm: 0ef72b837fb67e9f4b098bac771ddd72c7fdbb69 RealmSwift: 07a9ae0505091eda6b2ee7c190c3786d6e90a7b0 + YALResult: 0ce7a5a3b12a4e06b39725cebb86f3e7ea3c9d8e PODFILE CHECKSUM: 25c038a30b752fe0113957b0d196c0fc91287f73 From 0a6b36f3796e682b9d47e3ff829aa8b14e5d5f8d Mon Sep 17 00:00:00 2001 From: Anton Vodolazkyi Date: Thu, 4 Oct 2018 11:17:32 +0300 Subject: [PATCH 15/47] update podspec --- DBClient.podspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DBClient.podspec b/DBClient.podspec index fbc181b..6a9f917 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DBClient" - s.version = "0.7" + s.version = "1.0" s.requires_arc = true s.summary = "CoreData & Realm wrapper written on Swift" s.homepage = "" From 19dc37cd64887ffd33e61e538c719f4f711f31a4 Mon Sep 17 00:00:00 2001 From: Anton Vodolazkyi Date: Thu, 4 Oct 2018 11:23:31 +0300 Subject: [PATCH 16/47] add license --- DBClient.podspec | 2 +- License | 21 +++++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 License diff --git a/DBClient.podspec b/DBClient.podspec index 6a9f917..adb4dde 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -4,7 +4,7 @@ Pod::Spec.new do |s| s.requires_arc = true s.summary = "CoreData & Realm wrapper written on Swift" s.homepage = "" - s.license = 'MIT' + s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Yalantis" => "mail@yalantis.com" } s.source = { :git => "https://github.com/Yalantis/DBClient.git", :tag => s.version } s.social_media_url = 'https://yalantis.com/' diff --git a/License b/License new file mode 100644 index 0000000..2ccfa04 --- /dev/null +++ b/License @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2017 Yalantis + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. \ No newline at end of file From ce1e6d9c55e1a5d75fd3f393111678693aa62303 Mon Sep 17 00:00:00 2001 From: Denis Zabrod'ko Date: Thu, 11 Oct 2018 16:14:07 +0300 Subject: [PATCH 17/47] [Add] synchronous transactions possibility --- DBClient/Core/DBClient.swift | 28 ++++++ DBClient/CoreData/CoreDataDBClient.swift | 123 +++++++++++++++++++++++ DBClient/Realm/RealmDBClient.swift | 65 ++++++++++++ 3 files changed, 216 insertions(+) diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index eb2c5d2..bacd510 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -72,6 +72,34 @@ public protocol DBClient { /// - completion: `Result` with inserted and updated instances. func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) + /// Synchronously inserts objects to database. + /// + /// - Parameters: + /// - objects: list of objects to be inserted + /// - Returns: `Result` with inserted objects or appropriate error in case of failure. + func insert(_ objects: [T]) -> Result<[T]> + + /// Synchronously updates changed performed with objects to database. + /// + /// - Parameters: + /// - objects: list of objects to be updated + /// - Returns: `Result` with updated objects or appropriate error in case of failure. + func update(_ objects: [T]) -> Result<[T]> + + /// Synchronously deletes objects from database. + /// + /// - Parameters: + /// - objects: list of objects to be deleted + /// - Returns: `Result` with appropriate error in case of failure. + func delete(_ objects: [T]) -> Result<()> + + /// Synchronously iterates through given objects and updates existing in database instances or creates them + /// + /// - Parameters: + /// - objects: objects to be worked with + /// - Returns: `Result` with inserted and updated instances. + func upsert(_ objects: [T]) -> Result<(updated: [T], inserted: [T])> + } public extension DBClient { diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index a0d4c17..e451b7a 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -256,6 +256,22 @@ public class CoreDataDBClient { } } + fileprivate func performWriteTaskAndWait(_ closure: @escaping (NSManagedObjectContext, (() throws -> ())) -> ()) { + let context = writeManagedContext + context.performAndWait { + closure(context) { + try context.save(includingParent: true) + } + } + } + + fileprivate func performReadTaskAndWait(closure: @escaping (NSManagedObjectContext) -> ()) { + let context = readManagedContext + context.performAndWait { + closure(context) + } + } + } // MARK: - DBClient methods @@ -384,6 +400,113 @@ extension CoreDataDBClient: DBClient { } } + public func insert(_ objects: [T]) -> Result<[T]> { + checkType(T.self) + + var result: Result<[T]>! + + performWriteTaskAndWait { context, savingClosure in + var insertedObjects = [T]() + let foundObjects = self.find(objects: objects, in: context) + for (object, storedObject) in foundObjects { + if storedObject != nil { + continue + } + + _ = object.upsertManagedObject(in: context, existedInstance: nil) + insertedObjects.append(object as! T) + } + + do { + try savingClosure() + result = .success(insertedObjects) + } catch let error { + result = .failure(error) + } + } + + return result + } + + public func update(_ objects: [T]) -> Result<[T]> { + checkType(T.self) + + var result: Result<[T]>! + + performWriteTaskAndWait { context, savingClosure in + var updatedObjects = [T]() + + let foundObjects = self.find(objects: objects, in: context) + for (object, storedObject) in foundObjects { + guard let storedObject = storedObject else { + continue + } + + _ = object.upsertManagedObject(in: context, existedInstance: storedObject) + updatedObjects.append(object as! T) + } + + do { + try savingClosure() + result = .success(updatedObjects) + } catch let error { + result = .failure(error) + } + } + + return result + } + + public func delete(_ objects: [T]) -> Result<()> { + checkType(T.self) + + var result: Result<()>! + + performWriteTask { context, savingClosure in + let foundObjects = self.find(objects, in: context) + foundObjects.forEach { context.delete($0) } + + do { + try savingClosure() + result = .success(()) + } catch let error { + result = completion(.failure(error) + } + } + + return result + } + + public func upsert(_ objects: [T]) -> Result<(updated: [T], inserted: [T])> { + checkType(T.self) + + var result: Result<(updated: [T], inserted: [T])>! + + performWriteTaskAndWait { context, savingClosure in + var updatedObjects = [T]() + var insertedObjects = [T]() + let foundObjects = self.find(objects: objects, in: context) + + for (object, storedObject) in foundObjects { + _ = object.upsertManagedObject(in: context, existedInstance: storedObject) + if storedObject == nil { + insertedObjects.append(object as! T) + } else { + updatedObjects.append(object as! T) + } + } + + do { + try savingClosure() + result = .success((updated: updatedObjects, inserted: insertedObjects)) + } catch let error { + result = .failure(error) + } + } + + return result + } + } private extension CoreDataDBClient { diff --git a/DBClient/Realm/RealmDBClient.swift b/DBClient/Realm/RealmDBClient.swift index a172d70..b497cb1 100644 --- a/DBClient/Realm/RealmDBClient.swift +++ b/DBClient/Realm/RealmDBClient.swift @@ -137,6 +137,71 @@ extension RealmDBClient: DBClient { return RealmObservable(request: request, realm: realm) } + public func insert(_ objects: [T]) -> Result<[T]> { + checkType(T.self) + + let realmObjects = objects.compactMap { ($0 as? RealmModelConvertible)?.toRealmObject() } + + do { + realm.beginWrite() + realm.add(realmObjects) + try realm.commitWrite() + return .success(objects) + } catch let error { + return .failure(error) + } + } + + public func update(_ objects: [T]) -> Result<[T]> { + checkType(T.self) + + let realmObjects = separate(objects: objects) + .present + .compactMap { ($0 as? RealmModelConvertible)?.toRealmObject() } + do { + realm.beginWrite() + realm.add(realmObjects, update: true) + try realm.commitWrite() + + return .success(objects) + } catch let error { + return .failure(error) + } + } + + public func delete(_ objects: [T]) -> Result<()> { + let type = checkType(T.self) + + let realmType = type.realmClass() + + do { + let primaryValues = objects.compactMap { $0.valueOfPrimaryKey } + let realmObjects = primaryValues.compactMap { realm.object(ofType: realmType, forPrimaryKey: $0) } + realm.beginWrite() + realm.delete(realmObjects) + try realm.commitWrite() + + return .success(()) + } catch let error { + return .failure(error) + } + } + + public func upsert(_ objects: [T]) -> Result<(updated: [T], inserted: [T])> { + checkType(T.self) + + let separatedObjects = separate(objects: objects) + let realmObjects = objects.compactMap { ($0 as? RealmModelConvertible)?.toRealmObject() } + do { + realm.beginWrite() + realm.add(realmObjects, update: true) + try realm.commitWrite() + return .success((updated: separatedObjects.present, inserted: separatedObjects.new)) + } catch let error { + return .failure(error) + } + } + } private extension RealmDBClient { From 115aa4ae64d40b12b3e06ba9d408aad61dfef0dd Mon Sep 17 00:00:00 2001 From: Denis Zabrod'ko Date: Thu, 11 Oct 2018 16:23:41 +0300 Subject: [PATCH 18/47] [Fix] compile error --- DBClient/CoreData/CoreDataDBClient.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index e451b7a..919ac6a 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -470,7 +470,7 @@ extension CoreDataDBClient: DBClient { try savingClosure() result = .success(()) } catch let error { - result = completion(.failure(error) + result = .failure(error) } } From 53be981c0ca4abfbf7e930762377591cd973100e Mon Sep 17 00:00:00 2001 From: Denis Zabrod'ko Date: Thu, 11 Oct 2018 16:28:38 +0300 Subject: [PATCH 19/47] [Add] @discardableResult directives for new methods --- DBClient/Core/DBClient.swift | 4 ++++ DBClient/CoreData/CoreDataDBClient.swift | 4 ++++ DBClient/Realm/RealmDBClient.swift | 4 ++++ 3 files changed, 12 insertions(+) diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index bacd510..a27a075 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -77,6 +77,7 @@ public protocol DBClient { /// - Parameters: /// - objects: list of objects to be inserted /// - Returns: `Result` with inserted objects or appropriate error in case of failure. + @discardableResult func insert(_ objects: [T]) -> Result<[T]> /// Synchronously updates changed performed with objects to database. @@ -84,6 +85,7 @@ public protocol DBClient { /// - Parameters: /// - objects: list of objects to be updated /// - Returns: `Result` with updated objects or appropriate error in case of failure. + @discardableResult func update(_ objects: [T]) -> Result<[T]> /// Synchronously deletes objects from database. @@ -91,6 +93,7 @@ public protocol DBClient { /// - Parameters: /// - objects: list of objects to be deleted /// - Returns: `Result` with appropriate error in case of failure. + @discardableResult func delete(_ objects: [T]) -> Result<()> /// Synchronously iterates through given objects and updates existing in database instances or creates them @@ -98,6 +101,7 @@ public protocol DBClient { /// - Parameters: /// - objects: objects to be worked with /// - Returns: `Result` with inserted and updated instances. + @discardableResult func upsert(_ objects: [T]) -> Result<(updated: [T], inserted: [T])> } diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index 919ac6a..f90e299 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -400,6 +400,7 @@ extension CoreDataDBClient: DBClient { } } + @discardableResult public func insert(_ objects: [T]) -> Result<[T]> { checkType(T.self) @@ -428,6 +429,7 @@ extension CoreDataDBClient: DBClient { return result } + @discardableResult public func update(_ objects: [T]) -> Result<[T]> { checkType(T.self) @@ -457,6 +459,7 @@ extension CoreDataDBClient: DBClient { return result } + @discardableResult public func delete(_ objects: [T]) -> Result<()> { checkType(T.self) @@ -477,6 +480,7 @@ extension CoreDataDBClient: DBClient { return result } + @discardableResult public func upsert(_ objects: [T]) -> Result<(updated: [T], inserted: [T])> { checkType(T.self) diff --git a/DBClient/Realm/RealmDBClient.swift b/DBClient/Realm/RealmDBClient.swift index b497cb1..a167040 100644 --- a/DBClient/Realm/RealmDBClient.swift +++ b/DBClient/Realm/RealmDBClient.swift @@ -137,6 +137,7 @@ extension RealmDBClient: DBClient { return RealmObservable(request: request, realm: realm) } + @discardableResult public func insert(_ objects: [T]) -> Result<[T]> { checkType(T.self) @@ -152,6 +153,7 @@ extension RealmDBClient: DBClient { } } + @discardableResult public func update(_ objects: [T]) -> Result<[T]> { checkType(T.self) @@ -169,6 +171,7 @@ extension RealmDBClient: DBClient { } } + @discardableResult public func delete(_ objects: [T]) -> Result<()> { let type = checkType(T.self) @@ -187,6 +190,7 @@ extension RealmDBClient: DBClient { } } + @discardableResult public func upsert(_ objects: [T]) -> Result<(updated: [T], inserted: [T])> { checkType(T.self) From 5bdcf6a4ab2cbb802de7fb111a588b0a5293692b Mon Sep 17 00:00:00 2001 From: Denis Zabrod'ko Date: Thu, 11 Oct 2018 17:25:12 +0300 Subject: [PATCH 20/47] [Add] synchronous methods for fetch transactions --- DBClient/Core/DBClient.swift | 40 ++++++++++++++++++++++++ DBClient/CoreData/CoreDataDBClient.swift | 24 ++++++++++++++ DBClient/Realm/RealmDBClient.swift | 12 +++++++ 3 files changed, 76 insertions(+) diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index a27a075..2e5bdef 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -72,6 +72,13 @@ public protocol DBClient { /// - completion: `Result` with inserted and updated instances. func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) + /// Synchronously executes given request and calls completion result wrapped in `Result`. + /// + /// - Parameters: + /// - request: request to execute + /// - Returns: `Result` with array of objects or error in case of failude. + func execute(_ request: FetchRequest) -> Result<[T]> + /// Synchronously inserts objects to database. /// /// - Parameters: @@ -115,6 +122,13 @@ public extension DBClient { execute(FetchRequest(), completion: completion) } + /// Synchronously fetch all entities from database + /// + /// - Returns: `Result` with array of objects + func findAll() -> Result<[T]> { + return execute(FetchRequest()) + } + /// Finds first element with given value as primary. /// If no primary key specified for given type, or object with such value doesn't exist returns nil. /// @@ -143,6 +157,32 @@ public extension DBClient { } } + /// Synchronously finds first element with given value as primary. + /// If no primary key specified for given type, or object with such value doesn't exist returns nil. + /// + /// - Parameters: + /// - type: type of object to search for + /// - primaryValue: the value of primary key field to search for + /// - predicate: predicate for request + /// - Returns: `Result` with found object or nil + func findFirst(_ type: T.Type, primaryValue: String, predicate: NSPredicate? = nil) -> Result { + guard let primaryKey = type.primaryKeyName else { + return .failure(DBClientError.noPrimaryKey) + } + + let primaryKeyPredicate = NSPredicate(format: "\(primaryKey) == %@", primaryValue) + let fetchPredicate: NSPredicate + if let predicate = predicate { + fetchPredicate = NSCompoundPredicate(andPredicateWithSubpredicates: [primaryKeyPredicate, predicate]) + } else { + fetchPredicate = primaryKeyPredicate + } + let request = FetchRequest(predicate: fetchPredicate, fetchLimit: 1) + + let result = execute(request) + return result.map({ $0.first }) + } + /// Inserts object to database. /// /// - Parameters: diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index f90e299..17cd0d2 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -400,6 +400,30 @@ extension CoreDataDBClient: DBClient { } } + public func execute(_ request: FetchRequest) -> Result<[T]> { + let coreDataModelType = checkType(T.self) + + var executeResult: Result<[T]>! + + performReadTaskAndWait { context in + let fetchRequest = self.fetchRequest(for: coreDataModelType) + fetchRequest.predicate = request.predicate + fetchRequest.sortDescriptors = [request.sortDescriptor].compactMap { $0 } + fetchRequest.fetchLimit = request.fetchLimit + fetchRequest.fetchOffset = request.fetchOffset + do { + let result = try context.fetch(fetchRequest) as! [NSManagedObject] + let resultModels = result.compactMap { coreDataModelType.from($0) as? T } + + executeResult = .success(resultModels) + } catch let error { + executeResult = .failure(error) + } + } + + return executeResult + } + @discardableResult public func insert(_ objects: [T]) -> Result<[T]> { checkType(T.self) diff --git a/DBClient/Realm/RealmDBClient.swift b/DBClient/Realm/RealmDBClient.swift index a167040..fd26b79 100644 --- a/DBClient/Realm/RealmDBClient.swift +++ b/DBClient/Realm/RealmDBClient.swift @@ -137,6 +137,18 @@ extension RealmDBClient: DBClient { return RealmObservable(request: request, realm: realm) } + public func execute(_ request: FetchRequest) -> Result<[T]> { + let modelType = checkType(T.self) + let neededType = modelType.realmClass() + let objects = request + .applyTo(realmObjects: realm.objects(neededType)) + .map { $0 } + .get(offset: request.fetchOffset, limit: request.fetchLimit) + .compactMap { modelType.from($0) as? T } + + return .success(objects) + } + @discardableResult public func insert(_ objects: [T]) -> Result<[T]> { checkType(T.self) From ba613b0a99781856ae7da9bb8d76831064cd3068 Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 16 Oct 2018 17:50:52 +0300 Subject: [PATCH 21/47] [Fix] license file --- DBClient.podspec | 4 +--- License => LICENSE.md | 2 +- 2 files changed, 2 insertions(+), 4 deletions(-) rename License => LICENSE.md (97%) diff --git a/DBClient.podspec b/DBClient.podspec index adb4dde..22d2e37 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -4,7 +4,7 @@ Pod::Spec.new do |s| s.requires_arc = true s.summary = "CoreData & Realm wrapper written on Swift" s.homepage = "" - s.license = { :type => "MIT", :file => "LICENSE" } + s.license = { :type => "MIT", :file => "LICENSE.md" } s.author = { "Yalantis" => "mail@yalantis.com" } s.source = { :git => "https://github.com/Yalantis/DBClient.git", :tag => s.version } s.social_media_url = 'https://yalantis.com/' @@ -18,7 +18,6 @@ Pod::Spec.new do |s| s.subspec "Core" do |spec| spec.source_files = ['DBClient/Core/*.swift'] - spec.frameworks = ['Foundation'] end s.subspec "CoreData" do |spec| @@ -32,5 +31,4 @@ Pod::Spec.new do |s| spec.source_files = ['DBClient/Realm/*.swift'] spec.dependency "RealmSwift", "~> 2.10.1" end - end diff --git a/License b/LICENSE.md similarity index 97% rename from License rename to LICENSE.md index 2ccfa04..3466917 100644 --- a/License +++ b/LICENSE.md @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2017 Yalantis +Copyright (c) 2018 Yalantis Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal From c4d4ec8319df334760c4739522f4e7b32a4891ec Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 16 Oct 2018 18:15:45 +0300 Subject: [PATCH 22/47] [Update] realm dependency --- .gitignore | 34 ++++++++++++++++++- DBClient.podspec | 2 +- DBClient/Realm/RealmObservable.swift | 2 +- Example/Example.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/IDEWorkspaceChecks.plist | 8 +++++ Example/Podfile | 4 +-- Example/Podfile.lock | 26 +++++++------- 7 files changed, 58 insertions(+), 20 deletions(-) create mode 100644 Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist diff --git a/.gitignore b/.gitignore index 20ba027..2a68483 100644 --- a/.gitignore +++ b/.gitignore @@ -29,4 +29,36 @@ timeline.xctimeline playground.xcworkspace # CocoaPods -Pods/ \ No newline at end of file +Pods/ + +# Carthage +Carthage/Build +Carthage/Checkouts + +# fastlane +fastlane/report.xml + +# development + +logs/* + +# AppCode + +.idea/ + +# fastlane +# +# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the +# screenshots whenever they are needed. +# For more information about the recommended setup visit: +# https://docs.fastlane.tools/best-practices/source-control/#source-control + +fastlane/report.xml +fastlane/README.md +fastlane/Preview.html +fastlane/screenshots/**/*.png +fastlane/test_output + +## GitLab CI +.bundle/ +vendor/ \ No newline at end of file diff --git a/DBClient.podspec b/DBClient.podspec index 22d2e37..1d3e10b 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -29,6 +29,6 @@ Pod::Spec.new do |s| s.subspec "Realm" do |spec| spec.dependency "DBClient/Core" spec.source_files = ['DBClient/Realm/*.swift'] - spec.dependency "RealmSwift", "~> 2.10.1" + spec.dependency "RealmSwift", "~> 3.11" end end diff --git a/DBClient/Realm/RealmObservable.swift b/DBClient/Realm/RealmObservable.swift index 0f2f303..99161fc 100644 --- a/DBClient/Realm/RealmObservable.swift +++ b/DBClient/Realm/RealmObservable.swift @@ -35,7 +35,7 @@ internal class RealmObservable: RequestObservable { } let realmObjects = request.applyTo(realmObjects: realm.objects(realmModelType.realmClass())) - notificationToken = realmObjects.addNotificationBlock { changes in + notificationToken = realmObjects.observe { changes in switch changes { case .initial(let initial): let mapped = initial.map { realmModelType.from($0) as! T } diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 108ad4f..c7090b3 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -187,9 +187,9 @@ C533726E1E261A30004ECBCF /* Models */ = { isa = PBXGroup; children = ( - C53372781E261A30004ECBCF /* User.swift */, C533726F1E261A30004ECBCF /* CoreData */, C53372751E261A30004ECBCF /* Realm */, + C53372781E261A30004ECBCF /* User.swift */, ); path = Models; sourceTree = ""; diff --git a/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist b/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist new file mode 100644 index 0000000..18d9810 --- /dev/null +++ b/Example/Example.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist @@ -0,0 +1,8 @@ + + + + + IDEDidComputeMac32BitWarning + + + diff --git a/Example/Podfile b/Example/Podfile index 93b7041..1707261 100644 --- a/Example/Podfile +++ b/Example/Podfile @@ -5,12 +5,10 @@ target 'Example' do pod 'DBClient/Realm', :path => '../' pod 'DBClient/CoreData', :path => '../' - end target 'DBClientTests' do pod 'DBClient/Realm', :path => '../' - pod 'DBClient/CoreData', :path => '../' - + pod 'DBClient/CoreData', :path => '../' end diff --git a/Example/Podfile.lock b/Example/Podfile.lock index f2eeda9..167cb30 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,18 +1,18 @@ PODS: - - DBClient/Core (0.7): + - DBClient/Core (1.0): - YALResult (= 1.0) - - DBClient/CoreData (0.7): + - DBClient/CoreData (1.0): - DBClient/Core - YALResult (= 1.0) - - DBClient/Realm (0.7): + - DBClient/Realm (1.0): - DBClient/Core - - RealmSwift (~> 2.10.1) + - RealmSwift (~> 3.11) - YALResult (= 1.0) - - Realm (2.10.2): - - Realm/Headers (= 2.10.2) - - Realm/Headers (2.10.2) - - RealmSwift (2.10.2): - - Realm (= 2.10.2) + - Realm (3.11.0): + - Realm/Headers (= 3.11.0) + - Realm/Headers (3.11.0) + - RealmSwift (3.11.0): + - Realm (= 3.11.0) - YALResult (1.0) DEPENDENCIES: @@ -30,11 +30,11 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - DBClient: f932742894913327efd48fb5312c6746145d9fe5 - Realm: 0ef72b837fb67e9f4b098bac771ddd72c7fdbb69 - RealmSwift: 07a9ae0505091eda6b2ee7c190c3786d6e90a7b0 + DBClient: 612b814b0b79d6c3018db268408071ee0abeebbc + Realm: 92f09a102692b96a9a10e9617f214f15c5ab85fc + RealmSwift: 5f0481cd658bb751c509314b964a35eaa264d2cf YALResult: 0ce7a5a3b12a4e06b39725cebb86f3e7ea3c9d8e -PODFILE CHECKSUM: 25c038a30b752fe0113957b0d196c0fc91287f73 +PODFILE CHECKSUM: 01e2cf56f70348c45b9e7248a505591dd86d210b COCOAPODS: 1.5.3 From 6d2d75941582e18ae70e560fe3451873b68c1ba6 Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 16 Oct 2018 18:16:11 +0300 Subject: [PATCH 23/47] Bump version --- DBClient.podspec | 4 ++-- LICENSE.md => LICENSE | 0 2 files changed, 2 insertions(+), 2 deletions(-) rename LICENSE.md => LICENSE (100%) diff --git a/DBClient.podspec b/DBClient.podspec index 1d3e10b..b635015 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,10 +1,10 @@ Pod::Spec.new do |s| s.name = "DBClient" - s.version = "1.0" + s.version = "1.1" s.requires_arc = true s.summary = "CoreData & Realm wrapper written on Swift" s.homepage = "" - s.license = { :type => "MIT", :file => "LICENSE.md" } + s.license = { :type => "MIT", :file => "LICENSE" } s.author = { "Yalantis" => "mail@yalantis.com" } s.source = { :git => "https://github.com/Yalantis/DBClient.git", :tag => s.version } s.social_media_url = 'https://yalantis.com/' diff --git a/LICENSE.md b/LICENSE similarity index 100% rename from LICENSE.md rename to LICENSE From 2dfd0f9f9c4c7c5ac043e452eb44c43a41bba450 Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 16 Oct 2018 18:46:33 +0300 Subject: [PATCH 24/47] [Update] readme to include usage section for realm --- Example/Podfile.lock | 8 ++--- README.md | 70 +++++++++++++++++++++++++++++++------------- 2 files changed, 53 insertions(+), 25 deletions(-) diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 167cb30..195ecdb 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,10 +1,10 @@ PODS: - - DBClient/Core (1.0): + - DBClient/Core (1.1): - YALResult (= 1.0) - - DBClient/CoreData (1.0): + - DBClient/CoreData (1.1): - DBClient/Core - YALResult (= 1.0) - - DBClient/Realm (1.0): + - DBClient/Realm (1.1): - DBClient/Core - RealmSwift (~> 3.11) - YALResult (= 1.0) @@ -30,7 +30,7 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - DBClient: 612b814b0b79d6c3018db268408071ee0abeebbc + DBClient: 661a375086ae5321cb8ac00dfcb11a301823530e Realm: 92f09a102692b96a9a10e9617f214f15c5ab85fc RealmSwift: 5f0481cd658bb751c509314b964a35eaa264d2cf YALResult: 0ce7a5a3b12a4e06b39725cebb86f3e7ea3c9d8e diff --git a/README.md b/README.md index 956b275..8e50898 100644 --- a/README.md +++ b/README.md @@ -1,39 +1,67 @@ # DBClient -## Requirements +[![cocoapods](https://img.shields.io/badge/pod-1.1-blue.svg)](https://cocoapods.org/pods/DBClient) ![swift](https://img.shields.io/badge/Swift-4.2-orange.svg) ![Platform](http://img.shields.io/badge/platform-iOS-blue.svg?style=flat) [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/Yalantis/DBClient/blob/master/LICENSE) -- Xcode 9 -- Swift 4 -- iOS 9+ +## Integration (Cocoapods) -## Installation +There're three podspecs: -### Cocoapods +- `DBClient/Core` contain pure (CoreData/Realm-free) interface / types used to abstract from implementation. Use it only in case you're about to provide custom implementation of any available storage types. +- `DBClient/CoreData` contain CoreData implementation. +- `DBClient/Realm` contain Realm implementation. -There're 3 podspecs: +## Usage -Core, common classes for any database: +Depending on DataBase type you need to create a client: -```ruby -pod 'DBClient', :git => 'https://github.com/Yalantis/DBClient.git' +`let client: DBClient = RealmDBClient(realm: realm)` +or +`let client: DBClient = CoreDataDBClient(forModel: "Users")` + +Base methods (`CRUD`, `observe`) are the same for each type and could be found documented in [`DBClient.swift`](https://github.com/Yalantis/DBClient/blob/master/DBClient/Core/DBClient.swift) + +### Realm + +To adopt Realm, you need to provide `RealmModelConvertible` protocol implementation for each model you want to support. +`extension User: RealmModelConvertible` + +The protocol contains three required methods. + +The first one provides a class (decendant of realm's `Object`) to be associated with your model: +``` +static func realmClass() -> Object.Type { + return ObjectUser.self +} ``` -Wrapper for CoreData: +The second one converts abstract realm's `Object` to your model: +``` +static func from(_ realmObject: Object) -> Stored { + guard let objectUser = realmObject as? ObjectUser else { + fatalError("Can't create `User` from \(realmObject)") + } -```ruby -pod 'DBClient/CoreData', :git => 'https://github.com/Yalantis/DBClient.git' + return User(id: objectUser.id, name: objectUser.name) +} ``` -Wrapper for Realm: +The last one converts your model to realm's object: +``` +func toRealmObject() -> Object { + let user = ObjectUser() + user.id = id + user.name = name -```ruby -pod 'DBClient/Realm', :git => 'https://github.com/Yalantis/DBClient.git' + return user +} ``` -## Xcode 8 and Swift 3.2 support +### CoreData -To support old version of DBClient for Xcode 8 and Swift 3.2 need add previous version tag ```ruby 0.4.2 ```: +TBD -```ruby -pod 'DBClient', :git => 'https://github.com/Yalantis/DBClient.git', :tag => '0.4.2' -``` +## Version history + +- `1.1` Swift 4.2 support +- `0.7` Swift 4.0 support +- `0.4.2`Swift 3.2 support From 2a7c1502127ad291d4d970d869cd8acbeb4f3a98 Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 16 Oct 2018 19:07:29 +0300 Subject: [PATCH 25/47] [Update] readme to include coredata manual --- README.md | 76 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 72 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 8e50898..6685d9f 100644 --- a/README.md +++ b/README.md @@ -58,10 +58,78 @@ func toRealmObject() -> Object { ### CoreData -TBD +To adopt CoreData, you need to create your model and provide appropriate file name to client's constructor (bundle could also be specified) and for each your model provide implementation of the `CoreDataModelConvertible` protocol. +`extension User: CoreDataModelConvertible` + +The protocol requires four methods and one field to be implemented. Documentation for each method/field could be found in [`CoreDataDBClient.swift`](https://github.com/Yalantis/DBClient/blob/master/DBClient/CoreData/CoreDataDBClient.swift) + +In the field `entityName` you should provide entity name (equal to one specified in your model): +``` +public static var entityName: String { + return String(describing: self) +} +``` + +The next method used to determine associated `NSManagedObject` ancestor to your model: +``` +public static func managedObjectClass() -> NSManagedObject.Type { + return ManagedUser.self +} +``` + +The next method determines whether given object equal to current: +``` +func isPrimaryValueEqualTo(value: Any) -> Bool { + if let value = value as? String { + return value == id + } + + return false +} +``` + +Next method used to convert `NSManagedObject` to your model. Feel free to fail with `fatalError` here since it's developer's issue. +``` +public static func from(_ managedObject: NSManagedObject) -> Stored { + guard let managedUser = managedObject as? ManagedUser else { + fatalError("can't create User object from object \(managedObject)") + } + guard let id = managedUser.id, + let name = managedUser.name else { + fatalError("can't get required properties for user \(managedObject)") + } + + return User(id: id, name: name) +} +``` + +The last method used to create/update `NSManagedObject` in given context based on your model: +``` +public func upsertManagedObject(in context: NSManagedObjectContext, existedInstance: NSManagedObject?) -> NSManagedObject { + var user: ManagedUser + if let result = existedInstance as? ManagedUser { // fetch existing + user = result + } else { // or create new + user = NSEntityDescription.insertNewObject( + forEntityName: User.entityName, + into: context + ) as! ManagedUser + } + user.id = id + user.name = name + + return user +} + +``` ## Version history -- `1.1` Swift 4.2 support -- `0.7` Swift 4.0 support -- `0.4.2`Swift 3.2 support + +| Version | Swift | Dependencies | iOS | +|-----------|-------|------------------|------| +| `1.1` | 4.2 | RealmSwift 3.11, YALResult 1.0 | 10 | +| `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | +| `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | +| `0.6` | 4 | RealmSwift 2.10.1, BoltsSwift 1.3 | 9 | +| `0.4.2` | 3.2 | RealmSwift 2.1.1, BoltsSwift 1.3 | 9 | From e9b6ec858196cf86af7aa4628b5791256044feb8 Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Wed, 17 Oct 2018 11:21:26 +0300 Subject: [PATCH 26/47] [Update] readme --- README.md | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 6685d9f..f998f24 100644 --- a/README.md +++ b/README.md @@ -13,13 +13,28 @@ There're three podspecs: ## Usage Depending on DataBase type you need to create a client: - `let client: DBClient = RealmDBClient(realm: realm)` or `let client: DBClient = CoreDataDBClient(forModel: "Users")` Base methods (`CRUD`, `observe`) are the same for each type and could be found documented in [`DBClient.swift`](https://github.com/Yalantis/DBClient/blob/master/DBClient/Core/DBClient.swift) +Each model you create required to conform `Stored` protocol with two properties: +``` +extension User: Stored { + + public static var primaryKeyName: String? { + return "id" + } + + public var valueOfPrimaryKey: CVarArg? { + return id + } +} +``` + +For each model you create you need to define associated database model described below. + ### Realm To adopt Realm, you need to provide `RealmModelConvertible` protocol implementation for each model you want to support. From 338742142a4a17501efa958f239dbe514da9daef Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Wed, 17 Oct 2018 11:30:49 +0300 Subject: [PATCH 27/47] [Add] upsert method for single object; basic refactoring --- DBClient/Core/DBClient.swift | 27 ++++++++++++++++++++------- DBClient/Core/FetchRequest.swift | 25 +++---------------------- 2 files changed, 23 insertions(+), 29 deletions(-) diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index eb2c5d2..899e450 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -12,8 +12,8 @@ import YALResult public typealias Result = YALResult.Result public enum DBClientError: Error { - case noPrimaryKey - case noData + + case missingPrimaryKey, missingData } /// Protocol for transaction restrictions in `DBClient`. @@ -25,7 +25,6 @@ public protocol Stored { /// Primary value for an instance var valueOfPrimaryKey: CVarArg? { get } - } /// Describes abstract database transactions, common for all engines. @@ -71,7 +70,6 @@ public protocol DBClient { /// - objects: objects to be worked with /// - completion: `Result` with inserted and updated instances. func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) - } public extension DBClient { @@ -93,7 +91,7 @@ public extension DBClient { /// - completion: `Result` with found object or nil func findFirst(_ type: T.Type, primaryValue: String, predicate: NSPredicate? = nil, completion: @escaping (Result) -> Void) { guard let primaryKey = type.primaryKeyName else { - completion(.failure(DBClientError.noPrimaryKey)) + completion(.failure(DBClientError.missingPrimaryKey)) return } @@ -138,11 +136,26 @@ public extension DBClient { delete([object], completion: completion) } + /// Updates existing in database instances or creates them using upsert method defined in your model + /// + /// - Parameters: + /// - object: object to be worked with + /// - completion: `Result` with inserted or updated instance. + func upsert(_ object: T, completion: @escaping (Result<(object: T, isUpdated: Bool)>) -> Void) { + upsert([object]) { result in + completion(result.next { (updated: [T], inserted: [T]) -> Result<(object: T, isUpdated: Bool)> in + guard let object = updated.first ?? inserted.first else { + return Result.failure(DBClientError.missingData) + } + return Result.success((object: object, isUpdated: !updated.isEmpty)) + }) + } + } + private func convertArrayTaskToSingleObject(_ array: [T]) -> Result { guard let first = array.first else { - return .failure(DBClientError.noData) + return .failure(DBClientError.missingData) } return .success(first) } - } diff --git a/DBClient/Core/FetchRequest.swift b/DBClient/Core/FetchRequest.swift index 45560d2..28f16d7 100644 --- a/DBClient/Core/FetchRequest.swift +++ b/DBClient/Core/FetchRequest.swift @@ -21,18 +21,12 @@ public struct FetchRequest { /// - sortDescriptor: Sort descriptor; nil by default. /// - fetchOffset: Offset of data for request; 0 by default (no offset). /// - fetchLimit: Amount of objects to be fetched; no limit if zero given; 0 by default. - public init( - predicate: NSPredicate? = nil, - sortDescriptor: NSSortDescriptor? = nil, - fetchOffset: Int = 0, - fetchLimit: Int = 0 - ) { + public init(predicate: NSPredicate? = nil, sortDescriptor: NSSortDescriptor? = nil, fetchOffset: Int = 0, fetchLimit: Int = 0) { self.predicate = predicate self.sortDescriptor = sortDescriptor self.fetchOffset = fetchOffset self.fetchLimit = fetchLimit } - } // MARK: - Filtering @@ -74,7 +68,6 @@ public extension FetchRequest { func filtered(with key: String, notIn value: [String]) -> FetchRequest { return request(withPredicate: NSPredicate(format: "NOT (\(key) IN %@)", value)) } - } // MARK: - Sorting @@ -96,7 +89,6 @@ public extension FetchRequest { func sorted(with key: String?, ascending: Bool, selector: Selector) -> FetchRequest { return request(withSortDescriptor: NSSortDescriptor(key: key, ascending: ascending, selector: selector)) } - } // MARK: - Private @@ -104,21 +96,10 @@ public extension FetchRequest { private extension FetchRequest { func request(withPredicate predicate: NSPredicate) -> FetchRequest { - return FetchRequest( - predicate: predicate, - sortDescriptor: sortDescriptor, - fetchOffset: fetchOffset, - fetchLimit: fetchLimit - ) + return FetchRequest(predicate: predicate, sortDescriptor: sortDescriptor, fetchOffset: fetchOffset, fetchLimit: fetchLimit) } func request(withSortDescriptor sortDescriptor: NSSortDescriptor) -> FetchRequest { - return FetchRequest( - predicate: predicate, - sortDescriptor: sortDescriptor, - fetchOffset: fetchOffset, - fetchLimit: fetchLimit - ) + return FetchRequest(predicate: predicate, sortDescriptor: sortDescriptor, fetchOffset: fetchOffset, fetchLimit: fetchLimit) } - } From 73b2cd3d0d797a1dee0ca5fc20dbeb0fcec65b87 Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Wed, 17 Oct 2018 11:45:03 +0300 Subject: [PATCH 28/47] [Update] pod shield --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f998f24..ffd8c4d 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DBClient -[![cocoapods](https://img.shields.io/badge/pod-1.1-blue.svg)](https://cocoapods.org/pods/DBClient) ![swift](https://img.shields.io/badge/Swift-4.2-orange.svg) ![Platform](http://img.shields.io/badge/platform-iOS-blue.svg?style=flat) [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/Yalantis/DBClient/blob/master/LICENSE) +[![cocoapods](https://img.shields.io/cocoapods/v/DBClient.svg)](https://img.shields.io/cocoapods/v/DBClient.svg) ![swift](https://img.shields.io/badge/Swift-4.2-orange.svg) ![Platform](http://img.shields.io/badge/platform-iOS-blue.svg?style=flat) [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/Yalantis/DBClient/blob/master/LICENSE) ## Integration (Cocoapods) From 9201e0609c07c5d960afc205af8831bee5ae010f Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Fri, 19 Oct 2018 18:33:08 +0300 Subject: [PATCH 29/47] [Update] result dependency --- DBClient.podspec | 2 +- Example/Podfile.lock | 12 ++++++------ README.md | 10 +++++----- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/DBClient.podspec b/DBClient.podspec index b635015..6b32d1b 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -12,7 +12,7 @@ Pod::Spec.new do |s| s.ios.deployment_target = "10.0" - s.dependency 'YALResult', '1.0' + s.dependency 'YALResult', '1.1' s.default_subspec = "Core" diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 195ecdb..9a8b7ba 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,19 +1,19 @@ PODS: - DBClient/Core (1.1): - - YALResult (= 1.0) + - YALResult (= 1.1) - DBClient/CoreData (1.1): - DBClient/Core - - YALResult (= 1.0) + - YALResult (= 1.1) - DBClient/Realm (1.1): - DBClient/Core - RealmSwift (~> 3.11) - - YALResult (= 1.0) + - YALResult (= 1.1) - Realm (3.11.0): - Realm/Headers (= 3.11.0) - Realm/Headers (3.11.0) - RealmSwift (3.11.0): - Realm (= 3.11.0) - - YALResult (1.0) + - YALResult (1.1) DEPENDENCIES: - DBClient/CoreData (from `../`) @@ -30,10 +30,10 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - DBClient: 661a375086ae5321cb8ac00dfcb11a301823530e + DBClient: fa60a7f1d189fb9c59e35b4de433b4508001b359 Realm: 92f09a102692b96a9a10e9617f214f15c5ab85fc RealmSwift: 5f0481cd658bb751c509314b964a35eaa264d2cf - YALResult: 0ce7a5a3b12a4e06b39725cebb86f3e7ea3c9d8e + YALResult: e7acf1405252ef9a2a1f121176cc44b588844d93 PODFILE CHECKSUM: 01e2cf56f70348c45b9e7248a505591dd86d210b diff --git a/README.md b/README.md index ffd8c4d..16b7e4c 100644 --- a/README.md +++ b/README.md @@ -143,8 +143,8 @@ public func upsertManagedObject(in context: NSManagedObjectContext, existedInsta | Version | Swift | Dependencies | iOS | |-----------|-------|------------------|------| -| `1.1` | 4.2 | RealmSwift 3.11, YALResult 1.0 | 10 | -| `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | -| `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | -| `0.6` | 4 | RealmSwift 2.10.1, BoltsSwift 1.3 | 9 | -| `0.4.2` | 3.2 | RealmSwift 2.1.1, BoltsSwift 1.3 | 9 | +| `1.1` | 4.2 | RealmSwift 3.11, YALResult 1.1 | 10 | +| `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | +| `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | +| `0.6` | 4 | RealmSwift 2.10.1, BoltsSwift 1.3 | 9 | +| `0.4.2` | 3.2 | RealmSwift 2.1.1, BoltsSwift 1.3 | 9 | From cd8cca33e4ce973f9dc226ac89cdc9b97b3ef835 Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 23 Oct 2018 12:05:37 +0300 Subject: [PATCH 30/47] [Add] sync fetching; updated tests; fixes #11 --- DBClient.podspec | 2 +- DBClient/Core/DBClient.swift | 1 + DBClient/CoreData/CoreDataDBClient.swift | 43 +++++++++++++------ DBClient/Realm/RealmDBClient.swift | 17 +++----- Example/DBClientTests/DBClientTest.swift | 32 +++++++------- .../Interface/ExecuteTests.swift | 20 ++++----- .../DBClientTests/Interface/FetchTests.swift | 14 ++++++ .../Interface/ObservableTests.swift | 13 +++--- Example/Example/Models/User.swift | 5 ++- README.md | 2 +- 10 files changed, 90 insertions(+), 59 deletions(-) diff --git a/DBClient.podspec b/DBClient.podspec index 6b32d1b..cfe47d0 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DBClient" - s.version = "1.1" + s.version = "1.2" s.requires_arc = true s.summary = "CoreData & Realm wrapper written on Swift" s.homepage = "" diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index 899e450..fb1b141 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -36,6 +36,7 @@ public protocol DBClient { /// - request: request to execute /// - completion: `Result` with array of objects or error in case of failude. func execute(_ request: FetchRequest, completion: @escaping (Result<[T]>) -> Void) + func execute(_ request: FetchRequest) -> Result<[T]> /// Creates observable request from given `FetchRequest`. /// diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index a0d4c17..4e3dd27 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -35,7 +35,6 @@ public protocol CoreDataModelConvertible: Stored { /// Decides whether primary value of object equal to given func isPrimaryValueEqualTo(value: Any) -> Bool - } extension NSManagedObject: Stored { @@ -43,7 +42,6 @@ extension NSManagedObject: Stored { public static var primaryKeyName: String? { return nil } public var valueOfPrimaryKey: CVarArg? { return nil } - } public enum MigrationType { @@ -64,7 +62,6 @@ public enum MigrationType { return false } } - } /// Implementation of database client for CoreData storage type. @@ -240,7 +237,7 @@ public class CoreDataDBClient { // MARK: - Read/write - fileprivate func performWriteTask(_ closure: @escaping (NSManagedObjectContext, (() throws -> ())) -> ()) { + private func performWriteTask(_ closure: @escaping (NSManagedObjectContext, (() throws -> ())) -> ()) { let context = writeManagedContext context.perform { closure(context) { @@ -249,24 +246,23 @@ public class CoreDataDBClient { } } - fileprivate func performReadTask(closure: @escaping (NSManagedObjectContext) -> ()) { + private func performReadTask(closure: @escaping (NSManagedObjectContext) -> ()) { let context = readManagedContext context.perform { closure(context) } } - } // MARK: - DBClient methods extension CoreDataDBClient: DBClient { - + public func observable(for request: FetchRequest) -> RequestObservable { return CoreDataObservable(request: request, context: mainContext) } - public func execute(_ request: FetchRequest, completion: @escaping (Result<[T]>) -> Void) where T : Stored { + public func execute(_ request: FetchRequest, completion: @escaping (Result<[T]>) -> Void) where T: Stored { let coreDataModelType = checkType(T.self) performReadTask { context in @@ -286,9 +282,31 @@ extension CoreDataDBClient: DBClient { } } + public func execute(_ request: FetchRequest) -> Result<[T]> where T: Stored { + let coreDataModelType = checkType(T.self) + let context = readManagedContext + var executionResult: Result<[T]>? + context.performAndWait { + let fetchRequest = self.fetchRequest(for: coreDataModelType) + fetchRequest.predicate = request.predicate + fetchRequest.sortDescriptors = [request.sortDescriptor].compactMap { $0 } + fetchRequest.fetchLimit = request.fetchLimit + fetchRequest.fetchOffset = request.fetchOffset + do { + let result = try context.fetch(fetchRequest) as! [NSManagedObject] + let resultModels = result.compactMap { coreDataModelType.from($0) as? T } + executionResult = .success(resultModels) + } catch { + executionResult = .failure(error) + } + } + + return executionResult! + } + /// Insert given objects into context and save it /// If appropriate object already exists in DB it will be ignored and nothing will be inserted - public func insert(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T : Stored { + public func insert(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T: Stored { checkType(T.self) performWriteTask { context, savingClosure in @@ -314,7 +332,7 @@ extension CoreDataDBClient: DBClient { /// Method to update existed in DB objects /// if there is no such object in db nothing will happened - public func update(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T : Stored { + public func update(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T: Stored { checkType(T.self) performWriteTask { context, savingClosure in @@ -340,7 +358,7 @@ extension CoreDataDBClient: DBClient { } /// Update object if it exists or insert new one otherwise - public func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) where T : Stored { + public func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) where T: Stored { checkType(T.self) performWriteTask { context, savingClosure in @@ -368,7 +386,7 @@ extension CoreDataDBClient: DBClient { /// For each element in collection: /// After all deletes try to save context - public func delete(_ objects: [T], completion: @escaping (Result<()>) -> Void) where T : Stored { + public func delete(_ objects: [T], completion: @escaping (Result<()>) -> Void) where T: Stored { checkType(T.self) performWriteTask { context, savingClosure in @@ -447,5 +465,4 @@ private extension CoreDataDBClient { return objects.compactMap { $0 as? CoreDataModelConvertible } } - } diff --git a/DBClient/Realm/RealmDBClient.swift b/DBClient/Realm/RealmDBClient.swift index a172d70..3859808 100644 --- a/DBClient/Realm/RealmDBClient.swift +++ b/DBClient/Realm/RealmDBClient.swift @@ -23,7 +23,6 @@ public protocol RealmModelConvertible: Stored { /// Executes backward mapping from `Realm.Object` func toRealmObject() -> Object - } extension RealmModelConvertible { @@ -31,7 +30,6 @@ extension RealmModelConvertible { func realmClassForInstance() -> Object.Type { return Self.realmClass() } - } /// Implementation of database client for Realm storage type. @@ -43,7 +41,6 @@ public class RealmDBClient { public init(realm: Realm) { self.realm = realm } - } // MARK: DBClient @@ -52,15 +49,19 @@ extension RealmDBClient: DBClient { /// Executes given request. Fetches all entities and then applies all given restrictions public func execute(_ request: FetchRequest, completion: @escaping (Result<[T]>) -> Void) { + completion(execute(request)) + } + + public func execute(_ request: FetchRequest) -> Result<[T]> where T : Stored { let modelType = checkType(T.self) let neededType = modelType.realmClass() let objects = request .applyTo(realmObjects: realm.objects(neededType)) .map { $0 } - .get(offset: request.fetchOffset, limit: request.fetchLimit) + .slice(offset: request.fetchOffset, limit: request.fetchLimit) .compactMap { modelType.from($0) as? T } - completion(.success(objects)) + return .success(objects) } /// Inserts new objects to database. If object with such `primaryKeyValue` already exists Realm'll throw an error @@ -136,7 +137,6 @@ extension RealmDBClient: DBClient { return RealmObservable(request: request, realm: realm) } - } private extension RealmDBClient { @@ -174,7 +174,6 @@ private extension RealmDBClient { return (present: presentObjects, new: notPresentObjects) } - } internal extension FetchRequest { @@ -190,12 +189,11 @@ internal extension FetchRequest { return objects } - } private extension Array { - func get(offset: Int, limit: Int) -> [T] { + func slice(offset: Int, limit: Int) -> [T] { var lim = 0 var off = 0 let count = self.count @@ -211,5 +209,4 @@ private extension Array { return (off..) in - if let objects = result.value { - self.dbClient.delete(objects) { _ in - isDeleted = true - expectationDeleletion.fulfill() - } + guard let objects = result.value else { + expectationDeleletion.fulfill() + return + } + self.dbClient.delete(objects) { _ in + isDeleted = true + expectationDeleletion.fulfill() } } @@ -64,5 +63,4 @@ class DBClientTest: XCTestCase { XCTAssert(isDeleted) } } - } diff --git a/Example/DBClientTests/Interface/ExecuteTests.swift b/Example/DBClientTests/Interface/ExecuteTests.swift index e71ee3b..7a5c968 100644 --- a/Example/DBClientTests/Interface/ExecuteTests.swift +++ b/Example/DBClientTests/Interface/ExecuteTests.swift @@ -104,20 +104,20 @@ final class ExecuteTests: DBClientTest { let arg = "1" let predicate = NSPredicate(format: "SELF.id ENDSWITH %@", arg) let randomUsers: [User] = (0...10).map { _ in User.createRandom() } - let preicatedUsers = randomUsers.filter { - $0.id.hasSuffix(arg) - } + let preicatedUsers = randomUsers.filter { $0.id.hasSuffix(arg) } let expectationObjects = expectation(description: "Object") var expectedUsers = [User]() self.dbClient.insert(randomUsers) { result in - if result.value != nil { - let request = FetchRequest(predicate: predicate) - - self.dbClient.execute(request) { result in - expectedUsers = result.value ?? [] - expectationObjects.fulfill() - } + guard result.value != nil else { + expectationObjects.fulfill() + return + } + let request = FetchRequest(predicate: predicate) + + self.dbClient.execute(request) { result in + expectedUsers = result.value ?? [] + expectationObjects.fulfill() } } diff --git a/Example/DBClientTests/Interface/FetchTests.swift b/Example/DBClientTests/Interface/FetchTests.swift index b587745..9b44af3 100644 --- a/Example/DBClientTests/Interface/FetchTests.swift +++ b/Example/DBClientTests/Interface/FetchTests.swift @@ -13,6 +13,20 @@ import DBClient class FetchTests: DBClientTest { + func test_SyncFetch_WhenSuccessful_ReturnObject() { + let randomUser = User.createRandom() + let expectationObject = expectation(description: "Inserting object") + + self.dbClient.insert(randomUser) { _ in expectationObject.fulfill() } + + waitForExpectations(timeout: 1) { _ in + let result = self.dbClient.execute(FetchRequest(predicate: NSPredicate(format: "id == %@", randomUser.id))) + XCTAssertEqual(result.require().count, 1) + let object = result.require().first + XCTAssertEqual(object, randomUser) + } + } + func test_SingleFetch_WhenSuccessful_ReturnsObject() { let randomUser = User.createRandom() let expectationObject = expectation(description: "Object") diff --git a/Example/DBClientTests/Interface/ObservableTests.swift b/Example/DBClientTests/Interface/ObservableTests.swift index a6e1b4c..cc1d25b 100644 --- a/Example/DBClientTests/Interface/ObservableTests.swift +++ b/Example/DBClientTests/Interface/ObservableTests.swift @@ -31,7 +31,7 @@ final class ObservableTests: DBClientTest { dbClient.insert(objectsToCreate) { _ in } waitForExpectations(timeout: 1) { _ in - XCTAssertEqual(expectedInsertedObjects, objectsToCreate) + XCTAssertEqual(expectedInsertedObjects.sorted(), objectsToCreate.sorted()) } } @@ -39,7 +39,7 @@ final class ObservableTests: DBClientTest { let request = FetchRequest() let observable = dbClient.observable(for: request) let objectsToCreate: [User] = (0...100).map { _ in User.createRandom() } - let expectationObject = expectation(description: "Object") + let expectationObject = expectation(description: "Changes observe") var expectedUpdatedObjects = [User]() observable.observe { (change: ObservableChange) in @@ -53,12 +53,16 @@ final class ObservableTests: DBClientTest { } } + let updateExpectation = expectation(description: "Insert and update") dbClient.insert(objectsToCreate) { _ in - self.dbClient.update(objectsToCreate) { _ in } + objectsToCreate.forEach { $0.mutate() } + self.dbClient.update(objectsToCreate) { _ in + updateExpectation.fulfill() + } } waitForExpectations(timeout: 1) { _ in - XCTAssertEqual(expectedUpdatedObjects, objectsToCreate) + XCTAssertEqual(expectedUpdatedObjects.sorted(), objectsToCreate.sorted()) } } @@ -88,5 +92,4 @@ final class ObservableTests: DBClientTest { XCTAssertEqual(expectedDeletedObjectsCount, objectsToCreate.count) } } - } diff --git a/Example/Example/Models/User.swift b/Example/Example/Models/User.swift index d10e438..c84d59e 100644 --- a/Example/Example/Models/User.swift +++ b/Example/Example/Models/User.swift @@ -19,6 +19,9 @@ class User { self.name = name } + func mutate() { + name = String(name.reversed()) + } } extension User: Stored { @@ -30,7 +33,6 @@ extension User: Stored { public var valueOfPrimaryKey: CVarArg? { return id } - } extension User { @@ -41,5 +43,4 @@ extension User { return user } - } diff --git a/README.md b/README.md index 16b7e4c..d1813d9 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ public func upsertManagedObject(in context: NSManagedObjectContext, existedInsta | Version | Swift | Dependencies | iOS | |-----------|-------|------------------|------| -| `1.1` | 4.2 | RealmSwift 3.11, YALResult 1.1 | 10 | +| `1.2` | 4.2 | RealmSwift 3.11, YALResult 1.1 | 10 | | `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | | `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | | `0.6` | 4 | RealmSwift 2.10.1, BoltsSwift 1.3 | 9 | From f6cd95b86151ddeef1fceeea63fb48a1c297980c Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 23 Oct 2018 14:02:18 +0300 Subject: [PATCH 31/47] [Update] separate tests for different clients --- Example/DBClientTests/DBClientTest.swift | 24 +--- .../CoreData/CoreDataCreateTests.swift | 44 ++++++ .../CoreData/CoreDataDeleteTests.swift | 51 +++++++ .../CoreData/CoreDataExecuteTests.swift | 128 ++++++++++++++++++ .../CoreData/CoreDataFetchTests.swift | 64 +++++++++ .../CoreData/CoreDataObservableTests.swift | 95 +++++++++++++ .../CoreData/CoreDataUpdateTests.swift | 31 +++++ .../CoreData/CoreDataUpsertTests.swift | 32 +++++ .../CoreData/DBClientCoreDataTest.swift | 18 +++ .../Interface/Realm/DBClientRealmTest.swift | 19 +++ .../RealmCreateTests.swift} | 4 +- .../RealmDeleteTests.swift} | 4 +- .../RealmExecuteTests.swift} | 4 +- .../RealmFetchTests.swift} | 5 +- .../RealmObservableTests.swift} | 4 +- .../RealmUpdateTests.swift} | 4 +- .../RealmUpsertTests.swift} | 4 +- Example/Example.xcodeproj/project.pbxproj | 108 +++++++++++---- 18 files changed, 580 insertions(+), 63 deletions(-) create mode 100644 Example/DBClientTests/Interface/CoreData/CoreDataCreateTests.swift create mode 100644 Example/DBClientTests/Interface/CoreData/CoreDataDeleteTests.swift create mode 100644 Example/DBClientTests/Interface/CoreData/CoreDataExecuteTests.swift create mode 100644 Example/DBClientTests/Interface/CoreData/CoreDataFetchTests.swift create mode 100644 Example/DBClientTests/Interface/CoreData/CoreDataObservableTests.swift create mode 100644 Example/DBClientTests/Interface/CoreData/CoreDataUpdateTests.swift create mode 100644 Example/DBClientTests/Interface/CoreData/CoreDataUpsertTests.swift create mode 100644 Example/DBClientTests/Interface/CoreData/DBClientCoreDataTest.swift create mode 100644 Example/DBClientTests/Interface/Realm/DBClientRealmTest.swift rename Example/DBClientTests/Interface/{CreateTests.swift => Realm/RealmCreateTests.swift} (93%) rename Example/DBClientTests/Interface/{DeleteTests.swift => Realm/RealmDeleteTests.swift} (94%) rename Example/DBClientTests/Interface/{ExecuteTests.swift => Realm/RealmExecuteTests.swift} (98%) rename Example/DBClientTests/Interface/{FetchTests.swift => Realm/RealmFetchTests.swift} (96%) rename Example/DBClientTests/Interface/{ObservableTests.swift => Realm/RealmObservableTests.swift} (97%) rename Example/DBClientTests/Interface/{UpdateTests.swift => Realm/RealmUpdateTests.swift} (90%) rename Example/DBClientTests/Interface/{UpsertTests.swift => Realm/RealmUpsertTests.swift} (92%) diff --git a/Example/DBClientTests/DBClientTest.swift b/Example/DBClientTests/DBClientTest.swift index d84e906..2bbd894 100644 --- a/Example/DBClientTests/DBClientTest.swift +++ b/Example/DBClientTests/DBClientTest.swift @@ -8,43 +8,27 @@ import XCTest import DBClient -import RealmSwift @testable import Example -enum StorageType { - - case realm - case coreData -} - -let storageType: StorageType = .coreData - class DBClientTest: XCTestCase { - var dbClient: DBClient! + var dbClient: DBClient! { return nil } override func setUp() { super.setUp() - switch storageType { - case .realm: - let realm = try! Realm() - dbClient = RealmDBClient(realm: realm) - - case .coreData: - dbClient = CoreDataDBClient(forModel: "Users") - } cleanUpDatabase() } override func tearDown() { - super.tearDown() - cleanUpDatabase() + + super.tearDown() } // removes all objects from the database func cleanUpDatabase() { + guard dbClient != nil else { return } let expectationDeleletion = expectation(description: "Deletion") var isDeleted = false diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataCreateTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataCreateTests.swift new file mode 100644 index 0000000..45545ed --- /dev/null +++ b/Example/DBClientTests/Interface/CoreData/CoreDataCreateTests.swift @@ -0,0 +1,44 @@ +// +// CoreDataCreateTests.swift +// DBClient-Example +// +// Created by Roman Kyrylenko on 2/8/17. +// Copyright © 2017 Yalantis. All rights reserved. +// + +import XCTest +@testable import Example + +final class CoreDataCreateTests: DBClientCoreDataTest { + + func test_SingleInsertion_WhenSuccessful_ReturnsObject() { + let randomUser = User.createRandom() + let expectationObject = expectation(description: "Object") + var expectedObject: User? + + self.dbClient.insert(randomUser) { result in + expectedObject = result.value + expectationObject.fulfill() + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertNotNil(expectedObject) + } + } + + func test_BulkInsertion_WhenSuccessful_ReturnsBulk() { + let randomUsers: [User] = (0...100).map { _ in User.createRandom() } + + let expectationObjects = expectation(description: "Objects") + var expectedObjectsCount = 0 + + self.dbClient.insert(randomUsers) { result in + expectedObjectsCount = result.value?.count ?? 0 + expectationObjects.fulfill() + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedObjectsCount, randomUsers.count) + } + } +} diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataDeleteTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataDeleteTests.swift new file mode 100644 index 0000000..dce73de --- /dev/null +++ b/Example/DBClientTests/Interface/CoreData/CoreDataDeleteTests.swift @@ -0,0 +1,51 @@ +// +// CoreDataDeleteTests.swift +// DBClient-Example +// +// Created by Roman Kyrylenko on 2/9/17. +// Copyright © 2017 Yalantis. All rights reserved. +// + +import XCTest +@testable import Example + +final class CoreDataDeleteTests: DBClientCoreDataTest { + + func test_SingleDeletion_WhenSuccessful_ReturnsNil() { + let randomUser = User.createRandom() + let expectationHit = expectation(description: "Object") + var isDeleted = false + + self.dbClient.insert(randomUser) { result in + if let object = result.value { + self.dbClient.delete(object) { result in + isDeleted = result.value != nil + expectationHit.fulfill() + } + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssert(isDeleted) + } + } + + func test_BulkDeletion_WhenSuccessful_ReturnsNil() { + let randomUsers: [User] = (0...100).map { _ in User.createRandom() } + let expectationHit = expectation(description: "Object") + var isDeleted = false + + self.dbClient.insert(randomUsers) { result in + if let objects = result.value { + self.dbClient.delete(objects) { result in + isDeleted = result.value != nil + expectationHit.fulfill() + } + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssert(isDeleted) + } + } +} diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataExecuteTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataExecuteTests.swift new file mode 100644 index 0000000..375d05a --- /dev/null +++ b/Example/DBClientTests/Interface/CoreData/CoreDataExecuteTests.swift @@ -0,0 +1,128 @@ +// +// CoreDataExecuteTests.swift +// DBClient-Example +// +// Created by Roman Kyrylenko on 2/9/17. +// Copyright © 2017 Yalantis. All rights reserved. +// + +import XCTest +import DBClient +@testable import Example + +final class CoreDataExecuteTests: DBClientCoreDataTest { + + func test_SingleExecute_WhenSuccessful_ReturnsCount() { + let randomUser = User.createRandom() + let expectationObject = expectation(description: "Object") + var expectedCount = 0 + + self.dbClient.insert(randomUser) { result in + if result.value != nil { + let request = FetchRequest() + self.dbClient.execute(request) { result in + expectedCount = result.value?.count ?? 0 + expectationObject.fulfill() + } + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedCount, 1) + } + } + + func test_ExecuteWithOffset_WhenSuccessful_ReturnsCount() { + let randomUsers: [User] = (0...10).map { _ in User.createRandom() } + let offset = 5 + let shiftedUsers = Array(randomUsers[offset..(fetchOffset: offset) + self.dbClient.execute(request) { result in + expectedCount = result.value?.count ?? 0 + expectationObjects.fulfill() + } + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedCount, shiftedUsers.count) + } + } + + func test_ExecuteWithLimit_WhenSuccessful_ReturnsCount() { + let randomUsers: [User] = (0...10).map { _ in User.createRandom() } + let limit = 3 + + let expectationObjects = expectation(description: "Object") + var expectedCount = 0 + + self.dbClient.insert(randomUsers) { result in + if result.value != nil { + let request = FetchRequest(fetchLimit: limit) + self.dbClient.execute(request) { result in + expectedCount = result.value?.count ?? 0 + expectationObjects.fulfill() + } + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedCount, limit) + } + } + + func test_ExecuteWithSortDescriptor_WhenSuccessful_ReturnsCount() { + let sortDescriptor = NSSortDescriptor(key: "name", ascending: true) + let order: ComparisonResult = sortDescriptor.ascending ? .orderedAscending : .orderedDescending + let randomUsers: [User] = (0...10).map { _ in User.createRandom() } + let sortedUsers = randomUsers.sorted { $0.name.compare($1.name) == order } + let expectationObjects = expectation(description: "Object") + var expectedUsers = [User]() + + self.dbClient.insert(randomUsers) { result in + if result.value != nil { + let request = FetchRequest(sortDescriptor: sortDescriptor) + + self.dbClient.execute(request) { result in + expectedUsers = result.value ?? [] + expectationObjects.fulfill() + } + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUsers, sortedUsers) + } + } + + func test_ExecuteWithPredicate_WhenSuccessful_ReturnsCount() { + let arg = "1" + let predicate = NSPredicate(format: "SELF.id ENDSWITH %@", arg) + let randomUsers: [User] = (0...10).map { _ in User.createRandom() } + let preicatedUsers = randomUsers.filter { $0.id.hasSuffix(arg) } + let expectationObjects = expectation(description: "Object") + var expectedUsers = [User]() + + self.dbClient.insert(randomUsers) { result in + guard result.value != nil else { + expectationObjects.fulfill() + return + } + let request = FetchRequest(predicate: predicate) + + self.dbClient.execute(request) { result in + expectedUsers = result.value ?? [] + expectationObjects.fulfill() + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUsers, preicatedUsers) + } + } +} diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataFetchTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataFetchTests.swift new file mode 100644 index 0000000..988ec78 --- /dev/null +++ b/Example/DBClientTests/Interface/CoreData/CoreDataFetchTests.swift @@ -0,0 +1,64 @@ +// +// CoreDataFetchTests.swift +// DBClient-Example +// +// Created by Roman Kyrylenko on 2/8/17. +// Copyright © 2017 Yalantis. All rights reserved. +// + +import Foundation +import XCTest +import DBClient +@testable import Example + +class CoreDataFetchTests: DBClientCoreDataTest { + + func test_SyncFetch_WhenSuccessful_ReturnObject() { + let randomUser = User.createRandom() + let expectationObject = expectation(description: "Inserting object") + + self.dbClient.insert(randomUser) { _ in expectationObject.fulfill() } + + waitForExpectations(timeout: 1) { _ in + let result = self.dbClient.execute(FetchRequest(predicate: NSPredicate(format: "id == %@", randomUser.id))) + XCTAssertEqual(result.require().count, 1) + let object = result.require().first + XCTAssertEqual(object, randomUser) + } + } + + func test_SingleFetch_WhenSuccessful_ReturnsObject() { + let randomUser = User.createRandom() + let expectationObject = expectation(description: "Object") + var expectedObject: User? + + self.dbClient.insert(randomUser) { result in + self.dbClient.findFirst(User.self, primaryValue: randomUser.id) { result in + expectedObject = result.require() + expectationObject.fulfill() + } + } + + waitForExpectations(timeout: 5) { _ in + XCTAssertNotNil(expectedObject) + } + } + + func test_BulkFetch_WhenSuccessful_ReturnsBulk() { + let randomUsers: [User] = (0...100).map { _ in User.createRandom() } + + let expectationObjects = expectation(description: "Objects") + var expectedObjectsCount = 0 + + self.dbClient.insert(randomUsers) { result in + self.dbClient.findAll { (result: Result<[User]>) in + expectedObjectsCount = result.value?.count ?? 0 + expectationObjects.fulfill() + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedObjectsCount, randomUsers.count) + } + } +} diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataObservableTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataObservableTests.swift new file mode 100644 index 0000000..8dec9b0 --- /dev/null +++ b/Example/DBClientTests/Interface/CoreData/CoreDataObservableTests.swift @@ -0,0 +1,95 @@ +// +// CoreDataObservableTests.swift +// DBClient-Example +// +// Created by Roman Kyrylenko on 2/13/17. +// Copyright © 2017 Yalantis. All rights reserved. +// + +import XCTest +import DBClient +@testable import Example + +final class CoreDataObservableTests: DBClientCoreDataTest { + + func test_InsertionObservation_WhenSuccessful_InvokesChnages() { + let request = FetchRequest() + let observable = dbClient.observable(for: request) + let objectsToCreate: [User] = (0...100).map { _ in User.createRandom() } + let expectationObject = expectation(description: "Object") + var expectedInsertedObjects = [User]() + + observable.observe { (change: ObservableChange) in + switch change { + case .change(let change): + expectedInsertedObjects = change.insertions.map { $0.element } + expectationObject.fulfill() + default: break + } + } + + dbClient.insert(objectsToCreate) { _ in } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedInsertedObjects.sorted(), objectsToCreate.sorted()) + } + } + + func test_UpdationObservation_WhenSuccessful_InvokesChnages() { + let request = FetchRequest() + let observable = dbClient.observable(for: request) + let objectsToCreate: [User] = (0...100).map { _ in User.createRandom() } + let expectationObject = expectation(description: "Changes observe") + var expectedUpdatedObjects = [User]() + + observable.observe { (change: ObservableChange) in + switch change { + case .change(let change): + if !change.modifications.isEmpty { + expectedUpdatedObjects = change.modifications.map { $0.element } + expectationObject.fulfill() + } + default: break + } + } + + let updateExpectation = expectation(description: "Insert and update") + dbClient.insert(objectsToCreate) { _ in + objectsToCreate.forEach { $0.mutate() } + self.dbClient.update(objectsToCreate) { _ in + updateExpectation.fulfill() + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUpdatedObjects.sorted(), objectsToCreate.sorted()) + } + } + + func test_DeletionObservation_WhenSuccessful_InvokesChnages() { + let request = FetchRequest() + let observable = dbClient.observable(for: request) + let objectsToCreate: [User] = (0...100).map { _ in User.createRandom() } + let expectationObject = expectation(description: "Object") + var expectedDeletedObjectsCount = 0 + + observable.observe { (change: ObservableChange) in + switch change { + case .change(let change): + if !change.deletions.isEmpty { + expectedDeletedObjectsCount = change.deletions.count + expectationObject.fulfill() + } + default: break + } + } + + dbClient.insert(objectsToCreate) { _ in + self.dbClient.delete(objectsToCreate) { _ in } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedDeletedObjectsCount, objectsToCreate.count) + } + } +} diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataUpdateTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataUpdateTests.swift new file mode 100644 index 0000000..f2a9593 --- /dev/null +++ b/Example/DBClientTests/Interface/CoreData/CoreDataUpdateTests.swift @@ -0,0 +1,31 @@ +// +// CoreDataUpdateTests.swift +// DBClient-Example +// +// Created by Roman Kyrylenko on 2/9/17. +// Copyright © 2017 Yalantis. All rights reserved. +// + +import XCTest +@testable import Example + +final class CoreDataUpdateTests: DBClientCoreDataTest { + + func test_UpdateUserName_WhenSuccessful_SetsCorrectName() { + let randomUser = User.createRandom() + let expectationObject = expectation(description: "Object") + var expectedUser: User? + + self.dbClient.insert(randomUser) { result in + randomUser.name = "Bob" + self.dbClient.update(randomUser) { result in + expectedUser = result.value + expectationObject.fulfill() + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUser?.name, "Bob") + } + } +} diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataUpsertTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataUpsertTests.swift new file mode 100644 index 0000000..62f2d5a --- /dev/null +++ b/Example/DBClientTests/Interface/CoreData/CoreDataUpsertTests.swift @@ -0,0 +1,32 @@ +// +// CoreDataUpsertTests.swift +// DBClient-Example +// +// Created by Roman Kyrylenko on 2/15/17. +// Copyright © 2017 Yalantis. All rights reserved. +// + +import XCTest +@testable import Example + +final class CoreDataUpsertTests: DBClientCoreDataTest { + + func test_UpsertUsers_WhenSuccessful_ReturnsUpsertedUsers() { + let newUsers: [User] = (0...5).map { _ in User.createRandom() } + let savedUsers: [User] = (0...5).map { _ in User.createRandom() } + let expectationObjects = expectation(description: "Object") + var expectedUsers = [User]() + let combinedUsers = savedUsers + newUsers + + self.dbClient.insert(savedUsers) { _ in + self.dbClient.upsert(combinedUsers) { result in + expectedUsers = result.require().updated + result.require().inserted + expectationObjects.fulfill() + } + } + + waitForExpectations(timeout: 1) { _ in + XCTAssertEqual(expectedUsers, combinedUsers) + } + } +} diff --git a/Example/DBClientTests/Interface/CoreData/DBClientCoreDataTest.swift b/Example/DBClientTests/Interface/CoreData/DBClientCoreDataTest.swift new file mode 100644 index 0000000..81b7da5 --- /dev/null +++ b/Example/DBClientTests/Interface/CoreData/DBClientCoreDataTest.swift @@ -0,0 +1,18 @@ +// +// DBClientCoreDataTest.swift +// DBClientTests +// +// Created by Roman Kyrylenko on 10/23/18. +// Copyright © 2018 Yalantis. All rights reserved. +// + +import XCTest +import DBClient + +class DBClientCoreDataTest: DBClientTest { + + private let client = CoreDataDBClient(forModel: "Users") + override var dbClient: DBClient! { + return client + } +} diff --git a/Example/DBClientTests/Interface/Realm/DBClientRealmTest.swift b/Example/DBClientTests/Interface/Realm/DBClientRealmTest.swift new file mode 100644 index 0000000..88930fd --- /dev/null +++ b/Example/DBClientTests/Interface/Realm/DBClientRealmTest.swift @@ -0,0 +1,19 @@ +// +// DBClientRealmTest.swift +// DBClientTests +// +// Created by Roman Kyrylenko on 10/23/18. +// Copyright © 2018 Yalantis. All rights reserved. +// + +import XCTest +import DBClient +import RealmSwift + +class DBClientRealmTest: DBClientTest { + + private let client = RealmDBClient(realm: try! Realm()) + override var dbClient: DBClient! { + return client + } +} diff --git a/Example/DBClientTests/Interface/CreateTests.swift b/Example/DBClientTests/Interface/Realm/RealmCreateTests.swift similarity index 93% rename from Example/DBClientTests/Interface/CreateTests.swift rename to Example/DBClientTests/Interface/Realm/RealmCreateTests.swift index 7f7548d..8e2f9c5 100644 --- a/Example/DBClientTests/Interface/CreateTests.swift +++ b/Example/DBClientTests/Interface/Realm/RealmCreateTests.swift @@ -1,5 +1,5 @@ // -// CreateTests.swift +// RealmCreateTests.swift // DBClient-Example // // Created by Roman Kyrylenko on 2/8/17. @@ -9,7 +9,7 @@ import XCTest @testable import Example -final class CreateTests: DBClientTest { +final class RealmCreateTests: DBClientRealmTest { func test_SingleInsertion_WhenSuccessful_ReturnsObject() { let randomUser = User.createRandom() diff --git a/Example/DBClientTests/Interface/DeleteTests.swift b/Example/DBClientTests/Interface/Realm/RealmDeleteTests.swift similarity index 94% rename from Example/DBClientTests/Interface/DeleteTests.swift rename to Example/DBClientTests/Interface/Realm/RealmDeleteTests.swift index b34489e..cf65b54 100644 --- a/Example/DBClientTests/Interface/DeleteTests.swift +++ b/Example/DBClientTests/Interface/Realm/RealmDeleteTests.swift @@ -1,5 +1,5 @@ // -// DeleteTests.swift +// RealmDeleteTests.swift // DBClient-Example // // Created by Roman Kyrylenko on 2/9/17. @@ -9,7 +9,7 @@ import XCTest @testable import Example -final class DeleteTests: DBClientTest { +final class RealmDeleteTests: DBClientRealmTest { func test_SingleDeletion_WhenSuccessful_ReturnsNil() { let randomUser = User.createRandom() diff --git a/Example/DBClientTests/Interface/ExecuteTests.swift b/Example/DBClientTests/Interface/Realm/RealmExecuteTests.swift similarity index 98% rename from Example/DBClientTests/Interface/ExecuteTests.swift rename to Example/DBClientTests/Interface/Realm/RealmExecuteTests.swift index 7a5c968..3ac71c6 100644 --- a/Example/DBClientTests/Interface/ExecuteTests.swift +++ b/Example/DBClientTests/Interface/Realm/RealmExecuteTests.swift @@ -1,5 +1,5 @@ // -// ExecuteTests.swift +// RealmExecuteTests.swift // DBClient-Example // // Created by Roman Kyrylenko on 2/9/17. @@ -10,7 +10,7 @@ import XCTest import DBClient @testable import Example -final class ExecuteTests: DBClientTest { +final class RealmExecuteTests: DBClientRealmTest { func test_SingleExecute_WhenSuccessful_ReturnsCount() { let randomUser = User.createRandom() diff --git a/Example/DBClientTests/Interface/FetchTests.swift b/Example/DBClientTests/Interface/Realm/RealmFetchTests.swift similarity index 96% rename from Example/DBClientTests/Interface/FetchTests.swift rename to Example/DBClientTests/Interface/Realm/RealmFetchTests.swift index 9b44af3..727a8af 100644 --- a/Example/DBClientTests/Interface/FetchTests.swift +++ b/Example/DBClientTests/Interface/Realm/RealmFetchTests.swift @@ -1,5 +1,5 @@ // -// FetchTests.swift +// RealmFetchTests.swift // DBClient-Example // // Created by Roman Kyrylenko on 2/8/17. @@ -11,7 +11,7 @@ import XCTest import DBClient @testable import Example -class FetchTests: DBClientTest { +class RealmFetchTests: DBClientRealmTest { func test_SyncFetch_WhenSuccessful_ReturnObject() { let randomUser = User.createRandom() @@ -61,5 +61,4 @@ class FetchTests: DBClientTest { XCTAssertEqual(expectedObjectsCount, randomUsers.count) } } - } diff --git a/Example/DBClientTests/Interface/ObservableTests.swift b/Example/DBClientTests/Interface/Realm/RealmObservableTests.swift similarity index 97% rename from Example/DBClientTests/Interface/ObservableTests.swift rename to Example/DBClientTests/Interface/Realm/RealmObservableTests.swift index cc1d25b..43a1345 100644 --- a/Example/DBClientTests/Interface/ObservableTests.swift +++ b/Example/DBClientTests/Interface/Realm/RealmObservableTests.swift @@ -1,5 +1,5 @@ //// -//// ObservableTests.swift +//// RealmObservableTests.swift //// DBClient-Example //// //// Created by Roman Kyrylenko on 2/13/17. @@ -10,7 +10,7 @@ import XCTest import DBClient @testable import Example -final class ObservableTests: DBClientTest { +final class RealmObservableTests: DBClientRealmTest { func test_InsertionObservation_WhenSuccessful_InvokesChnages() { let request = FetchRequest() diff --git a/Example/DBClientTests/Interface/UpdateTests.swift b/Example/DBClientTests/Interface/Realm/RealmUpdateTests.swift similarity index 90% rename from Example/DBClientTests/Interface/UpdateTests.swift rename to Example/DBClientTests/Interface/Realm/RealmUpdateTests.swift index e6ad4dd..82b3928 100644 --- a/Example/DBClientTests/Interface/UpdateTests.swift +++ b/Example/DBClientTests/Interface/Realm/RealmUpdateTests.swift @@ -1,5 +1,5 @@ // -// UpdateTests.swift +// RealmUpdateTests.swift // DBClient-Example // // Created by Roman Kyrylenko on 2/9/17. @@ -9,7 +9,7 @@ import XCTest @testable import Example -final class UpdateTests: DBClientTest { +final class RealmUpdateTests: DBClientRealmTest { func test_UpdateUserName_WhenSuccessful_SetsCorrectName() { let randomUser = User.createRandom() diff --git a/Example/DBClientTests/Interface/UpsertTests.swift b/Example/DBClientTests/Interface/Realm/RealmUpsertTests.swift similarity index 92% rename from Example/DBClientTests/Interface/UpsertTests.swift rename to Example/DBClientTests/Interface/Realm/RealmUpsertTests.swift index 4d1e68d..f3b1f42 100644 --- a/Example/DBClientTests/Interface/UpsertTests.swift +++ b/Example/DBClientTests/Interface/Realm/RealmUpsertTests.swift @@ -1,5 +1,5 @@ // -// UpsertTests.swift +// RealmUpsertTests.swift // DBClient-Example // // Created by Roman Kyrylenko on 2/15/17. @@ -9,7 +9,7 @@ import XCTest @testable import Example -final class UpsertTests: DBClientTest { +final class RealmUpsertTests: DBClientRealmTest { func test_UpsertUsers_WhenSuccessful_ReturnsUpsertedUsers() { let newUsers: [User] = (0...5).map { _ in User.createRandom() } diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index c7090b3..06cd657 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -10,15 +10,24 @@ 6544BEF6B0A326E74A0930F3 /* Pods_Example.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 8EE3B934F48958491C32E38F /* Pods_Example.framework */; }; 91146657A7C9C72AEB2CA0A0 /* Pods_DBClientTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F5FF85D02C1E34B0EF6E75EB /* Pods_DBClientTests.framework */; }; B8275B001E4B6D2600232EE4 /* DBClientTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8275AFF1E4B6D2600232EE4 /* DBClientTest.swift */; }; - B8477E9C1E4DC0EA00608B78 /* CreateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8477E951E4DC0EA00608B78 /* CreateTests.swift */; }; - B8477E9D1E4DC0EA00608B78 /* DeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8477E961E4DC0EA00608B78 /* DeleteTests.swift */; }; - B8477E9E1E4DC0EA00608B78 /* ExecuteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8477E971E4DC0EA00608B78 /* ExecuteTests.swift */; }; - B8477E9F1E4DC0EA00608B78 /* FetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8477E981E4DC0EA00608B78 /* FetchTests.swift */; }; - B8477EA01E4DC0EA00608B78 /* UpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8477E991E4DC0EA00608B78 /* UpdateTests.swift */; }; B8477EA11E4DC0EA00608B78 /* User+Comparable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8477E9A1E4DC0EA00608B78 /* User+Comparable.swift */; }; B8477EA21E4DC0EA00608B78 /* User+Equtable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8477E9B1E4DC0EA00608B78 /* User+Equtable.swift */; }; - B88711421E5211AF00189D40 /* ObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B88711411E5211AF00189D40 /* ObservableTests.swift */; }; - B8E447AB1E54916E0078D81A /* UpsertTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8E447AA1E54916E0078D81A /* UpsertTests.swift */; }; + B8D2D430217F35200069CC57 /* CoreDataUpsertTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D429217F35200069CC57 /* CoreDataUpsertTests.swift */; }; + B8D2D431217F35200069CC57 /* CoreDataExecuteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D42A217F35200069CC57 /* CoreDataExecuteTests.swift */; }; + B8D2D432217F35200069CC57 /* CoreDataDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D42B217F35200069CC57 /* CoreDataDeleteTests.swift */; }; + B8D2D433217F35200069CC57 /* CoreDataUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D42C217F35200069CC57 /* CoreDataUpdateTests.swift */; }; + B8D2D434217F35200069CC57 /* CoreDataObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D42D217F35200069CC57 /* CoreDataObservableTests.swift */; }; + B8D2D435217F35200069CC57 /* CoreDataFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D42E217F35200069CC57 /* CoreDataFetchTests.swift */; }; + B8D2D436217F35200069CC57 /* CoreDataCreateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D42F217F35200069CC57 /* CoreDataCreateTests.swift */; }; + B8D2D43E217F35260069CC57 /* RealmUpdateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D437217F35260069CC57 /* RealmUpdateTests.swift */; }; + B8D2D43F217F35260069CC57 /* RealmCreateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D438217F35260069CC57 /* RealmCreateTests.swift */; }; + B8D2D440217F35260069CC57 /* RealmDeleteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D439217F35260069CC57 /* RealmDeleteTests.swift */; }; + B8D2D441217F35260069CC57 /* RealmUpsertTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D43A217F35260069CC57 /* RealmUpsertTests.swift */; }; + B8D2D442217F35260069CC57 /* RealmExecuteTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D43B217F35260069CC57 /* RealmExecuteTests.swift */; }; + B8D2D443217F35260069CC57 /* RealmFetchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D43C217F35260069CC57 /* RealmFetchTests.swift */; }; + B8D2D444217F35260069CC57 /* RealmObservableTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D43D217F35260069CC57 /* RealmObservableTests.swift */; }; + B8D2D446217F36B40069CC57 /* DBClientRealmTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D445217F36B40069CC57 /* DBClientRealmTest.swift */; }; + B8D2D448217F36DC0069CC57 /* DBClientCoreDataTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = B8D2D447217F36DC0069CC57 /* DBClientCoreDataTest.swift */; }; C533725A1E26155D004ECBCF /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = C53372591E26155D004ECBCF /* AppDelegate.swift */; }; C533725C1E26155D004ECBCF /* MasterViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C533725B1E26155D004ECBCF /* MasterViewController.swift */; }; C533725E1E26155D004ECBCF /* DetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C533725D1E26155D004ECBCF /* DetailViewController.swift */; }; @@ -54,15 +63,24 @@ B8275AFD1E4B6D2500232EE4 /* DBClientTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DBClientTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; B8275AFF1E4B6D2600232EE4 /* DBClientTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBClientTest.swift; sourceTree = ""; }; B8275B011E4B6D2600232EE4 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - B8477E951E4DC0EA00608B78 /* CreateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CreateTests.swift; sourceTree = ""; }; - B8477E961E4DC0EA00608B78 /* DeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeleteTests.swift; sourceTree = ""; }; - B8477E971E4DC0EA00608B78 /* ExecuteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ExecuteTests.swift; sourceTree = ""; }; - B8477E981E4DC0EA00608B78 /* FetchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchTests.swift; sourceTree = ""; }; - B8477E991E4DC0EA00608B78 /* UpdateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpdateTests.swift; sourceTree = ""; }; B8477E9A1E4DC0EA00608B78 /* User+Comparable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "User+Comparable.swift"; sourceTree = ""; }; B8477E9B1E4DC0EA00608B78 /* User+Equtable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "User+Equtable.swift"; sourceTree = ""; }; - B88711411E5211AF00189D40 /* ObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObservableTests.swift; sourceTree = ""; }; - B8E447AA1E54916E0078D81A /* UpsertTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UpsertTests.swift; sourceTree = ""; }; + B8D2D429217F35200069CC57 /* CoreDataUpsertTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataUpsertTests.swift; sourceTree = ""; }; + B8D2D42A217F35200069CC57 /* CoreDataExecuteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataExecuteTests.swift; sourceTree = ""; }; + B8D2D42B217F35200069CC57 /* CoreDataDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataDeleteTests.swift; sourceTree = ""; }; + B8D2D42C217F35200069CC57 /* CoreDataUpdateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataUpdateTests.swift; sourceTree = ""; }; + B8D2D42D217F35200069CC57 /* CoreDataObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataObservableTests.swift; sourceTree = ""; }; + B8D2D42E217F35200069CC57 /* CoreDataFetchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataFetchTests.swift; sourceTree = ""; }; + B8D2D42F217F35200069CC57 /* CoreDataCreateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreDataCreateTests.swift; sourceTree = ""; }; + B8D2D437217F35260069CC57 /* RealmUpdateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmUpdateTests.swift; sourceTree = ""; }; + B8D2D438217F35260069CC57 /* RealmCreateTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmCreateTests.swift; sourceTree = ""; }; + B8D2D439217F35260069CC57 /* RealmDeleteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmDeleteTests.swift; sourceTree = ""; }; + B8D2D43A217F35260069CC57 /* RealmUpsertTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmUpsertTests.swift; sourceTree = ""; }; + B8D2D43B217F35260069CC57 /* RealmExecuteTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmExecuteTests.swift; sourceTree = ""; }; + B8D2D43C217F35260069CC57 /* RealmFetchTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmFetchTests.swift; sourceTree = ""; }; + B8D2D43D217F35260069CC57 /* RealmObservableTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RealmObservableTests.swift; sourceTree = ""; }; + B8D2D445217F36B40069CC57 /* DBClientRealmTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBClientRealmTest.swift; sourceTree = ""; }; + B8D2D447217F36DC0069CC57 /* DBClientCoreDataTest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DBClientCoreDataTest.swift; sourceTree = ""; }; C53372561E26155D004ECBCF /* Example.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Example.app; sourceTree = BUILT_PRODUCTS_DIR; }; C53372591E26155D004ECBCF /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; C533725B1E26155D004ECBCF /* MasterViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MasterViewController.swift; sourceTree = ""; }; @@ -137,17 +155,42 @@ B8477E941E4DC0EA00608B78 /* Interface */ = { isa = PBXGroup; children = ( - B8477E951E4DC0EA00608B78 /* CreateTests.swift */, - B8477E961E4DC0EA00608B78 /* DeleteTests.swift */, - B8477E971E4DC0EA00608B78 /* ExecuteTests.swift */, - B8477E981E4DC0EA00608B78 /* FetchTests.swift */, - B88711411E5211AF00189D40 /* ObservableTests.swift */, - B8477E991E4DC0EA00608B78 /* UpdateTests.swift */, - B8E447AA1E54916E0078D81A /* UpsertTests.swift */, + B8D2D427217F35010069CC57 /* CoreData */, + B8D2D428217F350B0069CC57 /* Realm */, ); path = Interface; sourceTree = ""; }; + B8D2D427217F35010069CC57 /* CoreData */ = { + isa = PBXGroup; + children = ( + B8D2D42F217F35200069CC57 /* CoreDataCreateTests.swift */, + B8D2D42B217F35200069CC57 /* CoreDataDeleteTests.swift */, + B8D2D42A217F35200069CC57 /* CoreDataExecuteTests.swift */, + B8D2D42E217F35200069CC57 /* CoreDataFetchTests.swift */, + B8D2D42D217F35200069CC57 /* CoreDataObservableTests.swift */, + B8D2D42C217F35200069CC57 /* CoreDataUpdateTests.swift */, + B8D2D429217F35200069CC57 /* CoreDataUpsertTests.swift */, + B8D2D447217F36DC0069CC57 /* DBClientCoreDataTest.swift */, + ); + path = CoreData; + sourceTree = ""; + }; + B8D2D428217F350B0069CC57 /* Realm */ = { + isa = PBXGroup; + children = ( + B8D2D445217F36B40069CC57 /* DBClientRealmTest.swift */, + B8D2D438217F35260069CC57 /* RealmCreateTests.swift */, + B8D2D439217F35260069CC57 /* RealmDeleteTests.swift */, + B8D2D43B217F35260069CC57 /* RealmExecuteTests.swift */, + B8D2D43C217F35260069CC57 /* RealmFetchTests.swift */, + B8D2D43D217F35260069CC57 /* RealmObservableTests.swift */, + B8D2D437217F35260069CC57 /* RealmUpdateTests.swift */, + B8D2D43A217F35260069CC57 /* RealmUpsertTests.swift */, + ); + path = Realm; + sourceTree = ""; + }; C533724D1E26155D004ECBCF = { isa = PBXGroup; children = ( @@ -429,16 +472,25 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + B8D2D434217F35200069CC57 /* CoreDataObservableTests.swift in Sources */, + B8D2D442217F35260069CC57 /* RealmExecuteTests.swift in Sources */, + B8D2D435217F35200069CC57 /* CoreDataFetchTests.swift in Sources */, + B8D2D444217F35260069CC57 /* RealmObservableTests.swift in Sources */, + B8D2D441217F35260069CC57 /* RealmUpsertTests.swift in Sources */, B8275B001E4B6D2600232EE4 /* DBClientTest.swift in Sources */, - B8477E9C1E4DC0EA00608B78 /* CreateTests.swift in Sources */, - B8477EA01E4DC0EA00608B78 /* UpdateTests.swift in Sources */, + B8D2D43F217F35260069CC57 /* RealmCreateTests.swift in Sources */, + B8D2D436217F35200069CC57 /* CoreDataCreateTests.swift in Sources */, + B8D2D448217F36DC0069CC57 /* DBClientCoreDataTest.swift in Sources */, + B8D2D431217F35200069CC57 /* CoreDataExecuteTests.swift in Sources */, + B8D2D443217F35260069CC57 /* RealmFetchTests.swift in Sources */, + B8D2D433217F35200069CC57 /* CoreDataUpdateTests.swift in Sources */, + B8D2D440217F35260069CC57 /* RealmDeleteTests.swift in Sources */, + B8D2D432217F35200069CC57 /* CoreDataDeleteTests.swift in Sources */, B8477EA21E4DC0EA00608B78 /* User+Equtable.swift in Sources */, - B88711421E5211AF00189D40 /* ObservableTests.swift in Sources */, + B8D2D446217F36B40069CC57 /* DBClientRealmTest.swift in Sources */, + B8D2D43E217F35260069CC57 /* RealmUpdateTests.swift in Sources */, + B8D2D430217F35200069CC57 /* CoreDataUpsertTests.swift in Sources */, B8477EA11E4DC0EA00608B78 /* User+Comparable.swift in Sources */, - B8477E9D1E4DC0EA00608B78 /* DeleteTests.swift in Sources */, - B8477E9F1E4DC0EA00608B78 /* FetchTests.swift in Sources */, - B8E447AB1E54916E0078D81A /* UpsertTests.swift in Sources */, - B8477E9E1E4DC0EA00608B78 /* ExecuteTests.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; From 3e9233c538881ce02676855e439b67c4259ec3dc Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 23 Oct 2018 14:23:20 +0300 Subject: [PATCH 32/47] [Fix] readme misspelling --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 16b7e4c..f8745c4 100644 --- a/README.md +++ b/README.md @@ -6,9 +6,9 @@ There're three podspecs: -- `DBClient/Core` contain pure (CoreData/Realm-free) interface / types used to abstract from implementation. Use it only in case you're about to provide custom implementation of any available storage types. -- `DBClient/CoreData` contain CoreData implementation. -- `DBClient/Realm` contain Realm implementation. +- `DBClient/Core` contains pure (CoreData/Realm-free) interface / types used to abstract from implementation. Use it only in case you're about to provide custom implementation of any available storage types. +- `DBClient/CoreData` contains CoreData implementation. +- `DBClient/Realm` contains Realm implementation. ## Usage From ab1ac9adeae3abd78ef323898a6cb3cd1a3ca831 Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Tue, 23 Oct 2018 15:07:39 +0300 Subject: [PATCH 33/47] [Add] swift version --- .swift-version | 1 + DBClient.podspec | 1 + 2 files changed, 2 insertions(+) create mode 100644 .swift-version diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..bf77d54 --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +4.2 diff --git a/DBClient.podspec b/DBClient.podspec index cfe47d0..816691d 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -11,6 +11,7 @@ Pod::Spec.new do |s| s.homepage = 'https://yalantis.com/' s.ios.deployment_target = "10.0" + s.swift_version = '4.2' s.dependency 'YALResult', '1.1' From 035b9cad6f97913a55195119dea8e47c9ffd9258 Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Wed, 24 Oct 2018 12:47:11 +0300 Subject: [PATCH 34/47] [Add] single versions of sync methods; tests --- DBClient/Core/DBClient.swift | 29 +++++++++++++++++-- .../CoreData/CoreDataCreateTests.swift | 24 +++++++++++++-- .../CoreData/CoreDataDeleteTests.swift | 22 ++++++++++++-- .../CoreData/CoreDataExecuteTests.swift | 12 +++++++- .../CoreData/CoreDataUpdateTests.swift | 10 +++++++ .../CoreData/CoreDataUpsertTests.swift | 12 ++++++++ 6 files changed, 102 insertions(+), 7 deletions(-) diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index 4ef0018..6333d1e 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -100,7 +100,7 @@ public protocol DBClient { /// - objects: list of objects to be deleted /// - Returns: `Result` with appropriate error in case of failure. @discardableResult - func delete(_ objects: [T]) -> Result<()> + func delete(_ objects: [T]) -> Result /// Synchronously iterates through given objects and updates existing in database instances or creates them /// @@ -190,6 +190,11 @@ public extension DBClient { insert([object], completion: { completion($0.next(self.convertArrayTaskToSingleObject)) }) } + @discardableResult + func insert(_ object: T) -> Result { + return insert([object]).next(convertArrayTaskToSingleObject) + } + /// Updates changed performed with object to database. /// /// - Parameters: @@ -199,6 +204,11 @@ public extension DBClient { update([object], completion: { completion($0.next(self.convertArrayTaskToSingleObject)) }) } + @discardableResult + func update(_ object: T) -> Result { + return update([object]).next(convertArrayTaskToSingleObject) + } + /// Deletes object from database. /// /// - Parameters: @@ -208,12 +218,17 @@ public extension DBClient { delete([object], completion: completion) } + @discardableResult + func delete(_ object: T) -> Result { + return delete([object]) + } + /// Updates existing in database instances or creates them using upsert method defined in your model /// /// - Parameters: /// - object: object to be worked with /// - completion: `Result` with inserted or updated instance. - func upsert(_ object: T, completion: @escaping (Result<(object: T, isUpdated: Bool)>) -> Void) { + func upsert(_ object: T, completion: @escaping (Result<(object: T, isUpdated: Bool)>) -> Void) { upsert([object]) { result in completion(result.next { (updated: [T], inserted: [T]) -> Result<(object: T, isUpdated: Bool)> in guard let object = updated.first ?? inserted.first else { @@ -224,6 +239,16 @@ public extension DBClient { } } + @discardableResult + func upsert(_ object: T) -> Result<(object: T, isUpdated: Bool)> { + return upsert([object]).next { (updated: [T], inserted: [T]) in + guard let object = updated.first ?? inserted.first else { + return Result.failure(DBClientError.missingData) + } + return Result.success((object: object, isUpdated: !updated.isEmpty)) + } + } + private func convertArrayTaskToSingleObject(_ array: [T]) -> Result { guard let first = array.first else { return .failure(DBClientError.missingData) diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataCreateTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataCreateTests.swift index 45545ed..45c21fd 100644 --- a/Example/DBClientTests/Interface/CoreData/CoreDataCreateTests.swift +++ b/Example/DBClientTests/Interface/CoreData/CoreDataCreateTests.swift @@ -11,12 +11,32 @@ import XCTest final class CoreDataCreateTests: DBClientCoreDataTest { + func test_SyncSingleInsertion_WhenSuccessful_ReturnsObject() { + let randomUser = User.createRandom() + let result = dbClient.insert(randomUser) + switch result { + case .failure(let error): XCTFail(error.localizedDescription) + case .success(let user): XCTAssertEqual(randomUser, user) + } + } + + func test_SyncBulkInsertion_WhenSuccessful_ReturnsObjects() { + let randomUsers: [User] = (0...100).map { _ in User.createRandom() } + + let result = dbClient.insert(randomUsers) + + switch result { + case .failure(let error): XCTFail(error.localizedDescription) + case .success(let users): XCTAssertEqual(users.sorted(), randomUsers.sorted()) + } + } + func test_SingleInsertion_WhenSuccessful_ReturnsObject() { let randomUser = User.createRandom() let expectationObject = expectation(description: "Object") var expectedObject: User? - self.dbClient.insert(randomUser) { result in + dbClient.insert(randomUser) { result in expectedObject = result.value expectationObject.fulfill() } @@ -32,7 +52,7 @@ final class CoreDataCreateTests: DBClientCoreDataTest { let expectationObjects = expectation(description: "Objects") var expectedObjectsCount = 0 - self.dbClient.insert(randomUsers) { result in + dbClient.insert(randomUsers) { result in expectedObjectsCount = result.value?.count ?? 0 expectationObjects.fulfill() } diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataDeleteTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataDeleteTests.swift index dce73de..e3efd44 100644 --- a/Example/DBClientTests/Interface/CoreData/CoreDataDeleteTests.swift +++ b/Example/DBClientTests/Interface/CoreData/CoreDataDeleteTests.swift @@ -11,12 +11,30 @@ import XCTest final class CoreDataDeleteTests: DBClientCoreDataTest { + func test_SyncSingleDeletion_WhenSuccessful_ReturnsNil() { + let randomUser = User.createRandom() + + let result = dbClient.insert(randomUser) + let removalResult = dbClient.delete(result.require()) + + XCTAssertNotNil(removalResult.value) + } + + func test_SyncBulkDeletion_WhenSuccessful_ReturnsNil() { + let randomUsers: [User] = (0...100).map { _ in User.createRandom() } + + let insertionResult = dbClient.insert(randomUsers) + let removalResult = dbClient.delete(insertionResult.require()) + + XCTAssertNotNil(removalResult.value) + } + func test_SingleDeletion_WhenSuccessful_ReturnsNil() { let randomUser = User.createRandom() let expectationHit = expectation(description: "Object") var isDeleted = false - self.dbClient.insert(randomUser) { result in + dbClient.insert(randomUser) { result in if let object = result.value { self.dbClient.delete(object) { result in isDeleted = result.value != nil @@ -35,7 +53,7 @@ final class CoreDataDeleteTests: DBClientCoreDataTest { let expectationHit = expectation(description: "Object") var isDeleted = false - self.dbClient.insert(randomUsers) { result in + dbClient.insert(randomUsers) { result in if let objects = result.value { self.dbClient.delete(objects) { result in isDeleted = result.value != nil diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataExecuteTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataExecuteTests.swift index 375d05a..de23225 100644 --- a/Example/DBClientTests/Interface/CoreData/CoreDataExecuteTests.swift +++ b/Example/DBClientTests/Interface/CoreData/CoreDataExecuteTests.swift @@ -12,6 +12,16 @@ import DBClient final class CoreDataExecuteTests: DBClientCoreDataTest { + func test_SingleSyncExecute_WhenSuccessful_ReturnsCount() { + let randomUser = User.createRandom() + + dbClient.insert(randomUser) + let request = FetchRequest() + let executionResult = dbClient.execute(request) + + XCTAssertEqual(executionResult.require().first!, randomUser) + } + func test_SingleExecute_WhenSuccessful_ReturnsCount() { let randomUser = User.createRandom() let expectationObject = expectation(description: "Object") @@ -122,7 +132,7 @@ final class CoreDataExecuteTests: DBClientCoreDataTest { } waitForExpectations(timeout: 1) { _ in - XCTAssertEqual(expectedUsers, preicatedUsers) + XCTAssertEqual(expectedUsers.sorted(), preicatedUsers.sorted()) } } } diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataUpdateTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataUpdateTests.swift index f2a9593..568f123 100644 --- a/Example/DBClientTests/Interface/CoreData/CoreDataUpdateTests.swift +++ b/Example/DBClientTests/Interface/CoreData/CoreDataUpdateTests.swift @@ -11,6 +11,16 @@ import XCTest final class CoreDataUpdateTests: DBClientCoreDataTest { + func test_SyncUpdateUserName_WhenSuccessful_SetsCorrectName() { + let randomUser = User.createRandom() + + dbClient.insert(randomUser) + randomUser.name = "Bob" + let updationResult = dbClient.update(randomUser) + + XCTAssertEqual(randomUser, updationResult.value) + } + func test_UpdateUserName_WhenSuccessful_SetsCorrectName() { let randomUser = User.createRandom() let expectationObject = expectation(description: "Object") diff --git a/Example/DBClientTests/Interface/CoreData/CoreDataUpsertTests.swift b/Example/DBClientTests/Interface/CoreData/CoreDataUpsertTests.swift index 62f2d5a..be6e124 100644 --- a/Example/DBClientTests/Interface/CoreData/CoreDataUpsertTests.swift +++ b/Example/DBClientTests/Interface/CoreData/CoreDataUpsertTests.swift @@ -11,6 +11,18 @@ import XCTest final class CoreDataUpsertTests: DBClientCoreDataTest { + func test_SyncUpsertUsers_WhenSuccessful_ReturnsUpsertedUsers() { + let newUsers: [User] = (0...5).map { _ in User.createRandom() } + let savedUsers: [User] = (0...5).map { _ in User.createRandom() } + let combinedUsers = savedUsers + newUsers + + dbClient.insert(savedUsers) + let result = dbClient.upsert(combinedUsers) + + let expectedUsers = result.require().updated + result.require().inserted + XCTAssertEqual(expectedUsers.sorted(), combinedUsers.sorted()) + } + func test_UpsertUsers_WhenSuccessful_ReturnsUpsertedUsers() { let newUsers: [User] = (0...5).map { _ in User.createRandom() } let savedUsers: [User] = (0...5).map { _ in User.createRandom() } From 94f55fb9704c862eb84f6f77ad883139576da66b Mon Sep 17 00:00:00 2001 From: Roman Kyrylenko Date: Wed, 24 Oct 2018 12:54:45 +0300 Subject: [PATCH 35/47] [Update] realm version --- DBClient.podspec | 2 +- Example/Podfile.lock | 24 ++++++++++++------------ README.md | 14 +++++++------- 3 files changed, 20 insertions(+), 20 deletions(-) diff --git a/DBClient.podspec b/DBClient.podspec index 816691d..3e2dfde 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -30,6 +30,6 @@ Pod::Spec.new do |s| s.subspec "Realm" do |spec| spec.dependency "DBClient/Core" spec.source_files = ['DBClient/Realm/*.swift'] - spec.dependency "RealmSwift", "~> 3.11" + spec.dependency "RealmSwift", "~> 3.11.1" end end diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 9a8b7ba..7e05404 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,18 +1,18 @@ PODS: - - DBClient/Core (1.1): + - DBClient/Core (1.2): - YALResult (= 1.1) - - DBClient/CoreData (1.1): + - DBClient/CoreData (1.2): - DBClient/Core - YALResult (= 1.1) - - DBClient/Realm (1.1): + - DBClient/Realm (1.2): - DBClient/Core - - RealmSwift (~> 3.11) + - RealmSwift (~> 3.11.1) - YALResult (= 1.1) - - Realm (3.11.0): - - Realm/Headers (= 3.11.0) - - Realm/Headers (3.11.0) - - RealmSwift (3.11.0): - - Realm (= 3.11.0) + - Realm (3.11.1): + - Realm/Headers (= 3.11.1) + - Realm/Headers (3.11.1) + - RealmSwift (3.11.1): + - Realm (= 3.11.1) - YALResult (1.1) DEPENDENCIES: @@ -30,9 +30,9 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - DBClient: fa60a7f1d189fb9c59e35b4de433b4508001b359 - Realm: 92f09a102692b96a9a10e9617f214f15c5ab85fc - RealmSwift: 5f0481cd658bb751c509314b964a35eaa264d2cf + DBClient: 28d90aa11a7ba462416ccc2e91bfb19282cf5941 + Realm: 037c5919b9ceb59d6beed5d3b031096856b119b3 + RealmSwift: c9580133e73ef40ed340401af2dbc9a5790dfea7 YALResult: e7acf1405252ef9a2a1f121176cc44b588844d93 PODFILE CHECKSUM: 01e2cf56f70348c45b9e7248a505591dd86d210b diff --git a/README.md b/README.md index 2c77726..5b1a95f 100644 --- a/README.md +++ b/README.md @@ -141,10 +141,10 @@ public func upsertManagedObject(in context: NSManagedObjectContext, existedInsta ## Version history -| Version | Swift | Dependencies | iOS | -|-----------|-------|------------------|------| -| `1.2` | 4.2 | RealmSwift 3.11, YALResult 1.1 | 10 | -| `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | -| `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | -| `0.6` | 4 | RealmSwift 2.10.1, BoltsSwift 1.3 | 9 | -| `0.4.2` | 3.2 | RealmSwift 2.1.1, BoltsSwift 1.3 | 9 | +| Version | Swift | Dependencies | iOS | +|----------|-------|----------------------------------------|------| +| `1.2` | 4.2 | RealmSwift 3.11.1, YALResult 1.1 | 10 | +| `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | +| `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | +| `0.6` | 4 | RealmSwift 2.10.1, BoltsSwift 1.3 | 9 | +| `0.4.2` | 3.2 | RealmSwift 2.1.1, BoltsSwift 1.3 | 9 | From c97d0ff7ea58bd3dec146778687b0de2c892b82c Mon Sep 17 00:00:00 2001 From: rnkyr Date: Fri, 7 Dec 2018 12:55:33 +0200 Subject: [PATCH 36/47] [Add] removeAllObjects method --- DBClient/Core/DBClient.swift | 3 +++ DBClient/CoreData/CoreDataDBClient.swift | 16 ++++++++++++++++ DBClient/Realm/RealmDBClient.swift | 17 +++++++++++++++++ 3 files changed, 36 insertions(+) diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index 6333d1e..5f31903 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -64,6 +64,9 @@ public protocol DBClient { /// - completion: `Result` with appropriate error in case of failure. func delete(_ objects: [T], completion: @escaping (Result<()>) -> Void) + /// Removes all object of a given type from database. + func deleteAllObjects(of type: T, completion: @escaping (Result<()>) -> Void) + /// Iterates through given objects and updates existing in database instances or creates them /// /// - Parameters: diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index 13ce676..1a54619 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -501,6 +501,22 @@ extension CoreDataDBClient: DBClient { return result } + public func deleteAllObjects(of type: T, completion: @escaping (Result<()>) -> Void) where T : Stored { + let type = checkType(T.self) + + let fetchRequest = NSFetchRequest(entityName: type.entityName) + let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + performWriteTask { context, savingClosure in + do { + try context.execute(deleteRequest) + try savingClosure() + completion(.success(())) + } catch { + completion(.failure(error)) + } + } + } + @discardableResult public func upsert(_ objects: [T]) -> Result<(updated: [T], inserted: [T])> { checkType(T.self) diff --git a/DBClient/Realm/RealmDBClient.swift b/DBClient/Realm/RealmDBClient.swift index 1715fed..4491ea5 100644 --- a/DBClient/Realm/RealmDBClient.swift +++ b/DBClient/Realm/RealmDBClient.swift @@ -66,6 +66,23 @@ extension RealmDBClient: DBClient { public func delete(_ objects: [T], completion: @escaping (Result<()>) -> Void) where T : Stored { completion(delete(objects)) } + + public func deleteAllObjects(of type: T, completion: @escaping (Result<()>) -> Void) where T: Stored { + let type = checkType(T.self) + + let realmType = type.realmClass() + + do { + let realmObjects = realm.objects(realmType) + realm.beginWrite() + realm.delete(realmObjects) + try realm.commitWrite() + + completion(.success(())) + } catch { + completion(.failure(error)) + } + } public func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) where T : Stored { completion(upsert(objects)) From 4cdecd2f11eac13b282a7f194fb340b39cf89991 Mon Sep 17 00:00:00 2001 From: rnkyr Date: Fri, 7 Dec 2018 13:02:08 +0200 Subject: [PATCH 37/47] [Fix] argument type mismatch --- DBClient/Core/DBClient.swift | 2 +- DBClient/CoreData/CoreDataDBClient.swift | 2 +- DBClient/Realm/RealmDBClient.swift | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index 5f31903..239e00e 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -65,7 +65,7 @@ public protocol DBClient { func delete(_ objects: [T], completion: @escaping (Result<()>) -> Void) /// Removes all object of a given type from database. - func deleteAllObjects(of type: T, completion: @escaping (Result<()>) -> Void) + func deleteAllObjects(of type: T.Type, completion: @escaping (Result<()>) -> Void) /// Iterates through given objects and updates existing in database instances or creates them /// diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index 1a54619..475ab74 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -501,7 +501,7 @@ extension CoreDataDBClient: DBClient { return result } - public func deleteAllObjects(of type: T, completion: @escaping (Result<()>) -> Void) where T : Stored { + public func deleteAllObjects(of type: T.Type, completion: @escaping (Result<()>) -> Void) where T : Stored { let type = checkType(T.self) let fetchRequest = NSFetchRequest(entityName: type.entityName) diff --git a/DBClient/Realm/RealmDBClient.swift b/DBClient/Realm/RealmDBClient.swift index 4491ea5..3496b2f 100644 --- a/DBClient/Realm/RealmDBClient.swift +++ b/DBClient/Realm/RealmDBClient.swift @@ -67,7 +67,7 @@ extension RealmDBClient: DBClient { completion(delete(objects)) } - public func deleteAllObjects(of type: T, completion: @escaping (Result<()>) -> Void) where T: Stored { + public func deleteAllObjects(of type: T.Type, completion: @escaping (Result<()>) -> Void) where T: Stored { let type = checkType(T.self) let realmType = type.realmClass() From 6e649afb99daa14663bc38c8dd55cce2c6256195 Mon Sep 17 00:00:00 2001 From: rnkyr Date: Fri, 7 Dec 2018 13:15:34 +0200 Subject: [PATCH 38/47] [Bump] version --- DBClient.podspec | 30 +++++++++++++++--------------- README.md | 2 +- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/DBClient.podspec b/DBClient.podspec index 3e2dfde..ac285e8 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,35 +1,35 @@ Pod::Spec.new do |s| - s.name = "DBClient" - s.version = "1.2" + s.name = 'DBClient' + s.version = '1.3' s.requires_arc = true - s.summary = "CoreData & Realm wrapper written on Swift" - s.homepage = "" - s.license = { :type => "MIT", :file => "LICENSE" } - s.author = { "Yalantis" => "mail@yalantis.com" } - s.source = { :git => "https://github.com/Yalantis/DBClient.git", :tag => s.version } + s.summary = 'CoreData & Realm wrapper written on Swift' + s.homepage = '' + s.license = { :type => 'MIT', :file => 'LICENSE' } + s.author = { 'Yalantis' => 'mail@yalantis.com' } + s.source = { :git => 'https://github.com/Yalantis/DBClient.git', :tag => s.version } s.social_media_url = 'https://yalantis.com/' s.homepage = 'https://yalantis.com/' - s.ios.deployment_target = "10.0" + s.ios.deployment_target = '10.0' s.swift_version = '4.2' s.dependency 'YALResult', '1.1' - s.default_subspec = "Core" + s.default_subspec = 'Core' - s.subspec "Core" do |spec| + s.subspec 'Core' do |spec| spec.source_files = ['DBClient/Core/*.swift'] end - s.subspec "CoreData" do |spec| - spec.dependency "DBClient/Core" + s.subspec 'CoreData' do |spec| + spec.dependency 'DBClient/Core' spec.source_files = ['DBClient/CoreData/*.swift'] spec.frameworks = ['CoreData'] end - s.subspec "Realm" do |spec| - spec.dependency "DBClient/Core" + s.subspec 'Realm' do |spec| + spec.dependency 'DBClient/Core' spec.source_files = ['DBClient/Realm/*.swift'] - spec.dependency "RealmSwift", "~> 3.11.1" + spec.dependency 'RealmSwift', '~> 3.11.1' end end diff --git a/README.md b/README.md index 5b1a95f..232dcc6 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ public func upsertManagedObject(in context: NSManagedObjectContext, existedInsta | Version | Swift | Dependencies | iOS | |----------|-------|----------------------------------------|------| -| `1.2` | 4.2 | RealmSwift 3.11.1, YALResult 1.1 | 10 | +| `1.3` | 4.2 | RealmSwift 3.11.1, YALResult 1.1 | 10 | | `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | | `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | | `0.6` | 4 | RealmSwift 2.10.1, BoltsSwift 1.3 | 9 | From e1d4dd5c96b7a490e8e29760c62e1d2373e3e6ec Mon Sep 17 00:00:00 2001 From: Denis Zabrod'ko Date: Tue, 28 May 2019 17:03:09 +0300 Subject: [PATCH 39/47] [Update] code to Swift 5 version --- DBClient.podspec | 8 ++--- DBClient/Core/DBClient.swift | 2 +- DBClient/CoreData/CoreDataObservable.swift | 4 +++ Example/Example.xcodeproj/project.pbxproj | 9 +++--- Example/Podfile.lock | 34 +++++++++++----------- 5 files changed, 31 insertions(+), 26 deletions(-) diff --git a/DBClient.podspec b/DBClient.podspec index ac285e8..32ba1cc 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'DBClient' - s.version = '1.3' + s.version = '1.4' s.requires_arc = true s.summary = 'CoreData & Realm wrapper written on Swift' s.homepage = '' @@ -11,9 +11,9 @@ Pod::Spec.new do |s| s.homepage = 'https://yalantis.com/' s.ios.deployment_target = '10.0' - s.swift_version = '4.2' + s.swift_version = '5' - s.dependency 'YALResult', '1.1' + s.dependency 'YALResult', '1.4' s.default_subspec = 'Core' @@ -30,6 +30,6 @@ Pod::Spec.new do |s| s.subspec 'Realm' do |spec| spec.dependency 'DBClient/Core' spec.source_files = ['DBClient/Realm/*.swift'] - spec.dependency 'RealmSwift', '~> 3.11.1' + spec.dependency 'RealmSwift', '~> 3.15.0' end end diff --git a/DBClient/Core/DBClient.swift b/DBClient/Core/DBClient.swift index 239e00e..ee4a7e0 100644 --- a/DBClient/Core/DBClient.swift +++ b/DBClient/Core/DBClient.swift @@ -9,7 +9,7 @@ import Foundation import YALResult -public typealias Result = YALResult.Result +public typealias Result = YALResult public enum DBClientError: Error { diff --git a/DBClient/CoreData/CoreDataObservable.swift b/DBClient/CoreData/CoreDataObservable.swift index c4ad07d..3926aab 100644 --- a/DBClient/CoreData/CoreDataObservable.swift +++ b/DBClient/CoreData/CoreDataObservable.swift @@ -111,6 +111,10 @@ private class FetchedResultsControllerDelegate: NSObject, NS case .update, .move: batchChanges.append(.update(indexPath!.row, object)) + + @unknown + default: + assertionFailure("trying to handle unknown case \(type)") } } diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 06cd657..627386d 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -328,6 +328,7 @@ developmentRegion = English; hasScannedForEncodings = 0; knownRegions = ( + English, en, Base, ); @@ -551,7 +552,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.DBClientTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; }; name = Debug; @@ -565,7 +566,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.DBClientTests; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Example.app/Example"; }; name = Release; @@ -688,7 +689,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.dbclient.Example; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Debug; }; @@ -701,7 +702,7 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = com.yalantis.dbclient.Example; PRODUCT_NAME = "$(TARGET_NAME)"; - SWIFT_VERSION = 4.2; + SWIFT_VERSION = 5.0; }; name = Release; }; diff --git a/Example/Podfile.lock b/Example/Podfile.lock index 7e05404..b787e69 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,19 +1,19 @@ PODS: - - DBClient/Core (1.2): - - YALResult (= 1.1) - - DBClient/CoreData (1.2): + - DBClient/Core (1.4): + - YALResult (= 1.4) + - DBClient/CoreData (1.4): - DBClient/Core - - YALResult (= 1.1) - - DBClient/Realm (1.2): + - YALResult (= 1.4) + - DBClient/Realm (1.4): - DBClient/Core - - RealmSwift (~> 3.11.1) - - YALResult (= 1.1) - - Realm (3.11.1): - - Realm/Headers (= 3.11.1) - - Realm/Headers (3.11.1) - - RealmSwift (3.11.1): - - Realm (= 3.11.1) - - YALResult (1.1) + - RealmSwift (~> 3.15.0) + - YALResult (= 1.4) + - Realm (3.15.0): + - Realm/Headers (= 3.15.0) + - Realm/Headers (3.15.0) + - RealmSwift (3.15.0): + - Realm (= 3.15.0) + - YALResult (1.4) DEPENDENCIES: - DBClient/CoreData (from `../`) @@ -30,10 +30,10 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - DBClient: 28d90aa11a7ba462416ccc2e91bfb19282cf5941 - Realm: 037c5919b9ceb59d6beed5d3b031096856b119b3 - RealmSwift: c9580133e73ef40ed340401af2dbc9a5790dfea7 - YALResult: e7acf1405252ef9a2a1f121176cc44b588844d93 + DBClient: e97c5dd333ac354778f17740ea7bcd6e27645ce9 + Realm: 9b834e1be6062f544805252c812348872dc5d4ed + RealmSwift: 8a41886f8ab6efef9eb8df97de2f2bb911561a79 + YALResult: 26915691cdd19269936336d6f28e1a015c64175e PODFILE CHECKSUM: 01e2cf56f70348c45b9e7248a505591dd86d210b From 642ae265ce783d17b4b9809c6d75658e09f133d9 Mon Sep 17 00:00:00 2001 From: Denis Zabrod'ko Date: Tue, 28 May 2019 17:14:40 +0300 Subject: [PATCH 40/47] [Update] README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 232dcc6..3c6696f 100644 --- a/README.md +++ b/README.md @@ -143,6 +143,7 @@ public func upsertManagedObject(in context: NSManagedObjectContext, existedInsta | Version | Swift | Dependencies | iOS | |----------|-------|----------------------------------------|------| +| `1.4` | 5 | RealmSwift 3.15.0, YALResult 1.4 | 10 | | `1.3` | 4.2 | RealmSwift 3.11.1, YALResult 1.1 | 10 | | `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | | `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | From fe08e31dc562e287d0056308cc3c4e9209c9bb83 Mon Sep 17 00:00:00 2001 From: rnkyr Date: Tue, 18 Jun 2019 12:12:46 +0300 Subject: [PATCH 41/47] [update] .swift-version --- .swift-version | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.swift-version b/.swift-version index bf77d54..819e07a 100644 --- a/.swift-version +++ b/.swift-version @@ -1 +1 @@ -4.2 +5.0 From d2b95026f50bc544c628c0d498ddc1efa098a751 Mon Sep 17 00:00:00 2001 From: rnkyr Date: Tue, 18 Jun 2019 12:17:31 +0300 Subject: [PATCH 42/47] [remove] .swift-version --- .swift-version | 1 - 1 file changed, 1 deletion(-) delete mode 100644 .swift-version diff --git a/.swift-version b/.swift-version deleted file mode 100644 index 819e07a..0000000 --- a/.swift-version +++ /dev/null @@ -1 +0,0 @@ -5.0 From ff6a1a156de5494cdd4c08598f2f2ccbee1a313c Mon Sep 17 00:00:00 2001 From: rnkyr Date: Wed, 19 Jun 2019 14:41:42 +0300 Subject: [PATCH 43/47] [update] specify swift version --- .swift-version | 1 + DBClient.podspec | 6 +----- README.md | 4 ++-- 3 files changed, 4 insertions(+), 7 deletions(-) create mode 100644 .swift-version diff --git a/.swift-version b/.swift-version new file mode 100644 index 0000000..819e07a --- /dev/null +++ b/.swift-version @@ -0,0 +1 @@ +5.0 diff --git a/DBClient.podspec b/DBClient.podspec index 32ba1cc..cb4d108 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'DBClient' - s.version = '1.4' + s.version = '1.4.1' s.requires_arc = true s.summary = 'CoreData & Realm wrapper written on Swift' s.homepage = '' @@ -9,12 +9,8 @@ Pod::Spec.new do |s| s.source = { :git => 'https://github.com/Yalantis/DBClient.git', :tag => s.version } s.social_media_url = 'https://yalantis.com/' s.homepage = 'https://yalantis.com/' - s.ios.deployment_target = '10.0' - s.swift_version = '5' - s.dependency 'YALResult', '1.4' - s.default_subspec = 'Core' s.subspec 'Core' do |spec| diff --git a/README.md b/README.md index 3c6696f..d9b9a2c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # DBClient -[![cocoapods](https://img.shields.io/cocoapods/v/DBClient.svg)](https://img.shields.io/cocoapods/v/DBClient.svg) ![swift](https://img.shields.io/badge/Swift-4.2-orange.svg) ![Platform](http://img.shields.io/badge/platform-iOS-blue.svg?style=flat) [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/Yalantis/DBClient/blob/master/LICENSE) +[![cocoapods](https://img.shields.io/cocoapods/v/DBClient.svg)](https://img.shields.io/cocoapods/v/DBClient.svg) ![swift](https://img.shields.io/badge/Swift-5.0-orange.svg) ![Platform](http://img.shields.io/badge/platform-iOS-blue.svg?style=flat) [![License](http://img.shields.io/badge/license-MIT-green.svg?style=flat)](https://github.com/Yalantis/DBClient/blob/master/LICENSE) ## Integration (Cocoapods) @@ -143,7 +143,7 @@ public func upsertManagedObject(in context: NSManagedObjectContext, existedInsta | Version | Swift | Dependencies | iOS | |----------|-------|----------------------------------------|------| -| `1.4` | 5 | RealmSwift 3.15.0, YALResult 1.4 | 10 | +| `1.4.1` | 5 | RealmSwift 3.15.0, YALResult 1.4 | 10 | | `1.3` | 4.2 | RealmSwift 3.11.1, YALResult 1.1 | 10 | | `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | | `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | From 5af69d087c8a11757a8e6d7547032e4912c5b390 Mon Sep 17 00:00:00 2001 From: Denis Zabrod'ko Date: Tue, 2 Jul 2019 14:33:15 +0300 Subject: [PATCH 44/47] [Update] FetchRequest for support of multiple sort descriptors --- DBClient/Core/FetchRequest.swift | 26 +++++++++++++--------- DBClient/CoreData/CoreDataDBClient.swift | 4 ++-- DBClient/CoreData/CoreDataObservable.swift | 4 ++-- DBClient/Realm/RealmDBClient.swift | 16 +++++++++++-- Example/Example/MasterViewController.swift | 2 +- 5 files changed, 34 insertions(+), 18 deletions(-) diff --git a/DBClient/Core/FetchRequest.swift b/DBClient/Core/FetchRequest.swift index 28f16d7..da7c6d8 100644 --- a/DBClient/Core/FetchRequest.swift +++ b/DBClient/Core/FetchRequest.swift @@ -11,19 +11,19 @@ import Foundation /// Describes a fetch request to get objects from a database. public struct FetchRequest { - public let sortDescriptor: NSSortDescriptor? + public let sortDescriptors: [NSSortDescriptor]? public let predicate: NSPredicate? public let fetchOffset: Int public let fetchLimit: Int /// - Parameters: /// - predicate: Predicate for objects filtering; nil by default. - /// - sortDescriptor: Sort descriptor; nil by default. + /// - sortDescriptors: Sort descriptors; nil by default. /// - fetchOffset: Offset of data for request; 0 by default (no offset). /// - fetchLimit: Amount of objects to be fetched; no limit if zero given; 0 by default. - public init(predicate: NSPredicate? = nil, sortDescriptor: NSSortDescriptor? = nil, fetchOffset: Int = 0, fetchLimit: Int = 0) { + public init(predicate: NSPredicate? = nil, sortDescriptors: [NSSortDescriptor]? = nil, fetchOffset: Int = 0, fetchLimit: Int = 0) { self.predicate = predicate - self.sortDescriptor = sortDescriptor + self.sortDescriptors = sortDescriptors self.fetchOffset = fetchOffset self.fetchLimit = fetchLimit } @@ -75,19 +75,23 @@ public extension FetchRequest { public extension FetchRequest { func sorted(with sortDescriptor: NSSortDescriptor) -> FetchRequest { - return request(withSortDescriptor: sortDescriptor) + return request(withSortDescriptors: [sortDescriptor]) + } + + func sorted(with sortDescriptors: [NSSortDescriptor]) -> FetchRequest { + return request(withSortDescriptors: sortDescriptors) } func sorted(with key: String?, ascending: Bool, comparator cmptr: @escaping Comparator) -> FetchRequest { - return request(withSortDescriptor: NSSortDescriptor(key: key, ascending: ascending, comparator: cmptr)) + return request(withSortDescriptors: [NSSortDescriptor(key: key, ascending: ascending, comparator: cmptr)]) } func sorted(with key: String?, ascending: Bool) -> FetchRequest { - return request(withSortDescriptor: NSSortDescriptor(key: key, ascending: ascending)) + return request(withSortDescriptors: [NSSortDescriptor(key: key, ascending: ascending)]) } func sorted(with key: String?, ascending: Bool, selector: Selector) -> FetchRequest { - return request(withSortDescriptor: NSSortDescriptor(key: key, ascending: ascending, selector: selector)) + return request(withSortDescriptors: [NSSortDescriptor(key: key, ascending: ascending, selector: selector)]) } } @@ -96,10 +100,10 @@ public extension FetchRequest { private extension FetchRequest { func request(withPredicate predicate: NSPredicate) -> FetchRequest { - return FetchRequest(predicate: predicate, sortDescriptor: sortDescriptor, fetchOffset: fetchOffset, fetchLimit: fetchLimit) + return FetchRequest(predicate: predicate, sortDescriptors: sortDescriptors, fetchOffset: fetchOffset, fetchLimit: fetchLimit) } - func request(withSortDescriptor sortDescriptor: NSSortDescriptor) -> FetchRequest { - return FetchRequest(predicate: predicate, sortDescriptor: sortDescriptor, fetchOffset: fetchOffset, fetchLimit: fetchLimit) + func request(withSortDescriptors sortDescriptors: [NSSortDescriptor]) -> FetchRequest { + return FetchRequest(predicate: predicate, sortDescriptors: sortDescriptors, fetchOffset: fetchOffset, fetchLimit: fetchLimit) } } diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index 475ab74..d33ab0e 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -285,7 +285,7 @@ extension CoreDataDBClient: DBClient { performReadTask { context in let fetchRequest = self.fetchRequest(for: coreDataModelType) fetchRequest.predicate = request.predicate - fetchRequest.sortDescriptors = [request.sortDescriptor].compactMap { $0 } + fetchRequest.sortDescriptors = request.sortDescriptors fetchRequest.fetchLimit = request.fetchLimit fetchRequest.fetchOffset = request.fetchOffset do { @@ -405,7 +405,7 @@ extension CoreDataDBClient: DBClient { performReadTaskAndWait { context in let fetchRequest = self.fetchRequest(for: coreDataModelType) fetchRequest.predicate = request.predicate - fetchRequest.sortDescriptors = [request.sortDescriptor].compactMap { $0 } + fetchRequest.sortDescriptors = request.sortDescriptors fetchRequest.fetchLimit = request.fetchLimit fetchRequest.fetchOffset = request.fetchOffset do { diff --git a/DBClient/CoreData/CoreDataObservable.swift b/DBClient/CoreData/CoreDataObservable.swift index 3926aab..d442047 100644 --- a/DBClient/CoreData/CoreDataObservable.swift +++ b/DBClient/CoreData/CoreDataObservable.swift @@ -28,8 +28,8 @@ class CoreDataObservable: RequestObservable { if let predicate = request.predicate { fetchRequest.predicate = predicate } - if let sortDescriptor = request.sortDescriptor { - fetchRequest.sortDescriptors = [sortDescriptor] + if let sortDescriptors = request.sortDescriptors { + fetchRequest.sortDescriptors = sortDescriptors } else { guard let primaryKeyName = coreDataModelType.primaryKeyName else { fatalError("Fetch request shoud have sortDescriptor or core data model need implement primaryKeyName") diff --git a/DBClient/Realm/RealmDBClient.swift b/DBClient/Realm/RealmDBClient.swift index 3496b2f..aa80718 100644 --- a/DBClient/Realm/RealmDBClient.swift +++ b/DBClient/Realm/RealmDBClient.swift @@ -218,8 +218,8 @@ internal extension FetchRequest { func applyTo(realmObjects: Results) -> Results { var objects: Results = realmObjects - if let sortDescriptor = sortDescriptor, let key = sortDescriptor.key { - objects = realmObjects.sorted(byKeyPath: key, ascending: sortDescriptor.ascending) + if let sortDescriptors = sortDescriptors?.compactMap(SortDescriptor.init), !sortDescriptors.isEmpty { + objects = realmObjects.sorted(by: sortDescriptors) } if let predicate = predicate { objects = objects.filter(predicate) @@ -229,6 +229,18 @@ internal extension FetchRequest { } } +private extension SortDescriptor { + + init?(_ descriptor: NSSortDescriptor) { + if let key = descriptor.key { + self = SortDescriptor(keyPath: key, ascending: descriptor.ascending) + } else { + return nil + } + } + +} + private extension Array { func slice(offset: Int, limit: Int) -> [T] { diff --git a/Example/Example/MasterViewController.swift b/Example/Example/MasterViewController.swift index 7347a4b..de99bcf 100644 --- a/Example/Example/MasterViewController.swift +++ b/Example/Example/MasterViewController.swift @@ -18,7 +18,7 @@ final class MasterViewController: UITableViewController, DBClientInjectable { override func viewDidLoad() { super.viewDidLoad() - let observable = dbClient.observable(for: FetchRequest(sortDescriptor: NSSortDescriptor(key: "name", ascending: true))) + let observable = dbClient.observable(for: FetchRequest(sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)])) userChangesObservable = observable observable.observe { [weak self] changeSet in self?.observeChanges(changeSet) From f33c21a0ce177afde485a217bbe729ecabcb8adf Mon Sep 17 00:00:00 2001 From: Denis Zabrod'ko Date: Tue, 2 Jul 2019 16:04:43 +0300 Subject: [PATCH 45/47] [Update] version to 1.4.2 --- DBClient.podspec | 2 +- Example/Example.xcodeproj/project.pbxproj | 11 +++++------ Example/Podfile.lock | 10 +++++----- README.md | 2 +- 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/DBClient.podspec b/DBClient.podspec index 32ba1cc..0705822 100644 --- a/DBClient.podspec +++ b/DBClient.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'DBClient' - s.version = '1.4' + s.version = '1.4.2' s.requires_arc = true s.summary = 'CoreData & Realm wrapper written on Swift' s.homepage = '' diff --git a/Example/Example.xcodeproj/project.pbxproj b/Example/Example.xcodeproj/project.pbxproj index 627386d..742e25d 100644 --- a/Example/Example.xcodeproj/project.pbxproj +++ b/Example/Example.xcodeproj/project.pbxproj @@ -370,7 +370,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-DBClientTests/Pods-DBClientTests-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-DBClientTests/Pods-DBClientTests-frameworks.sh", "${BUILT_PRODUCTS_DIR}/DBClient/DBClient.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", @@ -385,7 +385,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-DBClientTests/Pods-DBClientTests-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-DBClientTests/Pods-DBClientTests-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 5DD0265501157C9DFB18D487 /* [CP] Embed Pods Frameworks */ = { @@ -394,7 +394,7 @@ files = ( ); inputPaths = ( - "${SRCROOT}/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks.sh", + "${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh", "${BUILT_PRODUCTS_DIR}/DBClient/DBClient.framework", "${BUILT_PRODUCTS_DIR}/Realm/Realm.framework", "${BUILT_PRODUCTS_DIR}/RealmSwift/RealmSwift.framework", @@ -409,7 +409,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = "\"${SRCROOT}/Pods/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n"; + shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Example/Pods-Example-frameworks.sh\"\n"; showEnvVarsInLog = 0; }; 7A7AA963D82D9DF854F879D4 /* [CP] Check Pods Manifest.lock */ = { @@ -445,8 +445,7 @@ ); runOnlyForDeploymentPostprocessing = 0; shellPath = /bin/sh; - shellScript = " -"; + shellScript = "\n"; }; BF151D4AF0AD5B6D29F45416 /* [CP] Check Pods Manifest.lock */ = { isa = PBXShellScriptBuildPhase; diff --git a/Example/Podfile.lock b/Example/Podfile.lock index b787e69..65ae243 100644 --- a/Example/Podfile.lock +++ b/Example/Podfile.lock @@ -1,10 +1,10 @@ PODS: - - DBClient/Core (1.4): + - DBClient/Core (1.4.2): - YALResult (= 1.4) - - DBClient/CoreData (1.4): + - DBClient/CoreData (1.4.2): - DBClient/Core - YALResult (= 1.4) - - DBClient/Realm (1.4): + - DBClient/Realm (1.4.2): - DBClient/Core - RealmSwift (~> 3.15.0) - YALResult (= 1.4) @@ -30,11 +30,11 @@ EXTERNAL SOURCES: :path: "../" SPEC CHECKSUMS: - DBClient: e97c5dd333ac354778f17740ea7bcd6e27645ce9 + DBClient: 6833b6f3abb9bbd90dc3289621179f3fad102c0c Realm: 9b834e1be6062f544805252c812348872dc5d4ed RealmSwift: 8a41886f8ab6efef9eb8df97de2f2bb911561a79 YALResult: 26915691cdd19269936336d6f28e1a015c64175e PODFILE CHECKSUM: 01e2cf56f70348c45b9e7248a505591dd86d210b -COCOAPODS: 1.5.3 +COCOAPODS: 1.6.1 diff --git a/README.md b/README.md index 3c6696f..590ac46 100644 --- a/README.md +++ b/README.md @@ -143,7 +143,7 @@ public func upsertManagedObject(in context: NSManagedObjectContext, existedInsta | Version | Swift | Dependencies | iOS | |----------|-------|----------------------------------------|------| -| `1.4` | 5 | RealmSwift 3.15.0, YALResult 1.4 | 10 | +| `1.4.2` | 5 | RealmSwift 3.15.0, YALResult 1.4 | 10 | | `1.3` | 4.2 | RealmSwift 3.11.1, YALResult 1.1 | 10 | | `1.0` | 4.2 | RealmSwift 2.10.1, YALResult 1.0 | 10 | | `0.7` | 4.0 | RealmSwift 2.10.1, BoltsSwift 1.4 | 9 | From db4013721355b53352d04947b783ddd7a2b26b30 Mon Sep 17 00:00:00 2001 From: Navka Sergey Date: Tue, 16 Jun 2020 21:30:12 +0300 Subject: [PATCH 46/47] [fix] update main context after NSBatchDeleteRequest --- DBClient/CoreData/CoreDataDBClient.swift | 24 +++++++++++++++--------- 1 file changed, 15 insertions(+), 9 deletions(-) diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index d33ab0e..670eb04 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -38,7 +38,7 @@ public protocol CoreDataModelConvertible: Stored { } extension NSManagedObject: Stored { - + public static var primaryKeyName: String? { return nil } public var valueOfPrimaryKey: CVarArg? { return nil } @@ -281,7 +281,7 @@ extension CoreDataDBClient: DBClient { public func execute(_ request: FetchRequest, completion: @escaping (Result<[T]>) -> Void) where T: Stored { let coreDataModelType = checkType(T.self) - + performReadTask { context in let fetchRequest = self.fetchRequest(for: coreDataModelType) fetchRequest.predicate = request.predicate @@ -303,7 +303,7 @@ extension CoreDataDBClient: DBClient { /// If appropriate object already exists in DB it will be ignored and nothing will be inserted public func insert(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T: Stored { checkType(T.self) - + performWriteTask { context, savingClosure in var insertedObjects = [T]() let foundObjects = self.find(objects: objects, in: context) @@ -329,7 +329,7 @@ extension CoreDataDBClient: DBClient { /// if there is no such object in db nothing will happened public func update(_ objects: [T], completion: @escaping (Result<[T]>) -> Void) where T: Stored { checkType(T.self) - + performWriteTask { context, savingClosure in var updatedObjects = [T]() @@ -355,7 +355,7 @@ extension CoreDataDBClient: DBClient { /// Update object if it exists or insert new one otherwise public func upsert(_ objects: [T], completion: @escaping (Result<(updated: [T], inserted: [T])>) -> Void) where T: Stored { checkType(T.self) - + performWriteTask { context, savingClosure in var updatedObjects = [T]() var insertedObjects = [T]() @@ -383,7 +383,7 @@ extension CoreDataDBClient: DBClient { /// After all deletes try to save context public func delete(_ objects: [T], completion: @escaping (Result<()>) -> Void) where T: Stored { checkType(T.self) - + performWriteTask { context, savingClosure in let foundObjects = self.find(objects, in: context) foundObjects.forEach { context.delete($0) } @@ -426,7 +426,7 @@ extension CoreDataDBClient: DBClient { checkType(T.self) var result: Result<[T]>! - + performWriteTaskAndWait { context, savingClosure in var insertedObjects = [T]() let foundObjects = self.find(objects: objects, in: context) @@ -506,9 +506,15 @@ extension CoreDataDBClient: DBClient { let fetchRequest = NSFetchRequest(entityName: type.entityName) let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - performWriteTask { context, savingClosure in + performWriteTask { [weak mainContext] context, savingClosure in do { - try context.execute(deleteRequest) + let result = try context.execute(deleteRequest) as? NSBatchDeleteResult + if let objectIDs = result?.result as? [NSManagedObjectID] { + for objectID in objectIDs { + guard let object = mainContext?.object(with: objectID) else { continue } + mainContext?.delete(object) + } + } try savingClosure() completion(.success(())) } catch { From 614aa36865032a881e13c77a4c1de91149f1c685 Mon Sep 17 00:00:00 2001 From: Navka Sergey Date: Tue, 16 Jun 2020 21:44:27 +0300 Subject: [PATCH 47/47] added missed result type --- DBClient/CoreData/CoreDataDBClient.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/DBClient/CoreData/CoreDataDBClient.swift b/DBClient/CoreData/CoreDataDBClient.swift index 670eb04..c8799fb 100644 --- a/DBClient/CoreData/CoreDataDBClient.swift +++ b/DBClient/CoreData/CoreDataDBClient.swift @@ -506,6 +506,7 @@ extension CoreDataDBClient: DBClient { let fetchRequest = NSFetchRequest(entityName: type.entityName) let deleteRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) + deleteRequest.resultType = .resultTypeObjectIDs performWriteTask { [weak mainContext] context, savingClosure in do { let result = try context.execute(deleteRequest) as? NSBatchDeleteResult