diff --git a/HardcoreData.xcodeproj/project.pbxproj b/HardcoreData.xcodeproj/project.pbxproj index 69f338e..04e87d6 100644 --- a/HardcoreData.xcodeproj/project.pbxproj +++ b/HardcoreData.xcodeproj/project.pbxproj @@ -21,6 +21,8 @@ B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.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 */; }; + 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 */; }; B5D1E22C19FA9FBC003B2874 /* NSError+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+HardcoreData.swift */; }; 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 = ""; }; B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataTransaction.swift; sourceTree = ""; }; B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Transaction.swift"; sourceTree = ""; }; + B5D19BFA1AA14063001D1A99 /* AsynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsynchronousDataTransaction.swift; sourceTree = ""; }; + B5D19BFE1AA14351001D1A99 /* SynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronousDataTransaction.swift; sourceTree = ""; }; B5D1E22919FA9E63003B2874 /* PersistentStoreResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PersistentStoreResult.swift; sourceTree = ""; }; B5D1E22B19FA9FBC003B2874 /* NSError+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+HardcoreData.swift"; sourceTree = ""; }; B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; @@ -237,6 +241,8 @@ isa = PBXGroup; children = ( B5CFF23F19FD383100D6DFC4 /* DataTransaction.swift */, + B5D19BFE1AA14351001D1A99 /* SynchronousDataTransaction.swift */, + B5D19BFA1AA14063001D1A99 /* AsynchronousDataTransaction.swift */, B5CFD36D1A0775F000B7885F /* SaveResult.swift */, B582DF851A98B11B003F09C6 /* HardcoreData+Transaction.swift */, B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */, @@ -450,8 +456,10 @@ B5F409E91A8B11CE00A228EA /* HardcoreDataLogger.swift in Sources */, B5D8081A1A3495BD00A44484 /* NSObject+HardcoreData.swift in Sources */, B595CAC41A9A11C1009A397F /* NSManagedObjectContext+Setup.swift in Sources */, + B5D19BFF1AA14351001D1A99 /* SynchronousDataTransaction.swift in Sources */, B5E209E01A0726460089C9D4 /* NSManagedObject+Transaction.swift in Sources */, B582DF821A98B0E7003F09C6 /* HardcoreData+Querying.swift in Sources */, + B5D19BFB1AA14063001D1A99 /* AsynchronousDataTransaction.swift in Sources */, B595CAC81A9A161B009A397F /* WeakObject.swift in Sources */, B5D1E22A19FA9E63003B2874 /* PersistentStoreResult.swift in Sources */, B5F409F11A8B27A600A228EA /* CustomizeQuery.swift in Sources */, diff --git a/HardcoreData/AsynchronousDataTransaction.swift b/HardcoreData/AsynchronousDataTransaction.swift new file mode 100644 index 0000000..1c2427d --- /dev/null +++ b/HardcoreData/AsynchronousDataTransaction.swift @@ -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 +} diff --git a/HardcoreData/DataStack+Transaction.swift b/HardcoreData/DataStack+Transaction.swift index 258d54c..303fd26 100644 --- a/HardcoreData/DataStack+Transaction.swift +++ b/HardcoreData/DataStack+Transaction.swift @@ -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. */ - public func performTransaction(closure: (transaction: DataTransaction) -> Void) { + public func performTransaction(closure: (transaction: AsynchronousDataTransaction) -> Void) { - DataTransaction( + AsynchronousDataTransaction( mainContext: self.mainContext, - queue: self.transactionQueue, + queue: self.childTransactionQueue, 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. :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, - queue: self.transactionQueue, + queue: self.childTransactionQueue, closure: closure).performAndWait() } } \ No newline at end of file diff --git a/HardcoreData/DataStack.swift b/HardcoreData/DataStack.swift index 40d23ae..bbed98a 100644 --- a/HardcoreData/DataStack.swift +++ b/HardcoreData/DataStack.swift @@ -66,7 +66,7 @@ public class DataStack { /** 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) { @@ -82,8 +82,6 @@ public class DataStack { return mapping } - println(self.entityNameMapping) - self.rootSavingContext.parentStack = self } @@ -268,7 +266,7 @@ public class DataStack { // MARK: Internal 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? { diff --git a/HardcoreData/DataTransaction.swift b/HardcoreData/DataTransaction.swift index 94dbc41..ce9e490 100644 --- a/HardcoreData/DataTransaction.swift +++ b/HardcoreData/DataTransaction.swift @@ -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(_:). */ -public final class DataTransaction { - - // MARK: - Public - +public /*abstract*/ class DataTransaction { // MARK: Object management @@ -48,8 +45,9 @@ public final class DataTransaction { */ public func create(entity: T.Type) -> T { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to create an NSManagedObject outside a transaction queue.") - HardcoreData.assert(!self.isCommitted, "Attempted to create an NSManagedObject from an already committed DataTransaction.") + 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 \(self.dynamicType).") + return T.createInContext(self.context) } @@ -61,8 +59,9 @@ public final class DataTransaction { */ public func fetch(object: T) -> T? { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to update an NSManagedObject outside a transaction queue.") - HardcoreData.assert(!self.isCommitted, "Attempted to update an NSManagedObject from an already committed DataTransaction.") + 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 entity of type \(object.dynamicType) from an already committed \(self.dynamicType).") + return object.inContext(self.context) } @@ -73,8 +72,9 @@ public final class DataTransaction { */ public func delete(object: NSManagedObject) { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to delete an NSManagedObject outside a transaction queue.") - HardcoreData.assert(!self.isCommitted, "Attempted to delete an NSManagedObject from an already committed DataTransaction.") + 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 entity of type \(object.dynamicType) from an already committed \(self.dynamicType).") + object.deleteFromContext() } @@ -85,69 +85,25 @@ public final class DataTransaction { */ public func rollback() { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext() == true, "Attempted to rollback a DataTransaction outside a transaction queue.") - HardcoreData.assert(!self.isCommitted, "Attempted to rollback an already committed DataTransaction.") + 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 \(self.dynamicType).") + 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. - */ - 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 + // MARK: Internal 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.closure = closure let context = mainContext.temporaryContextInTransaction(nil) self.context = context @@ -155,30 +111,4 @@ public final class DataTransaction { context.retainsRegisteredObjects = true 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") } diff --git a/HardcoreData/HardcoreData+Transaction.swift b/HardcoreData/HardcoreData+Transaction.swift index 12f91e5..8fafe92 100644 --- a/HardcoreData/HardcoreData+Transaction.swift +++ b/HardcoreData/HardcoreData+Transaction.swift @@ -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. */ - public static func performTransaction(closure: (transaction: DataTransaction) -> Void) { + public static func performTransaction(closure: (transaction: AsynchronousDataTransaction) -> Void) { 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. :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) } diff --git a/HardcoreData/SynchronousDataTransaction.swift b/HardcoreData/SynchronousDataTransaction.swift new file mode 100644 index 0000000..34033ba --- /dev/null +++ b/HardcoreData/SynchronousDataTransaction.swift @@ -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 +}