From 4ce3d5de3c20267e168fa05be3987ea6639d7391 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 5 Dec 2015 18:21:21 +0900 Subject: [PATCH] undo interface --- CoreStore.xcodeproj/project.pbxproj | 6 +++ .../AsynchronousDataTransaction.swift | 3 +- .../BaseDataTransaction.swift | 23 ++++++---- .../CoreStore+Transaction.swift | 23 +++++----- .../DataStack+Transaction.swift | 24 ++++++----- .../SynchronousDataTransaction.swift | 15 +++---- .../UnsafeDataTransaction.swift | 43 ++++++++++++------- 7 files changed, 82 insertions(+), 55 deletions(-) diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 20d777b..3520012 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -1268,6 +1268,7 @@ 2F03A54719C5C6DA005002A5 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", @@ -1277,12 +1278,14 @@ LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = CoreStoreTests; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; 2F03A54819C5C6DA005002A5 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; INFOPLIST_FILE = CoreStoreTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; @@ -1343,6 +1346,7 @@ B52DD1871BE1F8CD00949AFE /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; DEBUG_INFORMATION_FORMAT = dwarf; @@ -1353,12 +1357,14 @@ PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStore; PRODUCT_NAME = CoreStoreTests; SDKROOT = macosx; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; B52DD1881BE1F8CD00949AFE /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_ENABLE_MODULES = YES; CODE_SIGN_IDENTITY = "-"; COMBINE_HIDPI_IMAGES = YES; COPY_PHASE_STRIP = NO; diff --git a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift index 945cff4..bfcd152 100644 --- a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift @@ -216,8 +216,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { self.closure = closure - super.init(mainContext: mainContext, queue: queue) - self.context.undoManager = nil + super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false) } internal func perform() { diff --git a/CoreStore/Saving and Processing/BaseDataTransaction.swift b/CoreStore/Saving and Processing/BaseDataTransaction.swift index 65934dc..df4bcb7 100644 --- a/CoreStore/Saving and Processing/BaseDataTransaction.swift +++ b/CoreStore/Saving and Processing/BaseDataTransaction.swift @@ -194,26 +194,33 @@ public /*abstract*/ class BaseDataTransaction { internal let context: NSManagedObjectContext internal let transactionQueue: GCDQueue internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue") + internal let supportsUndo: Bool + internal let bypassesQueueing: Bool + internal var isCommitted = false internal var result: SaveResult? - internal init(mainContext: NSManagedObjectContext, queue: GCDQueue) { - - self.transactionQueue = queue + internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, supportsUndo: Bool, bypassesQueueing: Bool) { let context = mainContext.temporaryContextInTransactionWithConcurrencyType( queue == .Main ? .MainQueueConcurrencyType : .PrivateQueueConcurrencyType ) + self.transactionQueue = queue self.context = context + self.supportsUndo = supportsUndo + self.bypassesQueueing = bypassesQueueing context.parentTransaction = self - } - - internal var bypassesQueueing: Bool { - - return false + if !supportsUndo { + + context.undoManager = nil + } + else if context.undoManager == nil { + + context.undoManager = NSUndoManager() + } } } diff --git a/CoreStore/Saving and Processing/CoreStore+Transaction.swift b/CoreStore/Saving and Processing/CoreStore+Transaction.swift index 00fbd38..fb1941f 100644 --- a/CoreStore/Saving and Processing/CoreStore+Transaction.swift +++ b/CoreStore/Saving and Processing/CoreStore+Transaction.swift @@ -43,25 +43,26 @@ public extension CoreStore { } /** - Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. - - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously - */ + Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ public static func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { return self.defaultStack.beginSynchronous(closure) } /** - Using the `defaultStack`, begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. An unsafe transaction object should typically be only used from the main queue. - - - returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made. - */ + Using the `defaultStack`, begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. An unsafe transaction object should typically be only used from the main queue. + + - prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited. + - returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made. + */ @warn_unused_result - public static func beginUnsafe() -> UnsafeDataTransaction { + public static func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction { - return self.defaultStack.beginUnsafe() + return self.defaultStack.beginUnsafe(supportsUndo: supportsUndo) } @available(*, deprecated=1.3.1, renamed="beginUnsafe") diff --git a/CoreStore/Saving and Processing/DataStack+Transaction.swift b/CoreStore/Saving and Processing/DataStack+Transaction.swift index 789b157..53a345f 100644 --- a/CoreStore/Saving and Processing/DataStack+Transaction.swift +++ b/CoreStore/Saving and Processing/DataStack+Transaction.swift @@ -50,11 +50,11 @@ public extension DataStack { } /** - Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. - - - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously - */ + Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. + + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + */ public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { return SynchronousDataTransaction( @@ -64,19 +64,21 @@ public extension DataStack { } /** - Begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. - - - returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made. - */ + Begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. + + - prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited. + - returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made. + */ @warn_unused_result - public func beginUnsafe() -> UnsafeDataTransaction { + public func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction { return UnsafeDataTransaction( mainContext: self.rootSavingContext, queue: .createSerial( "com.coreStore.dataStack.unsafeTransactionQueue", targetQueue: .UserInitiated - ) + ), + supportsUndo: supportsUndo ) } diff --git a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift index 08bcf57..8d55e35 100644 --- a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift @@ -202,6 +202,13 @@ public final class SynchronousDataTransaction: BaseDataTransaction { // MARK: Internal + internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: SynchronousDataTransaction) -> Void) { + + self.closure = closure + + super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false) + } + internal func performAndWait() -> SaveResult? { self.transactionQueue.sync { @@ -219,14 +226,6 @@ public final class SynchronousDataTransaction: BaseDataTransaction { return self.result } - internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: SynchronousDataTransaction) -> Void) { - - self.closure = closure - - super.init(mainContext: mainContext, queue: queue) - self.context.undoManager = nil - } - // MARK: Private diff --git a/CoreStore/Saving and Processing/UnsafeDataTransaction.swift b/CoreStore/Saving and Processing/UnsafeDataTransaction.swift index 3ef3703..f74d2ea 100644 --- a/CoreStore/Saving and Processing/UnsafeDataTransaction.swift +++ b/CoreStore/Saving and Processing/UnsafeDataTransaction.swift @@ -62,6 +62,10 @@ public final class UnsafeDataTransaction: BaseDataTransaction { */ public func rollback() { + CoreStore.assert( + self.supportsUndo, + "Attempted to rollback a \(typeName(self)) with Undo support disabled." + ) self.context.rollback() } @@ -70,6 +74,10 @@ public final class UnsafeDataTransaction: BaseDataTransaction { */ public func undo() { + CoreStore.assert( + self.supportsUndo, + "Attempted to undo a \(typeName(self)) with Undo support disabled." + ) self.context.undo() } @@ -78,30 +86,36 @@ public final class UnsafeDataTransaction: BaseDataTransaction { */ public func redo() { + CoreStore.assert( + self.supportsUndo, + "Attempted to redo a \(typeName(self)) with Undo support disabled." + ) self.context.redo() } /** - Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. - - - returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made. - */ + Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. + + - prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited. + - returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made. + */ @warn_unused_result - public func beginUnsafe() -> UnsafeDataTransaction { + public func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction { return UnsafeDataTransaction( mainContext: self.context, - queue: self.transactionQueue + queue: self.transactionQueue, + supportsUndo: supportsUndo ) } /** - Returns the `NSManagedObjectContext` for this unsafe transaction. Use only for cases where external frameworks need an `NSManagedObjectContext` instance to work with. - - Note that it is the developer's responsibility to ensure the following: - - that the `UnsafeDataTransaction` that owns this context should be strongly referenced and prevented from being deallocated during the context's lifetime - - that all saves will be done either through the `UnsafeDataTransaction`'s `commit(...)` method, or by calling `save()` manually on the context, its parent, and all other ancestor contexts if there are any. - */ + Returns the `NSManagedObjectContext` for this unsafe transaction. Use only for cases where external frameworks need an `NSManagedObjectContext` instance to work with. + + Note that it is the developer's responsibility to ensure the following: + - that the `UnsafeDataTransaction` that owns this context should be strongly referenced and prevented from being deallocated during the context's lifetime + - that all saves will be done either through the `UnsafeDataTransaction`'s `commit(...)` method, or by calling `save()` manually on the context, its parent, and all other ancestor contexts if there are any. + */ public var internalContext: NSManagedObjectContext { return self.context @@ -117,9 +131,8 @@ public final class UnsafeDataTransaction: BaseDataTransaction { // MARK: Internal - internal override var bypassesQueueing: Bool { + internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, supportsUndo: Bool) { - return true + super.init(mainContext: mainContext, queue: queue, supportsUndo: supportsUndo, bypassesQueueing: true) } } -