From 3a8348d0b19524d1131b82ec1627be4672da6fe0 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 24 May 2018 04:29:24 -0300 Subject: [PATCH 01/19] Updated convenience persistent store URL related methods --- .../PersistentContainerType.swift | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift index 5b93b2d..e54c8cb 100644 --- a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift @@ -43,17 +43,16 @@ extension PersistentContainerType { extension PersistentContainerType { - public static func persistentStoreURL(withName name: String? = nil, in bundle: Bundle? = nil) throws -> URL { + public static func persistentStoreURL(withName name: String? = nil, inPath path: String? = nil) throws -> URL { guard let applicationSupportURL = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask).last else { throw PersistentContainerError.applicationSupportDirectoryNotFound } - let bundle = bundle ?? Bundle.main - let bundleLastPathComponent = bundle.bundleURL.deletingPathExtension().lastPathComponent - let name = name ?? bundleLastPathComponent + let name = name ?? Bundle.main.bundleURL.deletingPathExtension().lastPathComponent + let path = path ?? name let persistentStoreURL = applicationSupportURL - .appendingPathComponent(bundleLastPathComponent, isDirectory: true) + .appendingPathComponent(path, isDirectory: true) .appendingPathComponent("CoreData", isDirectory: true) .appendingPathComponent(name, isDirectory: false) .appendingPathExtension("sqlite") @@ -61,19 +60,17 @@ extension PersistentContainerType { return persistentStoreURL } - public static func persistentStoreURL(withName name: String? = nil, forSecurityApplicationGroupIdentifier applicationGroupIdentifier: String, in bundle: Bundle? = nil) throws -> URL { + public static func persistentStoreURL(withName name: String, inPath path: String? = nil, forSecurityApplicationGroupIdentifier applicationGroupIdentifier: String) throws -> URL { guard let containerURL = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: applicationGroupIdentifier) else { throw PersistentContainerError.invalidGroupContainerURL } - let bundle = bundle ?? Bundle.main - let bundleLastPathComponent = bundle.bundleURL.deletingPathExtension().lastPathComponent - let name = name ?? bundleLastPathComponent + let path = path ?? name let persistentStoreURL = containerURL .appendingPathComponent("Library", isDirectory: true) .appendingPathComponent("Application Support", isDirectory: true) - .appendingPathComponent(bundleLastPathComponent, isDirectory: true) + .appendingPathComponent(path, isDirectory: true) .appendingPathComponent("CoreData", isDirectory: true) .appendingPathComponent(name, isDirectory: false) .appendingPathExtension("sqlite") From c58d758c604c94502d5167b84c886496fbb459c3 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Wed, 6 Jun 2018 08:26:29 -0300 Subject: [PATCH 02/19] Minor change to how (deprecated) ubiquitous changes are merged --- .../Core/Persistent Container/PersistentContainer.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift index f825ccd..84b8aa9 100644 --- a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift @@ -71,7 +71,7 @@ open class PersistentContainer: NSPersistentContainer { return } - context.perform { + context.performAndWait { context.mergeChanges(fromContextDidSave: notification) } } From fae8b5b57d9fe9da510c8bc4f2f7ddce5276b594 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Wed, 6 Jun 2018 08:38:58 -0300 Subject: [PATCH 03/19] Added `persistentStoreDescriptionOptions` to persistent container initialiser --- .../CustomPersistentContainer.swift | 4 ++-- .../Persistent Container/PersistentContainer.swift | 11 +++++++++-- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift index 55ba6b3..cb3ece8 100644 --- a/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift +++ b/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift @@ -66,8 +66,8 @@ open class CustomPersistentContainer { try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), ubiquitousConfiguration: nil) } - public init(storageType: PersistentContainerStorageType = .disk, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { - self.rawValue = try HelperPersistentContainer(storageType: storageType, managedObjectModel: managedObjectModel, persistentStoreURL: persistentStoreURL, ubiquitousConfiguration: ubiquitousConfiguration) + public init(storageType: PersistentContainerStorageType = .disk, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, persistentStoreDescriptionOptions: [String : NSObject]? = nil, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { + self.rawValue = try HelperPersistentContainer(storageType: storageType, managedObjectModel: managedObjectModel, persistentStoreURL: persistentStoreURL, persistentStoreDescriptionOptions: persistentStoreDescriptionOptions, ubiquitousConfiguration: ubiquitousConfiguration) } // MARK: - diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift index 84b8aa9..7cbabbc 100644 --- a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift @@ -22,7 +22,7 @@ open class PersistentContainer: NSPersistentContainer { try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), ubiquitousConfiguration: nil) } - public init(storageType: PersistentContainerStorageType, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { + public init(storageType: PersistentContainerStorageType, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, persistentStoreDescriptionOptions: [String : NSObject]? = nil, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { // let name = persistentStoreURL.deletingPathExtension().lastPathComponent super.init(name: name, managedObjectModel: managedObjectModel) @@ -38,12 +38,19 @@ open class PersistentContainer: NSPersistentContainer { // let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreURL) - + + // persistentStoreDescription.type = (storageType == .disk ? NSSQLiteStoreType : NSInMemoryStoreType) persistentStoreDescription.shouldAddStoreAsynchronously = false persistentStoreDescription.shouldInferMappingModelAutomatically = true persistentStoreDescription.shouldMigrateStoreAutomatically = true + + // a change for configuring options (such `NSPersistentHistoryTrackingKey`, for example) + persistentStoreDescriptionOptions?.forEach { + persistentStoreDescription.setOption($0.value, forKey: $0.key) + } + // deprecated ubiquitous support #if os(macOS) || os(iOS) if let ubiquitousConfiguration = ubiquitousConfiguration { persistentStoreDescription.setOption(ubiquitousConfiguration.containerIdentifier as NSString, forKey: NSPersistentStoreUbiquitousContainerIdentifierKey) From 7594cdb6b15a8e5d6251126d30084d721bc854e5 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Wed, 6 Jun 2018 08:45:55 -0300 Subject: [PATCH 04/19] Minor changes --- .../Core/Persistent Container/PersistentContainer.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift index 7cbabbc..ba5fc5f 100644 --- a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift @@ -19,7 +19,7 @@ open class PersistentContainer: NSPersistentContainer { // MARK: - public convenience init() { - try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), ubiquitousConfiguration: nil) + try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), persistentStoreDescriptionOptions: nil, ubiquitousConfiguration: nil) } public init(storageType: PersistentContainerStorageType, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, persistentStoreDescriptionOptions: [String : NSObject]? = nil, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { @@ -45,7 +45,7 @@ open class PersistentContainer: NSPersistentContainer { persistentStoreDescription.shouldInferMappingModelAutomatically = true persistentStoreDescription.shouldMigrateStoreAutomatically = true - // a change for configuring options (such `NSPersistentHistoryTrackingKey`, for example) + // a chance for configuring options (such `NSPersistentHistoryTrackingKey`, for example) persistentStoreDescriptionOptions?.forEach { persistentStoreDescription.setOption($0.value, forKey: $0.key) } From ca416369e2691635fd5cb33b258d0d4b459bdef5 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Wed, 6 Jun 2018 22:55:06 -0300 Subject: [PATCH 05/19] Enabled more persistent container customisations; added a default `backgrouncContext` to persistent containers --- .../CustomPersistentContainer.swift | 91 +++++++------- .../PersistentContainer.swift | 116 ++++++++++++------ 2 files changed, 119 insertions(+), 88 deletions(-) diff --git a/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift index cb3ece8..1667b58 100644 --- a/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift +++ b/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift @@ -12,49 +12,8 @@ import CoreData open class CustomPersistentContainer { // MARK: - - - fileprivate final class HelperPersistentContainer: PersistentContainer { - - private lazy var _viewContext: NSManagedObjectContext = { - let context = Context(concurrencyType: .mainQueueConcurrencyType) - - context.persistentStoreCoordinator = self.persistentStoreCoordinator - - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - return context - }() - - fileprivate override var viewContext: NSManagedObjectContext { return self._viewContext } - - fileprivate override func newBackgroundContext() -> NSManagedObjectContext { - let context = Context(concurrencyType: .privateQueueConcurrencyType) - - context.persistentStoreCoordinator = self.persistentStoreCoordinator - - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - return context - } - - fileprivate override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { - super.performBackgroundTask { context in - // - context.persistentStoreCoordinator = self.persistentStoreCoordinator - - // - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - // - block(context) - } - } - - } - + + public private(set) lazy var backgroundContext: Context = self.newBackgroundContext() // MARK: - @@ -62,12 +21,17 @@ open class CustomPersistentContainer { // MARK: - - public convenience init() { - try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), ubiquitousConfiguration: nil) + /// Use caution when using this initializer. + public init(name: String? = nil) { + self.rawValue = HelperPersistentContainer(name: name) } - - public init(storageType: PersistentContainerStorageType = .disk, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, persistentStoreDescriptionOptions: [String : NSObject]? = nil, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { - self.rawValue = try HelperPersistentContainer(storageType: storageType, managedObjectModel: managedObjectModel, persistentStoreURL: persistentStoreURL, persistentStoreDescriptionOptions: persistentStoreDescriptionOptions, ubiquitousConfiguration: ubiquitousConfiguration) + + public init(name: String? = nil, managedObjectModel: NSManagedObjectModel, storageType: PersistentContainerStorageType, persistentStoreURL: URL, persistentStoreDescriptionOptions: [String : NSObject]? = nil, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { + self.rawValue = try HelperPersistentContainer(name: name, managedObjectModel: managedObjectModel, storageType: storageType, persistentStoreURL: persistentStoreURL, persistentStoreDescriptionOptions: persistentStoreDescriptionOptions, ubiquitousConfiguration: ubiquitousConfiguration) + } + + public init(name: String, managedObjectModel: NSManagedObjectModel, persistentStoreDescription: NSPersistentStoreDescription, completionHandler: @escaping (NSPersistentContainer, NSPersistentStoreDescription, Error?) -> Void) throws { + self.rawValue = try HelperPersistentContainer(name: name, managedObjectModel: managedObjectModel, persistentStoreDescription: persistentStoreDescription, completionHandler: completionHandler) } // MARK: - @@ -87,3 +51,34 @@ open class CustomPersistentContainer { } } + +// MARK: - + +extension CustomPersistentContainer { + + fileprivate final class HelperPersistentContainer: PersistentContainer { + + private lazy var _viewContext: NSManagedObjectContext = { + let context = Context(concurrencyType: .mainQueueConcurrencyType) + self.configureManagedObjectContext(context) + + return context + }() + + fileprivate override var viewContext: NSManagedObjectContext { return self._viewContext } + + fileprivate override func newBackgroundContext() -> NSManagedObjectContext { + let context = Context(concurrencyType: .privateQueueConcurrencyType) + self.configureManagedObjectContext(context) + + return context + } + + fileprivate override func configureManagedObjectContext(_ context: NSManagedObjectContext) { + context.persistentStoreCoordinator = self.persistentStoreCoordinator + super.configureManagedObjectContext(context) + } + + } + +} diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift index ba5fc5f..e915912 100644 --- a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift @@ -10,22 +10,27 @@ import Foundation import CoreData @objc(ALCPersistentContainer) -open class PersistentContainer: NSPersistentContainer { +open class PersistentContainer: BasePersistentContainer { // MARK: - - private var didImportUbiquitousContentNotificationObserver: NSObjectProtocol? + public private(set) lazy var backgroundContext: ManagedObjectContext = self.newBackgroundContext() // MARK: - - public convenience init() { - try! self.init(storageType: .disk, managedObjectModel: type(of: self).managedObjectModel(), persistentStoreURL: type(of: self).persistentStoreURL(), persistentStoreDescriptionOptions: nil, ubiquitousConfiguration: nil) + fileprivate var didImportUbiquitousContentNotificationObserver: NSObjectProtocol? + + + // MARK: - + + /// Use caution when using this initializer. + public convenience init(name: String? = nil) { + try! self.init(name: name, managedObjectModel: type(of: self).managedObjectModel(), storageType: .disk, persistentStoreURL: try! type(of: self).persistentStoreURL(), persistentStoreDescriptionOptions: nil, ubiquitousConfiguration: nil) } - public init(storageType: PersistentContainerStorageType, managedObjectModel: NSManagedObjectModel, persistentStoreURL: URL, persistentStoreDescriptionOptions: [String : NSObject]? = nil, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { + public init(name: String? = nil, managedObjectModel: NSManagedObjectModel, storageType: PersistentContainerStorageType, persistentStoreURL: URL, persistentStoreDescriptionOptions: [String : NSObject]? = nil, ubiquitousConfiguration: PersistentContainerUbiquitousConfiguration? = nil) throws { // - let name = persistentStoreURL.deletingPathExtension().lastPathComponent - super.init(name: name, managedObjectModel: managedObjectModel) + let name = name ?? persistentStoreURL.deletingPathExtension().lastPathComponent // if storageType == .disk { @@ -38,13 +43,12 @@ open class PersistentContainer: NSPersistentContainer { // let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreURL) - + // persistentStoreDescription.type = (storageType == .disk ? NSSQLiteStoreType : NSInMemoryStoreType) - persistentStoreDescription.shouldAddStoreAsynchronously = false persistentStoreDescription.shouldInferMappingModelAutomatically = true persistentStoreDescription.shouldMigrateStoreAutomatically = true - + // a chance for configuring options (such `NSPersistentHistoryTrackingKey`, for example) persistentStoreDescriptionOptions?.forEach { persistentStoreDescription.setOption($0.value, forKey: $0.key) @@ -60,21 +64,16 @@ open class PersistentContainer: NSPersistentContainer { #endif // - self.persistentStoreDescriptions = [persistentStoreDescription] - - // this should run synchronously since shouldAddStoreAsynchronously is false - var outError: Swift.Error? - - self.loadPersistentStores { description, error in - if let error = error { - outError = error + try super.init(name: name, managedObjectModel: managedObjectModel, persistentStoreDescription: persistentStoreDescription) { persistentContainer, _, error in + guard error == nil else { + return } // #if os(macOS) || os(iOS) if let _ = ubiquitousConfiguration { - self.didImportUbiquitousContentNotificationObserver = NotificationCenter.default.addObserver(forName: .NSPersistentStoreDidImportUbiquitousContentChanges, object: self.persistentStoreCoordinator, queue: nil) { [weak self] notification in - guard let context = self?.viewContext.parent ?? self?.viewContext else { + (persistentContainer as? PersistentContainer)?.didImportUbiquitousContentNotificationObserver = NotificationCenter.default.addObserver(forName: .NSPersistentStoreDidImportUbiquitousContentChanges, object: persistentContainer.persistentStoreCoordinator, queue: nil) { [weak persistentContainer] notification in + guard let context = persistentContainer?.viewContext.parent ?? persistentContainer?.viewContext else { return } @@ -84,43 +83,80 @@ open class PersistentContainer: NSPersistentContainer { } } #endif - - // - self.viewContext.automaticallyMergesChangesFromParent = true - self.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - } - - if let outError = outError { - throw outError } } + public override init(name: String, managedObjectModel: NSManagedObjectModel, persistentStoreDescription: NSPersistentStoreDescription, completionHandler: @escaping (NSPersistentContainer, NSPersistentStoreDescription, Error?) -> Void) throws { + try super.init(name: name, managedObjectModel: managedObjectModel, persistentStoreDescription: persistentStoreDescription, completionHandler: completionHandler) + } + deinit { if let didImportUbiquitousContentNotificationObserver = self.didImportUbiquitousContentNotificationObserver { - self.didImportUbiquitousContentNotificationObserver = nil NotificationCenter.default.removeObserver(didImportUbiquitousContentNotificationObserver) + self.didImportUbiquitousContentNotificationObserver = nil + } + } + +} + + +// MARK: - + +open class BasePersistentContainer: NSPersistentContainer { + + // MARK: - + + public init(name: String, managedObjectModel: NSManagedObjectModel, persistentStoreDescription: NSPersistentStoreDescription, completionHandler: @escaping (NSPersistentContainer, NSPersistentStoreDescription, Error?) -> Void) throws { + // + super.init(name: name, managedObjectModel: managedObjectModel) + + // we need to load synchronously in this implementation + persistentStoreDescription.shouldAddStoreAsynchronously = false + self.persistentStoreDescriptions = [persistentStoreDescription] + + // + var outError: Swift.Error? + + self.loadPersistentStores { description, error in + // + if let error = error { + outError = error + } + else { + self.configureManagedObjectContext(self.viewContext) + } + + // + completionHandler(self, description, error) + } + + if let outError = outError { + throw outError } } // MARK: - - + open override func newBackgroundContext() -> NSManagedObjectContext { let context = super.newBackgroundContext() - - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - + self.configureManagedObjectContext(context) + return context } - + open override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { super.performBackgroundTask { context in - // - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - // + self.configureManagedObjectContext(context) block(context) } } + + // MARK: - + + open func configureManagedObjectContext(_ context: NSManagedObjectContext) { + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + } + } + From f81be2c68a3b3e52cf33d1434523e208a64442bb Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 7 Jun 2018 00:23:08 -0300 Subject: [PATCH 06/19] Added some ManagedObjectContext conveniences methods --- .../AlecrimCoreData.xcodeproj/project.pbxproj | 8 +- .../ManagedObjectContextType.swift | 83 +++++++++++++++++++ 2 files changed, 89 insertions(+), 2 deletions(-) create mode 100644 Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift diff --git a/Source/AlecrimCoreData.xcodeproj/project.pbxproj b/Source/AlecrimCoreData.xcodeproj/project.pbxproj index ec0b171..f5b400c 100644 --- a/Source/AlecrimCoreData.xcodeproj/project.pbxproj +++ b/Source/AlecrimCoreData.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 1454786A20B2487300831016 /* CustomPersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1454786920B2487300831016 /* CustomPersistentContainer.swift */; }; 146B042F208AEAE3002091BF /* FetchRequestController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */; }; 14B9460020759D0D00A7CFFD /* NSTableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */; }; + 14C2028F20C8DBAB00821A79 /* ManagedObjectContextType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14C2028E20C8DBAB00821A79 /* ManagedObjectContextType.swift */; }; 14CC3374205B28CA00BA682A /* NSArrayController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */; }; 14CC3375205B28CA00BA682A /* UITableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */; }; 14CC3376205B28CA00BA682A /* UICollectionView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14CC3360205B28CA00BA682A /* UICollectionView+Extensions.swift */; }; @@ -42,6 +43,7 @@ 1454786920B2487300831016 /* CustomPersistentContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPersistentContainer.swift; sourceTree = ""; }; 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequestController+Extensions.swift"; sourceTree = ""; }; 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Extensions.swift"; sourceTree = ""; }; + 14C2028E20C8DBAB00821A79 /* ManagedObjectContextType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectContextType.swift; sourceTree = ""; }; 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSArrayController+Extensions.swift"; sourceTree = ""; }; 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UITableView+Extensions.swift"; sourceTree = ""; }; 14CC3360205B28CA00BA682A /* UICollectionView+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UICollectionView+Extensions.swift"; sourceTree = ""; }; @@ -113,11 +115,12 @@ isa = PBXGroup; children = ( 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */, - 14CC3360205B28CA00BA682A /* UICollectionView+Extensions.swift */, - 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */, + 14C2028E20C8DBAB00821A79 /* ManagedObjectContextType.swift */, 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */, 14CC3361205B28CA00BA682A /* NSCollectionView+Extensions.swift */, 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */, + 14CC3360205B28CA00BA682A /* UICollectionView+Extensions.swift */, + 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */, ); path = Convenience; sourceTree = ""; @@ -261,6 +264,7 @@ 14CC3374205B28CA00BA682A /* NSArrayController+Extensions.swift in Sources */, 14CC3385205B28CA00BA682A /* FetchedResultsControllerDelegate.swift in Sources */, 14CC337C205B28CA00BA682A /* Expression.swift in Sources */, + 14C2028F20C8DBAB00821A79 /* ManagedObjectContextType.swift in Sources */, 14CC3380205B28CA00BA682A /* Config.swift in Sources */, 14CC3377205B28CA00BA682A /* NSCollectionView+Extensions.swift in Sources */, 1454786820B2484900831016 /* PersistentContainerAuxiliarTypes.swift in Sources */, diff --git a/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift b/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift new file mode 100644 index 0000000..d51221b --- /dev/null +++ b/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift @@ -0,0 +1,83 @@ +// +// ManagedObjectContextType.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 07/06/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation + +// MARK: - + +extension ManagedObjectContext: ManagedObjectContextType {} + +// MARK: - + +public protocol ManagedObjectContextType { + func perform(_ block: @escaping () -> Void) + func performAndWait(_ block: () -> Void) +} + +extension ManagedObjectContextType { + + public func async(execute closure: @escaping (Self) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { + let context = self + + context.perform { + do { + let value = try closure(context) + completion(value, nil) + } + catch { + completion(nil, error) + } + } + } + + public func async(execute closure: @escaping (Self) -> Value, completion: ((Value) -> Void)? = nil) { + let context = self + + context.perform { + let value = closure(context) + completion?(value) + } + } + + @discardableResult + public func sync(execute closure: (Self) throws -> Value) throws -> Value { + var value: Value? + var outError: Error? + + let context = self + + context.performAndWait { + do { + value = try closure(context) + } + catch { + outError = error + } + } + + if let outError = outError { + throw outError + } + + return value! + } + + @discardableResult + public func sync(execute closure: (Self) -> Value) -> Value { + var value: Value? + + let context = self + + context.performAndWait { + value = closure(context) + } + + return value! + } + +} From 69ff2fc5a8823e985889956abf2fe76b3f0755de Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 7 Jun 2018 00:49:07 -0300 Subject: [PATCH 07/19] Added convenience async methods --- .../ManagedObjectContextType.swift | 103 ++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift b/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift index d51221b..277ca29 100644 --- a/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift +++ b/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift @@ -19,6 +19,8 @@ public protocol ManagedObjectContextType { func performAndWait(_ block: () -> Void) } +// MARK: - + extension ManagedObjectContextType { public func async(execute closure: @escaping (Self) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { @@ -81,3 +83,104 @@ extension ManagedObjectContextType { } } + +// MARK: - + +extension PersistentContainer { + + public func async(execute closure: @escaping (ManagedObjectContext) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { + return self.backgroundContext.async(execute: closure, completion: completion) + } + + public func async(execute closure: @escaping (ManagedObjectContext) -> Value, completion: ((Value) -> Void)? = nil) { + return self.backgroundContext.async(execute: closure, completion: completion) + } + + @discardableResult + public func sync(execute closure: (ManagedObjectContext) throws -> Value) throws -> Value { + return try self.backgroundContext.sync(execute: closure) + } + + @discardableResult + public func sync(execute closure: (ManagedObjectContext) -> Value) -> Value { + return self.backgroundContext.sync(execute: closure) + } + +} + +// MARK: - + +extension CustomPersistentContainer { + + public func async(execute closure: @escaping (Context) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { + return self.backgroundContext.async(execute: closure, completion: completion) + } + + public func async(execute closure: @escaping (Context) -> Value, completion: ((Value) -> Void)? = nil) { + return self.backgroundContext.async(execute: closure, completion: completion) + } + + @discardableResult + public func sync(execute closure: (Context) throws -> Value) throws -> Value { + return try self.backgroundContext.sync(execute: closure) + } + + @discardableResult + public func sync(execute closure: (Context) -> Value) -> Value { + return self.backgroundContext.sync(execute: closure) + } + +} + +// MARK: - AlecrimAsyncKit (https://github.com/Alecrim/AlecrimAsyncKit) support + +#if canImport(AlecrimAsyncKit) + +import AlecrimAsyncKit + +extension ManagedObjectContextType { + + public func async(execute closure: @escaping (Self) throws -> Value) -> Task { + return AlecrimAsyncKit.async { task in + self.async(execute: closure, completion: { value, error in + task.finish(with: value, or: error) + }) + } + } + + public func async(execute closure: @escaping (Self) -> Value) -> NonFailableTask { + return AlecrimAsyncKit.async { task in + self.async(execute: closure, completion: { value in + task.finish(with: value) + }) + } + } + +} + +extension PersistentContainer { + + public func async(execute closure: @escaping (ManagedObjectContext) throws -> Value) -> Task { + return self.backgroundContext.async(execute: closure) + } + + public func async(execute closure: @escaping (ManagedObjectContext) -> Value) -> NonFailableTask { + return self.backgroundContext.async(execute: closure) + } + +} + +extension CustomPersistentContainer { + + public func async(execute closure: @escaping (Context) throws -> Value) -> Task { + return self.backgroundContext.async(execute: closure) + } + + public func async(execute closure: @escaping (Context) -> Value) -> NonFailableTask { + return self.backgroundContext.async(execute: closure) + } + +} + +#endif + From 726f4d1c63871f7a2c107f5c291529186afc73da Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 7 Jun 2018 01:13:29 -0300 Subject: [PATCH 08/19] Added EntityObserver convenience class --- .../AlecrimCoreData.xcodeproj/project.pbxproj | 4 ++ .../Convenience/EntityObserver.swift | 40 +++++++++++++++++++ .../PersistentContainerType.swift | 11 ++++- 3 files changed, 54 insertions(+), 1 deletion(-) create mode 100644 Source/AlecrimCoreData/Convenience/EntityObserver.swift diff --git a/Source/AlecrimCoreData.xcodeproj/project.pbxproj b/Source/AlecrimCoreData.xcodeproj/project.pbxproj index f5b400c..93d6180 100644 --- a/Source/AlecrimCoreData.xcodeproj/project.pbxproj +++ b/Source/AlecrimCoreData.xcodeproj/project.pbxproj @@ -11,6 +11,7 @@ 1454786620B247F300831016 /* PersistentContainerType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1454786520B247F300831016 /* PersistentContainerType.swift */; }; 1454786820B2484900831016 /* PersistentContainerAuxiliarTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1454786720B2484800831016 /* PersistentContainerAuxiliarTypes.swift */; }; 1454786A20B2487300831016 /* CustomPersistentContainer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1454786920B2487300831016 /* CustomPersistentContainer.swift */; }; + 1462C65F20C8E63A00A7A4E6 /* EntityObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1462C65E20C8E63A00A7A4E6 /* EntityObserver.swift */; }; 146B042F208AEAE3002091BF /* FetchRequestController+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */; }; 14B9460020759D0D00A7CFFD /* NSTableView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */; }; 14C2028F20C8DBAB00821A79 /* ManagedObjectContextType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 14C2028E20C8DBAB00821A79 /* ManagedObjectContextType.swift */; }; @@ -41,6 +42,7 @@ 1454786520B247F300831016 /* PersistentContainerType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentContainerType.swift; sourceTree = ""; }; 1454786720B2484800831016 /* PersistentContainerAuxiliarTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistentContainerAuxiliarTypes.swift; sourceTree = ""; }; 1454786920B2487300831016 /* CustomPersistentContainer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomPersistentContainer.swift; sourceTree = ""; }; + 1462C65E20C8E63A00A7A4E6 /* EntityObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EntityObserver.swift; sourceTree = ""; }; 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "FetchRequestController+Extensions.swift"; sourceTree = ""; }; 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSTableView+Extensions.swift"; sourceTree = ""; }; 14C2028E20C8DBAB00821A79 /* ManagedObjectContextType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManagedObjectContextType.swift; sourceTree = ""; }; @@ -114,6 +116,7 @@ 14CC335D205B28CA00BA682A /* Convenience */ = { isa = PBXGroup; children = ( + 1462C65E20C8E63A00A7A4E6 /* EntityObserver.swift */, 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */, 14C2028E20C8DBAB00821A79 /* ManagedObjectContextType.swift */, 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */, @@ -272,6 +275,7 @@ 1454786620B247F300831016 /* PersistentContainerType.swift in Sources */, 14CC337B205B28CA00BA682A /* Query.swift in Sources */, 14CC3384205B28CA00BA682A /* FetchedResultsSectionInfo.swift in Sources */, + 1462C65F20C8E63A00A7A4E6 /* EntityObserver.swift in Sources */, 14CC3375205B28CA00BA682A /* UITableView+Extensions.swift in Sources */, 14CC3379205B28CA00BA682A /* ManagedObjectContext.swift in Sources */, 14CC3383205B28CA00BA682A /* FetchRequestController.swift in Sources */, diff --git a/Source/AlecrimCoreData/Convenience/EntityObserver.swift b/Source/AlecrimCoreData/Convenience/EntityObserver.swift new file mode 100644 index 0000000..83a2cc7 --- /dev/null +++ b/Source/AlecrimCoreData/Convenience/EntityObserver.swift @@ -0,0 +1,40 @@ +// +// EntityObserver.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 07/06/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation + +// MARK: - + +/// A fetch request controller wrapper for one entity only. Can be used as observer for an entity detail presentation, for example. +public final class EntityObserver { + + private let frc: FetchRequestController + + fileprivate init(entity: EntityType, propertyName: String, updateHandler didChangeContentClosure: @escaping () -> Void, context: ManagedObjectContext) { + self.frc = Query(in: context) + .filtered(using: NSPredicate(format: "SELF == %@", argumentArray: [entity])) + .sorted(by: SortDescriptor(key: propertyName, ascending: true)) + .toFetchRequestController() + + self.frc.didChangeContent(closure: didChangeContentClosure) + } + + deinit { + self.frc.removeAllBindings() + } + +} + +// MARK: - + +extension PersistentContainerType { + public func observer(for entity: EntityType, updateHandler: @escaping () -> Void) -> EntityObserver { + let propertyName = entity.entity.properties.first!.name // using any property here is fine, but there must be at least one property + return EntityObserver(entity: entity, propertyName: propertyName, updateHandler: updateHandler, context: self.viewContext) + } +} diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift index e54c8cb..00b83c2 100644 --- a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift @@ -9,11 +9,20 @@ import Foundation import CoreData -public protocol PersistentContainerType: AnyObject {} +// MARK: - + +public protocol PersistentContainerType: AnyObject { + associatedtype ManagedObjectContextType: ManagedObjectContext + + var viewContext: ManagedObjectContextType { get } + var backgroundContext: ManagedObjectContextType { get } +} extension PersistentContainer: PersistentContainerType {} extension CustomPersistentContainer: PersistentContainerType {} +// MARK: - helper static methods + extension PersistentContainerType { public static func managedObjectModel(withName name: String? = nil, in bundle: Bundle? = nil) throws -> NSManagedObjectModel { From afb6739d3952ccabb5e5944d38d587de9017fb6f Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 7 Jun 2018 12:01:14 -0300 Subject: [PATCH 09/19] Removed AlecrimAsyncKit related code from public build --- .../ManagedObjectContextType.swift | 55 +------------------ 1 file changed, 1 insertion(+), 54 deletions(-) diff --git a/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift b/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift index 277ca29..b06d618 100644 --- a/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift +++ b/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift @@ -23,7 +23,7 @@ public protocol ManagedObjectContextType { extension ManagedObjectContextType { - public func async(execute closure: @escaping (Self) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { + fileprivate func async(execute closure: @escaping (Self) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { let context = self context.perform { @@ -131,56 +131,3 @@ extension CustomPersistentContainer { } } - -// MARK: - AlecrimAsyncKit (https://github.com/Alecrim/AlecrimAsyncKit) support - -#if canImport(AlecrimAsyncKit) - -import AlecrimAsyncKit - -extension ManagedObjectContextType { - - public func async(execute closure: @escaping (Self) throws -> Value) -> Task { - return AlecrimAsyncKit.async { task in - self.async(execute: closure, completion: { value, error in - task.finish(with: value, or: error) - }) - } - } - - public func async(execute closure: @escaping (Self) -> Value) -> NonFailableTask { - return AlecrimAsyncKit.async { task in - self.async(execute: closure, completion: { value in - task.finish(with: value) - }) - } - } - -} - -extension PersistentContainer { - - public func async(execute closure: @escaping (ManagedObjectContext) throws -> Value) -> Task { - return self.backgroundContext.async(execute: closure) - } - - public func async(execute closure: @escaping (ManagedObjectContext) -> Value) -> NonFailableTask { - return self.backgroundContext.async(execute: closure) - } - -} - -extension CustomPersistentContainer { - - public func async(execute closure: @escaping (Context) throws -> Value) -> Task { - return self.backgroundContext.async(execute: closure) - } - - public func async(execute closure: @escaping (Context) -> Value) -> NonFailableTask { - return self.backgroundContext.async(execute: closure) - } - -} - -#endif - From b093cdfba54fbddf2549dd7346f18467bf8ed44e Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Thu, 7 Jun 2018 12:02:52 -0300 Subject: [PATCH 10/19] Fixed method access --- .../AlecrimCoreData/Convenience/ManagedObjectContextType.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift b/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift index b06d618..bf5b1c9 100644 --- a/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift +++ b/Source/AlecrimCoreData/Convenience/ManagedObjectContextType.swift @@ -23,7 +23,7 @@ public protocol ManagedObjectContextType { extension ManagedObjectContextType { - fileprivate func async(execute closure: @escaping (Self) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { + public func async(execute closure: @escaping (Self) throws -> Value, completion: @escaping ((Value?, Error?) -> Void)) { let context = self context.perform { From 600dc308449ecd6a3217957ee77de90872f8f6b2 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 Jun 2018 00:24:49 -0300 Subject: [PATCH 11/19] Minor change to the convenience entity observer --- Source/AlecrimCoreData/Convenience/EntityObserver.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Source/AlecrimCoreData/Convenience/EntityObserver.swift b/Source/AlecrimCoreData/Convenience/EntityObserver.swift index 83a2cc7..993d1f2 100644 --- a/Source/AlecrimCoreData/Convenience/EntityObserver.swift +++ b/Source/AlecrimCoreData/Convenience/EntityObserver.swift @@ -17,6 +17,7 @@ public final class EntityObserver { fileprivate init(entity: EntityType, propertyName: String, updateHandler didChangeContentClosure: @escaping () -> Void, context: ManagedObjectContext) { self.frc = Query(in: context) + .setBatchSize(0) .filtered(using: NSPredicate(format: "SELF == %@", argumentArray: [entity])) .sorted(by: SortDescriptor(key: propertyName, ascending: true)) .toFetchRequestController() From 2ccb295a1f719ed9b35dcf6cde3f2726eee3bae6 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 Jun 2018 00:26:52 -0300 Subject: [PATCH 12/19] Renamed `setBatchSize` to `batchSize --- Source/AlecrimCoreData/Convenience/EntityObserver.swift | 2 +- Source/AlecrimCoreData/Core/Query/FetchRequest.swift | 2 +- Source/AlecrimCoreData/Core/Query/Query.swift | 4 ++-- Source/AlecrimCoreData/Core/Query/Queryable.swift | 2 +- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Source/AlecrimCoreData/Convenience/EntityObserver.swift b/Source/AlecrimCoreData/Convenience/EntityObserver.swift index 993d1f2..3525965 100644 --- a/Source/AlecrimCoreData/Convenience/EntityObserver.swift +++ b/Source/AlecrimCoreData/Convenience/EntityObserver.swift @@ -17,7 +17,7 @@ public final class EntityObserver { fileprivate init(entity: EntityType, propertyName: String, updateHandler didChangeContentClosure: @escaping () -> Void, context: ManagedObjectContext) { self.frc = Query(in: context) - .setBatchSize(0) + .batchSize(0) .filtered(using: NSPredicate(format: "SELF == %@", argumentArray: [entity])) .sorted(by: SortDescriptor(key: propertyName, ascending: true)) .toFetchRequestController() diff --git a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift index a4bb8d3..9132b27 100644 --- a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift +++ b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift @@ -61,7 +61,7 @@ extension FetchRequest { return clone } - public func setBatchSize(_ batchSize: Int) -> FetchRequest { + public func batchSize(_ batchSize: Int) -> FetchRequest { var clone = self clone.batchSize = batchSize diff --git a/Source/AlecrimCoreData/Core/Query/Query.swift b/Source/AlecrimCoreData/Core/Query/Query.swift index f96e71b..cbdbe4b 100644 --- a/Source/AlecrimCoreData/Core/Query/Query.swift +++ b/Source/AlecrimCoreData/Core/Query/Query.swift @@ -244,9 +244,9 @@ extension Query: Queryable { return clone } - public func setBatchSize(_ batchSize: Int) -> Query { + public func batchSize(_ batchSize: Int) -> Query { var clone = self - clone.fetchRequest = clone.fetchRequest.setBatchSize(batchSize) + clone.fetchRequest = clone.fetchRequest.batchSize(batchSize) return clone } diff --git a/Source/AlecrimCoreData/Core/Query/Queryable.swift b/Source/AlecrimCoreData/Core/Query/Queryable.swift index 692ba48..89ea5f4 100644 --- a/Source/AlecrimCoreData/Core/Query/Queryable.swift +++ b/Source/AlecrimCoreData/Core/Query/Queryable.swift @@ -17,7 +17,7 @@ public protocol Queryable { func dropFirst(_ n: Int) -> Self func prefix(_ maxLength: Int) -> Self - func setBatchSize(_ batchSize: Int) -> Self + func batchSize(_ batchSize: Int) -> Self func filtered(using predicate: Predicate) -> Self From 7db53bcd9eea7a2e98d753995eb9b74889cae4a9 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 Jun 2018 00:42:01 -0300 Subject: [PATCH 13/19] Turning some internals public --- Source/AlecrimCoreData/Core/Query/FetchRequest.swift | 2 +- Source/AlecrimCoreData/Core/Query/Query.swift | 4 ++-- .../Fetch Request Controller/FetchRequestController.swift | 6 ++++++ 3 files changed, 9 insertions(+), 3 deletions(-) diff --git a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift index 9132b27..305556a 100644 --- a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift +++ b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift @@ -25,7 +25,7 @@ public struct FetchRequest: Queryable { public init() { } - internal func toRaw() -> NSFetchRequest { + public func toRaw() -> NSFetchRequest { let entityDescription = Entity.entity() let rawValue = NSFetchRequest(entityName: entityDescription.name!) diff --git a/Source/AlecrimCoreData/Core/Query/Query.swift b/Source/AlecrimCoreData/Core/Query/Query.swift index cbdbe4b..b4deebf 100644 --- a/Source/AlecrimCoreData/Core/Query/Query.swift +++ b/Source/AlecrimCoreData/Core/Query/Query.swift @@ -13,8 +13,8 @@ import CoreData public struct Query { - internal let context: ManagedObjectContext - internal fileprivate(set) var fetchRequest: FetchRequest + public let context: ManagedObjectContext + public fileprivate(set) var fetchRequest: FetchRequest public init(in context: ManagedObjectContext, fetchRequest: FetchRequest = FetchRequest()) { self.context = context diff --git a/Source/AlecrimCoreData/Fetch Request Controller/FetchRequestController.swift b/Source/AlecrimCoreData/Fetch Request Controller/FetchRequestController.swift index 4cea6b8..1dfeb9d 100644 --- a/Source/AlecrimCoreData/Fetch Request Controller/FetchRequestController.swift +++ b/Source/AlecrimCoreData/Fetch Request Controller/FetchRequestController.swift @@ -22,7 +22,11 @@ import CoreData public final class FetchRequestController { + // MARK: - + + public let fetchRequest: FetchRequest public let rawValue: NSFetchedResultsController + internal let rawValueDelegate: FetchedResultsControllerDelegate fileprivate let initialPredicate: Predicate? @@ -47,7 +51,9 @@ public final class FetchRequestController { } public init(fetchRequest: FetchRequest, context: ManagedObjectContext, sectionNameKeyPath: String? = nil, cacheName: String? = nil) { + self.fetchRequest = fetchRequest self.rawValue = NSFetchedResultsController(fetchRequest: fetchRequest.toRaw() as NSFetchRequest, managedObjectContext: context, sectionNameKeyPath: sectionNameKeyPath, cacheName: cacheName) + self.rawValueDelegate = FetchedResultsControllerDelegate() self.initialPredicate = fetchRequest.predicate From e3b5e5d110f826f0e6f8125a50a474db4025d338 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 9 Jun 2018 00:42:24 -0300 Subject: [PATCH 14/19] Minor change to EntityObserver` --- Source/AlecrimCoreData/Convenience/EntityObserver.swift | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Source/AlecrimCoreData/Convenience/EntityObserver.swift b/Source/AlecrimCoreData/Convenience/EntityObserver.swift index 3525965..17003b9 100644 --- a/Source/AlecrimCoreData/Convenience/EntityObserver.swift +++ b/Source/AlecrimCoreData/Convenience/EntityObserver.swift @@ -18,7 +18,7 @@ public final class EntityObserver { fileprivate init(entity: EntityType, propertyName: String, updateHandler didChangeContentClosure: @escaping () -> Void, context: ManagedObjectContext) { self.frc = Query(in: context) .batchSize(0) - .filtered(using: NSPredicate(format: "SELF == %@", argumentArray: [entity])) + .filtered(using: Predicate(format: "SELF == %@", argumentArray: [entity])) .sorted(by: SortDescriptor(key: propertyName, ascending: true)) .toFetchRequestController() @@ -35,7 +35,11 @@ public final class EntityObserver { extension PersistentContainerType { public func observer(for entity: EntityType, updateHandler: @escaping () -> Void) -> EntityObserver { - let propertyName = entity.entity.properties.first!.name // using any property here is fine, but there must be at least one property + // using any property here is fine, but there must be at least one property + guard let propertyName = entity.entity.properties.first(where: { $0.isTransient == false })?.name else { + fatalError("No property found.") + } + return EntityObserver(entity: entity, propertyName: propertyName, updateHandler: updateHandler, context: self.viewContext) } } From 863085b1acc7036c67e2b662a4e6727eed2118ed Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Fri, 22 Jun 2018 18:45:23 -0300 Subject: [PATCH 15/19] Updated convenience "binding" for collection views Updated NS/UI collection views convenience "binding" to call the custom cell configuration handler when moving cells (NS/UI table views appear to not need this workaround). --- .../Convenience/NSCollectionView+Extensions.swift | 6 ++++++ .../Convenience/UICollectionView+Extensions.swift | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift index d841265..687caff 100644 --- a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift @@ -126,6 +126,12 @@ extension FetchRequestController { case .move(let oldIndexPath, let newIndexPath): collectionView.moveItem(at: oldIndexPath, to: newIndexPath) + + // workaround to be sure that cells will be refreshed + // note: this only works when using a cell configuration handler + if itemConfigurationHandler != nil { + updatedIndexPaths.append(newIndexPath) + } } } }, completionHandler: { _ in diff --git a/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift index 6c66c19..1b8c7df 100644 --- a/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift @@ -122,6 +122,12 @@ extension FetchRequestController { case .move(let oldIndexPath, let newIndexPath): collectionView.moveItem(at: oldIndexPath, to: newIndexPath) + + // workaround to be sure that cells will be refreshed + // note: this only works when using a cell configuration handler + if cellConfigurationHandler != nil { + updatedIndexPaths.append(newIndexPath) + } } } }, completion: { _ in From e1f34bed70fa85f1a1140390ae17562c6a1a9b60 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Sat, 23 Jun 2018 15:27:08 -0300 Subject: [PATCH 16/19] Removed unnecessary code from `FetchedResultsControllerDelegate --- .../FetchedResultsControllerDelegate.swift | 33 ++----------------- 1 file changed, 3 insertions(+), 30 deletions(-) diff --git a/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift b/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift index 693ff72..6399183 100644 --- a/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift +++ b/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift @@ -107,15 +107,11 @@ public func +=(left: ClosuresContainer, right: Closure) { } -// MARK: - - // MARK: - FetchRequestController extensions extension FetchRequestController { public func removeAllBindings() { - self.performFetchIfNeeded() - self.rawValueDelegate.needsReloadDataClosure = nil self.rawValueDelegate.willChangeContentClosuresContainer.removeAll() @@ -137,18 +133,16 @@ extension FetchRequestController { extension FetchRequestController { public func refresh() { - self.performFetchIfNeeded() - - self.rawValueDelegate.needsReloadDataClosure?() + self.rawValueDelegate.willChangeContentClosuresContainer.closures.forEach { $0() } if let cacheName = self.rawValue.cacheName { NSFetchedResultsController.deleteCache(withName: cacheName) } - + self.performFetch() - + self.rawValueDelegate.didChangeContentClosuresContainer.closures.forEach { $0() } } @@ -158,85 +152,64 @@ extension FetchRequestController { @discardableResult internal func needsReloadData(closure: @escaping FetchedResultsControllerDelegate.NeedsReloadDataClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.needsReloadDataClosure = closure return self } } - extension FetchRequestController { @discardableResult public func willChangeContent(closure: @escaping FetchedResultsControllerDelegate.ChangeContentClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.willChangeContentClosuresContainer += closure return self } @discardableResult public func didChangeContent(closure: @escaping FetchedResultsControllerDelegate.ChangeContentClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.didChangeContentClosuresContainer += closure return self } @discardableResult public func didInsertSection(closure: @escaping FetchedResultsControllerDelegate.ChangeSectionClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.didInsertSectionClosuresContainer += closure return self } @discardableResult public func didDeleteSection(closure: @escaping FetchedResultsControllerDelegate.ChangeSectionClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.didDeleteSectionClosuresContainer += closure return self } @discardableResult public func didInsertObject(closure: @escaping FetchedResultsControllerDelegate.ChangeItemClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.didInsertObjectClosuresContainer += closure return self } @discardableResult public func didDeleteObject(closure: @escaping FetchedResultsControllerDelegate.ChangeItemClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.didDeleteObjectClosuresContainer += closure return self } @discardableResult public func didUpdateObject(closure: @escaping FetchedResultsControllerDelegate.ChangeItemClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.didUpdateObjectClosuresContainer += closure return self } @discardableResult public func didMoveObject(closure: @escaping FetchedResultsControllerDelegate.MoveItemClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.didMoveObjectClosuresContainer += closure return self } @discardableResult public func sectionIndexTitle(closure: @escaping FetchedResultsControllerDelegate.SectionIndexTitleClosure) -> Self { - self.performFetchIfNeeded() - self.rawValueDelegate.sectionIndexTitleClosure = closure return self } From 3c19bf9fa06b88b21a6ad8c5dd912c2b7042d92a Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Mon, 25 Jun 2018 17:28:05 -0300 Subject: [PATCH 17/19] Fixed execute method in Query type --- Source/AlecrimCoreData/Core/Query/Query.swift | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Source/AlecrimCoreData/Core/Query/Query.swift b/Source/AlecrimCoreData/Core/Query/Query.swift index b4deebf..86bfbfa 100644 --- a/Source/AlecrimCoreData/Core/Query/Query.swift +++ b/Source/AlecrimCoreData/Core/Query/Query.swift @@ -34,10 +34,9 @@ extension Query { } fileprivate func execute(fetchRequest: FetchRequest) -> [Entity] { - return try! self.context.fetch(self.fetchRequest.toRaw()) + return try! self.context.fetch(fetchRequest.toRaw()) } - // public func count() -> Int { @@ -67,6 +66,14 @@ extension Query { public func first() -> Entity? { return self.execute(fetchRequest: self.fetchRequest.prefix(1)).first } + + public func last() -> Entity? { + guard let sortDescriptors = self.fetchRequest.sortDescriptors, !sortDescriptors.isEmpty else { + return self.execute().last // not memory efficient, but will do the job + } + + return self.execute(fetchRequest: self.fetchRequest.reversed().prefix(1)).first + } } From 5fcde2a74cb51fa9e571afa6ae4cf0bcc3de11f6 Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Mon, 25 Jun 2018 17:29:18 -0300 Subject: [PATCH 18/19] Added support to `last` method in Query --- Source/AlecrimCoreData/Core/Query/FetchRequest.swift | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift index 305556a..5321c4c 100644 --- a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift +++ b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift @@ -41,6 +41,17 @@ public struct FetchRequest: Queryable { return rawValue } + internal func reversed() -> FetchRequest { + guard let existingSortDescriptors = self.sortDescriptors, !existingSortDescriptors.isEmpty else { + return self + } + + var clone = self + clone.sortDescriptors = existingSortDescriptors.map { SortDescriptor(key: $0.key, ascending: !$0.ascending) } + + return clone + } + } // MARK: - From 1585c26be42f1f8f5348b0f7400a1170087933ae Mon Sep 17 00:00:00 2001 From: Vanderlei Martinelli Date: Fri, 29 Jun 2018 21:11:37 -0300 Subject: [PATCH 19/19] Bumped version and build numbers --- AlecrimCoreData.podspec | 2 +- Source/AlecrimCoreData.xcodeproj/project.pbxproj | 4 ++-- Source/AlecrimCoreData/Info.plist | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/AlecrimCoreData.podspec b/AlecrimCoreData.podspec index 7b14cf9..a59f03b 100644 --- a/AlecrimCoreData.podspec +++ b/AlecrimCoreData.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "AlecrimCoreData" - s.version = "6.0" + s.version = "6.0.1" s.summary = "A powerful and elegant Core Data framework for Swift." s.homepage = "https://www.alecrim.com/AlecrimCoreData" diff --git a/Source/AlecrimCoreData.xcodeproj/project.pbxproj b/Source/AlecrimCoreData.xcodeproj/project.pbxproj index 93d6180..3a7a6c5 100644 --- a/Source/AlecrimCoreData.xcodeproj/project.pbxproj +++ b/Source/AlecrimCoreData.xcodeproj/project.pbxproj @@ -420,7 +420,7 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1815; + CURRENT_PROJECT_VERSION = 1837; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -452,7 +452,7 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1815; + CURRENT_PROJECT_VERSION = 1837; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/Source/AlecrimCoreData/Info.plist b/Source/AlecrimCoreData/Info.plist index e2953de..4956a91 100644 --- a/Source/AlecrimCoreData/Info.plist +++ b/Source/AlecrimCoreData/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 6.0 + 6.0.1 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright