From 40f22761bf4882ac68fa9e296dbe0f3d5836eb99 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Wed, 1 Jul 2015 08:03:09 +0900 Subject: [PATCH 01/19] Big update. Migration tools in progress --- CoreStore.podspec | 2 +- CoreStore.xcodeproj/project.pbxproj | 48 ++- .../BaseDataTransaction+Querying.swift | 32 +- .../Concrete Clauses/From.swift | 81 +++- .../Concrete Clauses/OrderBy.swift | 10 +- .../Concrete Clauses/Select.swift | 6 +- .../CoreStore+Querying.swift | 24 +- .../DataStack+Querying.swift | 24 +- CoreStore/Info.plist | 2 +- .../NSManagedObject+Transaction.swift | 6 +- .../NSManagedObjectContext+CoreStore.swift | 7 +- CoreStore/Logging/CoreStoreLogger.swift | 10 + CoreStore/Migrating/DataStack+Migration.swift | 385 +++++++++++------- CoreStore/Migrating/MigrationChain.swift | 129 ++++++ CoreStore/Migrating/MigrationResult.swift | 151 +++++++ CoreStore/NSError+CoreStore.swift | 5 - CoreStore/Observing/CoreStore+Observing.swift | 44 +- CoreStore/Observing/DataStack+Observing.swift | 48 +-- ...ListController.swift => ListMonitor.swift} | 291 ++++++------- CoreStore/Observing/ListObserver.swift | 147 +++++++ .../Observing/ManagedObjectListObserver.swift | 147 ------- ...ctController.swift => ObjectMonitor.swift} | 81 ++-- ...ectObserver.swift => ObjectObserver.swift} | 24 +- CoreStore/Observing/SectionBy.swift | 72 ++++ .../AsynchronousDataTransaction.swift | 6 +- .../BaseDataTransaction.swift | 116 +----- CoreStore/Saving and Processing/Into.swift | 145 +++++++ .../SynchronousDataTransaction.swift | 12 +- CoreStore/Setting Up/DataStack.swift | 51 +-- .../CoreStoreDemo.xcodeproj/project.pbxproj | 64 ++- .../xcshareddata/CoreStoreDemo.xccheckout | 6 +- .../WorkspaceSettings.xcsettings | 10 + .../xcdebugger/Breakpoints_v2.xcbkptlist | 34 ++ .../CoreStoreDemo/Base.lproj/Main.storyboard | 114 +++++- CoreStoreDemo/CoreStoreDemo/Info.plist | 2 +- ...t => ListObserverDemoViewController.swift} | 38 +- .../ObjectObserverDemoViewController.swift | 36 +- .../ObserversViewController.swift | 2 +- .../MigrationsDemoViewController.swift | 187 +++++++++ .../MIgrations Demo/OrganismProtocol.swift | 14 + .../MIgrations Demo/OrganismV1.swift | 24 ++ .../MIgrations Demo/OrganismV2.swift | 26 ++ .../xcmapping.xml | 97 +++++ .../OrganismV2ToV3MigrationPolicy.swift | 27 ++ .../MIgrations Demo/OrganismV3.swift | 28 ++ .../.xccurrentversion | 2 +- .../MigrationDemo.xcdatamodel/contents | 7 +- .../MigrationDemoV2.xcdatamodel/contents | 8 +- .../MigrationDemoV3.xcdatamodel/contents | 12 + .../StackSetupDemoViewController.swift | 8 +- .../TransactionsDemoViewController.swift | 14 +- CoreStoreTests/CoreStoreTests.swift | 2 +- README.md | 279 ++++++++----- 53 files changed, 2177 insertions(+), 970 deletions(-) create mode 100644 CoreStore/Migrating/MigrationChain.swift create mode 100644 CoreStore/Migrating/MigrationResult.swift rename CoreStore/Observing/{ManagedObjectListController.swift => ListMonitor.swift} (66%) create mode 100644 CoreStore/Observing/ListObserver.swift delete mode 100644 CoreStore/Observing/ManagedObjectListObserver.swift rename CoreStore/Observing/{ManagedObjectController.swift => ObjectMonitor.swift} (75%) rename CoreStore/Observing/{ManagedObjectObserver.swift => ObjectObserver.swift} (65%) create mode 100644 CoreStore/Observing/SectionBy.swift create mode 100644 CoreStore/Saving and Processing/Into.swift create mode 100644 CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcuserdata/johnestropia.xcuserdatad/WorkspaceSettings.xcsettings rename CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/{ObjectListObserverDemoViewController.swift => ListObserverDemoViewController.swift} (74%) create mode 100644 CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MigrationsDemoViewController.swift create mode 100644 CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismProtocol.swift create mode 100644 CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV1.swift create mode 100644 CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2.swift create mode 100644 CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2ToV3.xcmappingmodel/xcmapping.xml create mode 100644 CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2ToV3MigrationPolicy.swift create mode 100644 CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV3.swift create mode 100644 CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV3.xcdatamodel/contents diff --git a/CoreStore.podspec b/CoreStore.podspec index 48aeb2f..de0a5a4 100644 --- a/CoreStore.podspec +++ b/CoreStore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "CoreStore" - s.version = "0.2.0" + s.version = "1.0.0" s.license = "MIT" s.summary = "Simple, elegant, and smart Core Data programming with Swift" s.homepage = "https://github.com/JohnEstropia/CoreStore" diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 2fe0aeb..3254c18 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -12,7 +12,11 @@ 2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A54C19C5C872005002A5 /* CoreData.framework */; }; 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; }; + B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56007101B3F6BD500A9A8F9 /* Into.swift */; }; + B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56007131B3F6C2800A9A8F9 /* SectionBy.swift */; }; + B56007161B4018AB00A9A8F9 /* MigrationChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56007151B4018AB00A9A8F9 /* MigrationChain.swift */; }; B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */; }; + B56965241B356B820075EE4A /* MigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965231B356B820075EE4A /* MigrationResult.swift */; }; B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; }; B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D372851A39CDDB00F583D9 /* TestEntity1.swift */; }; @@ -43,10 +47,10 @@ B5E84F151AFF847B0064E85B /* CoreStore+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F071AFF847B0064E85B /* CoreStore+Querying.swift */; }; B5E84F201AFF84860064E85B /* DataStack+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1A1AFF84860064E85B /* DataStack+Observing.swift */; }; B5E84F211AFF84860064E85B /* CoreStore+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1B1AFF84860064E85B /* CoreStore+Observing.swift */; }; - B5E84F221AFF84860064E85B /* ManagedObjectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1C1AFF84860064E85B /* ManagedObjectController.swift */; }; - B5E84F231AFF84860064E85B /* ManagedObjectListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1D1AFF84860064E85B /* ManagedObjectListController.swift */; }; - B5E84F241AFF84860064E85B /* ManagedObjectListObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1E1AFF84860064E85B /* ManagedObjectListObserver.swift */; }; - B5E84F251AFF84860064E85B /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1F1AFF84860064E85B /* ManagedObjectObserver.swift */; }; + B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1C1AFF84860064E85B /* ObjectMonitor.swift */; }; + B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1D1AFF84860064E85B /* ListMonitor.swift */; }; + B5E84F241AFF84860064E85B /* ListObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1E1AFF84860064E85B /* ListObserver.swift */; }; + B5E84F251AFF84860064E85B /* ObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1F1AFF84860064E85B /* ObjectObserver.swift */; }; B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F271AFF84920064E85B /* NSManagedObject+Convenience.swift */; }; B5E84F2E1AFF849C0064E85B /* AssociatedObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */; }; B5E84F2F1AFF849C0064E85B /* NotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */; }; @@ -100,7 +104,11 @@ 2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 2F291E2619C6D3CF007AF63F /* CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoreStore.swift; sourceTree = ""; }; B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = ""; }; + B56007101B3F6BD500A9A8F9 /* Into.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Into.swift; sourceTree = ""; }; + B56007131B3F6C2800A9A8F9 /* SectionBy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionBy.swift; sourceTree = ""; }; + B56007151B4018AB00A9A8F9 /* MigrationChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationChain.swift; sourceTree = ""; }; B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Migration.swift"; sourceTree = ""; }; + B56965231B356B820075EE4A /* MigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationResult.swift; sourceTree = ""; }; B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+CoreStore.swift"; sourceTree = ""; }; 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 = ""; }; @@ -134,10 +142,10 @@ B5E84F071AFF847B0064E85B /* CoreStore+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Querying.swift"; sourceTree = ""; }; B5E84F1A1AFF84860064E85B /* DataStack+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Observing.swift"; sourceTree = ""; }; B5E84F1B1AFF84860064E85B /* CoreStore+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Observing.swift"; sourceTree = ""; }; - B5E84F1C1AFF84860064E85B /* ManagedObjectController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectController.swift; sourceTree = ""; }; - B5E84F1D1AFF84860064E85B /* ManagedObjectListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListController.swift; sourceTree = ""; }; - B5E84F1E1AFF84860064E85B /* ManagedObjectListObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListObserver.swift; sourceTree = ""; }; - B5E84F1F1AFF84860064E85B /* ManagedObjectObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectObserver.swift; sourceTree = ""; }; + B5E84F1C1AFF84860064E85B /* ObjectMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectMonitor.swift; sourceTree = ""; }; + B5E84F1D1AFF84860064E85B /* ListMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListMonitor.swift; sourceTree = ""; }; + B5E84F1E1AFF84860064E85B /* ListObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListObserver.swift; sourceTree = ""; }; + B5E84F1F1AFF84860064E85B /* ObjectObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserver.swift; sourceTree = ""; }; B5E84F271AFF84920064E85B /* NSManagedObject+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Convenience.swift"; sourceTree = ""; }; B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociatedObjects.swift; sourceTree = ""; }; B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationObserver.swift; sourceTree = ""; }; @@ -256,6 +264,8 @@ isa = PBXGroup; children = ( B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */, + B56007151B4018AB00A9A8F9 /* MigrationChain.swift */, + B56965231B356B820075EE4A /* MigrationResult.swift */, ); path = Migrating; sourceTree = ""; @@ -308,6 +318,7 @@ B5E84EE91AFF846E0064E85B /* Saving and Processing */ = { isa = PBXGroup; children = ( + B56007101B3F6BD500A9A8F9 /* Into.swift */, B5E84EEB1AFF846E0064E85B /* BaseDataTransaction.swift */, B5E84EEA1AFF846E0064E85B /* AsynchronousDataTransaction.swift */, B5E84EEC1AFF846E0064E85B /* DataStack+Transaction.swift */, @@ -355,12 +366,13 @@ B5E84F191AFF84860064E85B /* Observing */ = { isa = PBXGroup; children = ( + B56007131B3F6C2800A9A8F9 /* SectionBy.swift */, B5E84F1A1AFF84860064E85B /* DataStack+Observing.swift */, B5E84F1B1AFF84860064E85B /* CoreStore+Observing.swift */, - B5E84F1C1AFF84860064E85B /* ManagedObjectController.swift */, - B5E84F1F1AFF84860064E85B /* ManagedObjectObserver.swift */, - B5E84F1D1AFF84860064E85B /* ManagedObjectListController.swift */, - B5E84F1E1AFF84860064E85B /* ManagedObjectListObserver.swift */, + B5E84F1C1AFF84860064E85B /* ObjectMonitor.swift */, + B5E84F1F1AFF84860064E85B /* ObjectObserver.swift */, + B5E84F1D1AFF84860064E85B /* ListMonitor.swift */, + B5E84F1E1AFF84860064E85B /* ListObserver.swift */, ); path = Observing; sourceTree = ""; @@ -520,27 +532,31 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - B5E84F221AFF84860064E85B /* ManagedObjectController.swift in Sources */, + B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */, B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */, B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */, B5E84F131AFF847B0064E85B /* Where.swift in Sources */, B5E84F141AFF847B0064E85B /* DataStack+Querying.swift in Sources */, + B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */, B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */, + B56007161B4018AB00A9A8F9 /* MigrationChain.swift in Sources */, B5E84F0E1AFF847B0064E85B /* Tweak.swift in Sources */, B5E84F121AFF847B0064E85B /* OrderBy.swift in Sources */, B5E84F361AFF85470064E85B /* NSManagedObjectContext+Setup.swift in Sources */, B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */, + B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */, B5E84F111AFF847B0064E85B /* Select.swift in Sources */, B5E84EE11AFF84500064E85B /* PersistentStoreResult.swift in Sources */, - B5E84F251AFF84860064E85B /* ManagedObjectObserver.swift in Sources */, + B5E84F251AFF84860064E85B /* ObjectObserver.swift in Sources */, B5E84F2F1AFF849C0064E85B /* NotificationObserver.swift in Sources */, B5E84F381AFF85470064E85B /* NSManagedObject+Transaction.swift in Sources */, + B56965241B356B820075EE4A /* MigrationResult.swift in Sources */, 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */, B5E84F411AFF8CCD0064E85B /* ClauseTypes.swift in Sources */, B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */, - B5E84F231AFF84860064E85B /* ManagedObjectListController.swift in Sources */, + B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */, B5E84EF71AFF846E0064E85B /* DetachedDataTransaction.swift in Sources */, B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */, B5E84EF51AFF846E0064E85B /* BaseDataTransaction.swift in Sources */, @@ -559,7 +575,7 @@ B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */, B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */, B5E84F151AFF847B0064E85B /* CoreStore+Querying.swift in Sources */, - B5E84F241AFF84860064E85B /* ManagedObjectListObserver.swift in Sources */, + B5E84F241AFF84860064E85B /* ListObserver.swift in Sources */, B5E84F2E1AFF849C0064E85B /* AssociatedObjects.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; diff --git a/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift b/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift index 336dedd..13d5940 100644 --- a/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift +++ b/CoreStore/Fetching and Querying/BaseDataTransaction+Querying.swift @@ -90,11 +90,11 @@ public extension BaseDataTransaction { } /** - Fetches the number of `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchCount(from: From, _ fetchClauses: FetchClause...) -> Int? { @@ -104,11 +104,11 @@ public extension BaseDataTransaction { } /** - Fetches the number of `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchCount(from: From, _ fetchClauses: [FetchClause]) -> Int? { @@ -146,11 +146,11 @@ public extension BaseDataTransaction { } /** - Fetches the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchObjectIDs(from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { @@ -160,11 +160,11 @@ public extension BaseDataTransaction { } /** - Fetches the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchObjectIDs(from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { @@ -174,11 +174,11 @@ public extension BaseDataTransaction { } /** - Deletes all `NSManagedObject`'s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number of `NSManagedObject`'s deleted + :returns: the number of `NSManagedObject`s deleted */ public func deleteAll(from: From, _ deleteClauses: DeleteClause...) -> Int? { @@ -188,11 +188,11 @@ public extension BaseDataTransaction { } /** - Deletes all `NSManagedObject`'s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number of `NSManagedObject`'s deleted + :returns: the number of `NSManagedObject`s deleted */ public func deleteAll(from: From, _ deleteClauses: [DeleteClause]) -> Int? { @@ -202,7 +202,7 @@ public extension BaseDataTransaction { } /** - Queries aggregate values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. @@ -219,7 +219,7 @@ public extension BaseDataTransaction { } /** - Queries aggregate values or aggregates as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Queries aggregate values or aggregates as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. @@ -236,7 +236,7 @@ public extension BaseDataTransaction { } /** - Queries a dictionary of attribute values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. @@ -253,7 +253,7 @@ public extension BaseDataTransaction { } /** - Queries a dictionary of attribute values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. diff --git a/CoreStore/Fetching and Querying/Concrete Clauses/From.swift b/CoreStore/Fetching and Querying/Concrete Clauses/From.swift index ddb4047..8c14751 100644 --- a/CoreStore/Fetching and Querying/Concrete Clauses/From.swift +++ b/CoreStore/Fetching and Querying/Concrete Clauses/From.swift @@ -38,72 +38,110 @@ public struct From { public init(){ + self.entityClass = T.self self.findPersistentStores = { _ in nil } } public init(_ entity: T.Type) { + self.entityClass = entity + self.findPersistentStores = { _ in nil } + } + + public init(_ entityClass: AnyClass) { + + self.entityClass = entityClass self.findPersistentStores = { _ in nil } } public init(_ configurations: String?...) { - self.init(configurations: configurations) + self.init(entityClass: T.self, configurations: configurations) } public init(_ configurations: [String?]) { - self.init(configurations: configurations) + self.init(entityClass: T.self, configurations: configurations) } public init(_ entity: T.Type, _ configurations: String?...) { - self.init(configurations: configurations) + self.init(entityClass: entity, configurations: configurations) } public init(_ entity: T.Type, _ configurations: [String?]) { - self.init(configurations: configurations) + self.init(entityClass: entity, configurations: configurations) + } + + public init(_ entityClass: AnyClass, _ configurations: String?...) { + + self.init(entityClass: entityClass, configurations: configurations) + } + + public init(_ entityClass: AnyClass, _ configurations: [String?]) { + + self.init(entityClass: entityClass, configurations: configurations) } public init(_ storeURLs: NSURL...) { - self.init(storeURLs: storeURLs) + self.init(entityClass: T.self, storeURLs: storeURLs) } public init(_ storeURLs: [NSURL]) { - self.init(storeURLs: storeURLs) + self.init(entityClass: T.self, storeURLs: storeURLs) } public init(_ entity: T.Type, _ storeURLs: NSURL...) { - self.init(storeURLs: storeURLs) + self.init(entityClass: entity, storeURLs: storeURLs) } public init(_ entity: T.Type, _ storeURLs: [NSURL]) { - self.init(storeURLs: storeURLs) + self.init(entityClass: entity, storeURLs: storeURLs) + } + + public init(_ entityClass: AnyClass, _ storeURLs: NSURL...) { + + self.init(entityClass: entityClass, storeURLs: storeURLs) + } + + public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) { + + self.init(entityClass: entityClass, storeURLs: storeURLs) } public init(_ persistentStores: NSPersistentStore...) { - self.init(persistentStores: persistentStores) + self.init(entityClass: T.self, persistentStores: persistentStores) } public init(_ persistentStores: [NSPersistentStore]) { - self.init(persistentStores: persistentStores) + self.init(entityClass: T.self, persistentStores: persistentStores) } public init(_ entity: T.Type, _ persistentStores: NSPersistentStore...) { - self.init(persistentStores: persistentStores) + self.init(entityClass: entity, persistentStores: persistentStores) } public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) { - self.init(persistentStores: persistentStores) + self.init(entityClass: entity, persistentStores: persistentStores) + } + + public init(_ entityClass: AnyClass, _ persistentStores: NSPersistentStore...) { + + self.init(entityClass: entityClass, persistentStores: persistentStores) + } + + public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) { + + self.init(entityClass: entityClass, persistentStores: persistentStores) } @@ -111,45 +149,50 @@ public struct From { internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) { - fetchRequest.entity = context.entityDescriptionForEntityClass(T.self) + fetchRequest.entity = context.entityDescriptionForEntityClass(self.entityClass) fetchRequest.affectedStores = self.findPersistentStores(context: context) } // MARK: Private + private let entityClass: AnyClass + private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]? - private init(configurations: [String?]) { + private init(entityClass: AnyClass, configurations: [String?]) { let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName }) + self.entityClass = entityClass self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in - return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter { + return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter { return configurationsSet.contains($0.configurationName) } } } - private init(storeURLs: [NSURL]) { + private init(entityClass: AnyClass, storeURLs: [NSURL]) { let storeURLsSet = Set(storeURLs) + self.entityClass = entityClass self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in - return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter { + return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter { return $0.URL != nil && storeURLsSet.contains($0.URL!) } } } - private init(persistentStores: [NSPersistentStore]) { + private init(entityClass: AnyClass, persistentStores: [NSPersistentStore]) { let persistentStores = Set(persistentStores) + self.entityClass = entityClass self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in - return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter { + return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter { return persistentStores.contains($0) } diff --git a/CoreStore/Fetching and Querying/Concrete Clauses/OrderBy.swift b/CoreStore/Fetching and Querying/Concrete Clauses/OrderBy.swift index e41e195..c1b3416 100644 --- a/CoreStore/Fetching and Querying/Concrete Clauses/OrderBy.swift +++ b/CoreStore/Fetching and Querying/Concrete Clauses/OrderBy.swift @@ -73,7 +73,7 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause { /** Initializes a `OrderBy` clause with a list of sort descriptors - :param: sortDescriptors a series of `NSSortDescriptor`'s + :param: sortDescriptors a series of `NSSortDescriptor`s */ public init(_ sortDescriptors: [NSSortDescriptor]) { @@ -99,9 +99,9 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause { } /** - Initializes a `OrderBy` clause with a series of `SortKey`'s + Initializes a `OrderBy` clause with a series of `SortKey`s - :param: sortKey a series of `SortKey`'s + :param: sortKey a series of `SortKey`s */ public init(_ sortKey: [SortKey]) { @@ -121,10 +121,10 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause { } /** - Initializes a `OrderBy` clause with a series of `SortKey`'s + Initializes a `OrderBy` clause with a series of `SortKey`s :param: sortKey a single `SortKey` - :param: sortKeys a series of `SortKey`'s + :param: sortKeys a series of `SortKey`s */ public init(_ sortKey: SortKey, _ sortKeys: SortKey...) { diff --git a/CoreStore/Fetching and Querying/Concrete Clauses/Select.swift b/CoreStore/Fetching and Querying/Concrete Clauses/Select.swift index ba032ee..0b179f5 100644 --- a/CoreStore/Fetching and Querying/Concrete Clauses/Select.swift +++ b/CoreStore/Fetching and Querying/Concrete Clauses/Select.swift @@ -267,7 +267,7 @@ Valid return types depend on the query: - for `queryAttributes(...)` methods: - `NSDictionary` -:param: sortDescriptors a series of `NSSortDescriptor`'s +:param: sortDescriptors a series of `NSSortDescriptor`s */ public struct Select { @@ -279,10 +279,10 @@ public struct Select { public typealias ReturnType = T /** - Initializes a `Select` clause with a list of `SelectTerm`'s + Initializes a `Select` clause with a list of `SelectTerm`s :param: selectTerm a `SelectTerm` - :param: selectTerms a series of `SelectTerm`'s + :param: selectTerms a series of `SelectTerm`s */ public init(_ selectTerm: SelectTerm, _ selectTerms: SelectTerm...) { diff --git a/CoreStore/Fetching and Querying/CoreStore+Querying.swift b/CoreStore/Fetching and Querying/CoreStore+Querying.swift index 1698ce1..efa7d4a 100644 --- a/CoreStore/Fetching and Querying/CoreStore+Querying.swift +++ b/CoreStore/Fetching and Querying/CoreStore+Querying.swift @@ -80,11 +80,11 @@ public extension CoreStore { } /** - Using the `defaultStack`, fetches the number of `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public static func fetchCount(from: From, _ fetchClauses: FetchClause...) -> Int? { @@ -92,11 +92,11 @@ public extension CoreStore { } /** - Using the `defaultStack`, fetches the number of `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public static func fetchCount(from: From, _ fetchClauses: [FetchClause]) -> Int? { @@ -128,11 +128,11 @@ public extension CoreStore { } /** - Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public static func fetchObjectIDs(from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { @@ -140,11 +140,11 @@ public extension CoreStore { } /** - Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public static func fetchObjectIDs(from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { @@ -152,7 +152,7 @@ public extension CoreStore { } /** - Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. @@ -167,7 +167,7 @@ public extension CoreStore { } /** - Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. @@ -182,7 +182,7 @@ public extension CoreStore { } /** - Using the `defaultStack`, queries a dictionary of attribtue values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Using the `defaultStack`, queries a dictionary of attribtue values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. @@ -197,7 +197,7 @@ public extension CoreStore { } /** - Using the `defaultStack`, queries a dictionary of attribute values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Using the `defaultStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. diff --git a/CoreStore/Fetching and Querying/DataStack+Querying.swift b/CoreStore/Fetching and Querying/DataStack+Querying.swift index dae22de..befdbe5 100644 --- a/CoreStore/Fetching and Querying/DataStack+Querying.swift +++ b/CoreStore/Fetching and Querying/DataStack+Querying.swift @@ -91,11 +91,11 @@ public extension DataStack { } /** - Fetches the number of `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchCount(from: From, _ fetchClauses: FetchClause...) -> Int? { @@ -105,11 +105,11 @@ public extension DataStack { } /** - Fetches the number of `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchCount(from: From, _ fetchClauses: [FetchClause]) -> Int? { @@ -147,11 +147,11 @@ public extension DataStack { } /** - Fetches the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchObjectIDs(from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { @@ -161,11 +161,11 @@ public extension DataStack { } /** - Fetches the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s + :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchObjectIDs(from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { @@ -175,7 +175,7 @@ public extension DataStack { } /** - Queries aggregate values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. @@ -192,7 +192,7 @@ public extension DataStack { } /** - Queries aggregate values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. @@ -209,7 +209,7 @@ public extension DataStack { } /** - Queries a dictionary of attribute values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. @@ -226,7 +226,7 @@ public extension DataStack { } /** - Queries a dictionary of attribute values as specified by the `QueryClause`'s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. diff --git a/CoreStore/Info.plist b/CoreStore/Info.plist index 75994d8..5b188f2 100644 --- a/CoreStore/Info.plist +++ b/CoreStore/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType FMWK CFBundleShortVersionString - 0.2.0 + 1.0.0 CFBundleSignature ???? CFBundleVersion diff --git a/CoreStore/Internal/NSManagedObject+Transaction.swift b/CoreStore/Internal/NSManagedObject+Transaction.swift index c574773..233c2e8 100644 --- a/CoreStore/Internal/NSManagedObject+Transaction.swift +++ b/CoreStore/Internal/NSManagedObject+Transaction.swift @@ -33,15 +33,15 @@ internal extension NSManagedObject { // MARK: Internal - internal class func createInContext(context: NSManagedObjectContext) -> Self { + internal dynamic class func createInContext(context: NSManagedObjectContext) -> Self { return self( - entity: context.entityDescriptionForEntityClass(self)!, + entity: context.entityDescriptionForEntityType(self)!, insertIntoManagedObjectContext: context ) } - internal class func inContext(context: NSManagedObjectContext, withObjectID objectID: NSManagedObjectID) -> Self? { + internal dynamic class func inContext(context: NSManagedObjectContext, withObjectID objectID: NSManagedObjectID) -> Self? { return self.typedObjectInContext(context, objectID: objectID) } diff --git a/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift b/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift index 68424fe..f4e2f1e 100644 --- a/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift +++ b/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift @@ -54,7 +54,12 @@ internal extension NSManagedObjectContext { } } - internal func entityDescriptionForEntityClass(entity: NSManagedObject.Type) -> NSEntityDescription? { + internal func entityDescriptionForEntityType(entity: NSManagedObject.Type) -> NSEntityDescription? { + + return self.entityDescriptionForEntityClass(entity) + } + + internal func entityDescriptionForEntityClass(entity: AnyClass) -> NSEntityDescription? { if let entityName = self.parentStack?.entityNameForEntityClass(entity) { diff --git a/CoreStore/Logging/CoreStoreLogger.swift b/CoreStore/Logging/CoreStoreLogger.swift index 631b266..fdf2756 100644 --- a/CoreStore/Logging/CoreStoreLogger.swift +++ b/CoreStore/Logging/CoreStoreLogger.swift @@ -88,3 +88,13 @@ internal func typeName(value: T) -> String { return "<\(_stdlib_getDemangledTypeName(value))>" } + +internal func typeName(value: T.Type) -> String { + + return "<\(value)>" +} + +internal func typeName(value: AnyClass) -> String { + + return "<\(value)>" +} diff --git a/CoreStore/Migrating/DataStack+Migration.swift b/CoreStore/Migrating/DataStack+Migration.swift index 871668b..33c4135 100644 --- a/CoreStore/Migrating/DataStack+Migration.swift +++ b/CoreStore/Migrating/DataStack+Migration.swift @@ -32,55 +32,38 @@ import GCDKit public extension DataStack { - // MARK: Public - /** - Initializes a `DataStack` from the specified model name and a version-specific model name. - - :param: rootModelName the name of the (.xcdatamodeld) model file - :param: versionModelName the name of the version-specific (.xcdatamodeld) model file - */ - public convenience init(rootModelName: String, versionModelName: String) { - - let modelVersionURL: NSURL! = NSBundle.mainBundle().URLForResource( - rootModelName.stringByAppendingPathExtension("momd")!.stringByAppendingPathComponent(versionModelName), - withExtension: "mom" - ) - CoreStore.assert(modelVersionURL != nil, "Could not find a \"mom\" resource from the main bundle.") - - let managedObjectModel: NSManagedObjectModel! = NSManagedObjectModel(contentsOfURL: modelVersionURL) - CoreStore.assert(managedObjectModel != nil, "Could not create an <\(NSManagedObjectModel.self)> from the resource at URL \"\(modelVersionURL)\".") - - self.init(managedObjectModel: managedObjectModel) - } - - /** - Checks if the store at the specified filename and configuration needs to be migrated to the `DataStack`'s managed object model version. + Checks if the store with the specified filename and configuration needs to be migrated to the `DataStack`'s managed object model version. :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. + :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. + :param: mappingModelBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. + :return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed. */ - public func needsMigrationForSQLiteStore(fileName: String, configuration: String? = nil) -> Bool? { + public func needsMigrationForSQLiteStore(fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as! [NSBundle]) -> MigrationType? { return needsMigrationForSQLiteStore( fileURL: applicationSupportDirectory.URLByAppendingPathComponent( fileName, isDirectory: false ), - configuration: configuration + configuration: configuration, + sourceBundles: sourceBundles ) } /** Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version. - :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. + :param: fileURL the local file URL for the SQLite persistent store. + :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. + :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. + :return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed. */ - public func needsMigrationForSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil) -> Bool? { + public func needsMigrationForSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as! [NSBundle]) -> MigrationType? { var error: NSError? - let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( + let metadata: [NSObject : AnyObject]! = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( NSSQLiteStoreType, URL: fileURL, error: &error @@ -89,35 +72,74 @@ public extension DataStack { CoreStore.handleError( error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".") + "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\"." + ) return nil } - return !self.coordinator.managedObjectModel.isConfiguration( + let coordinator = self.coordinator; + let destinationModel = coordinator.managedObjectModel + if destinationModel.isConfiguration( configuration, - compatibleWithStoreMetadata: metadata - ) + compatibleWithStoreMetadata: metadata) { + + return .None + } + + let sourceModel = NSManagedObjectModel( + byMergingModels: [destinationModel], + forStoreMetadata: metadata + )! + + if NSMappingModel( + fromBundles: mappingModelBundles, + forSourceModel: sourceModel, + destinationModel: destinationModel) != nil { + + return .Heavyweight + } + + if NSMappingModel.inferredMappingModelForSourceModel( + sourceModel, + destinationModel: destinationModel, + error: nil) != nil { + + return .Lightweight + } + + return nil } /** - EXPERIMENTAL + Migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack. + + :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. + :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. + :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. + :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. */ - private func upgradeSQLiteStoreIfNeeded(fileName: String, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { + public func upgradeSQLiteStoreIfNeeded(fileName: String, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) -> MigrationType? { - self.upgradeSQLiteStoreIfNeeded( + return self.upgradeSQLiteStoreIfNeeded( fileURL: applicationSupportDirectory.URLByAppendingPathComponent( fileName, isDirectory: false ), configuration: configuration, + sourceBundles: sourceBundles, completion: completion ) } /** - EXPERIMENTAL + Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack. + + :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. + :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. + :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. + :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. */ - private func upgradeSQLiteStoreIfNeeded(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { + public func upgradeSQLiteStoreIfNeeded(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) -> MigrationType? { var metadataError: NSError? let metadata: [NSObject: AnyObject]! = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( @@ -127,106 +149,79 @@ public extension DataStack { ) if metadata == nil { + let error = metadataError ?? NSError(coreStoreErrorCode: .UnknownError) CoreStore.handleError( - metadataError ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\".") + error, + "Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\"." + ) GCDQueue.Main.async { - // TODO: inspect valid errors for metadataForPersistentStoreOfType() - completion(PersistentStoreResult(.PersistentStoreNotFound)) + completion(MigrationResult(error)) } - return + return nil } let coordinator = self.coordinator; - if let store = coordinator.persistentStoreForURL(fileURL) { - - let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false) - - if store.type == NSSQLiteStoreType - && isExistingStoreAutomigrating - && store.configurationName == (configuration ?? Into.defaultConfigurationName) { - - GCDQueue.Main.async { - - completion(PersistentStoreResult(store)) - } - return - } - - CoreStore.handleError( - NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL), - "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists.") - - GCDQueue.Main.async { - - completion(PersistentStoreResult(.DifferentPersistentStoreExistsAtURL)) - } - return - } - - let managedObjectModel = self.coordinator.managedObjectModel - let migrationManager = NSMigrationManager( - sourceModel: NSManagedObjectModel( - byMergingModels: [managedObjectModel], - forStoreMetadata: metadata! - )!, - destinationModel: managedObjectModel - ) - - var mappingModel: NSMappingModel! = NSMappingModel( - fromBundles: nil, // TODO: parametize - forSourceModel: migrationManager.sourceModel, - destinationModel: migrationManager.destinationModel - ) - var modelError: NSError? - if mappingModel == nil { - - mappingModel = NSMappingModel.inferredMappingModelForSourceModel( - migrationManager.sourceModel, - destinationModel: migrationManager.destinationModel, - error: &modelError - ) - } - if mappingModel == nil { - - CoreStore.handleError( - NSError(coreStoreErrorCode: .UnknownError), - "Failed to load an <\(NSMappingModel.self)> for migration from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\".") - - GCDQueue.Main.async { - - completion(PersistentStoreResult(.MappingModelNotFound)) - } - return - } - - let temporaryFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)!.URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString) - - var migrationError: NSError? - if !migrationManager.migrateStoreFromURL( - fileURL, - type: NSSQLiteStoreType, - options: nil, - withMappingModel: mappingModel, - toDestinationURL: temporaryFileURL, - destinationType: NSSQLiteStoreType, - destinationOptions: nil, - error: &migrationError - ) { - - CoreStore.handleError( - migrationError ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to prepare for migration from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\".") + let destinationModel = coordinator.managedObjectModel + if destinationModel.isConfiguration( + configuration, + compatibleWithStoreMetadata: metadata) { GCDQueue.Main.async { - completion(PersistentStoreResult(.MigrationFailed)) + completion(MigrationResult(.None)) } - return + return .None } + let sourceModel = NSManagedObjectModel( + byMergingModels: [destinationModel], + forStoreMetadata: metadata + )! + + if let mappingModel = NSMappingModel( + fromBundles: sourceBundles, + forSourceModel: sourceModel, + destinationModel: destinationModel) { + + self.startMigrationForSQLiteStore( + fileURL, + sourceModel: sourceModel, + destinationModel: destinationModel, + mappingModel: mappingModel, + migrationType: .Heavyweight, + completion: completion + ) + return .Heavyweight + } + + if let mappingModel = NSMappingModel.inferredMappingModelForSourceModel( + sourceModel, + destinationModel: destinationModel, + error: nil) { + + self.startMigrationForSQLiteStore( + fileURL, + sourceModel: sourceModel, + destinationModel: destinationModel, + mappingModel: mappingModel, + migrationType: .Lightweight, + completion: completion + ) + return .Lightweight + } + + CoreStore.handleError( + NSError(coreStoreErrorCode: .UnknownError), + "Failed to load an <\(NSMappingModel.self)> for migration from version model \"\(sourceModel)\" to version model \"\(destinationModel)\"." + ) + + GCDQueue.Main.async { + + completion(MigrationResult(.MappingModelNotFound)) + } + return nil } /** @@ -236,7 +231,7 @@ public extension DataStack { :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. :param: completion the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. */ - public func addSQLiteStore(fileName: String, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { + public func addSQLiteStore(fileName: String, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) { self.addSQLiteStore( fileURL: applicationSupportDirectory.URLByAppendingPathComponent( @@ -244,6 +239,7 @@ public extension DataStack { isDirectory: false ), configuration: configuration, + sourceBundles: sourceBundles, completion: completion ) } @@ -255,25 +251,29 @@ public extension DataStack { :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. :param: completion the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. */ - public func addSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { + public func addSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) { - var error: NSError? - let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( - NSSQLiteStoreType, - URL: fileURL, - error: &error - ) - if metadata == nil { + if NSFileManager.defaultManager().fileExistsAtPath(fileURL.path!) { - CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\".") - - GCDQueue.Main.async { + var error: NSError? + let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( + NSSQLiteStoreType, + URL: fileURL, + error: &error + ) + if metadata == nil { - completion(PersistentStoreResult(.UnknownError)) + CoreStore.handleError( + error ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\"." + ) + + GCDQueue.Main.async { + + completion(PersistentStoreResult(.UnknownError)) + } + return } - return } let coordinator = self.coordinator; @@ -294,7 +294,8 @@ public extension DataStack { CoreStore.handleError( NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL), - "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists.") + "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists." + ) GCDQueue.Main.async { @@ -313,7 +314,9 @@ public extension DataStack { CoreStore.handleError( directoryError ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to create directory for SQLite store at \"\(fileURL)\".") + "Failed to create directory for SQLite store at \"\(fileURL)\"." + ) + GCDQueue.Main.async { completion(PersistentStoreResult(directoryError!)) @@ -347,11 +350,105 @@ public extension DataStack { CoreStore.handleError( persistentStoreError ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".") + "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\"." + ) completion(PersistentStoreResult(.UnknownError)) } } } } + + + // MARK: Private + + private func startMigrationForSQLiteStore(fileURL: NSURL, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType, completion: (MigrationResult) -> Void) { + + let migrationManager = NSMigrationManager( + sourceModel: sourceModel, + destinationModel: destinationModel + ) + + self.migrationQueue.async { + + var lastReportedProgress: Float = -1 + let timer = GCDTimer.createSuspended( + .Main, + interval: 0.1, + eventHandler: { (timer) -> Void in + + let progress = migrationManager.migrationProgress + if progress > lastReportedProgress { + + // TODO: progress + CoreStore.log(.Trace, message: "migration progress: \(progress)") + lastReportedProgress = progress + } + } + ) + + let temporaryFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)!.URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString) + + var migrationError: NSError? + let migrationCompleted = migrationManager.migrateStoreFromURL( + fileURL, + type: NSSQLiteStoreType, + options: nil, + withMappingModel: mappingModel, + toDestinationURL: temporaryFileURL, + destinationType: NSSQLiteStoreType, + destinationOptions: nil, + error: &migrationError + ) + + timer.suspend() + + let fileManager = NSFileManager.defaultManager() + if !migrationCompleted { + + fileManager.removeItemAtURL(temporaryFileURL, error: nil) + + let error = migrationError ?? NSError(coreStoreErrorCode: .UnknownError) + CoreStore.handleError( + error, + "Failed to migrate from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"." + ) + + GCDQueue.Main.async { + + completion(MigrationResult(error)) + } + return + } + + var replaceError: NSError? + if !fileManager.replaceItemAtURL( + fileURL, + withItemAtURL: temporaryFileURL, + backupItemName: nil, + options: .allZeros, + resultingItemURL: nil, + error: &replaceError) { + + fileManager.removeItemAtURL(temporaryFileURL, error: nil) + + let error = replaceError ?? NSError(coreStoreErrorCode: .UnknownError) + CoreStore.handleError( + error, + "Failed to save store after migrating from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"." + ) + + GCDQueue.Main.async { + + completion(MigrationResult(error)) + } + return + } + + GCDQueue.Main.async { + + completion(MigrationResult(migrationType)) + } + } + } } diff --git a/CoreStore/Migrating/MigrationChain.swift b/CoreStore/Migrating/MigrationChain.swift new file mode 100644 index 0000000..c6a1877 --- /dev/null +++ b/CoreStore/Migrating/MigrationChain.swift @@ -0,0 +1,129 @@ +// +// MigrationChain.swift +// CoreStore +// +// 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: - MigrationChain + +public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, DictionaryLiteralConvertible, ArrayLiteralConvertible { + + // MARK: Public + + public func contains(version: String) -> Bool { + + return self.latestVersion == version + || find(self.versionTree.keys, version) != nil + } + + + // MARK: NilLiteralConvertible + + public init(nilLiteral: ()) { + + self.latestVersion = nil + self.versionTree = [:] + } + + + // MARK: StringLiteralConvertible + + public init(stringLiteral value: String) { + + self.latestVersion = value + self.versionTree = [:] + } + + // MARK: ExtendedGraphemeClusterLiteralConvertible + + public init(extendedGraphemeClusterLiteral value: String) { + + self.latestVersion = value + self.versionTree = [:] + } + + + // MARK: UnicodeScalarLiteralConvertible + + public init(unicodeScalarLiteral value: String) { + + self.latestVersion = value + self.versionTree = [:] + } + + + // MARK: DictionaryLiteralConvertible + + public init(dictionaryLiteral elements: (String, String)...) { + + let versionTree = elements.reduce([String: String]()) { (var versionTree, tuple: (String, String)) -> [String: String] in + + versionTree[tuple.0] = tuple.1 + return versionTree + } + let latestVersions = elements.filter { (tuple: (String, String)) -> Bool in + + return versionTree[tuple.1] == nil + } + + CoreStore.assert(latestVersions.count == 1, "\(typeName(MigrationChain))'s migration chain could not be resolved due to multiple leaf versions.") + + self.latestVersion = latestVersions.first?.1 + self.versionTree = versionTree + } + + + // MARK: ArrayLiteralConvertible + + public init(arrayLiteral elements: String...) { + + CoreStore.assert(Set(elements).count == elements.count, "\(typeName(MigrationChain))'s migration chain could not be created due to duplicate version strings.") + + var lastVersion: String? + var versionTree = [String: String]() + for version in elements { + + if let lastVersion = lastVersion { + + versionTree[lastVersion] = version + } + lastVersion = version + } + + self.versionTree = versionTree + self.latestVersion = elements.last + } + + + // MARK: Internal + + internal let latestVersion: String? + + + // MARK: Private + + private let versionTree: [String: String] +} diff --git a/CoreStore/Migrating/MigrationResult.swift b/CoreStore/Migrating/MigrationResult.swift new file mode 100644 index 0000000..1454e5f --- /dev/null +++ b/CoreStore/Migrating/MigrationResult.swift @@ -0,0 +1,151 @@ +// +// MigrationResult.swift +// CoreStore +// +// Copyright (c) 2014 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: - MigrationType + +/** +The `MigrationType` specifies the type of migration required for a store. +*/ +public enum MigrationType: BooleanType { + + // MARK: Public + + /** + Indicates that the persistent store matches the latest model version and no migration is needed + */ + case None + + /** + Indicates that the persistent store does not match the latest model version but Core Data can infer the mapping model, so a lightweight migration is needed + */ + case Lightweight + + /** + Indicates that the persistent store does not match the latest model version and Core Data could not infer a mapping model, so a custom migration is needed + */ + case Heavyweight + + + // MARK: BooleanType + + public var boolValue: Bool { + + switch self { + + case .None: return false + case .Lightweight: return true + case .Heavyweight: return true + } + } +} + + +// MARK: - MigrationResult + +/** +The `MigrationResult` indicates the result of a migration. +The `MigrationResult` can be treated as a boolean: + + CoreStore.upgradeSQLiteStoreIfNeeded { transaction in + // ... + let result = transaction.commit() + if result { + // succeeded + } + else { + // failed + } + } + +or as an `enum`, where the resulting associated object can also be inspected: + + CoreStore.beginAsynchronous { transaction in + // ... + let result = transaction.commit() + switch result { + case .Success(let hasChanges): + // hasChanges indicates if there were changes or not + case .Failure(let error): + // error is the NSError instance for the failure + } + } +``` +*/ +public enum MigrationResult { + + // MARK: Public + + /** + `MigrationResult.Success` indicates that the `commit()` for the transaction succeeded, either because the save succeeded or because there were no changes to save. The associated value `hasChanges` indicates if there were saved changes or not. + */ + case Success(MigrationType) + + /** + `SaveResult.Failure` indicates that the `commit()` for the transaction failed. The associated object for this value is the related `NSError` instance. + */ + case Failure(NSError) + + + // MARK: Internal + + internal init(_ migrationType: MigrationType) { + + self = .Success(migrationType) + } + + internal init(_ error: NSError) { + + self = .Failure(error) + } + + internal init(_ errorCode: CoreStoreErrorCode) { + + self.init(errorCode, userInfo: nil) + } + + internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) { + + self.init(NSError( + coreStoreErrorCode: errorCode, + userInfo: userInfo)) + } +} + + +// MARK: - MigrationResult: BooleanType + +extension MigrationResult: BooleanType { + + public var boolValue: Bool { + + switch self { + case .Success: return true + case .Failure: return false + } + } +} diff --git a/CoreStore/NSError+CoreStore.swift b/CoreStore/NSError+CoreStore.swift index 20f6049..6c1ae13 100644 --- a/CoreStore/NSError+CoreStore.swift +++ b/CoreStore/NSError+CoreStore.swift @@ -54,11 +54,6 @@ public enum CoreStoreErrorCode: Int { An `NSMappingModel` could not be found for a specific source and destination model versions. */ case MappingModelNotFound - - /** - An `NSMigrationManager` prepared to migrate the store. - */ - case MigrationFailed } diff --git a/CoreStore/Observing/CoreStore+Observing.swift b/CoreStore/Observing/CoreStore+Observing.swift index ed733db..9073768 100644 --- a/CoreStore/Observing/CoreStore+Observing.swift +++ b/CoreStore/Observing/CoreStore+Observing.swift @@ -34,63 +34,63 @@ public extension CoreStore { // MARK: Public /** - Using the `defaultStack`, creates a `ManagedObjectController` for the specified `NSManagedObject`. Multiple `ManagedObjectObserver`'s may then register themselves to be notified when changes are made to the `NSManagedObject`. + Using the `defaultStack`, creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`. :param: object the `NSManagedObject` to observe changes from - :returns: a `ManagedObjectController` that monitors changes to `object` + :returns: a `ObjectMonitor` that monitors changes to `object` */ - public static func observeObject(object: T) -> ManagedObjectController { + public static func monitorObject(object: T) -> ObjectMonitor { - return self.defaultStack.observeObject(object) + return self.defaultStack.monitorObject(object) } /** - Using the `defaultStack`, creates a `ManagedObjectListController` for a list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list. + Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ManagedObjectListController` instance that monitors changes to the list + :returns: a `ListMonitor` instance that monitors changes to the list */ - public static func observeObjectList(from: From, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ManagedObjectListController { + public static func monitorList(from: From, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ListMonitor { - return self.defaultStack.observeObjectList(from, queryClauses) + return self.defaultStack.monitorList(from, queryClauses) } /** - Using the `defaultStack`, creates a `ManagedObjectListController` for a list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list. + Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ManagedObjectListController` instance that monitors changes to the list + :returns: a `ListMonitor` instance that monitors changes to the list */ - public static func observeObjectList(from: From, _ groupBy: GroupBy? = nil, _ queryClauses: [FetchClause]) -> ManagedObjectListController { + public static func monitorList(from: From, _ groupBy: GroupBy? = nil, _ queryClauses: [FetchClause]) -> ListMonitor { - return self.defaultStack.observeObjectList(from, queryClauses) + return self.defaultStack.monitorList(from, queryClauses) } /** - Using the `defaultStack`, creates a `ManagedObjectListController` for a sectioned list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list. + Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. :param: from a `From` clause indicating the entity type - :param: sectionedBy a `SectionedBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + :param: sectionBy a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ManagedObjectListController` instance that monitors changes to the list + :returns: a `ListMonitor` instance that monitors changes to the list */ - public static func observeSectionedList(from: From, _ sectionedBy: SectionedBy, _ fetchClauses: FetchClause...) -> ManagedObjectListController { + public static func monitorSectionedList(from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { - return self.defaultStack.observeSectionedList(from, sectionedBy, fetchClauses) + return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses) } /** - Using the `defaultStack`, creates a `ManagedObjectListController` for a sectioned list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list. + Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. :param: from a `From` clause indicating the entity type - :param: sectionedBy a `SectionedBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + :param: sectionBy a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ManagedObjectListController` instance that monitors changes to the list + :returns: a `ListMonitor` instance that monitors changes to the list */ - public static func observeSectionedList(from: From, _ sectionedBy: SectionedBy, _ fetchClauses: [FetchClause]) -> ManagedObjectListController { + public static func monitorSectionedList(from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { - return self.defaultStack.observeSectionedList(from, sectionedBy, fetchClauses) + return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses) } } diff --git a/CoreStore/Observing/DataStack+Observing.swift b/CoreStore/Observing/DataStack+Observing.swift index 346cd00..75ab7d8 100644 --- a/CoreStore/Observing/DataStack+Observing.swift +++ b/CoreStore/Observing/DataStack+Observing.swift @@ -35,81 +35,81 @@ public extension DataStack { // MARK: Public /** - Creates a `ManagedObjectController` for the specified `NSManagedObject`. Multiple `ManagedObjectObserver`'s may then register themselves to be notified when changes are made to the `NSManagedObject`. + Creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`. :param: object the `NSManagedObject` to observe changes from - :returns: a `ManagedObjectController` that monitors changes to `object` + :returns: a `ObjectMonitor` that monitors changes to `object` */ - public func observeObject(object: T) -> ManagedObjectController { + public func monitorObject(object: T) -> ObjectMonitor { CoreStore.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.") - return ManagedObjectController( + return ObjectMonitor( dataStack: self, object: object ) } /** - Creates a `ManagedObjectListController` for a list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list. + Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ManagedObjectListController` instance that monitors changes to the list + :returns: a `ListMonitor` instance that monitors changes to the list */ - public func observeObjectList(from: From, _ fetchClauses: FetchClause...) -> ManagedObjectListController { + public func monitorList(from: From, _ fetchClauses: FetchClause...) -> ListMonitor { - return self.observeObjectList(from, fetchClauses) + return self.monitorList(from, fetchClauses) } /** - Creates a `ManagedObjectListController` for a list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list. + Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. :param: from a `From` clause indicating the entity type :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ManagedObjectListController` instance that monitors changes to the list + :returns: a `ListMonitor` instance that monitors changes to the list */ - public func observeObjectList(from: From, _ fetchClauses: [FetchClause]) -> ManagedObjectListController { + public func monitorList(from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.") - return ManagedObjectListController( + return ListMonitor( dataStack: self, from: from, - sectionedBy: nil, + sectionBy: nil, fetchClauses: fetchClauses ) } /** - Creates a `ManagedObjectListController` for a sectioned list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list. + Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. :param: from a `From` clause indicating the entity type - :param: sectionedBy a `SectionedBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + :param: sectionBy a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ManagedObjectListController` instance that monitors changes to the list + :returns: a `ListMonitor` instance that monitors changes to the list */ - public func observeSectionedList(from: From, _ sectionedBy: SectionedBy, _ fetchClauses: FetchClause...) -> ManagedObjectListController { + public func monitorSectionedList(from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { - return self.observeSectionedList(from, sectionedBy, fetchClauses) + return self.monitorSectionedList(from, sectionBy, fetchClauses) } /** - Creates a `ManagedObjectListController` for a sectioned list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list. + Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. :param: from a `From` clause indicating the entity type - :param: sectionedBy a `SectionedBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + :param: sectionBy a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ManagedObjectListController` instance that monitors changes to the list + :returns: a `ListMonitor` instance that monitors changes to the list */ - public func observeSectionedList(from: From, _ sectionedBy: SectionedBy, _ fetchClauses: [FetchClause]) -> ManagedObjectListController { + public func monitorSectionedList(from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { CoreStore.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.") - return ManagedObjectListController( + return ListMonitor( dataStack: self, from: from, - sectionedBy: sectionedBy, + sectionBy: sectionBy, fetchClauses: fetchClauses ) } diff --git a/CoreStore/Observing/ManagedObjectListController.swift b/CoreStore/Observing/ListMonitor.swift similarity index 66% rename from CoreStore/Observing/ManagedObjectListController.swift rename to CoreStore/Observing/ListMonitor.swift index d067756..d74a315 100644 --- a/CoreStore/Observing/ManagedObjectListController.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -1,5 +1,5 @@ // -// ManagedObjectListController.swift +// ListMonitor.swift // CoreStore // // Copyright (c) 2015 John Rommel Estropia @@ -28,96 +28,51 @@ import CoreData import GCDKit -// MARK: - SectionedBy +// MARK: - ListMonitor /** -The `SectionedBy` clause indicates the key path to use to group the `ManagedObjectListController` objects into sections. An optional closure can also be provided to transform the value into an appropriate section name: +The `ListMonitor` monitors changes to a list of `NSManagedObject` instances. Observers that implement the `ListObserver` protocol may then register themselves to the `ListMonitor`'s `addObserver(_:)` method: - let listController = CoreStore.observeSectionedList( - From(MyPersonEntity), - SectionedBy("age") { "Age \($0)" }, - OrderBy(.Ascending("lastName")) - ) -*/ -public struct SectionedBy { - - // MARK: Public - - /** - Initializes a `SectionedBy` clause with the key path to use to group `ManagedObjectListController` objects into sections - - :param: sectionKeyPath the key path to use to group the objects into sections - */ - public init(_ sectionKeyPath: KeyPath) { - - self.init(sectionKeyPath, { $0 }) - } - - /** - Initializes a `SectionedBy` clause with the key path to use to group `ManagedObjectListController` objects into sections, and a closure to transform the value for the key path to an appropriate section name - - :param: sectionKeyPath the key path to use to group the objects into sections - :param: sectionIndexTransformer a closure to transform the value for the key path to an appropriate section name - */ - public init(_ sectionKeyPath: KeyPath, _ sectionIndexTransformer: (sectionName: String?) -> String?) { - - self.sectionKeyPath = sectionKeyPath - self.sectionIndexTransformer = sectionIndexTransformer - } - - - // MARK: Internal - - internal let sectionKeyPath: KeyPath - internal let sectionIndexTransformer: (sectionName: KeyPath?) -> String? -} - - -// MARK: - ManagedObjectListController - -/** -The `ManagedObjectListController` monitors changes to a list of `NSManagedObject` instances. Observers that implement the `ManagedObjectListChangeObserver` protocol may then register themselves to the `ManagedObjectListController`'s `addObserver(_:)` method: - - let listController = CoreStore.observeObjectList( + let monitor = CoreStore.monitorList( From(MyPersonEntity), Where("title", isEqualTo: "Engineer"), OrderBy(.Ascending("lastName")) ) - listController.addObserver(self) + monitor.addObserver(self) -The `ManagedObjectListController` instance needs to be held on (retained) for as long as the list needs to be observed. -Observers registered via `addObserver(_:)` are not retained. `ManagedObjectListController` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles. +The `ListMonitor` instance needs to be held on (retained) for as long as the list needs to be observed. +Observers registered via `addObserver(_:)` are not retained. `ListMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles. -Lists created with `observeObjectList(...)` keep a single-section list of objects, where each object can be accessed by index: +Lists created with `monitorList(...)` keep a single-section list of objects, where each object can be accessed by index: - let firstPerson: MyPersonEntity = listController[0] + let firstPerson: MyPersonEntity = monitor[0] Accessing the list with an index above the valid range will throw an exception. -Creating a sectioned-list is also possible with the `observeSectionedList(...)` method: +Creating a sectioned-list is also possible with the `monitorSectionedList(...)` method: - let listController = CoreStore.observeSectionedList( + let monitor = CoreStore.monitorSectionedList( From(MyPersonEntity), - SectionedBy("age") { "Age \($0)" }, + SectionBy("age") { "Age \($0)" }, Where("title", isEqualTo: "Engineer"), OrderBy(.Ascending("lastName")) ) - listController.addObserver(self) + monitor.addObserver(self) -Objects from `ManagedObjectListController`'s created this way can be accessed either by an `NSIndexPath` or a tuple: +Objects from `ListMonitor`s created this way can be accessed either by an `NSIndexPath` or a tuple: let indexPath = NSIndexPath(forItem: 3, inSection: 2) - let person1 = listController[indexPath] - let person2 = listController[2, 3] + let person1 = monitor[indexPath] + let person2 = monitor[2, 3] In the example above, both `person1` and `person2` will contain the object at section=2, index=3. */ -public final class ManagedObjectListController { +public final class ListMonitor { // MARK: Public /** - Accesses the object at the given index within the first section. This subscript indexer is typically used for `ManagedObjectListController`'s created with `addObserver(_:)`. + Accesses the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `addObserver(_:)`. :param: index the index of the object. Using an index above the valid range will throw an exception. */ @@ -127,7 +82,7 @@ public final class ManagedObjectListController { } /** - Accesses the object at the given `NSIndexPath`. This subscript indexer is typically used for `ManagedObjectListController`'s created with `observeSectionedList(_:)`. + Accesses the object at the given `NSIndexPath`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. :param: indexPath the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will throw an exception. */ @@ -137,7 +92,7 @@ public final class ManagedObjectListController { } /** - Accesses the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ManagedObjectListController`'s created with `observeSectionedList(_:)`. + Accesses the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. :param: sectionIndex the section index for the object. Using a `sectionIndex` with an invalid range will throw an exception. :param: itemIndex the index for the object within the section. Using an `itemIndex` with an invalid range will throw an exception. @@ -176,17 +131,17 @@ public final class ManagedObjectListController { } /** - Registers a `ManagedObjectListChangeObserver` to be notified when changes to the receiver's list occur. + Registers a `ListObserver` to be notified when changes to the receiver's list occur. - To prevent retain-cycles, `ManagedObjectListController` only keeps `weak` references to its observers. + To prevent retain-cycles, `ListMonitor` only keeps `weak` references to its observers. For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread. - Calling `addObserver(_:)` multiple times on the same observer is safe, as `ManagedObjectListController` unregisters previous notifications to the observer before re-registering them. + Calling `addObserver(_:)` multiple times on the same observer is safe, as `ListMonitor` unregisters previous notifications to the observer before re-registering them. - :param: observer a `ManagedObjectListChangeObserver` to send change notifications to + :param: observer a `ListObserver` to send change notifications to */ - public func addObserver(observer: U) { + public func addObserver(observer: U) { CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") @@ -194,42 +149,42 @@ public final class ManagedObjectListController { self.registerChangeNotification( &NotificationKey.willChangeList, - name: ManagedObjectListControllerWillChangeListNotification, + name: ListMonitorWillChangeListNotification, toObserver: observer, - callback: { [weak observer] (listController) -> Void in + callback: { [weak observer] (monitor) -> Void in if let observer = observer { - observer.managedObjectListWillChange(listController) + observer.listMonitorWillChange(monitor) } } ) self.registerChangeNotification( &NotificationKey.didChangeList, - name: ManagedObjectListControllerDidChangeListNotification, + name: ListMonitorDidChangeListNotification, toObserver: observer, - callback: { [weak observer] (listController) -> Void in + callback: { [weak observer] (monitor) -> Void in if let observer = observer { - observer.managedObjectListDidChange(listController) + observer.listMonitorDidChange(monitor) } } ) } /** - Registers a `ManagedObjectListObjectObserver` to be notified when changes to the receiver's list occur. + Registers a `ListObjectObserver` to be notified when changes to the receiver's list occur. - To prevent retain-cycles, `ManagedObjectListController` only keeps `weak` references to its observers. + To prevent retain-cycles, `ListMonitor` only keeps `weak` references to its observers. For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread. - Calling `addObserver(_:)` multiple times on the same observer is safe, as `ManagedObjectListController` unregisters previous notifications to the observer before re-registering them. + Calling `addObserver(_:)` multiple times on the same observer is safe, as `ListMonitor` unregisters previous notifications to the observer before re-registering them. - :param: observer a `ManagedObjectListObjectObserver` to send change notifications to + :param: observer a `ListObjectObserver` to send change notifications to */ - public func addObserver(observer: U) { + public func addObserver(observer: U) { CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") @@ -237,39 +192,39 @@ public final class ManagedObjectListController { self.registerChangeNotification( &NotificationKey.willChangeList, - name: ManagedObjectListControllerWillChangeListNotification, + name: ListMonitorWillChangeListNotification, toObserver: observer, - callback: { [weak observer] (listController) -> Void in + callback: { [weak observer] (monitor) -> Void in if let observer = observer { - observer.managedObjectListWillChange(listController) + observer.listMonitorWillChange(monitor) } } ) self.registerChangeNotification( &NotificationKey.didChangeList, - name: ManagedObjectListControllerDidChangeListNotification, + name: ListMonitorDidChangeListNotification, toObserver: observer, - callback: { [weak observer] (listController) -> Void in + callback: { [weak observer] (monitor) -> Void in if let observer = observer { - observer.managedObjectListDidChange(listController) + observer.listMonitorDidChange(monitor) } } ) self.registerObjectNotification( &NotificationKey.didInsertObject, - name: ManagedObjectListControllerDidInsertObjectNotification, + name: ListMonitorDidInsertObjectNotification, toObserver: observer, - callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didInsertObject: object, toIndexPath: newIndexPath! ) @@ -278,14 +233,14 @@ public final class ManagedObjectListController { ) self.registerObjectNotification( &NotificationKey.didDeleteObject, - name: ManagedObjectListControllerDidDeleteObjectNotification, + name: ListMonitorDidDeleteObjectNotification, toObserver: observer, - callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didDeleteObject: object, fromIndexPath: indexPath! ) @@ -294,14 +249,14 @@ public final class ManagedObjectListController { ) self.registerObjectNotification( &NotificationKey.didUpdateObject, - name: ManagedObjectListControllerDidUpdateObjectNotification, + name: ListMonitorDidUpdateObjectNotification, toObserver: observer, - callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didUpdateObject: object, atIndexPath: indexPath! ) @@ -310,14 +265,14 @@ public final class ManagedObjectListController { ) self.registerObjectNotification( &NotificationKey.didMoveObject, - name: ManagedObjectListControllerDidMoveObjectNotification, + name: ListMonitorDidMoveObjectNotification, toObserver: observer, - callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didMoveObject: object, fromIndexPath: indexPath!, toIndexPath: newIndexPath! @@ -328,17 +283,17 @@ public final class ManagedObjectListController { } /** - Registers a `ManagedObjectListSectionObserver` to be notified when changes to the receiver's list occur. + Registers a `ListSectionObserver` to be notified when changes to the receiver's list occur. - To prevent retain-cycles, `ManagedObjectListController` only keeps `weak` references to its observers. + To prevent retain-cycles, `ListMonitor` only keeps `weak` references to its observers. For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread. - Calling `addObserver(_:)` multiple times on the same observer is safe, as `ManagedObjectListController` unregisters previous notifications to the observer before re-registering them. + Calling `addObserver(_:)` multiple times on the same observer is safe, as `ListMonitor` unregisters previous notifications to the observer before re-registering them. - :param: observer a `ManagedObjectListSectionObserver` to send change notifications to + :param: observer a `ListSectionObserver` to send change notifications to */ - public func addObserver(observer: U) { + public func addObserver(observer: U) { CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") @@ -346,39 +301,39 @@ public final class ManagedObjectListController { self.registerChangeNotification( &NotificationKey.willChangeList, - name: ManagedObjectListControllerWillChangeListNotification, + name: ListMonitorWillChangeListNotification, toObserver: observer, - callback: { [weak observer] (listController) -> Void in + callback: { [weak observer] (monitor) -> Void in if let observer = observer { - observer.managedObjectListWillChange(listController) + observer.listMonitorWillChange(monitor) } } ) self.registerChangeNotification( &NotificationKey.didChangeList, - name: ManagedObjectListControllerDidChangeListNotification, + name: ListMonitorDidChangeListNotification, toObserver: observer, - callback: { [weak observer] (listController) -> Void in + callback: { [weak observer] (monitor) -> Void in if let observer = observer { - observer.managedObjectListDidChange(listController) + observer.listMonitorDidChange(monitor) } } ) self.registerObjectNotification( &NotificationKey.didInsertObject, - name: ManagedObjectListControllerDidInsertObjectNotification, + name: ListMonitorDidInsertObjectNotification, toObserver: observer, - callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didInsertObject: object, toIndexPath: newIndexPath! ) @@ -387,14 +342,14 @@ public final class ManagedObjectListController { ) self.registerObjectNotification( &NotificationKey.didDeleteObject, - name: ManagedObjectListControllerDidDeleteObjectNotification, + name: ListMonitorDidDeleteObjectNotification, toObserver: observer, - callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didDeleteObject: object, fromIndexPath: indexPath! ) @@ -403,14 +358,14 @@ public final class ManagedObjectListController { ) self.registerObjectNotification( &NotificationKey.didUpdateObject, - name: ManagedObjectListControllerDidUpdateObjectNotification, + name: ListMonitorDidUpdateObjectNotification, toObserver: observer, - callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didUpdateObject: object, atIndexPath: indexPath! ) @@ -419,14 +374,14 @@ public final class ManagedObjectListController { ) self.registerObjectNotification( &NotificationKey.didMoveObject, - name: ManagedObjectListControllerDidMoveObjectNotification, + name: ListMonitorDidMoveObjectNotification, toObserver: observer, - callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in + callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didMoveObject: object, fromIndexPath: indexPath!, toIndexPath: newIndexPath! @@ -437,14 +392,14 @@ public final class ManagedObjectListController { self.registerSectionNotification( &NotificationKey.didInsertSection, - name: ManagedObjectListControllerDidInsertSectionNotification, + name: ListMonitorDidInsertSectionNotification, toObserver: observer, - callback: { [weak observer] (listController, sectionInfo, sectionIndex) -> Void in + callback: { [weak observer] (monitor, sectionInfo, sectionIndex) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didInsertSection: sectionInfo, toSectionIndex: sectionIndex ) @@ -453,14 +408,14 @@ public final class ManagedObjectListController { ) self.registerSectionNotification( &NotificationKey.didDeleteSection, - name: ManagedObjectListControllerDidDeleteSectionNotification, + name: ListMonitorDidDeleteSectionNotification, toObserver: observer, - callback: { [weak observer] (listController, sectionInfo, sectionIndex) -> Void in + callback: { [weak observer] (monitor, sectionInfo, sectionIndex) -> Void in if let observer = observer { - observer.managedObjectList( - listController, + observer.listMonitor( + monitor, didDeleteSection: sectionInfo, fromSectionIndex: sectionIndex ) @@ -470,13 +425,13 @@ public final class ManagedObjectListController { } /** - Unregisters a `ManagedObjectListChangeObserver` from receiving notifications for changes to the receiver's list. + Unregisters a `ListObserver` from receiving notifications for changes to the receiver's list. For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread. - :param: observer a `ManagedObjectListChangeObserver` to unregister notifications to + :param: observer a `ListObserver` to unregister notifications to */ - public func removeObserver(observer: U) { + public func removeObserver(observer: U) { CoreStore.assert(NSThread.isMainThread(), "Attempted to remove an observer of type \(typeName(observer)) outside the main thread.") @@ -496,7 +451,7 @@ public final class ManagedObjectListController { // MARK: Internal - internal init(dataStack: DataStack, from: From, sectionedBy: SectionedBy?, fetchClauses: [FetchClause]) { + internal init(dataStack: DataStack, from: From, sectionBy: SectionBy?, fetchClauses: [FetchClause]) { let context = dataStack.mainContext @@ -514,7 +469,7 @@ public final class ManagedObjectListController { let fetchedResultsController = NSFetchedResultsController( fetchRequest: fetchRequest, managedObjectContext: context, - sectionNameKeyPath: sectionedBy?.sectionKeyPath, + sectionNameKeyPath: sectionBy?.sectionKeyPath, cacheName: nil ) @@ -524,7 +479,7 @@ public final class ManagedObjectListController { self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate self.parentStack = dataStack - if let sectionIndexTransformer = sectionedBy?.sectionIndexTransformer { + if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer { self.sectionIndexTransformer = sectionIndexTransformer } @@ -554,7 +509,7 @@ public final class ManagedObjectListController { private let sectionIndexTransformer: (sectionName: KeyPath?) -> String? private weak var parentStack: DataStack? - private func registerChangeNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController) -> Void) { + private func registerChangeNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (monitor: ListMonitor) -> Void) { setAssociatedRetainedObject( NotificationObserver( @@ -564,7 +519,7 @@ public final class ManagedObjectListController { if let strongSelf = self { - callback(listController: strongSelf) + callback(monitor: strongSelf) } } ), @@ -573,7 +528,7 @@ public final class ManagedObjectListController { ) } - private func registerObjectNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController, object: T, indexPath: NSIndexPath?, newIndexPath: NSIndexPath?) -> Void) { + private func registerObjectNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (monitor: ListMonitor, object: T, indexPath: NSIndexPath?, newIndexPath: NSIndexPath?) -> Void) { setAssociatedRetainedObject( NotificationObserver( @@ -586,7 +541,7 @@ public final class ManagedObjectListController { let object = userInfo[UserInfoKeyObject] as? T { callback( - listController: strongSelf, + monitor: strongSelf, object: object, indexPath: userInfo[UserInfoKeyIndexPath] as? NSIndexPath, newIndexPath: userInfo[UserInfoKeyNewIndexPath] as? NSIndexPath @@ -599,7 +554,7 @@ public final class ManagedObjectListController { ) } - private func registerSectionNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController, sectionInfo: NSFetchedResultsSectionInfo, sectionIndex: Int) -> Void) { + private func registerSectionNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (monitor: ListMonitor, sectionInfo: NSFetchedResultsSectionInfo, sectionIndex: Int) -> Void) { setAssociatedRetainedObject( NotificationObserver( @@ -613,7 +568,7 @@ public final class ManagedObjectListController { let sectionIndex = (userInfo[UserInfoKeySectionIndex] as? NSNumber)?.integerValue { callback( - listController: strongSelf, + monitor: strongSelf, sectionInfo: sectionInfo, sectionIndex: sectionIndex ) @@ -627,9 +582,9 @@ public final class ManagedObjectListController { } -// MARK: - ManagedObjectListController: FetchedResultsControllerHandler +// MARK: - ListMonitor: FetchedResultsControllerHandler -extension ManagedObjectListController: FetchedResultsControllerHandler { +extension ListMonitor: FetchedResultsControllerHandler { // MARK: FetchedResultsControllerHandler @@ -639,7 +594,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler { case .Insert: NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerDidInsertObjectNotification, + ListMonitorDidInsertObjectNotification, object: self, userInfo: [ UserInfoKeyObject: anObject, @@ -649,7 +604,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler { case .Delete: NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerDidDeleteObjectNotification, + ListMonitorDidDeleteObjectNotification, object: self, userInfo: [ UserInfoKeyObject: anObject, @@ -659,7 +614,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler { case .Update: NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerDidUpdateObjectNotification, + ListMonitorDidUpdateObjectNotification, object: self, userInfo: [ UserInfoKeyObject: anObject, @@ -669,7 +624,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler { case .Move: NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerDidMoveObjectNotification, + ListMonitorDidMoveObjectNotification, object: self, userInfo: [ UserInfoKeyObject: anObject, @@ -686,7 +641,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler { case .Insert: NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerDidInsertSectionNotification, + ListMonitorDidInsertSectionNotification, object: self, userInfo: [ UserInfoKeySectionInfo: sectionInfo, @@ -696,7 +651,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler { case .Delete: NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerDidDeleteSectionNotification, + ListMonitorDidDeleteSectionNotification, object: self, userInfo: [ UserInfoKeySectionInfo: sectionInfo, @@ -712,7 +667,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler { private func controllerWillChangeContent(controller: NSFetchedResultsController) { NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerWillChangeListNotification, + ListMonitorWillChangeListNotification, object: self ) } @@ -720,7 +675,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler { private func controllerDidChangeContent(controller: NSFetchedResultsController) { NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerDidChangeListNotification, + ListMonitorDidChangeListNotification, object: self ) } @@ -799,16 +754,16 @@ private final class FetchedResultsControllerDelegate: NSFetchedResultsController } -private let ManagedObjectListControllerWillChangeListNotification = "ManagedObjectListControllerWillChangeListNotification" -private let ManagedObjectListControllerDidChangeListNotification = "ManagedObjectListControllerDidChangeListNotification" +private let ListMonitorWillChangeListNotification = "ListMonitorWillChangeListNotification" +private let ListMonitorDidChangeListNotification = "ListMonitorDidChangeListNotification" -private let ManagedObjectListControllerDidInsertObjectNotification = "ManagedObjectListControllerDidInsertObjectNotification" -private let ManagedObjectListControllerDidDeleteObjectNotification = "ManagedObjectListControllerDidDeleteObjectNotification" -private let ManagedObjectListControllerDidUpdateObjectNotification = "ManagedObjectListControllerDidUpdateObjectNotification" -private let ManagedObjectListControllerDidMoveObjectNotification = "ManagedObjectListControllerDidMoveObjectNotification" +private let ListMonitorDidInsertObjectNotification = "ListMonitorDidInsertObjectNotification" +private let ListMonitorDidDeleteObjectNotification = "ListMonitorDidDeleteObjectNotification" +private let ListMonitorDidUpdateObjectNotification = "ListMonitorDidUpdateObjectNotification" +private let ListMonitorDidMoveObjectNotification = "ListMonitorDidMoveObjectNotification" -private let ManagedObjectListControllerDidInsertSectionNotification = "ManagedObjectListControllerDidInsertSectionNotification" -private let ManagedObjectListControllerDidDeleteSectionNotification = "ManagedObjectListControllerDidDeleteSectionNotification" +private let ListMonitorDidInsertSectionNotification = "ListMonitorDidInsertSectionNotification" +private let ListMonitorDidDeleteSectionNotification = "ListMonitorDidDeleteSectionNotification" private let UserInfoKeyObject = "UserInfoKeyObject" private let UserInfoKeyIndexPath = "UserInfoKeyIndexPath" diff --git a/CoreStore/Observing/ListObserver.swift b/CoreStore/Observing/ListObserver.swift new file mode 100644 index 0000000..dc6b29b --- /dev/null +++ b/CoreStore/Observing/ListObserver.swift @@ -0,0 +1,147 @@ +// +// ListObserver.swift +// CoreStore +// +// 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: - ListObserver + +/** +Implement the `ListObserver` protocol to observe changes to a list of `NSManagedObject`s. `ListObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method: + + let monitor = CoreStore.monitorList( + From(MyPersonEntity), + OrderBy(.Ascending("lastName")) + ) + monitor.addObserver(self) +*/ +public protocol ListObserver: class { + + /** + The `NSManagedObject` type for the observed list + */ + typealias EntityType: NSManagedObject + + /** + Handles processing just before a change to the observed list occurs + + :param: monitor the `ListMonitor` monitoring the list being observed + */ + func listMonitorWillChange(monitor: ListMonitor) + + /** + Handles processing right after a change to the observed list occurs + + :param: monitor the `ListMonitor` monitoring the object being observed + */ + func listMonitorDidChange(monitor: ListMonitor) +} + + +// MARK: - ListObjectObserver + +/** +Implement the `ListObjectObserver` protocol to observe detailed changes to a list's object. `ListObjectObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method: + + let monitor = CoreStore.monitorList( + From(MyPersonEntity), + OrderBy(.Ascending("lastName")) + ) + monitor.addObserver(self) +*/ +public protocol ListObjectObserver: ListObserver { + + /** + Notifies that an object was inserted to the specified `NSIndexPath` in the list + + :param: monitor the `ListMonitor` monitoring the list being observed + :param: object the entity type for the inserted object + :param: indexPath the new `NSIndexPath` for the inserted object + */ + func listMonitor(monitor: ListMonitor, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath) + + /** + Notifies that an object was deleted from the specified `NSIndexPath` in the list + + :param: monitor the `ListMonitor` monitoring the list being observed + :param: object the entity type for the deleted object + :param: indexPath the `NSIndexPath` for the deleted object + */ + func listMonitor(monitor: ListMonitor, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath) + + /** + Notifies that an object at the specified `NSIndexPath` was updated + + :param: monitor the `ListMonitor` monitoring the list being observed + :param: object the entity type for the updated object + :param: indexPath the `NSIndexPath` for the updated object + */ + func listMonitor(monitor: ListMonitor, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath) + + /** + Notifies that an object's index changed + + :param: monitor the `ListMonitor` monitoring the list being observed + :param: object the entity type for the moved object + :param: fromIndexPath the previous `NSIndexPath` for the moved object + :param: toIndexPath the new `NSIndexPath` for the moved object + */ + func listMonitor(monitor: ListMonitor, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) +} + + +// MARK: - ListSectionObserver + +/** +Implement the `ListSectionObserver` protocol to observe changes to a list's section info. `ListSectionObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method: + + let monitor = CoreStore.monitorSectionedList( + From(MyPersonEntity), + SectionBy("age") { "Age \($0)" }, + OrderBy(.Ascending("lastName")) + ) + monitor.addObserver(self) +*/ +public protocol ListSectionObserver: ListObjectObserver { + + /** + Notifies that a section was inserted at the specified index + + :param: monitor the `ListMonitor` monitoring the list being observed + :param: sectionInfo the `NSFetchedResultsSectionInfo` for the inserted section + :param: sectionIndex the new section index for the new section + */ + func listMonitor(monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) + + /** + Notifies that a section was inserted at the specified index + + :param: monitor the `ListMonitor` monitoring the list being observed + :param: sectionInfo the `NSFetchedResultsSectionInfo` for the deleted section + :param: sectionIndex the previous section index for the deleted section + */ + func listMonitor(monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) +} diff --git a/CoreStore/Observing/ManagedObjectListObserver.swift b/CoreStore/Observing/ManagedObjectListObserver.swift deleted file mode 100644 index dd2b87e..0000000 --- a/CoreStore/Observing/ManagedObjectListObserver.swift +++ /dev/null @@ -1,147 +0,0 @@ -// -// ManagedObjectListObserver.swift -// CoreStore -// -// 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: - ManagedObjectListChangeObserver - -/** -Implement the `ManagedObjectListChangeObserver` protocol to observe changes to a list of `NSManagedObject`'s. `ManagedObjectListChangeObserver`'s may register themselves to a `ManagedObjectListController`'s `addObserver(_:)` method: - - let listController = CoreStore.observeObjectList( - From(MyPersonEntity), - OrderBy(.Ascending("lastName")) - ) - listController.addObserver(self) -*/ -public protocol ManagedObjectListChangeObserver: class { - - /** - The `NSManagedObject` type for the observed list - */ - typealias EntityType: NSManagedObject - - /** - Handles processing just before a change to the observed list occurs - - :param: listController the `ManagedObjectListController` monitoring the list being observed - */ - func managedObjectListWillChange(listController: ManagedObjectListController) - - /** - Handles processing right after a change to the observed list occurs - - :param: listController the `ManagedObjectListController` monitoring the object being observed - */ - func managedObjectListDidChange(listController: ManagedObjectListController) -} - - -// MARK: - ManagedObjectListObjectObserver - -/** -Implement the `ManagedObjectListObjectObserver` protocol to observe detailed changes to a list's object. `ManagedObjectListObjectObserver`'s may register themselves to a `ManagedObjectListController`'s `addObserver(_:)` method: - - let listController = CoreStore.observeObjectList( - From(MyPersonEntity), - OrderBy(.Ascending("lastName")) - ) - listController.addObserver(self) -*/ -public protocol ManagedObjectListObjectObserver: ManagedObjectListChangeObserver { - - /** - Notifies that an object was inserted to the specified `NSIndexPath` in the list - - :param: listController the `ManagedObjectListController` monitoring the list being observed - :param: object the entity type for the inserted object - :param: indexPath the new `NSIndexPath` for the inserted object - */ - func managedObjectList(listController: ManagedObjectListController, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath) - - /** - Notifies that an object was deleted from the specified `NSIndexPath` in the list - - :param: listController the `ManagedObjectListController` monitoring the list being observed - :param: object the entity type for the deleted object - :param: indexPath the `NSIndexPath` for the deleted object - */ - func managedObjectList(listController: ManagedObjectListController, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath) - - /** - Notifies that an object at the specified `NSIndexPath` was updated - - :param: listController the `ManagedObjectListController` monitoring the list being observed - :param: object the entity type for the updated object - :param: indexPath the `NSIndexPath` for the updated object - */ - func managedObjectList(listController: ManagedObjectListController, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath) - - /** - Notifies that an object's index changed - - :param: listController the `ManagedObjectListController` monitoring the list being observed - :param: object the entity type for the moved object - :param: fromIndexPath the previous `NSIndexPath` for the moved object - :param: toIndexPath the new `NSIndexPath` for the moved object - */ - func managedObjectList(listController: ManagedObjectListController, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) -} - - -// MARK: - ManagedObjectListSectionObserver - -/** -Implement the `ManagedObjectListSectionObserver` protocol to observe changes to a list's section info. `ManagedObjectListSectionObserver`'s may register themselves to a `ManagedObjectListController`'s `addObserver(_:)` method: - - let listController = CoreStore.observeSectionedList( - From(MyPersonEntity), - SectionedBy("age") { "Age \($0)" }, - OrderBy(.Ascending("lastName")) - ) - listController.addObserver(self) -*/ -public protocol ManagedObjectListSectionObserver: ManagedObjectListObjectObserver { - - /** - Notifies that a section was inserted at the specified index - - :param: listController the `ManagedObjectListController` monitoring the list being observed - :param: sectionInfo the `NSFetchedResultsSectionInfo` for the inserted section - :param: sectionIndex the new section index for the new section - */ - func managedObjectList(listController: ManagedObjectListController, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) - - /** - Notifies that a section was inserted at the specified index - - :param: listController the `ManagedObjectListController` monitoring the list being observed - :param: sectionInfo the `NSFetchedResultsSectionInfo` for the deleted section - :param: sectionIndex the previous section index for the deleted section - */ - func managedObjectList(listController: ManagedObjectListController, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) -} diff --git a/CoreStore/Observing/ManagedObjectController.swift b/CoreStore/Observing/ObjectMonitor.swift similarity index 75% rename from CoreStore/Observing/ManagedObjectController.swift rename to CoreStore/Observing/ObjectMonitor.swift index 261092d..e62fa15 100644 --- a/CoreStore/Observing/ManagedObjectController.swift +++ b/CoreStore/Observing/ObjectMonitor.swift @@ -1,5 +1,5 @@ // -// ManagedObjectController.swift +// ObjectMonitor.swift // CoreStore // // Copyright (c) 2015 John Rommel Estropia @@ -28,9 +28,9 @@ import CoreData import GCDKit -private let ManagedObjectListControllerWillChangeObjectNotification = "ManagedObjectListControllerWillChangeObjectNotification" -private let ManagedObjectListControllerDidDeleteObjectNotification = "ManagedObjectListControllerDidDeleteObjectNotification" -private let ManagedObjectListControllerDidUpdateObjectNotification = "ManagedObjectListControllerDidUpdateObjectNotification" +private let ObjectMonitorWillChangeObjectNotification = "ObjectMonitorWillChangeObjectNotification" +private let ObjectMonitorDidDeleteObjectNotification = "ObjectMonitorDidDeleteObjectNotification" +private let ObjectMonitorDidUpdateObjectNotification = "ObjectMonitorDidUpdateObjectNotification" private let UserInfoKeyObject = "UserInfoKeyObject" @@ -42,19 +42,19 @@ private struct NotificationKey { } -// MARK: - ManagedObjectController +// MARK: - ObjectMonitor /** -The `ManagedObjectController` monitors changes to a single `NSManagedObject` instance. Observers that implement the `ManagedObjectObserver` protocol may then register themselves to the `ManagedObjectController`'s `addObserver(_:)` method: +The `ObjectMonitor` monitors changes to a single `NSManagedObject` instance. Observers that implement the `ObjectObserver` protocol may then register themselves to the `ObjectMonitor`'s `addObserver(_:)` method: - let objectController = CoreStore.observeObject(object) - objectController.addObserver(self) + let monitor = CoreStore.monitorObject(object) + monitor.addObserver(self) -The created `ManagedObjectController` instance needs to be held on (retained) for as long as the object needs to be observed. +The created `ObjectMonitor` instance needs to be held on (retained) for as long as the object needs to be observed. -Observers registered via `addObserver(_:)` are not retained. `ManagedObjectController` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles. +Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles. */ -public final class ManagedObjectController { +public final class ObjectMonitor { // MARK: Public @@ -75,17 +75,17 @@ public final class ManagedObjectController { } /** - Registers a `ManagedObjectObserver` to be notified when changes to the receiver's `object` are made. + Registers an `ObjectObserver` to be notified when changes to the receiver's `object` are made. - To prevent retain-cycles, `ManagedObjectController` only keeps `weak` references to its observers. + To prevent retain-cycles, `ObjectMonitor` only keeps `weak` references to its observers. For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread. - Calling `addObserver(_:)` multiple times on the same observer is safe, as `ManagedObjectController` unregisters previous notifications to the observer before re-registering them. + Calling `addObserver(_:)` multiple times on the same observer is safe, as `ObjectMonitor` unregisters previous notifications to the observer before re-registering them. - :param: observer a `ManagedObjectObserver` to send change notifications to + :param: observer an `ObjectObserver` to send change notifications to */ - public func addObserver(observer: U) { + public func addObserver(observer: U) { CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") @@ -93,33 +93,33 @@ public final class ManagedObjectController { self.registerChangeNotification( &NotificationKey.willChangeObject, - name: ManagedObjectListControllerWillChangeObjectNotification, + name: ObjectMonitorWillChangeObjectNotification, toObserver: observer, - callback: { [weak self, weak observer] (objectController) -> Void in + callback: { [weak self, weak observer] (monitor) -> Void in if let strongSelf = self, let object = strongSelf.object, let observer = observer { - observer.managedObjectWillUpdate(objectController, object: object) + observer.objectMonitor(monitor, willUpdateObject: object) } } ) self.registerObjectNotification( &NotificationKey.didDeleteObject, - name: ManagedObjectListControllerDidDeleteObjectNotification, + name: ObjectMonitorDidDeleteObjectNotification, toObserver: observer, - callback: { [weak self, weak observer] (objectController, object) -> Void in + callback: { [weak self, weak observer] (monitor, object) -> Void in if let strongSelf = self, let observer = observer { - observer.managedObjectWasDeleted(objectController, object: object) + observer.objectMonitor(monitor, didDeleteObject: object) } } ) self.registerObjectNotification( &NotificationKey.didUpdateObject, - name: ManagedObjectListControllerDidUpdateObjectNotification, + name: ObjectMonitorDidUpdateObjectNotification, toObserver: observer, - callback: { [weak self, weak observer] (objectController, object) -> Void in + callback: { [weak self, weak observer] (monitor, object) -> Void in if let strongSelf = self, let observer = observer { @@ -136,9 +136,9 @@ public final class ManagedObjectController { } strongSelf.lastCommittedAttributes = currentCommitedAttributes - observer.managedObjectWasUpdated( - objectController, - object: object, + observer.objectMonitor( + monitor, + didUpdateObject: object, changedPersistentKeys: changedKeys ) } @@ -147,13 +147,13 @@ public final class ManagedObjectController { } /** - Unregisters a `ManagedObjectObserver` from receiving notifications for changes to the receiver's `object`. + Unregisters an `ObjectObserver` from receiving notifications for changes to the receiver's `object`. For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread. - :param: observer a `ManagedObjectObserver` to unregister notifications to + :param: observer an `ObjectObserver` to unregister notifications to */ - public func removeObserver(observer: U) { + public func removeObserver(observer: U) { CoreStore.assert(NSThread.isMainThread(), "Attempted to remove an observer of type \(typeName(observer)) outside the main thread.") @@ -171,7 +171,8 @@ public final class ManagedObjectController { let context = dataStack.mainContext let fetchRequest = NSFetchRequest() - fetchRequest.entity = context.entityDescriptionForEntityClass(T.self) + fetchRequest.entity = object.entity + fetchRequest.fetchLimit = 1 fetchRequest.resultType = .ManagedObjectResultType fetchRequest.sortDescriptors = [] @@ -216,7 +217,7 @@ public final class ManagedObjectController { private var lastCommittedAttributes = [NSString: NSObject]() private weak var parentStack: DataStack? - private func registerChangeNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (objectController: ManagedObjectController) -> Void) { + private func registerChangeNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor) -> Void) { setAssociatedRetainedObject( NotificationObserver( @@ -226,7 +227,7 @@ public final class ManagedObjectController { if let strongSelf = self { - callback(objectController: strongSelf) + callback(monitor: strongSelf) } } ), @@ -235,7 +236,7 @@ public final class ManagedObjectController { ) } - private func registerObjectNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (objectController: ManagedObjectController, object: T) -> Void) { + private func registerObjectNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor, object: T) -> Void) { setAssociatedRetainedObject( NotificationObserver( @@ -248,7 +249,7 @@ public final class ManagedObjectController { let object = userInfo[UserInfoKeyObject] as? T { callback( - objectController: strongSelf, + monitor: strongSelf, object: object ) } @@ -261,9 +262,9 @@ public final class ManagedObjectController { } -// MARK: - ManagedObjectController: FetchedResultsControllerHandler +// MARK: - ObjectMonitor: FetchedResultsControllerHandler -extension ManagedObjectController: FetchedResultsControllerHandler { +extension ObjectMonitor: FetchedResultsControllerHandler { // MARK: FetchedResultsControllerHandler @@ -273,14 +274,14 @@ extension ManagedObjectController: FetchedResultsControllerHandler { case .Delete: NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerDidDeleteObjectNotification, + ObjectMonitorDidDeleteObjectNotification, object: self, userInfo: [UserInfoKeyObject: anObject] ) case .Update: NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerDidUpdateObjectNotification, + ObjectMonitorDidUpdateObjectNotification, object: self, userInfo: [UserInfoKeyObject: anObject] ) @@ -293,7 +294,7 @@ extension ManagedObjectController: FetchedResultsControllerHandler { private func controllerWillChangeContent(controller: NSFetchedResultsController) { NSNotificationCenter.defaultCenter().postNotificationName( - ManagedObjectListControllerWillChangeObjectNotification, + ObjectMonitorWillChangeObjectNotification, object: self ) } diff --git a/CoreStore/Observing/ManagedObjectObserver.swift b/CoreStore/Observing/ObjectObserver.swift similarity index 65% rename from CoreStore/Observing/ManagedObjectObserver.swift rename to CoreStore/Observing/ObjectObserver.swift index ae53068..26de479 100644 --- a/CoreStore/Observing/ManagedObjectObserver.swift +++ b/CoreStore/Observing/ObjectObserver.swift @@ -1,5 +1,5 @@ // -// ManagedObjectObserver.swift +// ObjectObserver.swift // CoreStore // // Copyright (c) 2015 John Rommel Estropia @@ -27,15 +27,15 @@ import Foundation import CoreData -// MARK: - ManagedObjectObserver +// MARK: - ObjectObserver /** -Implement the `ManagedObjectObserver` protocol to observe changes to a single `NSManagedObject` instance. `ManagedObjectObserver`'s may register themselves to a `ManagedObjectController`'s `addObserver(_:)` method: +Implement the `ObjectObserver` protocol to observe changes to a single `NSManagedObject` instance. `ObjectObserver`s may register themselves to a `ObjectMonitor`'s `addObserver(_:)` method: - let objectController = CoreStore.observeObject(object) - objectController.addObserver(self) + let monitor = CoreStore.monitorObject(object) + monitor.addObserver(self) */ -public protocol ManagedObjectObserver: class { +public protocol ObjectObserver: class { /** The `NSManagedObject` type for the observed object @@ -45,25 +45,25 @@ public protocol ManagedObjectObserver: class { /** Handles processing just before a change to the observed `object` occurs - :param: objectController the `ManagedObjectController` monitoring the object being observed + :param: monitor the `ObjectMonitor` monitoring the object being observed :param: object the `NSManagedObject` instance being observed */ - func managedObjectWillUpdate(objectController: ManagedObjectController, object: EntityType) + func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: EntityType) /** Handles processing right after a change to the observed `object` occurs - :param: objectController the `ManagedObjectController` monitoring the object being observed + :param: monitor the `ObjectMonitor` monitoring the object being observed :param: object the `NSManagedObject` instance being observed :param: changedPersistentKeys a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported. */ - func managedObjectWasUpdated(objectController: ManagedObjectController, object: EntityType, changedPersistentKeys: Set) + func objectMonitor(monitor: ObjectMonitor, didUpdateObject object: EntityType, changedPersistentKeys: Set) /** Handles processing right after `object` is deleted - :param: objectController the `ManagedObjectController` monitoring the object being observed + :param: monitor the `ObjectMonitor` monitoring the object being observed :param: object the `NSManagedObject` instance being observed */ - func managedObjectWasDeleted(objectController: ManagedObjectController, object: EntityType) + func objectMonitor(monitor: ObjectMonitor, didDeleteObject object: EntityType) } diff --git a/CoreStore/Observing/SectionBy.swift b/CoreStore/Observing/SectionBy.swift new file mode 100644 index 0000000..a203c1d --- /dev/null +++ b/CoreStore/Observing/SectionBy.swift @@ -0,0 +1,72 @@ +// +// SectionBy.swift +// CoreStore +// +// 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: - SectionBy + +/** +The `SectionBy` clause indicates the key path to use to group the `ListMonitor` objects into sections. An optional closure can also be provided to transform the value into an appropriate section name: + + let monitor = CoreStore.monitorSectionedList( + From(MyPersonEntity), + SectionBy("age") { "Age \($0)" }, + OrderBy(.Ascending("lastName")) + ) +*/ +public struct SectionBy { + + // MARK: Public + + /** + Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections + + :param: sectionKeyPath the key path to use to group the objects into sections + */ + public init(_ sectionKeyPath: KeyPath) { + + self.init(sectionKeyPath, { $0 }) + } + + /** + Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name + + :param: sectionKeyPath the key path to use to group the objects into sections + :param: sectionIndexTransformer a closure to transform the value for the key path to an appropriate section name + */ + public init(_ sectionKeyPath: KeyPath, _ sectionIndexTransformer: (sectionName: String?) -> String?) { + + self.sectionKeyPath = sectionKeyPath + self.sectionIndexTransformer = sectionIndexTransformer + } + + + // MARK: Internal + + internal let sectionKeyPath: KeyPath + internal let sectionIndexTransformer: (sectionName: KeyPath?) -> String? +} diff --git a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift index 7bea106..3671ec5 100644 --- a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift @@ -143,7 +143,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { } /** - Deletes the specified `NSManagedObject`'s. + Deletes the specified `NSManagedObject`s. :param: object1 the `NSManagedObject` type to be deleted :param: object2 another `NSManagedObject` type to be deleted @@ -157,9 +157,9 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { } /** - Deletes the specified `NSManagedObject`'s. + Deletes the specified `NSManagedObject`s. - :param: objects the `NSManagedObject`'s type to be deleted + :param: objects the `NSManagedObject`s type to be deleted */ public override func delete(objects: [NSManagedObject?]) { diff --git a/CoreStore/Saving and Processing/BaseDataTransaction.swift b/CoreStore/Saving and Processing/BaseDataTransaction.swift index 055863c..bb5ea0e 100644 --- a/CoreStore/Saving and Processing/BaseDataTransaction.swift +++ b/CoreStore/Saving and Processing/BaseDataTransaction.swift @@ -28,91 +28,6 @@ import CoreData import GCDKit -// MARK: - Into - -/** -A `Into` clause contains the destination entity and destination persistent store for a `create(...)` method. A common usage is to just indicate the entity: - - let person = transaction.create(Into(MyPersonEntity)) - -For cases where multiple `NSPersistentStore`'s contain the same entity, the destination configuration's name needs to be specified as well: - - let person = transaction.create(Into("Configuration1")) - -This helps the `NSManagedObjectContext` to determine which -*/ -public struct Into { - - // MARK: Public - - internal static var defaultConfigurationName: String { - - return "PF_DEFAULT_CONFIGURATION_NAME" - } - - /** - Initializes an `Into` clause. - Sample Usage: - - let person = transaction.create(Into()) - */ - public init(){ - - self.configuration = nil - self.inferStoreIfPossible = true - } - - /** - Initializes an `Into` clause with the specified entity type. - Sample Usage: - - let person = transaction.create(Into(MyPersonEntity)) - - :param: entity the `NSManagedObject` type to be created - */ - public init(_ entity: T.Type) { - - self.configuration = nil - self.inferStoreIfPossible = true - } - - /** - Initializes an `Into` clause with the specified configuration. - Sample Usage: - - let person = transaction.create(Into("Configuration1")) - - :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. - */ - public init(_ configuration: String?) { - - self.configuration = configuration - self.inferStoreIfPossible = false - } - - /** - Initializes an `Into` clause with the specified entity type and configuration. - Sample Usage: - - let person = transaction.create(Into(MyPersonEntity.self, "Configuration1")) - - :param: entity the `NSManagedObject` type to be created - :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. - */ - public init(_ entity: T.Type, _ configuration: String?) { - - self.configuration = configuration - self.inferStoreIfPossible = false - } - - - // MARK: Internal - - internal let configuration: String? - internal let inferStoreIfPossible: Bool -} - - // MARK: - BaseDataTransaction /** @@ -141,25 +56,26 @@ public /*abstract*/ class BaseDataTransaction { CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to create an entity of type <\(T.self)> outside its designated queue.") let context = self.context - let object = T.createInContext(context) + let entityClass = (into.entityClass as! NSManagedObject.Type) + let object = entityClass.createInContext(context) as! T if into.inferStoreIfPossible { - switch context.parentStack!.persistentStoreForEntityClass(T.self, configuration: nil, inferStoreIfPossible: true) { + switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: nil, inferStoreIfPossible: true) { case (.Some(let persistentStore), _): context.assignObject(object, toPersistentStore: persistentStore) case (.None, true): - CoreStore.assert(false, "Attempted to create an entity of type \(typeName(object)) with ambiguous destination persistent store, but the configuration name was not specified.") + CoreStore.assert(false, "Attempted to create an entity of type <\(entityClass)> with ambiguous destination persistent store, but the configuration name was not specified.") default: - CoreStore.assert(false, "Attempted to create an entity of type \(typeName(object)), but a destination persistent store containing the entity type could not be found.") + CoreStore.assert(false, "Attempted to create an entity of type <\(entityClass)>, but a destination persistent store containing the entity type could not be found.") } } else { - switch context.parentStack!.persistentStoreForEntityClass(T.self, configuration: into.configuration, inferStoreIfPossible: false) { + switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: into.configuration, inferStoreIfPossible: false) { case (.Some(let persistentStore), _): context.assignObject(object, toPersistentStore: persistentStore) @@ -167,11 +83,11 @@ public /*abstract*/ class BaseDataTransaction { default: if let configuration = into.configuration { - CoreStore.assert(false, "Attempted to create an entity of type \(typeName(object)) into the configuration \"\(configuration)\", which it doesn't belong to.") + CoreStore.assert(false, "Attempted to create an entity of type <\(entityClass)> into the configuration \"\(configuration)\", which it doesn't belong to.") } else { - CoreStore.assert(false, "Attempted to create an entity of type \(typeName(object)) into the default configuration, which it doesn't belong to.") + CoreStore.assert(false, "Attempted to create an entity of type <\(entityClass)> into the default configuration, which it doesn't belong to.") } } } @@ -204,13 +120,13 @@ public /*abstract*/ class BaseDataTransaction { CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(T.self)> outside its designated queue.") CoreStore.assert(into.inferStoreIfPossible || (into.configuration ?? Into.defaultConfigurationName) == objectID.persistentStore?.configurationName, "Attempted to update an entity of type <\(T.self)> but the specified persistent store do not match the `NSManagedObjectID`.") - return T.inContext(self.context, withObjectID: objectID) + return (into.entityClass as! NSManagedObject.Type).inContext(self.context, withObjectID: objectID) as? T } /** Deletes a specified `NSManagedObject`. - :param: object the `NSManagedObject` type to be deleted + :param: object the `NSManagedObject` to be deleted */ public func delete(object: NSManagedObject?) { @@ -220,11 +136,11 @@ public /*abstract*/ class BaseDataTransaction { } /** - Deletes the specified `NSManagedObject`'s. + Deletes the specified `NSManagedObject`s. - :param: object1 the `NSManagedObject` type to be deleted - :param: object2 another `NSManagedObject` type to be deleted - :param: objects other `NSManagedObject`s type to be deleted + :param: object1 the `NSManagedObject` to be deleted + :param: object2 another `NSManagedObject` to be deleted + :param: objects other `NSManagedObject`s to be deleted */ public func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) { @@ -232,9 +148,9 @@ public /*abstract*/ class BaseDataTransaction { } /** - Deletes the specified `NSManagedObject`'s. + Deletes the specified `NSManagedObject`s. - :param: objects the `NSManagedObject`'s type to be deleted + :param: objects the `NSManagedObject`s to be deleted */ public func delete(objects: [NSManagedObject?]) { diff --git a/CoreStore/Saving and Processing/Into.swift b/CoreStore/Saving and Processing/Into.swift new file mode 100644 index 0000000..f0f916e --- /dev/null +++ b/CoreStore/Saving and Processing/Into.swift @@ -0,0 +1,145 @@ +// +// Into.swift +// CoreStore +// +// Copyright (c) 2014 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: - Into + +/** +A `Into` clause contains the destination entity and destination persistent store for a `create(...)` method. A common usage is to just indicate the entity: + +let person = transaction.create(Into(MyPersonEntity)) + +For cases where multiple `NSPersistentStore`s contain the same entity, the destination configuration's name needs to be specified as well: + +let person = transaction.create(Into("Configuration1")) + +This helps the `NSManagedObjectContext` to determine which +*/ +public struct Into { + + // MARK: Public + + internal static var defaultConfigurationName: String { + + return "PF_DEFAULT_CONFIGURATION_NAME" + } + + /** + Initializes an `Into` clause. + Sample Usage: + + let person = transaction.create(Into()) + */ + public init(){ + + self.configuration = nil + self.inferStoreIfPossible = true + self.entityClass = T.self + } + + /** + Initializes an `Into` clause with the specified entity type. + Sample Usage: + + let person = transaction.create(Into(MyPersonEntity)) + + :param: entity the `NSManagedObject` type to be created + */ + public init(_ entity: T.Type) { + + self.configuration = nil + self.inferStoreIfPossible = true + self.entityClass = entity + } + + /** + Initializes an `Into` clause with the specified entity class. + + :param: entityClass the `NSManagedObject` class type to be created + */ + public init(_ entityClass: AnyClass) { + + self.configuration = nil + self.inferStoreIfPossible = true + self.entityClass = entityClass + } + + /** + Initializes an `Into` clause with the specified configuration. + Sample Usage: + + let person = transaction.create(Into("Configuration1")) + + :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. + */ + public init(_ configuration: String?) { + + self.configuration = configuration + self.inferStoreIfPossible = false + self.entityClass = T.self + } + + /** + Initializes an `Into` clause with the specified entity type and configuration. + Sample Usage: + + let person = transaction.create(Into(MyPersonEntity.self, "Configuration1")) + + :param: entity the `NSManagedObject` type to be created + :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. + */ + public init(_ entity: T.Type, _ configuration: String?) { + + self.configuration = configuration + self.inferStoreIfPossible = false + self.entityClass = entity + } + + /** + Initializes an `Into` clause with the specified entity class and configuration. + Sample Usage: + + let person = transaction.create(Into(MyPersonEntity.self, "Configuration1")) + + :param: entityClass the `NSManagedObject` class type to be created + :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. + */ + public init(_ entityClass: AnyClass, _ configuration: String?) { + + self.configuration = configuration + self.inferStoreIfPossible = false + self.entityClass = entityClass + } + + + // MARK: Internal + + internal let entityClass: AnyClass + internal let configuration: String? + internal let inferStoreIfPossible: Bool +} diff --git a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift index d841a70..47f3fa2 100644 --- a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift @@ -122,11 +122,11 @@ public final class SynchronousDataTransaction: BaseDataTransaction { } /** - Deletes the specified `NSManagedObject`'s. + Deletes the specified `NSManagedObject`s. - :param: object1 the `NSManagedObject` type to be deleted - :param: object2 another `NSManagedObject` type to be deleted - :param: objects other `NSManagedObject`s type to be deleted + :param: object1 the `NSManagedObject` to be deleted + :param: object2 another `NSManagedObject` to be deleted + :param: objects other `NSManagedObject`s to be deleted */ public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) { @@ -136,9 +136,9 @@ public final class SynchronousDataTransaction: BaseDataTransaction { } /** - Deletes the specified `NSManagedObject`'s. + Deletes the specified `NSManagedObject`s. - :param: objects the `NSManagedObject`'s type to be deleted + :param: objects the `NSManagedObject`s to be deleted */ public override func delete(objects: [NSManagedObject?]) { diff --git a/CoreStore/Setting Up/DataStack.swift b/CoreStore/Setting Up/DataStack.swift index 18f25b8..c05d89b 100644 --- a/CoreStore/Setting Up/DataStack.swift +++ b/CoreStore/Setting Up/DataStack.swift @@ -45,42 +45,32 @@ public final class DataStack { // MARK: Public /** - Initializes a `DataStack` from a model created by merging all the models found in all bundles. - */ - public convenience init() { - - let mergedModel: NSManagedObjectModel! = NSManagedObjectModel.mergedModelFromBundles(NSBundle.allBundles()) - CoreStore.assert(mergedModel != nil, "Could not create a merged <\(NSManagedObjectModel.self)> from all bundles.") - - self.init(managedObjectModel: mergedModel) - } + Initializes a `DataStack` from an `NSManagedObjectModel`. - /** - Initializes a `DataStack` from the specified model name. - - :param: modelName the name of the (.xcdatamodeld) model file. + :param: modelName the name of the (.xcdatamodeld) model file. If not specified, the application name will be used + :param: sourceBundle an optional bundle to load models from. If not specified, the main bundle will be used. + :param: modelVersions the `MigrationChain` that indicates the heirarchy of the model's version names. If not specified, will default to a non-migrating data stack. */ - public convenience init(modelName: String) { + public required init(modelName: String = applicationName, sourceBundle: NSBundle = NSBundle.mainBundle(), modelVersions: MigrationChain = nil) { - let modelFilePath: String! = NSBundle.mainBundle().pathForResource(modelName, ofType: "momd") + let modelFilePath: String! = sourceBundle.pathForResource( + modelName, + ofType: "momd" + ) CoreStore.assert(modelFilePath != nil, "Could not find a \"momd\" resource from the main bundle.") let managedObjectModel: NSManagedObjectModel! = NSManagedObjectModel(contentsOfURL: NSURL(fileURLWithPath: modelFilePath)!) - CoreStore.assert(managedObjectModel != nil, "Could not create an <\(NSManagedObjectModel.self)> from the resource at path \"\(modelFilePath)\".") - - self.init(managedObjectModel: managedObjectModel) - } - - /** - Initializes a `DataStack` from an `NSManagedObjectModel`. - - :param: managedObjectModel the `NSManagedObjectModel` of the (.xcdatamodeld) model file. - */ - public required init(managedObjectModel: NSManagedObjectModel) { + CoreStore.assert( + managedObjectModel != nil, + "Could not create an <\(NSManagedObjectModel.self)> from the resource at path \"\(modelFilePath)\"." + ) + // TODO: assert existence of all model versions in the migrationChain self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator) self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext) + self.sourceBundle = sourceBundle + self.modelVersions = modelVersions var entityNameMapping = [EntityClassNameType: EntityNameType]() var entityConfigurationsMapping = [EntityClassNameType: Set]() @@ -279,14 +269,17 @@ public final class DataStack { internal let coordinator: NSPersistentStoreCoordinator internal let rootSavingContext: NSManagedObjectContext internal let mainContext: NSManagedObjectContext + internal let sourceBundle: NSBundle + internal let modelVersions: MigrationChain internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue") + internal let migrationQueue: GCDQueue = .createSerial("com.corestore.datastack.migrationqueue") - internal func entityNameForEntityClass(entityClass: NSManagedObject.Type) -> String? { + internal func entityNameForEntityClass(entityClass: AnyClass) -> String? { return self.entityNameMapping[NSStringFromClass(entityClass)] } - internal func persistentStoresForEntityClass(entityClass: NSManagedObject.Type) -> [NSPersistentStore]? { + internal func persistentStoresForEntityClass(entityClass: AnyClass) -> [NSPersistentStore]? { var returnValue: [NSPersistentStore]? = nil self.storeMetadataUpdateQueue.barrierSync { @@ -300,7 +293,7 @@ public final class DataStack { return returnValue } - internal func persistentStoreForEntityClass(entityClass: NSManagedObject.Type, configuration: String?, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) { + internal func persistentStoreForEntityClass(entityClass: AnyClass, configuration: String?, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) { var returnValue: (store: NSPersistentStore?, isAmbiguous: Bool) = (store: nil, isAmbiguous: false) self.storeMetadataUpdateQueue.barrierSync { diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj index 281bd17..50aef15 100644 --- a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - B503FADF1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADB1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift */; }; + B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */; }; B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */; }; B503FAE11AFDC71700F90881 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADD1AFDC71700F90881 /* Palette.swift */; }; B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */; }; @@ -21,21 +21,28 @@ B54AAD591AF4D26E00848AE0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD571AF4D26E00848AE0 /* Main.storyboard */; }; B54AAD5B1AF4D26E00848AE0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */; }; B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; }; + B56007011B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B56007001B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel */; }; + B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */; }; B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; }; B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; }; B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */; }; B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */; }; B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D91B231BCA0075EE4A /* MaleAccount.swift */; }; B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */; }; - B56964DF1B2321E30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */; }; B569650C1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */; }; B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965171B2E20CC0075EE4A /* TimeZone.swift */; }; B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; }; B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; }; + B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */; }; B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; }; B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; }; B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; + B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25841B36E23C0000406B /* OrganismV1.swift */; }; + B5EE25871B36E2520000406B /* OrganismV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25861B36E2520000406B /* OrganismV2.swift */; }; + B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */; }; + B5EE259B1B3EA4890000406B /* OrganismV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE259A1B3EA4890000406B /* OrganismV3.swift */; }; + B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -85,7 +92,7 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ - B503FADB1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectListObserverDemoViewController.swift; sourceTree = ""; }; + B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListObserverDemoViewController.swift; sourceTree = ""; }; B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserverDemoViewController.swift; sourceTree = ""; }; B503FADD1AFDC71700F90881 /* Palette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palette.swift; sourceTree = ""; }; B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaletteTableViewCell.swift; sourceTree = ""; }; @@ -101,20 +108,28 @@ B54AAD581AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; + B56007001B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = OrganismV2ToV3.xcmappingmodel; sourceTree = ""; }; + B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV2ToV3MigrationPolicy.swift; sourceTree = ""; }; B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = ""; }; B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = ""; }; B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = ""; }; B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = StackSetupDemo.xcdatamodel; sourceTree = ""; }; B56964D91B231BCA0075EE4A /* MaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MaleAccount.swift; sourceTree = ""; }; B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FemaleAccount.swift; sourceTree = ""; }; - B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = ""; }; - B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = ""; }; B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingAndQueryingDemoViewController.swift; sourceTree = ""; }; B56965171B2E20CC0075EE4A /* TimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = ""; }; B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingResultsViewController.swift; sourceTree = ""; }; B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = ""; }; + B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = ""; }; B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreStore.xcodeproj; path = ../CoreStore.xcodeproj; sourceTree = ""; }; B5D9C9181B20AB1900E64F0E /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; + B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = ""; }; + B5EE25841B36E23C0000406B /* OrganismV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV1.swift; sourceTree = ""; }; + B5EE25861B36E2520000406B /* OrganismV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV2.swift; sourceTree = ""; }; + B5EE25881B36E2750000406B /* MigrationDemoV3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV3.xcdatamodel; sourceTree = ""; }; + B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationsDemoViewController.swift; sourceTree = ""; }; + B5EE259A1B3EA4890000406B /* OrganismV3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV3.swift; sourceTree = ""; }; + B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismProtocol.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -136,7 +151,7 @@ isa = PBXGroup; children = ( B52977D81B120B80003D50A5 /* ObserversViewController.swift */, - B503FADB1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift */, + B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */, B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */, B503FADD1AFDC71700F90881 /* Palette.swift */, B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */, @@ -189,13 +204,14 @@ B503FADA1AFDC71700F90881 /* List and Object Observers Demo */, B52977DB1B120F2C003D50A5 /* Transactions Demo */, B56965091B2B35370075EE4A /* Fetching and Querying Demo */, + B569652F1B3591460075EE4A /* Migrations Demo */, B56964C61B20AC200075EE4A /* Loggers Demo */, B54AAD571AF4D26E00848AE0 /* Main.storyboard */, B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */, B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */, B54AAD501AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld */, B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */, - B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */, + B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */, B54AAD4C1AF4D26E00848AE0 /* Supporting Files */, ); path = CoreStoreDemo; @@ -239,6 +255,20 @@ path = "Fetching and Querying Demo"; sourceTree = ""; }; + B569652F1B3591460075EE4A /* Migrations Demo */ = { + isa = PBXGroup; + children = ( + B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */, + B5EE259A1B3EA4890000406B /* OrganismV3.swift */, + B5EE25861B36E2520000406B /* OrganismV2.swift */, + B5EE25841B36E23C0000406B /* OrganismV1.swift */, + B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */, + B56007001B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel */, + B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */, + ); + path = "Migrations Demo"; + sourceTree = ""; + }; B583A9151AF5F4F3001F76AF /* Products */ = { isa = PBXGroup; children = ( @@ -345,24 +375,31 @@ buildActionMask = 2147483647; files = ( B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */, + B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */, + B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */, B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */, - B56964DF1B2321E30075EE4A /* MigrationDemo.xcdatamodeld in Sources */, B52977E41B121635003D50A5 /* Place.swift in Sources */, B569650C1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift in Sources */, B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */, + B5EE25871B36E2520000406B /* OrganismV2.swift in Sources */, B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */, B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */, B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */, + B56007011B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel in Sources */, B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */, B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */, B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */, B54AAD521AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld in Sources */, + B5EE259B1B3EA4890000406B /* OrganismV3.swift in Sources */, B503FAE11AFDC71700F90881 /* Palette.swift in Sources */, B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */, - B503FADF1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift in Sources */, + B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */, + B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */, B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */, B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */, B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */, + B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */, + B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */, B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; @@ -559,13 +596,14 @@ sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; - B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */ = { + B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */ = { isa = XCVersionGroup; children = ( - B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */, - B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */, + B5EE25881B36E2750000406B /* MigrationDemoV3.xcdatamodel */, + B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */, + B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */, ); - currentVersion = B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */; + currentVersion = B5EE25881B36E2750000406B /* MigrationDemoV3.xcdatamodel */; path = MigrationDemo.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout index e76891f..a385032 100644 --- a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout @@ -7,7 +7,7 @@ IDESourceControlProjectIdentifier B6855E48-4B19-4321-B1C7-CB2706E12777 IDESourceControlProjectName - CoreStoreDemo + project IDESourceControlProjectOriginsDictionary 4B60F1BCB491FF717C56441AE7783C74F417BE48 @@ -16,13 +16,13 @@ github.com:JohnEstropia/GCDKit.git IDESourceControlProjectPath - CoreStoreDemo/CoreStoreDemo.xcodeproj + CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace IDESourceControlProjectRelativeInstallPathDictionary 4B60F1BCB491FF717C56441AE7783C74F417BE48 ../../.. 8B2E522D57154DFA93A06982C36315ECBEA4FA97 - ../../../Libraries/GCDKit + ../../..Libraries/GCDKit/ IDESourceControlProjectURL github.com:JohnEstropia/CoreStore.git diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcuserdata/johnestropia.xcuserdatad/WorkspaceSettings.xcsettings b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcuserdata/johnestropia.xcuserdatad/WorkspaceSettings.xcsettings new file mode 100644 index 0000000..659c876 --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcuserdata/johnestropia.xcuserdatad/WorkspaceSettings.xcsettings @@ -0,0 +1,10 @@ + + + + + HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges + + SnapshotAutomaticallyBeforeSignificantChanges + + + diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist b/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist index fe2b454..89f4bf2 100644 --- a/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcdebugger/Breakpoints_v2.xcbkptlist @@ -2,4 +2,38 @@ + + + + + + + + + + diff --git a/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard b/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard index 8757afd..4c778f7 100644 --- a/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard +++ b/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard @@ -88,6 +88,91 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -214,6 +299,29 @@ + + + + + + + + + + + + + @@ -425,7 +533,7 @@ - + @@ -482,7 +590,7 @@ - + @@ -854,6 +962,6 @@ - + diff --git a/CoreStoreDemo/CoreStoreDemo/Info.plist b/CoreStoreDemo/CoreStoreDemo/Info.plist index 80a2279..128f896 100644 --- a/CoreStoreDemo/CoreStoreDemo/Info.plist +++ b/CoreStoreDemo/CoreStoreDemo/Info.plist @@ -15,7 +15,7 @@ CFBundlePackageType APPL CFBundleShortVersionString - 1.0 + 1.0.0 CFBundleSignature ???? CFBundleVersion diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectListObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift similarity index 74% rename from CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectListObserverDemoViewController.swift rename to CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift index 7ad6ef0..616d75f 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectListObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ListObserverDemoViewController.swift @@ -1,5 +1,5 @@ // -// ObjectListObserverDemoViewController.swift +// ListObserverDemoViewController.swift // CoreStoreDemo // // Created by John Rommel Estropia on 2015/05/02. @@ -12,7 +12,7 @@ import CoreStore private struct Static { - static let palettes: ManagedObjectListController = { + static let palettes: ListMonitor = { CoreStore.addSQLiteStoreAndWait( "ColorsDemo.sqlite", @@ -20,18 +20,18 @@ private struct Static { resetStoreOnMigrationFailure: true ) - return CoreStore.observeSectionedList( + return CoreStore.monitorSectionedList( From(Palette), - SectionedBy("colorName"), + SectionBy("colorName"), OrderBy(.Ascending("hue")) ) }() } -// MARK: - ObjectListObserverDemoViewController +// MARK: - ListObserverDemoViewController -class ObjectListObserverDemoViewController: UITableViewController, ManagedObjectListSectionObserver { +class ListObserverDemoViewController: UITableViewController, ListSectionObserver { // MARK: NSObject @@ -138,32 +138,32 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject } - // MARK: ManagedObjectListChangeObserver + // MARK: ListObserver - func managedObjectListWillChange(listController: ManagedObjectListController) { + func listMonitorWillChange(monitor: ListMonitor) { self.tableView.beginUpdates() } - func managedObjectListDidChange(listController: ManagedObjectListController) { + func listMonitorDidChange(monitor: ListMonitor) { self.tableView.endUpdates() } - // MARK: ManagedObjectListObjectObserver + // MARK: ListObjectObserver - func managedObjectList(listController: ManagedObjectListController, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) { + func listMonitor(monitor: ListMonitor, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) { self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } - func managedObjectList(listController: ManagedObjectListController, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) { + func listMonitor(monitor: ListMonitor, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) { self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) } - func managedObjectList(listController: ManagedObjectListController, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) { + func listMonitor(monitor: ListMonitor, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) { if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? PaletteTableViewCell { @@ -173,21 +173,21 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject } } - func managedObjectList(listController: ManagedObjectListController, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { + func listMonitor(monitor: ListMonitor, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { self.tableView.deleteRowsAtIndexPaths([fromIndexPath], withRowAnimation: .Automatic) self.tableView.insertRowsAtIndexPaths([toIndexPath], withRowAnimation: .Automatic) } - // MARK: ManagedObjectListSectionObserver + // MARK: ListSectionObserver - func managedObjectList(listController: ManagedObjectListController, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { + func listMonitor(monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic) } - func managedObjectList(listController: ManagedObjectListController, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { + func listMonitor(monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic) } @@ -195,7 +195,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject // MARK: Private - @IBAction dynamic func resetBarButtonItemTouched(sender: AnyObject?) { + @IBAction private dynamic func resetBarButtonItemTouched(sender: AnyObject?) { CoreStore.beginAsynchronous { (transaction) -> Void in @@ -204,7 +204,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject } } - @IBAction dynamic func addBarButtonItemTouched(sender: AnyObject?) { + @IBAction private dynamic func addBarButtonItemTouched(sender: AnyObject?) { CoreStore.beginAsynchronous { (transaction) -> Void in diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift index edda2a0..0dfebef 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObjectObserverDemoViewController.swift @@ -12,23 +12,23 @@ import CoreStore // MARK: - ObjectObserverDemoViewController -class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver { +class ObjectObserverDemoViewController: UIViewController, ObjectObserver { var palette: Palette? { get { - return self.objectController?.object + return self.monitor?.object } set { if let palette = newValue { - self.objectController = CoreStore.observeObject(palette) + self.monitor = CoreStore.monitorObject(palette) } else { - self.objectController = nil + self.monitor = nil } } } @@ -37,7 +37,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver deinit { - self.objectController?.removeObserver(self) + self.monitor?.removeObserver(self) } @@ -47,7 +47,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver if let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue"))) { - self.objectController = CoreStore.observeObject(palette) + self.monitor = CoreStore.monitorObject(palette) } else { @@ -60,7 +60,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver } let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))! - self.objectController = CoreStore.observeObject(palette) + self.monitor = CoreStore.monitorObject(palette) } super.init(coder: aDecoder) @@ -69,28 +69,28 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver override func viewDidLoad() { super.viewDidLoad() - self.objectController?.addObserver(self) + self.monitor?.addObserver(self) - if let palette = self.objectController?.object { + if let palette = self.monitor?.object { self.reloadPaletteInfo(palette, changedKeys: nil) } } - // MARK: ManagedObjectObserver + // MARK: ObjectObserver - func managedObjectWillUpdate(objectController: ManagedObjectController, object: Palette) { + func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: Palette) { // none } - func managedObjectWasUpdated(objectController: ManagedObjectController, object: Palette, changedPersistentKeys: Set) { + func objectMonitor(monitor: ObjectMonitor, didUpdateObject object: Palette, changedPersistentKeys: Set) { self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys) } - func managedObjectWasDeleted(objectController: ManagedObjectController, object: Palette) { + func objectMonitor(monitor: ObjectMonitor, didDeleteObject object: Palette) { self.navigationItem.rightBarButtonItem?.enabled = false @@ -108,7 +108,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver // MARK: Private - var objectController: ManagedObjectController? + var monitor: ObjectMonitor? @IBOutlet weak var colorNameLabel: UILabel? @IBOutlet weak var colorView: UIView? @@ -123,7 +123,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver let hue = self.hueSlider?.value ?? 0 CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in - if let palette = transaction.edit(self?.objectController?.object) { + if let palette = transaction.edit(self?.monitor?.object) { palette.hue = Int32(hue) transaction.commit() @@ -136,7 +136,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver let saturation = self.saturationSlider?.value ?? 0 CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in - if let palette = transaction.edit(self?.objectController?.object) { + if let palette = transaction.edit(self?.monitor?.object) { palette.saturation = saturation transaction.commit() @@ -149,7 +149,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver let brightness = self.brightnessSlider?.value ?? 0 CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in - if let palette = transaction.edit(self?.objectController?.object) { + if let palette = transaction.edit(self?.monitor?.object) { palette.brightness = brightness transaction.commit() @@ -161,7 +161,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in - transaction.delete(self?.objectController?.object) + transaction.delete(self?.monitor?.object) transaction.commit() } } diff --git a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObserversViewController.swift b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObserversViewController.swift index aea2891..1f791e6 100644 --- a/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObserversViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/List and Object Observers Demo/ObserversViewController.swift @@ -21,7 +21,7 @@ class ObserversViewController: UIViewController { let alert = UIAlertController( title: "Observers Demo", - message: "This demo shows how to observe changes to a list of objects. The top and bottom view controllers both observe a single shared \"ManagedObjectListController\" instance.\n\nTap on a row to see how to observe changes made to a single object using a \"ManagedObjectController\".", + message: "This demo shows how to observe changes to a list of objects. The top and bottom view controllers both observe a single shared \"ListMonitor\" instance.\n\nTap on a row to see how to observe changes made to a single object using a \"ObjectMonitor\".", preferredStyle: .Alert ) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MigrationsDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MigrationsDemoViewController.swift new file mode 100644 index 0000000..415f2d9 --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/MigrationsDemoViewController.swift @@ -0,0 +1,187 @@ +// +// MigrationsDemoViewController.swift +// CoreStoreDemo +// +// Created by John Rommel Estropia on 2015/06/21. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import UIKit +import CoreStore + + +private struct Static { + + static let migrationStack: DataStack = { + + let dataStack = DataStack( + modelName: "MigrationDemo", + modelVersions: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"] + ) + dataStack.addSQLiteStoreAndWait( + "MigrationsDemo.sqlite", + automigrating: true, // default is true anyway + resetStoreOnMigrationFailure: true + ) + + dataStack.beginSynchronous { (transaction) -> Void in + + transaction.deleteAll(From(OrganismV1)) + + let organism = transaction.create(Into(OrganismV1)) + organism.hasHead = true + organism.hasTail = true + + transaction.commit() + } + + return dataStack + }() +} + + +// MARK: - MigrationsDemoViewController + +class MigrationsDemoViewController: UITableViewController { + + // MARK: UIViewController + + override func viewDidLoad() { + + super.viewDidLoad() + self.selectModelVersion(self.models.first!) + } + + + // MARK: UITableViewDataSource + + override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + + return self.models.count + } + + override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { + + let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath) as! UITableViewCell + cell.textLabel?.text = self.models[indexPath.row].version + return cell + } + + + // MARK: UITableViewDelegate + + override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { + + return "Model Versions" + } + + override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) { + + self.selectModelVersion(self.models[indexPath.row]) + } + + + // MARK: Private + + private typealias ModelMetadata = (version: String, entityType: AnyClass) + + private let models: [ModelMetadata] = [ + (version: "MigrationDemo", entityType: OrganismV1.self), + (version: "MigrationDemoV2", entityType: OrganismV2.self), + (version: "MigrationDemoV3", entityType: OrganismV3.self) + ] + + private var dataStack: DataStack? + private var organism: NSManagedObject? + + @IBOutlet private dynamic weak var titleLabel: UILabel? + @IBOutlet private dynamic weak var organismLabel: UILabel? + + @IBAction private dynamic func mutateBarButtonTapped(sender: AnyObject?) { + + if let dataStack = self.dataStack, let organism = self.organism { + + dataStack.beginSynchronous { (transaction) -> Void in + + let organism = transaction.edit(organism) + (organism as! OrganismProtocol).mutate() + + transaction.commit() + } + self.updateDisplay() + } + } + + private func selectModelVersion(model: ModelMetadata) { + + if self.organism?.entity.managedObjectClassName == "\(model.entityType)" { + + return + } + + self.organism = nil + self.dataStack = nil + + let dataStack = DataStack( + modelName: "MigrationDemo", + modelVersions: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"] + ) + self.dataStack = dataStack + + dataStack.addSQLiteStore( + "MigrationDemo.sqlite", + completion: { [weak self] (result) -> Void in + + if let strongSelf = self { + + if let organism = dataStack.fetchOne(From(model.entityType)) { + + strongSelf.organism = organism + } + else { + + dataStack.beginSynchronous { (transaction) -> Void in + + let organism = transaction.create(Into(model.entityType)) + (organism as! OrganismProtocol).mutate() + + transaction.commit() + } + strongSelf.organism = dataStack.fetchOne(From(model.entityType))! + } + + strongSelf.updateDisplay() + strongSelf.tableView.selectRowAtIndexPath( + NSIndexPath( + forRow: find( + strongSelf.models.map { $0.version }, + model.version + )!, + inSection: 0 + ), + animated: false, + scrollPosition: .None + ) + } + } + ) + } + + func updateDisplay() { + + var lines = [String]() + var organismType = "" + if let organism = self.organism { + + for property in organism.entity.properties as! [NSPropertyDescription] { + + let value: AnyObject = organism.valueForKey(property.name) ?? NSNull() + lines.append("\(property.name): \(value)") + } + organismType = "\(objc_getClass(organism.entity.managedObjectClassName))" + } + + self.titleLabel?.text = organismType + self.organismLabel?.text = "\n".join(lines) + } +} diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismProtocol.swift b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismProtocol.swift new file mode 100644 index 0000000..e3bc567 --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismProtocol.swift @@ -0,0 +1,14 @@ +// +// OrganismProtocol.swift +// CoreStoreDemo +// +// Created by John Rommel Estropia on 2015/06/27. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import Foundation + +protocol OrganismProtocol { + + func mutate() +} \ No newline at end of file diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV1.swift b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV1.swift new file mode 100644 index 0000000..f19bb6b --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV1.swift @@ -0,0 +1,24 @@ +// +// OrganismV1.swift +// CoreStoreDemo +// +// Created by John Rommel Estropia on 2015/06/21. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import Foundation +import CoreData + +class OrganismV1: NSManagedObject, OrganismProtocol { + + @NSManaged var hasHead: Bool + @NSManaged var hasTail: Bool + + // MARK: OrganismProtocol + + func mutate() { + + self.hasHead = arc4random_uniform(2) == 1 + self.hasTail = arc4random_uniform(2) == 1 + } +} diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2.swift b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2.swift new file mode 100644 index 0000000..26b3ac7 --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2.swift @@ -0,0 +1,26 @@ +// +// OrganismV2.swift +// CoreStoreDemo +// +// Created by John Rommel Estropia on 2015/06/21. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import Foundation +import CoreData + +class OrganismV2: NSManagedObject, OrganismProtocol { + + @NSManaged var hasHead: Bool + @NSManaged var hasTail: Bool + @NSManaged var numberOfFlippers: Int32 + + // MARK: OrganismProtocol + + func mutate() { + + self.hasHead = arc4random_uniform(2) == 1 + self.hasTail = arc4random_uniform(2) == 1 + self.numberOfFlippers = Int32(arc4random_uniform(9) / 2 * 2) + } +} diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2ToV3.xcmappingmodel/xcmapping.xml b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2ToV3.xcmappingmodel/xcmapping.xml new file mode 100644 index 0000000..eda621a --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2ToV3.xcmappingmodel/xcmapping.xml @@ -0,0 +1,97 @@ + + + + + + 134481920 + D40C3ECB-558B-40E6-99F3-D8261AFE97C0 + 107 + + + + NSPersistenceFrameworkVersion + 526 + NSStoreModelVersionHashes + + XDDevAttributeMapping + + 0plcXXRN7XHKl5CcF+fwriFmUpON3ZtcI/AfK748aWc= + + XDDevEntityMapping + + qeN1Ym3TkWN1G6dU9RfX6Kd2ccEvcDVWHpd3LpLgboI= + + XDDevMappingModel + + EqtMzvRnVZWkXwBHu4VeVGy8UyoOe+bi67KC79kphlQ= + + XDDevPropertyMapping + + XN33V44TTGY4JETlMoOB5yyTKxB+u4slvDIinv0rtGA= + + XDDevRelationshipMapping + + akYY9LhehVA/mCb4ATLWuI9XGLcjpm14wWL1oEBtIcs= + + + NSStoreModelVersionHashesVersion + 3 + NSStoreModelVersionIdentifiers + + + + + + + + + CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV2.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGBVoFW1gkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRv +cBIAAYagrxCTAAcACAAXADMANAA1AD0APgBZAFoAWwBhAGIAbgCCAIMAhACFAIYAhwCIAIkAigCjAKYArQCzAMIA0QDgAOMAWADzAQIBBgEKARkBHwEgASgBNwE4AUEBSwFMAU0BTgFjAWQBbAFtAW4BegGOAY8BkAGRAZIBkwGUAZUBlgGlAbQBwwHHAdYB5QH0AgMCEgIeAjACMQIyAjMCNAI1AjYCNwJGAlUCZAJzAnQCgwKSAqECqQK+Ar8CxwLTAucC9gMFAxQDGAMnAzYDRQNUA2MDbwOBA5ADnwOuA70DzAPbA+oD/wQABAgEFAQoBDcERgRVBFkEaAR3BIYElQSkBLAEwgTRBNIE4QTwBP8FAAUPBR4FLQUuBTEFOgU+BUIFRgVOBVEFVQVWVSRudWxs1wAJAAoACwAMAA0ADgAPABAAEQASABMAFAATABZfEA9feGRfcm9vdFBhY2thZ2VWJGNsYXNzXF94ZF9jb21tZW50c18QEF94ZF9tb2RlbE1hbmFnZXJfEBVfY29uZmlndXJhdGlvbnNCeU5hbWVdX3hkX21vZGVsTmFtZV8QF19tb2RlbFZlcnNpb25JZGVudGlmaWVygAKAkoCPgACAkIAAgJHeABgAGQAaABsAHAAdAB4ACgAfACAAIQAiACMAJAAlACYAJwAoACUAEwArACwALQAuAC8AJQAlABNfEBxYREJ1Y2tldEZvckNsYXNzZXN3YXNFbmNvZGVkXxAaWERCdWNrZXRGb3JQYWNrYWdlc3N0b3JhZ2VfEBxYREJ1Y2tldEZvckludGVyZmFjZXNzdG9yYWdlXxAPX3hkX293bmluZ01vZGVsXxAdWERCdWNrZXRGb3JQYWNrYWdlc3dhc0VuY29kZWRWX293bmVyXxAbWERCdWNrZXRGb3JEYXRhVHlwZXNzdG9yYWdlW192aXNpYmlsaXR5XxAZWERCdWNrZXRGb3JDbGFzc2Vzc3RvcmFnZVVfbmFtZV8QH1hEQnVja2V0Rm9ySW50ZXJmYWNlc3dhc0VuY29kZWRfEB5YREJ1Y2tldEZvckRhdGFUeXBlc3dhc0VuY29kZWRfEBBfdW5pcXVlRWxlbWVudElEgASAjYCLgAGABIAAgIyAjhAAgAWAA4AEgASAAFBTWUVT0wA2ADcACgA4ADoAPFdOUy5rZXlzWk5TLm9iamVjdHOhADmABqEAO4AHgCJYT3JnYW5pc23fEBAAPwBAAEEAQgAdAEMARAAfAEUARgAKACEARwBIACQASQBKAEsAJQAlABAATwBQAC0AJQBKAFMAOQBKAFYAVwBYXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JPd25lZEF0dHJpYnV0ZXNzdG9yYWdlXxAgWERCdWNrZXRGb3JTdGVyZW90eXBlc3dhc0VuY29kZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfEB1YREJ1Y2tldEZvclN0ZXJlb3R5cGVzc3RvcmFnZV8QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zZHVwbGljYXRlc18QJFhEQnVja2V0Rm9yR2VuZXJhbGl6YXRpb25zd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNvcmRlcmVkXxAhWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNzdG9yYWdlW19pc0Fic3RyYWN0gAmAKoAEgASAAoAKgIiABIAJgIqABoAJgImACAgSs6i+d1dvcmRlcmVk0wA2ADcACgBcAF4APKEAXYALoQBfgAyAIl5YRF9QU3RlcmVvdHlwZdkAHQAhAGMACgAkAGQAHwBJAGUAOwBdAEoAaQATACUALQBYAG1fECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECRYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3dhc0VuY29kZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2WAB4ALgAmAKYAAgAQIgA3TADYANwAKAG8AeAA8qABwAHEAcgBzAHQAdQB2AHeADoAPgBCAEYASgBOAFIAVqAB5AHoAewB8AH0AfgB/AICAFoAagBuAHIAegCCAI4AngCJfEBNYRFBNQ29tcG91bmRJbmRleGVzXxAQWERfUFNLX2VsZW1lbnRJRF8QGlhEX1BTS192ZXJzaW9uSGFzaE1vZGlmaWVyXxAZWERfUFNLX2ZldGNoUmVxdWVzdHNBcnJheV8QEVhEX1BTS19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gFuIW5BblFuYW5xboFuoW7BbtFu4W8BbxFvoXCBcVFyMXMBdDF1oXbBe3F9oX+hgaGBwYHhggGCIYJBglGCYYKBgpGCsYLBguGDAYMRgyGDQYNRg6GEcYTBhOGFAYVRhXGFkYWxiAGKQYyxjvGPEY8xj1GPcY+Rj7GPwY/hkLGRwZHhkgGSIZJBkmGSgZKhksGT0ZPxlBGUMZRRlHGUkZSxlNGU8ZjhmQGZIZlBmWGZcZmBmZGZoZnBmeGZ8ZoBmiGaMZ4hnkGeYZ6BnqGesZ7BntGe4Z8BnyGfMZ9Bn2GfcaNho4GjoaPBo+Gj8aQBpBGkIaRBpGGkcaSBpKGksaWBpZGloaXBqbGp0anxqhGqMapBqlGqYapxqpGqsarBqtGq8asBrvGvEa8xr1Gvca+Br5Gvoa+xr9Gv8bABsBGwMbBBtDG0UbRxtJG0sbTBtNG04bTxtRG1MbVBtVG1cbWBuXG5kbmxudG58boBuhG6IboxulG6cbqBupG6sbrBvrG+0b7xvxG/Mb9Bv1G/Yb9xv5G/sb/Bv9G/8cABwlHEkccByUHJYcmByaHJwcnhygHKEcoxywHL8cwRzDHMUcxxzJHMsczRzcHN4c4BziHOQc5hzoHOoc7B0rHS0dLx0xHTMdNB01HTYdNx05HTsdPB09HT8dQB1/HYEdgx2FHYcdiB2JHYodix2NHY8dkB2RHZMdlB3THdUd1x3ZHdsd3B3dHd4d3x3hHeMd5B3lHecd6B4nHikeKx4tHi8eMB4xHjIeMx41HjceOB45HjsePB57Hn0efx6BHoMehB6FHoYehx6JHosejB6NHo8ekB7PHtEe0x7VHtce2B7ZHtoe2x7dHt8e4B7hHuMe5B8jHyUfJx8pHysfLB8tHy4fLx8xHzMfNB81HzcfOB+DH6Yfxh/mH+gf6h/sH+4f8B/xH/If9B/1H/cf+B/6H/wf/R/+IAAgASAGIBMgGCAaIBwgISAjICUgJyBMIHAglyC7IL0gvyDBIMMgxSDHIMggyiDXIOgg6iDsIO4g8CDyIPQg9iD4IQkhCyENIQ8hESETIRUhFyEZIRshWiFcIV4hYCFiIWMhZCFlIWYhaCFqIWshbCFuIW8hriGwIbIhtCG2IbchuCG5IbohvCG+Ib8hwCHCIcMiAiIEIgYiCCIKIgsiDCINIg4iECISIhMiFCIWIhciJCIlIiYiKCJnImkiayJtIm8icCJxInIicyJ1IncieCJ5InsifCK7Ir0ivyLBIsMixCLFIsYixyLJIssizCLNIs8i0CMPIxEjEyMVIxcjGCMZIxojGyMdIx8jICMhIyMjJCNjI2UjZyNpI2sjbCNtI24jbyNxI3MjdCN1I3cjeCO3I7kjuyO9I78jwCPBI8IjwyPFI8cjyCPJI8sjzCPxJBUkPCRgJGIkZCRmJGgkaiRsJG0kbyR8JIskjSSPJJEkkySVJJckmSSoJKokrCSuJLAksiS0JLYkuCT3JPkk+yT9JP8lACUBJQIlAyUFJQclCCUJJQslDCUOJU0lTyVRJVMlVSVWJVclWCVZJVslXSVeJV8lYSViJaEloyWlJaclqSWqJaslrCWtJa8lsSWyJbMltSW2JfUl9yX5Jfsl/SX+Jf8mACYBJgMmBSYGJgcmCSYKJgwmSyZNJk8mUSZTJlQmVSZWJlcmWSZbJlwmXSZfJmAmnyahJqMmpSanJqgmqSaqJqsmrSavJrAmsSazJrQm8yb1Jvcm+Sb7Jvwm/Sb+Jv8nAScDJwQnBScHJwgnEyccJx0nHycoJzMnQidNJ1sncCeEJ5snrSe6J7snvCe+J8snzCfNJ88n3CfdJ94n4CfpJ/goBSgUKCYoOihRKGMobChtKG8ofCh9KH4ogCiBKIoolCibKKMotSi6KL8AAAAAAAACAgAAAAAAAAVeAAAAAAAAAAAAAAAAAAAowQ== + + CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV3.xcdatamodel + YnBsaXN0MDDUAAEAAgADAAQABQAGBp8GoFgkdmVyc2lvblgkb2JqZWN0c1kkYXJjaGl2ZXJUJHRv +cBIAAYagrxCsAAcACAAXADMANAA1AD0APgBZAFoAWwBhAGIAbgCCAIMAhACFAIYAhwCIAIkAigCjAKYArQCzAMIA0QDgAOMAWADzAQIBBgEKARkBHwEgASgBNwE4AUEBTQFOAU8BUAFRAWYBZwFvAXABcQF9AZEBkgGTAZQBlQGWAZcBmAGZAagBtwHGAcoB2QHoAekB+AIHAggCFwIjAjUCNgI3AjgCOQI6AjsCPAJLAloCaQJ4AnkCiAKXAqYCrgLDAsQCzALYAuwC+wMKAxkDHQMsAzsDSgNZA2gDdAOGA5UDpAOzA8IDwwPSA+ED8AQFBAYEDgQaBC4EPQRMBFsEXwRuBH0EjASbBKoEtgTIBNcE5gT1BQQFEwUiBTEFRgVHBU8FWwVvBX4FjQWcBaAFrwW+Bc0F3AXrBfcGCQYYBicGNgZFBlQGYwZyBnMGdgZ/BoMGhwaLBpMGlgaaBptVJG51bGzXAAkACgALAAwADQAOAA8AEAARABIAEwAUABMAFl8QD194ZF9yb290UGFja2FnZVYkY2xhc3NcX3hkX2NvbW1lbnRzXxAQX3hkX21vZGVsTWFuYWdlcl8QFV9jb25maWd1cmF0aW9uc0J5TmFtZV1feGRfbW9kZWxOYW1lXxAXX21vZGVsVmVyc2lvbklkZW50aWZpZXKAAoCrgKiAAICpgACAqt4AGAAZABoAGwAcAB0AHgAKAB8AIAAhACIAIwAkACUAJgAnACgAJQATACsALAAtAC4ALwAlACUAE18QHFhEQnVja2V0Rm9yQ2xhc3Nlc3dhc0VuY29kZWRfEBpYREJ1Y2tldEZvclBhY2thZ2Vzc3RvcmFnZV8QHFhEQnVja2V0Rm9ySW50ZXJmYWNlc3N0b3JhZ2VfEA9feGRfb3duaW5nTW9kZWxfEB1YREJ1Y2tldEZvclBhY2thZ2Vzd2FzRW5jb2RlZFZfb3duZXJfEBtYREJ1Y2tldEZvckRhdGFUeXBlc3N0b3JhZ2VbX3Zpc2liaWxpdHlfEBlYREJ1Y2tldEZvckNsYXNzZXNzdG9yYWdlVV9uYW1lXxAfWERCdWNrZXRGb3JJbnRlcmZhY2Vzd2FzRW5jb2RlZF8QHlhEQnVja2V0Rm9yRGF0YVR5cGVzd2FzRW5jb2RlZF8QEF91bmlxdWVFbGVtZW50SUSABICmgKSAAYAEgACApYCnEACABYADgASABIAAUFNZRVPTADYANwAKADgAOgA8V05TLmtleXNaTlMub2JqZWN0c6EAOYAGoQA7gAeAIlhPcmdhbmlzbd8QEAA/AEAAQQBCAB0AQwBEAB8ARQBGAAoAIQBHAEgAJABJAEoASwAlACUAEABPAFAALQAlAEoAUwA5AEoAVgBXAFhfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc29yZGVyZWRfECFYREJ1Y2tldEZvck93bmVkQXR0cmlidXRlc3N0b3JhZ2VfECBYREJ1Y2tldEZvclN0ZXJlb3R5cGVzd2FzRW5jb2RlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QHVhEQnVja2V0Rm9yU3RlcmVvdHlwZXNzdG9yYWdlXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnNkdXBsaWNhdGVzXxAkWERCdWNrZXRGb3JHZW5lcmFsaXphdGlvbnN3YXNFbmNvZGVkXxAdWERCdWNrZXRGb3JTdGVyZW90eXBlc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc29yZGVyZWRfECFYREJ1Y2tldEZvckdlbmVyYWxpemF0aW9uc3N0b3JhZ2VbX2lzQWJzdHJhY3SACYAqgASABIACgAqAoYAEgAmAo4AGgAmAooAICBJKbyNkV29yZGVyZWTTADYANwAKAFwAXgA8oQBdgAuhAF+ADIAiXlhEX1BTdGVyZW90eXBl2QAdACEAYwAKACQAZAAfAEkAZQA7AF0ASgBpABMAJQAtAFgAbV8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzb3JkZXJlZF8QJFhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzd2FzRW5jb2RlZF8QIVhEQnVja2V0Rm9yT3duZWRBdHRyaWJ1dGVzc3RvcmFnZYAHgAuACYApgACABAiADdMANgA3AAoAbwB4ADyoAHAAcQByAHMAdAB1AHYAd4AOgA+AEIARgBKAE4AUgBWoAHkAegB7AHwAfQB+AH8AgIAWgBqAG4AcgB6AIIAjgCeAIl8QE1hEUE1Db21wb3VuZEluZGV4ZXNfEBBYRF9QU0tfZWxlbWVudElEXxAaWERfUFNLX3ZlcnNpb25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cEx4TIBMhEyITIxMkEyYTKBMpEyoTLBMtE1ITdhOdE8ETwxPFE8cTyRPLE80TzhPQE90T7BPuE/AT8hP0E/YT+BP6FAkUCxQNFA8UERQTFBUUFxQZFDkUZBR+FJcUsRTRFPQVMxU1FTcVORU7FTwVPRU+FT8VQRVDFUQVRRVHFUgVhxWJFYsVjRWPFZAVkRWSFZMVlRWXFZgVmRWbFZwV2xXdFd8V4RXjFeQV5RXmFecV6RXrFewV7RXvFfAWLxYxFjMWNRY3FjgWORY6FjsWPRY/FkAWQRZDFkQWRhaFFocWiRaLFo0WjhaPFpAWkRaTFpUWlhaXFpkWmhbZFtsW3RbfFuEW4hbjFuQW5RbnFukW6hbrFu0W7hctFy8XMRczFzUXNhc3FzgXORc7Fz0XPhc/F0EXQhdLF1kXZhd0F4EXlBerF70YCBgrGEsYaxhtGG8YcRhzGHUYdhh3GHkYehh8GH0YfxiBGIIYgxiFGIYYixiYGJ0YnxihGKYYqBiqGKwY0Rj1GRwZQBlCGUQZRhlIGUoZTBlNGU8ZXBltGW8ZcRlzGXUZdxl5GXsZfRmOGZAZkhmUGZYZmBmaGZwZnhmgGd8Z4RnjGeUZ5xnoGekZ6hnrGe0Z7xnwGfEZ8xn0GjMaNRo3GjkaOxo8Gj0aPho/GkEaQxpEGkUaRxpIGocaiRqLGo0ajxqQGpEakhqTGpUalxqYGpkamxqcGqkaqhqrGq0a7BruGvAa8hr0GvUa9hr3Gvga+hr8Gv0a/hsAGwEbQBtCG0QbRhtIG0kbShtLG0wbThtQG1EbUhtUG1UblBuWG5gbmhucG50bnhufG6AbohukG6UbphuoG6kb6BvqG+wb7hvwG/Eb8hvzG/Qb9hv4G/kb+hv8G/0cPBw+HEAcQhxEHEUcRhxHHEgcShxMHE0cThxQHFEcdhyaHMEc5RznHOkc6xztHO8c8RzyHPQdAR0QHRIdFB0WHRgdGh0cHR4dLR0vHTEdMx01HTcdOR07HT0dfB1+HYAdgh2EHYUdhh2HHYgdih2MHY0djh2QHZEd0B3SHdQd1h3YHdkd2h3bHdwd3h3gHeEd4h3kHeUeJB4mHigeKh4sHi0eLh4vHjAeMh40HjUeNh44HjkeeB56Hnwefh6AHoEegh6DHoQehh6IHokeih6MHo0ekB7PHtEe0x7VHtce2B7ZHtoe2x7dHt8e4B7hHuMe5B8jHyUfJx8pHysfLB8tHy4fLx8xHzMfNB81HzcfOB93H3kfex99H38fgB+BH4Ifgx+FH4cfiB+JH4sfjB/XH/ogGiA6IDwgPiBAIEIgRCBFIEYgSCBJIEsgTCBOIFAgUSBSIFQgVSBaIGcgbCBuIHAgdSB3IHkgeyCgIMQg6yEPIREhEyEVIRchGSEbIRwhHiErITwhPiFAIUIhRCFGIUghSiFMIV0hXyFhIWMhZSFnIWkhayFtIW8hriGwIbIhtCG2IbchuCG5IbohvCG+Ib8hwCHCIcMiAiIEIgYiCCIKIgsiDCINIg4iECISIhMiFCIWIhciViJYIloiXCJeIl8iYCJhImIiZCJmImciaCJqImsieCJ5InoifCK7Ir0ivyLBIsMixCLFIsYixyLJIssizCLNIs8i0CMPIxEjEyMVIxcjGCMZIxojGyMdIx8jICMhIyMjJCNjI2UjZyNpI2sjbCNtI24jbyNxI3MjdCN1I3cjeCO3I7kjuyO9I78jwCPBI8IjwyPFI8cjyCPJI8sjzCQLJA0kDyQRJBMkFCQVJBYkFyQZJBskHCQdJB8kICRFJGkkkCS0JLYkuCS6JLwkviTAJMEkwyTQJN8k4STjJOUk5yTpJOsk7ST8JP4lACUCJQQlBiUIJQolDCVLJU0lTyVRJVMlVCVVJVYlVyVZJVslXCVdJV8lYCWfJaEloyWlJaclqCWpJaolqyWtJa8lsCWxJbMltCXzJfUl9yX5Jfsl/CX9Jf4l/yYBJgMmBCYFJgcmCCZHJkkmSyZNJk8mUCZRJlImUyZVJlcmWCZZJlsmXCabJp0mnyahJqMmpCalJqYmpyapJqsmrCatJq8msCbvJvEm8yb1Jvcm+Cb5Jvom+yb9Jv8nACcBJwMnBCdDJ0UnRydJJ0snTCdNJ04nTydRJ1MnVCdVJ1cnWCejJ8Yn5igGKAgoCigMKA4oECgRKBIoFCgVKBcoGCgaKBwoHSgeKCAoISgmKDMoOCg6KDwoQShDKEUoRyhsKJAotyjbKN0o3yjhKOMo5SjnKOgo6ij3KQgpCikMKQ4pECkSKRQpFikYKSkpKyktKS8pMSkzKTUpNyk5KTspeil8KX4pgCmCKYMphCmFKYYpiCmKKYspjCmOKY8pzinQKdIp1CnWKdcp2CnZKdop3CneKd8p4CniKeMqIiokKiYqKCoqKisqLCotKi4qMCoyKjMqNCo2KjcqRCpFKkYqSCqHKokqiyqNKo8qkCqRKpIqkyqVKpcqmCqZKpsqnCrbKt0q3yrhKuMq5CrlKuYq5yrpKusq7CrtKu8q8CsvKzErMys1KzcrOCs5KzorOys9Kz8rQCtBK0MrRCuDK4UrhyuJK4srjCuNK44rjyuRK5MrlCuVK5crmCvXK9kr2yvdK98r4CvhK+Ir4yvlK+cr6CvpK+sr7CwRLDUsXCyALIIshCyGLIgsiiyMLI0sjyycLKssrSyvLLEssyy1LLcsuSzILMoszCzOLNAs0izULNYs2C0XLRktGy0dLR8tIC0hLSItIy0lLSctKC0pLSstLC1rLW0tby1xLXMtdC11LXYtdy15LXstfC19LX8tgC2/LcEtwy3FLcctyC3JLcotyy3NLc8t0C3RLdMt1C4TLhUuFy4ZLhsuHC4dLh4uHy4hLiMuJC4lLicuKC5nLmkuay5tLm8ucC5xLnIucy51LncueC55LnsufC67Lr0uvy7BLsMuxC7FLsYuxy7JLssuzC7NLs8u0C8PLxEvEy8VLxcvGC8ZLxovGy8dLx8vIC8hLyMvJC8vLzgvOS87L0QvTy9eL2kvdy+ML6Avty/JL9Yv1y/YL9ov5y/oL+kv6y/4L/kv+i/8MAUwFDAhMDAwQjBWMG0wfzCIMIkwizCYMJkwmjCcMJ0wpjCwMLcwvzDRMNYw2wAAAAAAAAICAAAAAAAABqMAAAAAAAAAAAAAAAAAADDd + + + + + YnBsaXN0MDDUAQIDBAUGPD1YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKwH +CBMUGRoiJywtMjZVJG51bGzVCQoLDA0ODxAREllOU09wZXJhbmReTlNTZWxlY3Rvck5hbWVfEBBOU0V4cHJlc3Npb25UeXBlW05TQXJndW1lbnRzViRjbGFzc4ADgAIQBIAGgAtfEBB2YWx1ZUZvcktleVBhdGg60xULDRYXGFpOU1ZhcmlhYmxlgAQQAoAFVnNvdXJjZdIbHB0eWiRjbGFzc25hbWVYJGNsYXNzZXNfEBROU1ZhcmlhYmxlRXhwcmVzc2lvbqMfICFfEBROU1ZhcmlhYmxlRXhwcmVzc2lvblxOU0V4cHJlc3Npb25YTlNPYmplY3TSIw0kJlpOUy5vYmplY3RzoSWAB4AK0w0LKCkqK1lOU0tleVBhdGiACRAKgAhXaGFzVGFpbNIbHC4vXxAcTlNLZXlQYXRoU3BlY2lmaWVyRXhwcmVzc2lvbqMwMSFfEBxOU0tleVBhdGhTcGVjaWZpZXJFeHByZXNzaW9uXE5TRXhwcmVzc2lvbtIbHDM0Xk5TTXV0YWJsZUFycmF5ozM1IVdOU0FycmF50hscNzhfEBNOU0tleVBhdGhFeHByZXNzaW9upDk6OyFfEBNOU0tleVBhdGhFeHByZXNzaW9uXxAUTlNGdW5jdGlvbkV4cHJlc3Npb25cTlNFeHByZXNzaW9uXxAPTlNLZXllZEFyY2hpdmVy0T4/VHJvb3SAAQAIABEAGgAjAC0AMgA3AEQASgBVAF8AbgCBAI0AlACWAJgAmgCcAJ4AsQC4AMMAxQDHAMkA0ADVAOAA6QEAAQQBGwEoATEBNgFBAUMBRQFHAU4BWAFaAVwBXgFmAWsBigGOAa0BugG/Ac4B0gHaAd8B9QH6AhACJwI0AkYCSQJOAAAAAAAAAgEAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAlA= + + hasTail + + + + YnBsaXN0MDDUAQIDBAUGPD1YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKwH +CBMUGRoiJywtMjZVJG51bGzVCQoLDA0ODxAREllOU09wZXJhbmReTlNTZWxlY3Rvck5hbWVfEBBOU0V4cHJlc3Npb25UeXBlW05TQXJndW1lbnRzViRjbGFzc4ADgAIQBIAGgAtfEBB2YWx1ZUZvcktleVBhdGg60xULDRYXGFpOU1ZhcmlhYmxlgAQQAoAFVnNvdXJjZdIbHB0eWiRjbGFzc25hbWVYJGNsYXNzZXNfEBROU1ZhcmlhYmxlRXhwcmVzc2lvbqMfICFfEBROU1ZhcmlhYmxlRXhwcmVzc2lvblxOU0V4cHJlc3Npb25YTlNPYmplY3TSIw0kJlpOUy5vYmplY3RzoSWAB4AK0w0LKCkqK1lOU0tleVBhdGiACRAKgAhfEBBudW1iZXJPZkZsaXBwZXJz0hscLi9fEBxOU0tleVBhdGhTcGVjaWZpZXJFeHByZXNzaW9uozAxIV8QHE5TS2V5UGF0aFNwZWNpZmllckV4cHJlc3Npb25cTlNFeHByZXNzaW9u0hscMzReTlNNdXRhYmxlQXJyYXmjMzUhV05TQXJyYXnSGxw3OF8QE05TS2V5UGF0aEV4cHJlc3Npb26kOTo7IV8QE05TS2V5UGF0aEV4cHJlc3Npb25fEBROU0Z1bmN0aW9uRXhwcmVzc2lvblxOU0V4cHJlc3Npb25fEA9OU0tleWVkQXJjaGl2ZXLRPj9Ucm9vdIABAAgAEQAaACMALQAyADcARABKAFUAXwBuAIEAjQCUAJYAmACaAJwAngCxALgAwwDFAMcAyQDQANUA4ADpAQABBAEbASgBMQE2AUEBQwFFAUcBTgFYAVoBXAFeAXEBdgGVAZkBuAHFAcoB2QHdAeUB6gIAAgUCGwIyAj8CUQJUAlkAAAAAAAACAQAAAAAAAABAAAAAAAAAAAAAAAAAAAACWw== + + numberOfLimbs + + + + YnBsaXN0MDDUAQIDBAUGGBlYJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKQH +CA8QVSRudWxs0wkKCwwNDl8QD05TQ29uc3RhbnRWYWx1ZV8QEE5TRXhwcmVzc2lvblR5cGVWJGNsYXNzgAIQAIADCNIREhMUWiRjbGFzc25hbWVYJGNsYXNzZXNfEBlOU0NvbnN0YW50VmFsdWVFeHByZXNzaW9uoxUWF18QGU5TQ29uc3RhbnRWYWx1ZUV4cHJlc3Npb25cTlNFeHByZXNzaW9uWE5TT2JqZWN0XxAPTlNLZXllZEFyY2hpdmVy0RobVHJvb3SAAQAIABEAGgAjAC0AMgA3ADwAQgBJAFsAbgB1AHcAeQB7AHwAgQCMAJUAsQC1ANEA3gDnAPkA/AEBAAAAAAAAAgEAAAAAAAAAHAAAAAAAAAAAAAAAAAAAAQM= + + hasVertebrae + + + + CoreStoreDemo.OrganismV2ToV3MigrationPolicy + Organism + Undefined + 1 + Organism + 1 + + + + + + YnBsaXN0MDDUAQIDBAUGPD1YJHZlcnNpb25YJG9iamVjdHNZJGFyY2hpdmVyVCR0b3ASAAGGoKwH +CBMUGRoiJywtMjZVJG51bGzVCQoLDA0ODxAREllOU09wZXJhbmReTlNTZWxlY3Rvck5hbWVfEBBOU0V4cHJlc3Npb25UeXBlW05TQXJndW1lbnRzViRjbGFzc4ADgAIQBIAGgAtfEBB2YWx1ZUZvcktleVBhdGg60xULDRYXGFpOU1ZhcmlhYmxlgAQQAoAFVnNvdXJjZdIbHB0eWiRjbGFzc25hbWVYJGNsYXNzZXNfEBROU1ZhcmlhYmxlRXhwcmVzc2lvbqMfICFfEBROU1ZhcmlhYmxlRXhwcmVzc2lvblxOU0V4cHJlc3Npb25YTlNPYmplY3TSIw0kJlpOUy5vYmplY3RzoSWAB4AK0w0LKCkqK1lOU0tleVBhdGiACRAKgAhXaGFzSGVhZNIbHC4vXxAcTlNLZXlQYXRoU3BlY2lmaWVyRXhwcmVzc2lvbqMwMSFfEBxOU0tleVBhdGhTcGVjaWZpZXJFeHByZXNzaW9uXE5TRXhwcmVzc2lvbtIbHDM0Xk5TTXV0YWJsZUFycmF5ozM1IVdOU0FycmF50hscNzhfEBNOU0tleVBhdGhFeHByZXNzaW9upDk6OyFfEBNOU0tleVBhdGhFeHByZXNzaW9uXxAUTlNGdW5jdGlvbkV4cHJlc3Npb25cTlNFeHByZXNzaW9uXxAPTlNLZXllZEFyY2hpdmVy0T4/VHJvb3SAAQAIABEAGgAjAC0AMgA3AEQASgBVAF8AbgCBAI0AlACWAJgAmgCcAJ4AsQC4AMMAxQDHAMkA0ADVAOAA6QEAAQQBGwEoATEBNgFBAUMBRQFHAU4BWAFaAVwBXgFmAWsBigGOAa0BugG/Ac4B0gHaAd8B9QH6AhACJwI0AkYCSQJOAAAAAAAAAgEAAAAAAAAAQAAAAAAAAAAAAAAAAAAAAlA= + + hasHead + + + \ No newline at end of file diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2ToV3MigrationPolicy.swift b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2ToV3MigrationPolicy.swift new file mode 100644 index 0000000..eec7d58 --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2ToV3MigrationPolicy.swift @@ -0,0 +1,27 @@ +// +// OrganismV2ToV3MigrationPolicy.swift +// CoreStoreDemo +// +// Created by John Rommel Estropia on 2015/06/27. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import CoreData + +class OrganismV2ToV3MigrationPolicy: NSEntityMigrationPolicy { + + override func createDestinationInstancesForSourceInstance(sInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager, error: NSErrorPointer) -> Bool { + + if !super.createDestinationInstancesForSourceInstance(sInstance, entityMapping: mapping, manager: manager, error: error) { + + return false + } + + for dInstance in manager.destinationInstancesForEntityMappingNamed(mapping.name, sourceInstances: [sInstance]) { + + dInstance.setValue(false, forKey: "hasVertebrae") + dInstance.setValue(sInstance.valueForKey("numberOfFlippers"), forKey: "numberOfLimbs") + } + return true + } +} diff --git a/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV3.swift b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV3.swift new file mode 100644 index 0000000..3f00d4c --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV3.swift @@ -0,0 +1,28 @@ +// +// OrganismV3.swift +// CoreStoreDemo +// +// Created by John Rommel Estropia on 2015/06/27. +// Copyright (c) 2015 John Rommel Estropia. All rights reserved. +// + +import Foundation +import CoreData + +class OrganismV3: NSManagedObject, OrganismProtocol { + + @NSManaged var hasHead: Bool + @NSManaged var hasTail: Bool + @NSManaged var numberOfLimbs: Int32 + @NSManaged var hasVertebrae: Bool + + // MARK: OrganismProtocol + + func mutate() { + + self.hasHead = arc4random_uniform(2) == 1 + self.hasTail = arc4random_uniform(2) == 1 + self.numberOfLimbs = Int32(arc4random_uniform(9) / 2 * 2) + self.hasVertebrae = arc4random_uniform(2) == 1 + } +} diff --git a/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/.xccurrentversion b/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/.xccurrentversion index 508da66..d85ac4a 100644 --- a/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/.xccurrentversion +++ b/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/.xccurrentversion @@ -3,6 +3,6 @@ _XCCurrentVersionName - MigrationDemoV2.xcdatamodel + MigrationDemoV3.xcdatamodel diff --git a/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemo.xcdatamodel/contents b/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemo.xcdatamodel/contents index 86ab034..a6e01b0 100644 --- a/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemo.xcdatamodel/contents +++ b/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemo.xcdatamodel/contents @@ -1,7 +1,10 @@ - + + + + - + \ No newline at end of file diff --git a/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV2.xcdatamodel/contents b/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV2.xcdatamodel/contents index 86ab034..e594c0d 100644 --- a/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV2.xcdatamodel/contents +++ b/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV2.xcdatamodel/contents @@ -1,7 +1,11 @@ - + + + + + - + \ No newline at end of file diff --git a/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV3.xcdatamodel/contents b/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV3.xcdatamodel/contents new file mode 100644 index 0000000..48ffe4a --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo/MigrationDemo.xcdatamodeld/MigrationDemoV3.xcdatamodel/contents @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/CoreStoreDemo/CoreStoreDemo/Stack Setup Demo/StackSetupDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/Stack Setup Demo/StackSetupDemoViewController.swift index 9ee009c..4426e64 100644 --- a/CoreStoreDemo/CoreStoreDemo/Stack Setup Demo/StackSetupDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/Stack Setup Demo/StackSetupDemoViewController.swift @@ -176,11 +176,11 @@ class StackSetupDemoViewController: UITableViewController { // MARK: Private - @IBOutlet weak var accountTypeLabel: UILabel? - @IBOutlet weak var nameLabel: UILabel? - @IBOutlet weak var friendsLabel: UILabel? + @IBOutlet private dynamic weak var accountTypeLabel: UILabel? + @IBOutlet private dynamic weak var nameLabel: UILabel? + @IBOutlet private dynamic weak var friendsLabel: UILabel? - func updateDetailsWithAccount(account: UserAccount) { + private func updateDetailsWithAccount(account: UserAccount) { self.accountTypeLabel?.text = account.accountType self.nameLabel?.text = account.name diff --git a/CoreStoreDemo/CoreStoreDemo/Transactions Demo/TransactionsDemoViewController.swift b/CoreStoreDemo/CoreStoreDemo/Transactions Demo/TransactionsDemoViewController.swift index d01ce40..d9e1c61 100644 --- a/CoreStoreDemo/CoreStoreDemo/Transactions Demo/TransactionsDemoViewController.swift +++ b/CoreStoreDemo/CoreStoreDemo/Transactions Demo/TransactionsDemoViewController.swift @@ -16,7 +16,7 @@ import GCDKit private struct Static { - static let placeController: ManagedObjectController = { + static let placeController: ObjectMonitor = { CoreStore.addSQLiteStoreAndWait( "PlaceDemo.sqlite", @@ -37,14 +37,14 @@ private struct Static { place = CoreStore.fetchOne(From(Place)) } - return CoreStore.observeObject(place!) + return CoreStore.monitorObject(place!) }() } // MARK: - TransactionsDemoViewController -class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, ManagedObjectObserver { +class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, ObjectObserver { // MARK: NSObject @@ -120,14 +120,14 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Manag } - // MARK: ManagedObjectObserver + // MARK: ObjectObserver - func managedObjectWillUpdate(objectController: ManagedObjectController, object: Place) { + func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: Place) { // none } - func managedObjectWasUpdated(objectController: ManagedObjectController, object: Place, changedPersistentKeys: Set) { + func objectMonitor(monitor: ObjectMonitor, didUpdateObject object: Place, changedPersistentKeys: Set) { if let mapView = self.mapView { @@ -143,7 +143,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Manag } } - func managedObjectWasDeleted(objectController: ManagedObjectController, object: Place) { + func objectMonitor(monitor: ObjectMonitor, didDeleteObject object: Place) { // none } diff --git a/CoreStoreTests/CoreStoreTests.swift b/CoreStoreTests/CoreStoreTests.swift index 0107c78..d8ea259 100644 --- a/CoreStoreTests/CoreStoreTests.swift +++ b/CoreStoreTests/CoreStoreTests.swift @@ -43,7 +43,7 @@ class CoreStoreTests: XCTestCase { func testExample() { - let stack = DataStack() + let stack = DataStack(sourceBundles: NSBundle.allBundles() as? [NSBundle]) CoreStore.defaultStack = stack XCTAssert(CoreStore.defaultStack === stack, "CoreStore.defaultStack === stack") diff --git a/README.md b/README.md index e5db938..d4c4e40 100644 --- a/README.md +++ b/README.md @@ -3,25 +3,42 @@ [![Platform](https://img.shields.io/cocoapods/p/CoreStore.svg?style=flat)](http://cocoadocs.org/docsets/CoreStore) [![License](https://img.shields.io/cocoapods/l/CoreStore.svg?style=flat)](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE) -Simple, elegant, and smart Core Data programming with Swift +Unleashing the real power of Core Data with the elegance and safety of Swift (Swift, iOS 8+) [Click here for a wiki version of this README](https://github.com/JohnEstropia/CoreStore/wiki) +[Upgrading from 0.2.0 to 1.0.0](#changes0_2_0_to_1_0_0) -## What CoreStore does better: -- Heavily supports multiple persistent stores per data stack, just the way .xcdatamodeld files are designed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need. + +## Another Core Data wrapper? + +I have used (and abused) Core Data for almost 5 years. While the majority of Core Data wrappers serve their purpose really well (I worked with [MagicalRecord](https://github.com/magicalpanda/MagicalRecord) for a looong time), I have always felt that they "wrap" too much of the Core Data SDK's functionality. + +For example: +- a lot of iOS devs have never used (or heard of) "Configurations" +- very few are aware that entities can be saved in separate *sqlite* files to boost performance and reduce data corruption +- we're forced to name our `NSManagedObject` subclasses exactly the same as our Entities +- and so on... + +I wrote this library when Swift was made public, and CoreStore is now a powerhouse with functionalities rarely implemented in other Core Data libraries. + + +### What CoreStore does better: + +- Heavily supports multiple persistent stores per data stack, just the way *.xcdatamodeld* files are designed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need. - Ability to plug-in your own logging framework - Gets around a limitation with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you are free to name them independently. - Provides type-safe, easy to configure observers to replace `NSFetchedResultsController` and KVO - Exposes API not just for fetching, but also for querying aggregates and property values - Makes it hard to fall into common concurrency mistakes. All `NSManagedObjectContext` tasks are encapsulated into safer, higher-level abstractions without sacrificing flexibility and customizability. -- Provides convenient API for common use cases. -- Clean API designed around Swift’s code elegance and type safety. +- Exposes clean and convenient API designed around Swift’s code elegance and type safety. +- Documentation! No magic here; all public classes, functions, properties, etc. have detailed Apple Docs. This README also introduces a lot of concepts and explains a lot of CoreStore's behavior. -**CoreStore's goal is not to expose shorter, magical syntax, but to provide an API that prioritizes readability, consistency, and safety.** +**CoreStore's goal is not to expose shorter, magical syntax, but to provide an API that focuses on readability, consistency, and safety.** -#### TL;DR sample codes + +## TL;DR (a.k.a. sample codes) Quick-setup: ```swift @@ -67,21 +84,27 @@ let maxAge = CoreStore.queryValue( ) ``` -Check out the **CoreStoreDemo** app as well! +But really, there's a reason I wrote this huge README. Read up on the details! + +Check out the **CoreStoreDemo** app project for sample codes as well! ## Contents -- [Architecture](#architecture) -- [Setting up](#setup) -- [Saving and processing transactions](#transactions) -- [Fetching and querying](#fetch_query) -- [Logging and error handling](#logging) -- [Observing changes and notifications](#observing) +- Tutorials + - [Architecture](#architecture) + - [Setting up](#setup) + - [Saving and processing transactions](#transactions) + - [Fetching and querying](#fetch_query) + - [Logging and error handling](#logging) + - [Observing changes and notifications](#observing) +- [Roadmap](#roadmap) +- [Installation](#installation) + +(All of these have demos in the **CoreStoreDemo** app project!) - -## Architecture +## Architecture For maximum safety and performance, CoreStore will enforce coding patterns and practices it was designed for. (Don't worry, it's not as scary as it sounds.) But it is advisable to understand the "magic" of CoreStore before you use it in your apps. If you are already familiar with the inner workings of CoreData, here is a mapping of `CoreStore` abstractions: @@ -104,7 +127,7 @@ This allows for a butter-smooth main thread, while still taking advantage of saf -## Setting up +## Setting up The simplest way to initialize CoreStore is to add a default store to the default stack: ```swift CoreStore.addSQLiteStoreAndWait() @@ -140,7 +163,10 @@ case .Failure(let error): // error is an NSError instance CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on ``` -Note that you dont need to do the `CoreStore.defaultStack = dataStack` line. You can just as well hold a stack like below and call all methods directly from the `DataStack` instance: +(If you have never heard of "Configurations", you'll find them in your *.xcdatamodeld* file) +xcode configurations screenshot + +In our sample above, note that you don't need to do the `CoreStore.defaultStack = dataStack` line. You can just as well hold a reference to the `DataStack` like below and call all its instance methods directly: ```swift class MyViewController: UIViewController { let dataStack = DataStack(modelName: "MyModel") @@ -170,7 +196,7 @@ class MyViewController: UIViewController { -## Saving and processing transactions +## Saving and processing transactions To ensure deterministic state for objects in the read-only `NSManagedObjectContext`, CoreStore does not expose API's for updating and saving directly from the main context (or any other context for that matter.) Instead, you spawn *transactions* from `DataStack` instances: ```swift let dataStack = self.dataStack @@ -190,7 +216,10 @@ The `commit()` method saves the changes to the persistent store. If `commit()` i The examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at you disposal: *asynchronous*, *synchronous*, and *detached*. -**Asynchronous transactions** are spawned from `beginAsynchronous(...)`. This method returns immediately and executes its closure from a background serial queue: +### Transaction types + +#### Asynchronous transactions +are spawned from `beginAsynchronous(...)`. This method returns immediately and executes its closure from a background serial queue: ```swift CoreStore.beginAsynchronous { (transaction) -> Void in // make changes @@ -199,7 +228,8 @@ CoreStore.beginAsynchronous { (transaction) -> Void in ``` Transactions created from `beginAsynchronous(...)` are instances of `AsynchronousDataTransaction`. -**Synchronous transactions** are created from `beginSynchronous(...)`. While the syntax is similar to its asynchronous counterpart, `beginSynchronous(...)` waits for its transaction block to complete before returning: +#### Synchronous transactions +are created from `beginSynchronous(...)`. While the syntax is similar to its asynchronous counterpart, `beginSynchronous(...)` waits for its transaction block to complete before returning: ```swift CoreStore.beginSynchronous { (transaction) -> Void in // make changes @@ -210,7 +240,8 @@ CoreStore.beginSynchronous { (transaction) -> Void in Since `beginSynchronous(...)` technically blocks two queues (the caller's queue and the transaction's background queue), it is considered less safe as it's more prone to deadlock. Take special care that the closure does not block on any other external queues. -**Detached transactions** are special in that they do not enclose updates within a closure: +#### Detached transactions +are special in that they do not enclose updates within a closure: ```swift let transaction = CoreStore.beginDetached() // make changes @@ -233,6 +264,7 @@ As the above example also shows, only detached transactions are allowed to call You've seen how to create transactions, but we have yet to see how to make *creates*, *updates*, and *deletes*. The 3 types of transactions above are all subclasses of `BaseDataTransaction`, which implements the methods shown below. ### Creating objects + The `create(...)` method accepts an `Into` clause which specifies the entity for the object you want to create: ```swift let person = transaction.create(Into(MyPersonEntity)) @@ -241,7 +273,7 @@ While the syntax is straightforward, CoreStore does not just naively insert a ne - Checks that the entity type exists in any of the transaction's parent persistent store - If the entity belongs to only one persistent store, a new object is inserted into that store and returned from `create(...)` - If the entity does not belong to any store, an assert will be triggered. **This is a programmer error and should never occur in production code.** -- If the entity belongs to multiple stores, an assert will be triggered. **This is also a programmer error and should never occur in production code.** Normally, with Core Data you can insert an object in this state but saving the `NSManagedObjectContext` will always fail. CoreStore checks this for you at creation time where it makes sense (not during save). +- If the entity belongs to multiple stores, an assert will be triggered. **This is also a programmer error and should never occur in production code.** Normally, with Core Data you can insert an object in this state but saving the `NSManagedObjectContext` will always fail. CoreStore checks this for you at creation time when it makes sense (not during save). If the entity exists in multiple configurations, you need to provide the configuration name for the destination persistent store: @@ -251,7 +283,7 @@ or if the persistent store is the auto-generated "Default" configuration, specif let person = transaction.create(Into(nil)) -Note that if you do explicitly specify the configuration name, CoreStore will only try to insert the created object to that particular store and will fail if that store is not found; it will not fall back to any other store the entity belongs to. +Note that if you do explicitly specify the configuration name, CoreStore will only try to insert the created object to that particular store and will fail if that store is not found; it will not fall back to any other configuration that the entity belongs to. ### Updating objects @@ -303,9 +335,10 @@ CoreStore.beginAsynchronous { (transaction) -> Void in transaction.commit() } ``` + ### Deleting objects -Deleting an object is simpler as you can tell a transaction to delete an object directly without fetching an editable proxy (CoreStore does that for you): +Deleting an object is simpler because you can tell a transaction to delete an object directly without fetching an editable proxy (CoreStore does that for you): ```swift let john: MyPersonEntity = // ... @@ -335,7 +368,8 @@ CoreStore.beginAsynchronous { (transaction) -> Void in transaction.commit() } ``` -## Fetching and querying + +## Fetching and Querying Before we dive in, be aware that CoreStore distinguishes between *fetching* and *querying*: - A *fetch* executes searches from a specific *transaction* or *data stack*. This means fetches can include pending objects (i.e. before a transaction calls on `commit()`.) Use fetches when: - results need to be `NSManagedObject` instances @@ -343,9 +377,10 @@ Before we dive in, be aware that CoreStore distinguishes between *fetching* and - A *query* pulls data straight from the persistent store. This means faster searches when computing aggregates such as *count*, *min*, *max*, etc. Use queries when: - you need to compute aggregate functions (see below for a list of supported functions) - results can be raw values like `NSString`s, `NSNumber`s, `Int`s, `NSDate`s, an `NSDictionary` of key-values, etc. - - only specific attribute keys need to be included in the results + - only values for specified attribute keys need to be included in the results - unsaved objects should be ignored +#### `From` clause The search conditions for fetches and queries are specified using *clauses*. All fetches and queries require a `From` clause that indicates the target entity type: ```swift let people = CoreStore.fetchAll(From(MyPersonEntity)) @@ -353,7 +388,7 @@ let people = CoreStore.fetchAll(From(MyPersonEntity)) ``` `people` in the example above will be of type `[MyPersonEntity]`. The `From(MyPersonEntity)` clause indicates a fetch to all persistent stores that `MyPersonEntity` belong to. -If the entity exists in multiple configurations and you need to only search from a particular configuration, provide the `From` clause the configuration name for the destination persistent store: +If the entity exists in multiple configurations and you need to only search from a particular configuration, indicate in the `From` clause the configuration name for the destination persistent store: ```swift let people = CoreStore.fetchAll(From("Config1")) // ignore objects in persistent stores other than the "Config1" configuration ``` @@ -363,19 +398,19 @@ let person = CoreStore.fetchAll(From(nil)) ``` Now we know how to use a `From` clause, let's move on to fetching and querying. -#### Fetching +### Fetching There are currently 5 fetch methods you can call from `CoreStore`, from a `DataStack` instance, or from a `BaseDataTransaction` instance. All of the methods below accept the same parameters: a required `From` clause, and an optional series of `Where`, `OrderBy`, and/or `Tweak` clauses. -- `fetchAll(_:_:)` - returns an array of all objects that match the criteria. -- `fetchOne(_:_:)` - returns the first object that match the criteria. -- `fetchCount(_:_:)` - returns the number of objects that match the criteria. -- `fetchObjectIDs(_:_:)`` - returns an array of `NSManagedObjectID`s for all objects that match the criteria. -- `fetchObjectID(_:_:)` - returns the `NSManagedObjectID`s for the first objects that match the criteria. +- `fetchAll(...)` - returns an array of all objects that match the criteria. +- `fetchOne(...)` - returns the first object that match the criteria. +- `fetchCount(...)` - returns the number of objects that match the criteria. +- `fetchObjectIDs(...)`` - returns an array of `NSManagedObjectID`s for all objects that match the criteria. +- `fetchObjectID(...)` - returns the `NSManagedObjectID`s for the first objects that match the criteria. Each method's purpose is straightforward, but we need to understand how to set the clauses for the fetch. -**`Where` clause** +#### `Where` clause The `Where` clause is CoreStore's `NSPredicate` wrapper. It specifies the search filter to use when fetching (or querying). It implements all initializers that `NSPredicate` does (except for `-predicateWithBlock:`, which Core Data does not support): ```swift @@ -396,7 +431,7 @@ var people = CoreStore.fetchAll( Where(predicate) // predicate initializer ) ``` -`Where` clauses also implement the `&&`, `||`, and `!` logic operators, so you can provide logical conditions without writing too much `AND`, `OR`, and `NOT` strings in the conditions: +`Where` clauses also implement the `&&`, `||`, and `!` logic operators, so you can provide logical conditions without writing too much `AND`, `OR`, and `NOT` strings: ```swift var people = CoreStore.fetchAll( From(MyPersonEntity), @@ -405,7 +440,7 @@ var people = CoreStore.fetchAll( ``` If you do not provide a `Where` clause, all objects that belong to the specified `From` will be returned. -**`OrderBy` clause** +#### `OrderBy` clause The `OrderBy` clause is CoreStore's `NSSortDescriptor` wrapper. Use it to specify attribute keys in which to sort the fetch (or query) results with. ```swift @@ -428,9 +463,9 @@ var mostValuablePeople = CoreStore.fetchAll( ) ``` -**`Tweak` clause** +#### `Tweak` clause -The `Tweak` clause lets you, well, *tweak* the fetch (or query). `Tweak` exposes the `NSFetchRequest` in a closure where you can make changes to its properties: +The `Tweak` clause lets you, uh, *tweak* the fetch (or query). `Tweak` exposes the `NSFetchRequest` in a closure where you can make changes to its properties: ```swift var people = CoreStore.fetchAll( From(MyPersonEntity), @@ -446,21 +481,22 @@ var people = CoreStore.fetchAll( The clauses are evaluated the order they appear in the fetch/query, so you typically need to set `Tweak` as the last clause. `Tweak`'s closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions. -Do note that while `Tweak` lets you micro-configure its `NSFetchRequest`, don't forget that CoreStore already preconfigured that `NSFetchRequest` to suitable defaults. Only use `Tweak` when you know what you are doing! +While `Tweak` lets you micro-configure the `NSFetchRequest`, note that CoreStore already preconfigured that `NSFetchRequest` to suitable defaults. Only use `Tweak` when you know what you are doing! -#### Querying -One of the functionalities overlooked by other Core Data wrapper libraries is raw properties fetching. If you are familiar with `NSDictionaryResultType` and `-[NSFetchedRequest propertiesToFetch]`, you probably know how painful it is to setup a query for raw values and aggregate values. CoreStore makes querying easy by exposing the 2 methods below: +### Querying -- `queryValue(_:_:_:)` - returns a single raw value for an attribute or for an aggregate value. If there are multiple results, `queryValue(...)` only returns the first item. -- `queryAttributes(_:_:_:)` - returns an array of dictionaries containing attribute keys with their corresponding values. +One of the functionalities overlooked by other Core Data wrapper libraries is raw properties fetching. If you are familiar with `NSDictionaryResultType` and `-[NSFetchedRequest propertiesToFetch]`, you probably know how painful it is to setup a query for raw values and aggregate values. CoreStore makes this easy by exposing the 2 methods below: + +- `queryValue(...)` - returns a single raw value for an attribute or for an aggregate value. If there are multiple results, `queryValue(...)` only returns the first item. +- `queryAttributes(...)` - returns an array of dictionaries containing attribute keys with their corresponding values. Both methods above accept the same parameters: a required `From` clause, a required `Select` clause, and an optional series of `Where`, `OrderBy`, `GroupBy`, and/or `Tweak` clauses. Setting up the `From`, `Where`, `OrderBy`, and `Tweak` clauses is similar to how you would when fetching. For querying, you also need to know how to use the `Select` and `GroupBy` clauses. -**`Select` clause** +#### `Select` clause -The `Select` clause specifies the target attribute/aggregate key and the return type: +The `Select` clause specifies the target attribute/aggregate key, as well as the expected return type: ```swift let johnsAge = CoreStore.queryValue( From(MyPersonEntity), @@ -468,7 +504,7 @@ let johnsAge = CoreStore.queryValue( Where("name == %@", "John Smith") ) ``` -The example above queries the "age" property for the first object that matches the `Where` condition. `johnsAge` will be bound to type `Int?`, as indicated by the `Select` generic type. For `queryValue(...)`, the following are allowed as the return type (and as the generic type for `Select`): +The example above queries the "age" property for the first object that matches the `Where` condition. `johnsAge` will be bound to type `Int?`, as indicated by the `Select` generic type. For `queryValue(...)`, the following are allowed as the return type (and therefore as the generic type for `Select`): - `Bool` - `Int8` - `Int16` @@ -485,7 +521,7 @@ The example above queries the "age" property for the first object that matches t - `NSManagedObjectID` - `NSString` -For `queryAttributes(...)`, only `NSDictionary` is valid for `Select`, thus you are allowed omit the generic type: +For `queryAttributes(...)`, only `NSDictionary` is valid for `Select`, thus you are allowed to omit the generic type: ```swift let allAges = CoreStore.queryAttributes( From(MyPersonEntity), @@ -515,7 +551,7 @@ let personJSON = CoreStore.queryAttributes( ) ``` `personJSON` will then have the value: -```json +```swift [ [ "name": "John Smith", @@ -568,7 +604,7 @@ which now returns: ] ``` -**`GroupBy` clause** +#### `GroupBy` clause The `GroupBy` clause lets you group results by a specified attribute/aggregate. This is useful only for `queryAttributes(...)` since `queryValue(...)` just returns the first value. ```swift @@ -592,7 +628,7 @@ this returns dictionaries that shows the count for each `"age"`: ] ``` -## Logging and error handling +## Logging and error handling One unfortunate thing when using some third-party libraries is that they usually pollute the console with their own logging mechanisms. CoreStore provides its own default logging class, but you can plug-in your own favorite logger by implementing the `CoreStoreLogger` protocol. ```swift final class MyLogger: CoreStoreLogger { @@ -615,73 +651,84 @@ CoreStore.logger = MyLogger() ``` Doing so channels all logging calls to your logger. -Note that to keep the call stack information intact, all calls to these methods are not thread-managed. Thus you have to make sure that your logger is thread-safe or you may otherwise have to dispatch your logging implementation to a serial queue. +Note that to keep the call stack information intact, all calls to these methods are **NOT** thread-managed. Therefore you have to make sure that your logger is thread-safe or you may otherwise have to dispatch your logging implementation to a serial queue. -## Observing changes and notifications +## Observing changes and notifications CoreStore provides type-safe wrappers for observing managed objects: -- `ManagedObjectController`: use to observe changes to a single `NSManagedObject` instance (instead of Key-Value Observing) -- `ManagedObjectListController`: use to observe changes to a list of `NSManagedObject` instances (instead of `NSFetchedResultsController`) +- `ObjectMonitor`: use to monitor changes to a single `NSManagedObject` instance (instead of Key-Value Observing) +- `ListMonitor`: use to monitor changes to a list of `NSManagedObject` instances (instead of `NSFetchedResultsController`) -#### Observe a single object +### Observe a single object -To observe an object, implement the `ManagedObjectObserver` protocol and specify the `EntityType`: +To observe an object, implement the `ObjectObserver` protocol and specify the `EntityType`: ```swift -class MyViewController: UIViewController, ManagedObjectObserver { - func managedObjectWillUpdate(objectController: ManagedObjectController, object: MyPersonEntity) { +class MyViewController: UIViewController, ObjectObserver { + func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: MyPersonEntity) { // ... } - func managedObjectWasUpdated(objectController: ManagedObjectController, object: MyPersonEntity, changedPersistentKeys: Set) { + func objectMonitor(monitor: ObjectMonitor, didUpdateObject object: MyPersonEntity, changedPersistentKeys: Set) { // ... } - func managedObjectWasDeleted(objectController: ManagedObjectController, object: MyPersonEntity) { + func objectMonitor(monitor: ObjectMonitor, didDeleteObject object: MyPersonEntity) { // ... } } ``` -We then need to keep a `ManagedObjectController` instance and register our `ManagedObjectObserver` as an observer: +We then need to keep a `ObjectMonitor` instance and register our `ObjectObserver` as an observer: ```swift let person: MyPersonEntity = // ... -self.objectController = CoreStore.observeObject(person) -self.objectController.addObserver(self) +self.monitor = CoreStore.monitorObject(person) +self.monitor.addObserver(self) ``` -The controller will then notify our observer whenever the object's attributes change. You can add multiple `ManagedObjectObserver`s to a single `ManagedObjectController` without any problem. This means you can just share around the `ManagedObjectController` instance to different screens without problem. +The controller will then notify our observer whenever the object's attributes change. You can add multiple `ObjectObserver`s to a single `ObjectMonitor` without any problem. This means you can just share around the `ObjectMonitor` instance to different screens without problem. -You can get `ManagedObjectController`'s object through its `object` property. If the object is deleted, the `object` property will become `nil` to prevent further access. +You can get `ObjectMonitor`'s object through its `object` property. If the object is deleted, the `object` property will become `nil` to prevent further access. -While `ManagedObjectController` exposes `removeObserver(...)` as well, it only stores `weak` references of the observers and will safely unregister deallocated observers. +While `ObjectMonitor` exposes `removeObserver(...)` as well, it only stores `weak` references of the observers and will safely unregister deallocated observers. -#### Observe a list of objects -To observe a list of objects, implement one of the `ManagedObjectListChangeObserver` protocols and specify the `EntityType`: +### Observe a list of objects +To observe a list of objects, implement one of the `ListObserver` protocols and specify the `EntityType`: ```swift -class MyViewController: UIViewController, ManagedObjectListChangeObserver { - func managedObjectListWillChange(listController: ManagedObjectListController) { +class MyViewController: UIViewController, ListObserver { + func listMonitorWillChange(monitor: ListMonitor) { // ... } - func managedObjectListDidChange(listController: ManagedObjectListController) { + func listMonitorDidChange(monitor: ListMonitor) { // ... } } ``` -Including `ManagedObjectListChangeObserver`, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification: -- `ManagedObjectListChangeObserver`: lets you handle these callback methods: - - `func managedObjectListWillChange(listController: ManagedObjectListController)` - - `func managedObjectListDidChange(listController: ManagedObjectListController)` -- `ManagedObjectListObjectObserver`: in addition to `ManagedObjectListChangeObserver` methods, also lets you handle object inserts, updates, and deletes: - - `func managedObjectList(listController: ManagedObjectListController, didInsertObject object: T, toIndexPath indexPath: NSIndexPath)` - - `func managedObjectList(listController: ManagedObjectListController, didDeleteObject object: T, fromIndexPath indexPath: NSIndexPath)` - - `func managedObjectList(listController: ManagedObjectListController, didUpdateObject object: T, atIndexPath indexPath: NSIndexPath)` - - `func managedObjectList(listController: ManagedObjectListController, didMoveObject object: T, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)` -- `ManagedObjectListSectionObserver`: in addition to `ManagedObjectListObjectObserver` methods, also lets you handle section inserts and deletes: - - `func managedObjectList(listController: ManagedObjectListController, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)` - - `func managedObjectList(listController: ManagedObjectListController, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)` - -We then need to create a `ManagedObjectListController` instance and register our `ManagedObjectListChangeObserver` as an observer: +Including `ListObserver`, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification: +- `ListObserver`: lets you handle these callback methods: ```swift -self.listController = CoreStore.observeObjectList( + func listMonitorWillChange(monitor: ListMonitor) + + func listMonitorDidChange(monitor: ListMonitor) +``` +- `ListObjectObserver`: in addition to `ListObserver` methods, also lets you handle object inserts, updates, and deletes: +```swift + func listMonitor(monitor: ListMonitor, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath) + + func listMonitor(monitor: ListMonitor, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath) + + func listMonitor(monitor: ListMonitor, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath) + + func listMonitor(monitor: ListMonitor, didMoveObject object: MyPersonEntity, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) +``` +- `ListSectionObserver`: in addition to `ListObjectObserver` methods, also lets you handle section inserts and deletes: +```swift + func listMonitor(monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) + + func listMonitor(monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) +``` + +We then need to create a `ListMonitor` instance and register our `ListObserver` as an observer: +```swift +self.monitor = CoreStore.monitorList( From(MyPersonEntity), Where("age > 30"), OrderBy(.Ascending("name")), @@ -689,22 +736,22 @@ self.listController = CoreStore.observeObjectList( fetchRequest.fetchBatchSize = 20 } ) -self.listController.addObserver(self) +self.monitor.addObserver(self) ``` -Similar to `ManagedObjectController`, a `ManagedObjectListController` can also have multiple `ManagedObjectListChangeObserver`s registered to a single `ManagedObjectListController`. +Similar to `ObjectMonitor`, a `ListMonitor` can also have multiple `ListObserver`s registered to a single `ListMonitor`. -If you have noticed, the `observeObjectList(...)` method accepts `Where`, `OrderBy`, and `Tweak` clauses exactly like a fetch. As the list maintained by `ManagedObjectListController` needs to have a deterministic order, at least the `From` and `OrderBy` clauses are required. +If you have noticed, the `monitorList(...)` method accepts `Where`, `OrderBy`, and `Tweak` clauses exactly like a fetch. As the list maintained by `ListMonitor` needs to have a deterministic order, at least the `From` and `OrderBy` clauses are required. -A `ManagedObjectListController` created from `observeObjectList(...)` will maintain a single-section list. You can therefore access its contents with just an index: +A `ListMonitor` created from `monitorList(...)` will maintain a single-section list. You can therefore access its contents with just an index: ```swift -let firstPerson = self.listController[0] +let firstPerson = self.monitor[0] ``` -If the list needs to be grouped into sections, create the `ManagedObjectListController` instance with the `observeSectionedList(...)` method and a `SectionedBy` clause: +If the list needs to be grouped into sections, create the `ListMonitor` instance with the `monitorSectionedList(...)` method and a `SectionBy` clause: ```swift -self.listController = CoreStore.observeSectionedList( +self.monitor = CoreStore.monitorSectionedList( From(MyPersonEntity), - SectionedBy("age"), + SectionBy("age"), Where("gender", isEqualTo: "M"), OrderBy(.Ascending("age"), .Ascending("name")), Tweak { (fetchRequest) -> Void in @@ -712,13 +759,13 @@ self.listController = CoreStore.observeSectionedList( } ) ``` -A list controller created this way will group the objects by the attribute key indicated by the `SectionedBy` clause. One more thing to remember is that the `OrderBy` clause should sort the list in such a way that the `SectionedBy` attribute would be sorted together (a requirement shared by `NSFetchedResultsController`.) +A list controller created this way will group the objects by the attribute key indicated by the `SectionBy` clause. One more thing to remember is that the `OrderBy` clause should sort the list in such a way that the `SectionBy` attribute would be sorted together (a requirement shared by `NSFetchedResultsController`.) -The `SectionedBy` clause can also be passed a closure to transform the section name into a displayable string: +The `SectionBy` clause can also be passed a closure to transform the section name into a displayable string: ```swift -self.listController = CoreStore.observeSectionedList( +self.monitor = CoreStore.monitorSectionedList( From(MyPersonEntity), - SectionedBy("age") { (sectionName) -> String? in + SectionBy("age") { (sectionName) -> String? in "\(sectionName) years old" }, OrderBy(.Ascending("age"), .Ascending("name")) @@ -727,7 +774,7 @@ self.listController = CoreStore.observeSectionedList( This is useful when implementing a `UITableViewDelegate`'s section header: ```swift func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - let sectionInfo = self.listController.sectionInfoAtIndex(section) + let sectionInfo = self.monitor.sectionInfoAtIndex(section) // sectionInfo is an NSFetchedResultsSectionInfo instance return sectionInfo.name } @@ -736,20 +783,37 @@ func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> To access the objects of a sectioned list, use an `NSIndexPath` or a tuple: ```swift let indexPath = NSIndexPath(forRow: 2, inSection: 1) -let person1 = self.listController[indexPath] -let person2 = self.listController[1, 2] +let person1 = self.monitor[indexPath] +let person2 = self.monitor[1, 2] // person1 and person2 are the same object ``` -# TODO +# Changes from v0.2.0 to 1.0.0 +- Renamed some classes/protocols to shorter, more relevant, easier to remember names: + - `ManagedObjectController` to `ObjectMonitor` + - `ManagedObjectObserver` to `ObjectObserver` + - `ManagedObjectListController` to `ListMonitor` + - `ManagedObjectListChangeObserver` to `ListObserver` + - `ManagedObjectListObjectObserver` to `ListObjectObserver` + - `ManagedObjectListSectionObserver` to `ListSectionObserver` + - `SectionedBy` to `SectionBy` (match tense with `OrderBy` and `GroupBy`) +The protocols above had their methods renamed as well, to retain the natural language semantics. +- New migration utilities! (README still pending) Check out *DataStack+Migration.swift* and *CoreStore+Migration.swift* for the new methods. + + +# Roadmap +- Migration utilities (In progress!) +- Swift 2.0 syntax (In progress!) - Data importing utilities for transactions -- Migration utilities - Support iCloud stores # Installation -- Requires iOS 8 SDK and above -- Swift 1.2 +- Requires: + - iOS 8 SDK and above + - Swift 1.2 +- Dependencies: + - [GCDKit](https://github.com/JohnEstropia/GCDKit) ### Install with Cocoapods ``` @@ -761,8 +825,11 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift ``` git submodule add https://github.com/JohnEstropia/CoreStore.git ``` +Drag and drop **CoreStore.xcodeproj** to your project. + #### To install as a framework: Drag and drop **CoreStore.xcodeproj** to your project. + #### To include directly in your app module: Add all *.swift* files to your project. From 261c3a6001424532f85c09583e940ccf0bbe0af7 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Fri, 3 Jul 2015 00:22:21 +0900 Subject: [PATCH 02/19] minor changes before Swift 2.0 --- CoreStore/Migrating/DataStack+Migration.swift | 4 +- CoreStore/Migrating/MigrationChain.swift | 44 ++++++++++++------- CoreStore/Setting Up/DataStack.swift | 5 ++- .../xcshareddata/CoreStoreDemo.xccheckout | 4 +- README.md | 4 +- 5 files changed, 39 insertions(+), 22 deletions(-) diff --git a/CoreStore/Migrating/DataStack+Migration.swift b/CoreStore/Migrating/DataStack+Migration.swift index 33c4135..3f7c4d8 100644 --- a/CoreStore/Migrating/DataStack+Migration.swift +++ b/CoreStore/Migrating/DataStack+Migration.swift @@ -48,7 +48,7 @@ public extension DataStack { isDirectory: false ), configuration: configuration, - sourceBundles: sourceBundles + mappingModelBundles: mappingModelBundles ) } @@ -57,7 +57,7 @@ public extension DataStack { :param: fileURL the local file URL for the SQLite persistent store. :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. - :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. + :param: mappingModelBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. :return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed. */ public func needsMigrationForSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as! [NSBundle]) -> MigrationType? { diff --git a/CoreStore/Migrating/MigrationChain.swift b/CoreStore/Migrating/MigrationChain.swift index c6a1877..3cc563e 100644 --- a/CoreStore/Migrating/MigrationChain.swift +++ b/CoreStore/Migrating/MigrationChain.swift @@ -35,8 +35,14 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D public func contains(version: String) -> Bool { - return self.latestVersion == version - || find(self.versionTree.keys, version) != nil + return self.rootVersions.contains(version) + || self.leafVersions.contains(version) + || self.versionTree[version] != nil + } + + public func nextVersionFrom(version: String) -> String? { + + return self.versionTree[version] } @@ -44,8 +50,9 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D public init(nilLiteral: ()) { - self.latestVersion = nil self.versionTree = [:] + self.rootVersions = [] + self.leafVersions = [] } @@ -53,16 +60,18 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D public init(stringLiteral value: String) { - self.latestVersion = value self.versionTree = [:] + self.rootVersions = [value] + self.leafVersions = [value] } // MARK: ExtendedGraphemeClusterLiteralConvertible public init(extendedGraphemeClusterLiteral value: String) { - self.latestVersion = value self.versionTree = [:] + self.rootVersions = [value] + self.leafVersions = [value] } @@ -70,8 +79,9 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D public init(unicodeScalarLiteral value: String) { - self.latestVersion = value self.versionTree = [:] + self.rootVersions = [value] + self.leafVersions = [value] } @@ -84,15 +94,17 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D versionTree[tuple.0] = tuple.1 return versionTree } - let latestVersions = elements.filter { (tuple: (String, String)) -> Bool in - - return versionTree[tuple.1] == nil - } + let leafVersions = Set( + elements.filter { (tuple: (String, String)) -> Bool in + + return versionTree[tuple.1] == nil + + }.map { $1 } + ) - CoreStore.assert(latestVersions.count == 1, "\(typeName(MigrationChain))'s migration chain could not be resolved due to multiple leaf versions.") - - self.latestVersion = latestVersions.first?.1 self.versionTree = versionTree + self.rootVersions = Set(versionTree.keys).subtract(versionTree.values) + self.leafVersions = leafVersions } @@ -114,13 +126,15 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D } self.versionTree = versionTree - self.latestVersion = elements.last + self.rootVersions = Set(flatMap([elements.first]) { $0 == nil ? [] : [$0!] }) + self.leafVersions = Set(flatMap([elements.last]) { $0 == nil ? [] : [$0!] }) } // MARK: Internal - internal let latestVersion: String? + internal let rootVersions: Set + internal let leafVersions: Set // MARK: Private diff --git a/CoreStore/Setting Up/DataStack.swift b/CoreStore/Setting Up/DataStack.swift index c05d89b..e5a7597 100644 --- a/CoreStore/Setting Up/DataStack.swift +++ b/CoreStore/Setting Up/DataStack.swift @@ -57,7 +57,10 @@ public final class DataStack { modelName, ofType: "momd" ) - CoreStore.assert(modelFilePath != nil, "Could not find a \"momd\" resource from the main bundle.") + CoreStore.assert( + modelFilePath != nil, + "Could not find a \"momd\" resource from the main bundle." + ) let managedObjectModel: NSManagedObjectModel! = NSManagedObjectModel(contentsOfURL: NSURL(fileURLWithPath: modelFilePath)!) CoreStore.assert( diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout index a385032..ca9ddfb 100644 --- a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout @@ -7,7 +7,7 @@ IDESourceControlProjectIdentifier B6855E48-4B19-4321-B1C7-CB2706E12777 IDESourceControlProjectName - project + CoreStoreDemo IDESourceControlProjectOriginsDictionary 4B60F1BCB491FF717C56441AE7783C74F417BE48 @@ -16,7 +16,7 @@ github.com:JohnEstropia/GCDKit.git IDESourceControlProjectPath - CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace + CoreStoreDemo/CoreStoreDemo.xcodeproj IDESourceControlProjectRelativeInstallPathDictionary 4B60F1BCB491FF717C56441AE7783C74F417BE48 diff --git a/README.md b/README.md index 73e933c..560af1c 100644 --- a/README.md +++ b/README.md @@ -406,7 +406,7 @@ There are currently 5 fetch methods you can call from `CoreStore`, from a `DataS - `fetchAll(...)` - returns an array of all objects that match the criteria. - `fetchOne(...)` - returns the first object that match the criteria. - `fetchCount(...)` - returns the number of objects that match the criteria. -- `fetchObjectIDs(...)`` - returns an array of `NSManagedObjectID`s for all objects that match the criteria. +- `fetchObjectIDs(...)` - returns an array of `NSManagedObjectID`s for all objects that match the criteria. - `fetchObjectID(...)` - returns the `NSManagedObjectID`s for the first objects that match the criteria. Each method's purpose is straightforward, but we need to understand how to set the clauses for the fetch. @@ -450,7 +450,7 @@ var mostValuablePeople = CoreStore.fetchAll( OrderBy(.Descending("rating"), .Ascending("surname")) ) ``` -As seen above, `OrderBy` accepts a list of `SortKey` enumeration values, which can be either `.Ascending` or `.Descending`. The associated value for the `SortKey` enumeration is the attribute key string. +As seen above, `OrderBy` accepts a list of `SortKey` enumeration values, which can be either `.Ascending` or `.Descending`. You can use the `+` and `+=` operator to append `OrderBy`s together. This is useful when sorting conditionally: ```swift From bf0eebe0574cfcf40a55956308ed0c80b8d004ba Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Tue, 7 Jul 2015 07:58:16 +0900 Subject: [PATCH 03/19] migration utilities (beta) --- .../DataStack+Querying.swift | 162 +++-- .../Internal/NSManagedObjectModel+Setup.swift | 99 +++ .../Internal/RootManagedObjectModel.swift | 13 + CoreStore/Migrating/DataStack+Migration.swift | 612 ++++++++++-------- CoreStore/Observing/DataStack+Observing.swift | 47 +- .../DataStack+Transaction.swift | 23 +- CoreStore/Setting Up/DataStack.swift | 250 +++---- 7 files changed, 748 insertions(+), 458 deletions(-) create mode 100644 CoreStore/Internal/NSManagedObjectModel+Setup.swift create mode 100644 CoreStore/Internal/RootManagedObjectModel.swift diff --git a/CoreStore/Fetching and Querying/DataStack+Querying.swift b/CoreStore/Fetching and Querying/DataStack+Querying.swift index befdbe5..cdf57a9 100644 --- a/CoreStore/Fetching and Querying/DataStack+Querying.swift +++ b/CoreStore/Fetching and Querying/DataStack+Querying.swift @@ -37,13 +37,16 @@ public extension DataStack { /** Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s */ public func fetchOne(from: From, _ fetchClauses: FetchClause...) -> T? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchOne(from, fetchClauses) } @@ -51,13 +54,16 @@ public extension DataStack { /** Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s */ public func fetchOne(from: From, _ fetchClauses: [FetchClause]) -> T? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchOne(from, fetchClauses) } @@ -65,13 +71,16 @@ public extension DataStack { /** Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s */ public func fetchAll(from: From, _ fetchClauses: FetchClause...) -> [T]? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchAll(from, fetchClauses) } @@ -79,13 +88,16 @@ public extension DataStack { /** Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s */ public func fetchAll(from: From, _ fetchClauses: [FetchClause]) -> [T]? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchAll(from, fetchClauses) } @@ -93,13 +105,16 @@ public extension DataStack { /** Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchCount(from: From, _ fetchClauses: FetchClause...) -> Int? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchCount(from, fetchClauses) } @@ -107,13 +122,16 @@ public extension DataStack { /** Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchCount(from: From, _ fetchClauses: [FetchClause]) -> Int? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchCount(from, fetchClauses) } @@ -121,13 +139,16 @@ public extension DataStack { /** Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s */ public func fetchObjectID(from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchObjectID(from, fetchClauses) } @@ -135,13 +156,16 @@ public extension DataStack { /** Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s */ public func fetchObjectID(from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchObjectID(from, fetchClauses) } @@ -149,13 +173,16 @@ public extension DataStack { /** Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchObjectIDs(from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchObjectIDs(from, fetchClauses) } @@ -163,13 +190,16 @@ public extension DataStack { /** Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchObjectIDs(from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to fetch from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to fetch from a \(typeName(self)) outside the main thread." + ) return self.mainContext.fetchObjectIDs(from, fetchClauses) } @@ -179,14 +209,17 @@ public extension DataStack { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public func queryValue(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> U? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to query from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to query from a \(typeName(self)) outside the main thread." + ) return self.mainContext.queryValue(from, selectClause, queryClauses) } @@ -196,14 +229,17 @@ public extension DataStack { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public func queryValue(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> U? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to query from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to query from a \(typeName(self)) outside the main thread." + ) return self.mainContext.queryValue(from, selectClause, queryClauses) } @@ -213,14 +249,17 @@ public extension DataStack { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to query from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to query from a \(typeName(self)) outside the main thread." + ) return self.mainContext.queryAttributes(from, selectClause, queryClauses) } @@ -230,14 +269,17 @@ public extension DataStack { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to query from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to query from a \(typeName(self)) outside the main thread." + ) return self.mainContext.queryAttributes(from, selectClause, queryClauses) } diff --git a/CoreStore/Internal/NSManagedObjectModel+Setup.swift b/CoreStore/Internal/NSManagedObjectModel+Setup.swift new file mode 100644 index 0000000..50ce421 --- /dev/null +++ b/CoreStore/Internal/NSManagedObjectModel+Setup.swift @@ -0,0 +1,99 @@ +// +// NSManagedObjectModel+Setup.swift +// CoreStore +// +// 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: - NSManagedObjectModel + +internal extension NSManagedObjectModel { + + // MARK: Internal + + func entityNameForClass(entityClass: AnyClass) -> String { + + return self.entityNameMapping[NSStringFromClass(entityClass)]! + } + + func configurationsForClass(entityClass: AnyClass) -> Set { + + return self.entityConfigurationsMapping[NSStringFromClass(entityClass)]! + } + + + // MARK: Private + + + internal var entityNameMapping: [String: String] { + + get { + + if let mapping: NSDictionary? = getAssociatedObjectForKey(&PropertyKeys.entityNameMapping, inObject: self) { + + return mapping as! [String: String] + } + + let mapping = self.entities.reduce([String: String]()) { + (var mapping, entityDescription) -> [String: String] in + + if let entityName = entityDescription.name { + + let className = entityDescription.managedObjectClassName + mapping[className] = entityName + } + return mapping + } + setAssociatedCopiedObject( + mapping as NSDictionary, + forKey: &PropertyKeys.entityNameMapping, + inObject: self + ) + return mapping + } + } + + private lazy var entityConfigurationsMapping: [String: Set] = { + [unowned self] in + + return self.configurations.reduce([String: Set]()) { + (var mapping, configuration) -> [String: Set] in + + return (self.entitiesForConfiguration(configuration) ?? []).reduce(mapping) { + (var mapping, entityDescription) -> [String: Set] in + + let className = entityDescription.managedObjectClassName + mapping[className]?.insert(configuration) + return mapping + } + } + }() + + private struct PropertyKeys { + + static var entityNameMapping: Void? + static var entityConfigurationsMapping: Void? + } +} diff --git a/CoreStore/Internal/RootManagedObjectModel.swift b/CoreStore/Internal/RootManagedObjectModel.swift new file mode 100644 index 0000000..f9246e4 --- /dev/null +++ b/CoreStore/Internal/RootManagedObjectModel.swift @@ -0,0 +1,13 @@ +// +// RootManagedObjectModel.swift +// CoreStore +// +// Created by John Rommel Estropia on 2015/07/04. +// Copyright © 2015 John Rommel Estropia. All rights reserved. +// + +import UIKit + +class RootManagedObjectModel: NSManagedObjectModel { + +} diff --git a/CoreStore/Migrating/DataStack+Migration.swift b/CoreStore/Migrating/DataStack+Migration.swift index 3f7c4d8..732ea74 100644 --- a/CoreStore/Migrating/DataStack+Migration.swift +++ b/CoreStore/Migrating/DataStack+Migration.swift @@ -35,12 +35,12 @@ public extension DataStack { /** Checks if the store with the specified filename and configuration needs to be migrated to the `DataStack`'s managed object model version. - :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. - :param: mappingModelBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. + - parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. + - parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. :return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed. */ - public func needsMigrationForSQLiteStore(fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as! [NSBundle]) -> MigrationType? { + public func needsMigrationForSQLiteStore(fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) -> MigrationType? { return needsMigrationForSQLiteStore( fileURL: applicationSupportDirectory.URLByAppendingPathComponent( @@ -55,68 +55,72 @@ public extension DataStack { /** Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version. - :param: fileURL the local file URL for the SQLite persistent store. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. - :param: mappingModelBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. + - parameter fileURL: the local file URL for the SQLite persistent store. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. + - parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`. :return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed. */ - public func needsMigrationForSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as! [NSBundle]) -> MigrationType? { + public func needsMigrationForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) -> MigrationType? { - var error: NSError? - let metadata: [NSObject : AnyObject]! = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( - NSSQLiteStoreType, - URL: fileURL, - error: &error - ) - if metadata == nil { + let metadata: [String : AnyObject] + do { + + metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( + NSSQLiteStoreType, + URL: fileURL + ) + } + catch { CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\"." + error as NSError, + "Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\"." ) return nil } let coordinator = self.coordinator; let destinationModel = coordinator.managedObjectModel - if destinationModel.isConfiguration( - configuration, - compatibleWithStoreMetadata: metadata) { - - return .None + if destinationModel.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) { + + return .None } - let sourceModel = NSManagedObjectModel( - byMergingModels: [destinationModel], - forStoreMetadata: metadata - )! + guard let sourceModel = NSManagedObjectModel(byMergingModels: [destinationModel], forStoreMetadata: metadata) else { + + return nil + } - if NSMappingModel( + if let _ = NSMappingModel( fromBundles: mappingModelBundles, forSourceModel: sourceModel, - destinationModel: destinationModel) != nil { + destinationModel: destinationModel) { return .Heavyweight } - if NSMappingModel.inferredMappingModelForSourceModel( - sourceModel, - destinationModel: destinationModel, - error: nil) != nil { - - return .Lightweight + do { + + try NSMappingModel.inferredMappingModelForSourceModel( + sourceModel, + destinationModel: destinationModel + ) + + return .Lightweight + } + catch { + + return nil } - - return nil } /** Migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack. - :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. - :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. - :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. + - parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. + - parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. + - parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. */ public func upgradeSQLiteStoreIfNeeded(fileName: String, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) -> MigrationType? { @@ -134,102 +138,129 @@ public extension DataStack { /** Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack. - :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. - :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. - :param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. + - parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration. + - parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. + - parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`. */ - public func upgradeSQLiteStoreIfNeeded(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) -> MigrationType? { + public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) -> MigrationType? { - var metadataError: NSError? - let metadata: [NSObject: AnyObject]! = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( - NSSQLiteStoreType, - URL: fileURL, - error: &metadataError - ) - if metadata == nil { + let metadata: [String: AnyObject] + do { - let error = metadataError ?? NSError(coreStoreErrorCode: .UnknownError) + metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( + NSSQLiteStoreType, + URL: fileURL + ) + } + catch { + + let metadataError = error as NSError CoreStore.handleError( - error, - "Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\"." + metadataError, + "Failed to load SQLite \(typeName(NSPersistentStore)) metadata at \"\(fileURL)\"." ) GCDQueue.Main.async { - completion(MigrationResult(error)) + completion(MigrationResult(metadataError)) } return nil } - let coordinator = self.coordinator; - let destinationModel = coordinator.managedObjectModel - if destinationModel.isConfiguration( - configuration, - compatibleWithStoreMetadata: metadata) { - - GCDQueue.Main.async { - - completion(MigrationResult(.None)) - } - return .None - } - - let sourceModel = NSManagedObjectModel( - byMergingModels: [destinationModel], - forStoreMetadata: metadata - )! - - if let mappingModel = NSMappingModel( - fromBundles: sourceBundles, - forSourceModel: sourceModel, - destinationModel: destinationModel) { - - self.startMigrationForSQLiteStore( - fileURL, - sourceModel: sourceModel, - destinationModel: destinationModel, - mappingModel: mappingModel, - migrationType: .Heavyweight, - completion: completion - ) - return .Heavyweight - } - - if let mappingModel = NSMappingModel.inferredMappingModelForSourceModel( - sourceModel, - destinationModel: destinationModel, - error: nil) { - - self.startMigrationForSQLiteStore( - fileURL, - sourceModel: sourceModel, - destinationModel: destinationModel, - mappingModel: mappingModel, - migrationType: .Lightweight, - completion: completion - ) - return .Lightweight - } - - CoreStore.handleError( - NSError(coreStoreErrorCode: .UnknownError), - "Failed to load an <\(NSMappingModel.self)> for migration from version model \"\(sourceModel)\" to version model \"\(destinationModel)\"." - ) - - GCDQueue.Main.async { + guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, sourceBundles: sourceBundles) else { - completion(MigrationResult(.MappingModelNotFound)) + CoreStore.handleError( + NSError(coreStoreErrorCode: .MappingModelNotFound), + "Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(model)\"." + ) + + GCDQueue.Main.async { + + completion(MigrationResult(.MappingModelNotFound)) + } + return nil } - return nil + + if migrationSteps.count == 0 { + + GCDQueue.Main.async { + + completion(MigrationResult(.None)) + } + return .None + } + + var mergedMigrationType = MigrationType.None + var migrationResult: MigrationResult? + + var operations = [NSOperation]() + var cancelled = false + for (sourceModel, destinationModel, mappingModel, migrationType) in migrationSteps { + + switch (mergedMigrationType, migrationType) { + + case (.None, _), (.Lightweight, .Heavyweight): + mergedMigrationType = migrationType + + default: + break + } + + operations.append( + NSBlockOperation { [weak self] in + + guard let strongSelf = self where !cancelled else { + + return + } + + autoreleasepool { + + do { + + try strongSelf.startMigrationForSQLiteStore( + fileURL: fileURL, + sourceModel: sourceModel, + destinationModel: destinationModel, + mappingModel: mappingModel + ) + } + catch { + + migrationResult = MigrationResult(error as NSError) + cancelled = true + } + } + } + ) + } + + let migrationOperation = NSBlockOperation() + migrationOperation.qualityOfService = .Utility + operations.map { migrationOperation.addDependency($0) } + migrationOperation.addExecutionBlock { () -> Void in + + GCDQueue.Main.async { + + completion(migrationResult ?? MigrationResult(mergedMigrationType)) + return + } + } + + operations.append(migrationOperation) + + self.migrationQueue.addOperations(operations, waitUntilFinished: false) + + return mergedMigrationType } /** Asynchronously adds to the stack an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.) - :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. - :param: completion the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. + - parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. */ public func addSQLiteStore(fileName: String, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) { @@ -247,39 +278,16 @@ public extension DataStack { /** Asynchronously adds to the stack an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.) - :param: fileURL the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a ".sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. - :param: completion the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. + - parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a ".sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. + - parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. */ - public func addSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) { - - if NSFileManager.defaultManager().fileExistsAtPath(fileURL.path!) { - - var error: NSError? - let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( - NSSQLiteStoreType, - URL: fileURL, - error: &error - ) - if metadata == nil { - - CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\"." - ) - - GCDQueue.Main.async { - - completion(PersistentStoreResult(.UnknownError)) - } - return - } - } + public func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = NSBundle.allBundles(), completion: (PersistentStoreResult) -> Void) { let coordinator = self.coordinator; if let store = coordinator.persistentStoreForURL(fileURL) { - let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false) + let isExistingStoreAutomigrating = store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool == true if store.type == NSSQLiteStoreType && isExistingStoreAutomigrating @@ -294,7 +302,7 @@ public extension DataStack { CoreStore.handleError( NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL), - "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists." + "Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists." ) GCDQueue.Main.async { @@ -304,151 +312,239 @@ public extension DataStack { return } - let fileManager = NSFileManager.defaultManager() - var directoryError: NSError? - if !fileManager.createDirectoryAtURL( - fileURL.URLByDeletingLastPathComponent!, - withIntermediateDirectories: true, - attributes: nil, - error: &directoryError) { + do { + + try NSFileManager.defaultManager().createDirectoryAtURL( + fileURL.URLByDeletingLastPathComponent!, + withIntermediateDirectories: true, + attributes: nil + ) + } + catch _ { } + + self.upgradeSQLiteStoreIfNeeded( + fileURL: fileURL, + configuration: configuration, + sourceBundles: sourceBundles, + completion: { (result) -> Void in - CoreStore.handleError( - directoryError ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to create directory for SQLite store at \"\(fileURL)\"." + if case .Failure(let error) = result + where error.domain != NSCocoaErrorDomain || error.code != NSFileReadNoSuchFileError { + + completion(PersistentStoreResult(error)) + return + } + + let persistentStoreResult = self.addSQLiteStoreAndWait( + fileURL: fileURL, + configuration: configuration, + automigrating: false, + resetStoreOnMigrationFailure: false ) - GCDQueue.Main.async { - - completion(PersistentStoreResult(directoryError!)) - } - return - } - - coordinator.performBlock { - - var persistentStoreError: NSError? - let store = coordinator.addPersistentStoreWithType( - NSSQLiteStoreType, - configuration: configuration, - URL: fileURL, - options: [NSSQLitePragmasOption: ["WAL": "journal_mode"], - NSInferMappingModelAutomaticallyOption: true, - NSMigratePersistentStoresAutomaticallyOption: true], - error: &persistentStoreError) - - if let store = store { - - GCDQueue.Main.async { - - self.updateMetadataForPersistentStore(store) - completion(PersistentStoreResult(store)) - } + completion(persistentStoreResult) } - else { - - GCDQueue.Main.async { - - CoreStore.handleError( - persistentStoreError ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\"." - ) - - completion(PersistentStoreResult(.UnknownError)) - } - } - } + ) } // MARK: Private - private func startMigrationForSQLiteStore(fileURL: NSURL, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType, completion: (MigrationResult) -> Void) { + private func computeMigrationFromStoreMetadata(metadata: [String: AnyObject], configuration: String? = nil, sourceBundles: [NSBundle]? = nil) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? { + + let model = self.model + if model.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) { + + return [] + } + + let metadataModel = NSManagedObjectModel(byMergingModels: model.mergedModels(), forStoreMetadata: metadata)! + if let bypassModel = NSMappingModel( + fromBundles: sourceBundles, + forSourceModel: metadataModel, + destinationModel: model) { + + return [ + ( + sourceModel: metadataModel, + destinationModel: model, + mappingModel: bypassModel, + migrationType: .Heavyweight + ) + ] + } + + var initialModel: NSManagedObjectModel? + if let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : NSData], + let modelVersions = model.modelVersions { + + for modelVersion in modelVersions { + + if let versionModel = model[modelVersion] where modelHashes == versionModel.entityVersionHashesByName { + + initialModel = versionModel + break + } + } + } + + guard var currentVersion = initialModel?.currentModelVersion else { + + return nil + } + + let migrationChain = self.migrationChain + var migrationSteps = [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]() + + while let nextVersion = migrationChain.nextVersionFrom(currentVersion), + let sourceModel = model[currentVersion], + let destinationModel = model[nextVersion] { + + if let mappingModel = NSMappingModel( + fromBundles: sourceBundles, + forSourceModel: sourceModel, + destinationModel: destinationModel) { + + migrationSteps.append( + sourceModel: sourceModel, + destinationModel: destinationModel, + mappingModel: mappingModel, + migrationType: .Heavyweight + ) + } + else { + + do { + + let mappingModel = try NSMappingModel.inferredMappingModelForSourceModel( + sourceModel, + destinationModel: destinationModel + ) + + migrationSteps.append( + sourceModel: sourceModel, + destinationModel: destinationModel, + mappingModel: mappingModel, + migrationType: .Lightweight + ) + } + catch { + + return nil + } + } + currentVersion = nextVersion + } + + if migrationSteps.last?.destinationModel == model { + + return migrationSteps + } + + return nil + } + + private func startMigrationForSQLiteStore(fileURL fileURL: NSURL, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel) throws { let migrationManager = NSMigrationManager( sourceModel: sourceModel, destinationModel: destinationModel ) - self.migrationQueue.async { - - var lastReportedProgress: Float = -1 - let timer = GCDTimer.createSuspended( - .Main, - interval: 0.1, - eventHandler: { (timer) -> Void in + var lastReportedProgress: Float = -1 + let timer = GCDTimer.createSuspended( + .Main, + interval: 0.1, + eventHandler: { (timer) -> Void in + + let progress = migrationManager.migrationProgress + if progress > lastReportedProgress { - let progress = migrationManager.migrationProgress - if progress > lastReportedProgress { - - // TODO: progress - CoreStore.log(.Trace, message: "migration progress: \(progress)") - lastReportedProgress = progress - } + // TODO: progress + CoreStore.log(.Trace, message: "migration progress: \(progress)") + lastReportedProgress = progress } - ) + } + ) + + let temporaryDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString) + + let fileManager = NSFileManager.defaultManager() + try! fileManager.createDirectoryAtURL( + temporaryDirectoryURL, + withIntermediateDirectories: true, + attributes: nil + ) + do { - let temporaryFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)!.URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString) - - var migrationError: NSError? - let migrationCompleted = migrationManager.migrateStoreFromURL( + try migrationManager.migrateStoreFromURL( fileURL, type: NSSQLiteStoreType, - options: nil, + options: [ + NSSQLitePragmasOption: ["WAL": "journal_mode"] + ], withMappingModel: mappingModel, - toDestinationURL: temporaryFileURL, + toDestinationURL: temporaryDirectoryURL.URLByAppendingPathComponent(fileURL.lastPathComponent!, isDirectory: false), destinationType: NSSQLiteStoreType, - destinationOptions: nil, - error: &migrationError + destinationOptions: [ + NSSQLitePragmasOption: ["WAL": "journal_mode"], + NSSQLiteManualVacuumOption: true + ] ) + } + catch { timer.suspend() - let fileManager = NSFileManager.defaultManager() - if !migrationCompleted { + do { - fileManager.removeItemAtURL(temporaryFileURL, error: nil) + try fileManager.removeItemAtURL(temporaryDirectoryURL) + } + catch _ { } + + let migrationError = error as NSError + CoreStore.handleError( + migrationError, + "Failed to migrate from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"." + ) + + throw error + } + + timer.suspend() + + do { + + let originalDirectoryURL = fileURL.URLByDeletingLastPathComponent! + for temporaryFileURL in try fileManager.contentsOfDirectoryAtURL(temporaryDirectoryURL, includingPropertiesForKeys: nil, options: .SkipsSubdirectoryDescendants) { - let error = migrationError ?? NSError(coreStoreErrorCode: .UnknownError) - CoreStore.handleError( - error, - "Failed to migrate from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"." + try fileManager.replaceItemAtURL( + originalDirectoryURL.URLByAppendingPathComponent( + temporaryFileURL.lastPathComponent!, + isDirectory: false + ), + withItemAtURL: temporaryFileURL, + backupItemName: nil, + options: [], + resultingItemURL: nil ) - - GCDQueue.Main.async { - - completion(MigrationResult(error)) - } - return } + } + catch { - var replaceError: NSError? - if !fileManager.replaceItemAtURL( - fileURL, - withItemAtURL: temporaryFileURL, - backupItemName: nil, - options: .allZeros, - resultingItemURL: nil, - error: &replaceError) { - - fileManager.removeItemAtURL(temporaryFileURL, error: nil) - - let error = replaceError ?? NSError(coreStoreErrorCode: .UnknownError) - CoreStore.handleError( - error, - "Failed to save store after migrating from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"." - ) - - GCDQueue.Main.async { - - completion(MigrationResult(error)) - } - return - } - - GCDQueue.Main.async { + do { - completion(MigrationResult(migrationType)) + try fileManager.removeItemAtURL(temporaryDirectoryURL) } + catch _ { } + + let replaceError = error as NSError + CoreStore.handleError( + replaceError, + "Failed to save store after migrating from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"." + ) + + throw error } } } diff --git a/CoreStore/Observing/DataStack+Observing.swift b/CoreStore/Observing/DataStack+Observing.swift index 75ab7d8..01b66c3 100644 --- a/CoreStore/Observing/DataStack+Observing.swift +++ b/CoreStore/Observing/DataStack+Observing.swift @@ -37,12 +37,15 @@ public extension DataStack { /** Creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`. - :param: object the `NSManagedObject` to observe changes from - :returns: a `ObjectMonitor` that monitors changes to `object` + - parameter object: the `NSManagedObject` to observe changes from + - returns: a `ObjectMonitor` that monitors changes to `object` */ public func monitorObject(object: T) -> ObjectMonitor { - CoreStore.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to observe objects from \(typeName(self)) outside the main thread." + ) return ObjectMonitor( dataStack: self, @@ -53,9 +56,9 @@ public extension DataStack { /** Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ListMonitor` instance that monitors changes to the list + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: a `ListMonitor` instance that monitors changes to the list */ public func monitorList(from: From, _ fetchClauses: FetchClause...) -> ListMonitor { @@ -65,13 +68,16 @@ public extension DataStack { /** Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ListMonitor` instance that monitors changes to the list + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: a `ListMonitor` instance that monitors changes to the list */ public func monitorList(from: From, _ fetchClauses: [FetchClause]) -> ListMonitor { - CoreStore.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to observe objects from \(typeName(self)) outside the main thread." + ) return ListMonitor( dataStack: self, @@ -84,10 +90,10 @@ public extension DataStack { /** Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. - :param: from a `From` clause indicating the entity type - :param: sectionBy a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ListMonitor` instance that monitors changes to the list + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: a `ListMonitor` instance that monitors changes to the list */ public func monitorSectionedList(from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { @@ -97,14 +103,17 @@ public extension DataStack { /** Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. - :param: from a `From` clause indicating the entity type - :param: sectionBy a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ListMonitor` instance that monitors changes to the list + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: a `ListMonitor` instance that monitors changes to the list */ public func monitorSectionedList(from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { - CoreStore.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to observe objects from \(typeName(self)) outside the main thread." + ) return ListMonitor( dataStack: self, diff --git a/CoreStore/Saving and Processing/DataStack+Transaction.swift b/CoreStore/Saving and Processing/DataStack+Transaction.swift index c3aa4bc..ba49ee4 100644 --- a/CoreStore/Saving and Processing/DataStack+Transaction.swift +++ b/CoreStore/Saving and Processing/DataStack+Transaction.swift @@ -37,11 +37,14 @@ public extension DataStack { /** Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. - :param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. */ public func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) { - CoreStore.assert(NSThread.isMainThread(), "Attempted to begin a transaction from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to begin a transaction from a \(typeName(self)) outside the main thread." + ) AsynchronousDataTransaction( mainContext: self.rootSavingContext, @@ -52,12 +55,15 @@ public extension DataStack { /** Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. - :param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - :returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously */ public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { - CoreStore.assert(NSThread.isMainThread(), "Attempted to begin a transaction from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to begin a transaction from a \(typeName(self)) outside the main thread." + ) return SynchronousDataTransaction( mainContext: self.rootSavingContext, @@ -68,11 +74,14 @@ public extension DataStack { /** Begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. A detached transaction object should typically be only used from the main queue. - :returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made. + - returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made. */ public func beginDetached() -> DetachedDataTransaction { - CoreStore.assert(NSThread.isMainThread(), "Attempted to begin a transaction from a \(typeName(self)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to begin a transaction from a \(typeName(self)) outside the main thread." + ) return DetachedDataTransaction( mainContext: self.rootSavingContext, diff --git a/CoreStore/Setting Up/DataStack.swift b/CoreStore/Setting Up/DataStack.swift index e5a7597..50f7093 100644 --- a/CoreStore/Setting Up/DataStack.swift +++ b/CoreStore/Setting Up/DataStack.swift @@ -28,9 +28,9 @@ import CoreData import GCDKit -internal let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL +internal let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first! -internal let applicationName = ((NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData") +internal let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData" internal let defaultSQLiteStoreURL = applicationSupportDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite") @@ -47,47 +47,26 @@ public final class DataStack { /** Initializes a `DataStack` from an `NSManagedObjectModel`. - :param: modelName the name of the (.xcdatamodeld) model file. If not specified, the application name will be used - :param: sourceBundle an optional bundle to load models from. If not specified, the main bundle will be used. - :param: modelVersions the `MigrationChain` that indicates the heirarchy of the model's version names. If not specified, will default to a non-migrating data stack. + - parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name will be used + - parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used. + - parameter migrationChain: the `MigrationChain` that indicates the heirarchy of the model's version names. If not specified, will default to a non-migrating data stack. */ - public required init(modelName: String = applicationName, sourceBundle: NSBundle = NSBundle.mainBundle(), modelVersions: MigrationChain = nil) { + public required init(modelName: String = applicationName, bundle: NSBundle = NSBundle.mainBundle(), migrationChain: MigrationChain = nil) { - let modelFilePath: String! = sourceBundle.pathForResource( - modelName, - ofType: "momd" - ) - CoreStore.assert( - modelFilePath != nil, - "Could not find a \"momd\" resource from the main bundle." + let model = NSManagedObjectModel.fromBundle( + bundle, + modelName: modelName, + modelVersion: migrationChain.leafVersions.first ) - let managedObjectModel: NSManagedObjectModel! = NSManagedObjectModel(contentsOfURL: NSURL(fileURLWithPath: modelFilePath)!) - CoreStore.assert( - managedObjectModel != nil, - "Could not create an <\(NSManagedObjectModel.self)> from the resource at path \"\(modelFilePath)\"." - ) // TODO: assert existence of all model versions in the migrationChain - self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) + self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator) self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext) - self.sourceBundle = sourceBundle - self.modelVersions = modelVersions - - var entityNameMapping = [EntityClassNameType: EntityNameType]() - var entityConfigurationsMapping = [EntityClassNameType: Set]() - for entityDescription in managedObjectModel.entities as! [NSEntityDescription] { - - let managedObjectClassName = entityDescription.managedObjectClassName - entityConfigurationsMapping[managedObjectClassName] = [] - if let entityName = entityDescription.name { - - entityNameMapping[managedObjectClassName] = entityName - } - } - self.entityNameMapping = entityNameMapping - self.entityConfigurationsMapping = entityConfigurationsMapping + self.bundle = bundle + self.model = model + self.migrationChain = migrationChain self.rootSavingContext.parentStack = self } @@ -95,23 +74,30 @@ public final class DataStack { /** Adds an in-memory store to the stack. - :param: configuration an optional configuration name from the model file. If not specified, defaults to nil. - :returns: a `PersistentStoreResult` indicating success or failure. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil. + - returns: a `PersistentStoreResult` indicating success or failure. */ - public func addInMemoryStore(configuration: String? = nil) -> PersistentStoreResult { + public func addInMemoryStore(configuration configuration: String? = nil) -> PersistentStoreResult { let coordinator = self.coordinator; - var error: NSError? var store: NSPersistentStore? + var storeError: NSError? coordinator.performBlockAndWait { - store = coordinator.addPersistentStoreWithType( - NSInMemoryStoreType, - configuration: configuration, - URL: nil, - options: nil, - error: &error) + do { + + store = try coordinator.addPersistentStoreWithType( + NSInMemoryStoreType, + configuration: configuration, + URL: nil, + options: nil + ) + } + catch { + + storeError = error as NSError + } } if let store = store { @@ -120,18 +106,20 @@ public final class DataStack { return PersistentStoreResult(store) } - if let error = error { + if let error = storeError { CoreStore.handleError( error, - "Failed to add in-memory <\(NSPersistentStore.self)>.") + "Failed to add in-memory \(typeName(NSPersistentStore))." + ) return PersistentStoreResult(error) } else { CoreStore.handleError( NSError(coreStoreErrorCode: .UnknownError), - "Failed to add in-memory <\(NSPersistentStore.self)>.") + "Failed to add in-memory \(typeName(NSPersistentStore))." + ) return PersistentStoreResult(.UnknownError) } } @@ -139,11 +127,11 @@ public final class DataStack { /** Adds to the stack an SQLite store from the given SQLite file name. - :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. - :param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. - :param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false - :returns: a `PersistentStoreResult` indicating success or failure. + - parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them. + - parameter automigrating: Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. + - parameter resetStoreOnMigrationFailure: Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false + - returns: a `PersistentStoreResult` indicating success or failure. */ public func addSQLiteStoreAndWait(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { @@ -161,18 +149,18 @@ public final class DataStack { /** Adds to the stack an SQLite store from the given SQLite file URL. - :param: fileURL the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a ".sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. - :param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. - :param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. - :param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. - :returns: a `PersistentStoreResult` indicating success or failure. + - parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a ".sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them. + - parameter automigrating: Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. + - parameter resetStoreOnMigrationFailure: Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. + - returns: a `PersistentStoreResult` indicating success or failure. */ - public func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { + public func addSQLiteStoreAndWait(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { let coordinator = self.coordinator; if let store = coordinator.persistentStoreForURL(fileURL) { - let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false) + let isExistingStoreAutomigrating = (store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) == true if store.type == NSSQLiteStoreType && isExistingStoreAutomigrating == automigrating @@ -183,37 +171,44 @@ public final class DataStack { CoreStore.handleError( NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL), - "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists.") + "Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists." + ) return PersistentStoreResult(.DifferentPersistentStoreExistsAtURL) } let fileManager = NSFileManager.defaultManager() - var directoryError: NSError? - if !fileManager.createDirectoryAtURL( - fileURL.URLByDeletingLastPathComponent!, - withIntermediateDirectories: true, - attributes: nil, - error: &directoryError) { - - CoreStore.handleError( - directoryError ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to create directory for SQLite store at \"\(fileURL)\".") - return PersistentStoreResult(directoryError!) + do { + + try fileManager.createDirectoryAtURL( + fileURL.URLByDeletingLastPathComponent!, + withIntermediateDirectories: true, + attributes: nil + ) } + catch _ { } var store: NSPersistentStore? - var persistentStoreError: NSError? + var storeError: NSError? coordinator.performBlockAndWait { - store = coordinator.addPersistentStoreWithType( - NSSQLiteStoreType, - configuration: configuration, - URL: fileURL, - options: [NSSQLitePragmasOption: ["WAL": "journal_mode"], - NSInferMappingModelAutomaticallyOption: true, - NSMigratePersistentStoresAutomaticallyOption: automigrating], - error: &persistentStoreError) + do { + + store = try coordinator.addPersistentStoreWithType( + NSSQLiteStoreType, + configuration: configuration, + URL: fileURL, + options: [ + NSSQLitePragmasOption: ["WAL": "journal_mode"], + NSInferMappingModelAutomaticallyOption: true, + NSMigratePersistentStoresAutomaticallyOption: automigrating + ] + ) + } + catch { + + storeError = error as NSError + } } if let store = store { @@ -222,7 +217,7 @@ public final class DataStack { return PersistentStoreResult(store) } - if let error = persistentStoreError + if let error = storeError where ( resetStoreOnMigrationFailure && (error.code == NSPersistentStoreIncompatibleVersionHashError @@ -231,25 +226,44 @@ public final class DataStack { && error.domain == NSCocoaErrorDomain ) { - fileManager.removeItemAtURL(fileURL, error: nil) - fileManager.removeItemAtPath( - fileURL.path!.stringByAppendingString("-shm"), - error: nil) - fileManager.removeItemAtPath( - fileURL.path!.stringByAppendingString("-wal"), - error: nil) + do { + + try fileManager.removeItemAtURL(fileURL) + } + catch _ { } + + do { + + try fileManager.removeItemAtPath(fileURL.path!.stringByAppendingString("-shm")) + } + catch _ { } + + do { + + try fileManager.removeItemAtPath(fileURL.path!.stringByAppendingString("-wal")) + } + catch _ { } var store: NSPersistentStore? coordinator.performBlockAndWait { - store = coordinator.addPersistentStoreWithType( - NSSQLiteStoreType, - configuration: configuration, - URL: fileURL, - options: [NSSQLitePragmasOption: ["WAL": "journal_mode"], - NSInferMappingModelAutomaticallyOption: true, - NSMigratePersistentStoresAutomaticallyOption: automigrating], - error: &persistentStoreError) + do { + + store = try coordinator.addPersistentStoreWithType( + NSSQLiteStoreType, + configuration: configuration, + URL: fileURL, + options: [ + NSSQLitePragmasOption: ["WAL": "journal_mode"], + NSInferMappingModelAutomaticallyOption: true, + NSMigratePersistentStoresAutomaticallyOption: automigrating + ] + ) + } + catch { + + storeError = error as NSError + } } if let store = store { @@ -260,8 +274,9 @@ public final class DataStack { } CoreStore.handleError( - persistentStoreError ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".") + storeError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\"." + ) return PersistentStoreResult(.UnknownError) } @@ -272,14 +287,23 @@ public final class DataStack { internal let coordinator: NSPersistentStoreCoordinator internal let rootSavingContext: NSManagedObjectContext internal let mainContext: NSManagedObjectContext - internal let sourceBundle: NSBundle - internal let modelVersions: MigrationChain + internal let bundle: NSBundle + internal let model: NSManagedObjectModel + internal let migrationChain: MigrationChain internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue") - internal let migrationQueue: GCDQueue = .createSerial("com.corestore.datastack.migrationqueue") + internal let migrationQueue: NSOperationQueue = { + + let migrationQueue = NSOperationQueue() + migrationQueue.maxConcurrentOperationCount = 1 + migrationQueue.name = "com.coreStore.migrationOperationQueue" + migrationQueue.qualityOfService = .Utility + migrationQueue.underlyingQueue = dispatch_queue_create("com.coreStore.migrationQueue", DISPATCH_QUEUE_SERIAL) + return migrationQueue + }() internal func entityNameForEntityClass(entityClass: AnyClass) -> String? { - return self.entityNameMapping[NSStringFromClass(entityClass)] + return self.model.entityNameForClass(entityClass) } internal func persistentStoresForEntityClass(entityClass: AnyClass) -> [NSPersistentStore]? { @@ -287,11 +311,10 @@ public final class DataStack { var returnValue: [NSPersistentStore]? = nil self.storeMetadataUpdateQueue.barrierSync { - let configurationsForEntity = self.entityConfigurationsMapping[NSStringFromClass(entityClass)] ?? [] - returnValue = map(configurationsForEntity) { + returnValue = self.entityConfigurationsMapping[NSStringFromClass(entityClass)]?.map { return self.configurationStoreMapping[$0]! - } + } ?? [] } return returnValue } @@ -336,8 +359,12 @@ public final class DataStack { let configurationName = persistentStore.configurationName self.configurationStoreMapping[configurationName] = persistentStore - for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) as? [NSEntityDescription] ?? []) { + for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) ?? []) { + if self.entityConfigurationsMapping[entityDescription.managedObjectClassName] == nil { + + self.entityConfigurationsMapping[entityDescription.managedObjectClassName] = [] + } self.entityConfigurationsMapping[entityDescription.managedObjectClassName]?.insert(configurationName) } } @@ -346,12 +373,7 @@ public final class DataStack { // MARK: Private - private typealias EntityClassNameType = String - private typealias EntityNameType = String - private typealias ConfigurationNameType = String - - private let entityNameMapping: [EntityClassNameType: EntityNameType] private let storeMetadataUpdateQueue = GCDQueue.createConcurrent("com.coreStore.persistentStoreBarrierQueue") - private var configurationStoreMapping = [ConfigurationNameType: NSPersistentStore]() - private var entityConfigurationsMapping = [EntityClassNameType: Set]() + private var configurationStoreMapping = [String: NSPersistentStore]() + private var entityConfigurationsMapping = [String: Set]() } From 5b85b0749e410362f1c951ec2a4809bd30417859 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Tue, 7 Jul 2015 08:03:46 +0900 Subject: [PATCH 04/19] migration utilities (beta), swift 2 conversion --- CoreStore.xcodeproj/project.pbxproj | 12 +- .../xcshareddata/CoreStore.xcscmblueprint | 30 +++ .../xcshareddata/xcschemes/CoreStore.xcscheme | 5 +- .../BaseDataTransaction+Querying.swift | 184 +++++++++++------- .../Concrete Clauses/GroupBy.swift | 11 +- .../Concrete Clauses/OrderBy.swift | 19 +- .../Concrete Clauses/Select.swift | 61 +++--- .../Concrete Clauses/Tweak.swift | 2 +- .../Concrete Clauses/Where.swift | 21 +- .../CoreStore+Querying.swift | 92 ++++----- CoreStore/Info.plist | 2 +- CoreStore/Internal/AssociatedObjects.swift | 10 +- .../NSManagedObject+Transaction.swift | 56 ++++-- .../NSManagedObjectContext+CoreStore.swift | 21 +- .../NSManagedObjectContext+Querying.swift | 115 ++++++++--- .../NSManagedObjectContext+Transaction.swift | 101 +++++----- .../Internal/NSManagedObjectModel+Setup.swift | 183 ++++++++++++++--- CoreStore/Internal/NotificationObserver.swift | 2 +- .../Internal/RootManagedObjectModel.swift | 13 -- CoreStore/Logging/CoreStore+Logging.swift | 16 +- CoreStore/Logging/CoreStoreLogger.swift | 20 +- CoreStore/Logging/DefaultLogger.swift | 17 +- CoreStore/Migrating/MigrationChain.swift | 11 +- CoreStore/Observing/CoreStore+Observing.swift | 32 +-- CoreStore/Observing/ListMonitor.swift | 90 +++++---- CoreStore/Observing/ListObserver.swift | 42 ++-- CoreStore/Observing/ObjectMonitor.swift | 53 +++-- CoreStore/Observing/ObjectObserver.swift | 14 +- CoreStore/Observing/SectionBy.swift | 6 +- .../AsynchronousDataTransaction.swift | 106 +++++++--- .../BaseDataTransaction.swift | 80 +++++--- .../CoreStore+Transaction.swift | 8 +- .../DetachedDataTransaction.swift | 7 +- CoreStore/Saving and Processing/Into.swift | 14 +- .../Saving and Processing/SaveResult.swift | 4 +- .../SynchronousDataTransaction.swift | 89 ++++++--- CoreStore/Setting Up/CoreStore+Setup.swift | 24 +-- .../CoreStoreDemo.xcodeproj/project.pbxproj | 28 ++- .../xcshareddata/CoreStoreDemo.xccheckout | 4 +- .../xcshareddata/CoreStoreDemo.xcscmblueprint | 30 +++ .../xcschemes/CoreStoreDemo.xcscheme | 5 +- .../CoreStoreDemo/Base.lproj/Main.storyboard | 117 +++++++++-- ...etchingAndQueryingDemoViewController.swift | 4 +- .../FetchingResultsViewController.swift | 2 +- .../QueryingResultsViewController.swift | 2 +- CoreStoreDemo/CoreStoreDemo/Info.plist | 2 +- .../ObjectObserverDemoViewController.swift | 4 - .../CustomLoggerViewController.swift | 11 +- .../MigrationsDemoViewController.swift | 149 +++++++------- .../xcmapping.xml | 54 ++--- .../OrganismV2ToV3MigrationPolicy.swift | 8 +- .../MigrationDemoV2.xcdatamodel/contents | 2 +- .../MigrationDemoV3.xcdatamodel/contents | 2 +- .../StackSetupDemoViewController.swift | 2 +- .../TransactionsDemoViewController.swift | 6 +- CoreStoreTests/CoreStoreTests.swift | 27 ++- CoreStoreTests/Info.plist | 2 +- Libraries/GCDKit | 2 +- README.md | 16 +- 59 files changed, 1319 insertions(+), 733 deletions(-) create mode 100644 CoreStore.xcodeproj/project.xcworkspace/xcshareddata/CoreStore.xcscmblueprint delete mode 100644 CoreStore/Internal/RootManagedObjectModel.swift create mode 100644 CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xcscmblueprint diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 5053325..484d19a 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -12,6 +12,7 @@ 2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A54C19C5C872005002A5 /* CoreData.framework */; }; 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */; }; + B51BE06A1B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */; }; B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56007101B3F6BD500A9A8F9 /* Into.swift */; }; B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56007131B3F6C2800A9A8F9 /* SectionBy.swift */; }; B56007161B4018AB00A9A8F9 /* MigrationChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56007151B4018AB00A9A8F9 /* MigrationChain.swift */; }; @@ -104,6 +105,7 @@ 2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 2F291E2619C6D3CF007AF63F /* CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoreStore.swift; sourceTree = ""; }; B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = ""; }; + B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectModel+Setup.swift"; sourceTree = ""; }; B56007101B3F6BD500A9A8F9 /* Into.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Into.swift; sourceTree = ""; }; B56007131B3F6C2800A9A8F9 /* SectionBy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionBy.swift; sourceTree = ""; }; B56007151B4018AB00A9A8F9 /* MigrationChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationChain.swift; sourceTree = ""; }; @@ -389,6 +391,7 @@ isa = PBXGroup; children = ( B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */, + B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */, B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */, B5E84F341AFF85470064E85B /* NSManagedObject+Transaction.swift */, B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */, @@ -457,7 +460,8 @@ 2F03A52719C5C6DA005002A5 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0600; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; ORGANIZATIONNAME = "John Rommel Estropia"; TargetAttributes = { 2F03A52F19C5C6DA005002A5 = { @@ -564,6 +568,7 @@ B5E84F0F1AFF847B0064E85B /* From.swift in Sources */, B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */, B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */, + B51BE06A1B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift in Sources */, B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */, B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */, B5E84F311AFF849C0064E85B /* WeakObject.swift in Sources */, @@ -628,6 +633,7 @@ COPY_PHASE_STRIP = NO; CURRENT_PROJECT_VERSION = 1; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_OPTIMIZATION_LEVEL = 0; @@ -707,6 +713,7 @@ IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; OTHER_SWIFT_FLAGS = "-D DEBUG"; + PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; @@ -727,6 +734,7 @@ INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; SKIP_INSTALL = YES; SWIFT_OPTIMIZATION_LEVEL = "-O"; @@ -748,6 +756,7 @@ ); INFOPLIST_FILE = CoreStoreTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -762,6 +771,7 @@ ); INFOPLIST_FILE = CoreStoreTests/Info.plist; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)"; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/CoreStore.xcodeproj/project.xcworkspace/xcshareddata/CoreStore.xcscmblueprint b/CoreStore.xcodeproj/project.xcworkspace/xcshareddata/CoreStore.xcscmblueprint new file mode 100644 index 0000000..4cf66a2 --- /dev/null +++ b/CoreStore.xcodeproj/project.xcworkspace/xcshareddata/CoreStore.xcscmblueprint @@ -0,0 +1,30 @@ +{ + "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48", + "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { + + }, + "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { + "8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0, + "4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0 + }, + "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "F347F55F-7F5C-4476-9148-6E902F06E4AD", + "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { + "8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStoreLibraries\/GCDKit", + "4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore" + }, + "DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore", + "DVTSourceControlWorkspaceBlueprintVersion" : 203, + "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStore.xcodeproj", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/CoreStore.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48" + }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/GCDKit.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97" + } + ] +} \ No newline at end of file diff --git a/CoreStore.xcodeproj/xcshareddata/xcschemes/CoreStore.xcscheme b/CoreStore.xcodeproj/xcshareddata/xcschemes/CoreStore.xcscheme index 24b8263..3cf883e 100644 --- a/CoreStore.xcodeproj/xcshareddata/xcschemes/CoreStore.xcscheme +++ b/CoreStore.xcodeproj/xcshareddata/xcschemes/CoreStore.xcscheme @@ -1,6 +1,6 @@ + + (from: From, _ fetchClauses: FetchClause...) -> T? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchOne(from, fetchClauses) } @@ -50,13 +53,16 @@ public extension BaseDataTransaction { /** Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s */ public func fetchOne(from: From, _ fetchClauses: [FetchClause]) -> T? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchOne(from, fetchClauses) } @@ -64,13 +70,16 @@ public extension BaseDataTransaction { /** Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s */ public func fetchAll(from: From, _ fetchClauses: FetchClause...) -> [T]? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchAll(from, fetchClauses) } @@ -78,13 +87,16 @@ public extension BaseDataTransaction { /** Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s */ public func fetchAll(from: From, _ fetchClauses: [FetchClause]) -> [T]? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchAll(from, fetchClauses) } @@ -92,13 +104,16 @@ public extension BaseDataTransaction { /** Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchCount(from: From, _ fetchClauses: FetchClause...) -> Int? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchCount(from, fetchClauses) } @@ -106,13 +121,16 @@ public extension BaseDataTransaction { /** Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchCount(from: From, _ fetchClauses: [FetchClause]) -> Int? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchCount(from, fetchClauses) } @@ -120,13 +138,16 @@ public extension BaseDataTransaction { /** Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s */ public func fetchObjectID(from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchObjectID(from, fetchClauses) } @@ -134,13 +155,16 @@ public extension BaseDataTransaction { /** Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s */ public func fetchObjectID(from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchObjectID(from, fetchClauses) } @@ -148,13 +172,16 @@ public extension BaseDataTransaction { /** Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchObjectIDs(from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchObjectIDs(from, fetchClauses) } @@ -162,13 +189,16 @@ public extension BaseDataTransaction { /** Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public func fetchObjectIDs(from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to fetch from a \(typeName(self)) outside its designated queue." + ) return self.context.fetchObjectIDs(from, fetchClauses) } @@ -176,13 +206,16 @@ public extension BaseDataTransaction { /** Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number of `NSManagedObject`s deleted + - parameter from: a `From` clause indicating the entity type + - parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number of `NSManagedObject`s deleted */ public func deleteAll(from: From, _ deleteClauses: DeleteClause...) -> Int? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to delete from a \(typeName(self)) outside its designated queue." + ) return self.context.deleteAll(from, deleteClauses) } @@ -190,13 +223,16 @@ public extension BaseDataTransaction { /** Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number of `NSManagedObject`s deleted + - parameter from: a `From` clause indicating the entity type + - parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number of `NSManagedObject`s deleted */ public func deleteAll(from: From, _ deleteClauses: [DeleteClause]) -> Int? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to delete from a \(typeName(self)) outside its designated queue." + ) return self.context.deleteAll(from, deleteClauses) } @@ -206,14 +242,17 @@ public extension BaseDataTransaction { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public func queryValue(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> U? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to query from a \(typeName(self)) outside its designated queue." + ) return self.context.queryValue(from, selectClause, queryClauses) } @@ -223,14 +262,17 @@ public extension BaseDataTransaction { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public func queryValue(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> U? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to query from a \(typeName(self)) outside its designated queue." + ) return self.context.queryValue(from, selectClause, queryClauses) } @@ -240,14 +282,17 @@ public extension BaseDataTransaction { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to query from a \(typeName(self)) outside its designated queue." + ) return self.context.queryAttributes(from, selectClause, queryClauses) } @@ -257,14 +302,17 @@ public extension BaseDataTransaction { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to query from a \(typeName(self)) outside its designated queue." + ) return self.context.queryAttributes(from, selectClause, queryClauses) } diff --git a/CoreStore/Fetching and Querying/Concrete Clauses/GroupBy.swift b/CoreStore/Fetching and Querying/Concrete Clauses/GroupBy.swift index b01c1ae..53d3b1a 100644 --- a/CoreStore/Fetching and Querying/Concrete Clauses/GroupBy.swift +++ b/CoreStore/Fetching and Querying/Concrete Clauses/GroupBy.swift @@ -39,7 +39,7 @@ public struct GroupBy: QueryClause { /** Initializes a `GroupBy` clause with a list of key path strings - :param: keyPaths a list of key path strings to group results with + - parameter keyPaths: a list of key path strings to group results with */ public init(_ keyPaths: [KeyPath]) { @@ -57,8 +57,8 @@ public struct GroupBy: QueryClause { /** Initializes a `GroupBy` clause with a list of key path strings - :param: keyPath a key path string to group results with - :param: keyPaths a series of key path strings to group results with + - parameter keyPath: a key path string to group results with + - parameter keyPaths: a series of key path strings to group results with */ public init(_ keyPath: KeyPath, _ keyPaths: KeyPath...) { @@ -74,7 +74,10 @@ public struct GroupBy: QueryClause { if fetchRequest.propertiesToGroupBy != nil { - CoreStore.log(.Warning, message: "An existing \"propertiesToGroupBy\" for the <\(NSFetchRequest.self)> was overwritten by \(typeName(self)) query clause.") + CoreStore.log( + .Warning, + message: "An existing \"propertiesToGroupBy\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause." + ) } fetchRequest.propertiesToGroupBy = self.keyPaths diff --git a/CoreStore/Fetching and Querying/Concrete Clauses/OrderBy.swift b/CoreStore/Fetching and Querying/Concrete Clauses/OrderBy.swift index c1b3416..1cffc29 100644 --- a/CoreStore/Fetching and Querying/Concrete Clauses/OrderBy.swift +++ b/CoreStore/Fetching and Querying/Concrete Clauses/OrderBy.swift @@ -73,7 +73,7 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause { /** Initializes a `OrderBy` clause with a list of sort descriptors - :param: sortDescriptors a series of `NSSortDescriptor`s + - parameter sortDescriptors: a series of `NSSortDescriptor`s */ public init(_ sortDescriptors: [NSSortDescriptor]) { @@ -91,7 +91,7 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause { /** Initializes a `OrderBy` clause with a single sort descriptor - :param: sortDescriptor a `NSSortDescriptor` + - parameter sortDescriptor: a `NSSortDescriptor` */ public init(_ sortDescriptor: NSSortDescriptor) { @@ -101,14 +101,14 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause { /** Initializes a `OrderBy` clause with a series of `SortKey`s - :param: sortKey a series of `SortKey`s + - parameter sortKey: a series of `SortKey`s */ public init(_ sortKey: [SortKey]) { self.init( - sortKey.map { SortKey -> NSSortDescriptor in + sortKey.map { sortKey -> NSSortDescriptor in - switch SortKey { + switch sortKey { case .Ascending(let keyPath): return NSSortDescriptor(key: keyPath, ascending: true) @@ -123,8 +123,8 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause { /** Initializes a `OrderBy` clause with a series of `SortKey`s - :param: sortKey a single `SortKey` - :param: sortKeys a series of `SortKey`s + - parameter sortKey: a single `SortKey` + - parameter sortKeys: a series of `SortKey`s */ public init(_ sortKey: SortKey, _ sortKeys: SortKey...) { @@ -140,7 +140,10 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause { if fetchRequest.sortDescriptors != nil { - CoreStore.log(.Warning, message: "Existing sortDescriptors for the <\(NSFetchRequest.self)> was overwritten by \(typeName(self)) query clause.") + CoreStore.log( + .Warning, + message: "Existing sortDescriptors for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause." + ) } fetchRequest.sortDescriptors = self.sortDescriptors diff --git a/CoreStore/Fetching and Querying/Concrete Clauses/Select.swift b/CoreStore/Fetching and Querying/Concrete Clauses/Select.swift index 0b179f5..3653164 100644 --- a/CoreStore/Fetching and Querying/Concrete Clauses/Select.swift +++ b/CoreStore/Fetching and Querying/Concrete Clauses/Select.swift @@ -83,8 +83,8 @@ public enum SelectTerm: StringLiteralConvertible { Where("employeeID", isEqualTo: 1111) ) - :param: keyPath the attribute name - :returns: a `SelectTerm` to a `Select` clause for querying an entity attribute + - parameter keyPath: the attribute name + - returns: a `SelectTerm` to a `Select` clause for querying an entity attribute */ public static func Attribute(keyPath: KeyPath) -> SelectTerm { @@ -99,9 +99,9 @@ public enum SelectTerm: StringLiteralConvertible { Select(.Average("age")) ) - :param: keyPath the attribute name - :param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average()" is used - :returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute + - parameter keyPath: the attribute name + - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average()" is used + - returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute */ public static func Average(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { @@ -121,9 +121,9 @@ public enum SelectTerm: StringLiteralConvertible { Select(.Count("employeeID")) ) - :param: keyPath the attribute name - :param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count()" is used - :returns: a `SelectTerm` to a `Select` clause for a count query + - parameter keyPath: the attribute name + - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count()" is used + - returns: a `SelectTerm` to a `Select` clause for a count query */ public static func Count(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { @@ -143,9 +143,9 @@ public enum SelectTerm: StringLiteralConvertible { Select(.Maximum("age")) ) - :param: keyPath the attribute name - :param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max()" is used - :returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute + - parameter keyPath: the attribute name + - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max()" is used + - returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute */ public static func Maximum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { @@ -165,9 +165,9 @@ public enum SelectTerm: StringLiteralConvertible { Select(.Minimum("age")) ) - :param: keyPath the attribute name - :param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min()" is used - :returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute + - parameter keyPath: the attribute name + - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min()" is used + - returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute */ public static func Minimum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { @@ -187,9 +187,9 @@ public enum SelectTerm: StringLiteralConvertible { Select(.Sum("age")) ) - :param: keyPath the attribute name - :param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum()" is used - :returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute + - parameter keyPath: the attribute name + - parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum()" is used + - returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute */ public static func Sum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { @@ -267,7 +267,7 @@ Valid return types depend on the query: - for `queryAttributes(...)` methods: - `NSDictionary` -:param: sortDescriptors a series of `NSSortDescriptor`s +- parameter sortDescriptors: a series of `NSSortDescriptor`s */ public struct Select { @@ -281,8 +281,8 @@ public struct Select { /** Initializes a `Select` clause with a list of `SelectTerm`s - :param: selectTerm a `SelectTerm` - :param: selectTerms a series of `SelectTerm`s + - parameter selectTerm: a `SelectTerm` + - parameter selectTerms: a series of `SelectTerm`s */ public init(_ selectTerm: SelectTerm, _ selectTerms: SelectTerm...) { @@ -296,7 +296,10 @@ public struct Select { if fetchRequest.propertiesToFetch != nil { - CoreStore.log(.Warning, message: "An existing \"propertiesToFetch\" for the <\(NSFetchRequest.self)> was overwritten by \(typeName(self)) query clause.") + CoreStore.log( + .Warning, + message: "An existing \"propertiesToFetch\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause." + ) } fetchRequest.includesPendingChanges = false @@ -312,17 +315,20 @@ public struct Select { switch term { case ._Attribute(let keyPath): - if let propertyDescription = propertiesByName[keyPath] as? NSPropertyDescription { + if let propertyDescription = propertiesByName[keyPath] { propertiesToFetch.append(propertyDescription) } else { - CoreStore.log(.Warning, message: "The property \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by \(typeName(self)) query clause.") + CoreStore.log( + .Warning, + message: "The property \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause." + ) } case ._Aggregate(let function, let keyPath, let alias, let nativeType): - if let attributeDescription = attributesByName[keyPath] as? NSAttributeDescription { + if let attributeDescription = attributesByName[keyPath] { let expressionDescription = NSExpressionDescription() expressionDescription.name = alias @@ -343,7 +349,10 @@ public struct Select { } else { - CoreStore.log(.Warning, message: "The attribute \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by \(typeName(self)) query clause.") + CoreStore.log( + .Warning, + message: "The attribute \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause." + ) } } } @@ -572,7 +581,7 @@ extension NSString: SelectValueResultType { // MARK: - NSDecimalNumber: SelectValueResultType -extension NSDecimalNumber: SelectValueResultType { +extension NSDecimalNumber { public override class var attributeType: NSAttributeType { diff --git a/CoreStore/Fetching and Querying/Concrete Clauses/Tweak.swift b/CoreStore/Fetching and Querying/Concrete Clauses/Tweak.swift index 080d74b..299d154 100644 --- a/CoreStore/Fetching and Querying/Concrete Clauses/Tweak.swift +++ b/CoreStore/Fetching and Querying/Concrete Clauses/Tweak.swift @@ -49,7 +49,7 @@ public struct Tweak: FetchClause, QueryClause, DeleteClause { /** Initializes a `Tweak` clause with a closure where the `NSFetchRequest` may be configured. - :param: customization a list of key path strings to group results with + - parameter customization: a list of key path strings to group results with */ public init(_ customization: (fetchRequest: NSFetchRequest) -> Void) { diff --git a/CoreStore/Fetching and Querying/Concrete Clauses/Where.swift b/CoreStore/Fetching and Querying/Concrete Clauses/Where.swift index 1f80d57..1762b31 100644 --- a/CoreStore/Fetching and Querying/Concrete Clauses/Where.swift +++ b/CoreStore/Fetching and Querying/Concrete Clauses/Where.swift @@ -54,7 +54,7 @@ public struct Where: FetchClause, QueryClause, DeleteClause { /** Initializes a `Where` clause with an `NSPredicate` - :param: predicate the `NSPredicate` for the fetch or query + - parameter predicate: the `NSPredicate` for the fetch or query */ public init(_ predicate: NSPredicate) { @@ -72,7 +72,7 @@ public struct Where: FetchClause, QueryClause, DeleteClause { /** Initializes a `Where` clause with a predicate that always evaluates to the specified boolean value - :param: value the boolean value for the predicate + - parameter value: the boolean value for the predicate */ public init(_ value: Bool) { @@ -82,8 +82,8 @@ public struct Where: FetchClause, QueryClause, DeleteClause { /** Initializes a `Where` clause with a predicate using the specified string format and arguments - :param: format the format string for the predicate - :param: args the arguments for `format` + - parameter format: the format string for the predicate + - parameter args: the arguments for `format` */ public init(_ format: String, _ args: NSObject...) { @@ -93,8 +93,8 @@ public struct Where: FetchClause, QueryClause, DeleteClause { /** Initializes a `Where` clause with a predicate using the specified string format and arguments - :param: format the format string for the predicate - :param: argumentArray the arguments for `format` + - parameter format: the format string for the predicate + - parameter argumentArray: the arguments for `format` */ public init(_ format: String, argumentArray: [NSObject]?) { @@ -104,8 +104,8 @@ public struct Where: FetchClause, QueryClause, DeleteClause { /** Initializes a `Where` clause with a predicate using the specified string format and arguments - :param: format the format string for the predicate - :param: argumentArray the arguments for `format` + - parameter format: the format string for the predicate + - parameter argumentArray: the arguments for `format` */ public init(_ keyPath: KeyPath, isEqualTo value: NSObject?) { @@ -123,7 +123,10 @@ public struct Where: FetchClause, QueryClause, DeleteClause { if fetchRequest.predicate != nil { - CoreStore.log(.Warning, message: "An existing predicate for the <\(NSFetchRequest.self)> was overwritten by \(typeName(self)) query clause.") + CoreStore.log( + .Warning, + message: "An existing predicate for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause." + ) } fetchRequest.predicate = self.predicate diff --git a/CoreStore/Fetching and Querying/CoreStore+Querying.swift b/CoreStore/Fetching and Querying/CoreStore+Querying.swift index efa7d4a..1d28e95 100644 --- a/CoreStore/Fetching and Querying/CoreStore+Querying.swift +++ b/CoreStore/Fetching and Querying/CoreStore+Querying.swift @@ -34,9 +34,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s */ public static func fetchOne(from: From, _ fetchClauses: FetchClause...) -> T? { @@ -46,9 +46,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s */ public static func fetchOne(from: From, _ fetchClauses: [FetchClause]) -> T? { @@ -58,9 +58,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s */ public static func fetchAll(from: From, _ fetchClauses: FetchClause...) -> [T]? { @@ -70,9 +70,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s */ public static func fetchAll(from: From, _ fetchClauses: [FetchClause]) -> [T]? { @@ -82,9 +82,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public static func fetchCount(from: From, _ fetchClauses: FetchClause...) -> Int? { @@ -94,9 +94,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s */ public static func fetchCount(from: From, _ fetchClauses: [FetchClause]) -> Int? { @@ -106,9 +106,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s */ public static func fetchObjectID(from: From, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { @@ -118,9 +118,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s */ public static func fetchObjectID(from: From, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { @@ -130,9 +130,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public static func fetchObjectIDs(from: From, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { @@ -142,9 +142,9 @@ public extension CoreStore { /** Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s */ public static func fetchObjectIDs(from: From, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { @@ -156,10 +156,10 @@ public extension CoreStore { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public static func queryValue(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> U? { @@ -171,10 +171,10 @@ public extension CoreStore { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public static func queryValue(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> U? { @@ -186,10 +186,10 @@ public extension CoreStore { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public static func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { @@ -201,10 +201,10 @@ public extension CoreStore { A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result. - :param: from a `From` clause indicating the entity type - :param: selectClause a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. - :param: queryClauses a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. - :returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. + - parameter from: a `From` clause indicating the entity type + - parameter selectClause: a `Select` clause indicating the properties to fetch, and with the generic type indicating the return type. + - parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses. + - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select` parameter. */ public static func queryAttributes(from: From, _ selectClause: Select, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { diff --git a/CoreStore/Info.plist b/CoreStore/Info.plist index 5b188f2..60b9c00 100644 --- a/CoreStore/Info.plist +++ b/CoreStore/Info.plist @@ -7,7 +7,7 @@ CFBundleExecutable $(EXECUTABLE_NAME) CFBundleIdentifier - com.johnestropia.$(PRODUCT_NAME:rfc1034identifier) + $(PRODUCT_BUNDLE_IDENTIFIER) CFBundleInfoDictionaryVersion 6.0 CFBundleName diff --git a/CoreStore/Internal/AssociatedObjects.swift b/CoreStore/Internal/AssociatedObjects.swift index 34cb330..2018a37 100644 --- a/CoreStore/Internal/AssociatedObjects.swift +++ b/CoreStore/Internal/AssociatedObjects.swift @@ -42,27 +42,27 @@ internal func getAssociatedObjectForKey(key: UnsafePointer, internal func setAssociatedRetainedObject(associatedObject: T?, forKey key: UnsafePointer, inObject object: AnyObject) { - objc_setAssociatedObject(object, key, associatedObject, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } internal func setAssociatedCopiedObject(associatedObject: T?, forKey key: UnsafePointer, inObject object: AnyObject) { - objc_setAssociatedObject(object, key, associatedObject, UInt(OBJC_ASSOCIATION_COPY_NONATOMIC)) + objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_COPY_NONATOMIC) } internal func setAssociatedAssignedObject(associatedObject: T?, forKey key: UnsafePointer, inObject object: AnyObject) { - objc_setAssociatedObject(object, key, associatedObject, UInt(OBJC_ASSOCIATION_ASSIGN)) + objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_ASSIGN) } internal func setAssociatedWeakObject(associatedObject: T?, forKey key: UnsafePointer, inObject object: AnyObject) { if let associatedObject = associatedObject { - objc_setAssociatedObject(object, key, WeakObject(associatedObject), UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + objc_setAssociatedObject(object, key, WeakObject(associatedObject), .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } else { - objc_setAssociatedObject(object, key, nil, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) + objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC) } } diff --git a/CoreStore/Internal/NSManagedObject+Transaction.swift b/CoreStore/Internal/NSManagedObject+Transaction.swift index 233c2e8..9ef9ac4 100644 --- a/CoreStore/Internal/NSManagedObject+Transaction.swift +++ b/CoreStore/Internal/NSManagedObject+Transaction.swift @@ -35,7 +35,7 @@ internal extension NSManagedObject { internal dynamic class func createInContext(context: NSManagedObjectContext) -> Self { - return self( + return self.init( entity: context.entityDescriptionForEntityType(self)!, insertIntoManagedObjectContext: context ) @@ -61,16 +61,19 @@ internal extension NSManagedObject { private class func typedObjectInContext(context: NSManagedObjectContext, objectID: NSManagedObjectID) -> T? { - var error: NSError? - if let existingObject = context.existingObjectWithID(objectID, error: &error) { + do { + let existingObject = try context.existingObjectWithID(objectID) return (existingObject as! T) } - - CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to load existing \(typeName(self)) in context.") - return nil; + catch { + + CoreStore.handleError( + error as NSError, + "Failed to load existing \(typeName(self)) in context." + ) + return nil + } } private func typedObjectInContext(context: NSManagedObjectContext) -> T? { @@ -78,29 +81,42 @@ internal extension NSManagedObject { let objectID = self.objectID if objectID.temporaryID { - var error: NSError? - let didSucceed = withExtendedLifetime(self.managedObjectContext) { + var objectIDError: NSError? + let didSucceed = withExtendedLifetime(self.managedObjectContext) { (context: NSManagedObjectContext?) -> Bool in - return $0?.obtainPermanentIDsForObjects([self], error: &error) + do { + + try context?.obtainPermanentIDsForObjects([self]) + return true + } + catch { + + objectIDError = error as NSError + return false + } } if didSucceed != true { CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to obtain permanent ID for object.") + objectIDError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed to obtain permanent ID for object." + ) return nil } } - var error: NSError? - if let existingObject = context.existingObjectWithID(objectID, error: &error) { + do { + let existingObject = try context.existingObjectWithID(objectID) return (existingObject as! T) } - - CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to load existing \(typeName(self)) in context.") - return nil; + catch { + + CoreStore.handleError( + error as NSError, + "Failed to load existing \(typeName(self)) in context." + ) + return nil + } } } diff --git a/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift b/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift index f4e2f1e..cf69c65 100644 --- a/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift +++ b/CoreStore/Internal/NSManagedObjectContext+CoreStore.swift @@ -73,10 +73,7 @@ internal extension NSManagedObjectContext { internal func setupForCoreStoreWithContextName(contextName: String) { - if self.respondsToSelector("setName:") { - - self.name = contextName - } + self.name = contextName self.observerForWillSaveNotification = NotificationObserver( notificationName: NSManagedObjectContextWillSaveNotification, @@ -91,16 +88,18 @@ internal extension NSManagedObjectContext { return } - var error: NSError? - if context.obtainPermanentIDsForObjects(Array(insertedObjects), error: &error) { + do { + try context.obtainPermanentIDsForObjects(Array(insertedObjects)) return } - - CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to obtain permanent ID(s) for \(numberOfInsertedObjects) inserted object(s)." - ) + catch { + + CoreStore.handleError( + error as NSError, + "Failed to obtain permanent ID(s) for \(numberOfInsertedObjects) inserted object(s)." + ) + } } ) } diff --git a/CoreStore/Internal/NSManagedObjectContext+Querying.swift b/CoreStore/Internal/NSManagedObjectContext+Querying.swift index 3bce69f..2eaca52 100644 --- a/CoreStore/Internal/NSManagedObjectContext+Querying.swift +++ b/CoreStore/Internal/NSManagedObjectContext+Querying.swift @@ -52,16 +52,24 @@ internal extension NSManagedObjectContext { } var fetchResults: [T]? - var error: NSError? + var fetchError: NSError? self.performBlockAndWait { - fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T] + do { + + fetchResults = try self.executeFetchRequest(fetchRequest) as? [T] + } + catch { + + fetchError = error as NSError + } } if fetchResults == nil { CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed executing fetch request.") + fetchError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed executing fetch request." + ) return nil } @@ -87,16 +95,24 @@ internal extension NSManagedObjectContext { } var fetchResults: [T]? - var error: NSError? + var fetchError: NSError? self.performBlockAndWait { - fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T] + do { + + fetchResults = try self.executeFetchRequest(fetchRequest) as? [T] + } + catch { + + fetchError = error as NSError + } } if fetchResults == nil { CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed executing fetch request.") + fetchError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed executing fetch request." + ) return nil } @@ -128,7 +144,8 @@ internal extension NSManagedObjectContext { CoreStore.handleError( error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed executing fetch request.") + "Failed executing fetch request." + ) return nil } @@ -154,16 +171,24 @@ internal extension NSManagedObjectContext { } var fetchResults: [NSManagedObjectID]? - var error: NSError? + var fetchError: NSError? self.performBlockAndWait { - fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObjectID] + do { + + fetchResults = try self.executeFetchRequest(fetchRequest) as? [NSManagedObjectID] + } + catch { + + fetchError = error as NSError + } } if fetchResults == nil { CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed executing fetch request.") + fetchError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed executing fetch request." + ) return nil } @@ -189,16 +214,24 @@ internal extension NSManagedObjectContext { } var fetchResults: [NSManagedObjectID]? - var error: NSError? + var fetchError: NSError? self.performBlockAndWait { - fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObjectID] + do { + + fetchResults = try self.executeFetchRequest(fetchRequest) as? [NSManagedObjectID] + } + catch { + + fetchError = error as NSError + } } if fetchResults == nil { CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed executing fetch request.") + fetchError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed executing fetch request." + ) return nil } @@ -225,26 +258,32 @@ internal extension NSManagedObjectContext { } var numberOfDeletedObjects: Int? - var error: NSError? + var fetchError: NSError? self.performBlockAndWait { autoreleasepool { - if let fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T] { + do { - numberOfDeletedObjects = fetchResults.count + let fetchResults = try self.executeFetchRequest(fetchRequest) as? [T] ?? [] for object in fetchResults { self.deleteObject(object) } + numberOfDeletedObjects = fetchResults.count + } + catch { + + fetchError = error as NSError } } } if numberOfDeletedObjects == nil { CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed executing fetch request.") + fetchError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed executing fetch request." + ) return nil } @@ -271,10 +310,17 @@ internal extension NSManagedObjectContext { } var fetchResults: [AnyObject]? - var error: NSError? + var fetchError: NSError? self.performBlockAndWait { - fetchResults = self.executeFetchRequest(fetchRequest, error: &error) + do { + + fetchResults = try self.executeFetchRequest(fetchRequest) + } + catch { + + fetchError = error as NSError + } } if let fetchResults = fetchResults { @@ -287,8 +333,9 @@ internal extension NSManagedObjectContext { } CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed executing fetch request.") + fetchError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed executing fetch request." + ) return nil } @@ -312,10 +359,17 @@ internal extension NSManagedObjectContext { } var fetchResults: [AnyObject]? - var error: NSError? + var fetchError: NSError? self.performBlockAndWait { - fetchResults = self.executeFetchRequest(fetchRequest, error: &error) + do { + + fetchResults = try self.executeFetchRequest(fetchRequest) + } + catch { + + fetchError = error as NSError + } } if let fetchResults = fetchResults { @@ -323,8 +377,9 @@ internal extension NSManagedObjectContext { } CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed executing fetch request.") + fetchError ?? NSError(coreStoreErrorCode: .UnknownError), + "Failed executing fetch request." + ) return nil } } diff --git a/CoreStore/Internal/NSManagedObjectContext+Transaction.swift b/CoreStore/Internal/NSManagedObjectContext+Transaction.swift index 4f3173d..28777dd 100644 --- a/CoreStore/Internal/NSManagedObjectContext+Transaction.swift +++ b/CoreStore/Internal/NSManagedObjectContext+Transaction.swift @@ -68,44 +68,43 @@ internal extension NSManagedObjectContext { internal func saveSynchronously() -> SaveResult { var result = SaveResult(hasChanges: false) - self.performBlockAndWait { - [unowned self] () -> Void in + + self.performBlockAndWait { [unowned self] () -> Void in if !self.hasChanges { return } - var saveError: NSError? - if self.save(&saveError) { + do { - if self.shouldCascadeSavesToParent { - - if let parentContext = self.parentContext { - - switch parentContext.saveSynchronously() { - - case .Success(let hasChanges): - result = SaveResult(hasChanges: true) - case .Failure(let error): - result = SaveResult(error) - } - return - } - } - - result = SaveResult(hasChanges: true) + try self.save() } - else if let error = saveError { + catch { + let saveError = error as NSError CoreStore.handleError( - error, - "Failed to save <\(NSManagedObjectContext.self)>.") - result = SaveResult(error) + saveError, + "Failed to save \(typeName(NSManagedObjectContext))." + ) + result = SaveResult(saveError) + return + } + + if let parentContext = self.parentContext where self.shouldCascadeSavesToParent { + + switch parentContext.saveSynchronously() { + + case .Success: + result = SaveResult(hasChanges: true) + + case .Failure(let error): + result = SaveResult(error) + } } else { - result = SaveResult(hasChanges: false) + result = SaveResult(hasChanges: true) } } @@ -128,43 +127,35 @@ internal extension NSManagedObjectContext { return } - var saveError: NSError? - if self.save(&saveError) { + do { - if self.shouldCascadeSavesToParent { - - if let parentContext = self.parentContext { - - let result = parentContext.saveSynchronously() - if let completion = completion { - - GCDQueue.Main.async { - - completion(result: result) - } - } - return - } - } - - if let completion = completion { - - GCDQueue.Main.async { - - completion(result: SaveResult(hasChanges: true)) - } - } + try self.save() } - else if let error = saveError { + catch { + let saveError = error as NSError CoreStore.handleError( - error, - "Failed to save <\(NSManagedObjectContext.self)>.") + saveError, + "Failed to save \(typeName(NSManagedObjectContext))." + ) if let completion = completion { GCDQueue.Main.async { - completion(result: SaveResult(error)) + completion(result: SaveResult(saveError)) + } + } + return + } + + if let parentContext = self.parentContext where self.shouldCascadeSavesToParent { + + let result = parentContext.saveSynchronously() + if let completion = completion { + + GCDQueue.Main.async { + + completion(result: result) } } } @@ -172,7 +163,7 @@ internal extension NSManagedObjectContext { GCDQueue.Main.async { - completion(result: SaveResult(hasChanges: false)) + completion(result: SaveResult(hasChanges: true)) } } } diff --git a/CoreStore/Internal/NSManagedObjectModel+Setup.swift b/CoreStore/Internal/NSManagedObjectModel+Setup.swift index 50ce421..c9ddf00 100644 --- a/CoreStore/Internal/NSManagedObjectModel+Setup.swift +++ b/CoreStore/Internal/NSManagedObjectModel+Setup.swift @@ -33,25 +33,177 @@ internal extension NSManagedObjectModel { // MARK: Internal + private var modelFileURL: NSURL? { + + get { + + return self.modelVersionFileURL?.URLByDeletingLastPathComponent + } + } + + private(set) var currentModelVersion: String? { + + get { + + let value: NSString? = getAssociatedObjectForKey( + &PropertyKeys.currentModelVersion, + inObject: self + ) + return value as? String + } + set { + + setAssociatedCopiedObject( + newValue == nil ? nil : (newValue! as NSString), + forKey: &PropertyKeys.currentModelVersion, + inObject: self + ) + } + } + + private(set) var modelVersions: Set? { + + get { + + let value: NSSet? = getAssociatedObjectForKey( + &PropertyKeys.modelVersions, + inObject: self + ) + return value as? Set + } + set { + + setAssociatedCopiedObject( + newValue == nil ? nil : (newValue! as NSSet), + forKey: &PropertyKeys.modelVersions, + inObject: self + ) + } + } + func entityNameForClass(entityClass: AnyClass) -> String { return self.entityNameMapping[NSStringFromClass(entityClass)]! } - func configurationsForClass(entityClass: AnyClass) -> Set { + func mergedModels() -> [NSManagedObjectModel] { - return self.entityConfigurationsMapping[NSStringFromClass(entityClass)]! + return self.modelVersions?.map { self[$0] }.flatMap { $0 == nil ? [] : [$0!] } ?? [self] + } + + subscript(modelVersion: String) -> NSManagedObjectModel? { + + if modelVersion == self.currentModelVersion { + + return self + } + + guard let modelFileURL = self.modelFileURL, + let modelVersions = self.modelVersions + where modelVersions.contains(modelVersion) else { + + return nil + } + + let versionModelFileURL = modelFileURL.URLByAppendingPathComponent("\(modelVersion).mom", isDirectory: false) + guard let model = NSManagedObjectModel(contentsOfURL: versionModelFileURL) else { + + return nil + } + + model.currentModelVersion = modelVersion + model.modelVersionFileURL = versionModelFileURL + model.modelVersions = modelVersions + return model + } + + class func fromBundle(bundle: NSBundle, modelName: String, modelVersion: String? = nil) -> NSManagedObjectModel { + + guard let modelFilePath = bundle.pathForResource(modelName, ofType: "momd") else { + + CoreStore.fatalError("Could not find \"\(modelName).momd\" from the bundle. \(bundle)") + } + + let modelFileURL = NSURL(fileURLWithPath: modelFilePath) + let versionInfoPlistURL = modelFileURL.URLByAppendingPathComponent("VersionInfo.plist", isDirectory: false) + + guard let versionInfo = NSDictionary(contentsOfURL: versionInfoPlistURL), + let versionHashes = versionInfo["NSManagedObjectModel_VersionHashes"] as? [String: AnyObject] else { + + CoreStore.fatalError("Could not load \(typeName(NSManagedObjectModel)) metadata from path \"\(versionInfoPlistURL)\"." + ) + } + + let modelVersions = Set(versionHashes.keys) + let currentModelVersion: String + + if let modelVersion = modelVersion { + + precondition(modelVersions.contains(modelVersion)) + currentModelVersion = modelVersion + } + else { + + currentModelVersion = versionInfo["NSManagedObjectModel_CurrentVersionName"] as? String ?? modelVersions.first! + } + + var modelVersionFileURL: NSURL? + for modelVersion in modelVersions { + + let fileURL = modelFileURL.URLByAppendingPathComponent("\(modelVersion).mom", isDirectory: false) + + if modelVersion == currentModelVersion { + + modelVersionFileURL = fileURL + continue + } + + CoreStore.assert( + NSManagedObjectModel(contentsOfURL: fileURL) != nil, + "Could not find the \"\(modelVersion).mom\" version file for the model at URL \"\(modelFileURL)\"." + ) + } + + if let modelVersionFileURL = modelVersionFileURL, + let rootModel = NSManagedObjectModel(contentsOfURL: modelVersionFileURL) { + + rootModel.modelVersionFileURL = modelVersionFileURL + rootModel.modelVersions = modelVersions + rootModel.currentModelVersion = currentModelVersion + return rootModel + } + + CoreStore.fatalError("Could not create an \(typeName(NSManagedObjectModel)) from the model at URL \"\(modelFileURL)\".") } // MARK: Private - - internal var entityNameMapping: [String: String] { + private var modelVersionFileURL: NSURL? { get { - if let mapping: NSDictionary? = getAssociatedObjectForKey(&PropertyKeys.entityNameMapping, inObject: self) { + let value: NSURL? = getAssociatedObjectForKey( + &PropertyKeys.modelVersionFileURL, + inObject: self + ) + return value + } + set { + + setAssociatedCopiedObject( + newValue, + forKey: &PropertyKeys.modelVersionFileURL, + inObject: self + ) + } + } + + private var entityNameMapping: [String: String] { + + get { + + if let mapping: NSDictionary = getAssociatedObjectForKey(&PropertyKeys.entityNameMapping, inObject: self) { return mapping as! [String: String] } @@ -75,25 +227,12 @@ internal extension NSManagedObjectModel { } } - private lazy var entityConfigurationsMapping: [String: Set] = { - [unowned self] in - - return self.configurations.reduce([String: Set]()) { - (var mapping, configuration) -> [String: Set] in - - return (self.entitiesForConfiguration(configuration) ?? []).reduce(mapping) { - (var mapping, entityDescription) -> [String: Set] in - - let className = entityDescription.managedObjectClassName - mapping[className]?.insert(configuration) - return mapping - } - } - }() - private struct PropertyKeys { static var entityNameMapping: Void? - static var entityConfigurationsMapping: Void? + + static var modelVersionFileURL: Void? + static var modelVersions: Void? + static var currentModelVersion: Void? } } diff --git a/CoreStore/Internal/NotificationObserver.swift b/CoreStore/Internal/NotificationObserver.swift index ad4d899..b663a41 100644 --- a/CoreStore/Internal/NotificationObserver.swift +++ b/CoreStore/Internal/NotificationObserver.swift @@ -36,7 +36,7 @@ internal final class NotificationObserver { let object: AnyObject? let observer: NSObjectProtocol - init(notificationName: String, object: AnyObject?, closure: (note: NSNotification!) -> Void) { + init(notificationName: String, object: AnyObject?, closure: (note: NSNotification) -> Void) { self.notificationName = notificationName self.object = object diff --git a/CoreStore/Internal/RootManagedObjectModel.swift b/CoreStore/Internal/RootManagedObjectModel.swift deleted file mode 100644 index f9246e4..0000000 --- a/CoreStore/Internal/RootManagedObjectModel.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// RootManagedObjectModel.swift -// CoreStore -// -// Created by John Rommel Estropia on 2015/07/04. -// Copyright © 2015 John Rommel Estropia. All rights reserved. -// - -import UIKit - -class RootManagedObjectModel: NSManagedObjectModel { - -} diff --git a/CoreStore/Logging/CoreStore+Logging.swift b/CoreStore/Logging/CoreStore+Logging.swift index c0284f0..78653e4 100644 --- a/CoreStore/Logging/CoreStore+Logging.swift +++ b/CoreStore/Logging/CoreStore+Logging.swift @@ -60,7 +60,8 @@ public extension CoreStore { message: message, fileName: fileName, lineNumber: lineNumber, - functionName: functionName) + functionName: functionName + ) } internal static func assert(@autoclosure condition: () -> Bool, _ message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) { @@ -70,6 +71,17 @@ public extension CoreStore { message: message, fileName: fileName, lineNumber: lineNumber, - functionName: functionName) + functionName: functionName + ) + } + + @noreturn internal static func fatalError(message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) { + + self.logger.fatalError( + message, + fileName: fileName, + lineNumber: lineNumber, + functionName: functionName + ) } } diff --git a/CoreStore/Logging/CoreStoreLogger.swift b/CoreStore/Logging/CoreStoreLogger.swift index fdf2756..cf0401b 100644 --- a/CoreStore/Logging/CoreStoreLogger.swift +++ b/CoreStore/Logging/CoreStoreLogger.swift @@ -56,7 +56,7 @@ public protocol CoreStoreLogger { :lineNumber: the source line number :functionName: the source function name */ - func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) + func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) /** Handles errors sent by the `CoreStore` framework. @@ -67,7 +67,7 @@ public protocol CoreStoreLogger { :lineNumber: the source line number :functionName: the source function name */ - func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) + func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) /** Handles assertions made throughout the `CoreStore` framework. @@ -79,6 +79,16 @@ public protocol CoreStoreLogger { :functionName: the source function name */ func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) + + /** + Handles fatal errors made throughout the `CoreStore` framework. Implementations should guarantee that the method does not return, either by calling fatalError() or preconditionFailure(), or by raising an exception. + + :message: the error message + :fileName: the source file name + :lineNumber: the source line number + :functionName: the source function name + */ + @noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) } @@ -98,3 +108,9 @@ internal func typeName(value: AnyClass) -> String { return "<\(value)>" } + +internal func typeName(name: String?) -> String { + + let typeName = name ?? "unknown" + return "<\(typeName)>" +} diff --git a/CoreStore/Logging/DefaultLogger.swift b/CoreStore/Logging/DefaultLogger.swift index ca2907f..b18c007 100644 --- a/CoreStore/Logging/DefaultLogger.swift +++ b/CoreStore/Logging/DefaultLogger.swift @@ -31,15 +31,15 @@ import Foundation /** The `DefaultLogger` is a basic implementation of the `CoreStoreLogger` protocol. -- The `log(...)` method calls `println(...)` to print the level, source file name, line number, function name, and the log message. -- The `handleError(...)` method calls `println(...)` to print the source file name, line number, function name, and the error message. +- The `log(...)` method calls `print(...)` to print the level, source file name, line number, function name, and the log message. +- The `handleError(...)` method calls `print(...)` to print the source file name, line number, function name, and the error message. - The `assert(...)` method calls `assert(...)` on the arguments. */ public final class DefaultLogger: CoreStoreLogger { public init() { } - public func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { + public func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { #if DEBUG let levelString: String @@ -49,14 +49,14 @@ public final class DefaultLogger: CoreStoreLogger { case .Warning: levelString = "Warning" case .Fatal: levelString = "Fatal" } - Swift.println("[CoreStore:\(levelString)] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") + Swift.print("[CoreStore:\(levelString)] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") #endif } - public func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { + public func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { #if DEBUG - Swift.println("[CoreStore:Error] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message): \(error)\n") + Swift.print("[CoreStore:Error] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message): \(error)\n") #endif } @@ -66,4 +66,9 @@ public final class DefaultLogger: CoreStoreLogger { Swift.assert(condition, message, file: fileName, line: numericCast(lineNumber)) #endif } + + @noreturn public func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { + + Swift.fatalError("[CoreStore:Abort] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") + } } diff --git a/CoreStore/Migrating/MigrationChain.swift b/CoreStore/Migrating/MigrationChain.swift index 3cc563e..6cb899b 100644 --- a/CoreStore/Migrating/MigrationChain.swift +++ b/CoreStore/Migrating/MigrationChain.swift @@ -42,7 +42,11 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D public func nextVersionFrom(version: String) -> String? { - return self.versionTree[version] + if let nextVersion = self.versionTree[version] where nextVersion != version { + + return nextVersion + } + return nil } @@ -65,6 +69,7 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D self.leafVersions = [value] } + // MARK: ExtendedGraphemeClusterLiteralConvertible public init(extendedGraphemeClusterLiteral value: String) { @@ -126,8 +131,8 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D } self.versionTree = versionTree - self.rootVersions = Set(flatMap([elements.first]) { $0 == nil ? [] : [$0!] }) - self.leafVersions = Set(flatMap([elements.last]) { $0 == nil ? [] : [$0!] }) + self.rootVersions = Set([elements.first].flatMap { $0 == nil ? [] : [$0!] }) + self.leafVersions = Set([elements.last].flatMap { $0 == nil ? [] : [$0!] }) } diff --git a/CoreStore/Observing/CoreStore+Observing.swift b/CoreStore/Observing/CoreStore+Observing.swift index 9073768..ec3f9b5 100644 --- a/CoreStore/Observing/CoreStore+Observing.swift +++ b/CoreStore/Observing/CoreStore+Observing.swift @@ -36,8 +36,8 @@ public extension CoreStore { /** Using the `defaultStack`, creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`. - :param: object the `NSManagedObject` to observe changes from - :returns: a `ObjectMonitor` that monitors changes to `object` + - parameter object: the `NSManagedObject` to observe changes from + - returns: a `ObjectMonitor` that monitors changes to `object` */ public static func monitorObject(object: T) -> ObjectMonitor { @@ -47,9 +47,9 @@ public extension CoreStore { /** Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ListMonitor` instance that monitors changes to the list + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: a `ListMonitor` instance that monitors changes to the list */ public static func monitorList(from: From, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ListMonitor { @@ -59,9 +59,9 @@ public extension CoreStore { /** Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. - :param: from a `From` clause indicating the entity type - :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ListMonitor` instance that monitors changes to the list + - parameter from: a `From` clause indicating the entity type + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: a `ListMonitor` instance that monitors changes to the list */ public static func monitorList(from: From, _ groupBy: GroupBy? = nil, _ queryClauses: [FetchClause]) -> ListMonitor { @@ -71,10 +71,10 @@ public extension CoreStore { /** Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. - :param: from a `From` clause indicating the entity type - :param: sectionBy a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ListMonitor` instance that monitors changes to the list + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: a `ListMonitor` instance that monitors changes to the list */ public static func monitorSectionedList(from: From, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor { @@ -84,10 +84,10 @@ public extension CoreStore { /** Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. - :param: from a `From` clause indicating the entity type - :param: sectionBy a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. - :param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. - :returns: a `ListMonitor` instance that monitors changes to the list + - parameter from: a `From` clause indicating the entity type + - parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections. + - parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses. + - returns: a `ListMonitor` instance that monitors changes to the list */ public static func monitorSectionedList(from: From, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor { diff --git a/CoreStore/Observing/ListMonitor.swift b/CoreStore/Observing/ListMonitor.swift index d74a315..b32055e 100644 --- a/CoreStore/Observing/ListMonitor.swift +++ b/CoreStore/Observing/ListMonitor.swift @@ -74,32 +74,32 @@ public final class ListMonitor { /** Accesses the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `addObserver(_:)`. - :param: index the index of the object. Using an index above the valid range will throw an exception. + - parameter index: the index of the object. Using an index above the valid range will throw an exception. */ public subscript(index: Int) -> T { - return self.fetchedResultsController.objectAtIndexPath(NSIndexPath(forItem: index, inSection: 0)) as! T - } - - /** - Accesses the object at the given `NSIndexPath`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. - - :param: indexPath the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will throw an exception. - */ - public subscript(indexPath: NSIndexPath) -> T { - - return self.fetchedResultsController.objectAtIndexPath(indexPath) as! T + return self[0, index] } /** Accesses the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. - :param: sectionIndex the section index for the object. Using a `sectionIndex` with an invalid range will throw an exception. - :param: itemIndex the index for the object within the section. Using an `itemIndex` with an invalid range will throw an exception. + - parameter sectionIndex: the section index for the object. Using a `sectionIndex` with an invalid range will throw an exception. + - parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will throw an exception. */ public subscript(sectionIndex: Int, itemIndex: Int) -> T { - return self.fetchedResultsController.objectAtIndexPath(NSIndexPath(forItem: itemIndex, inSection: sectionIndex)) as! T + return self[NSIndexPath(forItem: itemIndex, inSection: sectionIndex)] + } + + /** + Accesses the object at the given `NSIndexPath`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`. + + - parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will throw an exception. + */ + public subscript(indexPath: NSIndexPath) -> T { + + return self.fetchedResultsController.objectAtIndexPath(indexPath) as! T } /** @@ -113,21 +113,21 @@ public final class ListMonitor { /** Returns the number of objects in the specified section - :param: section the section index + - parameter section: the section index */ public func numberOfObjectsInSection(section: Int) -> Int { - return (self.fetchedResultsController.sections?[section] as? NSFetchedResultsSectionInfo)?.numberOfObjects ?? 0 + return self.fetchedResultsController.sections?[section].numberOfObjects ?? 0 } /** Returns the `NSFetchedResultsSectionInfo` for the specified section - :param: section the section index + - parameter section: the section index */ public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo { - return self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo + return self.fetchedResultsController.sections![section] } /** @@ -139,11 +139,14 @@ public final class ListMonitor { Calling `addObserver(_:)` multiple times on the same observer is safe, as `ListMonitor` unregisters previous notifications to the observer before re-registering them. - :param: observer a `ListObserver` to send change notifications to + - parameter observer: a `ListObserver` to send change notifications to */ public func addObserver(observer: U) { - CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to add an observer of type \(typeName(observer)) outside the main thread." + ) self.removeObserver(observer) @@ -182,11 +185,14 @@ public final class ListMonitor { Calling `addObserver(_:)` multiple times on the same observer is safe, as `ListMonitor` unregisters previous notifications to the observer before re-registering them. - :param: observer a `ListObjectObserver` to send change notifications to + - parameter observer: a `ListObjectObserver` to send change notifications to */ public func addObserver(observer: U) { - CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to add an observer of type \(typeName(observer)) outside the main thread." + ) self.removeObserver(observer) @@ -291,11 +297,14 @@ public final class ListMonitor { Calling `addObserver(_:)` multiple times on the same observer is safe, as `ListMonitor` unregisters previous notifications to the observer before re-registering them. - :param: observer a `ListSectionObserver` to send change notifications to + - parameter observer: a `ListSectionObserver` to send change notifications to */ public func addObserver(observer: U) { - CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to add an observer of type \(typeName(observer)) outside the main thread." + ) self.removeObserver(observer) @@ -429,11 +438,14 @@ public final class ListMonitor { For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread. - :param: observer a `ListObserver` to unregister notifications to + - parameter observer: a `ListObserver` to unregister notifications to */ public func removeObserver(observer: U) { - CoreStore.assert(NSThread.isMainThread(), "Attempted to remove an observer of type \(typeName(observer)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to remove an observer of type \(typeName(observer)) outside the main thread." + ) let nilValue: AnyObject? = nil setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeList, inObject: observer) @@ -492,12 +504,16 @@ public final class ListMonitor { fetchedResultsControllerDelegate.handler = self fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController - var error: NSError? - if !fetchedResultsController.performFetch(&error) { + do { + + try fetchedResultsController.performFetch() + } + catch { CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to perform fetch on <\(NSFetchedResultsController.self)>.") + error as NSError, + "Failed to perform fetch on \(typeName(NSFetchedResultsController))." + ) } } @@ -705,31 +721,31 @@ private protocol FetchedResultsControllerHandler: class { // MARK: - FetchedResultsControllerDelegate -private final class FetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate { +private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate { // MARK: NSFetchedResultsControllerDelegate - @objc func controllerWillChangeContent(controller: NSFetchedResultsController) { + @objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) { self.handler?.controllerWillChangeContent(controller) } - @objc func controllerDidChangeContent(controller: NSFetchedResultsController) { + @objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) { self.handler?.controllerDidChangeContent(controller) } - @objc func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { + @objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath) } - @objc func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { + @objc dynamic func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { self.handler?.controller(controller, didChangeSection: sectionInfo, atIndex: sectionIndex, forChangeType: type) } - @objc func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? { + @objc dynamic func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? { return self.handler?.controller(controller, sectionIndexTitleForSectionName: sectionName) } diff --git a/CoreStore/Observing/ListObserver.swift b/CoreStore/Observing/ListObserver.swift index dc6b29b..7a9581a 100644 --- a/CoreStore/Observing/ListObserver.swift +++ b/CoreStore/Observing/ListObserver.swift @@ -48,14 +48,14 @@ public protocol ListObserver: class { /** Handles processing just before a change to the observed list occurs - :param: monitor the `ListMonitor` monitoring the list being observed + - parameter monitor: the `ListMonitor` monitoring the list being observed */ func listMonitorWillChange(monitor: ListMonitor) /** Handles processing right after a change to the observed list occurs - :param: monitor the `ListMonitor` monitoring the object being observed + - parameter monitor: the `ListMonitor` monitoring the object being observed */ func listMonitorDidChange(monitor: ListMonitor) } @@ -77,37 +77,37 @@ public protocol ListObjectObserver: ListObserver { /** Notifies that an object was inserted to the specified `NSIndexPath` in the list - :param: monitor the `ListMonitor` monitoring the list being observed - :param: object the entity type for the inserted object - :param: indexPath the new `NSIndexPath` for the inserted object + - parameter monitor: the `ListMonitor` monitoring the list being observed + - parameter object: the entity type for the inserted object + - parameter indexPath: the new `NSIndexPath` for the inserted object */ func listMonitor(monitor: ListMonitor, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath) /** Notifies that an object was deleted from the specified `NSIndexPath` in the list - :param: monitor the `ListMonitor` monitoring the list being observed - :param: object the entity type for the deleted object - :param: indexPath the `NSIndexPath` for the deleted object + - parameter monitor: the `ListMonitor` monitoring the list being observed + - parameter object: the entity type for the deleted object + - parameter indexPath: the `NSIndexPath` for the deleted object */ func listMonitor(monitor: ListMonitor, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath) /** Notifies that an object at the specified `NSIndexPath` was updated - :param: monitor the `ListMonitor` monitoring the list being observed - :param: object the entity type for the updated object - :param: indexPath the `NSIndexPath` for the updated object + - parameter monitor: the `ListMonitor` monitoring the list being observed + - parameter object: the entity type for the updated object + - parameter indexPath: the `NSIndexPath` for the updated object */ func listMonitor(monitor: ListMonitor, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath) /** Notifies that an object's index changed - :param: monitor the `ListMonitor` monitoring the list being observed - :param: object the entity type for the moved object - :param: fromIndexPath the previous `NSIndexPath` for the moved object - :param: toIndexPath the new `NSIndexPath` for the moved object + - parameter monitor: the `ListMonitor` monitoring the list being observed + - parameter object: the entity type for the moved object + - parameter fromIndexPath: the previous `NSIndexPath` for the moved object + - parameter toIndexPath: the new `NSIndexPath` for the moved object */ func listMonitor(monitor: ListMonitor, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) } @@ -130,18 +130,18 @@ public protocol ListSectionObserver: ListObjectObserver { /** Notifies that a section was inserted at the specified index - :param: monitor the `ListMonitor` monitoring the list being observed - :param: sectionInfo the `NSFetchedResultsSectionInfo` for the inserted section - :param: sectionIndex the new section index for the new section + - parameter monitor: the `ListMonitor` monitoring the list being observed + - parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section + - parameter sectionIndex: the new section index for the new section */ func listMonitor(monitor: ListMonitor, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) /** Notifies that a section was inserted at the specified index - :param: monitor the `ListMonitor` monitoring the list being observed - :param: sectionInfo the `NSFetchedResultsSectionInfo` for the deleted section - :param: sectionIndex the previous section index for the deleted section + - parameter monitor: the `ListMonitor` monitoring the list being observed + - parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section + - parameter sectionIndex: the previous section index for the deleted section */ func listMonitor(monitor: ListMonitor, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) } diff --git a/CoreStore/Observing/ObjectMonitor.swift b/CoreStore/Observing/ObjectMonitor.swift index e62fa15..6ac31ca 100644 --- a/CoreStore/Observing/ObjectMonitor.swift +++ b/CoreStore/Observing/ObjectMonitor.swift @@ -83,11 +83,14 @@ public final class ObjectMonitor { Calling `addObserver(_:)` multiple times on the same observer is safe, as `ObjectMonitor` unregisters previous notifications to the observer before re-registering them. - :param: observer an `ObjectObserver` to send change notifications to + - parameter observer: an `ObjectObserver` to send change notifications to */ public func addObserver(observer: U) { - CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to add an observer of type \(typeName(observer)) outside the main thread." + ) self.removeObserver(observer) @@ -95,9 +98,9 @@ public final class ObjectMonitor { &NotificationKey.willChangeObject, name: ObjectMonitorWillChangeObjectNotification, toObserver: observer, - callback: { [weak self, weak observer] (monitor) -> Void in + callback: { [weak observer] (monitor) -> Void in - if let strongSelf = self, let object = strongSelf.object, let observer = observer { + if let object = monitor.object, let observer = observer { observer.objectMonitor(monitor, willUpdateObject: object) } @@ -107,9 +110,9 @@ public final class ObjectMonitor { &NotificationKey.didDeleteObject, name: ObjectMonitorDidDeleteObjectNotification, toObserver: observer, - callback: { [weak self, weak observer] (monitor, object) -> Void in + callback: { [weak observer] (monitor, object) -> Void in - if let strongSelf = self, let observer = observer { + if let observer = observer { observer.objectMonitor(monitor, didDeleteObject: object) } @@ -124,15 +127,15 @@ public final class ObjectMonitor { if let strongSelf = self, let observer = observer { let previousCommitedAttributes = strongSelf.lastCommittedAttributes - let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [NSString: NSObject] + let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [String: NSObject] - var changedKeys = Set() - for key in currentCommitedAttributes.keys { + let changedKeys = currentCommitedAttributes.keys.reduce(Set()) { (var changedKeys, key) -> Set in if previousCommitedAttributes[key] != currentCommitedAttributes[key] { - changedKeys.insert(key as String) + changedKeys.insert(key) } + return changedKeys } strongSelf.lastCommittedAttributes = currentCommitedAttributes @@ -151,11 +154,14 @@ public final class ObjectMonitor { For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread. - :param: observer an `ObjectObserver` to unregister notifications to + - parameter observer: an `ObjectObserver` to unregister notifications to */ public func removeObserver(observer: U) { - CoreStore.assert(NSThread.isMainThread(), "Attempted to remove an observer of type \(typeName(observer)) outside the main thread.") + CoreStore.assert( + NSThread.isMainThread(), + "Attempted to remove an observer of type \(typeName(observer)) outside the main thread." + ) let nilValue: AnyObject? = nil setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeObject, inObject: observer) @@ -197,15 +203,20 @@ public final class ObjectMonitor { fetchedResultsControllerDelegate.handler = self fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController - var error: NSError? - if !fetchedResultsController.performFetch(&error) { + + do { + + try fetchedResultsController.performFetch() + } + catch { CoreStore.handleError( - error ?? NSError(coreStoreErrorCode: .UnknownError), - "Failed to perform fetch on <\(NSFetchedResultsController.self)>.") + error as NSError, + "Failed to perform fetch for \(typeName(NSFetchedResultsController))." + ) } - self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [NSString: NSObject]) ?? [:] + self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [String: NSObject]) ?? [:] } @@ -214,7 +225,7 @@ public final class ObjectMonitor { private let originalObjectID: NSManagedObjectID private let fetchedResultsController: NSFetchedResultsController private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate - private var lastCommittedAttributes = [NSString: NSObject]() + private var lastCommittedAttributes = [String: NSObject]() private weak var parentStack: DataStack? private func registerChangeNotification(notificationKey: UnsafePointer, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor) -> Void) { @@ -313,16 +324,16 @@ private protocol FetchedResultsControllerHandler: class { // MARK: - FetchedResultsControllerDelegate -private final class FetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate { +private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate { // MARK: NSFetchedResultsControllerDelegate - @objc func controllerWillChangeContent(controller: NSFetchedResultsController) { + @objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) { self.handler?.controllerWillChangeContent(controller) } - @objc func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { + @objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) { self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath) } diff --git a/CoreStore/Observing/ObjectObserver.swift b/CoreStore/Observing/ObjectObserver.swift index 26de479..40bcbc6 100644 --- a/CoreStore/Observing/ObjectObserver.swift +++ b/CoreStore/Observing/ObjectObserver.swift @@ -45,25 +45,25 @@ public protocol ObjectObserver: class { /** Handles processing just before a change to the observed `object` occurs - :param: monitor the `ObjectMonitor` monitoring the object being observed - :param: object the `NSManagedObject` instance being observed + - parameter monitor: the `ObjectMonitor` monitoring the object being observed + - parameter object: the `NSManagedObject` instance being observed */ func objectMonitor(monitor: ObjectMonitor, willUpdateObject object: EntityType) /** Handles processing right after a change to the observed `object` occurs - :param: monitor the `ObjectMonitor` monitoring the object being observed - :param: object the `NSManagedObject` instance being observed - :param: changedPersistentKeys a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported. + - parameter monitor: the `ObjectMonitor` monitoring the object being observed + - parameter object: the `NSManagedObject` instance being observed + - parameter changedPersistentKeys: a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported. */ func objectMonitor(monitor: ObjectMonitor, didUpdateObject object: EntityType, changedPersistentKeys: Set) /** Handles processing right after `object` is deleted - :param: monitor the `ObjectMonitor` monitoring the object being observed - :param: object the `NSManagedObject` instance being observed + - parameter monitor: the `ObjectMonitor` monitoring the object being observed + - parameter object: the `NSManagedObject` instance being observed */ func objectMonitor(monitor: ObjectMonitor, didDeleteObject object: EntityType) } diff --git a/CoreStore/Observing/SectionBy.swift b/CoreStore/Observing/SectionBy.swift index a203c1d..4a41b87 100644 --- a/CoreStore/Observing/SectionBy.swift +++ b/CoreStore/Observing/SectionBy.swift @@ -45,7 +45,7 @@ public struct SectionBy { /** Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections - :param: sectionKeyPath the key path to use to group the objects into sections + - parameter sectionKeyPath: the key path to use to group the objects into sections */ public init(_ sectionKeyPath: KeyPath) { @@ -55,8 +55,8 @@ public struct SectionBy { /** Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name - :param: sectionKeyPath the key path to use to group the objects into sections - :param: sectionIndexTransformer a closure to transform the value for the key path to an appropriate section name + - parameter sectionKeyPath: the key path to use to group the objects into sections + - parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name */ public init(_ sectionKeyPath: KeyPath, _ sectionIndexTransformer: (sectionName: String?) -> String?) { diff --git a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift index 3671ec5..144faf6 100644 --- a/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/AsynchronousDataTransaction.swift @@ -40,12 +40,18 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { /** Saves the transaction changes asynchronously. This method should not be used after the `commit()` method was already called once. - :param: completion the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block. + - parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block. */ public func commit(completion: (result: SaveResult) -> Void) { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue.") - CoreStore.assert(!self.isCommitted, "Attempted to commit a \(typeName(self)) more than once.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to commit a \(typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to commit a \(typeName(self)) more than once." + ) self.isCommitted = true let semaphore = GCDSemaphore(0) @@ -63,8 +69,14 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { */ public func commit() { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue.") - CoreStore.assert(!self.isCommitted, "Attempted to commit a \(typeName(self)) more than once.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to commit a \(typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to commit a \(typeName(self)) more than once." + ) self.isCommitted = true self.result = self.context.saveSynchronously() @@ -73,13 +85,19 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { /** Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once. - :param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - :returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously */ public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue.") - CoreStore.assert(!self.isCommitted, "Attempted to begin a child transaction from an already committed \(typeName(self)).") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to begin a child transaction from an already committed \(typeName(self))." + ) return SynchronousDataTransaction( mainContext: self.context, @@ -93,12 +111,15 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { /** Creates a new `NSManagedObject` with the specified entity type. - :param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration - :returns: a new `NSManagedObject` instance of the specified entity type. + - parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration + - returns: a new `NSManagedObject` instance of the specified entity type. */ public override func create(into: Into) -> T { - CoreStore.assert(!self.isCommitted, "Attempted to create an entity of type <\(T.self)> from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))." + ) return super.create(into) } @@ -106,12 +127,15 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { /** Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once. - :param: object the `NSManagedObject` type to be edited - :returns: an editable proxy for the specified `NSManagedObject`. + - parameter object: the `NSManagedObject` type to be edited + - returns: an editable proxy for the specified `NSManagedObject`. */ public override func edit(object: T?) -> T? { - CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self))." + ) return super.edit(object) } @@ -119,13 +143,16 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { /** Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once. - :param: into an `Into` clause specifying the entity type - :param: objectID the `NSManagedObjectID` for the object to be edited - :returns: an editable proxy for the specified `NSManagedObject`. + - parameter into: an `Into` clause specifying the entity type + - parameter objectID: the `NSManagedObjectID` for the object to be edited + - returns: an editable proxy for the specified `NSManagedObject`. */ public override func edit(into: Into, _ objectID: NSManagedObjectID) -> T? { - CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type <\(T.self)> from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to update an entity of type \(typeName(T)) from an already committed \(typeName(self))." + ) return super.edit(into, objectID) } @@ -133,11 +160,14 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { /** Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once. - :param: object the `NSManagedObject` type to be deleted + - parameter object: the `NSManagedObject` type to be deleted */ public override func delete(object: NSManagedObject?) { - CoreStore.assert(!self.isCommitted, "Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self))." + ) super.delete(object) } @@ -145,13 +175,16 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { /** Deletes the specified `NSManagedObject`s. - :param: object1 the `NSManagedObject` type to be deleted - :param: object2 another `NSManagedObject` type to be deleted - :param: objects other `NSManagedObject`s type to be deleted + - parameter object1: the `NSManagedObject` type to be deleted + - parameter object2: another `NSManagedObject` type to be deleted + - parameter objects: other `NSManagedObject`s type to be deleted */ public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) { - CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to delete an entities from an already committed \(typeName(self))." + ) super.delete([object1, object2] + objects) } @@ -159,11 +192,14 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { /** Deletes the specified `NSManagedObject`s. - :param: objects the `NSManagedObject`s type to be deleted + - parameter objects: the `NSManagedObject`s type to be deleted */ public override func delete(objects: [NSManagedObject?]) { - CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to delete an entities from an already committed \(typeName(self))." + ) super.delete(objects) } @@ -173,7 +209,10 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { */ public override func rollback() { - CoreStore.assert(!self.isCommitted, "Attempted to rollback an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to rollback an already committed \(typeName(self))." + ) super.rollback() } @@ -195,7 +234,10 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { self.closure(transaction: self) if !self.isCommitted && self.hasChanges { - CoreStore.log(.Warning, message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded.") + CoreStore.log( + .Warning, + message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded." + ) } } } @@ -205,9 +247,13 @@ public final class AsynchronousDataTransaction: BaseDataTransaction { self.transactionQueue.sync { self.closure(transaction: self) + if !self.isCommitted && self.hasChanges { - CoreStore.log(.Warning, message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded.") + CoreStore.log( + .Warning, + message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded." + ) } } return self.result diff --git a/CoreStore/Saving and Processing/BaseDataTransaction.swift b/CoreStore/Saving and Processing/BaseDataTransaction.swift index bb5ea0e..671e679 100644 --- a/CoreStore/Saving and Processing/BaseDataTransaction.swift +++ b/CoreStore/Saving and Processing/BaseDataTransaction.swift @@ -48,62 +48,68 @@ public /*abstract*/ class BaseDataTransaction { /** Creates a new `NSManagedObject` with the specified entity type. - :param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration - :returns: a new `NSManagedObject` instance of the specified entity type. + - parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration + - returns: a new `NSManagedObject` instance of the specified entity type. */ public func create(into: Into) -> T { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to create an entity of type <\(T.self)> outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to create an entity of type \(typeName(T)) outside its designated queue." + ) let context = self.context let entityClass = (into.entityClass as! NSManagedObject.Type) - let object = entityClass.createInContext(context) as! T - if into.inferStoreIfPossible { switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: nil, inferStoreIfPossible: true) { - case (.Some(let persistentStore), _): + case (let persistentStore?, _): + let object = entityClass.createInContext(context) as! T context.assignObject(object, toPersistentStore: persistentStore) + return object case (.None, true): - CoreStore.assert(false, "Attempted to create an entity of type <\(entityClass)> with ambiguous destination persistent store, but the configuration name was not specified.") + CoreStore.fatalError("Attempted to create an entity of type \(typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.") default: - CoreStore.assert(false, "Attempted to create an entity of type <\(entityClass)>, but a destination persistent store containing the entity type could not be found.") + CoreStore.fatalError("Attempted to create an entity of type \(typeName(entityClass)), but a destination persistent store containing the entity type could not be found.") } } else { switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: into.configuration, inferStoreIfPossible: false) { - case (.Some(let persistentStore), _): + case (let persistentStore?, _): + let object = entityClass.createInContext(context) as! T context.assignObject(object, toPersistentStore: persistentStore) + return object default: if let configuration = into.configuration { - CoreStore.assert(false, "Attempted to create an entity of type <\(entityClass)> into the configuration \"\(configuration)\", which it doesn't belong to.") + CoreStore.fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the configuration \"\(configuration)\", which it doesn't belong to.") } else { - CoreStore.assert(false, "Attempted to create an entity of type <\(entityClass)> into the default configuration, which it doesn't belong to.") + CoreStore.fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the default configuration, which it doesn't belong to.") } } } - - return object } /** Returns an editable proxy of a specified `NSManagedObject`. - :param: object the `NSManagedObject` type to be edited - :returns: an editable proxy for the specified `NSManagedObject`. + - parameter object: the `NSManagedObject` type to be edited + - returns: an editable proxy for the specified `NSManagedObject`. */ public func edit(object: T?) -> T? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type \(typeName(object)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to update an entity of type \(typeName(object)) outside its designated queue." + ) return object?.inContext(self.context) } @@ -111,14 +117,21 @@ public /*abstract*/ class BaseDataTransaction { /** Returns an editable proxy of the object with the specified `NSManagedObjectID`. - :param: into an `Into` clause specifying the entity type - :param: objectID the `NSManagedObjectID` for the object to be edited - :returns: an editable proxy for the specified `NSManagedObject`. + - parameter into: an `Into` clause specifying the entity type + - parameter objectID: the `NSManagedObjectID` for the object to be edited + - returns: an editable proxy for the specified `NSManagedObject`. */ public func edit(into: Into, _ objectID: NSManagedObjectID) -> T? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(T.self)> outside its designated queue.") - CoreStore.assert(into.inferStoreIfPossible || (into.configuration ?? Into.defaultConfigurationName) == objectID.persistentStore?.configurationName, "Attempted to update an entity of type <\(T.self)> but the specified persistent store do not match the `NSManagedObjectID`.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to update an entity of type \(typeName(T)) outside its designated queue." + ) + CoreStore.assert( + into.inferStoreIfPossible + || (into.configuration ?? Into.defaultConfigurationName) == objectID.persistentStore?.configurationName, + "Attempted to update an entity of type \(typeName(T)) but the specified persistent store do not match the `NSManagedObjectID`." + ) return (into.entityClass as! NSManagedObject.Type).inContext(self.context, withObjectID: objectID) as? T } @@ -126,11 +139,14 @@ public /*abstract*/ class BaseDataTransaction { /** Deletes a specified `NSManagedObject`. - :param: object the `NSManagedObject` to be deleted + - parameter object: the `NSManagedObject` to be deleted */ public func delete(object: NSManagedObject?) { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to delete an entity outside its designated queue." + ) object?.inContext(self.context)?.deleteFromContext() } @@ -138,9 +154,9 @@ public /*abstract*/ class BaseDataTransaction { /** Deletes the specified `NSManagedObject`s. - :param: object1 the `NSManagedObject` to be deleted - :param: object2 another `NSManagedObject` to be deleted - :param: objects other `NSManagedObject`s to be deleted + - parameter object1: the `NSManagedObject` to be deleted + - parameter object2: another `NSManagedObject` to be deleted + - parameter objects: other `NSManagedObject`s to be deleted */ public func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) { @@ -150,11 +166,14 @@ public /*abstract*/ class BaseDataTransaction { /** Deletes the specified `NSManagedObject`s. - :param: objects the `NSManagedObject`s to be deleted + - parameter objects: the `NSManagedObject`s to be deleted */ public func delete(objects: [NSManagedObject?]) { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete entities outside their designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to delete entities outside their designated queue." + ) let context = self.context for object in objects { @@ -170,7 +189,10 @@ public /*abstract*/ class BaseDataTransaction { */ public func rollback() { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to rollback a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to rollback a \(typeName(self)) outside its designated queue." + ) self.context.reset() } diff --git a/CoreStore/Saving and Processing/CoreStore+Transaction.swift b/CoreStore/Saving and Processing/CoreStore+Transaction.swift index b2fd5b0..e51628f 100644 --- a/CoreStore/Saving and Processing/CoreStore+Transaction.swift +++ b/CoreStore/Saving and Processing/CoreStore+Transaction.swift @@ -35,7 +35,7 @@ public extension CoreStore { /** Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. - :param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. */ public static func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) { @@ -45,8 +45,8 @@ public extension CoreStore { /** Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. - :param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - :returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously */ public static func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { @@ -56,7 +56,7 @@ public extension CoreStore { /** Using the `defaultStack`, begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. A detached transaction object should typically be only used from the main queue. - :returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made. + - returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made. */ public static func beginDetached() -> DetachedDataTransaction { diff --git a/CoreStore/Saving and Processing/DetachedDataTransaction.swift b/CoreStore/Saving and Processing/DetachedDataTransaction.swift index d577d03..db2851e 100644 --- a/CoreStore/Saving and Processing/DetachedDataTransaction.swift +++ b/CoreStore/Saving and Processing/DetachedDataTransaction.swift @@ -39,11 +39,14 @@ public final class DetachedDataTransaction: BaseDataTransaction { /** Saves the transaction changes asynchronously. For a `DetachedDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread. - :param: completion the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block. + - parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block. */ public func commit(completion: (result: SaveResult) -> Void) { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to commit a \(typeName(self)) outside its designated queue." + ) self.context.saveAsynchronouslyWithCompletion { (result) -> Void in diff --git a/CoreStore/Saving and Processing/Into.swift b/CoreStore/Saving and Processing/Into.swift index f0f916e..fecd936 100644 --- a/CoreStore/Saving and Processing/Into.swift +++ b/CoreStore/Saving and Processing/Into.swift @@ -68,7 +68,7 @@ public struct Into { let person = transaction.create(Into(MyPersonEntity)) - :param: entity the `NSManagedObject` type to be created + - parameter entity: the `NSManagedObject` type to be created */ public init(_ entity: T.Type) { @@ -80,7 +80,7 @@ public struct Into { /** Initializes an `Into` clause with the specified entity class. - :param: entityClass the `NSManagedObject` class type to be created + - parameter entityClass: the `NSManagedObject` class type to be created */ public init(_ entityClass: AnyClass) { @@ -95,7 +95,7 @@ public struct Into { let person = transaction.create(Into("Configuration1")) - :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. + - parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. */ public init(_ configuration: String?) { @@ -110,8 +110,8 @@ public struct Into { let person = transaction.create(Into(MyPersonEntity.self, "Configuration1")) - :param: entity the `NSManagedObject` type to be created - :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. + - parameter entity: the `NSManagedObject` type to be created + - parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. */ public init(_ entity: T.Type, _ configuration: String?) { @@ -126,8 +126,8 @@ public struct Into { let person = transaction.create(Into(MyPersonEntity.self, "Configuration1")) - :param: entityClass the `NSManagedObject` class type to be created - :param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. + - parameter entityClass: the `NSManagedObject` class type to be created + - parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration. */ public init(_ entityClass: AnyClass, _ configuration: String?) { diff --git a/CoreStore/Saving and Processing/SaveResult.swift b/CoreStore/Saving and Processing/SaveResult.swift index ea140f7..13b49d4 100644 --- a/CoreStore/Saving and Processing/SaveResult.swift +++ b/CoreStore/Saving and Processing/SaveResult.swift @@ -91,9 +91,7 @@ public enum SaveResult { internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) { - self.init(NSError( - coreStoreErrorCode: errorCode, - userInfo: userInfo)) + self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo)) } } diff --git a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift index 47f3fa2..e50f363 100644 --- a/CoreStore/Saving and Processing/SynchronousDataTransaction.swift +++ b/CoreStore/Saving and Processing/SynchronousDataTransaction.swift @@ -42,8 +42,14 @@ public final class SynchronousDataTransaction: BaseDataTransaction { */ public func commit() { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue.") - CoreStore.assert(!self.isCommitted, "Attempted to commit a \(typeName(self)) more than once.") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to commit a \(typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to commit a \(typeName(self)) more than once." + ) self.isCommitted = true self.result = self.context.saveSynchronously() @@ -52,13 +58,19 @@ public final class SynchronousDataTransaction: BaseDataTransaction { /** Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once. - :param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. - :returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously + - parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`. + - returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously */ public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? { - CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue.") - CoreStore.assert(!self.isCommitted, "Attempted to begin a child transaction from an already committed \(typeName(self)).") + CoreStore.assert( + self.transactionQueue.isCurrentExecutionContext(), + "Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue." + ) + CoreStore.assert( + !self.isCommitted, + "Attempted to begin a child transaction from an already committed \(typeName(self))." + ) return SynchronousDataTransaction( mainContext: self.context, @@ -72,12 +84,15 @@ public final class SynchronousDataTransaction: BaseDataTransaction { /** Creates a new `NSManagedObject` with the specified entity type. - :param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration - :returns: a new `NSManagedObject` instance of the specified entity type. + - parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration + - returns: a new `NSManagedObject` instance of the specified entity type. */ public override func create(into: Into) -> T { - CoreStore.assert(!self.isCommitted, "Attempted to create an entity of type <\(T.self)> from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))." + ) return super.create(into) } @@ -85,12 +100,15 @@ public final class SynchronousDataTransaction: BaseDataTransaction { /** Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once. - :param: object the `NSManagedObject` type to be edited - :returns: an editable proxy for the specified `NSManagedObject`. + - parameter object: the `NSManagedObject` type to be edited + - returns: an editable proxy for the specified `NSManagedObject`. */ public override func edit(object: T?) -> T? { - CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self))." + ) return super.edit(object) } @@ -98,13 +116,16 @@ public final class SynchronousDataTransaction: BaseDataTransaction { /** Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once. - :param: into an `Into` clause specifying the entity type - :param: objectID the `NSManagedObjectID` for the object to be edited - :returns: an editable proxy for the specified `NSManagedObject`. + - parameter into: an `Into` clause specifying the entity type + - parameter objectID: the `NSManagedObjectID` for the object to be edited + - returns: an editable proxy for the specified `NSManagedObject`. */ public override func edit(into: Into, _ objectID: NSManagedObjectID) -> T? { - CoreStore.assert(!self.isCommitted, "Attempted to update an entity of type <\(T.self)> from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to update an entity of type \(typeName(T)) from an already committed \(typeName(self))." + ) return super.edit(into, objectID) } @@ -112,11 +133,14 @@ public final class SynchronousDataTransaction: BaseDataTransaction { /** Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once. - :param: object the `NSManagedObject` type to be deleted + - parameter object: the `NSManagedObject` type to be deleted */ public override func delete(object: NSManagedObject?) { - CoreStore.assert(!self.isCommitted, "Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self))." + ) super.delete(object) } @@ -124,13 +148,16 @@ public final class SynchronousDataTransaction: BaseDataTransaction { /** Deletes the specified `NSManagedObject`s. - :param: object1 the `NSManagedObject` to be deleted - :param: object2 another `NSManagedObject` to be deleted - :param: objects other `NSManagedObject`s to be deleted + - parameter object1: the `NSManagedObject` to be deleted + - parameter object2: another `NSManagedObject` to be deleted + - parameter objects: other `NSManagedObject`s to be deleted */ public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) { - CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to delete an entities from an already committed \(typeName(self))." + ) super.delete([object1, object2] + objects) } @@ -138,11 +165,14 @@ public final class SynchronousDataTransaction: BaseDataTransaction { /** Deletes the specified `NSManagedObject`s. - :param: objects the `NSManagedObject`s to be deleted + - parameter objects: the `NSManagedObject`s to be deleted */ public override func delete(objects: [NSManagedObject?]) { - CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to delete an entities from an already committed \(typeName(self))." + ) super.delete(objects) } @@ -152,7 +182,10 @@ public final class SynchronousDataTransaction: BaseDataTransaction { */ public override func rollback() { - CoreStore.assert(!self.isCommitted, "Attempted to rollback an already committed \(typeName(self)).") + CoreStore.assert( + !self.isCommitted, + "Attempted to rollback an already committed \(typeName(self))." + ) super.rollback() } @@ -165,9 +198,13 @@ public final class SynchronousDataTransaction: BaseDataTransaction { self.transactionQueue.sync { self.closure(transaction: self) + if !self.isCommitted && self.hasChanges { - CoreStore.log(.Warning, message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded.") + CoreStore.log( + .Warning, + message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded." + ) } } return self.result diff --git a/CoreStore/Setting Up/CoreStore+Setup.swift b/CoreStore/Setting Up/CoreStore+Setup.swift index 82db6dd..41a695d 100644 --- a/CoreStore/Setting Up/CoreStore+Setup.swift +++ b/CoreStore/Setting Up/CoreStore+Setup.swift @@ -35,8 +35,8 @@ public extension CoreStore { /** Adds an in-memory store to the `defaultStack`. - :param: configuration an optional configuration name from the model file. If not specified, defaults to nil. - :returns: a `PersistentStoreResult` indicating success or failure. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil. + - returns: a `PersistentStoreResult` indicating success or failure. */ public static func addInMemoryStore(configuration: String? = nil) -> PersistentStoreResult { @@ -46,11 +46,11 @@ public extension CoreStore { /** Adds to the `defaultStack` an SQLite store from the given SQLite file name. - :param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. - :param: configuration an optional configuration name from the model file. If not specified, defaults to nil. - :param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. - :param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false - :returns: a `PersistentStoreResult` indicating success or failure. + - parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil. + - parameter automigrating: Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. + - parameter resetStoreOnMigrationFailure: Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false + - returns: a `PersistentStoreResult` indicating success or failure. */ public static func addSQLiteStoreAndWait(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { @@ -65,11 +65,11 @@ public extension CoreStore { /** Adds to the `defaultStack` an SQLite store from the given SQLite file URL. - :param: fileURL the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a ".sqlite" file in the "Application Support" directory. - :param: configuration an optional configuration name from the model file. If not specified, defaults to nil. - :param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. - :param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. - :returns: a `PersistentStoreResult` indicating success or failure. + - parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a ".sqlite" file in the "Application Support" directory. + - parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil. + - parameter automigrating: Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. + - parameter resetStoreOnMigrationFailure: Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. + - returns: a `PersistentStoreResult` indicating success or failure. */ public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj index 50aef15..9e0c439 100644 --- a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj @@ -21,7 +21,6 @@ B54AAD591AF4D26E00848AE0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD571AF4D26E00848AE0 /* Main.storyboard */; }; B54AAD5B1AF4D26E00848AE0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */; }; B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; }; - B56007011B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B56007001B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel */; }; B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */; }; B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; }; B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; }; @@ -43,6 +42,7 @@ B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */; }; B5EE259B1B3EA4890000406B /* OrganismV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE259A1B3EA4890000406B /* OrganismV3.swift */; }; B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */; }; + B5F45A611B4AE5A700831F2F /* OrganismV2ToV3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5F45A601B4AE5A700831F2F /* OrganismV2ToV3.xcmappingmodel */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -108,7 +108,6 @@ B54AAD581AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = ""; }; B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = ""; }; - B56007001B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = OrganismV2ToV3.xcmappingmodel; sourceTree = ""; }; B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV2ToV3MigrationPolicy.swift; sourceTree = ""; }; B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = ""; }; B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = ""; }; @@ -130,6 +129,7 @@ B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationsDemoViewController.swift; sourceTree = ""; }; B5EE259A1B3EA4890000406B /* OrganismV3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV3.swift; sourceTree = ""; }; B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismProtocol.swift; sourceTree = ""; }; + B5F45A601B4AE5A700831F2F /* OrganismV2ToV3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = OrganismV2ToV3.xcmappingmodel; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -263,7 +263,7 @@ B5EE25861B36E2520000406B /* OrganismV2.swift */, B5EE25841B36E23C0000406B /* OrganismV1.swift */, B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */, - B56007001B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel */, + B5F45A601B4AE5A700831F2F /* OrganismV2ToV3.xcmappingmodel */, B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */, ); path = "Migrations Demo"; @@ -307,7 +307,8 @@ B54AAD411AF4D26E00848AE0 /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 0630; + LastSwiftUpdateCheck = 0700; + LastUpgradeCheck = 0700; ORGANIZATIONNAME = "John Rommel Estropia"; TargetAttributes = { B54AAD481AF4D26E00848AE0 = { @@ -385,12 +386,12 @@ B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */, B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */, B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */, - B56007011B3EC87400A9A8F9 /* OrganismV2ToV3.xcmappingmodel in Sources */, B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */, B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */, B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */, B54AAD521AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld in Sources */, B5EE259B1B3EA4890000406B /* OrganismV3.swift in Sources */, + B5F45A611B4AE5A700831F2F /* OrganismV2ToV3.xcmappingmodel in Sources */, B503FAE11AFDC71700F90881 /* Palette.swift in Sources */, B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */, B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */, @@ -460,6 +461,7 @@ COPY_PHASE_STRIP = NO; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; ENABLE_STRICT_OBJC_MSGSEND = YES; + ENABLE_TESTABILITY = YES; GCC_C_LANGUAGE_STANDARD = gnu99; GCC_DYNAMIC_NO_PIC = NO; GCC_NO_COMMON_BLOCKS = YES; @@ -475,7 +477,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.3; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -513,7 +515,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 8.3; + IPHONEOS_DEPLOYMENT_TARGET = 8.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; VALIDATE_PRODUCT = YES; @@ -524,14 +526,10 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos", - "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-bnajhooxbfnxepepdtkvkfplrqyq/Build/Products/Debug-iphoneos", - ); INFOPLIST_FILE = CoreStoreDemo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Debug; @@ -540,14 +538,10 @@ isa = XCBuildConfiguration; buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; - FRAMEWORK_SEARCH_PATHS = ( - "$(inherited)", - "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos", - "$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-bnajhooxbfnxepepdtkvkfplrqyq/Build/Products/Debug-iphoneos", - ); INFOPLIST_FILE = CoreStoreDemo/Info.plist; IPHONEOS_DEPLOYMENT_TARGET = 8.0; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; + PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo; PRODUCT_NAME = "$(TARGET_NAME)"; }; name = Release; diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout index ca9ddfb..a385032 100644 --- a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xccheckout @@ -7,7 +7,7 @@ IDESourceControlProjectIdentifier B6855E48-4B19-4321-B1C7-CB2706E12777 IDESourceControlProjectName - CoreStoreDemo + project IDESourceControlProjectOriginsDictionary 4B60F1BCB491FF717C56441AE7783C74F417BE48 @@ -16,7 +16,7 @@ github.com:JohnEstropia/GCDKit.git IDESourceControlProjectPath - CoreStoreDemo/CoreStoreDemo.xcodeproj + CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace IDESourceControlProjectRelativeInstallPathDictionary 4B60F1BCB491FF717C56441AE7783C74F417BE48 diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xcscmblueprint b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xcscmblueprint new file mode 100644 index 0000000..19c5427 --- /dev/null +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcshareddata/CoreStoreDemo.xcscmblueprint @@ -0,0 +1,30 @@ +{ + "DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48", + "DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : { + + }, + "DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : { + "8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0, + "4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0 + }, + "DVTSourceControlWorkspaceBlueprintIdentifierKey" : "B6855E48-4B19-4321-B1C7-CB2706E12777", + "DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : { + "8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStoreLibraries\/GCDKit", + "4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore" + }, + "DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStoreDemo", + "DVTSourceControlWorkspaceBlueprintVersion" : 203, + "DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStoreDemo\/CoreStoreDemo.xcodeproj", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [ + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/CoreStore.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48" + }, + { + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/GCDKit.git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git", + "DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97" + } + ] +} \ No newline at end of file diff --git a/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcschemes/CoreStoreDemo.xcscheme b/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcschemes/CoreStoreDemo.xcscheme index 4ec6eab..c498118 100644 --- a/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcschemes/CoreStoreDemo.xcscheme +++ b/CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata/johnestropia.xcuserdatad/xcschemes/CoreStoreDemo.xcscheme @@ -1,6 +1,6 @@ + + diff --git a/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard b/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard index 4c778f7..21d95f8 100644 --- a/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard +++ b/CoreStoreDemo/CoreStoreDemo/Base.lproj/Main.storyboard @@ -1,11 +1,41 @@ - + - + + + + + AppleSDGothicNeo-Regular + + + HelveticaNeue-Bold + HelveticaNeue-Bold + HelveticaNeue + HelveticaNeue + + + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + HelveticaNeue-Light + + @@ -52,17 +82,21 @@ + +