diff --git a/CoreStore.podspec b/CoreStore.podspec index e92f916..a83f5e6 100644 --- a/CoreStore.podspec +++ b/CoreStore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreStore" - s.version = "2.1.1" + s.version = "3.0.0" s.license = "MIT" s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift" s.homepage = "https://github.com/JohnEstropia/CoreStore" diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 468c9aa..ef2d7cd 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -365,6 +365,14 @@ 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 */; }; + B596BBB61DD5BC67001DCDD9 /* FetchableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBB51DD5BC67001DCDD9 /* FetchableSource.swift */; }; + B596BBB71DD5BC67001DCDD9 /* FetchableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBB51DD5BC67001DCDD9 /* FetchableSource.swift */; }; + B596BBB81DD5BC67001DCDD9 /* FetchableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBB51DD5BC67001DCDD9 /* FetchableSource.swift */; }; + B596BBB91DD5BC67001DCDD9 /* FetchableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBB51DD5BC67001DCDD9 /* FetchableSource.swift */; }; + B596BBBB1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBBA1DD5C39F001DCDD9 /* QueryableSource.swift */; }; + B596BBBC1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBBA1DD5C39F001DCDD9 /* QueryableSource.swift */; }; + B596BBBD1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBBA1DD5C39F001DCDD9 /* QueryableSource.swift */; }; + B596BBBE1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = B596BBBA1DD5C39F001DCDD9 /* QueryableSource.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 */; }; @@ -652,6 +660,8 @@ 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 = ""; }; + B596BBB51DD5BC67001DCDD9 /* FetchableSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchableSource.swift; sourceTree = ""; }; + B596BBBA1DD5C39F001DCDD9 /* QueryableSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryableSource.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 = ""; }; @@ -1130,6 +1140,8 @@ B5E84EFE1AFF847B0064E85B /* BaseDataTransaction+Querying.swift */, B5E84F061AFF847B0064E85B /* DataStack+Querying.swift */, B5E84F071AFF847B0064E85B /* CoreStore+Querying.swift */, + B596BBB51DD5BC67001DCDD9 /* FetchableSource.swift */, + B596BBBA1DD5C39F001DCDD9 /* QueryableSource.swift */, B5E84F0A1AFF847B0064E85B /* Protocol Clauses */, B5E84EFF1AFF847B0064E85B /* Concrete Clauses */, ); @@ -1535,6 +1547,7 @@ B5D1E22C19FA9FBC003B2874 /* CoreStoreError.swift in Sources */, B5E84F131AFF847B0064E85B /* Where.swift in Sources */, B5D3F6451C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */, + B596BBBB1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */, B5ECDBFF1CA80CBA00C7F112 /* CSWhere.swift in Sources */, B5ECDC051CA8138100C7F112 /* CSOrderBy.swift in Sources */, B5E1B5981CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, @@ -1589,6 +1602,7 @@ B5FAD6AC1B51285300714891 /* MigrationManager.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, B5FEC18E1C9166E200532541 /* NSPersistentStore+Setup.swift in Sources */, + B596BBB61DD5BC67001DCDD9 /* FetchableSource.swift in Sources */, B5E1B5A21CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */, B59AFF411C6593E400C0ABE2 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, @@ -1685,6 +1699,7 @@ 82BA18AE1C4BBD3100A0916E /* DataStack+Transaction.swift in Sources */, 82BA18AB1C4BBD3100A0916E /* AsynchronousDataTransaction.swift in Sources */, 82BA18CE1C4BBD7100A0916E /* FetchedResultsControllerDelegate.swift in Sources */, + B596BBBC1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */, B5ECDC011CA80CBA00C7F112 /* CSWhere.swift in Sources */, B5ECDC071CA8138100C7F112 /* CSOrderBy.swift in Sources */, B5E1B59A1CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, @@ -1739,6 +1754,7 @@ 82BA18C61C4BBD5900A0916E /* DataStack+Migration.swift in Sources */, B59851491C90289D00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B5E1B5A41CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, + B596BBB71DD5BC67001DCDD9 /* FetchableSource.swift in Sources */, B5FEC18F1C9166E600532541 /* NSPersistentStore+Setup.swift in Sources */, 82BA18B71C4BBD3F00A0916E /* CoreStore+Querying.swift in Sources */, 82BA18AA1C4BBD3100A0916E /* BaseDataTransaction.swift in Sources */, @@ -1835,6 +1851,7 @@ B53FBA161CAB63CB00F0D40A /* NSProgress+ObjectiveC.swift in Sources */, B5ECDC271CA81A3900C7F112 /* CSCoreStore+Querying.swift in Sources */, B52DD1951BE1F92500949AFE /* CoreStoreError.swift in Sources */, + B596BBBE1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */, B546F9601C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */, B5ECDC0F1CA8161B00C7F112 /* CSGroupBy.swift in Sources */, B5ECDC211CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */, @@ -1889,6 +1906,7 @@ B5519A621CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, B52DD19C1BE1F92C00949AFE /* Into.swift in Sources */, B5FE4DA51C8481E100FA6A91 /* StorageInterface.swift in Sources */, + B596BBB91DD5BC67001DCDD9 /* FetchableSource.swift in Sources */, B529C2081CA4A2DC007E7EBD /* CSSaveResult.swift in Sources */, B5FE4DAA1C84FB4400FA6A91 /* InMemoryStore.swift in Sources */, B52DD1AF1BE1F93900949AFE /* GroupBy.swift in Sources */, @@ -1985,6 +2003,7 @@ B563219D1BD65216006C9394 /* DataStack+Observing.swift in Sources */, B56321961BD65216006C9394 /* From.swift in Sources */, B5ECDC021CA80CBA00C7F112 /* CSWhere.swift in Sources */, + B596BBBD1DD5C39F001DCDD9 /* QueryableSource.swift in Sources */, B5ECDC081CA8138100C7F112 /* CSOrderBy.swift in Sources */, B5E1B59B1CAA0C23007FD580 /* CSObjectObserver.swift in Sources */, B5519A611CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */, @@ -2039,6 +2058,7 @@ B5FEC1901C9166E700532541 /* NSPersistentStore+Setup.swift in Sources */, B56321A11BD65216006C9394 /* ListMonitor.swift in Sources */, B5E1B5A51CAA4365007FD580 /* CSCoreStore+Observing.swift in Sources */, + B596BBB81DD5BC67001DCDD9 /* FetchableSource.swift in Sources */, B56321881BD65216006C9394 /* BaseDataTransaction.swift in Sources */, B56321A31BD65216006C9394 /* DataStack+Migration.swift in Sources */, B56321901BD65216006C9394 /* ImportableUniqueObject.swift in Sources */, diff --git a/CoreStoreTests/SetupTests.swift b/CoreStoreTests/SetupTests.swift index 4f88479..f774046 100644 --- a/CoreStoreTests/SetupTests.swift +++ b/CoreStoreTests/SetupTests.swift @@ -42,7 +42,11 @@ class SetupTests: BaseTestCase { XCTAssertEqual(stack.coordinator.managedObjectModel, model) XCTAssertEqual(stack.rootSavingContext.persistentStoreCoordinator, stack.coordinator) XCTAssertNil(stack.rootSavingContext.parent) + XCTAssertFalse(stack.rootSavingContext.isDataStackContext) + XCTAssertFalse(stack.rootSavingContext.isTransactionContext) XCTAssertEqual(stack.mainContext.parent, stack.rootSavingContext) + XCTAssertTrue(stack.mainContext.isDataStackContext) + XCTAssertFalse(stack.mainContext.isTransactionContext) XCTAssertEqual(stack.model, model) XCTAssertTrue(stack.migrationChain.valid) XCTAssertTrue(stack.migrationChain.empty) diff --git a/CoreStoreTests/TransactionTests.swift b/CoreStoreTests/TransactionTests.swift index 78da1d3..d032c75 100644 --- a/CoreStoreTests/TransactionTests.swift +++ b/CoreStoreTests/TransactionTests.swift @@ -44,12 +44,20 @@ final class TransactionTests: BaseTestCase { let createExpectation = self.expectation(description: "create") stack.beginSynchronous { (transaction) in + XCTAssertEqual(transaction.context, transaction.internalContext()) + XCTAssertTrue(transaction.context.isTransactionContext) + XCTAssertFalse(transaction.context.isDataStackContext) + let object = transaction.create(Into()) + XCTAssertEqual(object.fetchSource()?.internalContext(), transaction.context) + XCTAssertEqual(object.querySource()?.internalContext(), transaction.context) + object.testEntityID = NSNumber(value: 1) object.testString = "string1" object.testNumber = 100 object.testDate = testDate + switch transaction.commitAndWait() { case .success(let hasChanges): @@ -66,6 +74,9 @@ final class TransactionTests: BaseTestCase { let object = stack.fetchOne(From()) XCTAssertNotNil(object) + XCTAssertEqual(object?.fetchSource()?.internalContext(), stack.mainContext) + XCTAssertEqual(object?.querySource()?.internalContext(), stack.mainContext) + XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) XCTAssertEqual(object?.testString, "string1") XCTAssertEqual(object?.testNumber, 100) @@ -355,7 +366,14 @@ final class TransactionTests: BaseTestCase { let createExpectation = self.expectation(description: "create") stack.beginAsynchronous { (transaction) in + XCTAssertEqual(transaction.context, transaction.internalContext()) + XCTAssertTrue(transaction.context.isTransactionContext) + XCTAssertFalse(transaction.context.isDataStackContext) + let object = transaction.create(Into()) + XCTAssertEqual(object.fetchSource()?.internalContext(), transaction.context) + XCTAssertEqual(object.querySource()?.internalContext(), transaction.context) + object.testEntityID = NSNumber(value: 1) object.testString = "string1" object.testNumber = 100 @@ -372,6 +390,9 @@ final class TransactionTests: BaseTestCase { let object = stack.fetchOne(From()) XCTAssertNotNil(object) + XCTAssertEqual(object?.fetchSource()?.internalContext(), stack.mainContext) + XCTAssertEqual(object?.querySource()?.internalContext(), stack.mainContext) + XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) XCTAssertEqual(object?.testString, "string1") XCTAssertEqual(object?.testNumber, 100) @@ -675,11 +696,17 @@ final class TransactionTests: BaseTestCase { self.prepareStack { (stack) in let transaction = stack.beginUnsafe() + XCTAssertEqual(transaction.context, transaction.internalContext()) + XCTAssertTrue(transaction.context.isTransactionContext) + XCTAssertFalse(transaction.context.isDataStackContext) let testDate = Date() do { let object = transaction.create(Into()) + XCTAssertEqual(object.fetchSource()?.internalContext(), transaction.context) + XCTAssertEqual(object.querySource()?.internalContext(), transaction.context) + object.testEntityID = NSNumber(value: 1) object.testString = "string1" object.testNumber = 100 @@ -693,6 +720,9 @@ final class TransactionTests: BaseTestCase { let object = stack.fetchOne(From()) XCTAssertNotNil(object) + XCTAssertEqual(object?.fetchSource()?.internalContext(), stack.mainContext) + XCTAssertEqual(object?.querySource()?.internalContext(), stack.mainContext) + XCTAssertEqual(object?.testEntityID, NSNumber(value: 1)) XCTAssertEqual(object?.testString, "string1") XCTAssertEqual(object?.testNumber, 100) diff --git a/Sources/Convenience/NSFetchedResultsController+Convenience.swift b/Sources/Convenience/NSFetchedResultsController+Convenience.swift index 9d53066..b6aca1a 100644 --- a/Sources/Convenience/NSFetchedResultsController+Convenience.swift +++ b/Sources/Convenience/NSFetchedResultsController+Convenience.swift @@ -34,6 +34,7 @@ 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. + - Note: It is the caller's responsibility to call `performFetch()` on the created `NSFetchedResultsController`. - 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 @@ -53,6 +54,7 @@ public extension DataStack { /** Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction. + - Note: It is the caller's responsibility to call `performFetch()` on the created `NSFetchedResultsController`. - 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 @@ -72,6 +74,7 @@ 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. + - Note: It is the caller's responsibility to call `performFetch()` on the created `NSFetchedResultsController`. - 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. @@ -90,6 +93,7 @@ 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. + - Note: It is the caller's responsibility to call `performFetch()` on the created `NSFetchedResultsController`. - 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. @@ -114,6 +118,7 @@ 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. + - Note: It is the caller's responsibility to call `performFetch()` on the created `NSFetchedResultsController`. - 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 @@ -133,6 +138,7 @@ 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. + - Note: It is the caller's responsibility to call `performFetch()` on the created `NSFetchedResultsController`. - 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 @@ -152,6 +158,7 @@ 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. + - Note: It is the caller's responsibility to call `performFetch()` on the created `NSFetchedResultsController`. - 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. @@ -170,6 +177,7 @@ 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. + - Note: It is the caller's responsibility to call `performFetch()` on the created `NSFetchedResultsController`. - 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. diff --git a/Sources/Convenience/NSManagedObject+Convenience.swift b/Sources/Convenience/NSManagedObject+Convenience.swift index 64aad9e..59e22f3 100644 --- a/Sources/Convenience/NSManagedObject+Convenience.swift +++ b/Sources/Convenience/NSManagedObject+Convenience.swift @@ -31,6 +31,52 @@ import CoreData public extension NSManagedObject { + /** + Exposes a `FetchableSource` that can fetch sibling objects of this `NSManagedObject` instance. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the obejct's parent is already deallocated. + - Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `fetchSource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `FetchableSource.internalContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time. + - returns: a `FetchableSource` that can fetch sibling objects of this `NSManagedObject` instance. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the object's parent is already deallocated. + */ + @nonobjc + public func fetchSource() -> FetchableSource? { + + guard let context = self.managedObjectContext else { + + return nil + } + if context.isTransactionContext { + + return context.parentTransaction + } + if context.isDataStackContext { + + return context.parentStack + } + return context + } + + /** + Exposes a `QueryableSource` that can query attributes and aggregate values. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the obejct's parent is already deallocated. + - Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `querySource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `QueryableSource.internalContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time. + - returns: a `QueryableSource` that can query attributes and aggregate values. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the object's parent is already deallocated. + */ + @nonobjc + public func querySource() -> QueryableSource? { + + guard let context = self.managedObjectContext else { + + return nil + } + if context.isTransactionContext { + + return context.parentTransaction + } + if context.isDataStackContext { + + return context.parentStack + } + return context + } + /** Provides a convenience wrapper for accessing `primitiveValueForKey(...)` with proper calls to `willAccessValueForKey(...)` and `didAccessValueForKey(...)`. This is useful when implementing accessor methods for transient attributes. diff --git a/Sources/Fetching and Querying/BaseDataTransaction+Querying.swift b/Sources/Fetching and Querying/BaseDataTransaction+Querying.swift index 84de9f9..777bcd0 100644 --- a/Sources/Fetching and Querying/BaseDataTransaction+Querying.swift +++ b/Sources/Fetching and Querying/BaseDataTransaction+Querying.swift @@ -29,206 +29,7 @@ import CoreData // MARK: - DataTransaction -public extension BaseDataTransaction { - - /** - Fetches the `NSManagedObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context. - - - parameter object: a reference to the object created/fetched outside the transaction - - returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found. - */ - public func fetchExisting(_ object: T) -> T? { - - do { - - return (try self.context.existingObject(with: object.objectID) as! T) - } - catch _ { - - return nil - } - } - - /** - Fetches the `NSManagedObject` instance in the transaction's context from an `NSManagedObjectID`. - - - parameter objectID: the `NSManagedObjectID` for the object - - returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found. - */ - public func fetchExisting(_ objectID: NSManagedObjectID) -> T? { - - do { - - return (try self.context.existingObject(with: objectID) as! T) - } - catch _ { - - return nil - } - } - - /** - Fetches the `NSManagedObject` instances in the transaction's context from references created from a transaction or from a different managed object context. - - - parameter objects: an array of `NSManagedObject`s created/fetched outside the transaction - - returns: the `NSManagedObject` array for objects that exists in the transaction - */ - public func fetchExisting(_ objects: S) -> [T] where S.Iterator.Element == T { - - return objects.flatMap { (try? self.context.existingObject(with: $0.objectID)) as? T } - } - - /** - Fetches the `NSManagedObject` instances in the transaction's context from a list of `NSManagedObjectID`. - - - parameter objectIDs: the `NSManagedObjectID` array for the objects - - returns: the `NSManagedObject` array for objects that exists in the transaction - */ - public func fetchExisting(_ objectIDs: S) -> [T] where S.Iterator.Element == NSManagedObjectID { - - return objectIDs.flatMap { (try? self.context.existingObject(with: $0)) as? T } - } - - /** - Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s - */ - public func fetchOne(_ from: From, _ fetchClauses: FetchClause...) -> T? { - - return self.fetchOne(from, fetchClauses) - } - - /** - Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s - */ - public func fetchOne(_ from: From, _ fetchClauses: [FetchClause]) -> T? { - - CoreStore.assert( - self.isRunningInAllowedQueue(), - "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." - ) - return self.context.fetchOne(from, fetchClauses) - } - - /** - Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s - */ - public func fetchAll(_ from: From, _ fetchClauses: FetchClause...) -> [T]? { - - return self.fetchAll(from, fetchClauses) - } - - /** - Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s - */ - public func fetchAll(_ from: From, _ fetchClauses: [FetchClause]) -> [T]? { - - CoreStore.assert( - self.isRunningInAllowedQueue(), - "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." - ) - return self.context.fetchAll(from, fetchClauses) - } - - /** - Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s - */ - public func fetchCount(_ from: From, _ fetchClauses: FetchClause...) -> Int? { - - return self.fetchCount(from, fetchClauses) - } - - /** - Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s - */ - public func fetchCount(_ from: From, _ fetchClauses: [FetchClause]) -> Int? { - - CoreStore.assert( - self.isRunningInAllowedQueue(), - "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." - ) - - return self.context.fetchCount(from, fetchClauses) - } - - /** - Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s - */ - public func fetchObjectID(_ from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { - - return self.fetchObjectID(from, fetchClauses) - } - - /** - Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s - */ - public func fetchObjectID(_ from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { - - CoreStore.assert( - self.isRunningInAllowedQueue(), - "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." - ) - return self.context.fetchObjectID(from, fetchClauses) - } - - /** - Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s - */ - public func fetchObjectIDs(_ from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { - - return self.fetchObjectIDs(from, fetchClauses) - } - - /** - Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - - parameter from: a `From` clause indicating the entity type - - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s - */ - public func fetchObjectIDs(_ from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { - - CoreStore.assert( - self.isRunningInAllowedQueue(), - "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." - ) - return self.context.fetchObjectIDs(from, fetchClauses) - } +extension BaseDataTransaction: FetchableSource, QueryableSource { /** Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. @@ -266,6 +67,216 @@ public extension BaseDataTransaction { return self.context.deleteAll(from, deleteClauses) } + + // MARK: FetchableSource + + /** + Fetches the `NSManagedObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context. + + - parameter object: a reference to the object created/fetched outside the transaction + - returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found. + */ + public func fetchExisting(_ object: T) -> T? { + + return self.context.fetchExisting(object) + } + + /** + Fetches the `NSManagedObject` instance in the transaction's context from an `NSManagedObjectID`. + + - parameter objectID: the `NSManagedObjectID` for the object + - returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found. + */ + public func fetchExisting(_ objectID: NSManagedObjectID) -> T? { + + return self.context.fetchExisting(objectID) + } + + /** + Fetches the `NSManagedObject` instances in the transaction's context from references created from a transaction or from a different managed object context. + + - parameter objects: an array of `NSManagedObject`s created/fetched outside the transaction + - returns: the `NSManagedObject` array for objects that exists in the transaction + */ + public func fetchExisting(_ objects: S) -> [T] where S.Iterator.Element == T { + + return self.context.fetchExisting(objects) + } + + /** + Fetches the `NSManagedObject` instances in the transaction's context from a list of `NSManagedObjectID`. + + - parameter objectIDs: the `NSManagedObjectID` array for the objects + - returns: the `NSManagedObject` array for objects that exists in the transaction + */ + public func fetchExisting(_ objectIDs: S) -> [T] where S.Iterator.Element == NSManagedObjectID { + + return self.context.fetchExisting(objectIDs) + } + + /** + Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s + */ + public func fetchOne(_ from: From, _ fetchClauses: FetchClause...) -> T? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchOne(from, fetchClauses) + } + + /** + Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s + */ + public func fetchOne(_ from: From, _ fetchClauses: [FetchClause]) -> T? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchOne(from, fetchClauses) + } + + /** + Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + */ + public func fetchAll(_ from: From, _ fetchClauses: FetchClause...) -> [T]? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchAll(from, fetchClauses) + } + + /** + Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + */ + public func fetchAll(_ from: From, _ fetchClauses: [FetchClause]) -> [T]? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchAll(from, fetchClauses) + } + + /** + Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + */ + public func fetchCount(_ from: From, _ fetchClauses: FetchClause...) -> Int? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchCount(from, fetchClauses) + } + + /** + Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + */ + public func fetchCount(_ from: From, _ fetchClauses: [FetchClause]) -> Int? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchCount(from, fetchClauses) + } + + /** + Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + */ + public func fetchObjectID(_ from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchObjectID(from, fetchClauses) + } + + /** + Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + */ + public func fetchObjectID(_ from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchObjectID(from, fetchClauses) + } + + /** + Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + */ + public func fetchObjectIDs(_ from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchObjectIDs(from, fetchClauses) + } + + /** + Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + */ + public func fetchObjectIDs(_ from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { + + CoreStore.assert( + self.isRunningInAllowedQueue(), + "Attempted to fetch from a \(cs_typeName(self)) outside its designated queue." + ) + return self.context.fetchObjectIDs(from, fetchClauses) + } + + + // MARK: QueryableSource + /** Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. @@ -282,7 +293,6 @@ public extension BaseDataTransaction { self.isRunningInAllowedQueue(), "Attempted to query from a \(cs_typeName(self)) outside its designated queue." ) - return self.context.queryValue(from, selectClause, queryClauses) } @@ -302,7 +312,6 @@ public extension BaseDataTransaction { self.isRunningInAllowedQueue(), "Attempted to query from a \(cs_typeName(self)) outside its designated queue." ) - return self.context.queryValue(from, selectClause, queryClauses) } @@ -322,7 +331,6 @@ public extension BaseDataTransaction { self.isRunningInAllowedQueue(), "Attempted to query from a \(cs_typeName(self)) outside its designated queue." ) - return self.context.queryAttributes(from, selectClause, queryClauses) } @@ -342,7 +350,17 @@ public extension BaseDataTransaction { self.isRunningInAllowedQueue(), "Attempted to query from a \(cs_typeName(self)) outside its designated queue." ) - return self.context.queryAttributes(from, selectClause, queryClauses) } + + + // MARK: FetchableSource, QueryableSource + + /** + The internal `NSManagedObjectContext` managed by this instance. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases. + */ + public func internalContext() -> NSManagedObjectContext { + + return self.context + } } diff --git a/Sources/Fetching and Querying/DataStack+Querying.swift b/Sources/Fetching and Querying/DataStack+Querying.swift index f7c43c0..d9a771a 100644 --- a/Sources/Fetching and Querying/DataStack+Querying.swift +++ b/Sources/Fetching and Querying/DataStack+Querying.swift @@ -29,7 +29,9 @@ import CoreData // MARK: - DataStack -public extension DataStack { +extension DataStack: FetchableSource, QueryableSource { + + // MARK: FetchableSource /** Fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context. @@ -39,14 +41,7 @@ public extension DataStack { */ public func fetchExisting(_ object: T) -> T? { - do { - - return (try self.mainContext.existingObject(with: object.objectID) as! T) - } - catch _ { - - return nil - } + return self.mainContext.fetchExisting(object) } /** @@ -57,14 +52,7 @@ public extension DataStack { */ public func fetchExisting(_ objectID: NSManagedObjectID) -> T? { - do { - - return (try self.mainContext.existingObject(with: objectID) as! T) - } - catch _ { - - return nil - } + return self.mainContext.fetchExisting(objectID) } /** @@ -75,7 +63,7 @@ public extension DataStack { */ public func fetchExisting(_ objects: S) -> [T] where S.Iterator.Element == T { - return objects.flatMap { (try? self.mainContext.existingObject(with: $0.objectID)) as? T } + return self.mainContext.fetchExisting(objects) } /** @@ -86,7 +74,7 @@ public extension DataStack { */ public func fetchExisting(_ objectIDs: S) -> [T] where S.Iterator.Element == NSManagedObjectID { - return objectIDs.flatMap { (try? self.mainContext.existingObject(with: $0)) as? T } + return self.mainContext.fetchExisting(objectIDs) } /** @@ -249,6 +237,9 @@ public extension DataStack { return self.mainContext.fetchObjectIDs(from, fetchClauses) } + + // MARK: QueryableSource + /** Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. @@ -324,4 +315,15 @@ public extension DataStack { ) return self.mainContext.queryAttributes(from, selectClause, queryClauses) } + + + // MARK: FetchableSource, QueryableSource + + /** + The internal `NSManagedObjectContext` managed by this instance. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases. + */ + public func internalContext() -> NSManagedObjectContext { + + return self.mainContext + } } diff --git a/Sources/Fetching and Querying/FetchableSource.swift b/Sources/Fetching and Querying/FetchableSource.swift new file mode 100644 index 0000000..0252d36 --- /dev/null +++ b/Sources/Fetching and Querying/FetchableSource.swift @@ -0,0 +1,163 @@ +// +// FetchableSource.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. +// + +import Foundation +import CoreData + + +// MARK: - FetchableSource + +/** + Encapsulates containers which manages an internal `NSManagedObjectContext`, such as `DataStack`s and transactions, that can be used for fetching objects. CoreStore provides implementations for this protocol and should be used as a read-only abstraction. + */ +public protocol FetchableSource: class { + + /** + Fetches the `NSManagedObject` instance in the `FetchableSource`'s context from a reference created from another managed object context. + + - parameter object: a reference to the object created/fetched outside the `FetchableSource`'s context + - returns: the `NSManagedObject` instance if the object exists in the `FetchableSource`'s context, or `nil` if not found. + */ + func fetchExisting(_ object: T) -> T? + + /** + Fetches the `NSManagedObject` instance in the `FetchableSource`'s context from an `NSManagedObjectID`. + + - parameter objectID: the `NSManagedObjectID` for the object + - returns: the `NSManagedObject` instance if the object exists in the `FetchableSource`, or `nil` if not found. + */ + func fetchExisting(_ objectID: NSManagedObjectID) -> T? + + /** + Fetches the `NSManagedObject` instances in the `FetchableSource`'s context from references created from another managed object context. + + - parameter objects: an array of `NSManagedObject`s created/fetched outside the `FetchableSource`'s context + - returns: the `NSManagedObject` array for objects that exists in the `FetchableSource` + */ + func fetchExisting(_ objects: S) -> [T] where S.Iterator.Element == T + + /** + Fetches the `NSManagedObject` instances in the `FetchableSource`'s context from a list of `NSManagedObjectID`. + + - parameter objectIDs: the `NSManagedObjectID` array for the objects + - returns: the `NSManagedObject` array for objects that exists in the `FetchableSource`'s context + */ + func fetchExisting(_ objectIDs: S) -> [T] where S.Iterator.Element == NSManagedObjectID + + /** + Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s + */ + func fetchOne(_ from: From, _ fetchClauses: FetchClause...) -> T? + + /** + Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s + */ + func fetchOne(_ from: From, _ fetchClauses: [FetchClause]) -> T? + + /** + Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + */ + func fetchAll(_ from: From, _ fetchClauses: FetchClause...) -> [T]? + + /** + Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + */ + func fetchAll(_ from: From, _ fetchClauses: [FetchClause]) -> [T]? + + /** + Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + */ + func fetchCount(_ from: From, _ fetchClauses: FetchClause...) -> Int? + + /** + Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + */ + func fetchCount(_ from: From, _ fetchClauses: [FetchClause]) -> Int? + + /** + Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + */ + func fetchObjectID(_ from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? + + /** + Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + */ + func fetchObjectID(_ from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? + + /** + Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + */ + func fetchObjectIDs(_ from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? + + /** + Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + */ + func fetchObjectIDs(_ from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? + + /** + The internal `NSManagedObjectContext` managed by this `FetchableSource`. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases. + */ + func internalContext() -> NSManagedObjectContext +} diff --git a/Sources/Fetching and Querying/QueryableSource.swift b/Sources/Fetching and Querying/QueryableSource.swift new file mode 100644 index 0000000..0c9529e --- /dev/null +++ b/Sources/Fetching and Querying/QueryableSource.swift @@ -0,0 +1,89 @@ +// +// QueryableSource.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. +// + +import Foundation +import CoreData + + +// MARK: - QueryableSource + +/** + Encapsulates containers which manages an internal `NSManagedObjectContext`, such as `DataStack`s and transactions, that can be used for querying values. CoreStore provides implementations for this protocol and should be used as a read-only abstraction. + */ +public protocol QueryableSource: class { + + /** + Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + + A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. + + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + */ + func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> U? + + /** + Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + + A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. + + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + */ + func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> U? + + /** + Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + + A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. + + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + */ + func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[String: Any]]? + + /** + Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + + A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. + + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + */ + func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[String: Any]]? + + /** + The internal `NSManagedObjectContext` managed by this `QueryableSource`. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases. + */ + func internalContext() -> NSManagedObjectContext +} diff --git a/Sources/Info.plist b/Sources/Info.plist index a60ea4a..e0f4bf7 100644 --- a/Sources/Info.plist +++ b/Sources/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 2.2.0 + 3.0.0 CFBundleSignature ???? CFBundleVersion diff --git a/Sources/Internal/NSManagedObjectContext+Querying.swift b/Sources/Internal/NSManagedObjectContext+Querying.swift index eaa4cb0..bb44498 100644 --- a/Sources/Internal/NSManagedObjectContext+Querying.swift +++ b/Sources/Internal/NSManagedObjectContext+Querying.swift @@ -29,12 +29,12 @@ import CoreData // MARK: - NSManagedObjectContext -internal extension NSManagedObjectContext { +extension NSManagedObjectContext: FetchableSource, QueryableSource { - // MARK: Internal: Fetch Existing + // MARK: FetchableSource @nonobjc - internal func fetchExisting(_ object: T) -> T? { + public func fetchExisting(_ object: T) -> T? { if object.objectID.isTemporaryID { @@ -69,17 +69,40 @@ internal extension NSManagedObjectContext { } } - - // MARK: Internal: Fetch One + @nonobjc + public func fetchExisting(_ objectID: NSManagedObjectID) -> T? { + + do { + + return (try self.existingObject(with: objectID) as! T) + } + catch _ { + + return nil + } + } @nonobjc - internal func fetchOne(_ from: From, _ fetchClauses: FetchClause...) -> T? { + public func fetchExisting(_ objects: S) -> [T] where S.Iterator.Element == T { + + return objects.flatMap { (try? self.existingObject(with: $0.objectID)) as? T } + } + + @nonobjc + public func fetchExisting(_ objectIDs: S) -> [T] where S.Iterator.Element == NSManagedObjectID { + + return objectIDs.flatMap { (try? self.existingObject(with: $0)) as? T } + } + + @nonobjc + public func fetchOne(_ from: From, _ fetchClauses: FetchClause...) -> T? { + return self.fetchOne(from, fetchClauses) } @nonobjc - internal func fetchOne(_ from: From, _ fetchClauses: [FetchClause]) -> T? { + public func fetchOne(_ from: From, _ fetchClauses: [FetchClause]) -> T? { let fetchRequest = CoreStoreFetchRequest() let storeFound = from.applyToFetchRequest(fetchRequest, context: self) @@ -96,43 +119,13 @@ internal extension NSManagedObjectContext { } @nonobjc - internal func fetchOne(_ fetchRequest: NSFetchRequest) -> T? { - - var fetchResults: [T]? - var fetchError: Error? - self.performAndWait { - - do { - - fetchResults = try self.fetch(fetchRequest) - } - catch { - - fetchError = error - } - } - if fetchResults == nil { - - CoreStore.log( - CoreStoreError(fetchError), - "Failed executing fetch request." - ) - return nil - } - return fetchResults?.first - } - - - // MARK: Internal: Fetch All - - @nonobjc - internal func fetchAll(_ from: From, _ fetchClauses: FetchClause...) -> [T]? { + public func fetchAll(_ from: From, _ fetchClauses: FetchClause...) -> [T]? { return self.fetchAll(from, fetchClauses) } @nonobjc - internal func fetchAll(_ from: From, _ fetchClauses: [FetchClause]) -> [T]? { + public func fetchAll(_ from: From, _ fetchClauses: [FetchClause]) -> [T]? { let fetchRequest = CoreStoreFetchRequest() let storeFound = from.applyToFetchRequest(fetchRequest, context: self) @@ -149,43 +142,13 @@ internal extension NSManagedObjectContext { } @nonobjc - internal func fetchAll(_ fetchRequest: NSFetchRequest) -> [T]? { - - var fetchResults: [T]? - var fetchError: Error? - self.performAndWait { - - do { - - fetchResults = try self.fetch(fetchRequest) - } - catch { - - fetchError = error - } - } - if fetchResults == nil { - - CoreStore.log( - CoreStoreError(fetchError), - "Failed executing fetch request." - ) - return nil - } - return fetchResults - } - - - // MARK: Internal: Count - - @nonobjc - internal func fetchCount(_ from: From, _ fetchClauses: FetchClause...) -> Int? { + public func fetchCount(_ from: From, _ fetchClauses: FetchClause...) -> Int? { return self.fetchCount(from, fetchClauses) } @nonobjc - internal func fetchCount(_ from: From, _ fetchClauses: [FetchClause]) -> Int? { + public func fetchCount(_ from: From, _ fetchClauses: [FetchClause]) -> Int? { let fetchRequest = CoreStoreFetchRequest() let storeFound = from.applyToFetchRequest(fetchRequest, context: self) @@ -199,43 +162,13 @@ internal extension NSManagedObjectContext { } @nonobjc - internal func fetchCount(_ fetchRequest: NSFetchRequest) -> Int? { - - var count = 0 - var countError: Error? - self.performAndWait { - - do { - - count = try self.count(for: fetchRequest) - } - catch { - - countError = error - } - } - if count == NSNotFound { - - CoreStore.log( - CoreStoreError(countError), - "Failed executing count request." - ) - return nil - } - return count - } - - - // MARK: Internal: Object ID - - @nonobjc - internal func fetchObjectID(_ from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { + public func fetchObjectID(_ from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { return self.fetchObjectID(from, fetchClauses) } @nonobjc - internal func fetchObjectID(_ from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { + public func fetchObjectID(_ from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { let fetchRequest = CoreStoreFetchRequest() let storeFound = from.applyToFetchRequest(fetchRequest, context: self) @@ -252,43 +185,13 @@ internal extension NSManagedObjectContext { } @nonobjc - internal func fetchObjectID(_ fetchRequest: NSFetchRequest) -> NSManagedObjectID? { - - var fetchResults: [NSManagedObjectID]? - var fetchError: Error? - self.performAndWait { - - do { - - fetchResults = try self.fetch(fetchRequest) - } - catch { - - fetchError = error - } - } - if fetchResults == nil { - - CoreStore.log( - CoreStoreError(fetchError), - "Failed executing fetch request." - ) - return nil - } - return fetchResults?.first - } - - - // MARK: Internal: Object IDs - - @nonobjc - internal func fetchObjectIDs(_ from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { + public func fetchObjectIDs(_ from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { return self.fetchObjectIDs(from, fetchClauses) } @nonobjc - internal func fetchObjectIDs(_ from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { + public func fetchObjectIDs(_ from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { let fetchRequest = CoreStoreFetchRequest() let storeFound = from.applyToFetchRequest(fetchRequest, context: self) @@ -332,79 +235,16 @@ internal extension NSManagedObjectContext { } - // MARK: Internal: Delete All + // MARK: QueryableSource @nonobjc - internal func deleteAll(_ from: From, _ deleteClauses: DeleteClause...) -> Int? { - - return self.deleteAll(from, deleteClauses) - } - - @nonobjc - internal func deleteAll(_ from: From, _ deleteClauses: [DeleteClause]) -> Int? { - - let fetchRequest = CoreStoreFetchRequest() - let storeFound = from.applyToFetchRequest(fetchRequest, context: self) - - fetchRequest.fetchLimit = 0 - fetchRequest.resultType = .managedObjectResultType - fetchRequest.returnsObjectsAsFaults = true - fetchRequest.includesPropertyValues = false - deleteClauses.forEach { $0.applyToFetchRequest(fetchRequest) } - - guard storeFound else { - - return nil - } - return self.deleteAll(fetchRequest.dynamicCast()) - } - - @nonobjc - internal func deleteAll(_ fetchRequest: NSFetchRequest) -> Int? { - - var numberOfDeletedObjects: Int? - var fetchError: Error? - self.performAndWait { - - autoreleasepool { - - do { - - let fetchResults = try self.fetch(fetchRequest) - for object in fetchResults { - - self.delete(object) - } - numberOfDeletedObjects = fetchResults.count - } - catch { - - fetchError = error - } - } - } - if numberOfDeletedObjects == nil { - - CoreStore.log( - CoreStoreError(fetchError), - "Failed executing fetch request." - ) - return nil - } - return numberOfDeletedObjects - } - - - // MARK: Internal: Value - - @nonobjc - internal func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> U? { + public func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> U? { return self.queryValue(from, selectClause, queryClauses) } @nonobjc - internal func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> U? { + public func queryValue(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> U? { let fetchRequest = CoreStoreFetchRequest() let storeFound = from.applyToFetchRequest(fetchRequest, context: self) @@ -422,6 +262,158 @@ internal extension NSManagedObjectContext { return self.queryValue(selectTerms, fetchRequest: fetchRequest) } + @nonobjc + public func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[String: Any]]? { + + return self.queryAttributes(from, selectClause, queryClauses) + } + + @nonobjc + public func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[String: Any]]? { + + let fetchRequest = CoreStoreFetchRequest() + let storeFound = from.applyToFetchRequest(fetchRequest, context: self) + + fetchRequest.fetchLimit = 0 + + selectClause.selectTerms.applyToFetchRequest(fetchRequest, owner: selectClause) + queryClauses.forEach { $0.applyToFetchRequest(fetchRequest) } + + guard storeFound else { + + return nil + } + return self.queryAttributes(fetchRequest) + } + + + // MARK: FetchableSource, QueryableSource + + @nonobjc + public func internalContext() -> NSManagedObjectContext { + + return self + } +} + + +// MARK: - NSManagedObjectContext (Internal) + +internal extension NSManagedObjectContext { + + // MARK: Fetching + + @nonobjc + internal func fetchOne(_ fetchRequest: NSFetchRequest) -> T? { + + var fetchResults: [T]? + var fetchError: Error? + self.performAndWait { + + do { + + fetchResults = try self.fetch(fetchRequest) + } + catch { + + fetchError = error + } + } + if fetchResults == nil { + + CoreStore.log( + CoreStoreError(fetchError), + "Failed executing fetch request." + ) + return nil + } + return fetchResults?.first + } + + @nonobjc + internal func fetchAll(_ fetchRequest: NSFetchRequest) -> [T]? { + + var fetchResults: [T]? + var fetchError: Error? + self.performAndWait { + + do { + + fetchResults = try self.fetch(fetchRequest) + } + catch { + + fetchError = error + } + } + if fetchResults == nil { + + CoreStore.log( + CoreStoreError(fetchError), + "Failed executing fetch request." + ) + return nil + } + return fetchResults + } + + @nonobjc + internal func fetchCount(_ fetchRequest: NSFetchRequest) -> Int? { + + var count = 0 + var countError: Error? + self.performAndWait { + + do { + + count = try self.count(for: fetchRequest) + } + catch { + + countError = error + } + } + if count == NSNotFound { + + CoreStore.log( + CoreStoreError(countError), + "Failed executing count request." + ) + return nil + } + return count + } + + @nonobjc + internal func fetchObjectID(_ fetchRequest: NSFetchRequest) -> NSManagedObjectID? { + + var fetchResults: [NSManagedObjectID]? + var fetchError: Error? + self.performAndWait { + + do { + + fetchResults = try self.fetch(fetchRequest) + } + catch { + + fetchError = error + } + } + if fetchResults == nil { + + CoreStore.log( + CoreStoreError(fetchError), + "Failed executing fetch request." + ) + return nil + } + return fetchResults?.first + } + + + // MARK: Querying + @nonobjc internal func queryValue(_ selectTerms: [SelectTerm], fetchRequest: NSFetchRequest) -> U? { @@ -488,33 +480,6 @@ internal extension NSManagedObjectContext { return nil } - - // MARK: Internal: Attributes - - @nonobjc - internal func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[String: Any]]? { - - return self.queryAttributes(from, selectClause, queryClauses) - } - - @nonobjc - internal func queryAttributes(_ from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[String: Any]]? { - - let fetchRequest = CoreStoreFetchRequest() - let storeFound = from.applyToFetchRequest(fetchRequest, context: self) - - fetchRequest.fetchLimit = 0 - - selectClause.selectTerms.applyToFetchRequest(fetchRequest, owner: selectClause) - queryClauses.forEach { $0.applyToFetchRequest(fetchRequest) } - - guard storeFound else { - - return nil - } - return self.queryAttributes(fetchRequest) - } - @nonobjc internal func queryAttributes(_ fetchRequest: NSFetchRequest) -> [[String: Any]]? { @@ -542,4 +507,67 @@ internal extension NSManagedObjectContext { ) return nil } + + + // MARK: Deleting + + @nonobjc + internal func deleteAll(_ from: From, _ deleteClauses: DeleteClause...) -> Int? { + + return self.deleteAll(from, deleteClauses) + } + + @nonobjc + internal func deleteAll(_ from: From, _ deleteClauses: [DeleteClause]) -> Int? { + + let fetchRequest = CoreStoreFetchRequest() + let storeFound = from.applyToFetchRequest(fetchRequest, context: self) + + fetchRequest.fetchLimit = 0 + fetchRequest.resultType = .managedObjectResultType + fetchRequest.returnsObjectsAsFaults = true + fetchRequest.includesPropertyValues = false + deleteClauses.forEach { $0.applyToFetchRequest(fetchRequest) } + + guard storeFound else { + + return nil + } + return self.deleteAll(fetchRequest.dynamicCast()) + } + + @nonobjc + internal func deleteAll(_ fetchRequest: NSFetchRequest) -> Int? { + + var numberOfDeletedObjects: Int? + var fetchError: Error? + self.performAndWait { + + autoreleasepool { + + do { + + let fetchResults = try self.fetch(fetchRequest) + for object in fetchResults { + + self.delete(object) + } + numberOfDeletedObjects = fetchResults.count + } + catch { + + fetchError = error + } + } + } + if numberOfDeletedObjects == nil { + + CoreStore.log( + CoreStoreError(fetchError), + "Failed executing fetch request." + ) + return nil + } + return numberOfDeletedObjects + } } diff --git a/Sources/Internal/NSManagedObjectContext+Transaction.swift b/Sources/Internal/NSManagedObjectContext+Transaction.swift index 7b137ab..3c233c2 100644 --- a/Sources/Internal/NSManagedObjectContext+Transaction.swift +++ b/Sources/Internal/NSManagedObjectContext+Transaction.swift @@ -74,6 +74,48 @@ internal extension NSManagedObjectContext { } } + @nonobjc + internal var isTransactionContext: Bool { + + get { + + let value: NSNumber? = cs_getAssociatedObjectForKey( + &PropertyKeys.isTransactionContext, + inObject: self + ) + return value?.boolValue == true + } + set { + + cs_setAssociatedCopiedObject( + NSNumber(value: newValue), + forKey: &PropertyKeys.isTransactionContext, + inObject: self + ) + } + } + + @nonobjc + internal var isDataStackContext: Bool { + + get { + + let value: NSNumber? = cs_getAssociatedObjectForKey( + &PropertyKeys.isDataStackContext, + inObject: self + ) + return value?.boolValue == true + } + set { + + cs_setAssociatedCopiedObject( + NSNumber(value: newValue), + forKey: &PropertyKeys.isDataStackContext, + inObject: self + ) + } + } + @nonobjc internal func isRunningInAllowedQueue() -> Bool { @@ -214,5 +256,7 @@ internal extension NSManagedObjectContext { static var parentTransaction: Void? static var isSavingSynchronously: Void? + static var isTransactionContext: Void? + static var isDataStackContext: Void? } } diff --git a/Sources/ObjectiveC/CSBaseDataTransaction+Querying.swift b/Sources/ObjectiveC/CSBaseDataTransaction+Querying.swift index 3bd434e..715dafd 100644 --- a/Sources/ObjectiveC/CSBaseDataTransaction+Querying.swift +++ b/Sources/ObjectiveC/CSBaseDataTransaction+Querying.swift @@ -40,14 +40,7 @@ public extension CSBaseDataTransaction { @objc public func fetchExistingObject(_ object: NSManagedObject) -> Any? { - do { - - return try self.bridgeToSwift.context.existingObject(with: object.objectID) - } - catch _ { - - return nil - } + return self.bridgeToSwift.context.fetchExisting(object) } /** @@ -59,14 +52,7 @@ public extension CSBaseDataTransaction { @objc public func fetchExistingObjectWithID(_ objectID: NSManagedObjectID) -> Any? { - do { - - return try self.bridgeToSwift.context.existingObject(with: objectID) - } - catch _ { - - return nil - } + return self.bridgeToSwift.context.fetchExisting(objectID) } /** @@ -78,7 +64,7 @@ public extension CSBaseDataTransaction { @objc public func fetchExistingObjects(_ objects: [NSManagedObject]) -> [Any] { - return objects.flatMap { try? self.bridgeToSwift.context.existingObject(with: $0.objectID) } + return self.bridgeToSwift.context.fetchExisting(objects) } /** @@ -90,7 +76,7 @@ public extension CSBaseDataTransaction { @objc public func fetchExistingObjectsWithIDs(_ objectIDs: [NSManagedObjectID]) -> [Any] { - return objectIDs.flatMap { try? self.bridgeToSwift.context.existingObject(with: $0) } + return self.bridgeToSwift.context.fetchExisting(objectIDs) } /** diff --git a/Sources/ObjectiveC/CSDataStack+Querying.swift b/Sources/ObjectiveC/CSDataStack+Querying.swift index 0a9d466..4db3ce1 100644 --- a/Sources/ObjectiveC/CSDataStack+Querying.swift +++ b/Sources/ObjectiveC/CSDataStack+Querying.swift @@ -40,14 +40,7 @@ public extension CSDataStack { @objc public func fetchExistingObject(_ object: NSManagedObject) -> Any? { - do { - - return try self.bridgeToSwift.mainContext.existingObject(with: object.objectID) - } - catch _ { - - return nil - } + return self.bridgeToSwift.mainContext.fetchExisting(object) } /** @@ -59,14 +52,7 @@ public extension CSDataStack { @objc public func fetchExistingObjectWithID(_ objectID: NSManagedObjectID) -> Any? { - do { - - return try self.bridgeToSwift.mainContext.existingObject(with: objectID) - } - catch _ { - - return nil - } + return self.bridgeToSwift.mainContext.fetchExisting(objectID) } /** @@ -78,7 +64,7 @@ public extension CSDataStack { @objc public func fetchExistingObjects(_ objects: [NSManagedObject]) -> [Any] { - return objects.flatMap { try? self.bridgeToSwift.mainContext.existingObject(with: $0.objectID) } + return self.bridgeToSwift.mainContext.fetchExisting(objects) } /** @@ -90,7 +76,7 @@ public extension CSDataStack { @objc public func fetchExistingObjectsWithIDs(_ objectIDs: [NSManagedObjectID]) -> [Any] { - return objectIDs.flatMap { try? self.bridgeToSwift.mainContext.existingObject(with: $0) } + return self.bridgeToSwift.mainContext.fetchExisting(objectIDs) } /** diff --git a/Sources/ObjectiveC/NSFetchedResultsController+ObjectiveC.swift b/Sources/ObjectiveC/NSFetchedResultsController+ObjectiveC.swift index 5a5345e..66dee61 100644 --- a/Sources/ObjectiveC/NSFetchedResultsController+ObjectiveC.swift +++ b/Sources/ObjectiveC/NSFetchedResultsController+ObjectiveC.swift @@ -35,6 +35,7 @@ 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. + - Note: It is the caller's responsibility to call `-performFetch:` on the created `NSFetchedResultsController`. - 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. @@ -60,6 +61,7 @@ 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. + - Note: It is the caller's responsibility to call `-performFetch:` on the created `NSFetchedResultsController`. - 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 diff --git a/Sources/Observing/ListObserver.swift b/Sources/Observing/ListObserver.swift index ce1e2a0..17c7254 100644 --- a/Sources/Observing/ListObserver.swift +++ b/Sources/Observing/ListObserver.swift @@ -49,28 +49,33 @@ public protocol ListObserver: class { associatedtype ListEntityType: NSManagedObject /** - Handles processing just before a change to the observed list occurs + Handles processing just before a change to the observed list occurs. (Optional) + The default implementation does nothing. - parameter monitor: the `ListMonitor` monitoring the list being observed */ func listMonitorWillChange(_ monitor: ListMonitor) /** - Handles processing right after a change to the observed list occurs + Handles processing right after a change to the observed list occurs. (Optional) + The default implementation does nothing. - parameter monitor: the `ListMonitor` monitoring the object being observed */ func listMonitorDidChange(_ monitor: ListMonitor) /** - This method is broadcast from within the `ListMonitor`'s `refetch(...)` method to let observers prepare for the internal `NSFetchedResultsController`'s pending change to its predicate, sort descriptors, etc. Note that the actual refetch will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. + This method is broadcast from within the `ListMonitor`'s `refetch(...)` method to let observers prepare for the internal `NSFetchedResultsController`'s pending change to its predicate, sort descriptors, etc. (Optional) + The default implementation does nothing. + - Note: The actual refetch will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes - parameter monitor: the `ListMonitor` monitoring the object being observed */ func listMonitorWillRefetch(_ monitor: ListMonitor) /** - After the `ListMonitor`'s `refetch(...)` method is called, this method is broadcast after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. + After the `ListMonitor`'s `refetch(...)` method is called, this method is broadcast after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes. (Optional) + The default implementation does nothing. - parameter monitor: the `ListMonitor` monitoring the object being observed */ @@ -82,25 +87,13 @@ public protocol ListObserver: class { public extension ListObserver { - /** - The default implementation does nothing. - */ - func listMonitorWillChange(_ monitor: ListMonitor) { } + public func listMonitorWillChange(_ monitor: ListMonitor) { } - /** - The default implementation does nothing. - */ - func listMonitorDidChange(_ monitor: ListMonitor) { } + public func listMonitorDidChange(_ monitor: ListMonitor) { } - /** - The default implementation does nothing. - */ - func listMonitorWillRefetch(_ monitor: ListMonitor) { } + public func listMonitorWillRefetch(_ monitor: ListMonitor) { } - /** - The default implementation does nothing. - */ - func listMonitorDidRefetch(_ monitor: ListMonitor) { } + public func listMonitorDidRefetch(_ monitor: ListMonitor) { } } @@ -119,7 +112,8 @@ public extension ListObserver { public protocol ListObjectObserver: ListObserver { /** - Notifies that an object was inserted to the specified `NSIndexPath` in the list + Notifies that an object was inserted to the specified `NSIndexPath` in the list. (Optional) + The default implementation does nothing. - parameter monitor: the `ListMonitor` monitoring the list being observed - parameter object: the entity type for the inserted object @@ -128,7 +122,8 @@ public protocol ListObjectObserver: ListObserver { func listMonitor(_ monitor: ListMonitor, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath) /** - Notifies that an object was deleted from the specified `NSIndexPath` in the list + Notifies that an object was deleted from the specified `NSIndexPath` in the list. (Optional) + The default implementation does nothing. - parameter monitor: the `ListMonitor` monitoring the list being observed - parameter object: the entity type for the deleted object @@ -137,7 +132,8 @@ public protocol ListObjectObserver: ListObserver { func listMonitor(_ monitor: ListMonitor, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath) /** - Notifies that an object at the specified `NSIndexPath` was updated + Notifies that an object at the specified `NSIndexPath` was updated. (Optional) + The default implementation does nothing. - parameter monitor: the `ListMonitor` monitoring the list being observed - parameter object: the entity type for the updated object @@ -146,7 +142,8 @@ public protocol ListObjectObserver: ListObserver { func listMonitor(_ monitor: ListMonitor, didUpdateObject object: ListEntityType, atIndexPath indexPath: IndexPath) /** - Notifies that an object's index changed + Notifies that an object's index changed. (Optional) + The default implementation does nothing. - parameter monitor: the `ListMonitor` monitoring the list being observed - parameter object: the entity type for the moved object @@ -161,25 +158,13 @@ public protocol ListObjectObserver: ListObserver { public extension ListObjectObserver { - /** - The default implementation does nothing. - */ - func listMonitor(_ monitor: ListMonitor, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath) { } + public func listMonitor(_ monitor: ListMonitor, didInsertObject object: ListEntityType, toIndexPath indexPath: IndexPath) { } - /** - The default implementation does nothing. - */ - func listMonitor(_ monitor: ListMonitor, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath) { } + public func listMonitor(_ monitor: ListMonitor, didDeleteObject object: ListEntityType, fromIndexPath indexPath: IndexPath) { } - /** - The default implementation does nothing. - */ - func listMonitor(_ monitor: ListMonitor, didUpdateObject object: ListEntityType, atIndexPath indexPath: IndexPath) { } + public func listMonitor(_ monitor: ListMonitor, didUpdateObject object: ListEntityType, atIndexPath indexPath: IndexPath) { } - /** - The default implementation does nothing. - */ - func listMonitor(_ monitor: ListMonitor, didMoveObject object: ListEntityType, fromIndexPath: IndexPath, toIndexPath: IndexPath) { } + public func listMonitor(_ monitor: ListMonitor, didMoveObject object: ListEntityType, fromIndexPath: IndexPath, toIndexPath: IndexPath) { } } @@ -199,7 +184,8 @@ public extension ListObjectObserver { public protocol ListSectionObserver: ListObjectObserver { /** - Notifies that a section was inserted at the specified index + Notifies that a section was inserted at the specified index. (Optional) + The default implementation does nothing. - parameter monitor: the `ListMonitor` monitoring the list being observed - parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section @@ -208,7 +194,8 @@ public protocol ListSectionObserver: ListObjectObserver { func listMonitor(_ monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) /** - Notifies that a section was inserted at the specified index + Notifies that a section was inserted at the specified index. (Optional) + The default implementation does nothing. - parameter monitor: the `ListMonitor` monitoring the list being observed - parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section @@ -222,15 +209,9 @@ public protocol ListSectionObserver: ListObjectObserver { public extension ListSectionObserver { - /** - The default implementation does nothing. - */ - func listMonitor(_ monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { } + public func listMonitor(_ monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { } - /** - The default implementation does nothing. - */ - func listMonitor(_ monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { } + public func listMonitor(_ monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { } } #endif diff --git a/Sources/Observing/ObjectObserver.swift b/Sources/Observing/ObjectObserver.swift index 3d622ae..b9fac1a 100644 --- a/Sources/Observing/ObjectObserver.swift +++ b/Sources/Observing/ObjectObserver.swift @@ -46,7 +46,8 @@ public protocol ObjectObserver: class { associatedtype ObjectEntityType: NSManagedObject /** - Handles processing just before a change to the observed `object` occurs + Handles processing just before a change to the observed `object` occurs. (Optional) + The default implementation does nothing. - parameter monitor: the `ObjectMonitor` monitoring the object being observed - parameter object: the `NSManagedObject` instance being observed @@ -54,7 +55,8 @@ public protocol ObjectObserver: class { func objectMonitor(_ monitor: ObjectMonitor, willUpdateObject object: ObjectEntityType) /** - Handles processing right after a change to the observed `object` occurs + Handles processing right after a change to the observed `object` occurs. (Optional) + The default implementation does nothing. - parameter monitor: the `ObjectMonitor` monitoring the object being observed - parameter object: the `NSManagedObject` instance being observed @@ -63,7 +65,8 @@ public protocol ObjectObserver: class { func objectMonitor(_ monitor: ObjectMonitor, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set) /** - Handles processing right after `object` is deleted + Handles processing right after `object` is deleted. (Optional) + The default implementation does nothing. - parameter monitor: the `ObjectMonitor` monitoring the object being observed - parameter object: the `NSManagedObject` instance being observed @@ -76,20 +79,11 @@ public protocol ObjectObserver: class { public extension ObjectObserver { - /** - The default implementation does nothing. - */ - func objectMonitor(_ monitor: ObjectMonitor, willUpdateObject object: ObjectEntityType) { } + public func objectMonitor(_ monitor: ObjectMonitor, willUpdateObject object: ObjectEntityType) { } - /** - The default implementation does nothing. - */ - func objectMonitor(_ monitor: ObjectMonitor, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set) { } + public func objectMonitor(_ monitor: ObjectMonitor, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set) { } - /** - The default implementation does nothing. - */ - func objectMonitor(_ monitor: ObjectMonitor, didDeleteObject object: ObjectEntityType) { } + public func objectMonitor(_ monitor: ObjectMonitor, didDeleteObject object: ObjectEntityType) { } } #endif diff --git a/Sources/Setup/DataStack.swift b/Sources/Setup/DataStack.swift index 59eed98..9dfea89 100644 --- a/Sources/Setup/DataStack.swift +++ b/Sources/Setup/DataStack.swift @@ -71,6 +71,8 @@ public final class DataStack { self.migrationChain = migrationChain self.rootSavingContext.parentStack = self + + self.mainContext.isDataStackContext = true } /** diff --git a/Sources/Transactions/BaseDataTransaction.swift b/Sources/Transactions/BaseDataTransaction.swift index 3acccad..6b6c0ec 100644 --- a/Sources/Transactions/BaseDataTransaction.swift +++ b/Sources/Transactions/BaseDataTransaction.swift @@ -470,6 +470,7 @@ public /*abstract*/ class BaseDataTransaction { self.bypassesQueueing = bypassesQueueing context.parentTransaction = self + context.isTransactionContext = true if !supportsUndo { context.undoManager = nil diff --git a/Sources/Transactions/UnsafeDataTransaction.swift b/Sources/Transactions/UnsafeDataTransaction.swift index e36ee70..ceb0bf9 100644 --- a/Sources/Transactions/UnsafeDataTransaction.swift +++ b/Sources/Transactions/UnsafeDataTransaction.swift @@ -135,18 +135,6 @@ public final class UnsafeDataTransaction: BaseDataTransaction { ) } - /** - Returns the `NSManagedObjectContext` for this unsafe transaction. Use only for cases where external frameworks need an `NSManagedObjectContext` instance to work with. - - - Important: It is the developer's responsibility to ensure the following: - - that the `UnsafeDataTransaction` that owns this context should be strongly referenced and prevented from being deallocated during the context's lifetime - - that all saves will be done either through the `UnsafeDataTransaction`'s `commit(...)` method, or by calling `save()` manually on the context, its parent, and all other ancestor contexts if there are any. - */ - public var internalContext: NSManagedObjectContext { - - return self.context - } - // MARK: Internal @@ -154,4 +142,13 @@ public final class UnsafeDataTransaction: BaseDataTransaction { super.init(mainContext: mainContext, queue: queue, supportsUndo: supportsUndo, bypassesQueueing: true) } + + + // MARK: Obsolete + + @available(*, obsoleted: 3.0.0, message: "Transaction contexts are now exposed through the FetchableSource and QueryableSource protocols.", renamed: "internalContext()") + public var internalContext: NSManagedObjectContext { + + fatalError() + } }