From 62a741c5bd111c911257265fae52817b64182819 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 May 2015 04:59:37 -0300 Subject: [PATCH 01/17] Only save context if it has changes --- Source/AlecrimCoreData/Classes/Stack.swift | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/Source/AlecrimCoreData/Classes/Stack.swift b/Source/AlecrimCoreData/Classes/Stack.swift index 2580efe..8d76d88 100644 --- a/Source/AlecrimCoreData/Classes/Stack.swift +++ b/Source/AlecrimCoreData/Classes/Stack.swift @@ -142,8 +142,13 @@ extension Stack { var error: NSError? = nil while let c = currentContext { - c.performBlockAndWait { - success = c.save(&error) + if c.hasChanges { + c.performBlockAndWait { + success = c.save(&error) + } + } + else { + success = true } if (!success) { From 67d36c4d713e71c9d37b9f134e6d95566f1f6201 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 May 2015 18:59:05 -0300 Subject: [PATCH 02/17] Better app extension support and some doc on ContextOptions --- .../Classes/ContextOptions.swift | 88 ++++++++++++------- 1 file changed, 57 insertions(+), 31 deletions(-) diff --git a/Source/AlecrimCoreData/Classes/ContextOptions.swift b/Source/AlecrimCoreData/Classes/ContextOptions.swift index a778a0a..748a4b1 100644 --- a/Source/AlecrimCoreData/Classes/ContextOptions.swift +++ b/Source/AlecrimCoreData/Classes/ContextOptions.swift @@ -9,6 +9,27 @@ import Foundation import CoreData +// +// To set context options: +// +// 1) Subclass the (possible generated by ACDGen) Context class +// 2) Create a new instance of ContextOptions inside subclass init +// 3) Set the properties you want to change and pass the created instance +// +// Example: +// +// public final class DataContext: BaseDataContext { +// let contextOptions = ContextOptions(stackType: StackType.SQLite) +// +// // set options here... +// +// super.init(contextOptions: contextOptions) +// } +// +// If you are writing an app extension you may be specially interested in +// "applicationGroupIdentifier", "modelBundle", "managedObjectModelName" and +// "persistentStoreRelativePath" properties. +// public final class ContextOptions { // MARK: - public static properties @@ -35,15 +56,15 @@ public final class ContextOptions { public var modelBundle: NSBundle = NSBundle.mainBundle() // you will have to change this if your xcdatamodeld file is not in the main bundle (in a framework bundle, for example) // MARK: - public properties - managed object model - public var managedObjectModelName: String! // defaults to main bundle name + public var managedObjectModelName: String! // defaults to main bundle name, you will have to set this if you are writing an app extension public private(set) var managedObjectModelURL: NSURL! = nil public private(set) var managedObjectModel: NSManagedObjectModel! = nil - // MARK: - public peroprties - app extensions - public var securityApplicationGroupIdentifier: String? // intented for app extensions use (com.apple.security.application-groups entitlement needed) + // MARK: - public properties - app extensions + public var applicationGroupIdentifier: String? // you will have to set this if you are writing an app extension (com.apple.security.application-groups entitlement needed) // MARK: - public properties - persistent location - public var persistentStoreRelativePath: String! = nil // defaults to main bundle identifier + public var persistentStoreRelativePath: String! = nil // defaults to main bundle identifier + "/CoreData", you will have to set this if you are writing an app extension public var persistentStoreFileName: String! = nil // defaults to managed object model name + ".sqlite" public private(set) var persistentStoreURL: NSURL! = nil @@ -98,42 +119,47 @@ extension ContextOptions { } // local store - if let bundleIdentifier = self.mainBundle.bundleIdentifier { - if self.persistentStoreRelativePath == nil { - self.persistentStoreRelativePath = bundleIdentifier + if self.persistentStoreRelativePath == nil { + self.persistentStoreRelativePath = NSString(format: "%@/%@", self.mainBundle.bundleIdentifier!, "CoreData") as String + } + + // + let fileManager = NSFileManager.defaultManager() + var persistentStoreContainerURL: NSURL? + + // + if let groupIdentifier = self.applicationGroupIdentifier { + // stored in "~/Library/Group Containers/." (this method also creates the directory if it does not yet exist) + persistentStoreContainerURL = fileManager.containerURLForSecurityApplicationGroupIdentifier(groupIdentifier) + persistentStoreContainerURL = persistentStoreContainerURL?.URLByAppendingPathComponent("Library", isDirectory: true) + persistentStoreContainerURL = persistentStoreContainerURL?.URLByAppendingPathComponent("Application Support", isDirectory: true) + } else{ + let urls = fileManager.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask) + persistentStoreContainerURL = urls.last as? NSURL + } + + // + if let persistentStoreRelativePath = self.persistentStoreRelativePath { + persistentStoreContainerURL = persistentStoreContainerURL?.URLByAppendingPathComponent(self.persistentStoreRelativePath, isDirectory: true) + } + + // + if let containerURL = persistentStoreContainerURL { + if self.persistentStoreFileName == nil { + self.persistentStoreFileName = self.managedObjectModelName.stringByAppendingPathExtension("sqlite")! } - let fileManager = NSFileManager.defaultManager() - let persistentStoreContainerURL: NSURL? - - if let securityApplicationGroupIdentifier = self.securityApplicationGroupIdentifier { - // stored in "~/Library/Group Containers/." (this method also creates the directory if it does not yet exist) - persistentStoreContainerURL = fileManager.containerURLForSecurityApplicationGroupIdentifier(securityApplicationGroupIdentifier) - } else{ - let urls = fileManager.URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask) - persistentStoreContainerURL = urls.last as? NSURL - } + self.persistentStoreURL = containerURL.URLByAppendingPathComponent(self.persistentStoreFileName, isDirectory: false) - if let containerURL = persistentStoreContainerURL { - if self.persistentStoreFileName == nil { - self.persistentStoreFileName = self.managedObjectModelName.stringByAppendingPathExtension("sqlite")! - } - - let persistentStoreDirectoryURL = containerURL.URLByAppendingPathComponent(self.persistentStoreRelativePath, isDirectory: true) - self.persistentStoreURL = persistentStoreDirectoryURL.URLByAppendingPathComponent(self.persistentStoreFileName, isDirectory: false) - - if !fileManager.fileExistsAtPath(persistentStoreDirectoryURL.path!) { - fileManager.createDirectoryAtURL(persistentStoreDirectoryURL, withIntermediateDirectories: true, attributes: nil, error: nil) - } + if !fileManager.fileExistsAtPath(containerURL.path!) { + fileManager.createDirectoryAtURL(containerURL, withIntermediateDirectories: true, attributes: nil, error: nil) } } // iCloud if self.ubiquityEnabled { if self.ubiquitousContainerIdentifier == nil { - if let bundleIdentifier = self.mainBundle.bundleIdentifier { - self.ubiquitousContainerIdentifier = NSString(format: "%@.%@", "iCloud", bundleIdentifier) as String - } + self.ubiquitousContainerIdentifier = NSString(format: "%@.%@", "iCloud", self.mainBundle.bundleIdentifier!) as String } if self.ubiquitousContainerIdentifier != nil { From ea9240e5c84ef8528289ebe6cdbcc17461761e33 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 May 2015 19:00:07 -0300 Subject: [PATCH 03/17] Typo --- Source/AlecrimCoreData/Classes/ContextOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/AlecrimCoreData/Classes/ContextOptions.swift b/Source/AlecrimCoreData/Classes/ContextOptions.swift index 748a4b1..c71fee1 100644 --- a/Source/AlecrimCoreData/Classes/ContextOptions.swift +++ b/Source/AlecrimCoreData/Classes/ContextOptions.swift @@ -12,7 +12,7 @@ import CoreData // // To set context options: // -// 1) Subclass the (possible generated by ACDGen) Context class +// 1) Subclass the (possibly generated by ACDGen) Context class // 2) Create a new instance of ContextOptions inside subclass init // 3) Set the properties you want to change and pass the created instance // From 74388209957b3438d1a18690f06b3f8d4f4cdf80 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 May 2015 19:01:02 -0300 Subject: [PATCH 04/17] Comment fix --- Source/AlecrimCoreData/Classes/ContextOptions.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/AlecrimCoreData/Classes/ContextOptions.swift b/Source/AlecrimCoreData/Classes/ContextOptions.swift index c71fee1..fc89146 100644 --- a/Source/AlecrimCoreData/Classes/ContextOptions.swift +++ b/Source/AlecrimCoreData/Classes/ContextOptions.swift @@ -14,7 +14,7 @@ import CoreData // // 1) Subclass the (possibly generated by ACDGen) Context class // 2) Create a new instance of ContextOptions inside subclass init -// 3) Set the properties you want to change and pass the created instance +// 3) Set the properties you want to change and pass the created instance to super // // Example: // From 4cec2d7dae951e13d2d16b48e64f098704b24688 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 May 2015 19:08:25 -0300 Subject: [PATCH 05/17] Fixed sample in ContextOptions --- Source/AlecrimCoreData/Classes/ContextOptions.swift | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Source/AlecrimCoreData/Classes/ContextOptions.swift b/Source/AlecrimCoreData/Classes/ContextOptions.swift index fc89146..18fe974 100644 --- a/Source/AlecrimCoreData/Classes/ContextOptions.swift +++ b/Source/AlecrimCoreData/Classes/ContextOptions.swift @@ -19,11 +19,15 @@ import CoreData // Example: // // public final class DataContext: BaseDataContext { -// let contextOptions = ContextOptions(stackType: StackType.SQLite) // -// // set options here... +// public init?() { +// let contextOptions = ContextOptions(stackType: StackType.SQLite) +// +// // set options here... +// +// super.init(contextOptions: contextOptions) +// } // -// super.init(contextOptions: contextOptions) // } // // If you are writing an app extension you may be specially interested in From 8683406f5d584af4334251112c98328efe42377e Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 May 2015 19:25:03 -0300 Subject: [PATCH 06/17] Added sample for app extensions config --- README.md | 5 +++ Samples/AppExtensionDataContension.swift | 39 ++++++++++++++++++++++++ Samples/EnsemblesDataContext.swift | 6 ++-- Samples/iCloudDataContext.swift | 6 ++-- 4 files changed, 50 insertions(+), 6 deletions(-) create mode 100644 Samples/AppExtensionDataContension.swift diff --git a/README.md b/README.md index 8a9dc4e..2fbdd55 100644 --- a/README.md +++ b/README.md @@ -336,6 +336,11 @@ dataContext.entities.batchUpdate(["modified" : true, "dateModified" : NSDate()]) You can use `ContextOptions` class for a custom configuration. +#### App Extensions + +See `Samples` folder for a configuration example for use in the main app and its extensions. + + #### iCloud Core Data sync See `Samples` folder for a configuration example for iCloud Core Data sync. diff --git a/Samples/AppExtensionDataContension.swift b/Samples/AppExtensionDataContension.swift new file mode 100644 index 0000000..d97fdd9 --- /dev/null +++ b/Samples/AppExtensionDataContension.swift @@ -0,0 +1,39 @@ +// +// AppExtensionDataContension.swift +// +// Created by Vanderlei Martinelli on 2015-05-09. +// + +import Foundation +import AlecrimCoreData + +class DataContext: AlecrimCoreData.Context { + + var people: Table { return Table(context: self) } + var departments: Table { return Table(context: self) } + + // MARK - custom init + + init?() { + let contextOptions = ContextOptions(stackType: .SQLite) + + // only needed if entity class names are different from entity names + contextOptions.entityClassNameSuffix = "Entity" + + // needed as your model probably is not in the main bundle + contextOptions.modelBundle = NSBundle(forClass: DataContext.self) + + // set the managed object model name, usually the same name as the main app name + contextOptions.managedObjectModelName = "MyModelName" + + // must be set to not infer from main bundle + contextOptions.persistentStoreRelativePath = "com.company.MyAppName/CoreData" + + // the same identifier used to group your main app and its extensions + contextOptions.applicationGroupIdentifier = "group.com.company.MyAppName" + + // call super + super.init(contextOptions: contextOptions) + } + +} diff --git a/Samples/EnsemblesDataContext.swift b/Samples/EnsemblesDataContext.swift index 8ef2dbb..26797e5 100644 --- a/Samples/EnsemblesDataContext.swift +++ b/Samples/EnsemblesDataContext.swift @@ -27,12 +27,12 @@ class EnsemblesDataContext: AlecrimCoreData.Context { init?() { let contextOptions = ContextOptions(stackType: .SQLite) - // only needed if model is not in main bundle - contextOptions.modelBundle = NSBundle(forClass: DataContext.self) - // only needed if entity class names are different from entity names contextOptions.entityClassNameSuffix = "Entity" + // only needed if model is not in main bundle + contextOptions.modelBundle = NSBundle(forClass: EnsemblesDataContext.self) + // call super super.init(contextOptions: contextOptions) diff --git a/Samples/iCloudDataContext.swift b/Samples/iCloudDataContext.swift index 93ea2a6..c933946 100644 --- a/Samples/iCloudDataContext.swift +++ b/Samples/iCloudDataContext.swift @@ -17,12 +17,12 @@ class iCloudDataContext: AlecrimCoreData.Context { init?() { let contextOptions = ContextOptions(stackType: .SQLite) - // only needed if model is not in main bundle - contextOptions.modelBundle = NSBundle(forClass: DataContext.self) - // only needed if entity class names are different from entity names contextOptions.entityClassNameSuffix = "Entity" + // only needed if model is not in main bundle + contextOptions.modelBundle = NSBundle(forClass: iCloudDataContext.self) + // enable iCloud Core Data sync contextOptions.ubiquityEnabled = true From 20550511c1bca095e3dc615f08f26b39142660fa Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 May 2015 19:28:43 -0300 Subject: [PATCH 07/17] Minor change --- Samples/iCloudDataContext.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Samples/iCloudDataContext.swift b/Samples/iCloudDataContext.swift index c933946..181a4ff 100644 --- a/Samples/iCloudDataContext.swift +++ b/Samples/iCloudDataContext.swift @@ -22,13 +22,13 @@ class iCloudDataContext: AlecrimCoreData.Context { // only needed if model is not in main bundle contextOptions.modelBundle = NSBundle(forClass: iCloudDataContext.self) - + + // only needed if the identifier is different from default identifier + contextOptions.ubiquitousContainerIdentifier = "iCloud.com.company.MyAppName" + // enable iCloud Core Data sync contextOptions.ubiquityEnabled = true - // only needed if the identifier is different from default identifier - contextOptions.ubiquitousContainerIdentifier = "iCloud.com.company.MyApp" - // call super super.init(contextOptions: contextOptions) } From 425ece64abef14899aa39404d960eb3edbaa6482 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 May 2015 19:30:35 -0300 Subject: [PATCH 08/17] Fixed file name (auto correction typo?) --- ...onDataContension.swift => AppExtensionDataContext.swift} | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) rename Samples/{AppExtensionDataContension.swift => AppExtensionDataContext.swift} (87%) diff --git a/Samples/AppExtensionDataContension.swift b/Samples/AppExtensionDataContext.swift similarity index 87% rename from Samples/AppExtensionDataContension.swift rename to Samples/AppExtensionDataContext.swift index d97fdd9..4c5e594 100644 --- a/Samples/AppExtensionDataContension.swift +++ b/Samples/AppExtensionDataContext.swift @@ -1,5 +1,5 @@ // -// AppExtensionDataContension.swift +// AppExtensionDataContext.swift // // Created by Vanderlei Martinelli on 2015-05-09. // @@ -7,7 +7,7 @@ import Foundation import AlecrimCoreData -class DataContext: AlecrimCoreData.Context { +class AppExtensionDataContext: AlecrimCoreData.Context { var people: Table { return Table(context: self) } var departments: Table { return Table(context: self) } @@ -21,7 +21,7 @@ class DataContext: AlecrimCoreData.Context { contextOptions.entityClassNameSuffix = "Entity" // needed as your model probably is not in the main bundle - contextOptions.modelBundle = NSBundle(forClass: DataContext.self) + contextOptions.modelBundle = NSBundle(forClass: AppExtensionDataContext.self) // set the managed object model name, usually the same name as the main app name contextOptions.managedObjectModelName = "MyModelName" From 76650ebcdc7e28c83871f3b7660e44f90b61625a Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Mon, 11 May 2015 20:26:11 -0300 Subject: [PATCH 09/17] Turned public inManagedObjectContext extension method --- .../AlecrimCoreData/Classes/NSManagedObjectExtensions.swift | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Source/AlecrimCoreData/Classes/NSManagedObjectExtensions.swift b/Source/AlecrimCoreData/Classes/NSManagedObjectExtensions.swift index 88c458e..d17c020 100644 --- a/Source/AlecrimCoreData/Classes/NSManagedObjectExtensions.swift +++ b/Source/AlecrimCoreData/Classes/NSManagedObjectExtensions.swift @@ -15,11 +15,7 @@ extension NSManagedObject { return self.inManagedObjectContext(context.managedObjectContext) } -} - -extension NSManagedObject { - - private func inManagedObjectContext(otherManagedObjectContext: NSManagedObjectContext) -> Self? { + public func inManagedObjectContext(otherManagedObjectContext: NSManagedObjectContext) -> Self? { if self.managedObjectContext == otherManagedObjectContext { return self } From 6d14bd8223fe3df278a6f9ac8e0db09a1af0135e Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 14 May 2015 21:13:30 -0300 Subject: [PATCH 10/17] Fixed background operations in context with custom initializers; a more secure background handling implementation using only one managed background context --- Samples/AppExtensionDataContext.swift | 6 +- Samples/EnsemblesDataContext.swift | 6 +- Samples/iCloudDataContext.swift | 6 +- Source/AlecrimCoreData/Classes/Context.swift | 98 ++++++++----------- .../Classes/ContextOptions.swift | 3 + Source/AlecrimCoreData/Classes/Query.swift | 8 +- Source/AlecrimCoreData/Classes/Stack.swift | 22 +++-- 7 files changed, 70 insertions(+), 79 deletions(-) diff --git a/Samples/AppExtensionDataContext.swift b/Samples/AppExtensionDataContext.swift index 4c5e594..0f828f9 100644 --- a/Samples/AppExtensionDataContext.swift +++ b/Samples/AppExtensionDataContext.swift @@ -14,7 +14,7 @@ class AppExtensionDataContext: AlecrimCoreData.Context { // MARK - custom init - init?() { + convenience init?() { let contextOptions = ContextOptions(stackType: .SQLite) // only needed if entity class names are different from entity names @@ -32,8 +32,8 @@ class AppExtensionDataContext: AlecrimCoreData.Context { // the same identifier used to group your main app and its extensions contextOptions.applicationGroupIdentifier = "group.com.company.MyAppName" - // call super - super.init(contextOptions: contextOptions) + // call designated initializer + self.init(contextOptions: contextOptions) } } diff --git a/Samples/EnsemblesDataContext.swift b/Samples/EnsemblesDataContext.swift index 26797e5..ac2c952 100644 --- a/Samples/EnsemblesDataContext.swift +++ b/Samples/EnsemblesDataContext.swift @@ -24,7 +24,7 @@ class EnsemblesDataContext: AlecrimCoreData.Context { // MARK - custom init - init?() { + convenience init?() { let contextOptions = ContextOptions(stackType: .SQLite) // only needed if entity class names are different from entity names @@ -33,8 +33,8 @@ class EnsemblesDataContext: AlecrimCoreData.Context { // only needed if model is not in main bundle contextOptions.modelBundle = NSBundle(forClass: EnsemblesDataContext.self) - // call super - super.init(contextOptions: contextOptions) + // call designated initializer + self.init(contextOptions: contextOptions) // configure Ensembles self.cloudFileSystem = CDEICloudFileSystem(ubiquityContainerIdentifier: "iCloud.com.company.MyApp") diff --git a/Samples/iCloudDataContext.swift b/Samples/iCloudDataContext.swift index 181a4ff..7f66081 100644 --- a/Samples/iCloudDataContext.swift +++ b/Samples/iCloudDataContext.swift @@ -14,7 +14,7 @@ class iCloudDataContext: AlecrimCoreData.Context { // MARK - custom init - init?() { + convenience init?() { let contextOptions = ContextOptions(stackType: .SQLite) // only needed if entity class names are different from entity names @@ -29,8 +29,8 @@ class iCloudDataContext: AlecrimCoreData.Context { // enable iCloud Core Data sync contextOptions.ubiquityEnabled = true - // call super - super.init(contextOptions: contextOptions) + // call designated initializer + self.init(contextOptions: contextOptions) } } diff --git a/Source/AlecrimCoreData/Classes/Context.swift b/Source/AlecrimCoreData/Classes/Context.swift index b36e02e..26f0e64 100644 --- a/Source/AlecrimCoreData/Classes/Context.swift +++ b/Source/AlecrimCoreData/Classes/Context.swift @@ -11,34 +11,28 @@ import CoreData public class Context { - private(set) internal var contextOptions: ContextOptions - private var stack: Stack! - private(set) public var managedObjectContext: NSManagedObjectContext! // The underlying managed object context + private let stack: Stack! + public let managedObjectContext: NSManagedObjectContext! // The underlying managed object context + private var background: Bool = false + + internal var contextOptions: ContextOptions { return self.stack.contextOptions } - public init?(contextOptions: ContextOptions? = nil) { - self.contextOptions = (contextOptions == nil ? ContextOptions() : contextOptions!) + public required init?(contextOptions: ContextOptions? = nil) { + let stackContextOptions = (contextOptions == nil ? ContextOptions() : contextOptions!) + stackContextOptions.fillEmptyOptions() - if self.contextOptions.filled { - // HAX: (vmartinelli) 2015-04-16 -> if filled == true, this constructor was called from the convenience init below and - // stack and managedObjectContext will be assigned there - self.stack = nil - self.managedObjectContext = nil + var stack = stackContextOptions.__stack + if stack == nil { + stack = Stack(contextOptions: stackContextOptions) + self.managedObjectContext = stack?.mainManagedObjectContext } else { - self.contextOptions.fillEmptyOptions() - - if let stack = Stack(contextOptions: self.contextOptions) { - self.stack = stack - self.managedObjectContext = stack.mainManagedObjectContext - } - else { - self.stack = nil - self.managedObjectContext = nil - - return nil - } + self.managedObjectContext = stack?.backgroundManagedObjectContext + self.background = true + stackContextOptions.__stack = nil } + self.stack = stack } public init?(rootManagedObjectContext: NSManagedObjectContext, mainManagedObjectContext: NSManagedObjectContext) { @@ -52,10 +46,10 @@ public class Context { } if stackType != nil { - self.contextOptions = ContextOptions(stackType: stackType, managedObjectModelName: nil, storeOptions: store.options) - self.contextOptions.fillEmptyOptions(customConfiguration: true) + let stackContextOptions = ContextOptions(stackType: stackType, managedObjectModelName: nil, storeOptions: store.options) + stackContextOptions.fillEmptyOptions(customConfiguration: true) - if let stack = Stack(rootManagedObjectContext: rootManagedObjectContext, mainManagedObjectContext: mainManagedObjectContext, contextOptions: self.contextOptions) { + if let stack = Stack(rootManagedObjectContext: rootManagedObjectContext, mainManagedObjectContext: mainManagedObjectContext, contextOptions: stackContextOptions) { self.stack = stack self.managedObjectContext = stack.mainManagedObjectContext } @@ -67,7 +61,6 @@ public class Context { } } else { - self.contextOptions = ContextOptions() self.stack = nil self.managedObjectContext = nil @@ -75,7 +68,6 @@ public class Context { } } else { - self.contextOptions = ContextOptions() self.stack = nil self.managedObjectContext = nil @@ -83,17 +75,6 @@ public class Context { } } - - // HAX: (vmartinelli) 2015-04-16 -> EXC_BAD_ACCESS if this contructor is not a convenience init - // and a property of inherited Context class is called - private convenience init?(parentContext: Context) { - self.init(contextOptions: parentContext.contextOptions) - - self.contextOptions = parentContext.contextOptions - self.stack = parentContext.stack - self.managedObjectContext = parentContext.stack.createBackgroundManagedObjectContext() - } - } extension Context { @@ -173,9 +154,15 @@ extension Context { internal func executeFetchRequest(fetchRequest: NSFetchRequest, error: NSErrorPointer) -> [AnyObject]? { var objects: [AnyObject]? - self.performAndWait { + if self.background { + // already in "performBlock" objects = self.managedObjectContext.executeFetchRequest(fetchRequest, error: error) } + else { + self.managedObjectContext.performBlockAndWait { + objects = self.managedObjectContext.executeFetchRequest(fetchRequest, error: error) + } + } return objects } @@ -215,23 +202,21 @@ extension Context { } internal func executeBatchUpdateRequestWithEntityDescription(entityDescription: NSEntityDescription, propertiesToUpdate: [NSObject : AnyObject], predicate: NSPredicate, completionClosure: (Int, NSError?) -> Void) { - performInBackground(self) { backgroundContext in - let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription) - batchUpdateRequest.propertiesToUpdate = propertiesToUpdate - batchUpdateRequest.predicate = predicate - batchUpdateRequest.resultType = .UpdatedObjectsCountResultType + let batchUpdateRequest = NSBatchUpdateRequest(entity: entityDescription) + batchUpdateRequest.propertiesToUpdate = propertiesToUpdate + batchUpdateRequest.predicate = predicate + batchUpdateRequest.resultType = .UpdatedObjectsCountResultType + + let moc = self.stack.backgroundManagedObjectContext + moc.performBlock { + var error: NSError? = nil + let batchUpdateResult = moc.executeRequest(batchUpdateRequest, error: &error) as! NSBatchUpdateResult - let moc = backgroundContext.managedObjectContext - moc.performBlock { - var error: NSError? = nil - let batchUpdateResult = moc.executeRequest(batchUpdateRequest, error: &error) as! NSBatchUpdateResult - - if error != nil { - completionClosure(0, error) - } - else { - completionClosure(batchUpdateResult.result as! Int, nil) - } + if error != nil { + completionClosure(0, error) + } + else { + completionClosure(batchUpdateResult.result as! Int, nil) } } } @@ -241,7 +226,8 @@ extension Context { // MARK: - public global functions public func performInBackground(parentContext: T, closure: (T) -> Void) { - let backgroundContext = T(parentContext: parentContext)! + parentContext.contextOptions.__stack = parentContext.stack + let backgroundContext = T(contextOptions: parentContext.contextOptions)! backgroundContext.perform { closure(backgroundContext) diff --git a/Source/AlecrimCoreData/Classes/ContextOptions.swift b/Source/AlecrimCoreData/Classes/ContextOptions.swift index 18fe974..6ec809b 100644 --- a/Source/AlecrimCoreData/Classes/ContextOptions.swift +++ b/Source/AlecrimCoreData/Classes/ContextOptions.swift @@ -83,6 +83,9 @@ public final class ContextOptions { internal private(set) var filled = false private var cachedEntityNames = Dictionary() + // MARK: - internal background context machinery support + internal var __stack: Stack? = nil + // MARK: - init (finally) public init(stackType: StackType = StackType.SQLite, managedObjectModelName: String? = nil, storeOptions: [NSObject : AnyObject]? = nil) { self.stackType = stackType diff --git a/Source/AlecrimCoreData/Classes/Query.swift b/Source/AlecrimCoreData/Classes/Query.swift index 0e6da57..53d7f48 100644 --- a/Source/AlecrimCoreData/Classes/Query.swift +++ b/Source/AlecrimCoreData/Classes/Query.swift @@ -51,12 +51,8 @@ public class Query { } internal func executeFetchRequest(fetchRequest: NSFetchRequest) -> [AnyObject]? { - var objects: [AnyObject]? - - self.context.managedObjectContext.performBlockAndWait { - var executeFetchRequestError: NSError? = nil - objects = self.context.managedObjectContext.executeFetchRequest(fetchRequest, error: &executeFetchRequestError) - } + var executeFetchRequestError: NSError? = nil + let objects = self.context.executeFetchRequest(fetchRequest, error: &executeFetchRequestError) return objects } diff --git a/Source/AlecrimCoreData/Classes/Stack.swift b/Source/AlecrimCoreData/Classes/Stack.swift index 8d76d88..85c0417 100644 --- a/Source/AlecrimCoreData/Classes/Stack.swift +++ b/Source/AlecrimCoreData/Classes/Stack.swift @@ -16,13 +16,13 @@ public enum StackType { internal final class Stack { - private let contextOptions: ContextOptions - + internal let contextOptions: ContextOptions private let coordinator: NSPersistentStoreCoordinator! private let store: NSPersistentStore! - private let rootManagedObjectContext: NSManagedObjectContext! + internal let rootManagedObjectContext: NSManagedObjectContext! internal let mainManagedObjectContext: NSManagedObjectContext! + internal lazy var backgroundManagedObjectContext: NSManagedObjectContext = { self.createBackgroundManagedObjectContext() }() // MARK: - constructors @@ -125,16 +125,22 @@ internal final class Stack { } -// MARK: - internal methods +// MARK: - private methods extension Stack { - - internal func createBackgroundManagedObjectContext() -> NSManagedObjectContext { + + private func createBackgroundManagedObjectContext() -> NSManagedObjectContext { let backgroundContext = StackBackgroundManagedObjectContext(stack: self) return backgroundContext } - + +} + +// MARK: - internal methods + +extension Stack { + internal func saveManagedObjectContext(context: NSManagedObjectContext) -> (Bool, NSError?) { var currentContext: NSManagedObjectContext? = context @@ -264,7 +270,7 @@ extension Stack { private final class StackBackgroundManagedObjectContext: NSManagedObjectContext { - private let stack: Stack + private unowned let stack: Stack private init(stack: Stack) { self.stack = stack From bf5eb5e7967121312f09eb74e3ecea47c6c6de19 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 14 May 2015 21:37:03 -0300 Subject: [PATCH 11/17] Minor changes --- Source/AlecrimCoreData/Classes/Context.swift | 9 +++++---- Source/AlecrimCoreData/Classes/ContextOptions.swift | 2 +- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Source/AlecrimCoreData/Classes/Context.swift b/Source/AlecrimCoreData/Classes/Context.swift index 26f0e64..78e4f90 100644 --- a/Source/AlecrimCoreData/Classes/Context.swift +++ b/Source/AlecrimCoreData/Classes/Context.swift @@ -12,8 +12,9 @@ import CoreData public class Context { private let stack: Stack! - public let managedObjectContext: NSManagedObjectContext! // The underlying managed object context - private var background: Bool = false + public let managedObjectContext: NSManagedObjectContext! // the underlying managed object context + + private var ___background: Bool = false // background context machinery (you did not see it) internal var contextOptions: ContextOptions { return self.stack.contextOptions } @@ -28,7 +29,7 @@ public class Context { } else { self.managedObjectContext = stack?.backgroundManagedObjectContext - self.background = true + self.___background = true stackContextOptions.__stack = nil } @@ -154,7 +155,7 @@ extension Context { internal func executeFetchRequest(fetchRequest: NSFetchRequest, error: NSErrorPointer) -> [AnyObject]? { var objects: [AnyObject]? - if self.background { + if self.___background { // already in "performBlock" objects = self.managedObjectContext.executeFetchRequest(fetchRequest, error: error) } diff --git a/Source/AlecrimCoreData/Classes/ContextOptions.swift b/Source/AlecrimCoreData/Classes/ContextOptions.swift index 6ec809b..540f3e6 100644 --- a/Source/AlecrimCoreData/Classes/ContextOptions.swift +++ b/Source/AlecrimCoreData/Classes/ContextOptions.swift @@ -83,7 +83,7 @@ public final class ContextOptions { internal private(set) var filled = false private var cachedEntityNames = Dictionary() - // MARK: - internal background context machinery support + // MARK: - internal properties - background context machinery (you did not see it) internal var __stack: Stack? = nil // MARK: - init (finally) From 3874d3d12dbbf9e2f2a2bdca13c920678c101bde Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 14 May 2015 21:39:00 -0300 Subject: [PATCH 12/17] Minor changes --- Source/AlecrimCoreData/Classes/Context.swift | 6 +++--- Source/AlecrimCoreData/Classes/ContextOptions.swift | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Source/AlecrimCoreData/Classes/Context.swift b/Source/AlecrimCoreData/Classes/Context.swift index 78e4f90..9c8a4ee 100644 --- a/Source/AlecrimCoreData/Classes/Context.swift +++ b/Source/AlecrimCoreData/Classes/Context.swift @@ -22,7 +22,7 @@ public class Context { let stackContextOptions = (contextOptions == nil ? ContextOptions() : contextOptions!) stackContextOptions.fillEmptyOptions() - var stack = stackContextOptions.__stack + var stack = stackContextOptions.___stack if stack == nil { stack = Stack(contextOptions: stackContextOptions) self.managedObjectContext = stack?.mainManagedObjectContext @@ -30,7 +30,7 @@ public class Context { else { self.managedObjectContext = stack?.backgroundManagedObjectContext self.___background = true - stackContextOptions.__stack = nil + stackContextOptions.___stack = nil } self.stack = stack @@ -227,7 +227,7 @@ extension Context { // MARK: - public global functions public func performInBackground(parentContext: T, closure: (T) -> Void) { - parentContext.contextOptions.__stack = parentContext.stack + parentContext.contextOptions.___stack = parentContext.stack let backgroundContext = T(contextOptions: parentContext.contextOptions)! backgroundContext.perform { diff --git a/Source/AlecrimCoreData/Classes/ContextOptions.swift b/Source/AlecrimCoreData/Classes/ContextOptions.swift index 540f3e6..c51e6e9 100644 --- a/Source/AlecrimCoreData/Classes/ContextOptions.swift +++ b/Source/AlecrimCoreData/Classes/ContextOptions.swift @@ -84,7 +84,7 @@ public final class ContextOptions { private var cachedEntityNames = Dictionary() // MARK: - internal properties - background context machinery (you did not see it) - internal var __stack: Stack? = nil + internal var ___stack: Stack? = nil // MARK: - init (finally) public init(stackType: StackType = StackType.SQLite, managedObjectModelName: String? = nil, storeOptions: [NSObject : AnyObject]? = nil) { From d6922386898e01123fb3f45e61b88eb83102abdb Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 14 May 2015 21:48:19 -0300 Subject: [PATCH 13/17] Changed fetchAsync completion dispatch --- Source/AlecrimCoreData/Classes/Table.swift | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Source/AlecrimCoreData/Classes/Table.swift b/Source/AlecrimCoreData/Classes/Table.swift index 0467473..2d02c5f 100644 --- a/Source/AlecrimCoreData/Classes/Table.swift +++ b/Source/AlecrimCoreData/Classes/Table.swift @@ -280,9 +280,7 @@ extension Table { public func fetchAsync(completionClosure: ([T]!, NSError?) -> Void) -> NSProgress { return self.context.executeAsynchronousFetchRequestWithFetchRequest(self.toFetchRequest()) { objects, error in - dispatch_async(dispatch_get_main_queue()) { - completionClosure(objects as? [T], error) - } + completionClosure(objects as? [T], error) } } From 93cb2f47e57c4594d05a753746b9aea9a7fd3d1d Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 16 May 2015 04:41:48 -0300 Subject: [PATCH 14/17] Sometimes you really need a new background managed object context --- Source/AlecrimCoreData/Classes/Context.swift | 15 ++++++++++++++- .../AlecrimCoreData/Classes/ContextOptions.swift | 1 + Source/AlecrimCoreData/Classes/Stack.swift | 12 +++--------- 3 files changed, 18 insertions(+), 10 deletions(-) diff --git a/Source/AlecrimCoreData/Classes/Context.swift b/Source/AlecrimCoreData/Classes/Context.swift index 9c8a4ee..3fabfdf 100644 --- a/Source/AlecrimCoreData/Classes/Context.swift +++ b/Source/AlecrimCoreData/Classes/Context.swift @@ -28,7 +28,14 @@ public class Context { self.managedObjectContext = stack?.mainManagedObjectContext } else { - self.managedObjectContext = stack?.backgroundManagedObjectContext + if stackContextOptions.___stackUsesNewBackgroundManagedObjectContext { + self.managedObjectContext = stack?.createBackgroundManagedObjectContext() + stackContextOptions.___stackUsesNewBackgroundManagedObjectContext = false + } + else { + self.managedObjectContext = stack?.backgroundManagedObjectContext + } + self.___background = true stackContextOptions.___stack = nil } @@ -227,10 +234,16 @@ extension Context { // MARK: - public global functions public func performInBackground(parentContext: T, closure: (T) -> Void) { + performInBackground(parentContext, false, closure) +} + +public func performInBackground(parentContext: T, createNewBackgroundManagedObjectContext: Bool, closure: (T) -> Void) { parentContext.contextOptions.___stack = parentContext.stack + parentContext.contextOptions.___stackUsesNewBackgroundManagedObjectContext = createNewBackgroundManagedObjectContext let backgroundContext = T(contextOptions: parentContext.contextOptions)! backgroundContext.perform { closure(backgroundContext) } } + diff --git a/Source/AlecrimCoreData/Classes/ContextOptions.swift b/Source/AlecrimCoreData/Classes/ContextOptions.swift index c51e6e9..7804ba2 100644 --- a/Source/AlecrimCoreData/Classes/ContextOptions.swift +++ b/Source/AlecrimCoreData/Classes/ContextOptions.swift @@ -85,6 +85,7 @@ public final class ContextOptions { // MARK: - internal properties - background context machinery (you did not see it) internal var ___stack: Stack? = nil + internal var ___stackUsesNewBackgroundManagedObjectContext = false // MARK: - init (finally) public init(stackType: StackType = StackType.SQLite, managedObjectModelName: String? = nil, storeOptions: [NSObject : AnyObject]? = nil) { diff --git a/Source/AlecrimCoreData/Classes/Stack.swift b/Source/AlecrimCoreData/Classes/Stack.swift index 85c0417..4742de7 100644 --- a/Source/AlecrimCoreData/Classes/Stack.swift +++ b/Source/AlecrimCoreData/Classes/Stack.swift @@ -125,22 +125,16 @@ internal final class Stack { } -// MARK: - private methods +// MARK: - internal methods extension Stack { - - private func createBackgroundManagedObjectContext() -> NSManagedObjectContext { + + internal func createBackgroundManagedObjectContext() -> NSManagedObjectContext { let backgroundContext = StackBackgroundManagedObjectContext(stack: self) return backgroundContext } -} - -// MARK: - internal methods - -extension Stack { - internal func saveManagedObjectContext(context: NSManagedObjectContext) -> (Bool, NSError?) { var currentContext: NSManagedObjectContext? = context From b766aca8ec04af4b3568a9ec0b93e3f1822cda37 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 16 May 2015 05:50:08 -0300 Subject: [PATCH 15/17] Reset the common background managed object context before executing closure in its queue --- Source/AlecrimCoreData/Classes/Context.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Source/AlecrimCoreData/Classes/Context.swift b/Source/AlecrimCoreData/Classes/Context.swift index 3fabfdf..7fd828c 100644 --- a/Source/AlecrimCoreData/Classes/Context.swift +++ b/Source/AlecrimCoreData/Classes/Context.swift @@ -243,6 +243,10 @@ public func performInBackground(parentContext: T, createNewBackgroun let backgroundContext = T(contextOptions: parentContext.contextOptions)! backgroundContext.perform { + if !createNewBackgroundManagedObjectContext { + backgroundContext.managedObjectContext.reset() + } + closure(backgroundContext) } } From cda23bba8d93e9e71386b774ce414fa33591199f Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Tue, 19 May 2015 03:32:06 -0300 Subject: [PATCH 16/17] Minor change to save method --- Source/AlecrimCoreData/Classes/Context.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/AlecrimCoreData/Classes/Context.swift b/Source/AlecrimCoreData/Classes/Context.swift index 7fd828c..56459db 100644 --- a/Source/AlecrimCoreData/Classes/Context.swift +++ b/Source/AlecrimCoreData/Classes/Context.swift @@ -87,7 +87,7 @@ public class Context { extension Context { - public func save() -> (Bool, NSError?) { + public func save() -> (success: Bool, error: NSError?) { return self.stack.saveManagedObjectContext(self.managedObjectContext) } From c95aee3cdd3befeca6da1496a9a50e39f8a64e81 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Wed, 20 May 2015 21:04:05 -0300 Subject: [PATCH 17/17] Updated README and pod spec file --- AlecrimCoreData.podspec | 2 +- README.md | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/AlecrimCoreData.podspec b/AlecrimCoreData.podspec index 37b5473..8e8481a 100644 --- a/AlecrimCoreData.podspec +++ b/AlecrimCoreData.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "AlecrimCoreData" - s.version = "3.0-beta.8" + s.version = "3.0" s.summary = "A framework to easily access Core Data objects in Swift." s.homepage = "https://github.com/Alecrim/AlecrimCoreData" diff --git a/README.md b/README.md index 2fbdd55..b7e85de 100644 --- a/README.md +++ b/README.md @@ -376,7 +376,7 @@ source 'https://github.com/CocoaPods/Specs.git' platform :ios, '8.0' use_frameworks! -pod 'AlecrimCoreData', '~> 3.0-beta.8’ +pod 'AlecrimCoreData', '~> 3.0' ``` Then, run the following command: @@ -404,11 +404,11 @@ If you want to contribute, please feel free to fork the repository and send pull ### Version History -- 3.0 - Swift framework; added attributes support and many other improvements -- 2.1 - Swift framework; added CocoaPods and Carthage support -- 2.0 - Swift framework; first public release as open source -- 1.1 - Objective-C framework; private Alecrim team use -- 1.0 - Objective-C framework; private Alecrim team use +- 3.0 - Swift framework: added attributes support and many other improvements +- 2.1 - Swift framework: added CocoaPods and Carthage support +- 2.0 - Swift framework: first public release as open source +- 1.1 - Objective-C framework: private Alecrim team use +- 1.0 - Objective-C framework: private Alecrim team use ---