From 33a5c123aabce25de84559f421a345316a77ba6d Mon Sep 17 00:00:00 2001 From: John Estropia Date: Fri, 11 Nov 2016 17:32:13 +0900 Subject: [PATCH] resurrect utilities for creating NSFetchedResultsController (fixes #119) --- CoreStore.xcodeproj/project.pbxproj | 8 + CoreStoreTests/BridgingTests.m | 32 ++ CoreStoreTests/ConvenienceTests.swift | 94 +++++ ...FetchedResultsController+Convenience.swift | 382 +++++++++--------- .../BaseDataTransaction+Importing.swift | 19 +- Sources/Info.plist | 2 +- ...SFetchedResultsController+ObjectiveC.swift | 112 +++-- 7 files changed, 405 insertions(+), 244 deletions(-) create mode 100644 CoreStoreTests/ConvenienceTests.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 2179563..468c9aa 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -362,6 +362,9 @@ B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; }; B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; }; B58B22F51C93C1BA00521925 /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A53019C5C6DA005002A5 /* CoreStore.framework */; }; + B596BBB21DD5A014001DCDD9 /* ConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */; }; + B596BBB31DD5A014001DCDD9 /* ConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */; }; + B596BBB41DD5A016001DCDD9 /* ConvenienceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */; }; B59851491C90289D00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59AFF401C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift */; }; B598514A1C90289E00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59AFF401C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift */; }; B598514B1C90289F00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59AFF401C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift */; }; @@ -648,6 +651,7 @@ B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = ""; }; B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = ""; }; B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = ""; }; + B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvenienceTests.swift; sourceTree = ""; }; B59AFF401C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSPersistentStoreCoordinator+Setup.swift"; sourceTree = ""; }; B59FA0AD1CCBAC95007C9BCA /* ICloudStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ICloudStore.swift; sourceTree = ""; }; B5A261201B64BFDB006EB6D3 /* MigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationType.swift; sourceTree = ""; }; @@ -872,6 +876,7 @@ B52557831D02A07400E51965 /* SectionByTests.swift */, B525576F1D02561A00E51965 /* SelectTests.swift */, B58085741CDF7F00004C2EEB /* SetupTests.swift */, + B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */, B5DC47C91C93D9C800FA3BF3 /* StorageInterfaceTests.swift */, 2F03A53D19C5C6DA005002A5 /* Supporting Files */, B5489F3C1CF5EEBC008B4978 /* TestEntities */, @@ -1640,6 +1645,7 @@ buildActionMask = 2147483647; files = ( B52557841D02A07400E51965 /* SectionByTests.swift in Sources */, + B596BBB21DD5A014001DCDD9 /* ConvenienceTests.swift in Sources */, B5220E101D0DA6AB009BC71E /* ListObserverTests.swift in Sources */, B5519A401CA1B17B002BEF78 /* ErrorTests.swift in Sources */, B525577C1D0291FE00E51965 /* GroupByTests.swift in Sources */, @@ -1789,6 +1795,7 @@ buildActionMask = 2147483647; files = ( B52557851D02A07400E51965 /* SectionByTests.swift in Sources */, + B596BBB31DD5A014001DCDD9 /* ConvenienceTests.swift in Sources */, B5220E111D0DA6AB009BC71E /* ListObserverTests.swift in Sources */, B5519A411CA1B17B002BEF78 /* ErrorTests.swift in Sources */, B525577D1D0291FE00E51965 /* GroupByTests.swift in Sources */, @@ -1938,6 +1945,7 @@ buildActionMask = 2147483647; files = ( B5519A421CA1B17B002BEF78 /* ErrorTests.swift in Sources */, + B596BBB41DD5A016001DCDD9 /* ConvenienceTests.swift in Sources */, B5220E271D1308D1009BC71E /* ObjectObserverTests.swift in Sources */, B525577E1D0291FE00E51965 /* GroupByTests.swift in Sources */, B52557761D02791400E51965 /* WhereTests.swift in Sources */, diff --git a/CoreStoreTests/BridgingTests.m b/CoreStoreTests/BridgingTests.m index 04c2efa..77c18df 100644 --- a/CoreStoreTests/BridgingTests.m +++ b/CoreStoreTests/BridgingTests.m @@ -233,5 +233,37 @@ } [self waitForExpectationsWithTimeout:10 handler:nil]; } + +#if TARGET_OS_IOS || TARGET_OS_WATCHOS || TARGET_OS_TV + +- (void)test_ThatDataStacks_CanCreateCustomFetchedResultsControllers { + + [CSCoreStore + setDefaultStack:[[CSDataStack alloc] + initWithModelName:@"Model" + bundle:[NSBundle bundleForClass:[self class]] + versionChain:nil]]; + [CSCoreStore + addInMemoryStorageAndWait:[CSInMemoryStore new] + error:nil]; + NSFetchedResultsController *controller = + [[CSCoreStore defaultStack] + createFetchedResultsControllerFrom:CSFromClass([TestEntity1 class]) + sectionBy:[CSSectionBy keyPath:CSKeyPath(TestEntity1, testString)] + fetchClauses:@[CSWhereFormat(@"%K > %d", CSKeyPath(TestEntity1, testEntityID), 100), + CSOrderByKeys(CSSortAscending(CSKeyPath(TestEntity1, testString)), nil), + CSTweakRequest(^(NSFetchRequest *fetchRequest) { fetchRequest.fetchLimit = 10; })]]; + + XCTAssertNotNil(controller); + XCTAssertEqualObjects(controller.fetchRequest.entity.managedObjectClassName, [[TestEntity1 class] description]); + XCTAssertEqualObjects(controller.sectionNameKeyPath, CSKeyPath(TestEntity1, testString)); + XCTAssertEqualObjects(controller.fetchRequest.predicate, + CSWhereFormat(@"%K > %d", CSKeyPath(TestEntity1, testEntityID), 100).predicate); + XCTAssertEqualObjects(controller.fetchRequest.sortDescriptors, + CSOrderByKeys(CSSortAscending(CSKeyPath(TestEntity1, testString)), nil).sortDescriptors); + XCTAssertEqual(controller.fetchRequest.fetchLimit, 10); +} + +#endif @end diff --git a/CoreStoreTests/ConvenienceTests.swift b/CoreStoreTests/ConvenienceTests.swift new file mode 100644 index 0000000..6fea9ba --- /dev/null +++ b/CoreStoreTests/ConvenienceTests.swift @@ -0,0 +1,94 @@ +// +// ConvenienceTests.swift +// CoreStore +// +// Copyright © 2016 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +@testable +import CoreStore + + +#if os(iOS) || os(watchOS) || os(tvOS) + +// MARK: - ConvenienceTests + +class ConvenienceTests: BaseTestCase { + + @objc + dynamic func test_ThatDataStacks_CanCreateFetchedResultsControllers() { + + self.prepareStack { (stack) in + + let controller = stack.createFetchedResultsController( + From(), + SectionBy(#keyPath(TestEntity1.testString)), + Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100), + OrderBy(.ascending(#keyPath(TestEntity1.testString))), + Tweak { $0.fetchLimit = 10 } + ) + XCTAssertEqual(controller.managedObjectContext, stack.mainContext) + XCTAssertEqual(controller.fetchRequest.entity?.managedObjectClassName, NSStringFromClass(TestEntity1.self)) + XCTAssertEqual(controller.sectionNameKeyPath, #keyPath(TestEntity1.testString)) + XCTAssertEqual( + controller.fetchRequest.sortDescriptors!, + OrderBy(.ascending(#keyPath(TestEntity1.testString))).sortDescriptors + ) + XCTAssertEqual( + controller.fetchRequest.predicate, + Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100).predicate + ) + XCTAssertEqual(controller.fetchRequest.fetchLimit, 10) + } + } + + @objc + dynamic func test_ThatUnsafeDataTransactions_CanCreateFetchedResultsControllers() { + + self.prepareStack { (stack) in + + _ = withExtendedLifetime(stack.beginUnsafe()) { (transaction: UnsafeDataTransaction) in + + let controller = transaction.createFetchedResultsController( + From(), + SectionBy(#keyPath(TestEntity1.testString)), + Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100), + OrderBy(.ascending(#keyPath(TestEntity1.testString))), + Tweak { $0.fetchLimit = 10 } + ) + XCTAssertEqual(controller.managedObjectContext, transaction.context) + XCTAssertEqual(controller.fetchRequest.entity?.managedObjectClassName, NSStringFromClass(TestEntity1.self)) + XCTAssertEqual(controller.sectionNameKeyPath, #keyPath(TestEntity1.testString)) + XCTAssertEqual( + controller.fetchRequest.sortDescriptors!, + OrderBy(.ascending(#keyPath(TestEntity1.testString))).sortDescriptors + ) + XCTAssertEqual( + controller.fetchRequest.predicate, + Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100).predicate + ) + XCTAssertEqual(controller.fetchRequest.fetchLimit, 10) + } + } + } +} + +#endif diff --git a/Sources/Convenience/NSFetchedResultsController+Convenience.swift b/Sources/Convenience/NSFetchedResultsController+Convenience.swift index 06ea096..9d53066 100644 --- a/Sources/Convenience/NSFetchedResultsController+Convenience.swift +++ b/Sources/Convenience/NSFetchedResultsController+Convenience.swift @@ -26,199 +26,189 @@ import Foundation import CoreData -// TODO: Uncomment -//#if os(iOS) || os(watchOS) || os(tvOS) -// -//// MARK: - NSFetchedResultsController -// -//public extension NSFetchedResultsController { -// -// /** -// Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. -// -// - parameter dataStack: the `DataStack` to observe objects from -// - parameter from: a `From` clause indicating the entity type -// - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections -// - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. -// - returns: an `NSFetchedResultsController` that observes a `DataStack` -// */ -// @nonobjc -// public static func createFor(_ dataStack: DataStack, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController { -// -// return self.createFromContext( -// dataStack.mainContext, -// fetchRequest: CoreStoreFetchRequest(), -// from: from, -// sectionBy: sectionBy, -// fetchClauses: fetchClauses -// ) -// } -// -// /** -// Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. -// -// - parameter dataStack: the `DataStack` to observe objects from -// - parameter from: a `From` clause indicating the entity type -// - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections -// - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. -// - returns: an `NSFetchedResultsController` that observes a `DataStack` -// */ -// @nonobjc -// public static func createFor(_ dataStack: DataStack, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController { -// -// return self.createFromContext( -// dataStack.mainContext, -// fetchRequest: CoreStoreFetchRequest(), -// from: from, -// sectionBy: sectionBy, -// fetchClauses: fetchClauses -// ) -// } -// -// /** -// Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. -// -// - parameter dataStack: the `DataStack` to observe objects from -// - parameter from: a `From` clause indicating the entity type -// - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. -// - returns: an `NSFetchedResultsController` that observes a `DataStack` -// */ -// @nonobjc -// public static func createFor(_ dataStack: DataStack, _ from: From, _ fetchClauses: FetchClause...) -> NSFetchedResultsController { -// -// return self.createFromContext( -// dataStack.mainContext, -// fetchRequest: CoreStoreFetchRequest(), -// from: from, -// sectionBy: nil, -// fetchClauses: fetchClauses -// ) -// } -// -// /** -// Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. -// -// - parameter dataStack: the `DataStack` to observe objects from -// - parameter from: a `From` clause indicating the entity type -// - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. -// - returns: an `NSFetchedResultsController` that observes a `DataStack` -// */ -// @nonobjc -// public static func createFor(_ dataStack: DataStack, _ from: From, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController { -// -// return self.createFromContext( -// dataStack.mainContext, -// fetchRequest: CoreStoreFetchRequest(), -// from: from, -// sectionBy: nil, -// fetchClauses: fetchClauses -// ) -// } -// -// /** -// Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. -// -// - parameter transaction: the `UnsafeDataTransaction` to observe objects from -// - parameter from: a `From` clause indicating the entity type -// - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections -// - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. -// - returns: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction` -// */ -// @nonobjc -// public static func createFor(_ transaction: UnsafeDataTransaction, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController { -// -// return self.createFromContext( -// transaction.context, -// fetchRequest: CoreStoreFetchRequest(), -// from: from, -// sectionBy: sectionBy, -// fetchClauses: fetchClauses -// ) -// } -// -// /** -// Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. -// -// - parameter transaction: the `UnsafeDataTransaction` to observe objects from -// - parameter from: a `From` clause indicating the entity type -// - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections -// - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. -// - returns: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction` -// */ -// @nonobjc -// public static func createFor(_ transaction: UnsafeDataTransaction, _ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController { -// -// return self.createFromContext( -// transaction.context, -// fetchRequest: CoreStoreFetchRequest(), -// from: from, -// sectionBy: sectionBy, -// fetchClauses: fetchClauses -// ) -// } -// -// /** -// Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. -// -// - parameter transaction: the `UnsafeDataTransaction` to observe objects from -// - parameter from: a `From` clause indicating the entity type -// - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. -// - returns: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction` -// */ -// @nonobjc -// public static func createFor(_ transaction: UnsafeDataTransaction, _ from: From, _ fetchClauses: FetchClause...) -> NSFetchedResultsController { -// -// return self.createFromContext( -// transaction.context, -// fetchRequest: CoreStoreFetchRequest(), -// from: from, -// sectionBy: nil, -// fetchClauses: fetchClauses -// ) -// } -// -// /** -// Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. -// -// - parameter transaction: the `UnsafeDataTransaction` to observe objects from -// - parameter from: a `From` clause indicating the entity type -// - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. -// */ -// @nonobjc -// public static func createFor(_ transaction: UnsafeDataTransaction, _ from: From, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController { -// -// return self.createFromContext( -// transaction.context, -// fetchRequest: CoreStoreFetchRequest(), -// from: from, -// sectionBy: nil, -// fetchClauses: fetchClauses -// ) -// } -// -// -// // MARK: Internal -// -// @nonobjc -// internal static func createFromContext(_ context: NSManagedObjectContext, fetchRequest: CoreStoreFetchRequest, from: From? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController { -// -// let controller = CoreStoreFetchedResultsController( -// context: context, -// fetchRequest: fetchRequest, -// from: from, -// sectionBy: sectionBy, -// applyFetchClauses: { fetchRequest in -// -// fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) } -// -// CoreStore.assert( -// fetchRequest.sortDescriptors?.isEmpty == false, -// "An \(cs_typeName(NSFetchedResultsController.self)) requires a sort information. Specify from a \(cs_typeName(OrderBy.self)) clause or any custom \(cs_typeName(FetchClause.self)) that provides a sort descriptor." -// ) -// } -// ) -// return controller.upcast() -// } -//} -// -//#endif +#if os(iOS) || os(watchOS) || os(tvOS) + +// MARK: - DataStack + +public extension DataStack { + + /** + Utility for creating an `NSFetchedResultsController` from the `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. + + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: an `NSFetchedResultsController` that observes the `DataStack` + */ + @nonobjc + public func createFetchedResultsController(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.mainContext, + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses + ) + } + + /** + Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. + + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: an `NSFetchedResultsController` that observes the `DataStack` + */ + @nonobjc + public func createFetchedResultsController(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.mainContext, + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses + ) + } + + /** + Utility for creating an `NSFetchedResultsController` from the `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: an `NSFetchedResultsController` that observes the `DataStack` + */ + @nonobjc + public func createFetchedResultsController(_ from: From, _ fetchClauses: FetchClause...) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.mainContext, + from: from, + sectionBy: nil, + fetchClauses: fetchClauses + ) + } + + /** + Utility for creating an `NSFetchedResultsController` from the `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: an `NSFetchedResultsController` that observes the `DataStack` + */ + @nonobjc + public func createFetchedResultsController(forDataStack dataStack: DataStack, _ from: From, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.mainContext, + from: from, + sectionBy: nil, + fetchClauses: fetchClauses + ) + } +} + + +// MARK: - UnsafeDataTransaction + +public extension UnsafeDataTransaction { + + /** + Utility for creating an `NSFetchedResultsController` from the `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. + + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: an `NSFetchedResultsController` that observes the `UnsafeDataTransaction` + */ + @nonobjc + public func createFetchedResultsController(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.context, + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses + ) + } + + /** + Utility for creating an `NSFetchedResultsController` from the `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. + + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: an `NSFetchedResultsController` that observes the `UnsafeDataTransaction` + */ + @nonobjc + public func createFetchedResultsController(_ from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.context, + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses + ) + } + + /** + Utility for creating an `NSFetchedResultsController` from the `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: an `NSFetchedResultsController` that observes the `UnsafeDataTransaction` + */ + @nonobjc + public func createFetchedResultsController(_ from: From, _ fetchClauses: FetchClause...) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.context, + from: from, + sectionBy: nil, + fetchClauses: fetchClauses + ) + } + + /** + Utility for creating an `NSFetchedResultsController` from the `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: an `NSFetchedResultsController` that observes the `UnsafeDataTransaction` + */ + @nonobjc + public func createFetchedResultsController(_ from: From, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.context, + from: from, + sectionBy: nil, + fetchClauses: fetchClauses + ) + } +} + + + +// MARK: - Private + +fileprivate func createFRC(fromContext context: NSManagedObjectContext, from: From? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController { + + let controller = CoreStoreFetchedResultsController( + context: context, + fetchRequest: CoreStoreFetchRequest().dynamicCast(), + from: from, + sectionBy: sectionBy, + applyFetchClauses: { (fetchRequest) in + + fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest as! NSFetchRequest) } + + CoreStore.assert( + fetchRequest.sortDescriptors?.isEmpty == false, + "An \(cs_typeName(NSFetchedResultsController.self)) requires a sort information. Specify from a \(cs_typeName(OrderBy.self)) clause or any custom \(cs_typeName(FetchClause.self)) that provides a sort descriptor." + ) + } + ) + return controller.dynamicCast() +} + +#endif diff --git a/Sources/Importing/BaseDataTransaction+Importing.swift b/Sources/Importing/BaseDataTransaction+Importing.swift index 0d32c37..51da4e8 100644 --- a/Sources/Importing/BaseDataTransaction+Importing.swift +++ b/Sources/Importing/BaseDataTransaction+Importing.swift @@ -184,9 +184,8 @@ public extension BaseDataTransaction { /** Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources. - Objects are called with `ImportableUniqueObject` methods in the same order as in import sources array. - The array returned from `importUniqueObjects(...)` correctly maps to the order of `sourceArray`. - If `sourceArray` contains multiple import sources with same ID, the last one will be imported. + `ImportableUniqueObject` methods are called on the objects in the same order as they are in the `sourceArray`, and are returned in an array with that same order. + - Warning: If `sourceArray` contains multiple import sources with same ID, no merging will occur and ONLY THE LAST duplicate will be imported. - parameter into: an `Into` clause specifying the entity type - parameter sourceArray: the array of objects to import values from @@ -217,8 +216,7 @@ public extension BaseDataTransaction { return nil } - // each subsequent import source with the same ID will replace the existing one - importSourceByID[uniqueIDValue] = source + importSourceByID[uniqueIDValue] = source // effectively replaces duplicate with the latest return uniqueIDValue } } @@ -227,17 +225,19 @@ public extension BaseDataTransaction { var existingObjectsByID = Dictionary() self.fetchAll(From(entityType), Where(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs))? - .forEach { existingObjectsByID[$0.uniqueIDValue] = $0 } + .forEach { existingObjectsByID[$0.uniqueIDValue] = $0 } var processedObjectIDs = Set() var result = [T]() - for objectID in sortedIDs { + for objectID in sortedIDs where !processedObjectIDs.contains(objectID) { + guard let source = importSourceByID[objectID] else { + + continue + } try autoreleasepool { - guard let source = importSourceByID[objectID], !processedObjectIDs.contains(objectID) else { return } - if let object = existingObjectsByID[objectID] { guard entityType.shouldUpdate(from: source, in: self) else { return } @@ -256,7 +256,6 @@ public extension BaseDataTransaction { processedObjectIDs.insert(objectID) } } - return result } } diff --git a/Sources/Info.plist b/Sources/Info.plist index 449c7b6..a60ea4a 100644 --- a/Sources/Info.plist +++ b/Sources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.1.1 + 2.2.0 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/ObjectiveC/NSFetchedResultsController+ObjectiveC.swift b/Sources/ObjectiveC/NSFetchedResultsController+ObjectiveC.swift index df6a123..5a5345e 100644 --- a/Sources/ObjectiveC/NSFetchedResultsController+ObjectiveC.swift +++ b/Sources/ObjectiveC/NSFetchedResultsController+ObjectiveC.swift @@ -27,40 +27,78 @@ import Foundation import CoreData -//#if os(iOS) || os(watchOS) || os(tvOS) -// -//// MARK: - NSFetchedResultsController -// -//public extension NSFetchedResultsController { -// -// /** -// Utility for creating an `NSFetchedResultsController` from a `CSDataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `CSListMonitor`s abstractio -// -// - parameter dataStack: the `CSDataStack` to observe objects from -// - parameter fetchRequest: the `NSFetchRequest` instance to use with the `NSFetchedResultsController` -// - parameter from: an optional `CSFrom` clause indicating the entity type. If not specified, the `fetchRequest` argument's `entity` property should already be set. -// - parameter sectionBy: a `CSSectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. -// - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses. -// */ -// @objc -// public static func cs_createForStack(_ dataStack: CSDataStack, fetchRequest: NSFetchRequest, from: CSFrom?, sectionBy: CSSectionBy?, fetchClauses: [CSFetchClause]) -> NSFetchedResultsController { -// -// return CoreStoreFetchedResultsController( -// context: dataStack.bridgeToSwift.mainContext, -// fetchRequest: fetchRequest, -// from: from?.bridgeToSwift, -// sectionBy: sectionBy?.bridgeToSwift, -// applyFetchClauses: { fetchRequest in -// -// fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) } -// -// CoreStore.assert( -// fetchRequest.sortDescriptors?.isEmpty == false, -// "An \(cs_typeName(NSFetchedResultsController)) requires a sort information. Specify from a \(cs_typeName(CSOrderBy)) clause or any custom \(cs_typeName(CSFetchClause)) that provides a sort descriptor." -// ) -// } -// ) -// } -//} -// -//#endif +#if os(iOS) || os(watchOS) || os(tvOS) + +// MARK: - CSDataStack + +public extension CSDataStack { + + /** + Utility for creating an `NSFetchedResultsController` from the `CSDataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `CSListMonitor`s abstraction. + + - parameter from: a `CSFrom` clause indicating the entity type + - parameter sectionBy: a `CSSectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + - parameter fetchClauses: a series of `CSFetchClause` instances for fetching the object list. Accepts `CSWhere`, `CSOrderBy`, and `CSTweak` clauses. + - returns: an `NSFetchedResultsController` that observes the `CSDataStack` + */ + @objc + public func createFetchedResultsControllerFrom(_ from: CSFrom, sectionBy: CSSectionBy, fetchClauses: [CSFetchClause]) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.bridgeToSwift.mainContext, + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses + ) + } +} + + +// MARK: - CSUnsafeDataTransaction + +public extension CSUnsafeDataTransaction { + + /** + Utility for creating an `NSFetchedResultsController` from the `CSUnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `CSListMonitor`s abstraction. + + - parameter from: a `CSFrom` clause indicating the entity type + - parameter sectionBy: a `CSSectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: an `NSFetchedResultsController` that observes an `CSUnsafeDataTransaction` + */ + @objc + public func createFetchedResultsControllerFrom(_ from: CSFrom, sectionBy: CSSectionBy, fetchClauses: [CSFetchClause]) -> NSFetchedResultsController { + + return createFRC( + fromContext: self.bridgeToSwift.context, + from: from, + sectionBy: sectionBy, + fetchClauses: fetchClauses + ) + } +} + + +// MARK: - Private + +fileprivate func createFRC(fromContext context: NSManagedObjectContext, from: CSFrom? = nil, sectionBy: CSSectionBy?, fetchClauses: [CSFetchClause]) -> NSFetchedResultsController { + + let controller = CoreStoreFetchedResultsController( + context: context, + fetchRequest: CoreStoreFetchRequest().dynamicCast(), + from: from?.bridgeToSwift.upcast(), + sectionBy: sectionBy?.bridgeToSwift, + applyFetchClauses: { (fetchRequest) in + + fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest as! NSFetchRequest) } + + CoreStore.assert( + fetchRequest.sortDescriptors?.isEmpty == false, + "An \(cs_typeName(NSFetchedResultsController.self)) requires a sort information. Specify from a \(cs_typeName(CSOrderBy.self)) clause or any custom \(cs_typeName(CSFetchClause.self)) that provides a sort descriptor." + ) + } + ) + return controller.dynamicCast() +} + +#endif