From 862ef27374d6ec4653b43b055dcce64411ad732b Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 30 Sep 2016 13:28:19 +0900 Subject: [PATCH] version bump, cleanup, unit test --- .travis.yml | 9 +- CoreStore.podspec | 2 +- CoreStore.xcodeproj/project.pbxproj | 20 +++- .../xcschemes/CoreStore OSX.xcscheme | 2 +- .../xcschemes/CoreStore iOS.xcscheme | 8 +- .../xcschemes/CoreStore tvOS.xcscheme | 2 +- .../xcschemes/CoreStore watchOS.xcscheme | 2 +- CoreStoreTests/TransactionTests.swift | 97 +++++++++++++++++++ Sources/Info.plist | 2 +- .../NSManagedObjectContext+Transaction.swift | 6 +- .../SynchronousDataTransaction.swift | 43 ++++---- .../Transactions/UnsafeDataTransaction.swift | 2 +- 12 files changed, 151 insertions(+), 44 deletions(-) diff --git a/.travis.yml b/.travis.yml index 869e0d4..ba39b4f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -12,10 +12,11 @@ env: matrix: - DESTINATION="OS=10.0,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" - DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" - - DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" - - DESTINATION="OS=8.3,name=iPhone 5S" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" - - DESTINATION="OS=8.2,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" - - DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="YES" +# iOS 8 testing currently broken on Xcode 8 +# - DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" +# - DESTINATION="OS=8.3,name=iPhone 5s" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" +# - DESTINATION="OS=8.2,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" +# - DESTINATION="OS=8.1,name=iPhone 4s" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="YES" - DESTINATION="arch=x86_64" SCHEME="CoreStore OSX" SDK=macosx10.12 RUN_TESTS="YES" POD_LINT="NO" - DESTINATION="OS=3.0,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator3.0 RUN_TESTS="NO" POD_LINT="NO" - DESTINATION="OS=2.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator3.0 RUN_TESTS="NO" POD_LINT="NO" diff --git a/CoreStore.podspec b/CoreStore.podspec index 5091e12..3fba484 100644 --- a/CoreStore.podspec +++ b/CoreStore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreStore" - s.version = "2.1.1" + s.version = "2.1.2" s.license = "MIT" s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift" s.homepage = "https://github.com/JohnEstropia/CoreStore" diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index cc811ec..bb1684c 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -1394,7 +1394,7 @@ isa = PBXProject; attributes = { LastSwiftUpdateCheck = 0730; - LastUpgradeCheck = 0700; + LastUpgradeCheck = 0800; ORGANIZATIONNAME = "John Rommel Estropia"; TargetAttributes = { 2F03A52F19C5C6DA005002A5 = { @@ -2096,8 +2096,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -2107,6 +2109,7 @@ ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; + GCC_NO_COMMON_BLOCKS = YES; GCC_OPTIMIZATION_LEVEL = 0; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -2152,8 +2155,10 @@ CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; CLANG_WARN_EMPTY_BODY = YES; CLANG_WARN_ENUM_CONVERSION = YES; + CLANG_WARN_INFINITE_RECURSION = YES; CLANG_WARN_INT_CONVERSION = YES; CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; + CLANG_WARN_SUSPICIOUS_MOVE = YES; CLANG_WARN_UNREACHABLE_CODE = YES; CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; @@ -2163,6 +2168,7 @@ ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; + GCC_NO_COMMON_BLOCKS = YES; GCC_PREPROCESSOR_DEFINITIONS = "USE_FRAMEWORKS=1"; GCC_WARN_64_TO_32_BIT_CONVERSION = YES; GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; @@ -2178,7 +2184,7 @@ PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStore; PRODUCT_NAME = CoreStore; SDKROOT = iphoneos; - SWIFT_OPTIMIZATION_LEVEL = "-O"; + SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; TARGETED_DEVICE_FAMILY = "1,2"; TVOS_DEPLOYMENT_TARGET = 9.0; VALIDATE_PRODUCT = YES; @@ -2192,6 +2198,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -2209,6 +2216,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -2258,6 +2266,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -2277,6 +2286,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=appletvos*]" = ""; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; @@ -2329,7 +2339,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; @@ -2351,7 +2361,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; - CODE_SIGN_IDENTITY = "-"; + CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; @@ -2411,6 +2421,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; DEBUG_INFORMATION_FORMAT = dwarf; DEFINES_MODULE = YES; DYLIB_COMPATIBILITY_VERSION = 1; @@ -2431,6 +2442,7 @@ isa = XCBuildConfiguration; buildSettings = { CLANG_ENABLE_MODULES = YES; + "CODE_SIGN_IDENTITY[sdk=watchos*]" = ""; COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEFINES_MODULE = YES; diff --git a/CoreStore.xcodeproj/xcshareddata/xcschemes/CoreStore OSX.xcscheme b/CoreStore.xcodeproj/xcshareddata/xcschemes/CoreStore OSX.xcscheme index 563c5d8..abd4547 100644 --- a/CoreStore.xcodeproj/xcshareddata/xcschemes/CoreStore OSX.xcscheme +++ b/CoreStore.xcodeproj/xcshareddata/xcschemes/CoreStore OSX.xcscheme @@ -1,6 +1,6 @@ + buildForAnalyzing = "YES"> + buildForAnalyzing = "NO"> Bool in + + XCTAssertEqual(events, 0) + XCTAssertEqual((note.userInfo ?? [:]), NSDictionary()) + defer { + + events += 1 + } + return events == 0 + } + ) + let didInsertObjectExpectation = self.expectationForNotification( + "listMonitor:didInsertObject:toIndexPath:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual(events, 1) + + let userInfo = note.userInfo + XCTAssertNotNil(userInfo) + XCTAssertEqual( + Set(((userInfo as? [String: AnyObject]) ?? [:]).keys), + ["indexPath", "object"] + ) + + let indexPath = userInfo?["indexPath"] as? NSIndexPath + XCTAssertEqual(indexPath?.section, 0) + XCTAssertEqual(indexPath?.row, 0) + + let object = userInfo?["object"] as? TestEntity1 + XCTAssertEqual(object?.testBoolean, NSNumber(bool: true)) + XCTAssertEqual(object?.testNumber, NSNumber(integer: 1)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:1") + defer { + + events += 1 + } + return events == 1 + } + ) + let didChangeExpectation = self.expectationForNotification( + "listMonitorDidChange:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual((note.userInfo ?? [:]), NSDictionary()) + defer { + + events += 1 + } + return events == 2 + } + ) + let saveExpectation = self.expectationWithDescription("save") + stack.beginSynchronous { (transaction) in + + let object = transaction.create(Into(TestEntity1)) + object.testBoolean = NSNumber(bool: true) + object.testNumber = NSNumber(integer: 1) + object.testDecimal = NSDecimalNumber(string: "1") + object.testString = "nil:TestEntity1:1" + + switch transaction.commit() { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + + default: + XCTFail() + } + } + XCTAssertEqual(events, 0) + XCTAssertEqual(monitor.numberOfObjects(), 0) + self.waitAndCheckExpectations() + } + } + @objc dynamic func test_ThatAsynchronousTransactions_CanPerformCRUDs() { diff --git a/Sources/Info.plist b/Sources/Info.plist index 449c7b6..3c29136 100644 --- a/Sources/Info.plist +++ b/Sources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 2.1.2 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/Internal/NSManagedObjectContext+Transaction.swift b/Sources/Internal/NSManagedObjectContext+Transaction.swift index 3d6b4b7..3d17ea1 100644 --- a/Sources/Internal/NSManagedObjectContext+Transaction.swift +++ b/Sources/Internal/NSManagedObjectContext+Transaction.swift @@ -101,7 +101,7 @@ internal extension NSManagedObjectContext { } @nonobjc - internal func saveSynchronously(mergeSynchronously: Bool = true) -> SaveResult { + internal func saveSynchronously(waitForMerge waitForMerge: Bool) -> SaveResult { var result = SaveResult(hasChanges: false) @@ -114,7 +114,7 @@ internal extension NSManagedObjectContext { do { - self.isSavingSynchronously = mergeSynchronously + self.isSavingSynchronously = waitForMerge try self.save() self.isSavingSynchronously = nil } @@ -131,7 +131,7 @@ internal extension NSManagedObjectContext { if let parentContext = self.parentContext where self.shouldCascadeSavesToParent { - switch parentContext.saveSynchronously(mergeSynchronously) { + switch parentContext.saveSynchronously(waitForMerge: waitForMerge) { case .Success: result = SaveResult(hasChanges: true) diff --git a/Sources/Transactions/SynchronousDataTransaction.swift b/Sources/Transactions/SynchronousDataTransaction.swift index 3a3341b..df41dbc 100644 --- a/Sources/Transactions/SynchronousDataTransaction.swift +++ b/Sources/Transactions/SynchronousDataTransaction.swift @@ -38,7 +38,8 @@ import CoreData public final class SynchronousDataTransaction: BaseDataTransaction { /** - Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` method was already called once. + Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once. + - Important: Unlike `SynchronousDataTransaction.commit()`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. - returns: a `SaveResult` containing the success or failure information */ @@ -55,35 +56,33 @@ public final class SynchronousDataTransaction: BaseDataTransaction { self.isCommitted = true - let result = self.context.saveSynchronously() + let result = self.context.saveSynchronously(waitForMerge: true) self.result = result return result } - + /** - Saves the transaction changes and waits for completion synchronously, but merges into the main context asynchronously. This method should not be used after the `commit()` method was already called once. - - This method can be used to avoid potential deadlocks that can arise when a background thread attempts to merge changes into the main context while the main queue is querying from that context. Note that this - introduces a possibility that the main thread can attempt to query for the changes before the asynchronous merge operation has happened. + Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` or `commitAndWait()` method was already called once. + - Important: Unlike `SynchronousDataTransaction.commitAndWait()`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. - returns: a `SaveResult` containing the success or failure information */ public func commit() -> SaveResult { - - CoreStore.assert( - self.transactionQueue.isCurrentExecutionContext(), - "Attempted to commit a \(cs_typeName(self)) outside its designated queue." - ) - CoreStore.assert( - !self.isCommitted, - "Attempted to commit a \(cs_typeName(self)) more than once." - ) - - self.isCommitted = true - - let result = self.context.saveSynchronously(false) - self.result = result - return result + + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to commit a \(cs_typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to commit a \(cs_typeName(self)) more than once." + ) + + self.isCommitted = true + + let result = self.context.saveSynchronously(waitForMerge: false) + self.result = result + return result } /** diff --git a/Sources/Transactions/UnsafeDataTransaction.swift b/Sources/Transactions/UnsafeDataTransaction.swift index b1698cf..2af4b55 100644 --- a/Sources/Transactions/UnsafeDataTransaction.swift +++ b/Sources/Transactions/UnsafeDataTransaction.swift @@ -58,7 +58,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction { */ public func commitAndWait() -> SaveResult { - let result = self.context.saveSynchronously() + let result = self.context.saveSynchronously(waitForMerge: true) self.result = result return result }