From e5a199489cb061121fd65041bf0381e19127c1f0 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Sun, 19 Jun 2016 02:40:25 +0900 Subject: [PATCH] WIP: objc utilities --- CoreStore.xcodeproj/project.pbxproj | 25 ++ .../BaseTests/BaseTestDataTestCase.swift | 2 +- CoreStoreTests/BridgingTests.m | 48 +- CoreStoreTests/FromTests.swift | 26 +- CoreStoreTests/ImportTests.swift | 12 +- CoreStoreTests/ListObserverTests.swift | 410 +++++++++++++++++- CoreStoreTests/ObjectObserverTests.swift | 9 +- Sources/CoreStore.h | 2 + .../Concrete Clauses/From.swift | 182 +++----- .../Concrete Clauses/GroupBy.swift | 21 +- .../Concrete Clauses/Select.swift | 3 +- .../Concrete Clauses/Where.swift | 19 +- ...reStore+CustomDebugStringConvertible.swift | 4 +- Sources/ObjectiveC/CSFrom.swift | 59 ++- Sources/ObjectiveC/CSGroupBy.swift | 8 + Sources/ObjectiveC/CSWhere.swift | 21 +- Sources/ObjectiveC/CoreStoreBridge.h | 152 +++++++ Sources/ObjectiveC/CoreStoreBridge.m | 76 ++++ Sources/Observing/ListMonitor.swift | 11 + 19 files changed, 913 insertions(+), 177 deletions(-) create mode 100644 Sources/ObjectiveC/CoreStoreBridge.h create mode 100644 Sources/ObjectiveC/CoreStoreBridge.m diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index cfbd89e..336900d 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -207,6 +207,11 @@ B52DD1C91BE1F94600949AFE /* NSManagedObjectContext+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */; }; B52DD1CA1BE1F94600949AFE /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; B52DD1CB1BE1F94600949AFE /* WeakObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2D1AFF849C0064E85B /* WeakObject.swift */; }; + B538BA761D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; }; + B538BA771D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; }; + B538BA781D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; }; + B538BA791D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; }; + B538BA7A1D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; }; B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; B53FB9FF1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; }; @@ -283,6 +288,11 @@ B5519A601CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5519A5E1CA21954002BEF78 /* CSAsynchronousDataTransaction.swift */; }; B5519A611CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5519A5E1CA21954002BEF78 /* CSAsynchronousDataTransaction.swift */; }; B5519A621CA21954002BEF78 /* CSAsynchronousDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5519A5E1CA21954002BEF78 /* CSAsynchronousDataTransaction.swift */; }; + B55717431D15B09D009BDBCA /* CoreStoreBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = B55717421D15AF9C009BDBCA /* CoreStoreBridge.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B55717441D15B09E009BDBCA /* CoreStoreBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = B55717421D15AF9C009BDBCA /* CoreStoreBridge.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B55717451D15B09F009BDBCA /* CoreStoreBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = B55717421D15AF9C009BDBCA /* CoreStoreBridge.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B55717461D15B0A1009BDBCA /* CoreStoreBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = B55717421D15AF9C009BDBCA /* CoreStoreBridge.h */; settings = {ATTRIBUTES = (Public, ); }; }; + B55717471D15B0A1009BDBCA /* CoreStoreBridge.h in Headers */ = {isa = PBXBuildFile; fileRef = B55717421D15AF9C009BDBCA /* CoreStoreBridge.h */; settings = {ATTRIBUTES = (Public, ); }; }; B5598BCC1BE2093D0092EFCE /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; B559CD431CAA8B6300E4D58B /* CSSetupResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559CD421CAA8B6300E4D58B /* CSSetupResult.swift */; }; B559CD441CAA8B6300E4D58B /* CSSetupResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B559CD421CAA8B6300E4D58B /* CSSetupResult.swift */; }; @@ -715,6 +725,7 @@ B529C2031CA4A2DB007E7EBD /* CSSaveResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSaveResult.swift; sourceTree = ""; }; B52DD1741BE1F8CC00949AFE /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B52DD17D1BE1F8CC00949AFE /* CoreStoreTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = CoreStoreTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + B538BA701D15B3E30003A766 /* CoreStoreBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreStoreBridge.m; sourceTree = ""; }; B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSMigrationResult.swift; sourceTree = ""; }; B53FBA031CAB300C00F0D40A /* CSMigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSMigrationType.swift; sourceTree = ""; }; B53FBA0A1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSCoreStore+Migrating.swift"; sourceTree = ""; }; @@ -737,6 +748,7 @@ B5519A5E1CA21954002BEF78 /* CSAsynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSAsynchronousDataTransaction.swift; sourceTree = ""; }; B5548CD51BD65AE00077652A /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/Foundation.framework; sourceTree = DEVELOPER_DIR; }; B5548CD71BD65AE50077652A /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.11.sdk/System/Library/Frameworks/CoreData.framework; sourceTree = DEVELOPER_DIR; }; + B55717421D15AF9C009BDBCA /* CoreStoreBridge.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = CoreStoreBridge.h; sourceTree = ""; }; B559CD421CAA8B6300E4D58B /* CSSetupResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSSetupResult.swift; sourceTree = ""; }; B559CD481CAA8C6D00E4D58B /* CSStorageInterface.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSStorageInterface.swift; sourceTree = ""; }; B56007101B3F6BD500A9A8F9 /* Into.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Into.swift; sourceTree = ""; }; @@ -1155,6 +1167,8 @@ B5DBE2CB1C99148100B5CEFA /* ObjectiveC */ = { isa = PBXGroup; children = ( + B55717421D15AF9C009BDBCA /* CoreStoreBridge.h */, + B538BA701D15B3E30003A766 /* CoreStoreBridge.m */, B5AEFAB41C9962AE00AD137F /* CoreStoreBridge.swift */, B5DBE2CC1C9914A900B5CEFA /* CSCoreStore.swift */, B5519A491CA1F4FB002BEF78 /* CSError.swift */, @@ -1368,6 +1382,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + B55717441D15B09E009BDBCA /* CoreStoreBridge.h in Headers */, 2F03A53619C5C6DA005002A5 /* CoreStore.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1376,6 +1391,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + B55717451D15B09F009BDBCA /* CoreStoreBridge.h in Headers */, 82BA18A01C4BBD1400A0916E /* CoreStore.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1384,6 +1400,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + B55717461D15B0A1009BDBCA /* CoreStoreBridge.h in Headers */, B52DD1931BE1F8FD00949AFE /* CoreStore.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1392,6 +1409,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + B55717471D15B0A1009BDBCA /* CoreStoreBridge.h in Headers */, B563217E1BD65110006C9394 /* CoreStore.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1400,6 +1418,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + B55717431D15B09D009BDBCA /* CoreStoreBridge.h in Headers */, B5D9E32F1CA2C317007A9D52 /* CoreStore.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -1764,6 +1783,7 @@ B5E84F101AFF847B0064E85B /* GroupBy.swift in Sources */, B5E84F201AFF84860064E85B /* DataStack+Observing.swift in Sources */, B501FDDD1CA8D05000BE22EF /* CSSectionBy.swift in Sources */, + B538BA771D15B3E30003A766 /* CoreStoreBridge.m in Sources */, B59FA0AE1CCBAC95007C9BCA /* ICloudStore.swift in Sources */, B5E84EF81AFF846E0064E85B /* CoreStore+Transaction.swift in Sources */, B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */, @@ -1911,6 +1931,7 @@ 82BA18B51C4BBD3F00A0916E /* BaseDataTransaction+Querying.swift in Sources */, B501FDDF1CA8D05000BE22EF /* CSSectionBy.swift in Sources */, B59FA0B01CCBACA7007C9BCA /* ICloudStore.swift in Sources */, + B538BA781D15B3E30003A766 /* CoreStoreBridge.m in Sources */, 82BA18D31C4BBD7100A0916E /* NSManagedObjectContext+CoreStore.swift in Sources */, 82BA18AD1C4BBD3100A0916E /* UnsafeDataTransaction.swift in Sources */, B546F96A1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, @@ -2058,6 +2079,7 @@ B5220E191D130761009BC71E /* ListMonitor.swift in Sources */, B5220E181D130711009BC71E /* ObjectObserver.swift in Sources */, B5220E251D13088E009BC71E /* ListObserver.swift in Sources */, + B538BA7A1D15B3E30003A766 /* CoreStoreBridge.m in Sources */, B52DD1A01BE1F92C00949AFE /* UnsafeDataTransaction.swift in Sources */, B5ECDC331CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */, B52DD1BB1BE1F94000949AFE /* MigrationType.swift in Sources */, @@ -2205,6 +2227,7 @@ B56321921BD65216006C9394 /* BaseDataTransaction+Querying.swift in Sources */, B501FDE01CA8D05000BE22EF /* CSSectionBy.swift in Sources */, B59FA0B11CCBACA7007C9BCA /* ICloudStore.swift in Sources */, + B538BA791D15B3E30003A766 /* CoreStoreBridge.m in Sources */, B56321B11BD6521C006C9394 /* NSManagedObjectContext+CoreStore.swift in Sources */, B563218D1BD65216006C9394 /* CoreStore+Transaction.swift in Sources */, B546F96B1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */, @@ -2233,6 +2256,7 @@ B5ECDC0C1CA8161B00C7F112 /* CSGroupBy.swift in Sources */, B5EA11DD1CA3AFD9002282F8 /* NSPersistentStoreCoordinator+Setup.swift in Sources */, B53FBA191CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */, + B538BA761D15B3E30003A766 /* CoreStoreBridge.m in Sources */, B5ECDC3A1CA8369400C7F112 /* CSDataStack.swift in Sources */, B5D9E2EF1CA2C317007A9D52 /* ObjectMonitor.swift in Sources */, B5ECDC3B1CA836AD00C7F112 /* CoreStoreBridge.swift in Sources */, @@ -2734,6 +2758,7 @@ PRODUCT_NAME = CoreStore_iOS7; SDKROOT = iphoneos; SKIP_INSTALL = YES; + SWIFT_OPTIMIZATION_LEVEL = "-Onone"; }; name = Debug; }; diff --git a/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift b/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift index d9601b8..64140d1 100644 --- a/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift +++ b/CoreStoreTests/BaseTests/BaseTestDataTestCase.swift @@ -28,7 +28,7 @@ class BaseTestDataTestCase: BaseTestCase { }() @nonobjc - func prepareTestDataForStack(stack: DataStack, configurations: [String?]) { + func prepareTestDataForStack(stack: DataStack, configurations: [String?] = [nil]) { stack.beginSynchronous { (transaction) in diff --git a/CoreStoreTests/BridgingTests.m b/CoreStoreTests/BridgingTests.m index 792037d..b62d65c 100644 --- a/CoreStoreTests/BridgingTests.m +++ b/CoreStoreTests/BridgingTests.m @@ -24,14 +24,17 @@ // #import "BridgingTests.h" +#import #import +#import "CoreStoreTests-Swift.h" @import CoreData; +// MARK: - BridgingTests @implementation BridgingTests -- (void)testFlags { +- (void)test_ThatFlags_HaveCorrectValues { XCTAssertEqual(CSLocalStorageOptionsNone, 0); XCTAssertEqual(CSLocalStorageOptionsRecreateStoreOnModelMismatch, 1); @@ -39,7 +42,48 @@ XCTAssertEqual(CSLocalStorageOptionsAllowSynchronousLightweightMigration, 4); } -- (void)testDataStack { +- (void)test_ThatFromClauses_BridgeCorrectly { + + { + CSFrom *from = From([TestEntity1 class]); + XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]); + XCTAssertNil(from.configurations); + } + { + CSFrom *from = From([TestEntity1 class], @[[NSNull null], @"Config2"]); + XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]); + + NSArray *configurations = @[[NSNull null], @"Config2"]; + XCTAssertEqualObjects(from.configurations, configurations); + } +} + +- (void)test_ThatWhereClauses_BridgeCorrectly { + + { + CSWhere *where = Where(@"%K == %@", @"key", @"value"); + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"key", @"value"]; + XCTAssertEqualObjects(where.predicate, predicate); + } + { + CSWhere *where = Where(YES); + NSPredicate *predicate = [NSPredicate predicateWithValue:YES]; + XCTAssertEqualObjects(where.predicate, predicate); + } + { + CSWhere *where = Where([NSPredicate predicateWithFormat:@"%K == %@", @"key", @"value"]); + NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"key", @"value"]; + XCTAssertEqualObjects(where.predicate, predicate); + } +} + +- (void)test_ThatGroupByClauses_BridgeCorrectly { + + CSGroupBy *groupBy = GroupBy(@[@"key"]); + XCTAssertEqualObjects(groupBy.keyPaths, @[@"key"]); +} + +- (void)test_ThatDataStacks_BridgeCorrectly { CSDataStack *dataStack = [[CSDataStack alloc] initWithModelName:@"Model" diff --git a/CoreStoreTests/FromTests.swift b/CoreStoreTests/FromTests.swift index a94f5fa..48665ce 100644 --- a/CoreStoreTests/FromTests.swift +++ b/CoreStoreTests/FromTests.swift @@ -40,45 +40,35 @@ final class FromTests: BaseTestCase { let from = From() XCTAssert(from.entityClass === NSManagedObject.self) - XCTAssertNil(from.dumpInfo) + XCTAssertNil(from.configurations) } do { let from = From() XCTAssert(from.entityClass === TestEntity1.self) - XCTAssertNil(from.dumpInfo) + XCTAssertNil(from.configurations) } do { let from = From("Config1") XCTAssert(from.entityClass === TestEntity1.self) - - let dumpInfo = from.dumpInfo - XCTAssertEqual(dumpInfo?.key, "configurations") - - let configurations = dumpInfo?.value as! [String?] - XCTAssertEqual(configurations.count, 1) - XCTAssertEqual(configurations[0], "Config1") + XCTAssertEqual(from.configurations?.count, 1) + XCTAssertEqual(from.configurations?[0], "Config1") } do { let from = From(nil, "Config1") XCTAssert(from.entityClass === TestEntity1.self) - - let dumpInfo = from.dumpInfo - XCTAssertEqual(dumpInfo?.key, "configurations") - - let configurations = dumpInfo?.value as! [String?] - XCTAssertEqual(configurations.count, 2) - XCTAssertEqual(configurations[0], nil) - XCTAssertEqual(configurations[1], "Config1") + XCTAssertEqual(from.configurations?.count, 2) + XCTAssertEqual(from.configurations?[0], nil) + XCTAssertEqual(from.configurations?[1], "Config1") } } @objc dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultConfigurations() { - self.prepareStack(configurations: [nil]) { (dataStack) in + self.prepareStack { (dataStack) in do { diff --git a/CoreStoreTests/ImportTests.swift b/CoreStoreTests/ImportTests.swift index 789a622..5190765 100644 --- a/CoreStoreTests/ImportTests.swift +++ b/CoreStoreTests/ImportTests.swift @@ -349,7 +349,7 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - self.prepareTestDataForStack(stack, configurations: [nil]) + self.prepareTestDataForStack(stack) stack.beginSynchronous { (transaction) in @@ -419,7 +419,7 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - self.prepareTestDataForStack(stack, configurations: [nil]) + self.prepareTestDataForStack(stack) stack.beginSynchronous { (transaction) in @@ -519,7 +519,7 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - self.prepareTestDataForStack(stack, configurations: [nil]) + self.prepareTestDataForStack(stack) stack.beginSynchronous { (transaction) in @@ -598,7 +598,7 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - self.prepareTestDataForStack(stack, configurations: [nil]) + self.prepareTestDataForStack(stack) stack.beginSynchronous { (transaction) in @@ -656,7 +656,7 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - self.prepareTestDataForStack(stack, configurations: [nil]) + self.prepareTestDataForStack(stack) stack.beginSynchronous { (transaction) in @@ -818,7 +818,7 @@ class ImportTests: BaseTestDataTestCase { self.prepareStack { (stack) in - self.prepareTestDataForStack(stack, configurations: [nil]) + self.prepareTestDataForStack(stack) stack.beginSynchronous { (transaction) in diff --git a/CoreStoreTests/ListObserverTests.swift b/CoreStoreTests/ListObserverTests.swift index 258869a..b5fdb08 100644 --- a/CoreStoreTests/ListObserverTests.swift +++ b/CoreStoreTests/ListObserverTests.swift @@ -44,12 +44,12 @@ class ListObserverTests: BaseTestDataTestCase { let monitor = stack.monitorSectionedList( From(TestEntity1), SectionBy("testBoolean"), - OrderBy(.Ascending("testEntityID")) + OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID")) ) monitor.addObserver(observer) + XCTAssertFalse(monitor.hasSections()) XCTAssertFalse(monitor.hasObjects()) - XCTAssertFalse(monitor.hasObjectsInSection(0)) XCTAssertTrue(monitor.objectsInAllSections().isEmpty) var events = 0 @@ -59,6 +59,7 @@ class ListObserverTests: BaseTestDataTestCase { object: observer, handler: { (note) -> Bool in + XCTAssertEqual(events, 0) XCTAssertEqual((note.userInfo ?? [:]), NSDictionary()) defer { @@ -72,6 +73,7 @@ class ListObserverTests: BaseTestDataTestCase { object: observer, handler: { (note) -> Bool in + XCTAssertEqual(events, 1) XCTAssertEqual( (note.userInfo ?? [:]), [ @@ -91,11 +93,20 @@ class ListObserverTests: BaseTestDataTestCase { object: observer, handler: { (note) -> Bool in - let indexPath = note.userInfo?["indexPath"] as? NSIndexPath + XCTAssertEqual(events, 2) + + let userInfo = note.userInfo + XCTAssertNotNil(userInfo) + XCTAssertEqual( + Set(((userInfo as? [String: AnyObject]) ?? [:]).keys), + ["indexPath", "object"] + ) + + let indexPath = userInfo?["indexPath"] as? NSIndexPath XCTAssertEqual(indexPath?.section, 0) XCTAssertEqual(indexPath?.row, 0) - let object = note.userInfo?["object"] as? TestEntity1 + let object = userInfo?["object"] as? TestEntity1 XCTAssertEqual(object?.testBoolean, NSNumber(bool: true)) XCTAssertEqual(object?.testNumber, NSNumber(integer: 1)) XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1")) @@ -149,6 +160,397 @@ class ListObserverTests: BaseTestDataTestCase { self.waitAndCheckExpectations() } } + + @objc + dynamic func test_ThatListObservers_CanReceiveUpdateNotifications() { + + self.prepareStack { (stack) in + + self.prepareTestDataForStack(stack) + + let observer = TestListObserver() + let monitor = stack.monitorSectionedList( + From(TestEntity1), + SectionBy("testBoolean"), + OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID")) + ) + monitor.addObserver(observer) + + XCTAssertTrue(monitor.hasSections()) + XCTAssertEqual(monitor.numberOfSections(), 2) + XCTAssertTrue(monitor.hasObjects()) + XCTAssertTrue(monitor.hasObjectsInSection(0)) + XCTAssertEqual(monitor.numberOfObjectsInSection(0), 2) + XCTAssertEqual(monitor.numberOfObjectsInSection(1), 3) + + var events = 0 + + let willChangeExpectation = self.expectationForNotification( + "listMonitorWillChange:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual(events, 0) + XCTAssertEqual((note.userInfo ?? [:]), NSDictionary()) + defer { + + events += 1 + } + return events == 0 + } + ) + for _ in 1 ... 2 { + + let didUpdateObjectExpectation = self.expectationForNotification( + "listMonitor:didUpdateObject:atIndexPath:", + object: observer, + handler: { (note) -> Bool in + + XCTAssert(events == 1 || events == 2) + + let userInfo = note.userInfo + XCTAssertNotNil(userInfo) + XCTAssertEqual( + Set(((userInfo as? [String: AnyObject]) ?? [:]).keys), + ["indexPath", "object"] + ) + + let indexPath = userInfo?["indexPath"] as? NSIndexPath + let object = userInfo?["object"] as? TestEntity1 + + switch object?.testEntityID { + + case NSNumber(integer: 101)?: + XCTAssertEqual(indexPath?.section, 1) + XCTAssertEqual(indexPath?.row, 0) + + XCTAssertEqual(object?.testBoolean, NSNumber(bool: true)) + XCTAssertEqual(object?.testNumber, NSNumber(integer: 11)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:11") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-11T00:00:00Z")!) + + case NSNumber(integer: 102)?: + XCTAssertEqual(indexPath?.section, 0) + XCTAssertEqual(indexPath?.row, 0) + + XCTAssertEqual(object?.testBoolean, NSNumber(bool: false)) + XCTAssertEqual(object?.testNumber, NSNumber(integer: 22)) + XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22")) + XCTAssertEqual(object?.testString, "nil:TestEntity1:22") + XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!) + XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-22T00:00:00Z")!) + + default: + XCTFail() + } + defer { + + events += 1 + } + return events == 1 || events == 2 + } + ) + } + let didChangeExpectation = self.expectationForNotification( + "listMonitorDidChange:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual(events, 3) + XCTAssertEqual((note.userInfo ?? [:]), NSDictionary()) + defer { + + events += 1 + } + return events == 3 + } + ) + let saveExpectation = self.expectationWithDescription("save") + stack.beginAsynchronous { (transaction) in + + if let object = transaction.fetchOne( + From(TestEntity1), + Where("testEntityID", isEqualTo: 101)) { + + object.testNumber = NSNumber(integer: 11) + object.testDecimal = NSDecimalNumber(string: "11") + object.testString = "nil:TestEntity1:11" + object.testData = ("nil:TestEntity1:11" as NSString).dataUsingEncoding(NSUTF8StringEncoding)! + object.testDate = self.dateFormatter.dateFromString("2000-01-11T00:00:00Z")! + } + else { + + XCTFail() + } + if let object = transaction.fetchOne( + From(TestEntity1), + Where("testEntityID", isEqualTo: 102)) { + + object.testNumber = NSNumber(integer: 22) + object.testDecimal = NSDecimalNumber(string: "22") + object.testString = "nil:TestEntity1:22" + object.testData = ("nil:TestEntity1:22" as NSString).dataUsingEncoding(NSUTF8StringEncoding)! + object.testDate = self.dateFormatter.dateFromString("2000-01-22T00:00:00Z")! + } + else { + + XCTFail() + } + transaction.commit { (result) in + + switch result { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + + case .Failure: + XCTFail() + } + } + } + self.waitAndCheckExpectations() + } + } + + @objc + dynamic func test_ThatListObservers_CanReceiveMoveNotifications() { + + self.prepareStack { (stack) in + + self.prepareTestDataForStack(stack) + + let observer = TestListObserver() + let monitor = stack.monitorSectionedList( + From(TestEntity1), + SectionBy("testBoolean"), + OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID")) + ) + monitor.addObserver(observer) + + var events = 0 + + let willChangeExpectation = self.expectationForNotification( + "listMonitorWillChange:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual(events, 0) + XCTAssertEqual((note.userInfo ?? [:]), NSDictionary()) + defer { + + events += 1 + } + return events == 0 + } + ) + let didMoveObjectExpectation = self.expectationForNotification( + "listMonitor:didMoveObject:fromIndexPath:toIndexPath:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual(events, 1) + + let userInfo = note.userInfo + XCTAssertNotNil(userInfo) + XCTAssertEqual( + Set(((userInfo as? [String: AnyObject]) ?? [:]).keys), + ["fromIndexPath", "toIndexPath", "object"] + ) + + let fromIndexPath = userInfo?["fromIndexPath"] as? NSIndexPath + XCTAssertEqual(fromIndexPath?.section, 0) + XCTAssertEqual(fromIndexPath?.row, 0) + + let toIndexPath = userInfo?["toIndexPath"] as? NSIndexPath + XCTAssertEqual(toIndexPath?.section, 1) + XCTAssertEqual(toIndexPath?.row, 1) + + let object = userInfo?["object"] as? TestEntity1 + XCTAssertEqual(object?.testEntityID, NSNumber(integer: 102)) + XCTAssertEqual(object?.testBoolean, NSNumber(bool: true)) + + defer { + + events += 1 + } + return events == 1 + } + ) + let didChangeExpectation = self.expectationForNotification( + "listMonitorDidChange:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual(events, 2) + XCTAssertEqual((note.userInfo ?? [:]), NSDictionary()) + defer { + + events += 1 + } + return events == 2 + } + ) + let saveExpectation = self.expectationWithDescription("save") + stack.beginAsynchronous { (transaction) in + + if let object = transaction.fetchOne( + From(TestEntity1), + Where("testEntityID", isEqualTo: 102)) { + + object.testBoolean = NSNumber(bool: true) + } + else { + + XCTFail() + } + transaction.commit { (result) in + + switch result { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + + case .Failure: + XCTFail() + } + } + } + self.waitAndCheckExpectations() + } + } + + @objc + dynamic func test_ThatListObservers_CanReceiveDeleteNotifications() { + + self.prepareStack { (stack) in + + self.prepareTestDataForStack(stack) + + let observer = TestListObserver() + let monitor = stack.monitorSectionedList( + From(TestEntity1), + SectionBy("testBoolean"), + OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID")) + ) + monitor.addObserver(observer) + + var events = 0 + + let willChangeExpectation = self.expectationForNotification( + "listMonitorWillChange:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual(events, 0) + XCTAssertEqual((note.userInfo ?? [:]), NSDictionary()) + defer { + + events += 1 + } + return events == 0 + } + ) + for _ in 1 ... 2 { + + let didUpdateObjectExpectation = self.expectationForNotification( + "listMonitor:didDeleteObject:fromIndexPath:", + object: observer, + handler: { (note) -> Bool in + + XCTAssert(events == 1 || events == 2) + + let userInfo = note.userInfo + XCTAssertNotNil(userInfo) + XCTAssertEqual( + Set(((userInfo as? [String: AnyObject]) ?? [:]).keys), + ["indexPath", "object"] + ) + + let indexPath = userInfo?["indexPath"] as? NSIndexPath + + XCTAssertEqual(indexPath?.section, 0) + XCTAssert(indexPath?.row == 0 || indexPath?.row == 1) + + let object = userInfo?["object"] as? TestEntity1 + XCTAssertEqual(object?.deleted, true) + + defer { + + events += 1 + } + return events == 1 || events == 2 + } + ) + } + let didDeleteSectionExpectation = self.expectationForNotification( + "listMonitor:didDeleteSection:fromSectionIndex:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual(events, 3) + + let userInfo = note.userInfo + XCTAssertNotNil(userInfo) + XCTAssertEqual( + Set(((userInfo as? [String: AnyObject]) ?? [:]).keys), + ["sectionInfo", "sectionIndex"] + ) + + let sectionInfo = userInfo?["sectionInfo"] + XCTAssertNotNil(sectionInfo) + XCTAssertEqual(sectionInfo?.name, "0") + + let sectionIndex = userInfo?["sectionIndex"] + XCTAssertEqual(sectionIndex as? NSNumber, NSNumber(integer: 0)) + + defer { + + events += 1 + } + return events == 3 + } + ) + let didChangeExpectation = self.expectationForNotification( + "listMonitorDidChange:", + object: observer, + handler: { (note) -> Bool in + + XCTAssertEqual(events, 4) + XCTAssertEqual((note.userInfo ?? [:]), NSDictionary()) + defer { + + events += 1 + } + return events == 4 + } + ) + let saveExpectation = self.expectationWithDescription("save") + stack.beginAsynchronous { (transaction) in + + transaction.deleteAll( + From(TestEntity1), + Where("testBoolean", isEqualTo: false) + ) + transaction.commit { (result) in + + switch result { + + case .Success(let hasChanges): + XCTAssertTrue(hasChanges) + saveExpectation.fulfill() + + case .Failure: + XCTFail() + } + } + } + self.waitAndCheckExpectations() + } + } } diff --git a/CoreStoreTests/ObjectObserverTests.swift b/CoreStoreTests/ObjectObserverTests.swift index c3d7f5d..cbcdd81 100644 --- a/CoreStoreTests/ObjectObserverTests.swift +++ b/CoreStoreTests/ObjectObserverTests.swift @@ -40,7 +40,7 @@ class ObjectObserverTests: BaseTestDataTestCase { self.prepareStack { (stack) in - self.prepareTestDataForStack(stack, configurations: [nil]) + self.prepareTestDataForStack(stack) guard let object = stack.fetchOne( From(TestEntity1), @@ -63,6 +63,7 @@ class ObjectObserverTests: BaseTestDataTestCase { object: observer, handler: { (note) -> Bool in + XCTAssertEqual(events, 0) XCTAssertEqual( (note.userInfo ?? [:]), ["object": object] as NSDictionary @@ -79,6 +80,7 @@ class ObjectObserverTests: BaseTestDataTestCase { object: observer, handler: { (note) -> Bool in + XCTAssertEqual(events, 1) XCTAssertEqual( (note.userInfo ?? [:]), [ @@ -89,7 +91,7 @@ class ObjectObserverTests: BaseTestDataTestCase { "testString" ] ) - ] as NSDictionary + ] as NSDictionary ) let object = note.userInfo?["object"] as? TestEntity1 XCTAssertEqual(object?.testNumber, NSNumber(integer: 10)) @@ -135,7 +137,7 @@ class ObjectObserverTests: BaseTestDataTestCase { self.prepareStack { (stack) in - self.prepareTestDataForStack(stack, configurations: [nil]) + self.prepareTestDataForStack(stack) guard let object = stack.fetchOne( From(TestEntity1), @@ -158,6 +160,7 @@ class ObjectObserverTests: BaseTestDataTestCase { object: observer, handler: { (note) -> Bool in + XCTAssertEqual(events, 0) XCTAssertEqual( (note.userInfo ?? [:]), ["object": object] as NSDictionary diff --git a/Sources/CoreStore.h b/Sources/CoreStore.h index 857b918..6e19c44 100644 --- a/Sources/CoreStore.h +++ b/Sources/CoreStore.h @@ -28,3 +28,5 @@ FOUNDATION_EXPORT double CoreStoreVersionNumber; FOUNDATION_EXPORT const unsigned char CoreStoreVersionString[]; + +#import "CoreStoreBridge.h" diff --git a/Sources/Fetching and Querying/Concrete Clauses/From.swift b/Sources/Fetching and Querying/Concrete Clauses/From.swift index 3121534..e5f43db 100644 --- a/Sources/Fetching and Querying/Concrete Clauses/From.swift +++ b/Sources/Fetching and Querying/Concrete Clauses/From.swift @@ -41,6 +41,17 @@ import CoreData */ public struct From { + /** + The associated `NSManagedObject` entity class + */ + public let entityClass: AnyClass + + /** + The `NSPersistentStore` configuration names to associate objects from. + May contain `String`s to pertain to named configurations, or `nil` to pertain to the default configuration + */ + public let configurations: [String?]? + /** Initializes a `From` clause. ``` @@ -49,7 +60,7 @@ public struct From { */ public init(){ - self.init(entityClass: T.self) + self.init(entityClass: T.self, configurations: nil) } /** @@ -58,11 +69,11 @@ public struct From { let people = transaction.fetchAll(From()) ``` - - parameter entity: the `NSManagedObject` type to be created + - parameter entity: the associated `NSManagedObject` type */ public init(_ entity: T.Type) { - self.init(entityClass: entity) + self.init(entityClass: entity, configurations: nil) } /** @@ -71,7 +82,7 @@ public struct From { let people = transaction.fetchAll(From()) ``` - - parameter entityClass: the `NSManagedObject` class type to be created + - parameter entityClass: the associated `NSManagedObject` entity class */ public init(_ entityClass: AnyClass) { @@ -79,7 +90,7 @@ public struct From { entityClass is T.Type, "Attempted to create generic type \(cs_typeName(From)) with entity class \(cs_typeName(entityClass))" ) - self.init(entityClass: entityClass) + self.init(entityClass: entityClass, configurations: nil) } /** @@ -178,9 +189,6 @@ public struct From { // MARK: Internal - internal let entityClass: AnyClass - internal let dumpInfo: (key: String, value: Any)? - @warn_unused_result internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext, applyAffectedStores: Bool = true) -> Bool { @@ -211,7 +219,7 @@ public struct From { return From( entityClass: self.entityClass, - dumpInfo: self.dumpInfo, + configurations: self.configurations, findPersistentStores: self.findPersistentStores ) } @@ -221,197 +229,145 @@ public struct From { private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]? - private init(entityClass: AnyClass) { + private init(entityClass: AnyClass, configurations: [String?]?) { - self.init( - entityClass: entityClass, - dumpInfo: nil, - findPersistentStores: { (context: NSManagedObjectContext) -> [NSPersistentStore]? in - - return context.parentStack?.persistentStoresForEntityClass(entityClass) - } - ) - } - - private init(entityClass: AnyClass, configurations: [String?]) { - - let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName }) - self.init( - entityClass: entityClass, - dumpInfo: ("configurations", configurations), - findPersistentStores: { (context: NSManagedObjectContext) -> [NSPersistentStore]? in + self.entityClass = entityClass + self.configurations = configurations + if let configurations = configurations { + + let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName }) + self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter { return configurationsSet.contains($0.configurationName) } } - ) - } - - private init(entityClass: AnyClass, storeURLs: [NSURL]) { - - let storeURLsSet = Set(storeURLs) - self.init( - entityClass: entityClass, - dumpInfo: ("storeURLs", storeURLs), - findPersistentStores: { (context: NSManagedObjectContext) -> [NSPersistentStore]? in + } + else { + + self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in - return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter { - - return $0.URL != nil && storeURLsSet.contains($0.URL!) - } + return context.parentStack?.persistentStoresForEntityClass(entityClass) } - ) + } } - private init(entityClass: AnyClass, persistentStores: [NSPersistentStore]) { - - let persistentStores = Set(persistentStores) - self.init( - entityClass: entityClass, - dumpInfo: ("persistentStores", persistentStores), - findPersistentStores: { (context: NSManagedObjectContext) -> [NSPersistentStore]? in - - return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter { - - return persistentStores.contains($0) - } - } - ) - } - - private init(entityClass: AnyClass, dumpInfo: (key: String, value: Any)?, findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?) { + private init(entityClass: AnyClass, configurations: [String?]?, findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?) { self.entityClass = entityClass - self.dumpInfo = dumpInfo + self.configurations = configurations self.findPersistentStores = findPersistentStores } - // MARK: Deprecated + // MARK: Obsolete /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ storeURL: NSURL, _ otherStoreURLs: NSURL...) { - self.init(entityClass: T.self, storeURLs: [storeURL] + otherStoreURLs) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ storeURLs: [NSURL]) { - self.init(entityClass: T.self, storeURLs: storeURLs) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ entity: T.Type, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) { - self.init(entityClass: entity, storeURLs: [storeURL] + otherStoreURLs) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ entity: T.Type, _ storeURLs: [NSURL]) { - self.init(entityClass: entity, storeURLs: storeURLs) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ entityClass: AnyClass, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) { - CoreStore.assert( - entityClass is T.Type, - "Attempted to create generic type \(cs_typeName(From)) with entity class \(cs_typeName(entityClass))" - ) - self.init(entityClass: entityClass, storeURLs: [storeURL] + otherStoreURLs) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) { - CoreStore.assert( - entityClass is T.Type, - "Attempted to create generic type \(cs_typeName(From)) with entity class \(cs_typeName(entityClass))" - ) - self.init(entityClass: entityClass, storeURLs: storeURLs) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) { - self.init(entityClass: T.self, persistentStores: [persistentStore] + otherPersistentStores) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ persistentStores: [NSPersistentStore]) { - self.init(entityClass: T.self, persistentStores: persistentStores) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ entity: T.Type, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) { - self.init(entityClass: entity, persistentStores: [persistentStore] + otherPersistentStores) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) { - self.init(entityClass: entity, persistentStores: persistentStores) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ entityClass: AnyClass, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) { - CoreStore.assert( - entityClass is T.Type, - "Attempted to create generic type \(cs_typeName(From)) with entity class \(cs_typeName(entityClass))" - ) - self.init(entityClass: entityClass, persistentStores: [persistentStore] + otherPersistentStores) + CoreStore.abort("Use initializers that accept configuration names.") } /** - Deprecated. Use initializers that accept configuration names. + Obsolete. Use initializers that accept configuration names. */ - @available(*, deprecated=2.0.0, message="Use initializers that accept configuration names.") + @available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.") public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) { - CoreStore.assert( - entityClass is T.Type, - "Attempted to create generic type \(cs_typeName(From)) with entity class \(cs_typeName(entityClass))" - ) - self.init(entityClass: entityClass, persistentStores: persistentStores) + CoreStore.abort("Use initializers that accept configuration names.") } } diff --git a/Sources/Fetching and Querying/Concrete Clauses/GroupBy.swift b/Sources/Fetching and Querying/Concrete Clauses/GroupBy.swift index 561635d..9afddf3 100644 --- a/Sources/Fetching and Querying/Concrete Clauses/GroupBy.swift +++ b/Sources/Fetching and Querying/Concrete Clauses/GroupBy.swift @@ -35,14 +35,9 @@ import CoreData public struct GroupBy: QueryClause, Hashable { /** - Initializes a `GroupBy` clause with a list of key path strings - - - parameter keyPaths: a list of key path strings to group results with + The list of key path strings to group results with */ - public init(_ keyPaths: [KeyPath]) { - - self.keyPaths = keyPaths - } + public let keyPaths: [KeyPath] /** Initializes a `GroupBy` clause with an empty list of key path strings @@ -52,6 +47,16 @@ public struct GroupBy: QueryClause, Hashable { self.init([]) } + /** + Initializes a `GroupBy` clause with a list of key path strings + + - parameter keyPaths: a list of key path strings to group results with + */ + public init(_ keyPaths: [KeyPath]) { + + self.keyPaths = keyPaths + } + /** Initializes a `GroupBy` clause with a list of key path strings @@ -63,8 +68,6 @@ public struct GroupBy: QueryClause, Hashable { self.init([keyPath] + keyPaths) } - public let keyPaths: [KeyPath] - // MARK: QueryClause diff --git a/Sources/Fetching and Querying/Concrete Clauses/Select.swift b/Sources/Fetching and Querying/Concrete Clauses/Select.swift index 7b52dbc..efda950 100644 --- a/Sources/Fetching and Querying/Concrete Clauses/Select.swift +++ b/Sources/Fetching and Querying/Concrete Clauses/Select.swift @@ -412,10 +412,11 @@ extension Bool: SelectValueResultType { } public static func fromResultObject(result: AnyObject) -> Bool? { - switch result { case let decimal as NSDecimalNumber: + // iOS: NSDecimalNumber(string: "0.5").boolValue // true + // OSX: NSDecimalNumber(string: "0.5").boolValue // false return NSNumber(double: decimal.doubleValue).boolValue case let number as NSNumber: diff --git a/Sources/Fetching and Querying/Concrete Clauses/Where.swift b/Sources/Fetching and Querying/Concrete Clauses/Where.swift index 18677a6..46e04be 100644 --- a/Sources/Fetching and Querying/Concrete Clauses/Where.swift +++ b/Sources/Fetching and Querying/Concrete Clauses/Where.swift @@ -51,14 +51,9 @@ public prefix func !(clause: Where) -> Where { public struct Where: FetchClause, QueryClause, DeleteClause, Hashable { /** - Initializes a `Where` clause with an `NSPredicate` - - - parameter predicate: the `NSPredicate` for the fetch or query + The `NSPredicate` for the fetch or query */ - public init(_ predicate: NSPredicate) { - - self.predicate = predicate - } + public let predicate: NSPredicate /** Initializes a `Where` clause with a predicate that always evaluates to `true` @@ -135,7 +130,15 @@ public struct Where: FetchClause, QueryClause, DeleteClause, Hashable { self.init(NSPredicate(format: "\(keyPath) IN %@", Array(list) as NSArray)) } - public let predicate: NSPredicate + /** + Initializes a `Where` clause with an `NSPredicate` + + - parameter predicate: the `NSPredicate` for the fetch or query + */ + public init(_ predicate: NSPredicate) { + + self.predicate = predicate + } // MARK: FetchClause, QueryClause, DeleteClause diff --git a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift index c2c24f1..68d93b0 100644 --- a/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift +++ b/Sources/Logging/CoreStore+CustomDebugStringConvertible.swift @@ -198,9 +198,9 @@ extension From: CustomDebugStringConvertible, CoreStoreDebugStringConvertible { public var coreStoreDumpString: String { var info: DumpInfo = [("entityClass", self.entityClass)] - if let extraInfo = self.dumpInfo { + if let configurations = self.configurations { - info.append(extraInfo) + info.append(("configurations", configurations)) } return createFormattedString( "(", ")", diff --git a/Sources/ObjectiveC/CSFrom.swift b/Sources/ObjectiveC/CSFrom.swift index 8904c7d..45d7df8 100644 --- a/Sources/ObjectiveC/CSFrom.swift +++ b/Sources/ObjectiveC/CSFrom.swift @@ -37,6 +37,32 @@ import CoreData @objc public final class CSFrom: NSObject, CoreStoreObjectiveCType { + /** + The associated `NSManagedObject` entity class + */ + @objc + public var entityClass: AnyClass { + + return self.bridgeToSwift.entityClass + } + + /** + The `NSPersistentStore` configuration names to associate objects from. + May contain `NSString` instances to pertain to named configurations, or `NSNull` to pertain to the default configuration + */ + @objc + public var configurations: [AnyObject]? { + + return self.bridgeToSwift.configurations?.map { + + switch $0 { + + case nil: return NSNull() + case let string as NSString: return string + } + } + } + /** Initializes a `CSFrom` clause with the specified entity class. ``` @@ -58,14 +84,24 @@ public final class CSFrom: NSObject, CoreStoreObjectiveCType { MyPersonEntity *people = [transaction fetchAllFrom:[CSFrom entityClass:[MyPersonEntity class] configuration:@"Configuration1"]]; ``` - - parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. + - parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `[NSNull null]` to use the default configuration. - parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter) - returns: a `CSFrom` clause with the specified configurations */ @objc - public static func entityClass(entityClass: AnyClass, configuration: String?) -> CSFrom { + public static func entityClass(entityClass: AnyClass, configuration: AnyObject) -> CSFrom { - return self.init(From(entityClass, configuration)) + switch configuration { + + case let string as String: + return self.init(From(entityClass, string)) + + case is NSNull: + return self.init(From(entityClass, nil)) + + default: + CoreStore.abort("The configuration argument only accepts NSString and NSNull values") + } } /** @@ -81,7 +117,22 @@ public final class CSFrom: NSObject, CoreStoreObjectiveCType { @objc public static func entityClass(entityClass: AnyClass, configurations: [AnyObject]) -> CSFrom { - return self.init(From(entityClass, configurations.map { $0 is NSNull ? nil : ($0 as! String) })) + var arguments = [String?]() + for configuration in configurations { + + switch configuration { + + case let string as String: + arguments.append(string) + + case is NSNull: + arguments.append(nil) + + default: + CoreStore.abort("The configurations argument only accepts NSString and NSNull values") + } + } + return self.init(From(entityClass, arguments)) } diff --git a/Sources/ObjectiveC/CSGroupBy.swift b/Sources/ObjectiveC/CSGroupBy.swift index e2f3634..48bcb19 100644 --- a/Sources/ObjectiveC/CSGroupBy.swift +++ b/Sources/ObjectiveC/CSGroupBy.swift @@ -37,6 +37,14 @@ import CoreData @objc public final class CSGroupBy: NSObject, CSQueryClause, CoreStoreObjectiveCType { + /** + The list of key path strings to group results with + */ + public var keyPaths: [KeyPath] { + + return self.bridgeToSwift.keyPaths + } + /** Initializes a `CSGroupBy` clause with a list of key path strings diff --git a/Sources/ObjectiveC/CSWhere.swift b/Sources/ObjectiveC/CSWhere.swift index 56da078..12ffe74 100644 --- a/Sources/ObjectiveC/CSWhere.swift +++ b/Sources/ObjectiveC/CSWhere.swift @@ -38,15 +38,12 @@ import CoreData public final class CSWhere: NSObject, CSFetchClause, CSQueryClause, CSDeleteClause, CoreStoreObjectiveCType { /** - Initializes a `CSWhere` clause with an `NSPredicate` - - - parameter predicate: the `NSPredicate` for the fetch or query - - returns: a `CSWhere` clause with an `NSPredicate` + The internal `NSPredicate` instance for the `Where` clause */ @objc - public static func predicate(predicate: NSPredicate) -> CSWhere { + public var predicate: NSPredicate { - return self.init(Where(predicate)) + return self.bridgeToSwift.predicate } /** @@ -100,6 +97,18 @@ public final class CSWhere: NSObject, CSFetchClause, CSQueryClause, CSDeleteClau return self.init(Where(keyPath, isMemberOf: list)) } + /** + Initializes a `CSWhere` clause with an `NSPredicate` + + - parameter predicate: the `NSPredicate` for the fetch or query + - returns: a `CSWhere` clause with an `NSPredicate` + */ + @objc + public static func predicate(predicate: NSPredicate) -> CSWhere { + + return self.init(Where(predicate)) + } + // MARK: NSObject diff --git a/Sources/ObjectiveC/CoreStoreBridge.h b/Sources/ObjectiveC/CoreStoreBridge.h new file mode 100644 index 0000000..6c93e94 --- /dev/null +++ b/Sources/ObjectiveC/CoreStoreBridge.h @@ -0,0 +1,152 @@ +// +// CoreStoreBridge.h +// 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 + +#ifndef CoreStoreBridge_h +#define CoreStoreBridge_h + +#if !__has_feature(objc_arc) +#error CoreStore Objective-C utilities require ARC be enabled +#endif + +#if !__has_extension(attribute_overloadable) +#error CoreStore Objective-C utilities can only be used on platforms that support C function overloading +#endif + +#define CS_OBJC_EXTERN extern +#define CS_OBJC_OVERLOADABLE __attribute__((__overloadable__)) +#define CS_OBJC_REQUIRES_NIL_TERMINATION(A, B) __attribute__((sentinel(A, B))) + + +// MARK: - From + +@class CSFrom; + +/** + @abstract + Initializes a CSFrom clause with the specified entity class. + + @code + MyPersonEntity *people = [transaction fetchAllFrom:From([MyPersonEntity class])]; + @endcode + + @param entityClass + the NSManagedObject class type to be created + + @result + a CSFrom clause with the specified entity class + */ +CS_OBJC_EXTERN CS_OBJC_OVERLOADABLE +CSFrom *_Nonnull From(Class _Nonnull entityClass); + +/** + @abstract + Initializes a CSFrom clause with the specified configurations. + + @code + MyPersonEntity *people = [transaction fetchAllFrom:From([MyPersonEntity class], @[@"Configuration1"])]; + @endcode + + @param entityClass + the NSManagedObject class type to be created + + @param configurations + an array of the NSPersistentStore configuration names to associate objects from. This parameter is required if multiple configurations contain the created NSManagedObject's entity type. Set to [NSNull null] to use the default configuration. + + @result + a CSFrom clause with the specified configurations + */ +CS_OBJC_EXTERN CS_OBJC_OVERLOADABLE +CSFrom *_Nonnull From(Class _Nonnull entityClass, NSArray *_Nonnull configurations); + + + +// MARK: - Where + +@class CSWhere; + +/** + @abstract + Initializes a CSWhere clause with a predicate that always evaluates to the specified boolean value + + @param value + the boolean value for the predicate + + @result + a CSWhere clause with a predicate that always evaluates to the specified boolean value + */ +CS_OBJC_EXTERN CS_OBJC_OVERLOADABLE +CSWhere *_Nonnull Where(BOOL value); + +/** + @abstract + Initializes a CSWhere clause with a predicate using the specified string format and arguments + + @param format + the format string for the predicate + + @param argumentArray + the arguments for format + + @result + a CSWhere clause with a predicate using the specified string format and arguments + */ +CS_OBJC_EXTERN CS_OBJC_OVERLOADABLE +CSWhere *_Nonnull Where(NSString *_Nonnull format, ...); + +/** + @abstract + Initializes a CSWhere clause with an NSPredicate + + @param predicate + the NSPredicate for the fetch or query + + @result + a CSWhere clause with an NSPredicate + */ +CS_OBJC_EXTERN CS_OBJC_OVERLOADABLE +CSWhere *_Nonnull Where(NSPredicate *_Nonnull predicate); + + +// MARK: - GroupBy + +@class CSGroupBy; + +/** + @abstract + Initializes a CSGroupBy clause with a list of key path strings + + @param keyPaths + a list of key path strings to group results with + + @result + a CSGroupBy clause with a list of key path strings + */ +CS_OBJC_OVERLOADABLE +CSGroupBy *_Nonnull GroupBy(NSArray *_Nonnull keyPaths); + + +#endif /* CoreStoreBridge_h */ diff --git a/Sources/ObjectiveC/CoreStoreBridge.m b/Sources/ObjectiveC/CoreStoreBridge.m new file mode 100644 index 0000000..f71a466 --- /dev/null +++ b/Sources/ObjectiveC/CoreStoreBridge.m @@ -0,0 +1,76 @@ +// +// CoreStoreBridge.m +// 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 "CoreStoreBridge.h" +#import + + +CS_OBJC_OVERLOADABLE +CSFrom *_Nonnull From(Class _Nonnull entityClass) { + + return [CSFrom entityClass:entityClass]; +} + +CS_OBJC_OVERLOADABLE +CSFrom *_Nonnull From(Class _Nonnull entityClass, NSArray *_Nonnull configurations) { + + return [CSFrom entityClass:entityClass configurations:configurations]; +} + + + +// MARK: - Where + +CS_OBJC_OVERLOADABLE +CSWhere *_Nonnull Where(BOOL value) { + + return [CSWhere value:value]; +} + +CS_OBJC_OVERLOADABLE +CSWhere *_Nonnull Where(NSString *_Nonnull format, ...) { + + CSWhere *where; + va_list args; + va_start(args, format); + where = [CSWhere predicate:[NSPredicate predicateWithFormat:format arguments:args]]; + va_end(args); + return where; +} + +CS_OBJC_OVERLOADABLE +CSWhere *_Nonnull Where(NSPredicate *_Nonnull predicate) { + + return [CSWhere predicate:predicate]; +} + + +// MARK: - GroupBy + +CS_OBJC_OVERLOADABLE +CSGroupBy *_Nonnull GroupBy(NSArray *_Nonnull keyPaths) { + + return [CSGroupBy keyPaths:keyPaths]; +} diff --git a/Sources/Observing/ListMonitor.swift b/Sources/Observing/ListMonitor.swift index 81e3538..0dfa317 100644 --- a/Sources/Observing/ListMonitor.swift +++ b/Sources/Observing/ListMonitor.swift @@ -158,6 +158,17 @@ public final class ListMonitor: Hashable { ] } + /** + Checks if the `ListMonitor` has at least one section + + - returns: `true` if at least one section exists, `false` otherwise + */ + @warn_unused_result + public func hasSections() -> Bool { + + return self.sections().count > 0 + } + /** Checks if the `ListMonitor` has at least one object in any section.