From bf41605da9fcef6bafc90134ed93e3559cb7e383 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Wed, 18 Mar 2015 01:25:58 +0900 Subject: [PATCH] added ability to query aggregates and attributes straight from the persistent store --- HardcoreData.xcodeproj/project.pbxproj | 82 ++- HardcoreData/AggregateFunction.swift | 99 ---- HardcoreData/AggregateResultType.swift | 232 -------- .../AsynchronousDataTransaction.swift | 5 +- .../BaseDataTransaction+Querying.swift | 70 ++- HardcoreData/BaseDataTransaction.swift | 8 +- ...tomizeQuery.swift => CustomizeFetch.swift} | 6 +- HardcoreData/DataStack+Observing.swift | 50 ++ HardcoreData/DataStack+Querying.swift | 71 ++- HardcoreData/DataStack+Transaction.swift | 7 + HardcoreData/DataStack.swift | 6 +- HardcoreData/DetachedDataTransaction.swift | 2 +- HardcoreData/FetchClause.swift | 5 +- HardcoreData/GroupBy.swift | 80 +++ HardcoreData/HardcoreData+Querying.swift | 38 +- HardcoreData/ManagedObjectController.swift | 27 + .../ManagedObjectListController.swift | 125 +++++ HardcoreData/ManagedObjectListObserver.swift | 80 +++ .../ManagedObjectListSectionInfo.swift | 13 + .../NSManagedObjectContext+Querying.swift | 147 +++-- ...r+HardcoreData.swift => QueryClause.swift} | 26 +- HardcoreData/Select.swift | 515 ++++++++++++++++++ HardcoreData/SortedBy.swift | 32 +- HardcoreData/SynchronousDataTransaction.swift | 3 +- HardcoreData/Where.swift | 8 +- HardcoreDataTests/HardcoreDataTests.swift | 60 +- HardcoreDataTests/TestEntity1.swift | 1 - HardcoreDataTests/TestEntity2.swift | 1 + 28 files changed, 1311 insertions(+), 488 deletions(-) delete mode 100644 HardcoreData/AggregateFunction.swift delete mode 100644 HardcoreData/AggregateResultType.swift rename HardcoreData/{CustomizeQuery.swift => CustomizeFetch.swift} (94%) create mode 100644 HardcoreData/DataStack+Observing.swift create mode 100644 HardcoreData/GroupBy.swift create mode 100644 HardcoreData/ManagedObjectController.swift create mode 100644 HardcoreData/ManagedObjectListController.swift create mode 100644 HardcoreData/ManagedObjectListObserver.swift create mode 100644 HardcoreData/ManagedObjectListSectionInfo.swift rename HardcoreData/{NSPersistentStoreCoordinator+HardcoreData.swift => QueryClause.swift} (68%) create mode 100644 HardcoreData/Select.swift diff --git a/HardcoreData.xcodeproj/project.pbxproj b/HardcoreData.xcodeproj/project.pbxproj index 54dd439..b98ce96 100644 --- a/HardcoreData.xcodeproj/project.pbxproj +++ b/HardcoreData.xcodeproj/project.pbxproj @@ -11,10 +11,14 @@ 2F03A54019C5C6DA005002A5 /* HardcoreDataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F03A53F19C5C6DA005002A5 /* HardcoreDataTests.swift */; }; 2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A54C19C5C872005002A5 /* CoreData.framework */; }; 2F291E2719C6D3CF007AF63F /* HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* HardcoreData.swift */; }; + B52B68BA1AAB46AD00CE7F48 /* ManagedObjectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B68B91AAB46AD00CE7F48 /* ManagedObjectController.swift */; }; + B52B68BC1AAB46BD00CE7F48 /* ManagedObjectListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B68BB1AAB46BD00CE7F48 /* ManagedObjectListController.swift */; }; + B52B68BE1AAB484C00CE7F48 /* DataStack+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B68BD1AAB484C00CE7F48 /* DataStack+Observing.swift */; }; + B52B68C01AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B68BF1AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift */; }; + B52B68C21AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52B68C11AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift */; }; B5398AA21AA8938D00B66388 /* DetachedDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5398AA11AA8938D00B66388 /* DetachedDataTransaction.swift */; }; - B54A9F031AA7640200AFEC05 /* AggregateFunction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54A9F021AA7640200AFEC05 /* AggregateFunction.swift */; }; - B54A9F051AA7644400AFEC05 /* AggregateResultType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54A9F041AA7644400AFEC05 /* AggregateResultType.swift */; }; B54A9F071AA7654400AFEC05 /* DataStack+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54A9F061AA7654400AFEC05 /* DataStack+Querying.swift */; }; + B54D53071AB3538500D55BA8 /* QueryClause.swift in Sources */ = {isa = PBXBuildFile; fileRef = B54D53061AB3538500D55BA8 /* QueryClause.swift */; }; B57078B01A50392D007E33F2 /* FetchClause.swift in Sources */ = {isa = PBXBuildFile; fileRef = B57078AF1A50392D007E33F2 /* FetchClause.swift */; }; B582DF821A98B0E7003F09C6 /* HardcoreData+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B582DF811A98B0E7003F09C6 /* HardcoreData+Querying.swift */; }; B582DF861A98B11B003F09C6 /* HardcoreData+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B582DF851A98B11B003F09C6 /* HardcoreData+Transaction.swift */; }; @@ -25,6 +29,8 @@ B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */; }; B5CFF24019FD383100D6DFC4 /* BaseDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5CFF23F19FD383100D6DFC4 /* BaseDataTransaction.swift */; }; B5D022661A90CD340070CA63 /* DataStack+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */; }; + B5D10DC01AB42C85004B4EEA /* GroupBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D10DBF1AB42C85004B4EEA /* GroupBy.swift */; }; + B5D10DC21AB4590F004B4EEA /* Select.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D10DC11AB4590F004B4EEA /* Select.swift */; }; B5D19BFB1AA14063001D1A99 /* AsynchronousDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D19BFA1AA14063001D1A99 /* AsynchronousDataTransaction.swift */; }; B5D19BFF1AA14351001D1A99 /* SynchronousDataTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D19BFE1AA14351001D1A99 /* SynchronousDataTransaction.swift */; }; B5D19C011AA15E1F001D1A99 /* HardcoreData+Logging.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D19C001AA15E1F001D1A99 /* HardcoreData+Logging.swift */; }; @@ -33,7 +39,6 @@ B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D372851A39CDDB00F583D9 /* TestEntity1.swift */; }; B5D399F119FC818E000E91BB /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D399F019FC818E000E91BB /* DataStack.swift */; }; - B5D399F519FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D399F419FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift */; }; B5D39A0219FD00C9000E91BB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0119FD00C9000E91BB /* Foundation.framework */; }; B5D39A0419FD00DE000E91BB /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0319FD00DE000E91BB /* UIKit.framework */; }; B5D5E0CF1A4D6AAB006468AF /* TestEntity2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */; }; @@ -47,7 +52,7 @@ B5F409EB1A8B199600A228EA /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409EA1A8B199600A228EA /* DefaultLogger.swift */; }; B5F409ED1A8B200700A228EA /* NSManagedObjectContext+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409EC1A8B200700A228EA /* NSManagedObjectContext+Querying.swift */; }; B5F409EF1A8B243D00A228EA /* BaseDataTransaction+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409EE1A8B243D00A228EA /* BaseDataTransaction+Querying.swift */; }; - B5F409F11A8B27A600A228EA /* CustomizeQuery.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409F01A8B27A600A228EA /* CustomizeQuery.swift */; }; + B5F409F11A8B27A600A228EA /* CustomizeFetch.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5F409F01A8B27A600A228EA /* CustomizeFetch.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -83,10 +88,14 @@ 2F03A53F19C5C6DA005002A5 /* HardcoreDataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = HardcoreDataTests.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; 2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 2F291E2619C6D3CF007AF63F /* HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = HardcoreData.swift; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.swift; }; + B52B68B91AAB46AD00CE7F48 /* ManagedObjectController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectController.swift; sourceTree = ""; }; + B52B68BB1AAB46BD00CE7F48 /* ManagedObjectListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListController.swift; sourceTree = ""; }; + B52B68BD1AAB484C00CE7F48 /* DataStack+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Observing.swift"; sourceTree = ""; }; + B52B68BF1AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListObserver.swift; sourceTree = ""; }; + B52B68C11AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListSectionInfo.swift; sourceTree = ""; }; B5398AA11AA8938D00B66388 /* DetachedDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DetachedDataTransaction.swift; sourceTree = ""; }; - B54A9F021AA7640200AFEC05 /* AggregateFunction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateFunction.swift; sourceTree = ""; }; - B54A9F041AA7644400AFEC05 /* AggregateResultType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AggregateResultType.swift; sourceTree = ""; }; B54A9F061AA7654400AFEC05 /* DataStack+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Querying.swift"; sourceTree = ""; }; + B54D53061AB3538500D55BA8 /* QueryClause.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryClause.swift; sourceTree = ""; }; B57078AF1A50392D007E33F2 /* FetchClause.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchClause.swift; sourceTree = ""; }; B582DF811A98B0E7003F09C6 /* HardcoreData+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HardcoreData+Querying.swift"; sourceTree = ""; }; B582DF851A98B11B003F09C6 /* HardcoreData+Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HardcoreData+Transaction.swift"; sourceTree = ""; }; @@ -97,6 +106,8 @@ B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+HardcoreData.swift"; sourceTree = ""; }; B5CFF23F19FD383100D6DFC4 /* BaseDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseDataTransaction.swift; sourceTree = ""; }; B5D022651A90CD340070CA63 /* DataStack+Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Transaction.swift"; sourceTree = ""; }; + B5D10DBF1AB42C85004B4EEA /* GroupBy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupBy.swift; sourceTree = ""; }; + B5D10DC11AB4590F004B4EEA /* Select.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Select.swift; sourceTree = ""; }; B5D19BFA1AA14063001D1A99 /* AsynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AsynchronousDataTransaction.swift; sourceTree = ""; }; B5D19BFE1AA14351001D1A99 /* SynchronousDataTransaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SynchronousDataTransaction.swift; sourceTree = ""; }; B5D19C001AA15E1F001D1A99 /* HardcoreData+Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "HardcoreData+Logging.swift"; sourceTree = ""; }; @@ -105,7 +116,6 @@ B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; B5D372851A39CDDB00F583D9 /* TestEntity1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity1.swift; sourceTree = ""; }; B5D399F019FC818E000E91BB /* DataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataStack.swift; sourceTree = ""; }; - B5D399F419FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSPersistentStoreCoordinator+HardcoreData.swift"; sourceTree = ""; }; B5D39A0119FD00C9000E91BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; B5D39A0319FD00DE000E91BB /* UIKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = UIKit.framework; path = System/Library/Frameworks/UIKit.framework; sourceTree = SDKROOT; }; B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity2.swift; sourceTree = ""; }; @@ -120,7 +130,7 @@ B5F409EA1A8B199600A228EA /* DefaultLogger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultLogger.swift; sourceTree = ""; }; B5F409EC1A8B200700A228EA /* NSManagedObjectContext+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Querying.swift"; sourceTree = ""; }; B5F409EE1A8B243D00A228EA /* BaseDataTransaction+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseDataTransaction+Querying.swift"; sourceTree = ""; }; - B5F409F01A8B27A600A228EA /* CustomizeQuery.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeQuery.swift; sourceTree = ""; }; + B5F409F01A8B27A600A228EA /* CustomizeFetch.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomizeFetch.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -175,6 +185,7 @@ B5D022621A90BCC60070CA63 /* Saving and Processing */, B5F409E51A8B11B600A228EA /* Logging */, B5E126531A7DCCE400AD8B39 /* Fetching and Querying */, + B52B68B61AAB45FB00CE7F48 /* Observing */, B5D808141A34945A00A44484 /* Internal */, 2F03A53319C5C6DA005002A5 /* Supporting Files */, ); @@ -220,6 +231,18 @@ name = Frameworks; sourceTree = ""; }; + B52B68B61AAB45FB00CE7F48 /* Observing */ = { + isa = PBXGroup; + children = ( + B52B68B91AAB46AD00CE7F48 /* ManagedObjectController.swift */, + B52B68BB1AAB46BD00CE7F48 /* ManagedObjectListController.swift */, + B52B68C11AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift */, + B52B68BF1AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift */, + B52B68BD1AAB484C00CE7F48 /* DataStack+Observing.swift */, + ); + name = Observing; + sourceTree = ""; + }; B54A9EFF1AA763D100AFEC05 /* Internal */ = { isa = PBXGroup; children = ( @@ -228,6 +251,27 @@ name = Internal; sourceTree = ""; }; + B54D53041AB350CF00D55BA8 /* Fetching */ = { + isa = PBXGroup; + children = ( + B57078AF1A50392D007E33F2 /* FetchClause.swift */, + B5E126541A7DCE1400AD8B39 /* Where.swift */, + B5E126561A7DCE5900AD8B39 /* SortedBy.swift */, + B5F409F01A8B27A600A228EA /* CustomizeFetch.swift */, + ); + name = Fetching; + sourceTree = ""; + }; + B54D53051AB350FF00D55BA8 /* Querying */ = { + isa = PBXGroup; + children = ( + B54D53061AB3538500D55BA8 /* QueryClause.swift */, + B5D10DC11AB4590F004B4EEA /* Select.swift */, + B5D10DBF1AB42C85004B4EEA /* GroupBy.swift */, + ); + name = Querying; + sourceTree = ""; + }; B595CAC01A9A0AC4009A397F /* Setting Up */ = { isa = PBXGroup; children = ( @@ -302,7 +346,6 @@ B595CAC71A9A161B009A397F /* WeakObject.swift */, B5D808191A3495BD00A44484 /* NSObject+HardcoreData.swift */, B5CFF23D19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift */, - B5D399F419FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift */, ); name = Internal; sourceTree = ""; @@ -310,15 +353,11 @@ B5E126531A7DCCE400AD8B39 /* Fetching and Querying */ = { isa = PBXGroup; children = ( - B57078AF1A50392D007E33F2 /* FetchClause.swift */, - B5E126541A7DCE1400AD8B39 /* Where.swift */, - B5E126561A7DCE5900AD8B39 /* SortedBy.swift */, - B5F409F01A8B27A600A228EA /* CustomizeQuery.swift */, - B54A9F021AA7640200AFEC05 /* AggregateFunction.swift */, - B54A9F041AA7644400AFEC05 /* AggregateResultType.swift */, B582DF811A98B0E7003F09C6 /* HardcoreData+Querying.swift */, B54A9F061AA7654400AFEC05 /* DataStack+Querying.swift */, B5F409EE1A8B243D00A228EA /* BaseDataTransaction+Querying.swift */, + B54D53041AB350CF00D55BA8 /* Fetching */, + B54D53051AB350FF00D55BA8 /* Querying */, B54A9EFF1AA763D100AFEC05 /* Internal */, ); name = "Fetching and Querying"; @@ -467,7 +506,6 @@ files = ( B5CFF24019FD383100D6DFC4 /* BaseDataTransaction.swift in Sources */, B5D19C011AA15E1F001D1A99 /* HardcoreData+Logging.swift in Sources */, - B5D399F519FCF4E0000E91BB /* NSPersistentStoreCoordinator+HardcoreData.swift in Sources */, B5CFD36E1A0775F000B7885F /* SaveResult.swift in Sources */, B5D022661A90CD340070CA63 /* DataStack+Transaction.swift in Sources */, B582DF861A98B11B003F09C6 /* HardcoreData+Transaction.swift in Sources */, @@ -476,27 +514,33 @@ B5CFF23E19FD1D1C00D6DFC4 /* NSManagedObjectContext+HardcoreData.swift in Sources */, B54A9F071AA7654400AFEC05 /* DataStack+Querying.swift in Sources */, B5E126571A7DCE5900AD8B39 /* SortedBy.swift in Sources */, + B54D53071AB3538500D55BA8 /* QueryClause.swift in Sources */, B5F409EF1A8B243D00A228EA /* BaseDataTransaction+Querying.swift in Sources */, 2F291E2719C6D3CF007AF63F /* HardcoreData.swift in Sources */, B5F409E91A8B11CE00A228EA /* HardcoreDataLogger.swift in Sources */, B5D8081A1A3495BD00A44484 /* NSObject+HardcoreData.swift in Sources */, B595CAC41A9A11C1009A397F /* NSManagedObjectContext+Setup.swift in Sources */, B5D19BFF1AA14351001D1A99 /* SynchronousDataTransaction.swift in Sources */, + B5D10DC21AB4590F004B4EEA /* Select.swift in Sources */, B5E209E01A0726460089C9D4 /* NSManagedObject+Transaction.swift in Sources */, B582DF821A98B0E7003F09C6 /* HardcoreData+Querying.swift in Sources */, B5D19BFB1AA14063001D1A99 /* AsynchronousDataTransaction.swift in Sources */, B595CAC81A9A161B009A397F /* WeakObject.swift in Sources */, - B54A9F051AA7644400AFEC05 /* AggregateResultType.swift in Sources */, B5D1E22A19FA9E63003B2874 /* PersistentStoreResult.swift in Sources */, B5398AA21AA8938D00B66388 /* DetachedDataTransaction.swift in Sources */, - B5F409F11A8B27A600A228EA /* CustomizeQuery.swift in Sources */, + B5F409F11A8B27A600A228EA /* CustomizeFetch.swift in Sources */, B5F409EB1A8B199600A228EA /* DefaultLogger.swift in Sources */, - B54A9F031AA7640200AFEC05 /* AggregateFunction.swift in Sources */, + B52B68C01AAB9DAD00CE7F48 /* ManagedObjectListObserver.swift in Sources */, B5D399F119FC818E000E91BB /* DataStack.swift in Sources */, B5D808161A34947300A44484 /* NotificationObserver.swift in Sources */, + B52B68BE1AAB484C00CE7F48 /* DataStack+Observing.swift in Sources */, + B52B68C21AAF554600CE7F48 /* ManagedObjectListSectionInfo.swift in Sources */, B57078B01A50392D007E33F2 /* FetchClause.swift in Sources */, B5F409ED1A8B200700A228EA /* NSManagedObjectContext+Querying.swift in Sources */, + B5D10DC01AB42C85004B4EEA /* GroupBy.swift in Sources */, B5E126551A7DCE1400AD8B39 /* Where.swift in Sources */, + B52B68BC1AAB46BD00CE7F48 /* ManagedObjectListController.swift in Sources */, + B52B68BA1AAB46AD00CE7F48 /* ManagedObjectController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/HardcoreData/AggregateFunction.swift b/HardcoreData/AggregateFunction.swift deleted file mode 100644 index 1a42639..0000000 --- a/HardcoreData/AggregateFunction.swift +++ /dev/null @@ -1,99 +0,0 @@ -// -// AggregateFunction.swift -// HardcoreData -// -// 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: - AggregateFunction - -public enum AggregateFunction { - - // MARK: Public - - case Average(AttributeName) - case Sum(AttributeName) - case Count(AttributeName) - case Minimum(AttributeName) - case Maximum(AttributeName) - case Medium(AttributeName) - case Mode(AttributeName) - case StandardDeviation(AttributeName) - - - // MARK: Internal - - internal func createExpression() -> NSExpression { - - switch self { - - case .Average(let attributeName): - return NSExpression( - forFunction: "average:", - arguments: [NSExpression(forKeyPath: "\(attributeName)")] - ) - - case .Sum(let attributeName): - return NSExpression( - forFunction: "sum:", - arguments: [NSExpression(forKeyPath: "\(attributeName)")] - ) - - case .Count(let attributeName): - return NSExpression( - forFunction: "count:", - arguments: [NSExpression(forKeyPath: "\(attributeName)")] - ) - - case .Minimum(let attributeName): - return NSExpression( - forFunction: "min:", - arguments: [NSExpression(forKeyPath: "\(attributeName)")] - ) - - case .Maximum(let attributeName): - return NSExpression( - forFunction: "max:", - arguments: [NSExpression(forKeyPath: "\(attributeName)")] - ) - - case .Medium(let attributeName): - return NSExpression( - forFunction: "medium:", - arguments: [NSExpression(forKeyPath: "\(attributeName)")] - ) - - case .Mode(let attributeName): - return NSExpression( - forFunction: "mode:", - arguments: [NSExpression(forKeyPath: "\(attributeName)")] - ) - - case .StandardDeviation(let attributeName): - return NSExpression( - forFunction: "stddev:", - arguments: [NSExpression(forKeyPath: "\(attributeName)")] - ) - } - } -} diff --git a/HardcoreData/AggregateResultType.swift b/HardcoreData/AggregateResultType.swift deleted file mode 100644 index 020b83e..0000000 --- a/HardcoreData/AggregateResultType.swift +++ /dev/null @@ -1,232 +0,0 @@ -// -// AggregateResultType.swift -// HardcoreData -// -// Copyright (c) 2015 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: - AggregateResultType - -public protocol AggregateResultType { - - static var attributeType: NSAttributeType { get } - static func fromResultObject(result: AnyObject) -> Self -} - - -extension Bool: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .BooleanAttributeType - } - - public static func fromResultObject(result: AnyObject) -> Bool { - - return (result as! NSNumber).boolValue - } -} - -extension Int8: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> Int8 { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension Int16: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> Int16 { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension Int32: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> Int32 { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension Int64: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> Int64 { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension Int: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> Int { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension UInt8: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> UInt8 { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension UInt16: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> UInt16 { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension UInt32: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> UInt32 { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension UInt64: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> UInt64 { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension UInt: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public static func fromResultObject(result: AnyObject) -> UInt { - - return numericCast((result as! NSNumber).longLongValue) - } -} - -extension Double: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .DoubleAttributeType - } - - public static func fromResultObject(result: AnyObject) -> Double { - - return (result as! NSNumber).doubleValue - } -} - -extension Float: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .FloatAttributeType - } - - public static func fromResultObject(result: AnyObject) -> Float { - - return (result as! NSNumber).floatValue - } -} - -extension NSNumber: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .Integer64AttributeType - } - - public class func fromResultObject(result: AnyObject) -> Self { - - return self(bytes: result.bytes, objCType: result.objCType) - } -} - -extension NSDate: AggregateResultType { - - public static var attributeType: NSAttributeType { - - return .DateAttributeType - } - - public class func fromResultObject(result: AnyObject) -> Self { - - return self(timeInterval: 0.0, sinceDate: result as! NSDate) - } -} diff --git a/HardcoreData/AsynchronousDataTransaction.swift b/HardcoreData/AsynchronousDataTransaction.swift index e2e221b..a3e605e 100644 --- a/HardcoreData/AsynchronousDataTransaction.swift +++ b/HardcoreData/AsynchronousDataTransaction.swift @@ -41,7 +41,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { */ public func commit(completion: (result: SaveResult) -> Void) { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside a transaction queue.") + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside its designated queue.") HardcoreData.assert(!self.isCommitted, "Attempted to commit a <\(self.dynamicType)> more than once.") self.isCommitted = true @@ -62,7 +62,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { */ public func commitAndWait() { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside a transaction queue.") + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside its designated queue.") HardcoreData.assert(!self.isCommitted, "Attempted to commit a <\(self.dynamicType)> more than once.") self.isCommitted = true @@ -77,6 +77,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { */ public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to begin a child transaction from a <\(self.dynamicType)> outside its designated queue.") HardcoreData.assert(!self.isCommitted, "Attempted to begin a child transaction from an already committed <\(self.dynamicType)>.") return SynchronousDataTransaction( diff --git a/HardcoreData/BaseDataTransaction+Querying.swift b/HardcoreData/BaseDataTransaction+Querying.swift index 6e64b96..cdf980e 100644 --- a/HardcoreData/BaseDataTransaction+Querying.swift +++ b/HardcoreData/BaseDataTransaction+Querying.swift @@ -35,63 +35,113 @@ public extension BaseDataTransaction { public func fetchOne(entity: T.Type, _ queryClauses: FetchClause...) -> T? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + return self.context.fetchOne(entity, queryClauses) } public func fetchOne(entity: T.Type, _ queryClauses: [FetchClause]) -> T? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + return self.context.fetchOne(entity, queryClauses) } public func fetchAll(entity: T.Type, _ queryClauses: FetchClause...) -> [T]? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + return self.context.fetchAll(entity, queryClauses) } public func fetchAll(entity: T.Type, _ queryClauses: [FetchClause]) -> [T]? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + return self.context.fetchAll(entity, queryClauses) } public func fetchCount(entity: T.Type, _ queryClauses: FetchClause...) -> Int? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + return self.context.fetchCount(entity, queryClauses) } public func fetchCount(entity: T.Type, _ queryClauses: [FetchClause]) -> Int? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + return self.context.fetchCount(entity, queryClauses) } + public func fetchObjectID(entity: T.Type, _ queryClauses: FetchClause...) -> NSManagedObjectID? { + + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + + return self.context.fetchObjectID(entity, queryClauses) + } + + public func fetchObjectID(entity: T.Type, _ queryClauses: [FetchClause]) -> NSManagedObjectID? { + + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + + return self.context.fetchObjectID(entity, queryClauses) + } + + public func fetchObjectIDs(entity: T.Type, _ queryClauses: FetchClause...) -> [NSManagedObjectID]? { + + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + + return self.context.fetchObjectIDs(entity, queryClauses) + } + + public func fetchObjectIDs(entity: T.Type, _ queryClauses: [FetchClause]) -> [NSManagedObjectID]? { + + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside its designated queue.") + + return self.context.fetchObjectIDs(entity, queryClauses) + } + public func deleteAll(entity: T.Type, _ queryClauses: FetchClause...) -> Int? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a <\(self.dynamicType)> outside its designated queue.") + return self.context.deleteAll(entity, queryClauses) } public func deleteAll(entity: T.Type, _ queryClauses: [FetchClause]) -> Int? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a <\(self.dynamicType)> outside its designated queue.") + return self.context.deleteAll(entity, queryClauses) } - public func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> Int? { + public func queryValue(entity: T.Type, _ selectClause: Select, _ queryClauses: FetchClause...) -> U? { - let result = self.context.queryAggregate(entity, function: function, queryClauses) - return result + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside its designated queue.") + + return self.context.queryValue(entity, selectClause, queryClauses) } - public func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> Int? { + public func queryValue(entity: T.Type, _ selectClause: Select, _ queryClauses: [FetchClause]) -> U? { - let result = self.context.queryAggregate(entity, function: function, queryClauses) - return result + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside its designated queue.") + + return self.context.queryValue(entity, selectClause, queryClauses) } - public func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> U? { + public func queryAttributes(entity: T.Type, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { - return self.context.queryAggregate(entity, function: function, queryClauses) + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside its designated queue.") + + return self.context.queryAttributes(entity, selectClause, queryClauses) } - public func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? { + public func queryAttributes(entity: T.Type, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { - return self.context.queryAggregate(entity, function: function, queryClauses) + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside its designated queue.") + + return self.context.queryAttributes(entity, selectClause, queryClauses) } } \ No newline at end of file diff --git a/HardcoreData/BaseDataTransaction.swift b/HardcoreData/BaseDataTransaction.swift index fe77538..a4e7cff 100644 --- a/HardcoreData/BaseDataTransaction.swift +++ b/HardcoreData/BaseDataTransaction.swift @@ -50,7 +50,7 @@ public /*abstract*/ class BaseDataTransaction { */ public func create(entity: T.Type) -> T { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to create an entity of type <\(entity)> outside a transaction queue.") + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to create an entity of type <\(entity)> outside its designated queue.") return T.createInContext(self.context) } @@ -63,7 +63,7 @@ public /*abstract*/ class BaseDataTransaction { */ public func fetch(object: T) -> T? { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(object.dynamicType)> outside a transaction queue.") + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(object.dynamicType)> outside its designated queue.") return object.inContext(self.context) } @@ -75,7 +75,7 @@ public /*abstract*/ class BaseDataTransaction { */ public func delete(object: NSManagedObject) { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity of type <\(object.dynamicType)> outside a transaction queue.") + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity of type <\(object.dynamicType)> outside its designated queue.") object.deleteFromContext() } @@ -87,7 +87,7 @@ public /*abstract*/ class BaseDataTransaction { */ public func rollback() { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to rollback a <\(self.dynamicType)> outside a transaction queue.") + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to rollback a <\(self.dynamicType)> outside its designated queue.") self.context.reset() } diff --git a/HardcoreData/CustomizeQuery.swift b/HardcoreData/CustomizeFetch.swift similarity index 94% rename from HardcoreData/CustomizeQuery.swift rename to HardcoreData/CustomizeFetch.swift index 73e9ba3..c199e0c 100644 --- a/HardcoreData/CustomizeQuery.swift +++ b/HardcoreData/CustomizeFetch.swift @@ -1,5 +1,5 @@ // -// CustomizeQuery.swift +// CustomizeFetch.swift // HardcoreData // // Copyright (c) 2015 John Rommel Estropia @@ -27,9 +27,9 @@ import Foundation import CoreData -// MARK: - CustomizeQuery +// MARK: - CustomizeFetch -public struct CustomizeQuery: FetchClause { +public struct CustomizeFetch: FetchClause { // MARK: Public diff --git a/HardcoreData/DataStack+Observing.swift b/HardcoreData/DataStack+Observing.swift new file mode 100644 index 0000000..16a68d2 --- /dev/null +++ b/HardcoreData/DataStack+Observing.swift @@ -0,0 +1,50 @@ +// +// DataStack+Observing.swift +// HardcoreData +// +// Copyright (c) 2015 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 +import GCDKit + + +// MARK: - DataStack + +public extension DataStack { + + // MARK: Public + + public func observeObjectList(entity: T.Type, _ queryClauses: FetchClause...) -> ManagedObjectListController { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + + // TODO: sectionNameKeyPath and cacheResults + return ManagedObjectListController( + dataStack: self, + entity: entity, + sectionNameKeyPath: nil, + cacheResults: false, + queryClauses: queryClauses + ) + } +} diff --git a/HardcoreData/DataStack+Querying.swift b/HardcoreData/DataStack+Querying.swift index ae961c5..e2dfa44 100644 --- a/HardcoreData/DataStack+Querying.swift +++ b/HardcoreData/DataStack+Querying.swift @@ -25,6 +25,7 @@ import Foundation import CoreData +import GCDKit // MARK: - DataStack @@ -35,63 +36,113 @@ public extension DataStack { public func fetchOne(entity: T.Type, _ queryClauses: FetchClause...) -> T? { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + return self.mainContext.fetchOne(entity, queryClauses) } public func fetchOne(entity: T.Type, _ queryClauses: [FetchClause]) -> T? { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + return self.mainContext.fetchOne(entity, queryClauses) } public func fetchAll(entity: T.Type, _ queryClauses: FetchClause...) -> [T]? { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + return self.mainContext.fetchAll(entity, queryClauses) } public func fetchAll(entity: T.Type, _ queryClauses: [FetchClause]) -> [T]? { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + return self.mainContext.fetchAll(entity, queryClauses) } public func fetchCount(entity: T.Type, _ queryClauses: FetchClause...) -> Int? { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + return self.mainContext.fetchCount(entity, queryClauses) } public func fetchCount(entity: T.Type, _ queryClauses: [FetchClause]) -> Int? { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + return self.mainContext.fetchCount(entity, queryClauses) } + public func fetchObjectID(entity: T.Type, _ queryClauses: FetchClause...) -> NSManagedObjectID? { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + + return self.mainContext.fetchObjectID(entity, queryClauses) + } + + public func fetchObjectID(entity: T.Type, _ queryClauses: [FetchClause]) -> NSManagedObjectID? { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + + return self.mainContext.fetchObjectID(entity, queryClauses) + } + + public func fetchObjectIDs(entity: T.Type, _ queryClauses: FetchClause...) -> [NSManagedObjectID]? { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + + return self.mainContext.fetchObjectIDs(entity, queryClauses) + } + + public func fetchObjectIDs(entity: T.Type, _ queryClauses: [FetchClause]) -> [NSManagedObjectID]? { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to fetch from a <\(self.dynamicType)> outside the main queue.") + + return self.mainContext.fetchObjectIDs(entity, queryClauses) + } + public func deleteAll(entity: T.Type, _ queryClauses: FetchClause...) -> Int? { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to delete from a <\(self.dynamicType)> outside the main queue.") + return self.mainContext.deleteAll(entity, queryClauses) } public func deleteAll(entity: T.Type, _ queryClauses: [FetchClause]) -> Int? { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to delete from a <\(self.dynamicType)> outside the main queue.") + return self.mainContext.deleteAll(entity, queryClauses) } - public func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> Int? { + public func queryValue(entity: T.Type, _ selectClause: Select, _ queryClauses: FetchClause...) -> U? { - let result = self.mainContext.queryAggregate(entity, function: function, queryClauses) - return result + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside the main queue.") + + return self.mainContext.queryValue(entity, selectClause, queryClauses) } - public func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> Int? { + public func queryValue(entity: T.Type, _ selectClause: Select, _ queryClauses: [FetchClause]) -> U? { - let result = self.mainContext.queryAggregate(entity, function: function, queryClauses) - return result + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside the main queue.") + + return self.mainContext.queryValue(entity, selectClause, queryClauses) } - public func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> U? { + public func queryAttributes(entity: T.Type, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { - return self.mainContext.queryAggregate(entity, function: function, queryClauses) + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside the main queue.") + + return self.mainContext.queryAttributes(entity, selectClause, queryClauses) } - public func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? { + public func queryAttributes(entity: T.Type, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { - return self.mainContext.queryAggregate(entity, function: function, queryClauses) + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to query from a <\(self.dynamicType)> outside the main queue.") + + return self.mainContext.queryAttributes(entity, selectClause, queryClauses) } } diff --git a/HardcoreData/DataStack+Transaction.swift b/HardcoreData/DataStack+Transaction.swift index 5fc81ec..b933f51 100644 --- a/HardcoreData/DataStack+Transaction.swift +++ b/HardcoreData/DataStack+Transaction.swift @@ -25,6 +25,7 @@ import Foundation import CoreData +import GCDKit // MARK: - DataStack @@ -40,6 +41,8 @@ public extension DataStack { */ public func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to begin a transaction from a <\(self.dynamicType)> outside the main queue.") + AsynchronousDataTransaction( mainContext: self.rootSavingContext, queue: self.childTransactionQueue, @@ -54,6 +57,8 @@ public extension DataStack { */ public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to begin a transaction from a <\(self.dynamicType)> outside the main queue.") + return SynchronousDataTransaction( mainContext: self.rootSavingContext, queue: self.childTransactionQueue, @@ -67,6 +72,8 @@ public extension DataStack { */ public func beginDetached() -> DetachedDataTransaction { + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to begin a transaction from a <\(self.dynamicType)> outside the main queue.") + return DetachedDataTransaction( mainContext: self.rootSavingContext, queue: .Main) diff --git a/HardcoreData/DataStack.swift b/HardcoreData/DataStack.swift index 78801f3..424e71d 100644 --- a/HardcoreData/DataStack.swift +++ b/HardcoreData/DataStack.swift @@ -105,7 +105,7 @@ public final class DataStack { var error: NSError? var store: NSPersistentStore? - coordinator.performSynchronously { + coordinator.performBlockAndWait { store = coordinator.addPersistentStoreWithType( NSInMemoryStoreType, @@ -203,7 +203,7 @@ public final class DataStack { var store: NSPersistentStore? var persistentStoreError: NSError? - coordinator.performSynchronously { + coordinator.performBlockAndWait { store = coordinator.addPersistentStoreWithType( NSSQLiteStoreType, @@ -238,7 +238,7 @@ public final class DataStack { error: nil) var store: NSPersistentStore? - coordinator.performSynchronously { + coordinator.performBlockAndWait { store = coordinator.addPersistentStoreWithType( NSSQLiteStoreType, diff --git a/HardcoreData/DetachedDataTransaction.swift b/HardcoreData/DetachedDataTransaction.swift index 4c75b4c..4853c34 100644 --- a/HardcoreData/DetachedDataTransaction.swift +++ b/HardcoreData/DetachedDataTransaction.swift @@ -41,7 +41,7 @@ public final class DetachedDataTransaction: BaseDataTransaction { */ public func commit(completion: (result: SaveResult) -> Void) { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside a transaction queue.") + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside its designated queue.") self.context.saveAsynchronouslyWithCompletion { (result) -> Void in diff --git a/HardcoreData/FetchClause.swift b/HardcoreData/FetchClause.swift index d21f684..0b3b7f2 100644 --- a/HardcoreData/FetchClause.swift +++ b/HardcoreData/FetchClause.swift @@ -29,7 +29,4 @@ import CoreData // MARK: - FetchClause -public protocol FetchClause { - - func applyToFetchRequest(fetchRequest: NSFetchRequest) -} +public protocol FetchClause: QueryClause { } diff --git a/HardcoreData/GroupBy.swift b/HardcoreData/GroupBy.swift new file mode 100644 index 0000000..5f463a3 --- /dev/null +++ b/HardcoreData/GroupBy.swift @@ -0,0 +1,80 @@ +// +// GroupBy.swift +// HardcoreData +// +// Copyright (c) 2015 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 + + +// MARK: - GroupBy + +public struct GroupBy: QueryClause { + + // MARK: Public + + public init(_ keyPaths: [KeyPath]) { + + self.keyPaths = keyPaths + } + + public init() { + + self.init([]) + } + + public init(_ keyPath: KeyPath, _ subKeyPaths: KeyPath...) { + + self.init([keyPath] + subKeyPaths) + } + + public let keyPaths: [KeyPath] + + + // MARK: QueryClause + + public func applyToFetchRequest(fetchRequest: NSFetchRequest) { + + if fetchRequest.propertiesToGroupBy != nil { + + HardcoreData.log(.Warning, message: "An existing \"propertiesToGroupBy\" for the <\(NSFetchRequest.self)> was overwritten by <\(self.dynamicType)> query clause.") + } + + fetchRequest.propertiesToGroupBy = self.keyPaths + +// let entityDescription = fetchRequest.entity! +// let propertyMapping = entityDescription.propertiesByName +// fetchRequest.propertiesToGroupBy = self.keyPaths.reduce([AnyObject]()) { (var properties, keyPath) -> [AnyObject] in +// +// if let propertyDescription: AnyObject = propertyMapping[keyPath] { +// +// properties.append(propertyDescription) +// } +// else { +// +// HardcoreData.log(.Warning, message: "The property \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by <\(self.dynamicType)> query clause.") +// } +// +// return properties +// } + } +} diff --git a/HardcoreData/HardcoreData+Querying.swift b/HardcoreData/HardcoreData+Querying.swift index 424070e..7a1be62 100644 --- a/HardcoreData/HardcoreData+Querying.swift +++ b/HardcoreData/HardcoreData+Querying.swift @@ -61,6 +61,26 @@ public extension HardcoreData { return self.defaultStack.fetchCount(entity, queryClauses) } + public static func fetchObjectID(entity: T.Type, _ queryClauses: FetchClause...) -> NSManagedObjectID? { + + return self.defaultStack.fetchObjectID(entity, queryClauses) + } + + public static func fetchObjectID(entity: T.Type, _ queryClauses: [FetchClause]) -> NSManagedObjectID? { + + return self.defaultStack.fetchObjectID(entity, queryClauses) + } + + public static func fetchObjectIDs(entity: T.Type, _ queryClauses: FetchClause...) -> [NSManagedObjectID]? { + + return self.defaultStack.fetchObjectIDs(entity, queryClauses) + } + + public static func fetchObjectIDs(entity: T.Type, _ queryClauses: [FetchClause]) -> [NSManagedObjectID]? { + + return self.defaultStack.fetchObjectIDs(entity, queryClauses) + } + public static func deleteAll(entity: T.Type, _ queryClauses: FetchClause...) -> Int? { return self.defaultStack.deleteAll(entity, queryClauses) @@ -71,25 +91,23 @@ public extension HardcoreData { return self.defaultStack.deleteAll(entity, queryClauses) } - public static func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> Int? { + public static func queryValue(entity: T.Type, _ selectClause: Select, _ queryClauses: FetchClause...) -> U? { - let result = self.defaultStack.queryAggregate(entity, function: function, queryClauses) - return result + return self.defaultStack.queryValue(entity, selectClause, queryClauses) } - public static func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> Int? { + public static func queryValue(entity: T.Type, _ selectClause: Select, _ queryClauses: [FetchClause]) -> U? { - let result = self.defaultStack.queryAggregate(entity, function: function, queryClauses) - return result + return self.defaultStack.queryValue(entity, selectClause, queryClauses) } - public static func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> U? { + public static func queryAttributes(entity: T.Type, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { - return self.defaultStack.queryAggregate(entity, function: function, queryClauses) + return self.defaultStack.queryAttributes(entity, selectClause, queryClauses) } - public static func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? { + public static func queryAttributes(entity: T.Type, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { - return self.defaultStack.queryAggregate(entity, function: function, queryClauses) + return self.defaultStack.queryAttributes(entity, selectClause, queryClauses) } } \ No newline at end of file diff --git a/HardcoreData/ManagedObjectController.swift b/HardcoreData/ManagedObjectController.swift new file mode 100644 index 0000000..7cb355f --- /dev/null +++ b/HardcoreData/ManagedObjectController.swift @@ -0,0 +1,27 @@ +// +// ManagedObjectController.swift +// HardcoreData +// +// Copyright (c) 2015 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 diff --git a/HardcoreData/ManagedObjectListController.swift b/HardcoreData/ManagedObjectListController.swift new file mode 100644 index 0000000..41e8504 --- /dev/null +++ b/HardcoreData/ManagedObjectListController.swift @@ -0,0 +1,125 @@ +// +// ManagedObjectListController.swift +// HardcoreData +// +// Copyright (c) 2015 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 +import GCDKit + + +// MARK: - ManagedObjectListController + +public final class ManagedObjectListController: NSFetchedResultsControllerDelegate { + + // MARK: Public + + public typealias EntityType = T + + public func addObserver(observer: U) { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to add a <\(U.self)> outside the main queue.") + + self.observers.addObject(observer) + } + + public func removeObserver(observer: U) { + + HardcoreData.assert(GCDQueue.Main.isCurrentExecutionContext(), "Attempted to remove a <\(U.self)> outside the main queue.") + + self.observers.removeObject(observer) + } + + + // MARK: NSFetchedResultsControllerDelegate + + private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { + + } + + private func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { + + } + + private func controllerWillChangeContent(controller: NSFetchedResultsController) { + + } + + private func controllerDidChangeContent(controller: NSFetchedResultsController) { + + } + + private func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? { + + return nil + } + + + // MARK: Internal + + internal init(dataStack: DataStack, entity: T.Type, sectionNameKeyPath: KeyPath?, cacheResults: Bool, queryClauses: [FetchClause]) { + + let context = dataStack.mainContext + + let fetchRequest = NSFetchRequest() + fetchRequest.entity = context.entityDescriptionForEntityClass(entity) + fetchRequest.fetchLimit = 0 + fetchRequest.resultType = .ManagedObjectResultType + + for clause in queryClauses { + + clause.applyToFetchRequest(fetchRequest) + } + + let fetchedResultsController = NSFetchedResultsController( + fetchRequest: fetchRequest, + managedObjectContext: context, + sectionNameKeyPath: sectionNameKeyPath, + cacheName: (cacheResults + ? "\(ManagedObjectListController.self).\(NSUUID())" + : nil) + ) + + + self.fetchedResultsController = fetchedResultsController + self.parentStack = dataStack + self.observers = NSHashTable.weakObjectsHashTable() + + fetchedResultsController.delegate = self + + var error: NSError? + if !fetchedResultsController.performFetch(&error) { + + HardcoreData.handleError( + error ?? NSError(hardcoreDataErrorCode: .UnknownError), + "Failed to perform fetch on \(NSFetchedResultsController.self)") + } + } + + + // MARK: Private + + private let fetchedResultsController: NSFetchedResultsController + private weak var parentStack: DataStack? + private let observers: NSHashTable +} diff --git a/HardcoreData/ManagedObjectListObserver.swift b/HardcoreData/ManagedObjectListObserver.swift new file mode 100644 index 0000000..3d9d22d --- /dev/null +++ b/HardcoreData/ManagedObjectListObserver.swift @@ -0,0 +1,80 @@ +// +// ManagedObjectListObserver.swift +// HardcoreData +// +// Copyright (c) 2015 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: - ManagedObjectListObserver + +public protocol ManagedObjectListObserver: class { + + typealias EntityType: NSManagedObject + + func managedObjectListWillChange(listController: ManagedObjectListController) + + +// func managedObjectList(listController: ManagedObjectListController, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) +// +// func managedObjectList(listController: ManagedObjectListController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int) +// +// func managedObjectList(listController: ManagedObjectListController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int) +// +// func managedObjectList(listController: ManagedObjectListController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int) + + + func managedObjectList(listController: ManagedObjectListController, didInsertObject object: EntityType, toItemIndex itemIndex: Int) + + func managedObjectList(listController: ManagedObjectListController, didDeleteObject object: EntityType, fromItemIndex itemIndex: Int) + + func managedObjectList(listController: ManagedObjectListController, didUpdateObject object: EntityType, atItemIndex itemIndex: Int) + + func managedObjectList(listController: ManagedObjectListController, didMoveObject object: EntityType, fromItemIndex: Int, toItemIndex: Int) + + + func managedObjectListDidChange(listController: ManagedObjectListController) + + +// private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { +// +// } +// +// private func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { +// +// } +// +// private func controllerWillChangeContent(controller: NSFetchedResultsController) { +// +// } +// +// private func controllerDidChangeContent(controller: NSFetchedResultsController) { +// +// } +// +// private func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? { +// +// return nil +// } +} diff --git a/HardcoreData/ManagedObjectListSectionInfo.swift b/HardcoreData/ManagedObjectListSectionInfo.swift new file mode 100644 index 0000000..795b7e7 --- /dev/null +++ b/HardcoreData/ManagedObjectListSectionInfo.swift @@ -0,0 +1,13 @@ +// +// ManagedObjectListSectionInfo.swift +// HardcoreData +// +// Created by John Rommel Estropia on 2015/03/11. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import UIKit + +class ManagedObjectListSectionInfo: NSObject { + +} diff --git a/HardcoreData/NSManagedObjectContext+Querying.swift b/HardcoreData/NSManagedObjectContext+Querying.swift index d263019..dadda8e 100644 --- a/HardcoreData/NSManagedObjectContext+Querying.swift +++ b/HardcoreData/NSManagedObjectContext+Querying.swift @@ -133,6 +133,74 @@ internal extension NSManagedObjectContext { return count } + internal func fetchObjectID(entity: T.Type, _ queryClauses: FetchClause...) -> NSManagedObjectID? { + + return self.fetchObjectID(entity, queryClauses) + } + + internal func fetchObjectID(entity: T.Type, _ queryClauses: [FetchClause]) -> NSManagedObjectID? { + + let fetchRequest = NSFetchRequest() + fetchRequest.entity = self.entityDescriptionForEntityClass(entity) + fetchRequest.fetchLimit = 1 + fetchRequest.resultType = .ManagedObjectIDResultType + + for clause in queryClauses { + + clause.applyToFetchRequest(fetchRequest) + } + + var fetchResults: [NSManagedObjectID]? + var error: NSError? + self.performBlockAndWait { + + fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObjectID] + } + if fetchResults == nil { + + HardcoreData.handleError( + error ?? NSError(hardcoreDataErrorCode: .UnknownError), + "Failed executing fetch request.") + return nil + } + + return fetchResults?.first + } + + internal func fetchObjectIDs(entity: T.Type, _ queryClauses: FetchClause...) -> [NSManagedObjectID]? { + + return self.fetchObjectIDs(entity, queryClauses) + } + + internal func fetchObjectIDs(entity: T.Type, _ queryClauses: [FetchClause]) -> [NSManagedObjectID]? { + + let fetchRequest = NSFetchRequest() + fetchRequest.entity = self.entityDescriptionForEntityClass(entity) + fetchRequest.fetchLimit = 0 + fetchRequest.resultType = .ManagedObjectIDResultType + + for clause in queryClauses { + + clause.applyToFetchRequest(fetchRequest) + } + + var fetchResults: [NSManagedObjectID]? + var error: NSError? + self.performBlockAndWait { + + fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObjectID] + } + if fetchResults == nil { + + HardcoreData.handleError( + error ?? NSError(hardcoreDataErrorCode: .UnknownError), + "Failed executing fetch request.") + return nil + } + + return fetchResults + } + internal func deleteAll(entity: T.Type, _ queryClauses: FetchClause...) -> Int? { return self.deleteAll(entity, queryClauses) @@ -178,38 +246,18 @@ internal extension NSManagedObjectContext { return numberOfDeletedObjects } - internal func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> Int? { + internal func queryValue(entity: T.Type, _ selectClause: Select, _ queryClauses: FetchClause...) -> U? { - return self.queryAggregateImplementation(entity, function: function, queryClauses) + return self.queryValue(entity, selectClause, queryClauses) } - internal func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> Int? { - - return self.queryAggregateImplementation(entity, function: function, queryClauses) - } - - internal func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: FetchClause...) -> U? { - - return self.queryAggregateImplementation(entity, function: function, queryClauses) - } - - internal func queryAggregate(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? { - - return self.queryAggregateImplementation(entity, function: function, queryClauses) - } - - internal func queryAggregateImplementation(entity: T.Type, function: AggregateFunction, _ queryClauses: [FetchClause]) -> U? { - - let expressionDescription = NSExpressionDescription() - expressionDescription.name = "queryAggregate" - expressionDescription.expressionResultType = U.attributeType - expressionDescription.expression = function.createExpression() + internal func queryValue(entity: T.Type, _ selectClause: Select, _ queryClauses: [FetchClause]) -> U? { let fetchRequest = NSFetchRequest() fetchRequest.entity = self.entityDescriptionForEntityClass(entity) - fetchRequest.resultType = .DictionaryResultType - fetchRequest.propertiesToFetch = [expressionDescription] - fetchRequest.includesPendingChanges = false + fetchRequest.fetchLimit = 0 + + selectClause.applyToFetchRequest(fetchRequest) for clause in queryClauses { @@ -222,19 +270,54 @@ internal extension NSManagedObjectContext { fetchResults = self.executeFetchRequest(fetchRequest, error: &error) } - if fetchResults == nil { + if let fetchResults = fetchResults { - HardcoreData.handleError( - error ?? NSError(hardcoreDataErrorCode: .UnknownError), - "Failed executing fetch request.") + if let rawResult = fetchResults.first as? NSDictionary, + let rawObject: AnyObject = rawResult[selectClause.keyPathForFirstSelectTerm()] { + + return Select.ReturnType.fromResultObject(rawObject) + } return nil } - if let result: AnyObject = (fetchResults?.first as! [String: AnyObject])[expressionDescription.name] { + HardcoreData.handleError( + error ?? NSError(hardcoreDataErrorCode: .UnknownError), + "Failed executing fetch request.") + return nil + } + + internal func queryAttributes(entity: T.Type, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { + + return self.queryAttributes(entity, selectClause, queryClauses) + } + + internal func queryAttributes(entity: T.Type, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { + + let fetchRequest = NSFetchRequest() + fetchRequest.entity = self.entityDescriptionForEntityClass(entity) + fetchRequest.fetchLimit = 0 + + selectClause.applyToFetchRequest(fetchRequest) + + for clause in queryClauses { - return U.fromResultObject(result) + clause.applyToFetchRequest(fetchRequest) } + var fetchResults: [AnyObject]? + var error: NSError? + self.performBlockAndWait { + + fetchResults = self.executeFetchRequest(fetchRequest, error: &error) + } + if let fetchResults = fetchResults { + + return Select.ReturnType.fromResultObjects(fetchResults) + } + + HardcoreData.handleError( + error ?? NSError(hardcoreDataErrorCode: .UnknownError), + "Failed executing fetch request.") return nil } } \ No newline at end of file diff --git a/HardcoreData/NSPersistentStoreCoordinator+HardcoreData.swift b/HardcoreData/QueryClause.swift similarity index 68% rename from HardcoreData/NSPersistentStoreCoordinator+HardcoreData.swift rename to HardcoreData/QueryClause.swift index fdac82d..6439cd8 100644 --- a/HardcoreData/NSPersistentStoreCoordinator+HardcoreData.swift +++ b/HardcoreData/QueryClause.swift @@ -1,8 +1,8 @@ // -// NSPersistentStoreCoordinator+HardcoreData.swift +// QueryClause.swift // HardcoreData // -// Copyright (c) 2014 John Rommel Estropia +// Copyright (c) 2015 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 @@ -27,23 +27,9 @@ import Foundation import CoreData -// MARK: - NSPersistentStoreCoordinator +// MARK: - QueryClause -internal extension NSPersistentStoreCoordinator { +public protocol QueryClause { - // MARK: Internal - - internal func performSynchronously(closure: () -> Void) { - - if self.respondsToSelector("performBlockAndWait:") { - - self.performBlockAndWait(closure) - } - else { - - self.lock() - autoreleasepool(closure) - self.unlock() - } - } -} \ No newline at end of file + func applyToFetchRequest(fetchRequest: NSFetchRequest) +} diff --git a/HardcoreData/Select.swift b/HardcoreData/Select.swift new file mode 100644 index 0000000..9bc8225 --- /dev/null +++ b/HardcoreData/Select.swift @@ -0,0 +1,515 @@ +// +// Select.swift +// HardcoreData +// +// Copyright (c) 2015 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: - SelectResultType + +public protocol SelectResultType { } + + +// MARK: - SelectValueResultType + +public protocol SelectValueResultType: SelectResultType { + + static func fromResultObject(result: AnyObject) -> Self? +} + + +// MARK: - SelectAttributesResultType + +public protocol SelectAttributesResultType: SelectResultType { + + static func fromResultObjects(result: [AnyObject]) -> [[NSString: AnyObject]] +} + + +// MARK: - SelectTerm + +public enum SelectTerm: StringLiteralConvertible { + + case Attribute(KeyPath) + case Aggregate(function: String, KeyPath, As: String) + + public static func Average(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { + + return .Aggregate( + function: "average:", + keyPath, + As: alias ?? "average(\(keyPath))" + ) + } + + public static func Count(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { + + return .Aggregate( + function: "count:", + keyPath, + As: alias ?? "count(\(keyPath))" + ) + } + + public static func Maximum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { + + return .Aggregate( + function: "max:", + keyPath, + As: alias ?? "max(\(keyPath))" + ) + } + + public static func Median(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { + + return .Aggregate( + function: "median:", + keyPath, As: + alias ?? "median(\(keyPath))" + ) + } + + public static func Minimum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { + + return .Aggregate( + function: "min:", + keyPath, + As: alias ?? "min(\(keyPath))" + ) + } + + public static func StandardDeviation(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { + + return .Aggregate( + function: "stddev:", + keyPath, + As: alias ?? "stddev(\(keyPath))" + ) + } + + public static func Sum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { + + return .Aggregate( + function: "sum:", + keyPath, + As: alias ?? "sum(\(keyPath))" + ) + } + + public init(stringLiteral value: KeyPath) { + + self = .Attribute(value) + } + + public init(unicodeScalarLiteral value: KeyPath) { + + self = .Attribute(value) + } + + public init(extendedGraphemeClusterLiteral value: KeyPath) { + + self = .Attribute(value) + } +} + + +// MARK: - Select + +public struct Select { + + // MARK: Public + + public typealias ReturnType = T + + public init(_ selectTerm: SelectTerm, _ selectTerms: SelectTerm...) { + + self.selectTerms = [selectTerm] + selectTerms + } + + + // MARK: Internal + + internal func applyToFetchRequest(fetchRequest: NSFetchRequest) { + + if fetchRequest.propertiesToFetch != nil { + + HardcoreData.log(.Warning, message: "An existing \"propertiesToFetch\" for the <\(NSFetchRequest.self)> was overwritten by <\(self.dynamicType)> query clause.") + } + + fetchRequest.includesPendingChanges = false + fetchRequest.resultType = .DictionaryResultType + + let entityDescription = fetchRequest.entity! + let propertiesByName = entityDescription.propertiesByName + let attributesByName = entityDescription.attributesByName + + var propertiesToFetch = [AnyObject]() + for term in self.selectTerms { + + switch term { + + case .Attribute(let keyPath): + if let propertyDescription = propertiesByName[keyPath] as? NSPropertyDescription { + + propertiesToFetch.append(propertyDescription) + } + else { + + HardcoreData.log(.Warning, message: "The property \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by <\(self.dynamicType)> query clause.") + } + + case .Aggregate(let function, let keyPath, let alias): + if let attributeDescription = attributesByName[keyPath] as? NSAttributeDescription { + + let expressionDescription = NSExpressionDescription() + expressionDescription.name = alias + expressionDescription.expressionResultType = attributeDescription.attributeType + expressionDescription.expression = NSExpression( + forFunction: function, + arguments: [NSExpression(forKeyPath: keyPath)] + ) + + propertiesToFetch.append(expressionDescription) + } + else { + + HardcoreData.log(.Warning, message: "The attribute \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by <\(self.dynamicType)> query clause.") + } + } + } + + fetchRequest.propertiesToFetch = propertiesToFetch + } + + internal func keyPathForFirstSelectTerm() -> KeyPath { + + switch self.selectTerms.first! { + + case .Attribute(let keyPath): + return keyPath + + case .Aggregate(_, _, let alias): + return alias + } + } + + + // MARK: Private + + private let selectTerms: [SelectTerm] +} + + +// MARK: - Bool: SelectValueResultType + +extension Bool: SelectValueResultType { + + public static var attributeType: NSAttributeType { + + return .BooleanAttributeType + } + + public static func fromResultObject(result: AnyObject) -> Bool? { + + return (result as? NSNumber)?.boolValue + } +} + + +// MARK: - Int8: SelectValueResultType + +extension Int8: SelectValueResultType { + + public static var attributeType: NSAttributeType { + + return .Integer64AttributeType + } + + public static func fromResultObject(result: AnyObject) -> Int8? { + + if let value = (result as? NSNumber)?.longLongValue { + + return numericCast(value) as Int8 + } + return nil + } +} + + +// MARK: - Int16: SelectValueResultType + +extension Int16: SelectValueResultType { + + public static var attributeType: NSAttributeType { + + return .Integer64AttributeType + } + + public static func fromResultObject(result: AnyObject) -> Int16? { + + if let value = (result as? NSNumber)?.longLongValue { + + return numericCast(value) as Int16 + } + return nil + } +} + + +// MARK: - Int32: SelectValueResultType + +extension Int32: SelectValueResultType { + + public static var attributeType: NSAttributeType { + + return .Integer64AttributeType + } + + public static func fromResultObject(result: AnyObject) -> Int32? { + + if let value = (result as? NSNumber)?.longLongValue { + + return numericCast(value) as Int32 + } + return nil + } +} + + +// MARK: - Int64: SelectValueResultType + +extension Int64: SelectValueResultType { + + public static var attributeType: NSAttributeType { + + return .Integer64AttributeType + } + + public static func fromResultObject(result: AnyObject) -> Int64? { + + return (result as? NSNumber)?.longLongValue + } +} + + +// MARK: - Int: SelectValueResultType + +extension Int: SelectValueResultType { + + public static var attributeType: NSAttributeType { + + return .Integer64AttributeType + } + + public static func fromResultObject(result: AnyObject) -> Int? { + + if let value = (result as? NSNumber)?.longLongValue { + + return numericCast(value) as Int + } + return nil + } +} + + +// MARK: - Double : SelectValueResultType + +extension Double: SelectValueResultType { + + public static var attributeType: NSAttributeType { + + return .DoubleAttributeType + } + + public static func fromResultObject(result: AnyObject) -> Double? { + + return (result as? NSNumber)?.doubleValue + } +} + + +// MARK: - Float: SelectValueResultType + +extension Float: SelectValueResultType { + + public static var attributeType: NSAttributeType { + + return .FloatAttributeType + } + + public static func fromResultObject(result: AnyObject) -> Float? { + + return (result as? NSNumber)?.floatValue + } +} + + +// MARK: - String: SelectValueResultType + +extension String: SelectValueResultType { + + public static var attributeType: NSAttributeType { + + return .StringAttributeType + } + + public static func fromResultObject(result: AnyObject) -> String? { + + return result as? NSString as? String + } +} + + +// MARK: - NSNumber: SelectValueResultType + +extension NSNumber: SelectValueResultType { + + public class var attributeType: NSAttributeType { + + return .Integer64AttributeType + } + + public class func fromResultObject(result: AnyObject) -> Self? { + + func forceCast(object: AnyObject) -> T? { + + return (object as? T) + } + return forceCast(result) + } +} + + +// MARK: - NSString: SelectValueResultType + +extension NSString: SelectValueResultType { + + public class var attributeType: NSAttributeType { + + return .StringAttributeType + } + + public class func fromResultObject(result: AnyObject) -> Self? { + + func forceCast(object: AnyObject) -> T? { + + return (object as? T) + } + return forceCast(result) + } +} + + +// MARK: - NSDecimalNumber: SelectValueResultType + +extension NSDecimalNumber: SelectValueResultType { + + public override class var attributeType: NSAttributeType { + + return .DecimalAttributeType + } + + public override class func fromResultObject(result: AnyObject) -> Self? { + + func forceCast(object: AnyObject) -> T? { + + return (object as? T) + } + return forceCast(result) + } +} + + +// MARK: - NSDate: SelectValueResultType + +extension NSDate: SelectValueResultType { + + public class var attributeType: NSAttributeType { + + return .DateAttributeType + } + + public class func fromResultObject(result: AnyObject) -> Self? { + + func forceCast(object: AnyObject) -> T? { + + return (object as? T) + } + return forceCast(result) + } +} + + +// MARK: - NSData: SelectValueResultType + +extension NSData: SelectValueResultType { + + public class var attributeType: NSAttributeType { + + return .BinaryDataAttributeType + } + + public class func fromResultObject(result: AnyObject) -> Self? { + + func forceCast(object: AnyObject) -> T? { + + return (object as? T) + } + return forceCast(result) + } +} + + +// MARK: - NSManagedObjectID: SelectValueResultType + +extension NSManagedObjectID: SelectValueResultType { + + public class var attributeType: NSAttributeType { + + return .ObjectIDAttributeType + } + + public class func fromResultObject(result: AnyObject) -> Self? { + + func forceCast(object: AnyObject) -> T? { + + return (object as? T) + } + return forceCast(result) + } +} + + +// MARK: - NSManagedObjectID: SelectAttributesResultType + +extension NSDictionary: SelectAttributesResultType { + + // MARK: SelectAttributesResultType + + public class func fromResultObjects(result: [AnyObject]) -> [[NSString: AnyObject]] { + + return result as! [[NSString: AnyObject]] + } +} diff --git a/HardcoreData/SortedBy.swift b/HardcoreData/SortedBy.swift index bfc6d5c..3abd804 100644 --- a/HardcoreData/SortedBy.swift +++ b/HardcoreData/SortedBy.swift @@ -32,17 +32,17 @@ public func +(left: SortedBy, right: SortedBy) -> SortedBy { } -// MARK: - AttributeName +// MARK: - KeyPath -public typealias AttributeName = Selector +public typealias KeyPath = String // MARK: - SortOrder public enum SortOrder { - case Ascending(AttributeName) - case Descending(AttributeName) + case Ascending(KeyPath) + case Descending(KeyPath) } @@ -69,21 +69,19 @@ public struct SortedBy: FetchClause { public init(_ order: [SortOrder]) { - self.init(order.map { sortOrder -> NSSortDescriptor in - - switch sortOrder { + self.init( + order.map { sortOrder -> NSSortDescriptor in - case .Ascending(let attributeName): - return NSSortDescriptor( - key: NSStringFromSelector(attributeName), - ascending: true) - - case .Descending(let attributeName): - return NSSortDescriptor( - key: NSStringFromSelector(attributeName), - ascending: false) + switch sortOrder { + + case .Ascending(let keyPath): + return NSSortDescriptor(key: keyPath, ascending: true) + + case .Descending(let keyPath): + return NSSortDescriptor(key: keyPath, ascending: false) + } } - }) + ) } public init(_ order: SortOrder, _ subOrder: SortOrder...) { diff --git a/HardcoreData/SynchronousDataTransaction.swift b/HardcoreData/SynchronousDataTransaction.swift index 1d19746..02bde8d 100644 --- a/HardcoreData/SynchronousDataTransaction.swift +++ b/HardcoreData/SynchronousDataTransaction.swift @@ -20,7 +20,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction { */ public func commitAndWait() { - HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside a transaction queue.") + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a <\(self.dynamicType)> outside its designated queue.") HardcoreData.assert(!self.isCommitted, "Attempted to commit a <\(self.dynamicType)> more than once.") self.isCommitted = true @@ -35,6 +35,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction { */ public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { + HardcoreData.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to begin a child transaction from a <\(self.dynamicType)> outside its designated queue.") HardcoreData.assert(!self.isCommitted, "Attempted to begin a child transaction from an already committed <\(self.dynamicType)>.") return SynchronousDataTransaction( diff --git a/HardcoreData/Where.swift b/HardcoreData/Where.swift index 7789781..aa8d2b8 100644 --- a/HardcoreData/Where.swift +++ b/HardcoreData/Where.swift @@ -68,16 +68,16 @@ public struct Where: FetchClause { self.init(NSPredicate(format: format, argumentArray: args)) } - public init(_ format: String, argumentArray: [AnyObject]?) { + public init(_ format: String, argumentArray: [NSObject]?) { self.init(NSPredicate(format: format, argumentArray: argumentArray)) } - public init(_ attributeName: AttributeName, isEqualTo value: NSObject?) { + public init(_ keyPath: KeyPath, isEqualTo value: NSObject?) { self.init(value == nil - ? NSPredicate(format: "\(attributeName) == nil") - : NSPredicate(format: "\(attributeName) == %@", value!)) + ? NSPredicate(format: "\(keyPath) == nil") + : NSPredicate(format: "\(keyPath) == %@", value!)) } public let predicate: NSPredicate diff --git a/HardcoreDataTests/HardcoreDataTests.swift b/HardcoreDataTests/HardcoreDataTests.swift index 0a461cc..e249e06 100644 --- a/HardcoreDataTests/HardcoreDataTests.swift +++ b/HardcoreDataTests/HardcoreDataTests.swift @@ -76,9 +76,9 @@ class HardcoreDataTests: XCTestCase { obj1.testNumber = 42 obj1.testDate = NSDate() - let count = transaction.queryAggregate( + let count = transaction.queryValue( TestEntity1.self, - function: .Count("testNumber") + Select(.Count("testNumber")) ) XCTAssertTrue(count == 0, "count == 0 (actual: \(count))") // counts only objects in store @@ -90,10 +90,16 @@ class HardcoreDataTests: XCTestCase { let obj3 = transaction.create(TestEntity2) obj3.testEntityID = 3 - obj3.testString = "hohoho" + obj3.testString = "hahaha" obj3.testNumber = 90 obj3.testDate = NSDate() + let obj4 = transaction.create(TestEntity2) + obj4.testEntityID = 5 + obj4.testString = "hohoho" + obj4.testNumber = 80 + obj4.testDate = NSDate() + transaction.beginSynchronous { (transaction) -> Void in @@ -106,12 +112,23 @@ class HardcoreDataTests: XCTestCase { let objs4test = transaction.fetchOne( TestEntity2.self, Where("testEntityID", isEqualTo: 4), - CustomizeQuery { (fetchRequest) -> Void in + CustomizeFetch { (fetchRequest) -> Void in fetchRequest.includesPendingChanges = true } ) XCTAssertNotNil(objs4test, "objs4test != nil") + + let objs5test = transaction.fetchOne( + TestEntity2.self, + Where("testEntityID", isEqualTo: 4), + CustomizeFetch { (fetchRequest) -> Void in + + fetchRequest.includesPendingChanges = false + } + ) + XCTAssertNil(objs5test, "objs5test == nil") + // Dont commit1 } @@ -119,12 +136,16 @@ class HardcoreDataTests: XCTestCase { let objs4test = HardcoreData.fetchOne( TestEntity2.self, - Where("testEntityID", isEqualTo: 4) + Where("testEntityID", isEqualTo: 4), + CustomizeFetch { (fetchRequest) -> Void in + + fetchRequest.includesPendingChanges = false + } ) XCTAssertNil(objs4test, "objs4test == nil") let objs5test = detachedTransaction.fetchCount(TestEntity2) - XCTAssertTrue(objs5test == 2, "objs5test == 2") + XCTAssertTrue(objs5test == 3, "objs5test == 3") XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()") switch result { @@ -149,7 +170,7 @@ class HardcoreDataTests: XCTestCase { TestEntity2.self, Where("testNumber", isEqualTo: 100) || Where("%K == %@", "testNumber", 90), SortedBy(.Ascending("testEntityID"), .Descending("testString")), - CustomizeQuery { (fetchRequest) -> Void in + CustomizeFetch { (fetchRequest) -> Void in fetchRequest.includesPendingChanges = true } @@ -159,6 +180,13 @@ class HardcoreDataTests: XCTestCase { transaction.commit { (result) -> Void in + let counts = HardcoreData.queryAttributes( + TestEntity2.self, + Select(.Count("testString", As: "count"), "testString"), + GroupBy("testString") + ) + println(counts) + XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()") switch result { @@ -174,15 +202,15 @@ class HardcoreDataTests: XCTestCase { self.waitForExpectationsWithTimeout(100, handler: nil) - let max1 = HardcoreData.queryAggregate( + let max1 = HardcoreData.queryValue( TestEntity2.self, - function: .Maximum("testNumber") + Select(.Maximum("testNumber")) ) XCTAssertTrue(max1 == 100, "max == 100 (actual: \(max1))") - let max2 = HardcoreData.queryAggregate( + let max2 = HardcoreData.queryValue( TestEntity2.self, - function: .Maximum("testNumber"), + Select(.Maximum("testNumber")), Where("%K > %@", "testEntityID", 2) ) XCTAssertTrue(max2 == 90, "max == 90 (actual: \(max2))") @@ -196,7 +224,7 @@ class HardcoreDataTests: XCTestCase { TestEntity2.self, Where("%K > %@", "testEntityID", 2) ) - XCTAssertTrue(numberOfDeletedObjects2 == 1, "numberOfDeletedObjects2 == 1 (actual: \(numberOfDeletedObjects2))") + XCTAssertTrue(numberOfDeletedObjects2 == 2, "numberOfDeletedObjects2 == 2 (actual: \(numberOfDeletedObjects2))") transaction.commitAndWait() } @@ -225,9 +253,9 @@ class HardcoreDataTests: XCTestCase { case .Success(let hasChanges): XCTAssertTrue(hasChanges, "hasChanges == true") - let count = HardcoreData.queryAggregate( + let count: Int? = HardcoreData.queryValue( TestEntity1.self, - function: .Count("testNumber") + Select(.Count("testNumber")) ) XCTAssertTrue(count == 1, "count == 1 (actual: \(count))") @@ -245,9 +273,9 @@ class HardcoreDataTests: XCTestCase { case .Success(let hasChanges): XCTAssertTrue(hasChanges, "hasChanges == true") - let count = HardcoreData.queryAggregate( + let count = HardcoreData.queryValue( TestEntity1.self, - function: .Count("testNumber") + Select(.Count("testNumber")) ) XCTAssertTrue(count == 2, "count == 2 (actual: \(count))") diff --git a/HardcoreDataTests/TestEntity1.swift b/HardcoreDataTests/TestEntity1.swift index 9fbbfe3..9751209 100644 --- a/HardcoreDataTests/TestEntity1.swift +++ b/HardcoreDataTests/TestEntity1.swift @@ -32,5 +32,4 @@ class TestEntity1: NSManagedObject { @NSManaged var testString: String? @NSManaged var testNumber: NSNumber? @NSManaged var testDate: NSDate? - } diff --git a/HardcoreDataTests/TestEntity2.swift b/HardcoreDataTests/TestEntity2.swift index 0392d5b..3c94952 100644 --- a/HardcoreDataTests/TestEntity2.swift +++ b/HardcoreDataTests/TestEntity2.swift @@ -33,4 +33,5 @@ class TestEntity2: NSManagedObject { @NSManaged var testNumber: NSNumber? @NSManaged var testDate: NSDate? + var testProperty: NSNumber? }