additional safety for transactions by splitting between sync and async subclasses

This commit is contained in:
John Rommel Estropia
2015-02-28 10:37:01 +09:00
parent 2da04d2d94
commit 59cd505dc6
7 changed files with 234 additions and 102 deletions

View File

@@ -21,6 +21,8 @@
B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */; }; B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */; };
B5CFF24019FD383100D6DFC4 /* DataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */; }; B5CFF24019FD383100D6DFC4 /* DataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */; };
B5D022661A90CD340070CA63 /* DataStack+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */; }; B5D022661A90CD340070CA63 /* DataStack+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */; };
B5D19BFB1AA14063001D1A99 /* AsynchronousDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D19BFA1AA14063001D1A99 /* AsynchronousDataTransaction.swift */; };
B5D19BFF1AA14351001D1A99 /* SynchronousDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D19BFE1AA14351001D1A99 /* SynchronousDataTransaction.swift */; };
B5D1E22A19FA9E63003B2874 /* PersistentStoreResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22919FA9E63003B2874 /* PersistentStoreResult.swift */; }; B5D1E22A19FA9E63003B2874 /* PersistentStoreResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22919FA9E63003B2874 /* PersistentStoreResult.swift */; };
B5D1E22C19FA9FBC003B2874 /* NSError+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+HardcoreData.swift */; }; B5D1E22C19FA9FBC003B2874 /* NSError+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+HardcoreData.swift */; };
B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; };
@@ -86,6 +88,8 @@
B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+HardcoreData.swift"; sourceTree = "<group>"; }; B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+HardcoreData.swift"; sourceTree = "<group>"; };
B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataTransaction.swift; sourceTree = "<group>"; }; B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataTransaction.swift; sourceTree = "<group>"; };
B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Transaction.swift"; sourceTree = "<group>"; }; B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Transaction.swift"; sourceTree = "<group>"; };
B5D19BFA1AA14063001D1A99 /* AsynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsynchronousDataTransaction.swift; sourceTree = "<group>"; };
B5D19BFE1AA14351001D1A99 /* SynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronousDataTransaction.swift; sourceTree = "<group>"; };
B5D1E22919FA9E63003B2874 /* PersistentStoreResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentStoreResult.swift; sourceTree = "<group>"; }; B5D1E22919FA9E63003B2874 /* PersistentStoreResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentStoreResult.swift; sourceTree = "<group>"; };
B5D1E22B19FA9FBC003B2874 /* NSError+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+HardcoreData.swift"; sourceTree = "<group>"; }; B5D1E22B19FA9FBC003B2874 /* NSError+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+HardcoreData.swift"; sourceTree = "<group>"; };
B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; }; B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
@@ -237,6 +241,8 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */, B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */,
B5D19BFE1AA14351001D1A99 /* SynchronousDataTransaction.swift */,
B5D19BFA1AA14063001D1A99 /* AsynchronousDataTransaction.swift */,
B5CFD36D1A0775F000B7885F /* SaveResult.swift */, B5CFD36D1A0775F000B7885F /* SaveResult.swift */,
B582DF851A98B11B003F09C6 /* HardcoreData+Transaction.swift */, B582DF851A98B11B003F09C6 /* HardcoreData+Transaction.swift */,
B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */, B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */,
@@ -450,8 +456,10 @@
B5F409E91A8B11CE00A228EA /* HardcoreDataLogger.swift in Sources */, B5F409E91A8B11CE00A228EA /* HardcoreDataLogger.swift in Sources */,
B5D8081A1A3495BD00A44484 /* NSObject+HardcoreData.swift in Sources */, B5D8081A1A3495BD00A44484 /* NSObject+HardcoreData.swift in Sources */,
B595CAC41A9A11C1009A397F /* NSManagedObjectContext+Setup.swift in Sources */, B595CAC41A9A11C1009A397F /* NSManagedObjectContext+Setup.swift in Sources */,
B5D19BFF1AA14351001D1A99 /* SynchronousDataTransaction.swift in Sources */,
B5E209E01A0726460089C9D4 /* NSManagedObject+Transaction.swift in Sources */, B5E209E01A0726460089C9D4 /* NSManagedObject+Transaction.swift in Sources */,
B582DF821A98B0E7003F09C6 /* HardcoreData+Querying.swift in Sources */, B582DF821A98B0E7003F09C6 /* HardcoreData+Querying.swift in Sources */,
B5D19BFB1AA14063001D1A99 /* AsynchronousDataTransaction.swift in Sources */,
B595CAC81A9A161B009A397F /* WeakObject.swift in Sources */, B595CAC81A9A161B009A397F /* WeakObject.swift in Sources */,
B5D1E22A19FA9E63003B2874 /* PersistentStoreResult.swift in Sources */, B5D1E22A19FA9E63003B2874 /* PersistentStoreResult.swift in Sources */,
B5F409F11A8B27A600A228EA /* CustomizeQuery.swift in Sources */, B5F409F11A8B27A600A228EA /* CustomizeQuery.swift in Sources */,

View File

@@ -0,0 +1,125 @@
//
// AsynchronousDataTransaction.swift
// HardcoreData
//
// Copyright (c) 2015 John Rommel Estropia
//
// 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.
//
import Foundation
import GCDKit
/**
The AsynchronousDataTransaction provides an interface for NSManagedObject creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from DataStack.performTransaction(_:), or from HardcoreData.performTransaction(_:).
*/
public class AsynchronousDataTransaction: DataTransaction {
// MARK: Public
/**
Saves the transaction changes asynchronously. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:param: completion the block executed after the save completes. Success or failure is reported by the SaveResult argument of the block.
*/
public func commit(completion: (result: SaveResult) -> Void) {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to commit a \(self.dynamicType) outside a transaction queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to commit a \(self.dynamicType) more than once.")
self.isCommitted = true
let semaphore = GCDSemaphore(0)
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
self.result = result
completion(result: result)
semaphore.signal()
}
semaphore.wait()
}
/**
Saves the transaction changes and waits for completion synchronously. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:returns: a SaveResult value indicating success or failure.
*/
public func commitAndWait() {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to commit a \(self.dynamicType) outside a transaction queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to commit a \(self.dynamicType) more than once.")
self.isCommitted = true
self.result = self.context.saveSynchronously()
}
/**
Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made.
:param: 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 performTransactionAndWait(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
return SynchronousDataTransaction(
mainContext: self.context,
queue: self.childTransactionQueue,
closure: closure).performAndWait()
}
// MARK: Internal
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: AsynchronousDataTransaction) -> Void) {
self.closure = closure
super.init(mainContext: mainContext, queue: queue)
}
internal func perform() {
self.transactionQueue.async {
self.closure(transaction: self)
if !self.isCommitted {
HardcoreData.log(.Warning, message: "The closure for the \(self.dynamicType) completed without being committed. All changes made within the transaction were discarded.")
}
}
}
internal func performAndWait() -> SaveResult? {
self.transactionQueue.sync {
self.closure(transaction: self)
if !self.isCommitted {
HardcoreData.log(.Warning, message: "The closure for the \(self.dynamicType) completed without being committed. All changes made within the transaction were discarded.")
}
}
return self.result
}
// MARK: Private
private let closure: (transaction: AsynchronousDataTransaction) -> Void
}

View File

@@ -38,11 +38,11 @@ public extension DataStack {
:param: 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. :param: 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.
*/ */
public func performTransaction(closure: (transaction: DataTransaction) -> Void) { public func performTransaction(closure: (transaction: AsynchronousDataTransaction) -> Void) {
DataTransaction( AsynchronousDataTransaction(
mainContext: self.mainContext, mainContext: self.mainContext,
queue: self.transactionQueue, queue: self.childTransactionQueue,
closure: closure).perform() closure: closure).perform()
} }
@@ -52,11 +52,11 @@ public extension DataStack {
:param: 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. :param: 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 performTransactionAndWait(closure: (transaction: DataTransaction) -> Void) -> SaveResult? { public func performTransactionAndWait(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
return DataTransaction( return SynchronousDataTransaction(
mainContext: self.mainContext, mainContext: self.mainContext,
queue: self.transactionQueue, queue: self.childTransactionQueue,
closure: closure).performAndWait() closure: closure).performAndWait()
} }
} }

View File

@@ -66,7 +66,7 @@ public class DataStack {
/** /**
Initializes a DataStack from an NSManagedObjectModel. Initializes a DataStack from an NSManagedObjectModel.
:param: modelName the name of the "momd" (or xcdatamodeld) file. :param: managedObjectModel the NSManagedObjectModel of the (.xcdatamodeld) model file.
*/ */
public required init(managedObjectModel: NSManagedObjectModel) { public required init(managedObjectModel: NSManagedObjectModel) {
@@ -82,8 +82,6 @@ public class DataStack {
return mapping return mapping
} }
println(self.entityNameMapping)
self.rootSavingContext.parentStack = self self.rootSavingContext.parentStack = self
} }
@@ -268,7 +266,7 @@ public class DataStack {
// MARK: Internal // MARK: Internal
internal let mainContext: NSManagedObjectContext internal let mainContext: NSManagedObjectContext
internal let transactionQueue: GCDQueue = .createSerial("com.hardcoredata.datastack.transactionqueue") internal let childTransactionQueue: GCDQueue = .createSerial("com.hardcoredata.datastack.childtransactionqueue")
internal func entityNameForEntityClass(entityClass: NSManagedObject.Type) -> String? { internal func entityNameForEntityClass(entityClass: NSManagedObject.Type) -> String? {

View File

@@ -33,10 +33,7 @@ import GCDKit
/** /**
The DataTransaction provides an interface for NSManagedObject creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from DataStack.performTransaction(_:), or from HardcoreData.performTransaction(_:). The DataTransaction provides an interface for NSManagedObject creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from DataStack.performTransaction(_:), or from HardcoreData.performTransaction(_:).
*/ */
public final class DataTransaction { public /*abstract*/ class DataTransaction {
// MARK: - Public
// MARK: Object management // MARK: Object management
@@ -48,8 +45,9 @@ public final class DataTransaction {
*/ */
public func create<T: NSManagedObject>(entity: T.Type) -> T { public func create<T: NSManagedObject>(entity: T.Type) -> T {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to create an NSManagedObject outside a transaction queue.") HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to create an entity of type \(entity) outside a transaction queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to create an NSManagedObject from an already committed DataTransaction.") HardcoreData.assert(!self.isCommitted, "Attempted to create an NSManagedObject from an already committed \(self.dynamicType).")
return T.createInContext(self.context) return T.createInContext(self.context)
} }
@@ -61,8 +59,9 @@ public final class DataTransaction {
*/ */
public func fetch<T: NSManagedObject>(object: T) -> T? { public func fetch<T: NSManagedObject>(object: T) -> T? {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to update an NSManagedObject outside a transaction queue.") HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to update an entity of type \(object.dynamicType) outside a transaction queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to update an NSManagedObject from an already committed DataTransaction.") HardcoreData.assert(!self.isCommitted, "Attempted to update an entity of type \(object.dynamicType) from an already committed \(self.dynamicType).")
return object.inContext(self.context) return object.inContext(self.context)
} }
@@ -73,8 +72,9 @@ public final class DataTransaction {
*/ */
public func delete(object: NSManagedObject) { public func delete(object: NSManagedObject) {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to delete an NSManagedObject outside a transaction queue.") HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to delete an entity of type \(object.dynamicType) outside a transaction queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to delete an NSManagedObject from an already committed DataTransaction.") HardcoreData.assert(!self.isCommitted, "Attempted to delete an entity of type \(object.dynamicType) from an already committed \(self.dynamicType).")
object.deleteFromContext() object.deleteFromContext()
} }
@@ -85,69 +85,25 @@ public final class DataTransaction {
*/ */
public func rollback() { public func rollback() {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to rollback a DataTransaction outside a transaction queue.") HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to rollback a \(self.dynamicType) outside a transaction queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to rollback an already committed DataTransaction.") HardcoreData.assert(!self.isCommitted, "Attempted to rollback an already committed \(self.dynamicType).")
self.context.reset() self.context.reset()
} }
/**
Saves the transaction changes asynchronously. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:param: completion the block executed after the save completes. Success or failure is reported by the SaveResult argument of the block. // MARK: Internal
*/
public func commit(completion: (result: SaveResult) -> Void) {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to commit a DataTransaction outside a transaction queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to commit a DataTransaction more than once.")
self.isCommitted = true
let semaphore = GCDSemaphore(0)
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
self.result = result
completion(result: result)
semaphore.signal()
}
semaphore.wait()
}
/**
Saves the transaction changes and waits for completion synchronously. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:returns: a SaveResult value indicating success or failure.
*/
public func commitAndWait() {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to commit a DataTransaction outside a transaction queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to commit a DataTransaction more than once.")
self.isCommitted = true
self.result = self.context.saveSynchronously()
}
/**
Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made.
:param: 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 performTransactionAndWait(closure: (transaction: DataTransaction) -> Void) -> SaveResult? {
return DataTransaction(
mainContext: self.context,
queue: self.childTransactionQueue,
closure: closure).performAndWait()
}
// MARK: - Internal
internal let context: NSManagedObjectContext internal let context: NSManagedObjectContext
internal let transactionQueue: GCDQueue
internal let childTransactionQueue: GCDQueue = .createSerial("com.hardcoredata.datastack.childtransactionqueue")
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: DataTransaction) -> Void) { internal var isCommitted = false
internal var result: SaveResult?
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue) {
self.transactionQueue = queue self.transactionQueue = queue
self.closure = closure
let context = mainContext.temporaryContextInTransaction(nil) let context = mainContext.temporaryContextInTransaction(nil)
self.context = context self.context = context
@@ -155,30 +111,4 @@ public final class DataTransaction {
context.retainsRegisteredObjects = true context.retainsRegisteredObjects = true
context.parentTransaction = self context.parentTransaction = self
} }
internal func perform() {
self.transactionQueue.async {
self.closure(transaction: self)
}
}
internal func performAndWait() -> SaveResult? {
self.transactionQueue.sync {
self.closure(transaction: self)
}
return self.result
}
// MARK: - Private
private var isCommitted = false
private var result: SaveResult?
private let transactionQueue: GCDQueue
private let closure: (transaction: DataTransaction) -> Void
private let childTransactionQueue: GCDQueue = .createSerial("com.hardcoredata.datastack.childtransactionqueue")
} }

View File

@@ -37,7 +37,7 @@ public extension HardcoreData {
:param: 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. :param: 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.
*/ */
public static func performTransaction(closure: (transaction: DataTransaction) -> Void) { public static func performTransaction(closure: (transaction: AsynchronousDataTransaction) -> Void) {
self.defaultStack.performTransaction(closure) self.defaultStack.performTransaction(closure)
} }
@@ -48,7 +48,7 @@ public extension HardcoreData {
:param: 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. :param: 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 performTransactionAndWait(closure: (transaction: DataTransaction) -> Void) -> SaveResult? { public static func performTransactionAndWait(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
return self.defaultStack.performTransactionAndWait(closure) return self.defaultStack.performTransactionAndWait(closure)
} }

View File

@@ -0,0 +1,71 @@
//
// SynchronousDataTransaction.swift
// HardcoreData
//
// Created by John Rommel Estropia on 2015/02/28.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import Foundation
import GCDKit
public class SynchronousDataTransaction: DataTransaction {
// MARK: Public
/**
Saves the transaction changes and waits for completion synchronously. Note that this method should not be used after either the commit(_:) or commitAndWait() method was already called once.
:returns: a SaveResult value indicating success or failure.
*/
public func commitAndWait() {
HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to commit a \(self.dynamicType) outside a transaction queue.")
HardcoreData.assert(!self.isCommitted, "Attempted to commit a \(self.dynamicType) more than once.")
self.isCommitted = true
self.result = self.context.saveSynchronously()
}
/**
Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made.
:param: 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 performTransactionAndWait(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
return SynchronousDataTransaction(
mainContext: self.context,
queue: self.childTransactionQueue,
closure: closure).performAndWait()
}
// MARK: Internal
internal func performAndWait() -> SaveResult? {
self.transactionQueue.sync {
self.closure(transaction: self)
if !self.isCommitted {
HardcoreData.log(.Warning, message: "The closure for the \(self.dynamicType) completed without being committed. All changes made within the transaction were discarded.")
}
}
return self.result
}
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: SynchronousDataTransaction) -> Void) {
self.closure = closure
super.init(mainContext: mainContext, queue: queue)
}
// MARK: - Private
private let closure: (transaction: SynchronousDataTransaction) -> Void
}