From a45c94dd52cb1487a16d0b53814f4d00a4a2e9fa Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sat, 28 Feb 2015 00:55:22 +0900 Subject: [PATCH] allow nested transactions --- HardcoreData/DataStack.swift | 3 +- HardcoreData/DataTransaction.swift | 22 +++++++++++--- HardcoreData/DefaultLogger.swift | 8 ++--- HardcoreData/HardcoreData.swift | 6 ++-- HardcoreData/HardcoreDataLogger.swift | 6 ++-- .../NSManagedObjectContext+Setup.swift | 3 +- .../NSManagedObjectContext+Transaction.swift | 3 +- HardcoreDataTests/HardcoreDataTests.swift | 29 +++++++++++++++++++ Libraries/GCDKit | 2 +- 9 files changed, 62 insertions(+), 20 deletions(-) diff --git a/HardcoreData/DataStack.swift b/HardcoreData/DataStack.swift index ba8f42b..40d23ae 100644 --- a/HardcoreData/DataStack.swift +++ b/HardcoreData/DataStack.swift @@ -73,7 +73,6 @@ public class DataStack { self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator) self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext) - self.transactionQueue = .createSerial("com.hardcoredata.datastack.transactionqueue") self.entityNameMapping = (managedObjectModel.entities as! [NSEntityDescription]).reduce([EntityClassNameType: EntityNameType]()) { (var mapping, entityDescription) in if let entityName = entityDescription.name { @@ -269,7 +268,7 @@ public class DataStack { // MARK: Internal internal let mainContext: NSManagedObjectContext - internal let transactionQueue: GCDQueue; + internal let transactionQueue: GCDQueue = .createSerial("com.hardcoredata.datastack.transactionqueue") internal func entityNameForEntityClass(entityClass: NSManagedObject.Type) -> String? { diff --git a/HardcoreData/DataTransaction.swift b/HardcoreData/DataTransaction.swift index 19dcca5..aae2a01 100644 --- a/HardcoreData/DataTransaction.swift +++ b/HardcoreData/DataTransaction.swift @@ -37,10 +37,6 @@ public final class DataTransaction { // MARK: - Public - /** - The background concurrent context managed by the transaction. - */ - public let context: NSManagedObjectContext // MARK: Object management @@ -129,9 +125,25 @@ public final class DataTransaction { 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 init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: DataTransaction) -> Void) { self.transactionQueue = queue @@ -168,4 +180,6 @@ public final class DataTransaction { private var result: SaveResult? private let transactionQueue: GCDQueue private let closure: (transaction: DataTransaction) -> Void + + private lazy var childTransactionQueue: GCDQueue = .createSerial("com.hardcoredata.datastack.childtransactionqueue") } diff --git a/HardcoreData/DefaultLogger.swift b/HardcoreData/DefaultLogger.swift index 3af499d..1e4b73d 100644 --- a/HardcoreData/DefaultLogger.swift +++ b/HardcoreData/DefaultLogger.swift @@ -30,24 +30,24 @@ import Foundation public final class DefaultLogger: HardcoreDataLogger { - public func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) { + public func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { #if DEBUG Swift.println("[HardcoreData] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") #endif } - public func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) { + public func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { #if DEBUG Swift.println("[HardcoreData] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message): \(error)\n") #endif } - public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) { + public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { #if DEBUG - Swift.assert(condition, message, file: fileName, line: lineNumber) + Swift.assert(condition, message, file: fileName, line: numericCast(lineNumber)) #endif } } diff --git a/HardcoreData/HardcoreData.swift b/HardcoreData/HardcoreData.swift index 0762f0e..048effa 100644 --- a/HardcoreData/HardcoreData.swift +++ b/HardcoreData/HardcoreData.swift @@ -78,7 +78,7 @@ public enum HardcoreData { // MARK: Internal - internal static func log(level: LogLevel, message: String, fileName: StaticString = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) { + internal static func log(level: LogLevel, message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) { self.logger.log( level: level, @@ -91,7 +91,7 @@ public enum HardcoreData { ) } - internal static func handleError(error: NSError, _ message: String, fileName: StaticString = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) { + internal static func handleError(error: NSError, _ message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) { self.logger.handleError( error: error, @@ -101,7 +101,7 @@ public enum HardcoreData { functionName: functionName) } - internal static func assert(@autoclosure condition: () -> Bool, _ message: String, fileName: StaticString = __FILE__, lineNumber: UWord = __LINE__, functionName: StaticString = __FUNCTION__) { + internal static func assert(@autoclosure condition: () -> Bool, _ message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) { self.logger.assert( condition, diff --git a/HardcoreData/HardcoreDataLogger.swift b/HardcoreData/HardcoreDataLogger.swift index 5478919..7a8fe1e 100644 --- a/HardcoreData/HardcoreDataLogger.swift +++ b/HardcoreData/HardcoreDataLogger.swift @@ -41,9 +41,9 @@ public enum LogLevel { public protocol HardcoreDataLogger { - func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) + func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) - func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) + func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) - func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: UWord, functionName: StaticString) + func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) } \ No newline at end of file diff --git a/HardcoreData/NSManagedObjectContext+Setup.swift b/HardcoreData/NSManagedObjectContext+Setup.swift index 538771f..8ccebe9 100644 --- a/HardcoreData/NSManagedObjectContext+Setup.swift +++ b/HardcoreData/NSManagedObjectContext+Setup.swift @@ -41,11 +41,12 @@ internal extension NSManagedObjectContext { return parentContext.parentStack } + return self.getAssociatedObjectForKey(&PropertyKeys.parentStack) } set { - if let parentContext = self.parentContext { + if self.parentContext != nil { return } diff --git a/HardcoreData/NSManagedObjectContext+Transaction.swift b/HardcoreData/NSManagedObjectContext+Transaction.swift index e6dc253..8a504d1 100644 --- a/HardcoreData/NSManagedObjectContext+Transaction.swift +++ b/HardcoreData/NSManagedObjectContext+Transaction.swift @@ -54,7 +54,7 @@ internal extension NSManagedObjectContext { context.parentContext = self context.parentStack = self.parentStack context.setupForHardcoreDataWithContextName("com.hardcoredata.temporarycontext") - context.shouldCascadeSavesToParent = true + context.shouldCascadeSavesToParent = (self.concurrencyType == .MainQueueConcurrencyType) return context } @@ -67,7 +67,6 @@ internal extension NSManagedObjectContext { if !self.hasChanges { - self.reset() return } diff --git a/HardcoreDataTests/HardcoreDataTests.swift b/HardcoreDataTests/HardcoreDataTests.swift index 5fe00c2..c549839 100644 --- a/HardcoreDataTests/HardcoreDataTests.swift +++ b/HardcoreDataTests/HardcoreDataTests.swift @@ -32,6 +32,7 @@ class HardcoreDataTests: XCTestCase { override func setUp() { super.setUp() + NSFileManager.defaultManager().removeItemAtURL(NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL, error: nil) } override func tearDown() { @@ -84,8 +85,35 @@ class HardcoreDataTests: XCTestCase { obj3.testNumber = 90 obj3.testDate = NSDate() + + transaction.performTransactionAndWait { (transaction) -> Void in + + let obj4 = transaction.create(TestEntity2) + obj4.testEntityID = 4 + obj4.testString = "hehehehe" + obj4.testNumber = 80 + obj4.testDate = NSDate() + + let objs4test = transaction.fetchOne( + TestEntity2.self, + Where("testEntityID", isEqualTo: 4), + CustomizeQuery { (fetchRequest) -> Void in + + fetchRequest.includesPendingChanges = true + } + ) + XCTAssertNotNil(objs4test, "objs4test != nil") + // Dont commit1 + } + transaction.commit { (result) -> Void in + let objs4test = HardcoreData.fetchOne( + TestEntity2.self, + Where("testEntityID", isEqualTo: 4) + ) + XCTAssertNil(objs4test, "objs4test == nil") + XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()") switch result { @@ -115,6 +143,7 @@ class HardcoreDataTests: XCTestCase { ) XCTAssertNotNil(objs2, "objs2 != nil") XCTAssertTrue(objs2?.count == 2, "objs2?.count == 2") + print(objs2) transaction.commit { (result) -> Void in diff --git a/Libraries/GCDKit b/Libraries/GCDKit index 2242a85..d8dabc0 160000 --- a/Libraries/GCDKit +++ b/Libraries/GCDKit @@ -1 +1 @@ -Subproject commit 2242a85814d9ee5097df142c73de80f0e2ab3788 +Subproject commit d8dabc024eaa02f786a2a4428247eeefb2c826ff