undo interface

This commit is contained in:
John Rommel Estropia
2015-12-05 18:21:21 +09:00
parent dec9757dc2
commit 4ce3d5de3c
7 changed files with 82 additions and 55 deletions

View File

@@ -1268,6 +1268,7 @@
2F03A54719C5C6DA005002A5 /* Debug */ = { 2F03A54719C5C6DA005002A5 /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_MODULES = YES;
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
GCC_PREPROCESSOR_DEFINITIONS = ( GCC_PREPROCESSOR_DEFINITIONS = (
"DEBUG=1", "DEBUG=1",
@@ -1277,12 +1278,14 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = CoreStoreTests; PRODUCT_NAME = CoreStoreTests;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
}; };
name = Debug; name = Debug;
}; };
2F03A54819C5C6DA005002A5 /* Release */ = { 2F03A54819C5C6DA005002A5 /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_MODULES = YES;
EMBEDDED_CONTENT_CONTAINS_SWIFT = YES; EMBEDDED_CONTENT_CONTAINS_SWIFT = YES;
INFOPLIST_FILE = CoreStoreTests/Info.plist; INFOPLIST_FILE = CoreStoreTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
@@ -1343,6 +1346,7 @@
B52DD1871BE1F8CD00949AFE /* Debug */ = { B52DD1871BE1F8CD00949AFE /* Debug */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
DEBUG_INFORMATION_FORMAT = dwarf; DEBUG_INFORMATION_FORMAT = dwarf;
@@ -1353,12 +1357,14 @@
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStore; PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStore;
PRODUCT_NAME = CoreStoreTests; PRODUCT_NAME = CoreStoreTests;
SDKROOT = macosx; SDKROOT = macosx;
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
}; };
name = Debug; name = Debug;
}; };
B52DD1881BE1F8CD00949AFE /* Release */ = { B52DD1881BE1F8CD00949AFE /* Release */ = {
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
CLANG_ENABLE_MODULES = YES;
CODE_SIGN_IDENTITY = "-"; CODE_SIGN_IDENTITY = "-";
COMBINE_HIDPI_IMAGES = YES; COMBINE_HIDPI_IMAGES = YES;
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;

View File

@@ -216,8 +216,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
self.closure = closure self.closure = closure
super.init(mainContext: mainContext, queue: queue) super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
self.context.undoManager = nil
} }
internal func perform() { internal func perform() {

View File

@@ -194,26 +194,33 @@ public /*abstract*/ class BaseDataTransaction {
internal let context: NSManagedObjectContext internal let context: NSManagedObjectContext
internal let transactionQueue: GCDQueue internal let transactionQueue: GCDQueue
internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue") internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue")
internal let supportsUndo: Bool
internal let bypassesQueueing: Bool
internal var isCommitted = false internal var isCommitted = false
internal var result: SaveResult? internal var result: SaveResult?
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue) { internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, supportsUndo: Bool, bypassesQueueing: Bool) {
self.transactionQueue = queue
let context = mainContext.temporaryContextInTransactionWithConcurrencyType( let context = mainContext.temporaryContextInTransactionWithConcurrencyType(
queue == .Main queue == .Main
? .MainQueueConcurrencyType ? .MainQueueConcurrencyType
: .PrivateQueueConcurrencyType : .PrivateQueueConcurrencyType
) )
self.transactionQueue = queue
self.context = context self.context = context
self.supportsUndo = supportsUndo
self.bypassesQueueing = bypassesQueueing
context.parentTransaction = self context.parentTransaction = self
} if !supportsUndo {
internal var bypassesQueueing: Bool { context.undoManager = nil
}
return false else if context.undoManager == nil {
context.undoManager = NSUndoManager()
}
} }
} }

View File

@@ -43,25 +43,26 @@ public extension CoreStore {
} }
/** /**
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. 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`. - 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 - 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? { public static func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
return self.defaultStack.beginSynchronous(closure) 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. 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. - 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 @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") @available(*, deprecated=1.3.1, renamed="beginUnsafe")

View File

@@ -50,11 +50,11 @@ public extension DataStack {
} }
/** /**
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. 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`. - 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 - 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? { public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
return SynchronousDataTransaction( 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. 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. - 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 @warn_unused_result
public func beginUnsafe() -> UnsafeDataTransaction { public func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction {
return UnsafeDataTransaction( return UnsafeDataTransaction(
mainContext: self.rootSavingContext, mainContext: self.rootSavingContext,
queue: .createSerial( queue: .createSerial(
"com.coreStore.dataStack.unsafeTransactionQueue", "com.coreStore.dataStack.unsafeTransactionQueue",
targetQueue: .UserInitiated targetQueue: .UserInitiated
) ),
supportsUndo: supportsUndo
) )
} }

View File

@@ -202,6 +202,13 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
// MARK: Internal // 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? { internal func performAndWait() -> SaveResult? {
self.transactionQueue.sync { self.transactionQueue.sync {
@@ -219,14 +226,6 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
return self.result 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 // MARK: Private

View File

@@ -62,6 +62,10 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
*/ */
public func rollback() { public func rollback() {
CoreStore.assert(
self.supportsUndo,
"Attempted to rollback a \(typeName(self)) with Undo support disabled."
)
self.context.rollback() self.context.rollback()
} }
@@ -70,6 +74,10 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
*/ */
public func undo() { public func undo() {
CoreStore.assert(
self.supportsUndo,
"Attempted to undo a \(typeName(self)) with Undo support disabled."
)
self.context.undo() self.context.undo()
} }
@@ -78,30 +86,36 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
*/ */
public func redo() { public func redo() {
CoreStore.assert(
self.supportsUndo,
"Attempted to redo a \(typeName(self)) with Undo support disabled."
)
self.context.redo() 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. 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. - 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 @warn_unused_result
public func beginUnsafe() -> UnsafeDataTransaction { public func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction {
return UnsafeDataTransaction( return UnsafeDataTransaction(
mainContext: self.context, 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. 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: 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 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. - 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 { public var internalContext: NSManagedObjectContext {
return self.context return self.context
@@ -117,9 +131,8 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
// MARK: Internal // 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)
} }
} }