diff --git a/AlecrimCoreData.podspec b/AlecrimCoreData.podspec index 0b53efc..7b14cf9 100644 --- a/AlecrimCoreData.podspec +++ b/AlecrimCoreData.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "AlecrimCoreData" - s.version = "6.0-beta.3" + s.version = "6.0" s.summary = "A powerful and elegant Core Data framework for Swift." s.homepage = "https://www.alecrim.com/AlecrimCoreData" diff --git a/Package.swift b/Package.swift new file mode 100644 index 0000000..a31d459 --- /dev/null +++ b/Package.swift @@ -0,0 +1,3 @@ +import PackageDescription + +let package = Package(name: "AlecrimCoreData") diff --git a/README.md b/README.md index 91db61d..08b523f 100644 --- a/README.md +++ b/README.md @@ -16,8 +16,8 @@ Simple do that: let query = persistentContainer.viewContext.people .where { \.city == "Piracicaba" } .orderBy { \.name } - -for person in query.skip(20).take(10) { + +for person in query.dropFirst(20).prefix(10) { print(person.name, person.address) } ``` @@ -30,7 +30,7 @@ persistentContainer.performBackgroundTask { context in .filtered(using: \.country == "Brazil" && \.isContributor == true) .sorted(by: .descending(\.contributionCount)) .sorted(by: \.name) - + if let person = query.first() { print(person.name, person.email) } @@ -60,7 +60,7 @@ Some well known features and functionalities may be reimplemented in a future re ## Contribute If you have any problems or need more information, please open an issue using the provided GitHub link. -You can also contribute by fixing errors or creating new features. When doing this, please submit your pull requests to this repository as I do not have much time to "hunt" forks for not submited patches. +You can also contribute by fixing errors or creating new features. When doing this, please submit your pull requests to this repository as I do not have much time to "hunt" forks for not submitted patches. - master - The production branch. Clone or fork this repository for the latest copy. - develop - The active development branch. [Pull requests](https://help.github.com/articles/creating-a-pull-request) should be directed to this branch. diff --git a/Source/AlecrimCoreData.xcodeproj/project.pbxproj b/Source/AlecrimCoreData.xcodeproj/project.pbxproj index 680ae73..ec0b171 100644 --- a/Source/AlecrimCoreData.xcodeproj/project.pbxproj +++ b/Source/AlecrimCoreData.xcodeproj/project.pbxproj @@ -8,6 +8,11 @@ /* Begin PBXBuildFile section */ 1420DF9E2054FFBC00B34160 /* AlecrimCoreData.h in Headers */ = {isa = PBXBuildFile; fileRef = 1420DF9C2054FFBC00B34160 /* AlecrimCoreData.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 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 */; }; + 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 */; }; 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 */; }; @@ -32,6 +37,11 @@ 1420DF992054FFBC00B34160 /* AlecrimCoreData.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = AlecrimCoreData.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 1420DF9C2054FFBC00B34160 /* AlecrimCoreData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = AlecrimCoreData.h; sourceTree = ""; }; 1420DF9D2054FFBC00B34160 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 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 = ""; }; + 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 = ""; }; 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 = ""; }; @@ -102,10 +112,12 @@ 14CC335D205B28CA00BA682A /* Convenience */ = { isa = PBXGroup; children = ( - 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */, - 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */, + 146B042E208AEAE3002091BF /* FetchRequestController+Extensions.swift */, 14CC3360205B28CA00BA682A /* UICollectionView+Extensions.swift */, + 14CC335F205B28CA00BA682A /* UITableView+Extensions.swift */, + 14CC335E205B28CA00BA682A /* NSArrayController+Extensions.swift */, 14CC3361205B28CA00BA682A /* NSCollectionView+Extensions.swift */, + 14B945FF20759D0D00A7CFFD /* NSTableView+Extensions.swift */, ); path = Convenience; sourceTree = ""; @@ -113,20 +125,23 @@ 14CC3362205B28CA00BA682A /* Core */ = { isa = PBXGroup; children = ( - 14CC3363205B28CA00BA682A /* Container */, + 14CC3363205B28CA00BA682A /* Persistent Container */, 14CC3367205B28CA00BA682A /* Query */, ); path = Core; sourceTree = ""; }; - 14CC3363205B28CA00BA682A /* Container */ = { + 14CC3363205B28CA00BA682A /* Persistent Container */ = { isa = PBXGroup; children = ( 14CC3364205B28CA00BA682A /* PersistentContainer.swift */, + 1454786920B2487300831016 /* CustomPersistentContainer.swift */, + 1454786720B2484800831016 /* PersistentContainerAuxiliarTypes.swift */, + 1454786520B247F300831016 /* PersistentContainerType.swift */, 14CC3365205B28CA00BA682A /* ManagedObjectContext.swift */, 14CC3366205B28CA00BA682A /* ManagedObject.swift */, ); - path = Container; + path = "Persistent Container"; sourceTree = ""; }; 14CC3367205B28CA00BA682A /* Query */ = { @@ -237,15 +252,20 @@ 14CC337E205B28CA00BA682A /* Predicate.swift in Sources */, 14CC337D205B28CA00BA682A /* FetchRequest.swift in Sources */, 14CC3381205B28CA00BA682A /* KeyPath.swift in Sources */, + 14B9460020759D0D00A7CFFD /* NSTableView+Extensions.swift in Sources */, + 146B042F208AEAE3002091BF /* FetchRequestController+Extensions.swift in Sources */, 14CC337F205B28CA00BA682A /* SortDescriptor.swift in Sources */, 14CC337A205B28CA00BA682A /* ManagedObject.swift in Sources */, + 1454786A20B2487300831016 /* CustomPersistentContainer.swift in Sources */, 14CC3382205B28CA00BA682A /* Queryable.swift in Sources */, 14CC3374205B28CA00BA682A /* NSArrayController+Extensions.swift in Sources */, 14CC3385205B28CA00BA682A /* FetchedResultsControllerDelegate.swift in Sources */, 14CC337C205B28CA00BA682A /* Expression.swift in Sources */, 14CC3380205B28CA00BA682A /* Config.swift in Sources */, 14CC3377205B28CA00BA682A /* NSCollectionView+Extensions.swift in Sources */, + 1454786820B2484900831016 /* PersistentContainerAuxiliarTypes.swift in Sources */, 14CC3378205B28CA00BA682A /* PersistentContainer.swift in Sources */, + 1454786620B247F300831016 /* PersistentContainerType.swift in Sources */, 14CC337B205B28CA00BA682A /* Query.swift in Sources */, 14CC3384205B28CA00BA682A /* FetchedResultsSectionInfo.swift in Sources */, 14CC3375205B28CA00BA682A /* UITableView+Extensions.swift in Sources */, @@ -392,7 +412,7 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1798; + CURRENT_PROJECT_VERSION = 1815; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; @@ -424,7 +444,7 @@ CODE_SIGN_IDENTITY = ""; CODE_SIGN_STYLE = Manual; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 1798; + CURRENT_PROJECT_VERSION = 1815; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = ""; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/Source/AlecrimCoreData/Convenience/FetchRequestController+Extensions.swift b/Source/AlecrimCoreData/Convenience/FetchRequestController+Extensions.swift new file mode 100644 index 0000000..0986562 --- /dev/null +++ b/Source/AlecrimCoreData/Convenience/FetchRequestController+Extensions.swift @@ -0,0 +1,20 @@ +// +// FetchRequestController+Extensions.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 21/04/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation + +extension FetchRequestController { + + internal enum Change { + case insert(T) + case delete(T) + case update(T) + case move(T, T) // from, to + } + +} diff --git a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift index 1b20baf..d841265 100644 --- a/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/NSCollectionView+Extensions.swift @@ -7,168 +7,148 @@ // #if os(macOS) - - import Foundation - import AppKit - - extension FetchRequestController { - - @discardableResult - public func bind(to collectionView: NSCollectionView, sectionOffset: Int = 0, cellConfigurationHandler: ((ItemType, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - - var reloadData = false - - // - func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - - reloadData = false + +import Foundation +import AppKit + +extension FetchRequestController { + + /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. + @discardableResult + public func bind(to collectionView: NSCollectionView, sectionOffset: Int = 0, animated: Bool = false, itemConfigurationHandler: ((NSCollectionViewItem, IndexPath) -> Void)? = nil) -> Self { + // + var reloadData = false + var sectionChanges = Array>() + var itemChanges = Array>() + + // + func reset() { + reloadData = false + sectionChanges.removeAll() + itemChanges.removeAll() + } + + // + self + .needsReloadData { + reloadData = true } - - // - self - .needsReloadData { + .willChangeContent { + if collectionView.numberOfSections == 0 { reloadData = true } - .willChangeContent { - if !reloadData { - reset() - } - } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } - } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex } - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex } - } - } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } + + guard !reloadData else { return } + reset() + } + .didInsertSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.insert(sectionIndex)) + } + .didDeleteSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.delete(sectionIndex)) + } + .didInsertObject { _, newIndexPath in + guard !reloadData else { return } + + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + itemChanges.append(.insert(newIndexPath)) + } + .didDeleteObject { _, indexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.delete(indexPath)) + } + .didUpdateObject { _, indexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.update(indexPath)) + } + .didMoveObject { entity, indexPath, newIndexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + + itemChanges.append(.move(indexPath, newIndexPath)) + } + .didChangeContent { [weak collectionView] in + // + guard let collectionView = collectionView else { + reset() + return } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - } + + // + guard !reloadData else { + collectionView.reloadData() + reset() + return } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) + + // + var updatedIndexPaths = [IndexPath]() + + let performer = animated ? collectionView.animator() : collectionView + + performer.performBatchUpdates({ + sectionChanges.forEach { + switch $0 { + case .insert(let sectionIndex): + collectionView.insertSections(IndexSet(integer: sectionIndex)) + + case .delete(let sectionIndex): + collectionView.deleteSections(IndexSet(integer: sectionIndex)) + + default: + break } } - } - .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } - else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) + + itemChanges.forEach { + switch $0 { + case .insert(let indexPath): + collectionView.insertItems(at: Set([indexPath])) + + case .delete(let indexPath): + collectionView.deleteItems(at: Set([indexPath])) + + case .update(let indexPath): + if itemConfigurationHandler == nil { + collectionView.reloadItems(at: Set([indexPath])) } - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) + else { + updatedIndexPaths.append(indexPath) } + + case .move(let oldIndexPath, let newIndexPath): + collectionView.moveItem(at: oldIndexPath, to: newIndexPath) } } - } - .didChangeContent { [weak collectionView] in - // - guard let collectionView = collectionView else { - reset() - return - } - - // - if reloadData { - collectionView.reloadData() - reset() - } - else { - collectionView.performBatchUpdates({ - if deletedSectionIndexes.count > 0 { - collectionView.deleteSections(deletedSectionIndexes as IndexSet) - } - - if insertedSectionIndexes.count > 0 { - collectionView.insertSections(insertedSectionIndexes as IndexSet) - } - - if updatedSectionIndexes.count > 0 { - collectionView.reloadSections(updatedSectionIndexes as IndexSet) - } - - if deletedItemIndexPaths.count > 0 { - collectionView.deleteItems(at: Set(deletedItemIndexPaths)) - } - - if insertedItemIndexPaths.count > 0 { - collectionView.insertItems(at: Set(insertedItemIndexPaths)) - } - - if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { - collectionView.reloadItems(at: Set(updatedItemIndexPaths)) - } - }, completionHandler: { finished in - if finished { - if let cellConfigurationHandler = cellConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let item = collectionView.item(at: updatedItemIndexPath) as? ItemType { - cellConfigurationHandler(item, updatedItemIndexPath) - } - } - } - - reset() - } - }) + }, completionHandler: { _ in + updatedIndexPaths.forEach { + if let item = collectionView.item(at: $0) { + itemConfigurationHandler?(item, $0) + } } - } - - // - collectionView.reloadData() - - // - return self + + reset() + }) } - + + // + collectionView.reloadData() + + // + return self } - + +} + #endif + + diff --git a/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift new file mode 100644 index 0000000..973abdb --- /dev/null +++ b/Source/AlecrimCoreData/Convenience/NSTableView+Extensions.swift @@ -0,0 +1,145 @@ +// +// NSTableView+Extensions.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 04/04/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +#if os(macOS) + +import Foundation +import Cocoa + +extension FetchRequestController { + + /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. + @discardableResult + public func bind(to tableView: NSTableView, animationOptions: NSTableView.AnimationOptions = .effectFade, sectionOffset: Int = 0, animated: Bool = false, cellViewConfigurationHandler: ((NSTableCellView, IndexPath) -> Void)? = nil) -> Self { + // + var reloadData = false + var sectionChanges = Array>() + var itemChanges = Array>() + + // + func reset() { + reloadData = false + sectionChanges.removeAll() + itemChanges.removeAll() + } + + // + self + .needsReloadData { + reloadData = true + } + .willChangeContent { + if tableView.numberOfRows == 0 { + reloadData = true + } + + guard !reloadData else { return } + reset() + } + .didInsertSection { _, sectionIndex in + reloadData = true + } + .didDeleteSection { _, sectionIndex in + reloadData = true + } + .didInsertObject { _, newIndexPath in + guard !reloadData else { return } + + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + itemChanges.append(.insert(newIndexPath)) + } + .didDeleteObject { _, indexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.delete(indexPath)) + } + .didUpdateObject { _, indexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.update(indexPath)) + } + .didMoveObject { entity, indexPath, newIndexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + + itemChanges.append(.move(indexPath, newIndexPath)) + } + .didChangeContent { [weak tableView] in + // + defer { reset() } + + // + guard let tableView = tableView else { + return + } + + // + guard !reloadData else { + tableView.reloadData() + return + } + + // + let performer = animated ? tableView.animator() : tableView + + // + performer.beginUpdates() + + // + var updatedIndexPaths = [IndexPath]() + + itemChanges.forEach { + switch $0 { + case .update(let indexPath): + if cellViewConfigurationHandler == nil { + performer.reloadData(forRowIndexes: IndexSet(integer: indexPath.item), columnIndexes: IndexSet()) + } + else { + updatedIndexPaths.append(indexPath) + } + + case .delete(let indexPath): + performer.removeRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) + + case .insert(let indexPath): + performer.insertRows(at: IndexSet(integer: indexPath.item), withAnimation: animationOptions) + + case .move(let oldIndexPath, let newIndexPath): + //performer.moveRow(at: oldIndexPath.item, to: newIndexPath.item) + performer.removeRows(at: IndexSet(integer: oldIndexPath.item), withAnimation: animationOptions) + performer.insertRows(at: IndexSet(integer: newIndexPath.item), withAnimation: animationOptions) + } + } + + // + performer.endUpdates() + + // + updatedIndexPaths.forEach { + if let item = tableView.view(atColumn: 0, row: $0.item, makeIfNecessary: false) as? NSTableCellView { + cellViewConfigurationHandler?(item, $0) + } + } + } + + // + tableView.reloadData() + + // + return self + } + +} + +#endif + + diff --git a/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift b/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift index 3a1af49..6c66c19 100644 --- a/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/UICollectionView+Extensions.swift @@ -7,166 +7,143 @@ // #if os(iOS) || os(tvOS) - - import Foundation - import UIKit - - extension FetchRequestController { - - @discardableResult - public func bind(to collectionView: UICollectionView, sectionOffset: Int = 0, cellConfigurationHandler: ((CellType, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - - var reloadData = false - - // - func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - - reloadData = false + +import Foundation +import UIKit + +extension FetchRequestController { + + /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. + @discardableResult + public func bind(to collectionView: UICollectionView, sectionOffset: Int = 0, cellConfigurationHandler: ((UICollectionViewCell, IndexPath) -> Void)? = nil) -> Self { + // + var reloadData = false + var sectionChanges = Array>() + var itemChanges = Array>() + + // + func reset() { + reloadData = false + sectionChanges.removeAll() + itemChanges.removeAll() + } + + // + self + .needsReloadData { + reloadData = true } - - // - self - .needsReloadData { + .willChangeContent { + if collectionView.numberOfSections == 0 { reloadData = true } - .willChangeContent { - if !reloadData { - reset() - } - } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } - } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex } - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex } - } - } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } + + guard !reloadData else { return } + reset() + } + .didInsertSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.insert(sectionIndex)) + } + .didDeleteSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.delete(sectionIndex)) + } + .didInsertObject { _, newIndexPath in + guard !reloadData else { return } + + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + itemChanges.append(.insert(newIndexPath)) + } + .didDeleteObject { _, indexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.delete(indexPath)) + } + .didUpdateObject { _, indexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.update(indexPath)) + } + .didMoveObject { entity, indexPath, newIndexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + + itemChanges.append(.move(indexPath, newIndexPath)) + } + .didChangeContent { [weak collectionView] in + guard let collectionView = collectionView else { + reset() + return } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - } + + guard !reloadData else { + collectionView.reloadData() + reset() + return } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) + + // + var updatedIndexPaths = [IndexPath]() + + collectionView.performBatchUpdates({ + sectionChanges.forEach { + switch $0 { + case .insert(let sectionIndex): + collectionView.insertSections(IndexSet(integer: sectionIndex)) + + case .delete(let sectionIndex): + collectionView.deleteSections(IndexSet(integer: sectionIndex)) + + default: + break } } - } - .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } - } - else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) + + itemChanges.forEach { + switch $0 { + case .insert(let indexPath): + collectionView.insertItems(at: [indexPath]) + + case .delete(let indexPath): + collectionView.deleteItems(at: [indexPath]) + + case .update(let indexPath): + if cellConfigurationHandler == nil { + collectionView.reloadItems(at: [indexPath]) } - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) + else { + updatedIndexPaths.append(indexPath) } + + case .move(let oldIndexPath, let newIndexPath): + collectionView.moveItem(at: oldIndexPath, to: newIndexPath) } } - } - .didChangeContent { [weak collectionView] in - guard let collectionView = collectionView else { - reset() - return - } - - if reloadData { - collectionView.reloadData() - reset() - } - else { - collectionView.performBatchUpdates({ - if deletedSectionIndexes.count > 0 { - collectionView.deleteSections(deletedSectionIndexes as IndexSet) - } - - if insertedSectionIndexes.count > 0 { - collectionView.insertSections(insertedSectionIndexes as IndexSet) - } - - if updatedSectionIndexes.count > 0 { - collectionView.reloadSections(updatedSectionIndexes as IndexSet) - } - - if deletedItemIndexPaths.count > 0 { - collectionView.deleteItems(at: deletedItemIndexPaths) - } - - if insertedItemIndexPaths.count > 0 { - collectionView.insertItems(at: insertedItemIndexPaths) - } - - if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { - collectionView.reloadItems(at: updatedItemIndexPaths) - } - }, completion: { finished in - if finished { - if let cellConfigurationHandler = cellConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let cell = collectionView.cellForItem(at: updatedItemIndexPath) as? CellType { - cellConfigurationHandler(cell, updatedItemIndexPath) - } - } - } - - reset() - } - }) + }, completion: { _ in + updatedIndexPaths.forEach { + if let cell = collectionView.cellForItem(at: $0) { + cellConfigurationHandler?(cell, $0) + } } - } - - // - collectionView.reloadData() - - // - return self + + reset() + }) } - + + // + collectionView.reloadData() + + // + return self } - + +} + #endif + diff --git a/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift b/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift index 90bcab2..0416f87 100644 --- a/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift +++ b/Source/AlecrimCoreData/Convenience/UITableView+Extensions.swift @@ -7,168 +7,151 @@ // #if os(iOS) || os(tvOS) - - import Foundation - import UIKit - - extension FetchRequestController { - - @discardableResult - public func bind(to tableView: UITableView, rowAnimation: UITableViewRowAnimation = .fade, sectionOffset: Int = 0, cellConfigurationHandler: ((CellType, IndexPath) -> Void)? = nil) -> Self { - let insertedSectionIndexes = NSMutableIndexSet() - let deletedSectionIndexes = NSMutableIndexSet() - let updatedSectionIndexes = NSMutableIndexSet() - - var insertedItemIndexPaths = [IndexPath]() - var deletedItemIndexPaths = [IndexPath]() - var updatedItemIndexPaths = [IndexPath]() - - var reloadData = false - - // - func reset() { - insertedSectionIndexes.removeAllIndexes() - deletedSectionIndexes.removeAllIndexes() - updatedSectionIndexes.removeAllIndexes() - - insertedItemIndexPaths.removeAll(keepingCapacity: false) - deletedItemIndexPaths.removeAll(keepingCapacity: false) - updatedItemIndexPaths.removeAll(keepingCapacity: false) - - reloadData = false + +import Foundation +import UIKit + +extension FetchRequestController { + + /// WARNING: To avoid memory leaks do not pass a func as the configuration handler, pass a closure with *weak* self. + @discardableResult + public func bind(to tableView: UITableView, rowAnimation: UITableViewRowAnimation = .fade, sectionOffset: Int = 0, cellConfigurationHandler: ((UITableViewCell, IndexPath) -> Void)? = nil) -> Self { + // + var reloadData = false + var sectionChanges = Array>() + var itemChanges = Array>() + + // + func reset() { + reloadData = false + sectionChanges.removeAll() + itemChanges.removeAll() + } + + // + self + .needsReloadData { + reloadData = true } - - // - self - .needsReloadData { + .willChangeContent { + if tableView.numberOfSections == 0 { reloadData = true } - .willChangeContent { - if !reloadData { - // - reset() - } - } - .didInsertSection { sectionInfo, sectionIndex in - if !reloadData { - insertedSectionIndexes.add(sectionIndex + sectionOffset) - } - } - .didDeleteSection { sectionInfo, sectionIndex in - if !reloadData { - deletedSectionIndexes.add(sectionIndex + sectionOffset) - deletedItemIndexPaths = deletedItemIndexPaths.filter { $0.section != sectionIndex} - updatedItemIndexPaths = updatedItemIndexPaths.filter { $0.section != sectionIndex} - } - } - .didInsertObject { entity, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(row: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } - } + + guard !reloadData else { return } + reset() + } + .didInsertSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.insert(sectionIndex)) + } + .didDeleteSection { _, sectionIndex in + guard !reloadData else { return } + sectionChanges.append(.delete(sectionIndex)) + } + .didInsertObject { _, newIndexPath in + guard !reloadData else { return } + + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + itemChanges.append(.insert(newIndexPath)) + } + .didDeleteObject { _, indexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.delete(indexPath)) + } + .didUpdateObject { _, indexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + itemChanges.append(.update(indexPath)) + } + .didMoveObject { entity, indexPath, newIndexPath in + guard !reloadData else { return } + + let indexPath = sectionOffset > 0 ? IndexPath(item: indexPath.item, section: indexPath.section + sectionOffset) : indexPath + let newIndexPath = sectionOffset > 0 ? IndexPath(item: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath + + itemChanges.append(.move(indexPath, newIndexPath)) + } + .didChangeContent { [weak tableView] in + // + defer { reset() } + + // + guard let tableView = tableView else { + return } - .didDeleteObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - } + + // + guard !reloadData else { + tableView.reloadData() + return } - .didUpdateObject { entity, indexPath in - if !reloadData { - let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } + + // + tableView.beginUpdates() + + // + sectionChanges.forEach { + switch $0 { + case .delete(let sectionIndex): + tableView.deleteSections(IndexSet(integer: sectionIndex), with: rowAnimation) + + case .insert(let sectionIndex): + tableView.insertSections(IndexSet(integer: sectionIndex), with: rowAnimation) + + default: + break } } - .didMoveObject { entity, indexPath, newIndexPath in - if !reloadData { - let newIndexPath = sectionOffset > 0 ? IndexPath(row: newIndexPath.item, section: newIndexPath.section + sectionOffset) : newIndexPath - let indexPath = sectionOffset > 0 ? IndexPath(row: indexPath.item, section: indexPath.section + sectionOffset) : indexPath - - if newIndexPath == indexPath { - if !deletedSectionIndexes.contains(indexPath.section) && deletedItemIndexPaths.index(of: indexPath) == nil && updatedItemIndexPaths.index(of: indexPath) == nil { - updatedItemIndexPaths.append(indexPath) - } + + // + var updatedIndexPaths = [IndexPath]() + + itemChanges.forEach { + switch $0 { + case .update(let indexPath): + if cellConfigurationHandler == nil { + tableView.reloadRows(at: [indexPath], with: rowAnimation) } else { - if !deletedSectionIndexes.contains(indexPath.section) { - deletedItemIndexPaths.append(indexPath) - } - - if !insertedSectionIndexes.contains(newIndexPath.section) { - insertedItemIndexPaths.append(newIndexPath) - } + updatedIndexPaths.append(indexPath) } + + case .delete(let indexPath): + tableView.deleteRows(at: [indexPath], with: rowAnimation) + + case .insert(let indexPath): + tableView.insertRows(at: [indexPath], with: rowAnimation) + + case .move(let oldIndexPath, let newIndexPath): + //tableView.moveRow(at: oldIndexPath, to: newIndexPath) + tableView.deleteRows(at: [oldIndexPath], with: rowAnimation) + tableView.insertRows(at: [newIndexPath], with: rowAnimation) } } - .didChangeContent { [weak tableView] in - // - defer { reset() } - - // - guard let tableView = tableView else { - return - } - - // - if reloadData { - tableView.reloadData() - } - else { - tableView.beginUpdates() - - if deletedSectionIndexes.count > 0 { - tableView.deleteSections(deletedSectionIndexes as IndexSet, with: rowAnimation) - } - - if insertedSectionIndexes.count > 0 { - tableView.insertSections(insertedSectionIndexes as IndexSet, with: rowAnimation) - } - - if updatedSectionIndexes.count > 0 { - tableView.reloadSections(updatedSectionIndexes as IndexSet, with: rowAnimation) - } - - if deletedItemIndexPaths.count > 0 { - tableView.deleteRows(at: deletedItemIndexPaths, with: rowAnimation) - } - - if insertedItemIndexPaths.count > 0 { - tableView.insertRows(at: insertedItemIndexPaths, with: rowAnimation) - } - - if updatedItemIndexPaths.count > 0 && cellConfigurationHandler == nil { - tableView.reloadRows(at: updatedItemIndexPaths, with: rowAnimation) - } - - tableView.endUpdates() - - if let cellConfigurationHandler = cellConfigurationHandler { - for updatedItemIndexPath in updatedItemIndexPaths { - if let cell = tableView.cellForRow(at: updatedItemIndexPath) as? CellType { - cellConfigurationHandler(cell, updatedItemIndexPath) - } - } - } + + // + tableView.endUpdates() + + // + updatedIndexPaths.forEach { + if let item = tableView.cellForRow(at: $0) { + cellConfigurationHandler?(item, $0) } - } - - // - tableView.reloadData() - - // - return self + } } - + + // + tableView.reloadData() + + // + return self } - + +} + #endif diff --git a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift deleted file mode 100644 index 941cc60..0000000 --- a/Source/AlecrimCoreData/Core/Container/PersistentContainer.swift +++ /dev/null @@ -1,285 +0,0 @@ -// -// PersistentContainer.swift -// AlecrimCoreData -// -// Created by Vanderlei Martinelli on 11/03/18. -// Copyright © 2018 Alecrim. All rights reserved. -// - -import Foundation -import CoreData - -// MARK: - - -@objc(ALCPersistentContainer) -open class PersistentContainer: NSPersistentContainer { - - // MARK: - - - private var didImportUbiquitousContentNotificationObserver: NSObjectProtocol? - - // MARK: - - - public convenience init() { - 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 { - // - let name = persistentStoreURL.deletingPathExtension().lastPathComponent - super.init(name: name, managedObjectModel: managedObjectModel) - - // - if storageType == .disk { - let directoryPath = persistentStoreURL.deletingLastPathComponent().path - - if !FileManager.default.fileExists(atPath: directoryPath) { - try FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: true) - } - } - - // - let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreURL) - - persistentStoreDescription.type = (storageType == .disk ? NSSQLiteStoreType : NSInMemoryStoreType) - persistentStoreDescription.shouldAddStoreAsynchronously = false - persistentStoreDescription.shouldInferMappingModelAutomatically = true - persistentStoreDescription.shouldMigrateStoreAutomatically = true - - #if os(macOS) || os(iOS) - if let ubiquitousConfiguration = ubiquitousConfiguration { - persistentStoreDescription.setOption(ubiquitousConfiguration.containerIdentifier as NSString, forKey: NSPersistentStoreUbiquitousContainerIdentifierKey) - persistentStoreDescription.setOption(ubiquitousConfiguration.contentRelativePath as NSString, forKey: NSPersistentStoreUbiquitousContentURLKey) - persistentStoreDescription.setOption(ubiquitousConfiguration.contentName as NSString, forKey: NSPersistentStoreUbiquitousContentNameKey) - } - #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 - } - - // - #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 { - return - } - - context.perform { - context.mergeChanges(fromContextDidSave: notification) - } - } - } - #endif - - // - self.viewContext.automaticallyMergesChangesFromParent = true - self.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - } - - if let outError = outError { - throw outError - } - } - - deinit { - if let didImportUbiquitousContentNotificationObserver = self.didImportUbiquitousContentNotificationObserver { - self.didImportUbiquitousContentNotificationObserver = nil - NotificationCenter.default.removeObserver(didImportUbiquitousContentNotificationObserver) - } - } - - // MARK: - - - open override func newBackgroundContext() -> NSManagedObjectContext { - let context = super.newBackgroundContext() - - context.automaticallyMergesChangesFromParent = true - context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy - - return context - } -} - -// MARK: - - -open class GenericPersistentContainer { - - // 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 - } - - } - - - // MARK: - - - fileprivate let rawValue: NSPersistentContainer - - // MARK: - - - public convenience init() { - 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) - } - - // MARK: - - - public final var viewContext: Context { - return unsafeDowncast(self.rawValue.viewContext, to: Context.self) - } - - public final func newBackgroundContext() -> Context { - return unsafeDowncast(self.rawValue.newBackgroundContext(), to: Context.self) - - } - - public final func performBackgroundTask(_ block: @escaping (Context) -> Void) { - self.rawValue.performBackgroundTask { managedObjectContext in - block(unsafeDowncast(managedObjectContext, to: Context.self)) - } - } - -} - - -// MARK: - - -public enum PersistentContainerStorageType { - case disk - case memory -} - - -public struct PersistentContainerUbiquitousConfiguration { - public let containerIdentifier: String - public let contentRelativePath: String - public let contentName: String - - public init(containerIdentifier: String, contentRelativePath: String = "Data/TransactionLogs", contentName: String = "UbiquityStore") { - self.containerIdentifier = containerIdentifier - self.contentRelativePath = contentRelativePath - self.contentName = contentName - } - -} - -public enum PersistentContainerError: Error { - case invalidManagedObjectModelURL - case invalidPersistentStoreURL - case invalidGroupContainerURL - case applicationSupportDirectoryNotFound - case managedObjectModelNotFound -} - - -// MARK: - - -public protocol PersistentContainerType: class {} - -extension PersistentContainer: PersistentContainerType {} -extension GenericPersistentContainer: PersistentContainerType {} - -extension PersistentContainerType { - - public static func managedObjectModel(withName name: String? = nil, in bundle: Bundle? = nil) throws -> NSManagedObjectModel { - let bundle = bundle ?? Bundle(for: Self.self) - let name = name ?? bundle.bundleURL.deletingPathExtension().lastPathComponent - - let managedObjectModelURL = try self.managedObjectModelURL(withName: name, in: bundle) - - guard let managedObjectModel = NSManagedObjectModel(contentsOf: managedObjectModelURL) else { - throw PersistentContainerError.managedObjectModelNotFound - } - - return managedObjectModel - } - - private static func managedObjectModelURL(withName name: String, in bundle: Bundle) throws -> URL { - let resourceURL = bundle.url(forResource: name, withExtension: "momd") ?? bundle.url(forResource: name, withExtension: "mom") - - guard let managedObjectModelURL = resourceURL else { - throw PersistentContainerError.invalidManagedObjectModelURL - } - - return managedObjectModelURL - } - -} - -extension PersistentContainerType { - - public static func persistentStoreURL(withName name: String? = nil, in bundle: Bundle? = 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 persistentStoreURL = applicationSupportURL - .appendingPathComponent(bundleLastPathComponent, isDirectory: true) - .appendingPathComponent("CoreData", isDirectory: true) - .appendingPathComponent(name, isDirectory: false) - .appendingPathExtension("sqlite") - - return persistentStoreURL - } - - public static func persistentStoreURL(withName name: String? = nil, forSecurityApplicationGroupIdentifier applicationGroupIdentifier: String, in bundle: Bundle? = nil) 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 persistentStoreURL = containerURL - .appendingPathComponent("Library", isDirectory: true) - .appendingPathComponent("Application Support", isDirectory: true) - .appendingPathComponent(bundleLastPathComponent, isDirectory: true) - .appendingPathComponent("CoreData", isDirectory: true) - .appendingPathComponent(name, isDirectory: false) - .appendingPathExtension("sqlite") - - return persistentStoreURL - } - -} diff --git a/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift new file mode 100644 index 0000000..55ba6b3 --- /dev/null +++ b/Source/AlecrimCoreData/Core/Persistent Container/CustomPersistentContainer.swift @@ -0,0 +1,89 @@ +// +// CustomPersistentContainer.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 20/05/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation +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) + } + } + + } + + + // MARK: - + + fileprivate let rawValue: NSPersistentContainer + + // MARK: - + + public convenience init() { + 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) + } + + // MARK: - + + open var viewContext: Context { + return unsafeDowncast(self.rawValue.viewContext, to: Context.self) + } + + open func newBackgroundContext() -> Context { + return unsafeDowncast(self.rawValue.newBackgroundContext(), to: Context.self) + } + + open func performBackgroundTask(_ block: @escaping (Context) -> Void) { + self.rawValue.performBackgroundTask { managedObjectContext in + block(unsafeDowncast(managedObjectContext, to: Context.self)) + } + } + +} diff --git a/Source/AlecrimCoreData/Core/Container/ManagedObject.swift b/Source/AlecrimCoreData/Core/Persistent Container/ManagedObject.swift similarity index 100% rename from Source/AlecrimCoreData/Core/Container/ManagedObject.swift rename to Source/AlecrimCoreData/Core/Persistent Container/ManagedObject.swift diff --git a/Source/AlecrimCoreData/Core/Container/ManagedObjectContext.swift b/Source/AlecrimCoreData/Core/Persistent Container/ManagedObjectContext.swift similarity index 100% rename from Source/AlecrimCoreData/Core/Container/ManagedObjectContext.swift rename to Source/AlecrimCoreData/Core/Persistent Container/ManagedObjectContext.swift diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift new file mode 100644 index 0000000..f825ccd --- /dev/null +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainer.swift @@ -0,0 +1,119 @@ +// +// PersistentContainer.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 11/03/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation +import CoreData + +@objc(ALCPersistentContainer) +open class PersistentContainer: NSPersistentContainer { + + // MARK: - + + private var didImportUbiquitousContentNotificationObserver: NSObjectProtocol? + + // MARK: - + + public convenience init() { + 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 { + // + let name = persistentStoreURL.deletingPathExtension().lastPathComponent + super.init(name: name, managedObjectModel: managedObjectModel) + + // + if storageType == .disk { + let directoryPath = persistentStoreURL.deletingLastPathComponent().path + + if !FileManager.default.fileExists(atPath: directoryPath) { + try FileManager.default.createDirectory(atPath: directoryPath, withIntermediateDirectories: true) + } + } + + // + let persistentStoreDescription = NSPersistentStoreDescription(url: persistentStoreURL) + + persistentStoreDescription.type = (storageType == .disk ? NSSQLiteStoreType : NSInMemoryStoreType) + persistentStoreDescription.shouldAddStoreAsynchronously = false + persistentStoreDescription.shouldInferMappingModelAutomatically = true + persistentStoreDescription.shouldMigrateStoreAutomatically = true + + #if os(macOS) || os(iOS) + if let ubiquitousConfiguration = ubiquitousConfiguration { + persistentStoreDescription.setOption(ubiquitousConfiguration.containerIdentifier as NSString, forKey: NSPersistentStoreUbiquitousContainerIdentifierKey) + persistentStoreDescription.setOption(ubiquitousConfiguration.contentRelativePath as NSString, forKey: NSPersistentStoreUbiquitousContentURLKey) + persistentStoreDescription.setOption(ubiquitousConfiguration.contentName as NSString, forKey: NSPersistentStoreUbiquitousContentNameKey) + } + #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 + } + + // + #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 { + return + } + + context.perform { + context.mergeChanges(fromContextDidSave: notification) + } + } + } + #endif + + // + self.viewContext.automaticallyMergesChangesFromParent = true + self.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + } + + if let outError = outError { + throw outError + } + } + + deinit { + if let didImportUbiquitousContentNotificationObserver = self.didImportUbiquitousContentNotificationObserver { + self.didImportUbiquitousContentNotificationObserver = nil + NotificationCenter.default.removeObserver(didImportUbiquitousContentNotificationObserver) + } + } + + // MARK: - + + open override func newBackgroundContext() -> NSManagedObjectContext { + let context = super.newBackgroundContext() + + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + return context + } + + open override func performBackgroundTask(_ block: @escaping (NSManagedObjectContext) -> Void) { + super.performBackgroundTask { context in + // + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + + // + block(context) + } + } +} diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerAuxiliarTypes.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerAuxiliarTypes.swift new file mode 100644 index 0000000..a63340b --- /dev/null +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerAuxiliarTypes.swift @@ -0,0 +1,35 @@ +// +// PersistentContainerAuxiliarTypes.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 20/05/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation + +public enum PersistentContainerStorageType { + case disk + case memory +} + +public struct PersistentContainerUbiquitousConfiguration { + public let containerIdentifier: String + public let contentRelativePath: String + public let contentName: String + + public init(containerIdentifier: String, contentRelativePath: String = "Data/TransactionLogs", contentName: String = "UbiquityStore") { + self.containerIdentifier = containerIdentifier + self.contentRelativePath = contentRelativePath + self.contentName = contentName + } + +} + +public enum PersistentContainerError: Error { + case invalidManagedObjectModelURL + case invalidPersistentStoreURL + case invalidGroupContainerURL + case applicationSupportDirectoryNotFound + case managedObjectModelNotFound +} diff --git a/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift new file mode 100644 index 0000000..5b93b2d --- /dev/null +++ b/Source/AlecrimCoreData/Core/Persistent Container/PersistentContainerType.swift @@ -0,0 +1,84 @@ +// +// PersistentContainerType.swift +// AlecrimCoreData +// +// Created by Vanderlei Martinelli on 20/05/18. +// Copyright © 2018 Alecrim. All rights reserved. +// + +import Foundation +import CoreData + +public protocol PersistentContainerType: AnyObject {} + +extension PersistentContainer: PersistentContainerType {} +extension CustomPersistentContainer: PersistentContainerType {} + +extension PersistentContainerType { + + public static func managedObjectModel(withName name: String? = nil, in bundle: Bundle? = nil) throws -> NSManagedObjectModel { + let bundle = bundle ?? Bundle(for: Self.self) + let name = name ?? bundle.bundleURL.deletingPathExtension().lastPathComponent + + let managedObjectModelURL = try self.managedObjectModelURL(withName: name, in: bundle) + + guard let managedObjectModel = NSManagedObjectModel(contentsOf: managedObjectModelURL) else { + throw PersistentContainerError.managedObjectModelNotFound + } + + return managedObjectModel + } + + private static func managedObjectModelURL(withName name: String, in bundle: Bundle) throws -> URL { + let resourceURL = bundle.url(forResource: name, withExtension: "momd") ?? bundle.url(forResource: name, withExtension: "mom") + + guard let managedObjectModelURL = resourceURL else { + throw PersistentContainerError.invalidManagedObjectModelURL + } + + return managedObjectModelURL + } + +} + +extension PersistentContainerType { + + public static func persistentStoreURL(withName name: String? = nil, in bundle: Bundle? = 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 persistentStoreURL = applicationSupportURL + .appendingPathComponent(bundleLastPathComponent, isDirectory: true) + .appendingPathComponent("CoreData", isDirectory: true) + .appendingPathComponent(name, isDirectory: false) + .appendingPathExtension("sqlite") + + return persistentStoreURL + } + + public static func persistentStoreURL(withName name: String? = nil, forSecurityApplicationGroupIdentifier applicationGroupIdentifier: String, in bundle: Bundle? = nil) 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 persistentStoreURL = containerURL + .appendingPathComponent("Library", isDirectory: true) + .appendingPathComponent("Application Support", isDirectory: true) + .appendingPathComponent(bundleLastPathComponent, isDirectory: true) + .appendingPathComponent("CoreData", isDirectory: true) + .appendingPathComponent(name, isDirectory: false) + .appendingPathExtension("sqlite") + + return persistentStoreURL + } + +} diff --git a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift index 94b78aa..a4bb8d3 100644 --- a/Source/AlecrimCoreData/Core/Query/FetchRequest.swift +++ b/Source/AlecrimCoreData/Core/Query/FetchRequest.swift @@ -47,14 +47,14 @@ public struct FetchRequest: Queryable { extension FetchRequest { - public func skip(_ offset: Int) -> FetchRequest { + public func dropFirst(_ n: Int) -> FetchRequest { var clone = self clone.offset = offset return clone } - public func take(_ limit: Int) -> FetchRequest { + public func prefix(_ maxLength: Int) -> FetchRequest { var clone = self clone.limit = limit diff --git a/Source/AlecrimCoreData/Core/Query/Query.swift b/Source/AlecrimCoreData/Core/Query/Query.swift index b34ea90..f96e71b 100644 --- a/Source/AlecrimCoreData/Core/Query/Query.swift +++ b/Source/AlecrimCoreData/Core/Query/Query.swift @@ -48,6 +48,16 @@ extension Query { return try! self.context.count(for: fetchRequest.toRaw()) } + // + + public func any() -> Bool { + return self.prefix(1).count() > 0 + } + + public func none() -> Bool { + return !self.any() + } + } // MARK: - @@ -55,7 +65,7 @@ extension Query { extension Query { public func first() -> Entity? { - return self.execute(fetchRequest: self.fetchRequest.take(1)).first + return self.execute(fetchRequest: self.fetchRequest.prefix(1)).first } } @@ -78,25 +88,25 @@ extension Query { extension Query { - public func firstOrNewEntity(where predicate: Predicate) -> Entity { + public func firstOrEmptyNew(where predicate: Predicate) -> Entity { guard let existingEntity = self.filtered(using: predicate).first() else { - return self.newEntity() + return self.new() } return existingEntity } - public func firstOrNewEntity(where rawValue: NSPredicate) -> Entity { + public func firstOrEmptyNew(where rawValue: NSPredicate) -> Entity { guard let existingEntity = self.filtered(using: Predicate(rawValue: rawValue)).first() else { - return self.newEntity() + return self.new() } return existingEntity } - public func firstOrNewEntity(where closure: () -> Predicate) -> Entity { + public func firstOrEmptyNew(where closure: () -> Predicate) -> Entity { guard let existingEntity = self.filtered(using: closure()).first() else { - return self.newEntity() + return self.new() } return existingEntity @@ -108,13 +118,14 @@ extension Query { // MARK: - extension Query: Sequence { - + + // MARK: - + public func makeIterator() -> QueryIterator { return QueryIterator(self.execute()) } public struct QueryIterator: IteratorProtocol { - private let entities: [Entity] private var index: Int @@ -133,20 +144,42 @@ extension Query: Sequence { return self.entities[index] } } - + + // MARK: - + + public func dropLast(_ n: Int) -> Query { + fatalError("Not applicable or not available.") + } + + public func drop(while predicate: (Entity) throws -> Bool) rethrows -> Query { + fatalError("Not applicable or not available.") + } + + public func prefix(while predicate: (Entity) throws -> Bool) rethrows -> Query { + fatalError("Not applicable or not available.") + } + + public func suffix(_ maxLength: Int) -> Query { + fatalError("Not applicable or not available.") + } + + public func split(maxSplits: Int, omittingEmptySubsequences: Bool, whereSeparator isSeparator: (Entity) throws -> Bool) rethrows -> [Query] { + fatalError("Not applicable or not available.") + } + } // MARK: - extension Query { - public func newEntity() -> Entity { + public func new() -> Entity { return Entity(context: self.context) } @discardableResult public func insert(with entityPropertiesInitializationClosure: (Entity) -> Void) -> Entity { - let entity = self.newEntity() + let entity = self.new() entityPropertiesInitializationClosure(entity) return entity @@ -197,16 +230,16 @@ extension Query { extension Query: Queryable { - public func skip(_ offset: Int) -> Query { + public func dropFirst(_ n: Int) -> Query { var clone = self - clone.fetchRequest = clone.fetchRequest.skip(offset) + clone.fetchRequest = clone.fetchRequest.dropFirst(n) return clone } - public func take(_ limit: Int) -> Query { + public func prefix(_ maxLength: Int) -> Query { var clone = self - clone.fetchRequest = clone.fetchRequest.take(limit) + clone.fetchRequest = clone.fetchRequest.prefix(maxLength) return clone } diff --git a/Source/AlecrimCoreData/Core/Query/Queryable.swift b/Source/AlecrimCoreData/Core/Query/Queryable.swift index 5137e29..692ba48 100644 --- a/Source/AlecrimCoreData/Core/Query/Queryable.swift +++ b/Source/AlecrimCoreData/Core/Query/Queryable.swift @@ -15,8 +15,8 @@ public protocol Queryable { associatedtype Element: ManagedObject - func skip(_ offset: Int) -> Self - func take(_ limit: Int) -> Self + func dropFirst(_ n: Int) -> Self + func prefix(_ maxLength: Int) -> Self func setBatchSize(_ batchSize: Int) -> Self func filtered(using predicate: Predicate) -> Self diff --git a/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift b/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift index 270f7a9..693ff72 100644 --- a/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift +++ b/Source/AlecrimCoreData/Fetch Request Controller/FetchedResultsControllerDelegate.swift @@ -114,6 +114,8 @@ public func +=(left: ClosuresContainer, right: Closure) { extension FetchRequestController { public func removeAllBindings() { + self.performFetchIfNeeded() + self.rawValueDelegate.needsReloadDataClosure = nil self.rawValueDelegate.willChangeContentClosuresContainer.removeAll() @@ -135,8 +137,10 @@ extension FetchRequestController { extension FetchRequestController { public func refresh() { + self.performFetchIfNeeded() + + self.rawValueDelegate.needsReloadDataClosure?() - self.rawValueDelegate.willChangeContentClosuresContainer.closures.forEach { $0() } if let cacheName = self.rawValue.cacheName { @@ -154,6 +158,8 @@ extension FetchRequestController { @discardableResult internal func needsReloadData(closure: @escaping FetchedResultsControllerDelegate.NeedsReloadDataClosure) -> Self { + self.performFetchIfNeeded() + self.rawValueDelegate.needsReloadDataClosure = closure return self } @@ -165,54 +171,72 @@ 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 } diff --git a/Source/AlecrimCoreData/Info.plist b/Source/AlecrimCoreData/Info.plist index bb6321d..e2953de 100644 --- a/Source/AlecrimCoreData/Info.plist +++ b/Source/AlecrimCoreData/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 1.0 + 6.0 CFBundleVersion $(CURRENT_PROJECT_VERSION) NSHumanReadableCopyright