Merge branch 'develop'

Conflicts:
	README.md
This commit is contained in:
John Rommel Estropia
2015-07-18 23:53:37 +09:00
84 changed files with 4895 additions and 2007 deletions

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = "CoreStore" s.name = "CoreStore"
s.version = "0.2.1" s.version = "1.0.0"
s.license = "MIT" s.license = "MIT"
s.summary = "Simple, elegant, and smart Core Data programming with Swift" s.summary = "Simple, elegant, and smart Core Data programming with Swift"
s.homepage = "https://github.com/JohnEstropia/CoreStore" s.homepage = "https://github.com/JohnEstropia/CoreStore"

View File

@@ -12,7 +12,12 @@
2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A54C19C5C872005002A5 /* CoreData.framework */; }; 2F03A54D19C5C872005002A5 /* CoreData.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A54C19C5C872005002A5 /* CoreData.framework */; };
2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; }; 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F291E2619C6D3CF007AF63F /* CoreStore.swift */; };
B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B504D0D51B02362500B2BBB1 /* CoreStore+Setup.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 */; };
B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D31B22FFAD0075EE4A /* DataStack+Migration.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 */; }; B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; };
B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; };
B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D372851A39CDDB00F583D9 /* TestEntity1.swift */; }; B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D372851A39CDDB00F583D9 /* TestEntity1.swift */; };
@@ -43,10 +48,10 @@
B5E84F151AFF847B0064E85B /* CoreStore+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F071AFF847B0064E85B /* CoreStore+Querying.swift */; }; 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 */; }; 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 */; }; B5E84F211AFF84860064E85B /* CoreStore+Observing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1B1AFF84860064E85B /* CoreStore+Observing.swift */; };
B5E84F221AFF84860064E85B /* ManagedObjectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1C1AFF84860064E85B /* ManagedObjectController.swift */; }; B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1C1AFF84860064E85B /* ObjectMonitor.swift */; };
B5E84F231AFF84860064E85B /* ManagedObjectListController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1D1AFF84860064E85B /* ManagedObjectListController.swift */; }; B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1D1AFF84860064E85B /* ListMonitor.swift */; };
B5E84F241AFF84860064E85B /* ManagedObjectListObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1E1AFF84860064E85B /* ManagedObjectListObserver.swift */; }; B5E84F241AFF84860064E85B /* ListObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1E1AFF84860064E85B /* ListObserver.swift */; };
B5E84F251AFF84860064E85B /* ManagedObjectObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F1F1AFF84860064E85B /* ManagedObjectObserver.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 */; }; B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F271AFF84920064E85B /* NSManagedObject+Convenience.swift */; };
B5E84F2E1AFF849C0064E85B /* AssociatedObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */; }; B5E84F2E1AFF849C0064E85B /* AssociatedObjects.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */; };
B5E84F2F1AFF849C0064E85B /* NotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */; }; B5E84F2F1AFF849C0064E85B /* NotificationObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */; };
@@ -57,6 +62,9 @@
B5E84F381AFF85470064E85B /* NSManagedObject+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F341AFF85470064E85B /* NSManagedObject+Transaction.swift */; }; B5E84F381AFF85470064E85B /* NSManagedObject+Transaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F341AFF85470064E85B /* NSManagedObject+Transaction.swift */; };
B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */; }; B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */; };
B5E84F411AFF8CCD0064E85B /* ClauseTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F401AFF8CCD0064E85B /* ClauseTypes.swift */; }; B5E84F411AFF8CCD0064E85B /* ClauseTypes.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84F401AFF8CCD0064E85B /* ClauseTypes.swift */; };
B5FAD6A91B50A4B400714891 /* NSProgress+Convenience.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FAD6A81B50A4B300714891 /* NSProgress+Convenience.swift */; };
B5FAD6AC1B51285300714891 /* MigrationManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FAD6AB1B51285300714891 /* MigrationManager.swift */; };
B5FAD6AE1B518DCB00714891 /* CoreStore+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5FAD6AD1B518DCB00714891 /* CoreStore+Migration.swift */; };
/* End PBXBuildFile section */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -100,7 +108,12 @@
2F03A54C19C5C872005002A5 /* CoreData.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreData.framework; path = System/Library/Frameworks/CoreData.framework; sourceTree = SDKROOT; }; 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 = "<group>"; }; 2F291E2619C6D3CF007AF63F /* CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; lineEnding = 0; path = CoreStore.swift; sourceTree = "<group>"; };
B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = "<group>"; }; B504D0D51B02362500B2BBB1 /* CoreStore+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Setup.swift"; sourceTree = "<group>"; };
B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectModel+Setup.swift"; sourceTree = "<group>"; };
B56007101B3F6BD500A9A8F9 /* Into.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Into.swift; sourceTree = "<group>"; };
B56007131B3F6C2800A9A8F9 /* SectionBy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SectionBy.swift; sourceTree = "<group>"; };
B56007151B4018AB00A9A8F9 /* MigrationChain.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationChain.swift; sourceTree = "<group>"; };
B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Migration.swift"; sourceTree = "<group>"; }; B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Migration.swift"; sourceTree = "<group>"; };
B56965231B356B820075EE4A /* MigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationResult.swift; sourceTree = "<group>"; };
B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+CoreStore.swift"; sourceTree = "<group>"; }; B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSError+CoreStore.swift"; sourceTree = "<group>"; };
B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; }; B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = "<group>"; };
B5D372851A39CDDB00F583D9 /* TestEntity1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity1.swift; sourceTree = "<group>"; }; B5D372851A39CDDB00F583D9 /* TestEntity1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity1.swift; sourceTree = "<group>"; };
@@ -134,10 +147,10 @@
B5E84F071AFF847B0064E85B /* CoreStore+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Querying.swift"; sourceTree = "<group>"; }; B5E84F071AFF847B0064E85B /* CoreStore+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Querying.swift"; sourceTree = "<group>"; };
B5E84F1A1AFF84860064E85B /* DataStack+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Observing.swift"; sourceTree = "<group>"; }; B5E84F1A1AFF84860064E85B /* DataStack+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DataStack+Observing.swift"; sourceTree = "<group>"; };
B5E84F1B1AFF84860064E85B /* CoreStore+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Observing.swift"; sourceTree = "<group>"; }; B5E84F1B1AFF84860064E85B /* CoreStore+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Observing.swift"; sourceTree = "<group>"; };
B5E84F1C1AFF84860064E85B /* ManagedObjectController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectController.swift; sourceTree = "<group>"; }; B5E84F1C1AFF84860064E85B /* ObjectMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectMonitor.swift; sourceTree = "<group>"; };
B5E84F1D1AFF84860064E85B /* ManagedObjectListController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListController.swift; sourceTree = "<group>"; }; B5E84F1D1AFF84860064E85B /* ListMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListMonitor.swift; sourceTree = "<group>"; };
B5E84F1E1AFF84860064E85B /* ManagedObjectListObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectListObserver.swift; sourceTree = "<group>"; }; B5E84F1E1AFF84860064E85B /* ListObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListObserver.swift; sourceTree = "<group>"; };
B5E84F1F1AFF84860064E85B /* ManagedObjectObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ManagedObjectObserver.swift; sourceTree = "<group>"; }; B5E84F1F1AFF84860064E85B /* ObjectObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserver.swift; sourceTree = "<group>"; };
B5E84F271AFF84920064E85B /* NSManagedObject+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Convenience.swift"; sourceTree = "<group>"; }; B5E84F271AFF84920064E85B /* NSManagedObject+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Convenience.swift"; sourceTree = "<group>"; };
B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociatedObjects.swift; sourceTree = "<group>"; }; B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AssociatedObjects.swift; sourceTree = "<group>"; };
B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationObserver.swift; sourceTree = "<group>"; }; B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NotificationObserver.swift; sourceTree = "<group>"; };
@@ -148,6 +161,9 @@
B5E84F341AFF85470064E85B /* NSManagedObject+Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Transaction.swift"; sourceTree = "<group>"; }; B5E84F341AFF85470064E85B /* NSManagedObject+Transaction.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Transaction.swift"; sourceTree = "<group>"; };
B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Querying.swift"; sourceTree = "<group>"; }; B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Querying.swift"; sourceTree = "<group>"; };
B5E84F401AFF8CCD0064E85B /* ClauseTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClauseTypes.swift; sourceTree = "<group>"; }; B5E84F401AFF8CCD0064E85B /* ClauseTypes.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ClauseTypes.swift; sourceTree = "<group>"; };
B5FAD6A81B50A4B300714891 /* NSProgress+Convenience.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSProgress+Convenience.swift"; sourceTree = "<group>"; };
B5FAD6AB1B51285300714891 /* MigrationManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationManager.swift; sourceTree = "<group>"; };
B5FAD6AD1B518DCB00714891 /* CoreStore+Migration.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CoreStore+Migration.swift"; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -256,6 +272,9 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */, B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */,
B5FAD6AD1B518DCB00714891 /* CoreStore+Migration.swift */,
B56007151B4018AB00A9A8F9 /* MigrationChain.swift */,
B56965231B356B820075EE4A /* MigrationResult.swift */,
); );
path = Migrating; path = Migrating;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -308,6 +327,7 @@
B5E84EE91AFF846E0064E85B /* Saving and Processing */ = { B5E84EE91AFF846E0064E85B /* Saving and Processing */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B56007101B3F6BD500A9A8F9 /* Into.swift */,
B5E84EEB1AFF846E0064E85B /* BaseDataTransaction.swift */, B5E84EEB1AFF846E0064E85B /* BaseDataTransaction.swift */,
B5E84EEA1AFF846E0064E85B /* AsynchronousDataTransaction.swift */, B5E84EEA1AFF846E0064E85B /* AsynchronousDataTransaction.swift */,
B5E84EEC1AFF846E0064E85B /* DataStack+Transaction.swift */, B5E84EEC1AFF846E0064E85B /* DataStack+Transaction.swift */,
@@ -355,12 +375,13 @@
B5E84F191AFF84860064E85B /* Observing */ = { B5E84F191AFF84860064E85B /* Observing */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B56007131B3F6C2800A9A8F9 /* SectionBy.swift */,
B5E84F1A1AFF84860064E85B /* DataStack+Observing.swift */, B5E84F1A1AFF84860064E85B /* DataStack+Observing.swift */,
B5E84F1B1AFF84860064E85B /* CoreStore+Observing.swift */, B5E84F1B1AFF84860064E85B /* CoreStore+Observing.swift */,
B5E84F1C1AFF84860064E85B /* ManagedObjectController.swift */, B5E84F1C1AFF84860064E85B /* ObjectMonitor.swift */,
B5E84F1F1AFF84860064E85B /* ManagedObjectObserver.swift */, B5E84F1F1AFF84860064E85B /* ObjectObserver.swift */,
B5E84F1D1AFF84860064E85B /* ManagedObjectListController.swift */, B5E84F1D1AFF84860064E85B /* ListMonitor.swift */,
B5E84F1E1AFF84860064E85B /* ManagedObjectListObserver.swift */, B5E84F1E1AFF84860064E85B /* ListObserver.swift */,
); );
path = Observing; path = Observing;
sourceTree = "<group>"; sourceTree = "<group>";
@@ -369,6 +390,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B5E84F271AFF84920064E85B /* NSManagedObject+Convenience.swift */, B5E84F271AFF84920064E85B /* NSManagedObject+Convenience.swift */,
B5FAD6A81B50A4B300714891 /* NSProgress+Convenience.swift */,
); );
path = "Convenience Helpers"; path = "Convenience Helpers";
sourceTree = "<group>"; sourceTree = "<group>";
@@ -378,11 +400,13 @@
children = ( children = (
B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */, B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */,
B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */, B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */,
B5FAD6AB1B51285300714891 /* MigrationManager.swift */,
B5E84F341AFF85470064E85B /* NSManagedObject+Transaction.swift */, B5E84F341AFF85470064E85B /* NSManagedObject+Transaction.swift */,
B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */, B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */,
B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */, B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */,
B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */, B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */,
B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */, B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */,
B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */,
B5E84F2D1AFF849C0064E85B /* WeakObject.swift */, B5E84F2D1AFF849C0064E85B /* WeakObject.swift */,
); );
path = Internal; path = Internal;
@@ -445,7 +469,8 @@
2F03A52719C5C6DA005002A5 /* Project object */ = { 2F03A52719C5C6DA005002A5 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0600; LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "John Rommel Estropia"; ORGANIZATIONNAME = "John Rommel Estropia";
TargetAttributes = { TargetAttributes = {
2F03A52F19C5C6DA005002A5 = { 2F03A52F19C5C6DA005002A5 = {
@@ -520,34 +545,42 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
B5E84F221AFF84860064E85B /* ManagedObjectController.swift in Sources */, B5E84F221AFF84860064E85B /* ObjectMonitor.swift in Sources */,
B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */, B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */,
B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */, B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */,
B5E84F131AFF847B0064E85B /* Where.swift in Sources */, B5E84F131AFF847B0064E85B /* Where.swift in Sources */,
B5E84F141AFF847B0064E85B /* DataStack+Querying.swift in Sources */, B5E84F141AFF847B0064E85B /* DataStack+Querying.swift in Sources */,
B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */,
B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */, B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */,
B56007161B4018AB00A9A8F9 /* MigrationChain.swift in Sources */,
B5E84F0E1AFF847B0064E85B /* Tweak.swift in Sources */, B5E84F0E1AFF847B0064E85B /* Tweak.swift in Sources */,
B5E84F121AFF847B0064E85B /* OrderBy.swift in Sources */, B5E84F121AFF847B0064E85B /* OrderBy.swift in Sources */,
B5E84F361AFF85470064E85B /* NSManagedObjectContext+Setup.swift in Sources */, B5E84F361AFF85470064E85B /* NSManagedObjectContext+Setup.swift in Sources */,
B5FAD6AE1B518DCB00714891 /* CoreStore+Migration.swift in Sources */,
B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */, B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */,
B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */,
B5E84F111AFF847B0064E85B /* Select.swift in Sources */, B5E84F111AFF847B0064E85B /* Select.swift in Sources */,
B5E84EE11AFF84500064E85B /* PersistentStoreResult.swift in Sources */, B5E84EE11AFF84500064E85B /* PersistentStoreResult.swift in Sources */,
B5E84F251AFF84860064E85B /* ManagedObjectObserver.swift in Sources */, B5E84F251AFF84860064E85B /* ObjectObserver.swift in Sources */,
B5E84F2F1AFF849C0064E85B /* NotificationObserver.swift in Sources */, B5E84F2F1AFF849C0064E85B /* NotificationObserver.swift in Sources */,
B5E84F381AFF85470064E85B /* NSManagedObject+Transaction.swift in Sources */, B5E84F381AFF85470064E85B /* NSManagedObject+Transaction.swift in Sources */,
B56965241B356B820075EE4A /* MigrationResult.swift in Sources */,
2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */, 2F291E2719C6D3CF007AF63F /* CoreStore.swift in Sources */,
B5E84F411AFF8CCD0064E85B /* ClauseTypes.swift in Sources */, B5E84F411AFF8CCD0064E85B /* ClauseTypes.swift in Sources */,
B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */, B5E84F0D1AFF847B0064E85B /* BaseDataTransaction+Querying.swift in Sources */,
B5FAD6AC1B51285300714891 /* MigrationManager.swift in Sources */,
B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */, B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */,
B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */, B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */,
B5E84F231AFF84860064E85B /* ManagedObjectListController.swift in Sources */, B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */,
B5E84EF71AFF846E0064E85B /* DetachedDataTransaction.swift in Sources */, B5E84EF71AFF846E0064E85B /* DetachedDataTransaction.swift in Sources */,
B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */, B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */,
B5E84EF51AFF846E0064E85B /* BaseDataTransaction.swift in Sources */, B5E84EF51AFF846E0064E85B /* BaseDataTransaction.swift in Sources */,
B5E84EFB1AFF846E0064E85B /* SaveResult.swift in Sources */, B5E84EFB1AFF846E0064E85B /* SaveResult.swift in Sources */,
B5E84F0F1AFF847B0064E85B /* From.swift in Sources */, B5E84F0F1AFF847B0064E85B /* From.swift in Sources */,
B5FAD6A91B50A4B400714891 /* NSProgress+Convenience.swift in Sources */,
B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */, B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */,
B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */, B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */,
B51BE06A1B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift in Sources */,
B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */, B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */,
B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */, B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */,
B5E84F311AFF849C0064E85B /* WeakObject.swift in Sources */, B5E84F311AFF849C0064E85B /* WeakObject.swift in Sources */,
@@ -559,7 +592,7 @@
B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */, B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */,
B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */, B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */,
B5E84F151AFF847B0064E85B /* CoreStore+Querying.swift in Sources */, B5E84F151AFF847B0064E85B /* CoreStore+Querying.swift in Sources */,
B5E84F241AFF84860064E85B /* ManagedObjectListObserver.swift in Sources */, B5E84F241AFF84860064E85B /* ListObserver.swift in Sources */,
B5E84F2E1AFF849C0064E85B /* AssociatedObjects.swift in Sources */, B5E84F2E1AFF849C0064E85B /* AssociatedObjects.swift in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
@@ -612,6 +645,7 @@
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
CURRENT_PROJECT_VERSION = 1; CURRENT_PROJECT_VERSION = 1;
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_OPTIMIZATION_LEVEL = 0; GCC_OPTIMIZATION_LEVEL = 0;
@@ -691,6 +725,7 @@
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
OTHER_SWIFT_FLAGS = "-D DEBUG"; OTHER_SWIFT_FLAGS = "-D DEBUG";
PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
@@ -711,6 +746,7 @@
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
SKIP_INSTALL = YES; SKIP_INSTALL = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
@@ -732,6 +768,7 @@
); );
INFOPLIST_FILE = CoreStoreTests/Info.plist; INFOPLIST_FILE = CoreStoreTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
}; };
name = Debug; name = Debug;
@@ -746,6 +783,7 @@
); );
INFOPLIST_FILE = CoreStoreTests/Info.plist; INFOPLIST_FILE = CoreStoreTests/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)";
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
}; };
name = Release; name = Release;

View File

@@ -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"
}
]
}

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "0630" LastUpgradeVersion = "0700"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -62,6 +62,8 @@
ReferencedContainer = "container:CoreStore.xcodeproj"> ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference> </BuildableReference>
</MacroExpansion> </MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction> </TestAction>
<LaunchAction <LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
@@ -71,6 +73,7 @@
buildConfiguration = "Debug" buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO" ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES" debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES"> allowLocationSimulation = "YES">
<MacroExpansion> <MacroExpansion>
<BuildableReference <BuildableReference

View File

@@ -31,6 +31,12 @@ import CoreData
public extension NSManagedObject { public extension NSManagedObject {
/**
Provides a convenience wrapper for accessing `primitiveValueForKey(...)` with proper calls to `willAccessValueForKey(...)` and `didAccessValueForKey(...)`. This is useful when implementing accessor methods for transient attributes.
- parameter KVCKey: the KVC key
- returns: the primitive value for the KVC key
*/
public func accessValueForKVCKey(KVCKey: KeyPath) -> AnyObject? { public func accessValueForKVCKey(KVCKey: KeyPath) -> AnyObject? {
self.willAccessValueForKey(KVCKey) self.willAccessValueForKey(KVCKey)
@@ -40,6 +46,12 @@ public extension NSManagedObject {
return primitiveValue return primitiveValue
} }
/**
Provides a convenience wrapper for setting `setPrimitiveValue(...)` with proper calls to `willChangeValueForKey(...)` and `didChangeValueForKey(...)`. This is useful when implementing mutator methods for transient attributes.
- parameter value: the value to set the KVC key with
- parameter KVCKey: the KVC key
*/
public func setValue(value: AnyObject?, forKVCKey KVCKey: KeyPath) { public func setValue(value: AnyObject?, forKVCKey KVCKey: KeyPath) {
self.willChangeValueForKey(KVCKey) self.willChangeValueForKey(KVCKey)

View File

@@ -0,0 +1,132 @@
//
// NSProgress+Convenience.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 GCDKit
// MARK: - NSProgress
public extension NSProgress {
// MARK: Public
/**
Sets a closure that the `NSProgress` calls whenever its `fractionCompleted` changes. You can use this instead of setting up KVO.
- parameter closure: the closure to execute on progress change
*/
public func setProgressHandler(closure: ((progress: NSProgress) -> Void)?) {
self.progressObserver.progressHandler = closure
}
// MARK: Private
private struct PropertyKeys {
static var progressObserver: Void?
}
private var progressObserver: ProgressObserver {
get {
let object: ProgressObserver? = getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
if let observer = object {
return observer
}
let observer = ProgressObserver(self)
setAssociatedRetainedObject(
observer,
forKey: &PropertyKeys.progressObserver,
inObject: self
)
return observer
}
}
}
@objc private final class ProgressObserver: NSObject {
private unowned let progress: NSProgress
private var progressHandler: ((progress: NSProgress) -> Void)? {
didSet {
let progressHandler = self.progressHandler
if (progressHandler == nil) == (oldValue == nil) {
return
}
if let _ = progressHandler {
self.progress.addObserver(
self,
forKeyPath: "fractionCompleted",
options: [.Initial, .New],
context: nil
)
}
else {
self.progress.removeObserver(self, forKeyPath: "fractionCompleted")
}
}
}
private init(_ progress: NSProgress) {
self.progress = progress
super.init()
}
deinit {
if let _ = self.progressHandler {
self.progressHandler = nil
self.progress.removeObserver(self, forKeyPath: "fractionCompleted")
}
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard let progress = object as? NSProgress where progress == self.progress && keyPath == "fractionCompleted" else {
return
}
GCDQueue.Main.async { [weak self] () -> Void in
self?.progressHandler?(progress: progress)
}
}
}

View File

@@ -36,13 +36,16 @@ public extension BaseDataTransaction {
/** /**
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. 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 - parameter 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. - 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 - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/ */
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? { public func fetchOne<T: NSManagedObject>(from: From<T>, _ 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) 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. 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 - parameter 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. - 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 - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/ */
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? { public func fetchOne<T: NSManagedObject>(from: From<T>, _ 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) 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. 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 - parameter 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. - 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 - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/ */
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? { public func fetchAll<T: NSManagedObject>(from: From<T>, _ 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) return self.context.fetchAll(from, fetchClauses)
} }
@@ -78,41 +87,50 @@ public extension BaseDataTransaction {
/** /**
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. 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 - parameter 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. - 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 - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/ */
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? { public func fetchAll<T: NSManagedObject>(from: From<T>, _ 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) return self.context.fetchAll(from, fetchClauses)
} }
/** /**
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 - parameter 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. - 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 - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? { public func fetchCount<T: NSManagedObject>(from: From<T>, _ 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) return self.context.fetchCount(from, fetchClauses)
} }
/** /**
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 - parameter 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. - 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 - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? { public func fetchCount<T: NSManagedObject>(from: From<T>, _ 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) 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. 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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/ */
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ 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) return self.context.fetchObjectID(from, fetchClauses)
} }
@@ -134,137 +155,164 @@ public extension BaseDataTransaction {
/** /**
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. 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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/ */
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ 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) return self.context.fetchObjectID(from, fetchClauses)
} }
/** /**
Fetches the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. 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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ 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) return self.context.fetchObjectIDs(from, fetchClauses)
} }
/** /**
Fetches the `NSManagedObjectID` for all `NSManagedObject`'s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. 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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ 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) return self.context.fetchObjectIDs(from, fetchClauses)
} }
/** /**
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 - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? { public func deleteAll<T: NSManagedObject>(from: From<T>, _ 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) return self.context.deleteAll(from, deleteClauses)
} }
/** /**
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 - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? { public func deleteAll<T: NSManagedObject>(from: From<T>, _ 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) return self.context.deleteAll(from, deleteClauses)
} }
/** /**
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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? { public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ 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) return self.context.queryValue(from, selectClause, queryClauses)
} }
/** /**
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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? { public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ 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) return self.context.queryValue(from, selectClause, queryClauses)
} }
/** /**
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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ 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) return self.context.queryAttributes(from, selectClause, queryClauses)
} }
/** /**
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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ 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) return self.context.queryAttributes(from, selectClause, queryClauses)
} }

View File

@@ -38,72 +38,110 @@ public struct From<T: NSManagedObject> {
public init(){ public init(){
self.entityClass = T.self
self.findPersistentStores = { _ in nil } self.findPersistentStores = { _ in nil }
} }
public init(_ entity: T.Type) { public init(_ entity: T.Type) {
self.entityClass = entity
self.findPersistentStores = { _ in nil }
}
public init(_ entityClass: AnyClass) {
self.entityClass = entityClass
self.findPersistentStores = { _ in nil } self.findPersistentStores = { _ in nil }
} }
public init(_ configurations: String?...) { public init(_ configurations: String?...) {
self.init(configurations: configurations) self.init(entityClass: T.self, configurations: configurations)
} }
public init(_ configurations: [String?]) { public init(_ configurations: [String?]) {
self.init(configurations: configurations) self.init(entityClass: T.self, configurations: configurations)
} }
public init(_ entity: T.Type, _ configurations: String?...) { public init(_ entity: T.Type, _ configurations: String?...) {
self.init(configurations: configurations) self.init(entityClass: entity, configurations: configurations)
} }
public init(_ entity: T.Type, _ configurations: [String?]) { 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...) { public init(_ storeURLs: NSURL...) {
self.init(storeURLs: storeURLs) self.init(entityClass: T.self, storeURLs: storeURLs)
} }
public init(_ storeURLs: [NSURL]) { public init(_ storeURLs: [NSURL]) {
self.init(storeURLs: storeURLs) self.init(entityClass: T.self, storeURLs: storeURLs)
} }
public init(_ entity: T.Type, _ storeURLs: NSURL...) { public init(_ entity: T.Type, _ storeURLs: NSURL...) {
self.init(storeURLs: storeURLs) self.init(entityClass: entity, storeURLs: storeURLs)
} }
public init(_ entity: T.Type, _ storeURLs: [NSURL]) { 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...) { public init(_ persistentStores: NSPersistentStore...) {
self.init(persistentStores: persistentStores) self.init(entityClass: T.self, persistentStores: persistentStores)
} }
public init(_ persistentStores: [NSPersistentStore]) { public init(_ persistentStores: [NSPersistentStore]) {
self.init(persistentStores: persistentStores) self.init(entityClass: T.self, persistentStores: persistentStores)
} }
public init(_ entity: T.Type, _ persistentStores: NSPersistentStore...) { public init(_ entity: T.Type, _ persistentStores: NSPersistentStore...) {
self.init(persistentStores: persistentStores) self.init(entityClass: entity, persistentStores: persistentStores)
} }
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) { 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<T: NSManagedObject> {
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) { 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) fetchRequest.affectedStores = self.findPersistentStores(context: context)
} }
// MARK: Private // MARK: Private
private let entityClass: AnyClass
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]? private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
private init(configurations: [String?]) { private init(entityClass: AnyClass, configurations: [String?]) {
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName }) let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter { return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return configurationsSet.contains($0.configurationName) return configurationsSet.contains($0.configurationName)
} }
} }
} }
private init(storeURLs: [NSURL]) { private init(entityClass: AnyClass, storeURLs: [NSURL]) {
let storeURLsSet = Set(storeURLs) let storeURLsSet = Set(storeURLs)
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in 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!) return $0.URL != nil && storeURLsSet.contains($0.URL!)
} }
} }
} }
private init(persistentStores: [NSPersistentStore]) { private init(entityClass: AnyClass, persistentStores: [NSPersistentStore]) {
let persistentStores = Set(persistentStores) let persistentStores = Set(persistentStores)
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter { return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return persistentStores.contains($0) return persistentStores.contains($0)
} }

View File

@@ -39,7 +39,7 @@ public struct GroupBy: QueryClause {
/** /**
Initializes a `GroupBy` clause with a list of key path strings 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]) { public init(_ keyPaths: [KeyPath]) {
@@ -57,8 +57,8 @@ public struct GroupBy: QueryClause {
/** /**
Initializes a `GroupBy` clause with a list of key path strings Initializes a `GroupBy` clause with a list of key path strings
:param: keyPath a key path string to group results with - parameter keyPath: a key path string to group results with
:param: keyPaths a series of key path strings to group results with - parameter keyPaths: a series of key path strings to group results with
*/ */
public init(_ keyPath: KeyPath, _ keyPaths: KeyPath...) { public init(_ keyPath: KeyPath, _ keyPaths: KeyPath...) {
@@ -74,7 +74,10 @@ public struct GroupBy: QueryClause {
if fetchRequest.propertiesToGroupBy != nil { 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 fetchRequest.propertiesToGroupBy = self.keyPaths

View File

@@ -73,7 +73,7 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
/** /**
Initializes a `OrderBy` clause with a list of sort descriptors 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]) { public init(_ sortDescriptors: [NSSortDescriptor]) {
@@ -91,7 +91,7 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
/** /**
Initializes a `OrderBy` clause with a single sort descriptor Initializes a `OrderBy` clause with a single sort descriptor
:param: sortDescriptor a `NSSortDescriptor` - parameter sortDescriptor: a `NSSortDescriptor`
*/ */
public init(_ sortDescriptor: NSSortDescriptor) { public init(_ sortDescriptor: NSSortDescriptor) {
@@ -99,16 +99,16 @@ 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 - parameter sortKey: a series of `SortKey`s
*/ */
public init(_ sortKey: [SortKey]) { public init(_ sortKey: [SortKey]) {
self.init( self.init(
sortKey.map { SortKey -> NSSortDescriptor in sortKey.map { sortKey -> NSSortDescriptor in
switch SortKey { switch sortKey {
case .Ascending(let keyPath): case .Ascending(let keyPath):
return NSSortDescriptor(key: keyPath, ascending: true) return NSSortDescriptor(key: keyPath, ascending: true)
@@ -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` - parameter sortKey: a single `SortKey`
:param: sortKeys a series of `SortKey`'s - parameter sortKeys: a series of `SortKey`s
*/ */
public init(_ sortKey: SortKey, _ sortKeys: SortKey...) { public init(_ sortKey: SortKey, _ sortKeys: SortKey...) {
@@ -140,7 +140,10 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
if fetchRequest.sortDescriptors != nil { 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 fetchRequest.sortDescriptors = self.sortDescriptors

View File

@@ -83,8 +83,8 @@ public enum SelectTerm: StringLiteralConvertible {
Where("employeeID", isEqualTo: 1111) Where("employeeID", isEqualTo: 1111)
) )
:param: keyPath the attribute name - parameter keyPath: the attribute name
:returns: a `SelectTerm` to a `Select` clause for querying an entity attribute - returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
*/ */
public static func Attribute(keyPath: KeyPath) -> SelectTerm { public static func Attribute(keyPath: KeyPath) -> SelectTerm {
@@ -99,9 +99,9 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Average("age")) Select<Int>(.Average("age"))
) )
:param: keyPath the attribute name - parameter 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(<attributeName>)" is used - 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(<attributeName>)" is used
:returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute - 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 { public static func Average(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
@@ -121,9 +121,9 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Count("employeeID")) Select<Int>(.Count("employeeID"))
) )
:param: keyPath the attribute name - parameter 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(<attributeName>)" is used - 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(<attributeName>)" is used
:returns: a `SelectTerm` to a `Select` clause for a count query - returns: a `SelectTerm` to a `Select` clause for a count query
*/ */
public static func Count(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm { public static func Count(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
@@ -143,9 +143,9 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Maximum("age")) Select<Int>(.Maximum("age"))
) )
:param: keyPath the attribute name - parameter 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(<attributeName>)" is used - 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(<attributeName>)" is used
:returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute - 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 { public static func Maximum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
@@ -165,9 +165,9 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Minimum("age")) Select<Int>(.Minimum("age"))
) )
:param: keyPath the attribute name - parameter 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(<attributeName>)" is used - 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(<attributeName>)" is used
:returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute - 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 { public static func Minimum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
@@ -187,9 +187,9 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Sum("age")) Select<Int>(.Sum("age"))
) )
:param: keyPath the attribute name - parameter 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(<attributeName>)" is used - 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(<attributeName>)" is used
:returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute - 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 { 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: - for `queryAttributes(...)` methods:
- `NSDictionary` - `NSDictionary`
:param: sortDescriptors a series of `NSSortDescriptor`'s - parameter sortDescriptors: a series of `NSSortDescriptor`s
*/ */
public struct Select<T: SelectResultType> { public struct Select<T: SelectResultType> {
@@ -279,10 +279,10 @@ public struct Select<T: SelectResultType> {
public typealias ReturnType = T 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` - parameter selectTerm: a `SelectTerm`
:param: selectTerms a series of `SelectTerm`'s - parameter selectTerms: a series of `SelectTerm`s
*/ */
public init(_ selectTerm: SelectTerm, _ selectTerms: SelectTerm...) { public init(_ selectTerm: SelectTerm, _ selectTerms: SelectTerm...) {
@@ -296,7 +296,10 @@ public struct Select<T: SelectResultType> {
if fetchRequest.propertiesToFetch != nil { 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 fetchRequest.includesPendingChanges = false
@@ -312,17 +315,20 @@ public struct Select<T: SelectResultType> {
switch term { switch term {
case ._Attribute(let keyPath): case ._Attribute(let keyPath):
if let propertyDescription = propertiesByName[keyPath] as? NSPropertyDescription { if let propertyDescription = propertiesByName[keyPath] {
propertiesToFetch.append(propertyDescription) propertiesToFetch.append(propertyDescription)
} }
else { 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): 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() let expressionDescription = NSExpressionDescription()
expressionDescription.name = alias expressionDescription.name = alias
@@ -343,7 +349,10 @@ public struct Select<T: SelectResultType> {
} }
else { 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 // MARK: - NSDecimalNumber: SelectValueResultType
extension NSDecimalNumber: SelectValueResultType { extension NSDecimalNumber {
public override class var attributeType: NSAttributeType { public override class var attributeType: NSAttributeType {

View File

@@ -49,7 +49,7 @@ public struct Tweak: FetchClause, QueryClause, DeleteClause {
/** /**
Initializes a `Tweak` clause with a closure where the `NSFetchRequest` may be configured. 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) { public init(_ customization: (fetchRequest: NSFetchRequest) -> Void) {

View File

@@ -54,7 +54,7 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
/** /**
Initializes a `Where` clause with an `NSPredicate` 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) { 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 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) { 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 Initializes a `Where` clause with a predicate using the specified string format and arguments
:param: format the format string for the predicate - parameter format: the format string for the predicate
:param: args the arguments for `format` - parameter args: the arguments for `format`
*/ */
public init(_ format: String, _ args: NSObject...) { 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 Initializes a `Where` clause with a predicate using the specified string format and arguments
:param: format the format string for the predicate - parameter format: the format string for the predicate
:param: argumentArray the arguments for `format` - parameter argumentArray: the arguments for `format`
*/ */
public init(_ format: String, argumentArray: [NSObject]?) { 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 Initializes a `Where` clause with a predicate using the specified string format and arguments
:param: format the format string for the predicate - parameter format: the format string for the predicate
:param: argumentArray the arguments for `format` - parameter argumentArray: the arguments for `format`
*/ */
public init(_ keyPath: KeyPath, isEqualTo value: NSObject?) { public init(_ keyPath: KeyPath, isEqualTo value: NSObject?) {
@@ -123,7 +123,10 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
if fetchRequest.predicate != nil { 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 fetchRequest.predicate = self.predicate

View File

@@ -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. 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 - parameter 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. - 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 - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/ */
public static func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? { public static func fetchOne<T: NSManagedObject>(from: From<T>, _ 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. 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 - parameter 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. - 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 - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/ */
public static func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? { public static func fetchOne<T: NSManagedObject>(from: From<T>, _ 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. 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 - parameter 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. - 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 - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/ */
public static func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? { public static func fetchAll<T: NSManagedObject>(from: From<T>, _ 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. 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 - parameter 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. - 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 - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/ */
public static func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? { public static func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
@@ -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 - parameter 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. - 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 - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public static func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? { public static func fetchCount<T: NSManagedObject>(from: From<T>, _ 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 - parameter 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. - 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 - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public static func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? { public static func fetchCount<T: NSManagedObject>(from: From<T>, _ 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. 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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/ */
public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ 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. 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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/ */
public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
@@ -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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ 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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
@@ -152,14 +152,14 @@ 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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? { public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
@@ -167,14 +167,14 @@ 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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? { public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
@@ -182,14 +182,14 @@ 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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
@@ -197,14 +197,14 @@ 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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {

View File

@@ -37,13 +37,16 @@ public extension DataStack {
/** /**
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. 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 - parameter 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. - 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 - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/ */
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? { public func fetchOne<T: NSManagedObject>(from: From<T>, _ 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) 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. 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 - parameter 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. - 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 - returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/ */
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? { public func fetchOne<T: NSManagedObject>(from: From<T>, _ 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) 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. 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 - parameter 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. - 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 - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/ */
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? { public func fetchAll<T: NSManagedObject>(from: From<T>, _ 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) return self.mainContext.fetchAll(from, fetchClauses)
} }
@@ -79,41 +88,50 @@ public extension DataStack {
/** /**
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. 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 - parameter 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. - 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 - returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/ */
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? { public func fetchAll<T: NSManagedObject>(from: From<T>, _ 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) return self.mainContext.fetchAll(from, fetchClauses)
} }
/** /**
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 - parameter 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. - 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 - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? { public func fetchCount<T: NSManagedObject>(from: From<T>, _ 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) return self.mainContext.fetchCount(from, fetchClauses)
} }
/** /**
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 - parameter 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. - 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 - returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? { public func fetchCount<T: NSManagedObject>(from: From<T>, _ 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) 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. 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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/ */
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? { public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ 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) return self.mainContext.fetchObjectID(from, fetchClauses)
} }
@@ -135,109 +156,130 @@ public extension DataStack {
/** /**
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses. 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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/ */
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? { public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ 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) return self.mainContext.fetchObjectID(from, fetchClauses)
} }
/** /**
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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? { public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ 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) return self.mainContext.fetchObjectIDs(from, fetchClauses)
} }
/** /**
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 - parameter 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. - 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 - returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/ */
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? { public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ 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) return self.mainContext.fetchObjectIDs(from, fetchClauses)
} }
/** /**
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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? { public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ 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) return self.mainContext.queryValue(from, selectClause, queryClauses)
} }
/** /**
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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? { public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ 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) return self.mainContext.queryValue(from, selectClause, queryClauses)
} }
/** /**
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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? { public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ 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) return self.mainContext.queryAttributes(from, selectClause, queryClauses)
} }
/** /**
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. 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 - parameter from: a `From` clause indicating the entity type
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type. - parameter selectClause: a `Select<U>` 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. - 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<U>` parameter. - returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/ */
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? { public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ 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) return self.mainContext.queryAttributes(from, selectClause, queryClauses)
} }

View File

@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>FMWK</string> <string>FMWK</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>0.2.1</string> <string>1.0.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>

View File

@@ -42,27 +42,27 @@ internal func getAssociatedObjectForKey<T: AnyObject>(key: UnsafePointer<Void>,
internal func setAssociatedRetainedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) { internal func setAssociatedRetainedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, 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<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) { internal func setAssociatedCopiedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, 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<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) { internal func setAssociatedAssignedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
objc_setAssociatedObject(object, key, associatedObject, UInt(OBJC_ASSOCIATION_ASSIGN)) objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_ASSIGN)
} }
internal func setAssociatedWeakObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) { internal func setAssociatedWeakObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
if let associatedObject = associatedObject { 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 { else {
objc_setAssociatedObject(object, key, nil, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC)) objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
} }
} }

View File

@@ -0,0 +1,61 @@
//
// MigrationManager.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: - MigrationManager
internal final class MigrationManager: NSMigrationManager, NSProgressReporting {
// MARK: NSObject
override func didChangeValueForKey(key: String) {
super.didChangeValueForKey(key)
if key == "migrationProgress" {
let progress = self.progress
progress.completedUnitCount = Int64(Float(progress.totalUnitCount) * self.migrationProgress)
}
}
// MARK: NSMigrationManager
init(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, progress: NSProgress) {
self.progress = progress
super.init(sourceModel: sourceModel, destinationModel: destinationModel)
}
// MARK: NSProgressReporting
let progress: NSProgress
}

View File

@@ -33,15 +33,15 @@ internal extension NSManagedObject {
// MARK: Internal // MARK: Internal
internal class func createInContext(context: NSManagedObjectContext) -> Self { internal dynamic class func createInContext(context: NSManagedObjectContext) -> Self {
return self( return self.init(
entity: context.entityDescriptionForEntityClass(self)!, entity: context.entityDescriptionForEntityType(self)!,
insertIntoManagedObjectContext: context 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) return self.typedObjectInContext(context, objectID: objectID)
} }
@@ -61,16 +61,19 @@ internal extension NSManagedObject {
private class func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext, objectID: NSManagedObjectID) -> T? { private class func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext, objectID: NSManagedObjectID) -> T? {
var error: NSError? do {
if let existingObject = context.existingObjectWithID(objectID, error: &error) {
let existingObject = try context.existingObjectWithID(objectID)
return (existingObject as! T) return (existingObject as! T)
} }
catch {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), error as NSError,
"Failed to load existing \(typeName(self)) in context.") "Failed to load existing \(typeName(self)) in context."
return nil; )
return nil
}
} }
private func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext) -> T? { private func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext) -> T? {
@@ -78,29 +81,42 @@ internal extension NSManagedObject {
let objectID = self.objectID let objectID = self.objectID
if objectID.temporaryID { if objectID.temporaryID {
var error: NSError? var objectIDError: NSError?
let didSucceed = withExtendedLifetime(self.managedObjectContext) { 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 { if didSucceed != true {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), objectIDError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed to obtain permanent ID for object.") "Failed to obtain permanent ID for object."
)
return nil return nil
} }
} }
var error: NSError? do {
if let existingObject = context.existingObjectWithID(objectID, error: &error) {
let existingObject = try context.existingObjectWithID(objectID)
return (existingObject as! T) return (existingObject as! T)
} }
catch {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), error as NSError,
"Failed to load existing \(typeName(self)) in context.") "Failed to load existing \(typeName(self)) in context."
return nil; )
return nil
}
} }
} }

View File

@@ -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) { if let entityName = self.parentStack?.entityNameForEntityClass(entity) {
@@ -68,10 +73,7 @@ internal extension NSManagedObjectContext {
internal func setupForCoreStoreWithContextName(contextName: String) { internal func setupForCoreStoreWithContextName(contextName: String) {
if self.respondsToSelector("setName:") { self.name = contextName
self.name = contextName
}
self.observerForWillSaveNotification = NotificationObserver( self.observerForWillSaveNotification = NotificationObserver(
notificationName: NSManagedObjectContextWillSaveNotification, notificationName: NSManagedObjectContextWillSaveNotification,
@@ -86,16 +88,18 @@ internal extension NSManagedObjectContext {
return return
} }
var error: NSError? do {
if context.obtainPermanentIDsForObjects(Array(insertedObjects), error: &error) {
try context.obtainPermanentIDsForObjects(Array(insertedObjects))
return return
} }
catch {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), error as NSError,
"Failed to obtain permanent ID(s) for \(numberOfInsertedObjects) inserted object(s)." "Failed to obtain permanent ID(s) for \(numberOfInsertedObjects) inserted object(s)."
) )
}
} }
) )
} }

View File

@@ -52,16 +52,24 @@ internal extension NSManagedObjectContext {
} }
var fetchResults: [T]? var fetchResults: [T]?
var error: NSError? var fetchError: NSError?
self.performBlockAndWait { 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 { if fetchResults == nil {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request.") "Failed executing fetch request."
)
return nil return nil
} }
@@ -87,16 +95,24 @@ internal extension NSManagedObjectContext {
} }
var fetchResults: [T]? var fetchResults: [T]?
var error: NSError? var fetchError: NSError?
self.performBlockAndWait { 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 { if fetchResults == nil {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request.") "Failed executing fetch request."
)
return nil return nil
} }
@@ -128,7 +144,8 @@ internal extension NSManagedObjectContext {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), error ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request.") "Failed executing fetch request."
)
return nil return nil
} }
@@ -154,16 +171,24 @@ internal extension NSManagedObjectContext {
} }
var fetchResults: [NSManagedObjectID]? var fetchResults: [NSManagedObjectID]?
var error: NSError? var fetchError: NSError?
self.performBlockAndWait { 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 { if fetchResults == nil {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request.") "Failed executing fetch request."
)
return nil return nil
} }
@@ -189,16 +214,24 @@ internal extension NSManagedObjectContext {
} }
var fetchResults: [NSManagedObjectID]? var fetchResults: [NSManagedObjectID]?
var error: NSError? var fetchError: NSError?
self.performBlockAndWait { 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 { if fetchResults == nil {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request.") "Failed executing fetch request."
)
return nil return nil
} }
@@ -218,6 +251,7 @@ internal extension NSManagedObjectContext {
fetchRequest.fetchLimit = 0 fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectResultType fetchRequest.resultType = .ManagedObjectResultType
fetchRequest.returnsObjectsAsFaults = true fetchRequest.returnsObjectsAsFaults = true
fetchRequest.includesPropertyValues = false
for clause in deleteClauses { for clause in deleteClauses {
@@ -225,26 +259,32 @@ internal extension NSManagedObjectContext {
} }
var numberOfDeletedObjects: Int? var numberOfDeletedObjects: Int?
var error: NSError? var fetchError: NSError?
self.performBlockAndWait { self.performBlockAndWait {
autoreleasepool { 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 { for object in fetchResults {
self.deleteObject(object) self.deleteObject(object)
} }
numberOfDeletedObjects = fetchResults.count
}
catch {
fetchError = error as NSError
} }
} }
} }
if numberOfDeletedObjects == nil { if numberOfDeletedObjects == nil {
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request.") "Failed executing fetch request."
)
return nil return nil
} }
@@ -271,10 +311,17 @@ internal extension NSManagedObjectContext {
} }
var fetchResults: [AnyObject]? var fetchResults: [AnyObject]?
var error: NSError? var fetchError: NSError?
self.performBlockAndWait { self.performBlockAndWait {
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) do {
fetchResults = try self.executeFetchRequest(fetchRequest)
}
catch {
fetchError = error as NSError
}
} }
if let fetchResults = fetchResults { if let fetchResults = fetchResults {
@@ -287,8 +334,9 @@ internal extension NSManagedObjectContext {
} }
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request.") "Failed executing fetch request."
)
return nil return nil
} }
@@ -312,10 +360,17 @@ internal extension NSManagedObjectContext {
} }
var fetchResults: [AnyObject]? var fetchResults: [AnyObject]?
var error: NSError? var fetchError: NSError?
self.performBlockAndWait { self.performBlockAndWait {
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) do {
fetchResults = try self.executeFetchRequest(fetchRequest)
}
catch {
fetchError = error as NSError
}
} }
if let fetchResults = fetchResults { if let fetchResults = fetchResults {
@@ -323,8 +378,9 @@ internal extension NSManagedObjectContext {
} }
CoreStore.handleError( CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError), fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request.") "Failed executing fetch request."
)
return nil return nil
} }
} }

View File

@@ -74,7 +74,8 @@ internal extension NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType) let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
context.parentContext = rootContext context.parentContext = rootContext
context.shouldCascadeSavesToParent = true context.mergePolicy = NSRollbackMergePolicy
context.shouldCascadeSavesToParent = false
context.undoManager = nil context.undoManager = nil
context.setupForCoreStoreWithContextName("com.corestore.maincontext") context.setupForCoreStoreWithContextName("com.corestore.maincontext")
context.observerForDidSaveNotification = NotificationObserver( context.observerForDidSaveNotification = NotificationObserver(

View File

@@ -68,44 +68,43 @@ internal extension NSManagedObjectContext {
internal func saveSynchronously() -> SaveResult { internal func saveSynchronously() -> SaveResult {
var result = SaveResult(hasChanges: false) var result = SaveResult(hasChanges: false)
self.performBlockAndWait {
[unowned self] () -> Void in self.performBlockAndWait { [unowned self] () -> Void in
if !self.hasChanges { if !self.hasChanges {
return return
} }
var saveError: NSError? do {
if self.save(&saveError) {
if self.shouldCascadeSavesToParent { try self.save()
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)
} }
else if let error = saveError { catch {
let saveError = error as NSError
CoreStore.handleError( CoreStore.handleError(
error, saveError,
"Failed to save <\(NSManagedObjectContext.self)>.") "Failed to save \(typeName(NSManagedObjectContext))."
result = SaveResult(error) )
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 { else {
result = SaveResult(hasChanges: false) result = SaveResult(hasChanges: true)
} }
} }
@@ -128,43 +127,35 @@ internal extension NSManagedObjectContext {
return return
} }
var saveError: NSError? do {
if self.save(&saveError) {
if self.shouldCascadeSavesToParent { try self.save()
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))
}
}
} }
else if let error = saveError { catch {
let saveError = error as NSError
CoreStore.handleError( CoreStore.handleError(
error, saveError,
"Failed to save <\(NSManagedObjectContext.self)>.") "Failed to save \(typeName(NSManagedObjectContext))."
)
if let completion = completion { if let completion = completion {
GCDQueue.Main.async { 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 { GCDQueue.Main.async {
completion(result: SaveResult(hasChanges: false)) completion(result: SaveResult(hasChanges: true))
} }
} }
} }

View File

@@ -0,0 +1,266 @@
//
// 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
@nonobjc internal class func fromBundle(bundle: NSBundle, modelName: String, modelVersionHints: Set<String> = []) -> NSManagedObjectModel {
guard let modelFilePath = bundle.pathForResource(modelName, ofType: "momd") else {
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 {
fatalError("Could not load \(typeName(NSManagedObjectModel)) metadata from path \"\(versionInfoPlistURL)\".")
}
let modelVersions = Set(versionHashes.keys)
let currentModelVersion: String
if let plistModelVersion = versionInfo["NSManagedObjectModel_CurrentVersionName"] as? String where modelVersionHints.isEmpty || modelVersionHints.contains(plistModelVersion) {
currentModelVersion = plistModelVersion
}
else if let resolvedVersion = modelVersions.intersect(modelVersionHints).first {
CoreStore.log(
.Warning,
message: "The MigrationChain leaf versions do not include the model file's current version. Resolving to version \"\(resolvedVersion)\"."
)
currentModelVersion = resolvedVersion
}
else if let resolvedVersion = modelVersions.first ?? modelVersionHints.first {
CoreStore.log(
.Warning,
message: "The MigrationChain leaf versions do not include any of the model file's embedded versions. Resolving to version \"\(resolvedVersion)\"."
)
currentModelVersion = resolvedVersion
}
else {
fatalError("No model files were found in URL \"\(modelFileURL)\".")
}
var modelVersionFileURL: NSURL?
for modelVersion in modelVersions {
let fileURL = modelFileURL.URLByAppendingPathComponent("\(modelVersion).mom", isDirectory: false)
if modelVersion == currentModelVersion {
modelVersionFileURL = fileURL
continue
}
precondition(
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
}
fatalError("Could not create an \(typeName(NSManagedObjectModel)) from the model at URL \"\(modelFileURL)\".")
}
@nonobjc private(set) internal 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
)
}
}
@nonobjc private(set) internal var modelVersions: Set<String>? {
get {
let value: NSSet? = getAssociatedObjectForKey(
&PropertyKeys.modelVersions,
inObject: self
)
return value as? Set<String>
}
set {
setAssociatedCopiedObject(
newValue == nil ? nil : (newValue! as NSSet),
forKey: &PropertyKeys.modelVersions,
inObject: self
)
}
}
@nonobjc internal func entityNameForClass(entityClass: AnyClass) -> String {
return self.entityNameMapping[NSStringFromClass(entityClass)]!
}
@nonobjc internal func mergedModels() -> [NSManagedObjectModel] {
return self.modelVersions?.map { self[$0] }.flatMap { $0 == nil ? [] : [$0!] } ?? [self]
}
@nonobjc internal 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
}
@nonobjc internal subscript(metadata: [String: AnyObject]) -> NSManagedObjectModel? {
if let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : NSData] {
for modelVersion in self.modelVersions ?? [] {
if let versionModel = self[modelVersion] where modelHashes == versionModel.entityVersionHashesByName {
return versionModel
}
}
}
return nil
}
// MARK: Private
private var modelFileURL: NSURL? {
get {
return self.modelVersionFileURL?.URLByDeletingLastPathComponent
}
}
private var modelVersionFileURL: NSURL? {
get {
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]
}
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 struct PropertyKeys {
static var entityNameMapping: Void?
static var modelVersionFileURL: Void?
static var modelVersions: Void?
static var currentModelVersion: Void?
}
}

View File

@@ -36,7 +36,7 @@ internal final class NotificationObserver {
let object: AnyObject? let object: AnyObject?
let observer: NSObjectProtocol 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.notificationName = notificationName
self.object = object self.object = object

View File

@@ -46,10 +46,8 @@ public extension CoreStore {
level: level, level: level,
message: message, message: message,
fileName: fileName, fileName: fileName,
lineNumber: lineNumber: lineNumber,
lineNumber, functionName: functionName
functionName:
functionName
) )
} }
@@ -60,7 +58,8 @@ public extension CoreStore {
message: message, message: message,
fileName: fileName, fileName: fileName,
lineNumber: lineNumber, lineNumber: lineNumber,
functionName: functionName) functionName: functionName
)
} }
internal static func assert(@autoclosure condition: () -> Bool, _ message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) { internal static func assert(@autoclosure condition: () -> Bool, _ message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) {
@@ -70,6 +69,7 @@ public extension CoreStore {
message: message, message: message,
fileName: fileName, fileName: fileName,
lineNumber: lineNumber, lineNumber: lineNumber,
functionName: functionName) functionName: functionName
)
} }
} }

View File

@@ -56,7 +56,7 @@ public protocol CoreStoreLogger {
:lineNumber: the source line number :lineNumber: the source line number
:functionName: the source function name :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. Handles errors sent by the `CoreStore` framework.
@@ -67,7 +67,7 @@ public protocol CoreStoreLogger {
:lineNumber: the source line number :lineNumber: the source line number
:functionName: the source function name :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. Handles assertions made throughout the `CoreStore` framework.
@@ -86,5 +86,21 @@ public protocol CoreStoreLogger {
internal func typeName<T>(value: T) -> String { internal func typeName<T>(value: T) -> String {
return "<\(_stdlib_getDemangledTypeName(value))>" return "'\(_stdlib_getDemangledTypeName(value))'"
}
internal func typeName<T>(value: T.Type) -> String {
return "'\(value)'"
}
internal func typeName(value: AnyClass) -> String {
return "'\(value)'"
}
internal func typeName(name: String?) -> String {
let typeName = name ?? "unknown"
return "<\(typeName)>"
} }

View File

@@ -31,39 +31,52 @@ import Foundation
/** /**
The `DefaultLogger` is a basic implementation of the `CoreStoreLogger` protocol. 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 `log(...)` method calls `print(...)` 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 `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. - The `assert(...)` method calls `assert(...)` on the arguments.
*/ */
public final class DefaultLogger: CoreStoreLogger { public final class DefaultLogger: CoreStoreLogger {
public init() { } 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 #if DEBUG
let icon: String
let levelString: String let levelString: String
switch level { switch level {
case .Trace: levelString = "Trace"
case .Notice: levelString = "Notice" case .Trace:
case .Warning: levelString = "Warning" icon = "🔹"
case .Fatal: levelString = "Fatal" levelString = "Trace"
case .Notice:
icon = "🔸"
levelString = "Notice"
case .Warning:
icon = "⚠️"
levelString = "Warning"
case .Fatal:
icon = ""
levelString = "Fatal"
} }
Swift.println("[CoreStore:\(levelString)] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n") Swift.print("\(icon) [CoreStore: \(levelString)] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
#endif #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 #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)\n \(error)\n")
#endif #endif
} }
public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) { public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
#if DEBUG #if DEBUG
Swift.assert(condition, message, file: fileName, line: numericCast(lineNumber)) Swift.assert(condition, "❗ [CoreStore: Assertion Failure] \(message)", file: fileName, line: numericCast(lineNumber))
#endif #endif
} }
} }

View File

@@ -0,0 +1,144 @@
//
// CoreStore+Migration.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
import GCDKit
// MARK: - CoreStore
public extension CoreStore {
/**
Asynchronously adds to the `defaultStack` 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`.)
- 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 mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- 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. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Asynchronously adds to the `defaultStack` 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`.)
- 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 "<Application name>.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 mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- 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. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Using the `defaultStack`, migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
- 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.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Using the `defaultStack`, 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.
- 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.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Using the `defaultStack`, checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
- 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: an array of `MigrationType`s indicating the chain of migrations 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 static func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForSQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
/**
Using the `defaultStack`, checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
- 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 static func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForSQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
}

View File

@@ -32,218 +32,24 @@ import GCDKit
public extension DataStack { 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.
: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.
*/
public func needsMigrationForSQLiteStore(fileName: String, configuration: String? = nil) -> Bool? {
return needsMigrationForSQLiteStore(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration
)
}
/**
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.
*/
public func needsMigrationForSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil) -> Bool? {
var error: NSError?
let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL,
error: &error
)
if metadata == nil {
CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".")
return nil
}
return !self.coordinator.managedObjectModel.isConfiguration(
configuration,
compatibleWithStoreMetadata: metadata
)
}
/**
EXPERIMENTAL
*/
private func upgradeSQLiteStoreIfNeeded(fileName: String, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
self.upgradeSQLiteStoreIfNeeded(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
completion: completion
)
}
/**
EXPERIMENTAL
*/
private func upgradeSQLiteStoreIfNeeded(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
var metadataError: NSError?
let metadata: [NSObject: AnyObject]! = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL,
error: &metadataError
)
if metadata == nil {
CoreStore.handleError(
metadataError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\".")
GCDQueue.Main.async {
// TODO: inspect valid errors for metadataForPersistentStoreOfType()
completion(PersistentStoreResult(.PersistentStoreNotFound))
}
return
}
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)\".")
GCDQueue.Main.async {
completion(PersistentStoreResult(.MigrationFailed))
}
return
}
}
/** /**
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`.) 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. - 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.
: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. - 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.
: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 mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- 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. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/ */
public func addSQLiteStore(fileName: String, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
self.addSQLiteStore( return try self.addSQLiteStore(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent( fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileName, fileName,
isDirectory: false isDirectory: false
), ),
configuration: configuration, configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion completion: completion
) )
} }
@@ -251,35 +57,23 @@ 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`.) 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 "<Application name>.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 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 "<Application name>.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. - 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.
: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 mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- 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. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/ */
public func addSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) { public func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
var error: NSError? CoreStore.assert(
let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( fileURL.fileURL,
NSSQLiteStoreType, "The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
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
}
let coordinator = self.coordinator; let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) { 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 if store.type == NSSQLiteStoreType
&& isExistingStoreAutomigrating && isExistingStoreAutomigrating
@@ -289,69 +83,487 @@ public extension DataStack {
completion(PersistentStoreResult(store)) completion(PersistentStoreResult(store))
} }
return return nil
} }
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError( CoreStore.handleError(
NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL), error,
"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."
)
throw error
}
do {
try NSFileManager.defaultManager().createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
}
catch _ { }
do {
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL
)
return self.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
metadata: metadata,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: { (result) -> Void in
if case .Failure(let error) = result {
completion(PersistentStoreResult(error))
return
}
do {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
automigrating: false,
resetStoreOnMigrationFailure: false
)
completion(PersistentStoreResult(store))
}
catch {
completion(PersistentStoreResult(error as NSError))
}
}
)
}
catch let error as NSError
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
automigrating: false,
resetStoreOnMigrationFailure: false
)
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
return nil
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
}
}
/**
Migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
- 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.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.upgradeSQLiteStoreIfNeeded(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
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.
- 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.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
let metadata: [String: AnyObject]
do {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL
)
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
}
return self.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
metadata: metadata,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
- 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: an array of `MigrationType`s indicating the chain of migrations 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 requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try requiredMigrationsForSQLiteStore(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
/**
Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
- 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 requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
let metadata: [String : AnyObject]
do {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL
)
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
}
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else {
let error = NSError(coreStoreErrorCode: .MappingModelNotFound)
CoreStore.handleError(
error,
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(self.modelVersion)\"."
)
throw error
}
return migrationSteps.map { $0.migrationType }
}
// MARK: Private
private func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL, metadata: [String: AnyObject], configuration: String?, mappingModelBundles: [NSBundle]?, completion: (MigrationResult) -> Void) -> NSProgress? {
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else {
CoreStore.handleError(
NSError(coreStoreErrorCode: .MappingModelNotFound),
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(model)\"."
)
GCDQueue.Main.async { GCDQueue.Main.async {
completion(PersistentStoreResult(.DifferentPersistentStoreExistsAtURL)) completion(MigrationResult(.MappingModelNotFound))
} }
return return nil
} }
let numberOfMigrations: Int64 = Int64(migrationSteps.count)
if numberOfMigrations == 0 {
GCDQueue.Main.async {
completion(MigrationResult([]))
return
}
return nil
}
let migrationTypes = migrationSteps.map { $0.migrationType }
var migrationResult: MigrationResult?
var operations = [NSOperation]()
var cancelled = false
let progress = NSProgress(parent: nil, userInfo: nil)
progress.totalUnitCount = numberOfMigrations
// todo nsprogress crashing sometimes
for (sourceModel, destinationModel, mappingModel, _) in migrationSteps {
progress.becomeCurrentWithPendingUnitCount(1)
let childProgress = NSProgress(parent: progress, userInfo: nil)
childProgress.totalUnitCount = 100
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,
progress: childProgress
)
}
catch {
migrationResult = MigrationResult(error as NSError)
cancelled = true
}
}
GCDQueue.Main.async {
withExtendedLifetime(childProgress) { (_: NSProgress) -> Void in }
return
}
}
)
progress.resignCurrent()
}
let migrationOperation = NSBlockOperation()
migrationOperation.qualityOfService = .Utility
operations.map { migrationOperation.addDependency($0) }
migrationOperation.addExecutionBlock { () -> Void in
GCDQueue.Main.async {
progress.setProgressHandler(nil)
completion(migrationResult ?? MigrationResult(migrationTypes))
return
}
}
operations.append(migrationOperation)
self.migrationQueue.addOperations(operations, waitUntilFinished: false)
return progress
}
private func computeMigrationFromStoreMetadata(metadata: [String: AnyObject], configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? {
let model = self.model
if model.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) {
return []
}
guard let initialModel = model[metadata],
var currentVersion = initialModel.currentModelVersion else {
return nil
}
let migrationChain: MigrationChain = self.migrationChain.empty
? [currentVersion: model.currentModelVersion!]
: 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] where sourceModel != model {
if let mappingModel = NSMappingModel(
fromBundles: mappingModelBundles,
forSourceModel: sourceModel,
destinationModel: destinationModel) {
migrationSteps.append(
(
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
migrationType: .Heavyweight(
sourceVersion: currentVersion,
destinationVersion: nextVersion
)
)
)
}
else {
do {
let mappingModel = try NSMappingModel.inferredMappingModelForSourceModel(
sourceModel,
destinationModel: destinationModel
)
migrationSteps.append(
(
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
migrationType: .Lightweight(
sourceVersion: currentVersion,
destinationVersion: nextVersion
)
)
)
}
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, progress: NSProgress) throws {
autoreleasepool {
let journalUpdatingCoordinator = NSPersistentStoreCoordinator(managedObjectModel: sourceModel)
let store = try! journalUpdatingCoordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: nil,
URL: fileURL,
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
)
try! journalUpdatingCoordinator.removePersistentStore(store)
}
let migrationManager = MigrationManager(
sourceModel: sourceModel,
destinationModel: destinationModel,
progress: progress
)
let temporaryDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString)
let fileManager = NSFileManager.defaultManager() let fileManager = NSFileManager.defaultManager()
var directoryError: NSError? try! fileManager.createDirectoryAtURL(
if !fileManager.createDirectoryAtURL( temporaryDirectoryURL,
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true, withIntermediateDirectories: true,
attributes: nil, attributes: nil
error: &directoryError) { )
CoreStore.handleError( let temporaryFileURL = temporaryDirectoryURL.URLByAppendingPathComponent(fileURL.lastPathComponent!, isDirectory: false)
directoryError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed to create directory for SQLite store at \"\(fileURL)\".")
GCDQueue.Main.async {
completion(PersistentStoreResult(directoryError!)) do {
}
return try migrationManager.migrateStoreFromURL(
fileURL,
type: NSSQLiteStoreType,
options: nil,
withMappingModel: mappingModel,
toDestinationURL: temporaryFileURL,
destinationType: NSSQLiteStoreType,
destinationOptions: nil
)
}
catch {
do {
try fileManager.removeItemAtURL(temporaryDirectoryURL)
}
catch _ { }
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
CoreStore.handleError(
error as NSError,
"Failed to migrate from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
)
throw error
} }
coordinator.performBlock { do {
var persistentStoreError: NSError? try fileManager.replaceItemAtURL(
let store = coordinator.addPersistentStoreWithType( fileURL,
NSSQLiteStoreType, withItemAtURL: temporaryFileURL,
configuration: configuration, backupItemName: nil,
URL: fileURL, options: [],
options: [NSSQLitePragmasOption: ["WAL": "journal_mode"], resultingItemURL: nil
NSInferMappingModelAutomaticallyOption: true, )
NSMigratePersistentStoresAutomaticallyOption: true],
error: &persistentStoreError)
if let store = store { progress.completedUnitCount = progress.totalUnitCount
GCDQueue.Main.async { do {
self.updateMetadataForPersistentStore(store) try fileManager.removeItemAtPath(fileURL.path! + "-shm")
completion(PersistentStoreResult(store))
}
} }
else { catch _ { }
}
catch {
GCDQueue.Main.async { do {
CoreStore.handleError( try fileManager.removeItemAtURL(temporaryDirectoryURL)
persistentStoreError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".")
completion(PersistentStoreResult(.UnknownError))
}
} }
catch _ { }
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
CoreStore.handleError(
error as NSError,
"Failed to save store after migrating from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
)
throw error
} }
} }
} }

View File

@@ -0,0 +1,245 @@
//
// 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
/**
A `MigrationChain` indicates the sequence of model versions to be used as the order for incremental migration. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants.
Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to use the .xcdatamodel's current version as the final version, and to disable incremental migrations:
let dataStack = DataStack(migrationChain: nil)
This means that the mapping model will be computed from the store's version straight to the `DataStack`'s model version.
To support incremental migrations, specify the linear order of versions:
let dataStack = DataStack(migrationChain:
["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"])
or for more complex migration paths, a version tree that maps the key-values to the source-destination versions:
let dataStack = DataStack(migrationChain: [
"MyAppModel": "MyAppModelV3",
"MyAppModelV2": "MyAppModelV4",
"MyAppModelV3": "MyAppModelV4"
])
This allows for different migration paths depending on the starting version. The example above resolves to the following paths:
- MyAppModel-MyAppModelV3-MyAppModelV4
- MyAppModelV2-MyAppModelV4
- MyAppModelV3-MyAppModelV4
The `MigrationChain` is validated when passed to the `DataStack` and unless it is empty, will raise an assertion if any of the following conditions are met:
- a version appears twice in an array
- a version appears twice as a key in a dictionary literal
- a loop is found in any of the paths
*/
public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, DictionaryLiteralConvertible, ArrayLiteralConvertible {
// MARK: NilLiteralConvertible
public init(nilLiteral: ()) {
self.versionTree = [:]
self.rootVersions = []
self.leafVersions = []
self.valid = true
}
// MARK: StringLiteralConvertible
public init(stringLiteral value: String) {
self.versionTree = [:]
self.rootVersions = [value]
self.leafVersions = [value]
self.valid = true
}
// MARK: ExtendedGraphemeClusterLiteralConvertible
public init(extendedGraphemeClusterLiteral value: String) {
self.versionTree = [:]
self.rootVersions = [value]
self.leafVersions = [value]
self.valid = true
}
// MARK: UnicodeScalarLiteralConvertible
public init(unicodeScalarLiteral value: String) {
self.versionTree = [:]
self.rootVersions = [value]
self.leafVersions = [value]
self.valid = true
}
// MARK: DictionaryLiteralConvertible
public init(dictionaryLiteral elements: (String, String)...) {
var valid = true
let versionTree = elements.reduce([String: String]()) { (var versionTree, tuple: (String, String)) -> [String: String] in
if let _ = versionTree.updateValue(tuple.1, forKey: tuple.0) {
CoreStore.assert(false, "\(typeName(MigrationChain))'s migration chain could not be created due to ambiguous version paths.")
valid = false
}
return versionTree
}
let leafVersions = Set(
elements.filter { (tuple: (String, String)) -> Bool in
return versionTree[tuple.1] == nil
}.map { $1 }
)
let isVersionAmbiguous = { (start: String) -> Bool in
var checklist: Set<String> = [start]
var version = start
while let nextVersion = versionTree[version] where nextVersion != version {
if checklist.contains(nextVersion) {
CoreStore.assert(false, "\(typeName(MigrationChain))'s migration chain could not be created due to looping version paths.")
return true
}
checklist.insert(nextVersion)
version = nextVersion
}
return false
}
self.versionTree = versionTree
self.rootVersions = Set(versionTree.keys).subtract(versionTree.values)
self.leafVersions = leafVersions
self.valid = valid && Set(versionTree.keys).union(versionTree.values).filter { isVersionAmbiguous($0) }.count <= 0
}
// 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]()
var valid = true
for version in elements {
if let lastVersion = lastVersion {
if let _ = versionTree.updateValue(version, forKey: lastVersion) {
valid = false
}
}
lastVersion = version
}
self.versionTree = versionTree
self.rootVersions = Set([elements.first].flatMap { $0 == nil ? [] : [$0!] })
self.leafVersions = Set([elements.last].flatMap { $0 == nil ? [] : [$0!] })
self.valid = valid
}
// MARK: Internal
internal let rootVersions: Set<String>
internal let leafVersions: Set<String>
internal let valid: Bool
internal var empty: Bool {
return self.versionTree.count <= 0
}
internal func contains(version: String) -> Bool {
return self.rootVersions.contains(version)
|| self.leafVersions.contains(version)
|| self.versionTree[version] != nil
}
internal func nextVersionFrom(version: String) -> String? {
if let nextVersion = self.versionTree[version] where nextVersion != version {
return nextVersion
}
return nil
}
// MARK: Private
private let versionTree: [String: String]
}
// MARK: - MigrationChain: CustomDebugStringConvertible
extension MigrationChain: CustomDebugStringConvertible {
public var debugDescription: String {
guard self.valid else {
return "<invalid migration chain>"
}
var paths = [String]()
for var version in self.rootVersions {
var steps = [version]
while let nextVersion = self.nextVersionFrom(version) {
steps.append(nextVersion)
version = nextVersion
}
paths.append("".join(steps))
}
return "[" + "], [".join(paths) + "]"
}
}

View File

@@ -0,0 +1,185 @@
//
// 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(version: String)
/**
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(sourceVersion: String, destinationVersion: String)
/**
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(sourceVersion: String, destinationVersion: String)
/**
Returns the source model version for the migration type. If no migration is required, `sourceVersion` will be equal to the `destinationVersion`.
*/
public var sourceVersion: String {
switch self {
case .None(let version):
return version
case .Lightweight(let sourceVersion, _):
return sourceVersion
case .Heavyweight(let sourceVersion, _):
return sourceVersion
}
}
/**
Returns the destination model version for the migration type. If no migration is required, `destinationVersion` will be equal to the `sourceVersion`.
*/
public var destinationVersion: String {
switch self {
case .None(let version):
return version
case .Lightweight(_, let destinationVersion):
return destinationVersion
case .Heavyweight(_, let destinationVersion):
return destinationVersion
}
}
// 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 either the migration succeeded, or there were no migrations needed. The associated value is an array of `MigrationType`s reflecting the migration steps completed.
*/
case Success([MigrationType])
/**
`SaveResult.Failure` indicates that the migration failed. The associated object for this value is the related `NSError` instance.
*/
case Failure(NSError)
// MARK: Internal
internal init(_ migrationTypes: [MigrationType]) {
self = .Success(migrationTypes)
}
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
}
}
}

View File

@@ -54,11 +54,6 @@ public enum CoreStoreErrorCode: Int {
An `NSMappingModel` could not be found for a specific source and destination model versions. An `NSMappingModel` could not be found for a specific source and destination model versions.
*/ */
case MappingModelNotFound case MappingModelNotFound
/**
An `NSMigrationManager` prepared to migrate the store.
*/
case MigrationFailed
} }

View File

@@ -34,63 +34,63 @@ public extension CoreStore {
// MARK: Public // 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 - parameter 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<T: NSManagedObject>(object: T) -> ManagedObjectController<T> { public static func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
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 - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ManagedObjectListController<T> { public static func monitorList<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ListMonitor<T> {
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 - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: [FetchClause]) -> ManagedObjectListController<T> { public static func monitorList<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: [FetchClause]) -> ListMonitor<T> {
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 - parameter 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. - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ sectionedBy: SectionedBy, _ fetchClauses: FetchClause...) -> ManagedObjectListController<T> { public static func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
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 - parameter 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. - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ sectionedBy: SectionedBy, _ fetchClauses: [FetchClause]) -> ManagedObjectListController<T> { public static func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
return self.defaultStack.observeSectionedList(from, sectionedBy, fetchClauses) return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses)
} }
} }

View File

@@ -35,81 +35,98 @@ public extension DataStack {
// MARK: Public // 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 - parameter 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<T: NSManagedObject>(object: T) -> ManagedObjectController<T> { public func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
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 ManagedObjectController( return ObjectMonitor(
dataStack: self, dataStack: self,
object: object 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 - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> ManagedObjectListController<T> { public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
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 - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> ManagedObjectListController<T> { public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
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."
)
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
return ManagedObjectListController( return ListMonitor(
dataStack: self, dataStack: self,
from: from, from: from,
sectionedBy: nil, sectionBy: nil,
fetchClauses: fetchClauses 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 - parameter 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. - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ sectionedBy: SectionedBy, _ fetchClauses: FetchClause...) -> ManagedObjectListController<T> { public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
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 - parameter 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. - parameter 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. - parameter 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<T: NSManagedObject>(from: From<T>, _ sectionedBy: SectionedBy, _ fetchClauses: [FetchClause]) -> ManagedObjectListController<T> { public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
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."
)
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
return ManagedObjectListController( return ListMonitor(
dataStack: self, dataStack: self,
from: from, from: from,
sectionedBy: sectionedBy, sectionBy: sectionBy,
fetchClauses: fetchClauses fetchClauses: fetchClauses
) )
} }

View File

@@ -1,5 +1,5 @@
// //
// ManagedObjectListController.swift // ListMonitor.swift
// CoreStore // CoreStore
// //
// Copyright (c) 2015 John Rommel Estropia // Copyright (c) 2015 John Rommel Estropia
@@ -28,125 +28,80 @@ import CoreData
import GCDKit 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( let monitor = CoreStore.monitorList(
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(
From(MyPersonEntity), From(MyPersonEntity),
Where("title", isEqualTo: "Engineer"), Where("title", isEqualTo: "Engineer"),
OrderBy(.Ascending("lastName")) 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. 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. `ManagedObjectListController` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles. 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. 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), From(MyPersonEntity),
SectionedBy("age") { "Age \($0)" }, SectionBy("age") { "Age \($0)" },
Where("title", isEqualTo: "Engineer"), Where("title", isEqualTo: "Engineer"),
OrderBy(.Ascending("lastName")) 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 indexPath = NSIndexPath(forItem: 3, inSection: 2)
let person1 = listController[indexPath] let person1 = monitor[indexPath]
let person2 = listController[2, 3] let person2 = monitor[2, 3]
In the example above, both `person1` and `person2` will contain the object at section=2, index=3. In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
*/ */
public final class ManagedObjectListController<T: NSManagedObject> { public final class ListMonitor<T: NSManagedObject> {
// MARK: Public // 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. - parameter index: the index of the object. Using an index above the valid range will throw an exception.
*/ */
public subscript(index: Int) -> T { public subscript(index: Int) -> T {
return self.fetchedResultsController.objectAtIndexPath(NSIndexPath(forItem: index, inSection: 0)) as! T return self[0, index]
} }
/** /**
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 `sectionIndex` and `itemIndex`. 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. - 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[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 { public subscript(indexPath: NSIndexPath) -> T {
return self.fetchedResultsController.objectAtIndexPath(indexPath) as! T return self.fetchedResultsController.objectAtIndexPath(indexPath) as! T
} }
/**
Accesses the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ManagedObjectListController`'s created with `observeSectionedList(_:)`.
: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.
*/
public subscript(sectionIndex: Int, itemIndex: Int) -> T {
return self.fetchedResultsController.objectAtIndexPath(NSIndexPath(forItem: itemIndex, inSection: sectionIndex)) as! T
}
/** /**
Returns the number of sections Returns the number of sections
*/ */
@@ -158,118 +113,124 @@ public final class ManagedObjectListController<T: NSManagedObject> {
/** /**
Returns the number of objects in the specified section 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 { 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 Returns the `NSFetchedResultsSectionInfo` for the specified section
:param: section the section index - parameter section: the section index
*/ */
public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo { public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo {
return self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo return self.fetchedResultsController.sections![section]
} }
/** /**
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. 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 - parameter observer: a `ListObserver` to send change notifications to
*/ */
public func addObserver<U: ManagedObjectListChangeObserver where U.EntityType == T>(observer: U) { public func addObserver<U: ListObserver where U.EntityType == T>(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) self.removeObserver(observer)
self.registerChangeNotification( self.registerChangeNotification(
&NotificationKey.willChangeList, &NotificationKey.willChangeList,
name: ManagedObjectListControllerWillChangeListNotification, name: ListMonitorWillChangeListNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController) -> Void in callback: { [weak observer] (monitor) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectListWillChange(listController) observer.listMonitorWillChange(monitor)
} }
} }
) )
self.registerChangeNotification( self.registerChangeNotification(
&NotificationKey.didChangeList, &NotificationKey.didChangeList,
name: ManagedObjectListControllerDidChangeListNotification, name: ListMonitorDidChangeListNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController) -> Void in callback: { [weak observer] (monitor) -> Void in
if let observer = observer { 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. 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 - parameter observer: a `ListObjectObserver` to send change notifications to
*/ */
public func addObserver<U: ManagedObjectListObjectObserver where U.EntityType == T>(observer: U) { public func addObserver<U: ListObjectObserver where U.EntityType == T>(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) self.removeObserver(observer)
self.registerChangeNotification( self.registerChangeNotification(
&NotificationKey.willChangeList, &NotificationKey.willChangeList,
name: ManagedObjectListControllerWillChangeListNotification, name: ListMonitorWillChangeListNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController) -> Void in callback: { [weak observer] (monitor) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectListWillChange(listController) observer.listMonitorWillChange(monitor)
} }
} }
) )
self.registerChangeNotification( self.registerChangeNotification(
&NotificationKey.didChangeList, &NotificationKey.didChangeList,
name: ManagedObjectListControllerDidChangeListNotification, name: ListMonitorDidChangeListNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController) -> Void in callback: { [weak observer] (monitor) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectListDidChange(listController) observer.listMonitorDidChange(monitor)
} }
} }
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didInsertObject, &NotificationKey.didInsertObject,
name: ManagedObjectListControllerDidInsertObjectNotification, name: ListMonitorDidInsertObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didInsertObject: object, didInsertObject: object,
toIndexPath: newIndexPath! toIndexPath: newIndexPath!
) )
@@ -278,14 +239,14 @@ public final class ManagedObjectListController<T: NSManagedObject> {
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didDeleteObject, &NotificationKey.didDeleteObject,
name: ManagedObjectListControllerDidDeleteObjectNotification, name: ListMonitorDidDeleteObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didDeleteObject: object, didDeleteObject: object,
fromIndexPath: indexPath! fromIndexPath: indexPath!
) )
@@ -294,14 +255,14 @@ public final class ManagedObjectListController<T: NSManagedObject> {
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didUpdateObject, &NotificationKey.didUpdateObject,
name: ManagedObjectListControllerDidUpdateObjectNotification, name: ListMonitorDidUpdateObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didUpdateObject: object, didUpdateObject: object,
atIndexPath: indexPath! atIndexPath: indexPath!
) )
@@ -310,14 +271,14 @@ public final class ManagedObjectListController<T: NSManagedObject> {
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didMoveObject, &NotificationKey.didMoveObject,
name: ManagedObjectListControllerDidMoveObjectNotification, name: ListMonitorDidMoveObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didMoveObject: object, didMoveObject: object,
fromIndexPath: indexPath!, fromIndexPath: indexPath!,
toIndexPath: newIndexPath! toIndexPath: newIndexPath!
@@ -328,57 +289,60 @@ public final class ManagedObjectListController<T: NSManagedObject> {
} }
/** /**
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. 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 - parameter observer: a `ListSectionObserver` to send change notifications to
*/ */
public func addObserver<U: ManagedObjectListSectionObserver where U.EntityType == T>(observer: U) { public func addObserver<U: ListSectionObserver where U.EntityType == T>(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) self.removeObserver(observer)
self.registerChangeNotification( self.registerChangeNotification(
&NotificationKey.willChangeList, &NotificationKey.willChangeList,
name: ManagedObjectListControllerWillChangeListNotification, name: ListMonitorWillChangeListNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController) -> Void in callback: { [weak observer] (monitor) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectListWillChange(listController) observer.listMonitorWillChange(monitor)
} }
} }
) )
self.registerChangeNotification( self.registerChangeNotification(
&NotificationKey.didChangeList, &NotificationKey.didChangeList,
name: ManagedObjectListControllerDidChangeListNotification, name: ListMonitorDidChangeListNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController) -> Void in callback: { [weak observer] (monitor) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectListDidChange(listController) observer.listMonitorDidChange(monitor)
} }
} }
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didInsertObject, &NotificationKey.didInsertObject,
name: ManagedObjectListControllerDidInsertObjectNotification, name: ListMonitorDidInsertObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didInsertObject: object, didInsertObject: object,
toIndexPath: newIndexPath! toIndexPath: newIndexPath!
) )
@@ -387,14 +351,14 @@ public final class ManagedObjectListController<T: NSManagedObject> {
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didDeleteObject, &NotificationKey.didDeleteObject,
name: ManagedObjectListControllerDidDeleteObjectNotification, name: ListMonitorDidDeleteObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didDeleteObject: object, didDeleteObject: object,
fromIndexPath: indexPath! fromIndexPath: indexPath!
) )
@@ -403,14 +367,14 @@ public final class ManagedObjectListController<T: NSManagedObject> {
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didUpdateObject, &NotificationKey.didUpdateObject,
name: ManagedObjectListControllerDidUpdateObjectNotification, name: ListMonitorDidUpdateObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didUpdateObject: object, didUpdateObject: object,
atIndexPath: indexPath! atIndexPath: indexPath!
) )
@@ -419,14 +383,14 @@ public final class ManagedObjectListController<T: NSManagedObject> {
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didMoveObject, &NotificationKey.didMoveObject,
name: ManagedObjectListControllerDidMoveObjectNotification, name: ListMonitorDidMoveObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didMoveObject: object, didMoveObject: object,
fromIndexPath: indexPath!, fromIndexPath: indexPath!,
toIndexPath: newIndexPath! toIndexPath: newIndexPath!
@@ -437,14 +401,14 @@ public final class ManagedObjectListController<T: NSManagedObject> {
self.registerSectionNotification( self.registerSectionNotification(
&NotificationKey.didInsertSection, &NotificationKey.didInsertSection,
name: ManagedObjectListControllerDidInsertSectionNotification, name: ListMonitorDidInsertSectionNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, sectionInfo, sectionIndex) -> Void in callback: { [weak observer] (monitor, sectionInfo, sectionIndex) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didInsertSection: sectionInfo, didInsertSection: sectionInfo,
toSectionIndex: sectionIndex toSectionIndex: sectionIndex
) )
@@ -453,14 +417,14 @@ public final class ManagedObjectListController<T: NSManagedObject> {
) )
self.registerSectionNotification( self.registerSectionNotification(
&NotificationKey.didDeleteSection, &NotificationKey.didDeleteSection,
name: ManagedObjectListControllerDidDeleteSectionNotification, name: ListMonitorDidDeleteSectionNotification,
toObserver: observer, toObserver: observer,
callback: { [weak observer] (listController, sectionInfo, sectionIndex) -> Void in callback: { [weak observer] (monitor, sectionInfo, sectionIndex) -> Void in
if let observer = observer { if let observer = observer {
observer.managedObjectList( observer.listMonitor(
listController, monitor,
didDeleteSection: sectionInfo, didDeleteSection: sectionInfo,
fromSectionIndex: sectionIndex fromSectionIndex: sectionIndex
) )
@@ -470,15 +434,18 @@ public final class ManagedObjectListController<T: NSManagedObject> {
} }
/** /**
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. 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 - parameter observer: a `ListObserver` to unregister notifications to
*/ */
public func removeObserver<U: ManagedObjectListChangeObserver where U.EntityType == T>(observer: U) { public func removeObserver<U: ListObserver where U.EntityType == T>(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 let nilValue: AnyObject? = nil
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeList, inObject: observer) setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeList, inObject: observer)
@@ -496,7 +463,7 @@ public final class ManagedObjectListController<T: NSManagedObject> {
// MARK: Internal // MARK: Internal
internal init(dataStack: DataStack, from: From<T>, sectionedBy: SectionedBy?, fetchClauses: [FetchClause]) { internal init(dataStack: DataStack, from: From<T>, sectionBy: SectionBy?, fetchClauses: [FetchClause]) {
let context = dataStack.mainContext let context = dataStack.mainContext
@@ -514,7 +481,7 @@ public final class ManagedObjectListController<T: NSManagedObject> {
let fetchedResultsController = NSFetchedResultsController( let fetchedResultsController = NSFetchedResultsController(
fetchRequest: fetchRequest, fetchRequest: fetchRequest,
managedObjectContext: context, managedObjectContext: context,
sectionNameKeyPath: sectionedBy?.sectionKeyPath, sectionNameKeyPath: sectionBy?.sectionKeyPath,
cacheName: nil cacheName: nil
) )
@@ -524,7 +491,7 @@ public final class ManagedObjectListController<T: NSManagedObject> {
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
self.parentStack = dataStack self.parentStack = dataStack
if let sectionIndexTransformer = sectionedBy?.sectionIndexTransformer { if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
self.sectionIndexTransformer = sectionIndexTransformer self.sectionIndexTransformer = sectionIndexTransformer
} }
@@ -533,17 +500,9 @@ public final class ManagedObjectListController<T: NSManagedObject> {
self.sectionIndexTransformer = { $0 } self.sectionIndexTransformer = { $0 }
} }
fetchedResultsControllerDelegate.handler = self fetchedResultsControllerDelegate.handler = self
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
try! fetchedResultsController.performFetch()
var error: NSError?
if !fetchedResultsController.performFetch(&error) {
CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed to perform fetch on <\(NSFetchedResultsController.self)>.")
}
} }
@@ -554,7 +513,7 @@ public final class ManagedObjectListController<T: NSManagedObject> {
private let sectionIndexTransformer: (sectionName: KeyPath?) -> String? private let sectionIndexTransformer: (sectionName: KeyPath?) -> String?
private weak var parentStack: DataStack? private weak var parentStack: DataStack?
private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController<T>) -> Void) { private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ListMonitor<T>) -> Void) {
setAssociatedRetainedObject( setAssociatedRetainedObject(
NotificationObserver( NotificationObserver(
@@ -564,7 +523,7 @@ public final class ManagedObjectListController<T: NSManagedObject> {
if let strongSelf = self { if let strongSelf = self {
callback(listController: strongSelf) callback(monitor: strongSelf)
} }
} }
), ),
@@ -573,7 +532,7 @@ public final class ManagedObjectListController<T: NSManagedObject> {
) )
} }
private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController<T>, object: T, indexPath: NSIndexPath?, newIndexPath: NSIndexPath?) -> Void) { private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ListMonitor<T>, object: T, indexPath: NSIndexPath?, newIndexPath: NSIndexPath?) -> Void) {
setAssociatedRetainedObject( setAssociatedRetainedObject(
NotificationObserver( NotificationObserver(
@@ -586,7 +545,7 @@ public final class ManagedObjectListController<T: NSManagedObject> {
let object = userInfo[UserInfoKeyObject] as? T { let object = userInfo[UserInfoKeyObject] as? T {
callback( callback(
listController: strongSelf, monitor: strongSelf,
object: object, object: object,
indexPath: userInfo[UserInfoKeyIndexPath] as? NSIndexPath, indexPath: userInfo[UserInfoKeyIndexPath] as? NSIndexPath,
newIndexPath: userInfo[UserInfoKeyNewIndexPath] as? NSIndexPath newIndexPath: userInfo[UserInfoKeyNewIndexPath] as? NSIndexPath
@@ -599,7 +558,7 @@ public final class ManagedObjectListController<T: NSManagedObject> {
) )
} }
private func registerSectionNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController<T>, sectionInfo: NSFetchedResultsSectionInfo, sectionIndex: Int) -> Void) { private func registerSectionNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ListMonitor<T>, sectionInfo: NSFetchedResultsSectionInfo, sectionIndex: Int) -> Void) {
setAssociatedRetainedObject( setAssociatedRetainedObject(
NotificationObserver( NotificationObserver(
@@ -613,7 +572,7 @@ public final class ManagedObjectListController<T: NSManagedObject> {
let sectionIndex = (userInfo[UserInfoKeySectionIndex] as? NSNumber)?.integerValue { let sectionIndex = (userInfo[UserInfoKeySectionIndex] as? NSNumber)?.integerValue {
callback( callback(
listController: strongSelf, monitor: strongSelf,
sectionInfo: sectionInfo, sectionInfo: sectionInfo,
sectionIndex: sectionIndex sectionIndex: sectionIndex
) )
@@ -627,9 +586,9 @@ public final class ManagedObjectListController<T: NSManagedObject> {
} }
// MARK: - ManagedObjectListController: FetchedResultsControllerHandler // MARK: - ListMonitor: FetchedResultsControllerHandler
extension ManagedObjectListController: FetchedResultsControllerHandler { extension ListMonitor: FetchedResultsControllerHandler {
// MARK: FetchedResultsControllerHandler // MARK: FetchedResultsControllerHandler
@@ -639,7 +598,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler {
case .Insert: case .Insert:
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerDidInsertObjectNotification, ListMonitorDidInsertObjectNotification,
object: self, object: self,
userInfo: [ userInfo: [
UserInfoKeyObject: anObject, UserInfoKeyObject: anObject,
@@ -649,7 +608,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler {
case .Delete: case .Delete:
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerDidDeleteObjectNotification, ListMonitorDidDeleteObjectNotification,
object: self, object: self,
userInfo: [ userInfo: [
UserInfoKeyObject: anObject, UserInfoKeyObject: anObject,
@@ -659,7 +618,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler {
case .Update: case .Update:
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerDidUpdateObjectNotification, ListMonitorDidUpdateObjectNotification,
object: self, object: self,
userInfo: [ userInfo: [
UserInfoKeyObject: anObject, UserInfoKeyObject: anObject,
@@ -669,7 +628,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler {
case .Move: case .Move:
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerDidMoveObjectNotification, ListMonitorDidMoveObjectNotification,
object: self, object: self,
userInfo: [ userInfo: [
UserInfoKeyObject: anObject, UserInfoKeyObject: anObject,
@@ -686,7 +645,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler {
case .Insert: case .Insert:
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerDidInsertSectionNotification, ListMonitorDidInsertSectionNotification,
object: self, object: self,
userInfo: [ userInfo: [
UserInfoKeySectionInfo: sectionInfo, UserInfoKeySectionInfo: sectionInfo,
@@ -696,7 +655,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler {
case .Delete: case .Delete:
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerDidDeleteSectionNotification, ListMonitorDidDeleteSectionNotification,
object: self, object: self,
userInfo: [ userInfo: [
UserInfoKeySectionInfo: sectionInfo, UserInfoKeySectionInfo: sectionInfo,
@@ -712,7 +671,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler {
private func controllerWillChangeContent(controller: NSFetchedResultsController) { private func controllerWillChangeContent(controller: NSFetchedResultsController) {
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerWillChangeListNotification, ListMonitorWillChangeListNotification,
object: self object: self
) )
} }
@@ -720,7 +679,7 @@ extension ManagedObjectListController: FetchedResultsControllerHandler {
private func controllerDidChangeContent(controller: NSFetchedResultsController) { private func controllerDidChangeContent(controller: NSFetchedResultsController) {
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerDidChangeListNotification, ListMonitorDidChangeListNotification,
object: self object: self
) )
} }
@@ -750,31 +709,31 @@ private protocol FetchedResultsControllerHandler: class {
// MARK: - FetchedResultsControllerDelegate // MARK: - FetchedResultsControllerDelegate
private final class FetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate { private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: NSFetchedResultsControllerDelegate // MARK: NSFetchedResultsControllerDelegate
@objc func controllerWillChangeContent(controller: NSFetchedResultsController) { @objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.handler?.controllerWillChangeContent(controller) self.handler?.controllerWillChangeContent(controller)
} }
@objc func controllerDidChangeContent(controller: NSFetchedResultsController) { @objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
self.handler?.controllerDidChangeContent(controller) 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) 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) 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) return self.handler?.controller(controller, sectionIndexTitleForSectionName: sectionName)
} }
@@ -799,16 +758,16 @@ private final class FetchedResultsControllerDelegate: NSFetchedResultsController
} }
private let ManagedObjectListControllerWillChangeListNotification = "ManagedObjectListControllerWillChangeListNotification" private let ListMonitorWillChangeListNotification = "ListMonitorWillChangeListNotification"
private let ManagedObjectListControllerDidChangeListNotification = "ManagedObjectListControllerDidChangeListNotification" private let ListMonitorDidChangeListNotification = "ListMonitorDidChangeListNotification"
private let ManagedObjectListControllerDidInsertObjectNotification = "ManagedObjectListControllerDidInsertObjectNotification" private let ListMonitorDidInsertObjectNotification = "ListMonitorDidInsertObjectNotification"
private let ManagedObjectListControllerDidDeleteObjectNotification = "ManagedObjectListControllerDidDeleteObjectNotification" private let ListMonitorDidDeleteObjectNotification = "ListMonitorDidDeleteObjectNotification"
private let ManagedObjectListControllerDidUpdateObjectNotification = "ManagedObjectListControllerDidUpdateObjectNotification" private let ListMonitorDidUpdateObjectNotification = "ListMonitorDidUpdateObjectNotification"
private let ManagedObjectListControllerDidMoveObjectNotification = "ManagedObjectListControllerDidMoveObjectNotification" private let ListMonitorDidMoveObjectNotification = "ListMonitorDidMoveObjectNotification"
private let ManagedObjectListControllerDidInsertSectionNotification = "ManagedObjectListControllerDidInsertSectionNotification" private let ListMonitorDidInsertSectionNotification = "ListMonitorDidInsertSectionNotification"
private let ManagedObjectListControllerDidDeleteSectionNotification = "ManagedObjectListControllerDidDeleteSectionNotification" private let ListMonitorDidDeleteSectionNotification = "ListMonitorDidDeleteSectionNotification"
private let UserInfoKeyObject = "UserInfoKeyObject" private let UserInfoKeyObject = "UserInfoKeyObject"
private let UserInfoKeyIndexPath = "UserInfoKeyIndexPath" private let UserInfoKeyIndexPath = "UserInfoKeyIndexPath"

View File

@@ -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
- parameter monitor: the `ListMonitor` monitoring the list being observed
*/
func listMonitorWillChange(monitor: ListMonitor<EntityType>)
/**
Handles processing right after a change to the observed list occurs
- parameter monitor: the `ListMonitor` monitoring the object being observed
*/
func listMonitorDidChange(monitor: ListMonitor<EntityType>)
}
// 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
- 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<EntityType>, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath)
/**
Notifies that an object was deleted from the specified `NSIndexPath` in the list
- 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<EntityType>, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath)
/**
Notifies that an object at the specified `NSIndexPath` was updated
- 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<EntityType>, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath)
/**
Notifies that an object's index changed
- 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<EntityType>, 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
- 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<EntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
/**
Notifies that a section was inserted at the specified index
- 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<EntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
}

View File

@@ -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<EntityType>)
/**
Handles processing right after a change to the observed list occurs
:param: listController the `ManagedObjectListController` monitoring the object being observed
*/
func managedObjectListDidChange(listController: ManagedObjectListController<EntityType>)
}
// 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<EntityType>, 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<EntityType>, 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<EntityType>, 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<EntityType>, 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<EntityType>, 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<EntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
}

View File

@@ -1,5 +1,5 @@
// //
// ManagedObjectController.swift // ObjectMonitor.swift
// CoreStore // CoreStore
// //
// Copyright (c) 2015 John Rommel Estropia // Copyright (c) 2015 John Rommel Estropia
@@ -28,9 +28,9 @@ import CoreData
import GCDKit import GCDKit
private let ManagedObjectListControllerWillChangeObjectNotification = "ManagedObjectListControllerWillChangeObjectNotification" private let ObjectMonitorWillChangeObjectNotification = "ObjectMonitorWillChangeObjectNotification"
private let ManagedObjectListControllerDidDeleteObjectNotification = "ManagedObjectListControllerDidDeleteObjectNotification" private let ObjectMonitorDidDeleteObjectNotification = "ObjectMonitorDidDeleteObjectNotification"
private let ManagedObjectListControllerDidUpdateObjectNotification = "ManagedObjectListControllerDidUpdateObjectNotification" private let ObjectMonitorDidUpdateObjectNotification = "ObjectMonitorDidUpdateObjectNotification"
private let UserInfoKeyObject = "UserInfoKeyObject" 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) let monitor = CoreStore.monitorObject(object)
objectController.addObserver(self) 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<T: NSManagedObject> { public final class ObjectMonitor<T: NSManagedObject> {
// MARK: Public // MARK: Public
@@ -75,70 +75,73 @@ public final class ManagedObjectController<T: NSManagedObject> {
} }
/** /**
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. 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 - parameter observer: an `ObjectObserver` to send change notifications to
*/ */
public func addObserver<U: ManagedObjectObserver where U.EntityType == T>(observer: U) { public func addObserver<U: ObjectObserver where U.EntityType == T>(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) self.removeObserver(observer)
self.registerChangeNotification( self.registerChangeNotification(
&NotificationKey.willChangeObject, &NotificationKey.willChangeObject,
name: ManagedObjectListControllerWillChangeObjectNotification, name: ObjectMonitorWillChangeObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak self, weak observer] (objectController) -> 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.managedObjectWillUpdate(objectController, object: object) observer.objectMonitor(monitor, willUpdateObject: object)
} }
} }
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didDeleteObject, &NotificationKey.didDeleteObject,
name: ManagedObjectListControllerDidDeleteObjectNotification, name: ObjectMonitorDidDeleteObjectNotification,
toObserver: observer, toObserver: observer,
callback: { [weak self, weak observer] (objectController, object) -> Void in callback: { [weak observer] (monitor, object) -> Void in
if let strongSelf = self, let observer = observer { if let observer = observer {
observer.managedObjectWasDeleted(objectController, object: object) observer.objectMonitor(monitor, didDeleteObject: object)
} }
} }
) )
self.registerObjectNotification( self.registerObjectNotification(
&NotificationKey.didUpdateObject, &NotificationKey.didUpdateObject,
name: ManagedObjectListControllerDidUpdateObjectNotification, name: ObjectMonitorDidUpdateObjectNotification,
toObserver: observer, 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 { if let strongSelf = self, let observer = observer {
let previousCommitedAttributes = strongSelf.lastCommittedAttributes let previousCommitedAttributes = strongSelf.lastCommittedAttributes
let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [NSString: NSObject] let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [String: NSObject]
var changedKeys = Set<String>() let changedKeys = currentCommitedAttributes.keys.reduce(Set<String>()) { (var changedKeys, key) -> Set<String> in
for key in currentCommitedAttributes.keys {
if previousCommitedAttributes[key] != currentCommitedAttributes[key] { if previousCommitedAttributes[key] != currentCommitedAttributes[key] {
changedKeys.insert(key as String) changedKeys.insert(key)
} }
return changedKeys
} }
strongSelf.lastCommittedAttributes = currentCommitedAttributes strongSelf.lastCommittedAttributes = currentCommitedAttributes
observer.managedObjectWasUpdated( observer.objectMonitor(
objectController, monitor,
object: object, didUpdateObject: object,
changedPersistentKeys: changedKeys changedPersistentKeys: changedKeys
) )
} }
@@ -147,15 +150,18 @@ public final class ManagedObjectController<T: NSManagedObject> {
} }
/** /**
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. 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 - parameter observer: an `ObjectObserver` to unregister notifications to
*/ */
public func removeObserver<U: ManagedObjectObserver where U.EntityType == T>(observer: U) { public func removeObserver<U: ObjectObserver where U.EntityType == T>(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 let nilValue: AnyObject? = nil
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeObject, inObject: observer) setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeObject, inObject: observer)
@@ -171,7 +177,8 @@ public final class ManagedObjectController<T: NSManagedObject> {
let context = dataStack.mainContext let context = dataStack.mainContext
let fetchRequest = NSFetchRequest() let fetchRequest = NSFetchRequest()
fetchRequest.entity = context.entityDescriptionForEntityClass(T.self) fetchRequest.entity = object.entity
fetchRequest.fetchLimit = 1 fetchRequest.fetchLimit = 1
fetchRequest.resultType = .ManagedObjectResultType fetchRequest.resultType = .ManagedObjectResultType
fetchRequest.sortDescriptors = [] fetchRequest.sortDescriptors = []
@@ -195,16 +202,9 @@ public final class ManagedObjectController<T: NSManagedObject> {
fetchedResultsControllerDelegate.handler = self fetchedResultsControllerDelegate.handler = self
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
try! fetchedResultsController.performFetch()
var error: NSError? self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [String: NSObject]) ?? [:]
if !fetchedResultsController.performFetch(&error) {
CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed to perform fetch on <\(NSFetchedResultsController.self)>.")
}
self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [NSString: NSObject]) ?? [:]
} }
@@ -213,10 +213,10 @@ public final class ManagedObjectController<T: NSManagedObject> {
private let originalObjectID: NSManagedObjectID private let originalObjectID: NSManagedObjectID
private let fetchedResultsController: NSFetchedResultsController private let fetchedResultsController: NSFetchedResultsController
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
private var lastCommittedAttributes = [NSString: NSObject]() private var lastCommittedAttributes = [String: NSObject]()
private weak var parentStack: DataStack? private weak var parentStack: DataStack?
private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (objectController: ManagedObjectController<T>) -> Void) { private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor<T>) -> Void) {
setAssociatedRetainedObject( setAssociatedRetainedObject(
NotificationObserver( NotificationObserver(
@@ -226,7 +226,7 @@ public final class ManagedObjectController<T: NSManagedObject> {
if let strongSelf = self { if let strongSelf = self {
callback(objectController: strongSelf) callback(monitor: strongSelf)
} }
} }
), ),
@@ -235,7 +235,7 @@ public final class ManagedObjectController<T: NSManagedObject> {
) )
} }
private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (objectController: ManagedObjectController<T>, object: T) -> Void) { private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor<T>, object: T) -> Void) {
setAssociatedRetainedObject( setAssociatedRetainedObject(
NotificationObserver( NotificationObserver(
@@ -248,7 +248,7 @@ public final class ManagedObjectController<T: NSManagedObject> {
let object = userInfo[UserInfoKeyObject] as? T { let object = userInfo[UserInfoKeyObject] as? T {
callback( callback(
objectController: strongSelf, monitor: strongSelf,
object: object object: object
) )
} }
@@ -261,9 +261,9 @@ public final class ManagedObjectController<T: NSManagedObject> {
} }
// MARK: - ManagedObjectController: FetchedResultsControllerHandler // MARK: - ObjectMonitor: FetchedResultsControllerHandler
extension ManagedObjectController: FetchedResultsControllerHandler { extension ObjectMonitor: FetchedResultsControllerHandler {
// MARK: FetchedResultsControllerHandler // MARK: FetchedResultsControllerHandler
@@ -273,14 +273,14 @@ extension ManagedObjectController: FetchedResultsControllerHandler {
case .Delete: case .Delete:
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerDidDeleteObjectNotification, ObjectMonitorDidDeleteObjectNotification,
object: self, object: self,
userInfo: [UserInfoKeyObject: anObject] userInfo: [UserInfoKeyObject: anObject]
) )
case .Update: case .Update:
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerDidUpdateObjectNotification, ObjectMonitorDidUpdateObjectNotification,
object: self, object: self,
userInfo: [UserInfoKeyObject: anObject] userInfo: [UserInfoKeyObject: anObject]
) )
@@ -293,7 +293,7 @@ extension ManagedObjectController: FetchedResultsControllerHandler {
private func controllerWillChangeContent(controller: NSFetchedResultsController) { private func controllerWillChangeContent(controller: NSFetchedResultsController) {
NSNotificationCenter.defaultCenter().postNotificationName( NSNotificationCenter.defaultCenter().postNotificationName(
ManagedObjectListControllerWillChangeObjectNotification, ObjectMonitorWillChangeObjectNotification,
object: self object: self
) )
} }
@@ -312,16 +312,16 @@ private protocol FetchedResultsControllerHandler: class {
// MARK: - FetchedResultsControllerDelegate // MARK: - FetchedResultsControllerDelegate
private final class FetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate { private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: NSFetchedResultsControllerDelegate // MARK: NSFetchedResultsControllerDelegate
@objc func controllerWillChangeContent(controller: NSFetchedResultsController) { @objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.handler?.controllerWillChangeContent(controller) 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) self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath)
} }

View File

@@ -1,5 +1,5 @@
// //
// ManagedObjectObserver.swift // ObjectObserver.swift
// CoreStore // CoreStore
// //
// Copyright (c) 2015 John Rommel Estropia // Copyright (c) 2015 John Rommel Estropia
@@ -27,15 +27,15 @@ import Foundation
import CoreData 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) let monitor = CoreStore.monitorObject(object)
objectController.addObserver(self) monitor.addObserver(self)
*/ */
public protocol ManagedObjectObserver: class { public protocol ObjectObserver: class {
/** /**
The `NSManagedObject` type for the observed object 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 Handles processing just before a change to the observed `object` occurs
:param: objectController the `ManagedObjectController` monitoring the object being observed - parameter monitor: the `ObjectMonitor` monitoring the object being observed
:param: object the `NSManagedObject` instance being observed - parameter object: the `NSManagedObject` instance being observed
*/ */
func managedObjectWillUpdate(objectController: ManagedObjectController<EntityType>, object: EntityType) func objectMonitor(monitor: ObjectMonitor<EntityType>, willUpdateObject object: EntityType)
/** /**
Handles processing right after a change to the observed `object` occurs Handles processing right after a change to the observed `object` occurs
:param: objectController the `ManagedObjectController` monitoring the object being observed - parameter monitor: the `ObjectMonitor` monitoring the object being observed
:param: object the `NSManagedObject` instance being observed - parameter 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 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<EntityType>, object: EntityType, changedPersistentKeys: Set<KeyPath>) func objectMonitor(monitor: ObjectMonitor<EntityType>, didUpdateObject object: EntityType, changedPersistentKeys: Set<KeyPath>)
/** /**
Handles processing right after `object` is deleted Handles processing right after `object` is deleted
:param: objectController the `ManagedObjectController` monitoring the object being observed - parameter monitor: the `ObjectMonitor` monitoring the object being observed
:param: object the `NSManagedObject` instance being observed - parameter object: the `NSManagedObject` instance being observed
*/ */
func managedObjectWasDeleted(objectController: ManagedObjectController<EntityType>, object: EntityType) func objectMonitor(monitor: ObjectMonitor<EntityType>, didDeleteObject object: EntityType)
} }

View File

@@ -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
- parameter 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
- 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?) {
self.sectionKeyPath = sectionKeyPath
self.sectionIndexTransformer = sectionIndexTransformer
}
// MARK: Internal
internal let sectionKeyPath: KeyPath
internal let sectionIndexTransformer: (sectionName: KeyPath?) -> String?
}

View File

@@ -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. 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) { public func commit(completion: (result: SaveResult) -> Void) {
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue.") CoreStore.assert(
CoreStore.assert(!self.isCommitted, "Attempted to commit a \(typeName(self)) more than once.") 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.isCommitted = true
let semaphore = GCDSemaphore(0) let semaphore = GCDSemaphore(0)
@@ -63,8 +69,14 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
*/ */
public func commit() { public func commit() {
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue.") CoreStore.assert(
CoreStore.assert(!self.isCommitted, "Attempted to commit a \(typeName(self)) more than once.") 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.isCommitted = true
self.result = self.context.saveSynchronously() 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. 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`. - 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 - 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? { 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(
CoreStore.assert(!self.isCommitted, "Attempted to begin a child transaction from an already committed \(typeName(self)).") 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( return SynchronousDataTransaction(
mainContext: self.context, mainContext: self.context,
@@ -93,12 +111,15 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
/** /**
Creates a new `NSManagedObject` with the specified entity type. Creates a new `NSManagedObject` with the specified entity type.
:param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration - 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. - returns: a new `NSManagedObject` instance of the specified entity type.
*/ */
public override func create<T: NSManagedObject>(into: Into<T>) -> T { public override func create<T: NSManagedObject>(into: Into<T>) -> 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) 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. 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 - parameter object: the `NSManagedObject` type to be edited
:returns: an editable proxy for the specified `NSManagedObject`. - returns: an editable proxy for the specified `NSManagedObject`.
*/ */
public override func edit<T: NSManagedObject>(object: T?) -> T? { public override func edit<T: NSManagedObject>(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) 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. 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 - parameter into: an `Into` clause specifying the entity type
:param: objectID the `NSManagedObjectID` for the object to be edited - parameter objectID: the `NSManagedObjectID` for the object to be edited
:returns: an editable proxy for the specified `NSManagedObject`. - returns: an editable proxy for the specified `NSManagedObject`.
*/ */
public override func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? { public override func edit<T: NSManagedObject>(into: Into<T>, _ 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) return super.edit(into, objectID)
} }
@@ -133,37 +160,46 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
/** /**
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once. 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?) { 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) super.delete(object)
} }
/** /**
Deletes the specified `NSManagedObject`'s. Deletes the specified `NSManagedObject`s.
:param: object1 the `NSManagedObject` type to be deleted - parameter object1: the `NSManagedObject` type to be deleted
:param: object2 another `NSManagedObject` type to be deleted - parameter object2: another `NSManagedObject` type to be deleted
:param: objects other `NSManagedObject`s type to be deleted - parameter objects: other `NSManagedObject`s type to be deleted
*/ */
public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) { 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) super.delete([object1, object2] + objects)
} }
/** /**
Deletes the specified `NSManagedObject`'s. 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?]) { 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) super.delete(objects)
} }
@@ -173,7 +209,10 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
*/ */
public override func rollback() { 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() super.rollback()
} }
@@ -195,7 +234,10 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
self.closure(transaction: self) self.closure(transaction: self)
if !self.isCommitted && self.hasChanges { 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.transactionQueue.sync {
self.closure(transaction: self) self.closure(transaction: self)
if !self.isCommitted && self.hasChanges { 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 return self.result

View File

@@ -28,91 +28,6 @@ import CoreData
import GCDKit 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<MyPersonEntity>("Configuration1"))
This helps the `NSManagedObjectContext` to determine which
*/
public struct Into<T: NSManagedObject> {
// MARK: Public
internal static var defaultConfigurationName: String {
return "PF_DEFAULT_CONFIGURATION_NAME"
}
/**
Initializes an `Into` clause.
Sample Usage:
let person = transaction.create(Into<MyPersonEntity>())
*/
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<MyPersonEntity>("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 // MARK: - BaseDataTransaction
/** /**
@@ -133,61 +48,68 @@ public /*abstract*/ class BaseDataTransaction {
/** /**
Creates a new `NSManagedObject` with the specified entity type. Creates a new `NSManagedObject` with the specified entity type.
:param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration - 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. - returns: a new `NSManagedObject` instance of the specified entity type.
*/ */
public func create<T: NSManagedObject>(into: Into<T>) -> T { public func create<T: NSManagedObject>(into: Into<T>) -> 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 context = self.context
let object = T.createInContext(context) let entityClass = (into.entityClass as! NSManagedObject.Type)
if into.inferStoreIfPossible { 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), _): case (let persistentStore?, _):
let object = entityClass.createInContext(context) as! T
context.assignObject(object, toPersistentStore: persistentStore) context.assignObject(object, toPersistentStore: persistentStore)
return object
case (.None, true): 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.") fatalError("Attempted to create an entity of type \(typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.")
default: 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.") fatalError("Attempted to create an entity of type \(typeName(entityClass)), but a destination persistent store containing the entity type could not be found.")
} }
} }
else { 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), _): case (let persistentStore?, _):
let object = entityClass.createInContext(context) as! T
context.assignObject(object, toPersistentStore: persistentStore) context.assignObject(object, toPersistentStore: persistentStore)
return object
default: default:
if let configuration = into.configuration { 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.") fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the configuration \"\(configuration)\", which it doesn't belong to.")
} }
else { else {
CoreStore.assert(false, "Attempted to create an entity of type \(typeName(object)) into the default configuration, which it doesn't belong to.") 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`. Returns an editable proxy of a specified `NSManagedObject`.
:param: object the `NSManagedObject` type to be edited - parameter object: the `NSManagedObject` type to be edited
:returns: an editable proxy for the specified `NSManagedObject`. - returns: an editable proxy for the specified `NSManagedObject`.
*/ */
public func edit<T: NSManagedObject>(object: T?) -> T? { public func edit<T: NSManagedObject>(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) return object?.inContext(self.context)
} }
@@ -195,36 +117,46 @@ public /*abstract*/ class BaseDataTransaction {
/** /**
Returns an editable proxy of the object with the specified `NSManagedObjectID`. Returns an editable proxy of the object with the specified `NSManagedObjectID`.
:param: into an `Into` clause specifying the entity type - parameter into: an `Into` clause specifying the entity type
:param: objectID the `NSManagedObjectID` for the object to be edited - parameter objectID: the `NSManagedObjectID` for the object to be edited
:returns: an editable proxy for the specified `NSManagedObject`. - returns: an editable proxy for the specified `NSManagedObject`.
*/ */
public func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? { public func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(T.self)> outside its designated queue.") CoreStore.assert(
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`.") 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 T.inContext(self.context, withObjectID: objectID) return (into.entityClass as! NSManagedObject.Type).inContext(self.context, withObjectID: objectID) as? T
} }
/** /**
Deletes a specified `NSManagedObject`. Deletes a specified `NSManagedObject`.
:param: object the `NSManagedObject` type to be deleted - parameter object: the `NSManagedObject` to be deleted
*/ */
public func delete(object: NSManagedObject?) { 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() object?.inContext(self.context)?.deleteFromContext()
} }
/** /**
Deletes the specified `NSManagedObject`'s. Deletes the specified `NSManagedObject`s.
:param: object1 the `NSManagedObject` type to be deleted - parameter object1: the `NSManagedObject` to be deleted
:param: object2 another `NSManagedObject` type to be deleted - parameter object2: another `NSManagedObject` to be deleted
:param: objects other `NSManagedObject`s type to be deleted - parameter objects: other `NSManagedObject`s to be deleted
*/ */
public func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) { public func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
@@ -232,13 +164,16 @@ public /*abstract*/ class BaseDataTransaction {
} }
/** /**
Deletes the specified `NSManagedObject`'s. Deletes the specified `NSManagedObject`s.
:param: objects the `NSManagedObject`'s type to be deleted - parameter objects: the `NSManagedObject`s to be deleted
*/ */
public func delete(objects: [NSManagedObject?]) { 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 let context = self.context
for object in objects { for object in objects {
@@ -254,7 +189,10 @@ public /*abstract*/ class BaseDataTransaction {
*/ */
public func rollback() { 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() self.context.reset()
} }

View File

@@ -35,7 +35,7 @@ public extension CoreStore {
/** /**
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. 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) { 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. 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`.
:returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously - 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? { 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. 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 { public static func beginDetached() -> DetachedDataTransaction {

View File

@@ -37,7 +37,7 @@ public extension DataStack {
/** /**
Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made. 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) { public func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) {
@@ -50,8 +50,8 @@ public extension DataStack {
/** /**
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. 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`. - 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 - 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? { public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
@@ -64,7 +64,7 @@ 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. 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 { public func beginDetached() -> DetachedDataTransaction {

View File

@@ -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. 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) { 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 self.context.saveAsynchronouslyWithCompletion { (result) -> Void in

View File

@@ -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<MyPersonEntity>("Configuration1"))
This helps the `NSManagedObjectContext` to determine which
*/
public struct Into<T: NSManagedObject> {
// MARK: Public
/**
Initializes an `Into` clause.
Sample Usage:
let person = transaction.create(Into<MyPersonEntity>())
*/
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))
- parameter 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.
- parameter 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<MyPersonEntity>("Configuration1"))
- 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?) {
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"))
- 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?) {
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"))
- 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?) {
self.configuration = configuration
self.inferStoreIfPossible = false
self.entityClass = entityClass
}
// MARK: Internal
internal static var defaultConfigurationName: String {
return "PF_DEFAULT_CONFIGURATION_NAME"
}
internal let entityClass: AnyClass
internal let configuration: String?
internal let inferStoreIfPossible: Bool
}

View File

@@ -91,9 +91,7 @@ public enum SaveResult {
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) { internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(NSError( self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
coreStoreErrorCode: errorCode,
userInfo: userInfo))
} }
} }

View File

@@ -42,8 +42,14 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
*/ */
public func commit() { public func commit() {
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue.") CoreStore.assert(
CoreStore.assert(!self.isCommitted, "Attempted to commit a \(typeName(self)) more than once.") 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.isCommitted = true
self.result = self.context.saveSynchronously() 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. 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`. - 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 - 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? { 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(
CoreStore.assert(!self.isCommitted, "Attempted to begin a child transaction from an already committed \(typeName(self)).") 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( return SynchronousDataTransaction(
mainContext: self.context, mainContext: self.context,
@@ -72,12 +84,15 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
/** /**
Creates a new `NSManagedObject` with the specified entity type. Creates a new `NSManagedObject` with the specified entity type.
:param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration - 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. - returns: a new `NSManagedObject` instance of the specified entity type.
*/ */
public override func create<T: NSManagedObject>(into: Into<T>) -> T { public override func create<T: NSManagedObject>(into: Into<T>) -> 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) 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. 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 - parameter object: the `NSManagedObject` type to be edited
:returns: an editable proxy for the specified `NSManagedObject`. - returns: an editable proxy for the specified `NSManagedObject`.
*/ */
public override func edit<T: NSManagedObject>(object: T?) -> T? { public override func edit<T: NSManagedObject>(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) 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. 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 - parameter into: an `Into` clause specifying the entity type
:param: objectID the `NSManagedObjectID` for the object to be edited - parameter objectID: the `NSManagedObjectID` for the object to be edited
:returns: an editable proxy for the specified `NSManagedObject`. - returns: an editable proxy for the specified `NSManagedObject`.
*/ */
public override func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? { public override func edit<T: NSManagedObject>(into: Into<T>, _ 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) return super.edit(into, objectID)
} }
@@ -112,37 +133,46 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
/** /**
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once. 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?) { 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) super.delete(object)
} }
/** /**
Deletes the specified `NSManagedObject`'s. Deletes the specified `NSManagedObject`s.
:param: object1 the `NSManagedObject` type to be deleted - parameter object1: the `NSManagedObject` to be deleted
:param: object2 another `NSManagedObject` type to be deleted - parameter object2: another `NSManagedObject` to be deleted
:param: objects other `NSManagedObject`s type to be deleted - parameter objects: other `NSManagedObject`s to be deleted
*/ */
public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) { 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) super.delete([object1, object2] + objects)
} }
/** /**
Deletes the specified `NSManagedObject`'s. Deletes the specified `NSManagedObject`s.
:param: objects the `NSManagedObject`'s type to be deleted - parameter objects: the `NSManagedObject`s to be deleted
*/ */
public override func delete(objects: [NSManagedObject?]) { 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) super.delete(objects)
} }
@@ -152,7 +182,10 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
*/ */
public override func rollback() { 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() super.rollback()
} }
@@ -165,9 +198,13 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
self.transactionQueue.sync { self.transactionQueue.sync {
self.closure(transaction: self) self.closure(transaction: self)
if !self.isCommitted && self.hasChanges { 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 return self.result

View File

@@ -35,27 +35,27 @@ public extension CoreStore {
/** /**
Adds an in-memory store to the `defaultStack`. Adds an in-memory store to the `defaultStack`.
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil. - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
:returns: a `PersistentStoreResult` indicating success or failure. - returns: the `NSPersistentStore` added to the stack.
*/ */
public static func addInMemoryStore(configuration: String? = nil) -> PersistentStoreResult { public static func addInMemoryStore(configuration configuration: String? = nil) throws -> NSPersistentStore {
return self.defaultStack.addInMemoryStore(configuration: configuration) return try self.defaultStack.addInMemoryStore(configuration: configuration)
} }
/** /**
Adds to the `defaultStack` an SQLite store from the given SQLite file name. 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. - 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.
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil. - parameter 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. - parameter 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 - 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. - returns: the `NSPersistentStore` added to the stack.
*/ */
public static func addSQLiteStoreAndWait(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { public static func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) throws -> NSPersistentStore {
return self.defaultStack.addSQLiteStoreAndWait( return try self.defaultStack.addSQLiteStoreAndWait(
fileName, fileName: fileName,
configuration: configuration, configuration: configuration,
automigrating: automigrating, automigrating: automigrating,
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure resetStoreOnMigrationFailure: resetStoreOnMigrationFailure
@@ -65,20 +65,19 @@ public extension CoreStore {
/** /**
Adds to the `defaultStack` an SQLite store from the given SQLite file URL. 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 "<Application name>.sqlite" file in the "Application Support" directory. - 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 "<Application name>.sqlite" file in the "Application Support" directory.
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil. - parameter 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. - parameter 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. - 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. - returns: the `NSPersistentStore` added to the stack.
*/ */
public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) throws -> NSPersistentStore {
return self.defaultStack.addSQLiteStoreAndWait( return try self.defaultStack.addSQLiteStoreAndWait(
fileURL: fileURL, fileURL: fileURL,
configuration: configuration, configuration: configuration,
automigrating: automigrating, automigrating: automigrating,
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure resetStoreOnMigrationFailure: resetStoreOnMigrationFailure
) )
} }
} }

View File

@@ -28,9 +28,9 @@ import CoreData
import GCDKit 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") internal let defaultSQLiteStoreURL = applicationSupportDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite")
@@ -44,117 +44,98 @@ public final class DataStack {
// MARK: Public // 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 the specified model name.
:param: modelName the name of the (.xcdatamodeld) model file.
*/
public convenience init(modelName: String) {
let modelFilePath: String! = NSBundle.mainBundle().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`. Initializes a `DataStack` from an `NSManagedObjectModel`.
:param: managedObjectModel the `NSManagedObjectModel` of the (.xcdatamodeld) model file. - 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 sequence of model versions to be used as the order for incremental migration. If not specified, will default to a non-migrating data stack.
*/ */
public required init(managedObjectModel: NSManagedObjectModel) { public required init(modelName: String = applicationName, bundle: NSBundle = NSBundle.mainBundle(), migrationChain: MigrationChain = nil) {
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel) CoreStore.assert(
migrationChain.valid,
"Invalid migration chain passed to the \(typeName(DataStack)). Check that the model versions' order is correct and that no repetitions or ambiguities exist."
)
let model = NSManagedObjectModel.fromBundle(
bundle,
modelName: modelName,
modelVersionHints: migrationChain.leafVersions
)
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator) self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext) self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
self.model = model
var entityNameMapping = [EntityClassNameType: EntityNameType]() self.migrationChain = migrationChain
var entityConfigurationsMapping = [EntityClassNameType: Set<String>]()
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.rootSavingContext.parentStack = self self.rootSavingContext.parentStack = self
} }
/**
Returns the `DataStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
*/
public var modelVersion: String {
return self.model.currentModelVersion!
}
/** /**
Adds an in-memory store to the stack. Adds an in-memory store to the stack.
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil. - parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
:returns: a `PersistentStoreResult` indicating success or failure. - returns: the `NSPersistentStore` added to the stack.
*/ */
public func addInMemoryStore(configuration: String? = nil) -> PersistentStoreResult { public func addInMemoryStore(configuration configuration: String? = nil) throws -> NSPersistentStore {
let coordinator = self.coordinator; let coordinator = self.coordinator;
var error: NSError?
var store: NSPersistentStore? var store: NSPersistentStore?
var storeError: NSError?
coordinator.performBlockAndWait { coordinator.performBlockAndWait {
store = coordinator.addPersistentStoreWithType( do {
NSInMemoryStoreType,
configuration: configuration, store = try coordinator.addPersistentStoreWithType(
URL: nil, NSInMemoryStoreType,
options: nil, configuration: configuration,
error: &error) URL: nil,
options: nil
)
}
catch {
storeError = error as NSError
}
} }
if let store = store { if let store = store {
self.updateMetadataForPersistentStore(store) self.updateMetadataForPersistentStore(store)
return PersistentStoreResult(store) return store
} }
if let error = error { let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
CoreStore.handleError(
CoreStore.handleError( error,
error, "Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
"Failed to add in-memory <\(NSPersistentStore.self)>.") )
return PersistentStoreResult(error) throw error
}
else {
CoreStore.handleError(
NSError(coreStoreErrorCode: .UnknownError),
"Failed to add in-memory <\(NSPersistentStore.self)>.")
return PersistentStoreResult(.UnknownError)
}
} }
/** /**
Adds to the stack an SQLite store from the given SQLite file name. 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. - 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.
: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. - 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.
:param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. - parameter 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 - 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. - returns: the `NSPersistentStore` added to the stack.
*/ */
public func addSQLiteStoreAndWait(fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult { public func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) throws -> NSPersistentStore {
return self.addSQLiteStoreAndWait( return try self.addSQLiteStoreAndWait(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent( fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileName, fileName,
isDirectory: false isDirectory: false
@@ -168,68 +149,81 @@ public final class DataStack {
/** /**
Adds to the stack an SQLite store from the given SQLite file URL. 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 "<Application name>.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 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 "<Application name>.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. - 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.
:param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true. - parameter 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. - 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. - returns: the `NSPersistentStore` added to the stack.
*/ */
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) throws -> NSPersistentStore {
CoreStore.assert(
fileURL.fileURL,
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
)
let coordinator = self.coordinator; let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) { 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 if store.type == NSSQLiteStoreType
&& isExistingStoreAutomigrating == automigrating && isExistingStoreAutomigrating == automigrating
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) { && store.configurationName == (configuration ?? Into.defaultConfigurationName) {
return PersistentStoreResult(store) return store
} }
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError( CoreStore.handleError(
NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL), error,
"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) throw error
} }
let fileManager = NSFileManager.defaultManager() let fileManager = NSFileManager.defaultManager()
var directoryError: NSError? do {
if !fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil,
error: &directoryError) {
CoreStore.handleError( try fileManager.createDirectoryAtURL(
directoryError ?? NSError(coreStoreErrorCode: .UnknownError), fileURL.URLByDeletingLastPathComponent!,
"Failed to create directory for SQLite store at \"\(fileURL)\".") withIntermediateDirectories: true,
return PersistentStoreResult(directoryError!) attributes: nil
)
} }
catch _ { }
var store: NSPersistentStore? var store: NSPersistentStore?
var persistentStoreError: NSError? var storeError: NSError?
coordinator.performBlockAndWait { coordinator.performBlockAndWait {
store = coordinator.addPersistentStoreWithType( do {
NSSQLiteStoreType,
configuration: configuration, store = try coordinator.addPersistentStoreWithType(
URL: fileURL, NSSQLiteStoreType,
options: [NSSQLitePragmasOption: ["WAL": "journal_mode"], configuration: configuration,
NSInferMappingModelAutomaticallyOption: true, URL: fileURL,
NSMigratePersistentStoresAutomaticallyOption: automigrating], options: [
error: &persistentStoreError) NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSInferMappingModelAutomaticallyOption: true,
NSMigratePersistentStoresAutomaticallyOption: automigrating
]
)
}
catch {
storeError = error as NSError
}
} }
if let store = store { if let store = store {
self.updateMetadataForPersistentStore(store) self.updateMetadataForPersistentStore(store)
return PersistentStoreResult(store) return store
} }
if let error = persistentStoreError if let error = storeError
where ( where (
resetStoreOnMigrationFailure resetStoreOnMigrationFailure
&& (error.code == NSPersistentStoreIncompatibleVersionHashError && (error.code == NSPersistentStoreIncompatibleVersionHashError
@@ -238,39 +232,59 @@ public final class DataStack {
&& error.domain == NSCocoaErrorDomain && error.domain == NSCocoaErrorDomain
) { ) {
fileManager.removeItemAtURL(fileURL, error: nil) do {
fileManager.removeItemAtPath(
fileURL.path!.stringByAppendingString("-shm"), try fileManager.removeItemAtURL(fileURL)
error: nil) }
fileManager.removeItemAtPath( catch _ { }
fileURL.path!.stringByAppendingString("-wal"),
error: nil) do {
try fileManager.removeItemAtPath(fileURL.path!.stringByAppendingString("-shm"))
}
catch _ { }
do {
try fileManager.removeItemAtPath(fileURL.path!.stringByAppendingString("-wal"))
}
catch _ { }
var store: NSPersistentStore? var store: NSPersistentStore?
coordinator.performBlockAndWait { coordinator.performBlockAndWait {
store = coordinator.addPersistentStoreWithType( do {
NSSQLiteStoreType,
configuration: configuration, store = try coordinator.addPersistentStoreWithType(
URL: fileURL, NSSQLiteStoreType,
options: [NSSQLitePragmasOption: ["WAL": "journal_mode"], configuration: configuration,
NSInferMappingModelAutomaticallyOption: true, URL: fileURL,
NSMigratePersistentStoresAutomaticallyOption: automigrating], options: [
error: &persistentStoreError) NSSQLitePragmasOption: ["journal_mode": "WAL"],
NSInferMappingModelAutomaticallyOption: true,
NSMigratePersistentStoresAutomaticallyOption: automigrating
]
)
}
catch {
storeError = error as NSError
}
} }
if let store = store { if let store = store {
self.updateMetadataForPersistentStore(store) self.updateMetadataForPersistentStore(store)
return PersistentStoreResult(store) return store
} }
} }
let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
CoreStore.handleError( CoreStore.handleError(
persistentStoreError ?? NSError(coreStoreErrorCode: .UnknownError), error,
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".") "Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\"."
)
return PersistentStoreResult(.UnknownError) throw error
} }
@@ -279,28 +293,38 @@ public final class DataStack {
internal let coordinator: NSPersistentStoreCoordinator internal let coordinator: NSPersistentStoreCoordinator
internal let rootSavingContext: NSManagedObjectContext internal let rootSavingContext: NSManagedObjectContext
internal let mainContext: NSManagedObjectContext internal let mainContext: NSManagedObjectContext
internal let model: NSManagedObjectModel
internal let migrationChain: MigrationChain
internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue") internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue")
internal let migrationQueue: NSOperationQueue = {
internal func entityNameForEntityClass(entityClass: NSManagedObject.Type) -> String? { 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
}()
return self.entityNameMapping[NSStringFromClass(entityClass)] internal func entityNameForEntityClass(entityClass: AnyClass) -> String? {
return self.model.entityNameForClass(entityClass)
} }
internal func persistentStoresForEntityClass(entityClass: NSManagedObject.Type) -> [NSPersistentStore]? { internal func persistentStoresForEntityClass(entityClass: AnyClass) -> [NSPersistentStore]? {
var returnValue: [NSPersistentStore]? = nil var returnValue: [NSPersistentStore]? = nil
self.storeMetadataUpdateQueue.barrierSync { self.storeMetadataUpdateQueue.barrierSync {
let configurationsForEntity = self.entityConfigurationsMapping[NSStringFromClass(entityClass)] ?? [] returnValue = self.entityConfigurationsMapping[NSStringFromClass(entityClass)]?.map {
returnValue = map(configurationsForEntity) {
return self.configurationStoreMapping[$0]! return self.configurationStoreMapping[$0]!
} } ?? []
} }
return returnValue 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) var returnValue: (store: NSPersistentStore?, isAmbiguous: Bool) = (store: nil, isAmbiguous: false)
self.storeMetadataUpdateQueue.barrierSync { self.storeMetadataUpdateQueue.barrierSync {
@@ -340,8 +364,12 @@ public final class DataStack {
let configurationName = persistentStore.configurationName let configurationName = persistentStore.configurationName
self.configurationStoreMapping[configurationName] = persistentStore 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) self.entityConfigurationsMapping[entityDescription.managedObjectClassName]?.insert(configurationName)
} }
} }
@@ -350,12 +378,19 @@ public final class DataStack {
// MARK: Private // 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 let storeMetadataUpdateQueue = GCDQueue.createConcurrent("com.coreStore.persistentStoreBarrierQueue")
private var configurationStoreMapping = [ConfigurationNameType: NSPersistentStore]() private var configurationStoreMapping = [String: NSPersistentStore]()
private var entityConfigurationsMapping = [EntityClassNameType: Set<String>]() private var entityConfigurationsMapping = [String: Set<String>]()
deinit {
for store in self.coordinator.persistentStores {
do {
try self.coordinator.removePersistentStore(store)
}
catch _ { }
}
}
} }

View File

@@ -30,26 +30,28 @@ import CoreData
// MARK: - PersistentStoreResult // MARK: - PersistentStoreResult
/** /**
The `PersistentStoreResult` indicates the result of initializing the persistent store. The `PersistentStoreResult` indicates the result of an asynchronous initialization of a persistent store.
The `PersistentStoreResult` can be treated as a boolean: The `PersistentStoreResult` can be treated as a boolean:
let result = CoreStore.addSQLiteStoreAndWait() try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
if result { if result {
// succeeded // succeeded
} }
else { else {
// failed // failed
} }
})
or as an `enum`, where the resulting associated object can also be inspected: or as an `enum`, where the resulting associated object can also be inspected:
let result = CoreStore.addSQLiteStoreAndWait() try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
switch result { switch result {
case .Success(let persistentStore): case .Success(let persistentStore):
// persistentStore is the related NSPersistentStore instance // persistentStore is the related NSPersistentStore instance
case .Failure(let error): case .Failure(let error):
// error is the NSError instance for the failure // error is the NSError instance for the failure
} }
})
``` ```
*/ */
public enum PersistentStoreResult { public enum PersistentStoreResult {

View File

@@ -7,10 +7,12 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */; };
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADD1AFDC71700F90881 /* Palette.swift */; }; B503FAE11AFDC71700F90881 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADD1AFDC71700F90881 /* Palette.swift */; };
B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */; }; B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */; };
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C111B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel */; };
B5125C141B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C131B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel */; };
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977D81B120B80003D50A5 /* ObserversViewController.swift */; }; B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977D81B120B80003D50A5 /* ObserversViewController.swift */; };
B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */; }; B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */; };
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52977DE1B120F83003D50A5 /* MapKit.framework */; }; B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52977DE1B120F83003D50A5 /* MapKit.framework */; };
@@ -21,21 +23,28 @@
B54AAD591AF4D26E00848AE0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD571AF4D26E00848AE0 /* Main.storyboard */; }; B54AAD591AF4D26E00848AE0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD571AF4D26E00848AE0 /* Main.storyboard */; };
B54AAD5B1AF4D26E00848AE0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */; }; B54AAD5B1AF4D26E00848AE0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */; };
B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; }; B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; };
B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */; };
B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; }; B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; };
B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; }; B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; };
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */; }; B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */; };
B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */; }; B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */; };
B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D91B231BCA0075EE4A /* MaleAccount.swift */; }; B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D91B231BCA0075EE4A /* MaleAccount.swift */; };
B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964DB1B231BCB0075EE4A /* FemaleAccount.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 */; }; B569650C1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */; };
B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965171B2E20CC0075EE4A /* TimeZone.swift */; }; B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965171B2E20CC0075EE4A /* TimeZone.swift */; };
B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; }; B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; };
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.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 */; }; 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, ); }; }; 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 */; }; 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, ); }; }; B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */; };
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 */ /* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */ /* Begin PBXContainerItemProxy section */
@@ -85,10 +94,12 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
B503FADB1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectListObserverDemoViewController.swift; sourceTree = "<group>"; }; B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListObserverDemoViewController.swift; sourceTree = "<group>"; };
B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserverDemoViewController.swift; sourceTree = "<group>"; }; B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserverDemoViewController.swift; sourceTree = "<group>"; };
B503FADD1AFDC71700F90881 /* Palette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palette.swift; sourceTree = "<group>"; }; B503FADD1AFDC71700F90881 /* Palette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palette.swift; sourceTree = "<group>"; };
B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaletteTableViewCell.swift; sourceTree = "<group>"; }; B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaletteTableViewCell.swift; sourceTree = "<group>"; };
B5125C111B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = OrganismV2ToV3.xcmappingmodel; sourceTree = "<group>"; };
B5125C131B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = OrganismV3ToV2.xcmappingmodel; sourceTree = "<group>"; };
B52977D81B120B80003D50A5 /* ObserversViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserversViewController.swift; sourceTree = "<group>"; }; B52977D81B120B80003D50A5 /* ObserversViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserversViewController.swift; sourceTree = "<group>"; };
B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsDemoViewController.swift; sourceTree = "<group>"; }; B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsDemoViewController.swift; sourceTree = "<group>"; };
B52977DE1B120F83003D50A5 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; }; B52977DE1B120F83003D50A5 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; };
@@ -101,20 +112,28 @@
B54AAD581AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; }; B54AAD581AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; }; B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; }; B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV2ToV3MigrationPolicy.swift; sourceTree = "<group>"; };
B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = "<group>"; }; B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = "<group>"; };
B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = "<group>"; }; B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = "<group>"; };
B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = "<group>"; }; B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = "<group>"; };
B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = StackSetupDemo.xcdatamodel; sourceTree = "<group>"; }; B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = StackSetupDemo.xcdatamodel; sourceTree = "<group>"; };
B56964D91B231BCA0075EE4A /* MaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MaleAccount.swift; sourceTree = "<group>"; }; B56964D91B231BCA0075EE4A /* MaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MaleAccount.swift; sourceTree = "<group>"; };
B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FemaleAccount.swift; sourceTree = "<group>"; }; B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FemaleAccount.swift; sourceTree = "<group>"; };
B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = "<group>"; };
B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingAndQueryingDemoViewController.swift; sourceTree = "<group>"; }; B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingAndQueryingDemoViewController.swift; sourceTree = "<group>"; };
B56965171B2E20CC0075EE4A /* TimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = "<group>"; }; B56965171B2E20CC0075EE4A /* TimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = "<group>"; };
B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingResultsViewController.swift; sourceTree = "<group>"; }; B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingResultsViewController.swift; sourceTree = "<group>"; };
B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = "<group>"; }; B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = "<group>"; };
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreStore.xcodeproj; path = ../CoreStore.xcodeproj; sourceTree = "<group>"; }; B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreStore.xcodeproj; path = ../CoreStore.xcodeproj; sourceTree = "<group>"; };
B5D9C9181B20AB1900E64F0E /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; }; B5D9C9181B20AB1900E64F0E /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OrganismTableViewCell.swift; path = "CoreStoreDemo/MIgrations Demo/OrganismTableViewCell.swift"; sourceTree = SOURCE_ROOT; };
B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = "<group>"; };
B5EE25841B36E23C0000406B /* OrganismV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV1.swift; sourceTree = "<group>"; };
B5EE25861B36E2520000406B /* OrganismV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV2.swift; sourceTree = "<group>"; };
B5EE25881B36E2750000406B /* MigrationDemoV3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV3.xcdatamodel; sourceTree = "<group>"; };
B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationsDemoViewController.swift; sourceTree = "<group>"; };
B5EE259A1B3EA4890000406B /* OrganismV3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV3.swift; sourceTree = "<group>"; };
B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismProtocol.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -136,7 +155,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
B52977D81B120B80003D50A5 /* ObserversViewController.swift */, B52977D81B120B80003D50A5 /* ObserversViewController.swift */,
B503FADB1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift */, B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */,
B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */, B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */,
B503FADD1AFDC71700F90881 /* Palette.swift */, B503FADD1AFDC71700F90881 /* Palette.swift */,
B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */, B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */,
@@ -189,13 +208,14 @@
B503FADA1AFDC71700F90881 /* List and Object Observers Demo */, B503FADA1AFDC71700F90881 /* List and Object Observers Demo */,
B52977DB1B120F2C003D50A5 /* Transactions Demo */, B52977DB1B120F2C003D50A5 /* Transactions Demo */,
B56965091B2B35370075EE4A /* Fetching and Querying Demo */, B56965091B2B35370075EE4A /* Fetching and Querying Demo */,
B569652F1B3591460075EE4A /* Migrations Demo */,
B56964C61B20AC200075EE4A /* Loggers Demo */, B56964C61B20AC200075EE4A /* Loggers Demo */,
B54AAD571AF4D26E00848AE0 /* Main.storyboard */, B54AAD571AF4D26E00848AE0 /* Main.storyboard */,
B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */, B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */,
B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */, B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */,
B54AAD501AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld */, B54AAD501AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld */,
B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */, B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */,
B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */, B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */,
B54AAD4C1AF4D26E00848AE0 /* Supporting Files */, B54AAD4C1AF4D26E00848AE0 /* Supporting Files */,
); );
path = CoreStoreDemo; path = CoreStoreDemo;
@@ -239,6 +259,22 @@
path = "Fetching and Querying Demo"; path = "Fetching and Querying Demo";
sourceTree = "<group>"; sourceTree = "<group>";
}; };
B569652F1B3591460075EE4A /* Migrations Demo */ = {
isa = PBXGroup;
children = (
B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */,
B5EE259A1B3EA4890000406B /* OrganismV3.swift */,
B5EE25861B36E2520000406B /* OrganismV2.swift */,
B5EE25841B36E23C0000406B /* OrganismV1.swift */,
B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */,
B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */,
B5125C111B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel */,
B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */,
B5125C131B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel */,
);
path = "Migrations Demo";
sourceTree = "<group>";
};
B583A9151AF5F4F3001F76AF /* Products */ = { B583A9151AF5F4F3001F76AF /* Products */ = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
@@ -277,7 +313,8 @@
B54AAD411AF4D26E00848AE0 /* Project object */ = { B54AAD411AF4D26E00848AE0 /* Project object */ = {
isa = PBXProject; isa = PBXProject;
attributes = { attributes = {
LastUpgradeCheck = 0630; LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "John Rommel Estropia"; ORGANIZATIONNAME = "John Rommel Estropia";
TargetAttributes = { TargetAttributes = {
B54AAD481AF4D26E00848AE0 = { B54AAD481AF4D26E00848AE0 = {
@@ -345,11 +382,14 @@
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */, B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */,
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */,
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */,
B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */,
B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */, B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */,
B56964DF1B2321E30075EE4A /* MigrationDemo.xcdatamodeld in Sources */,
B52977E41B121635003D50A5 /* Place.swift in Sources */, B52977E41B121635003D50A5 /* Place.swift in Sources */,
B569650C1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift in Sources */, B569650C1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift in Sources */,
B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */, B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */,
B5EE25871B36E2520000406B /* OrganismV2.swift in Sources */,
B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */, B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */,
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */, B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */,
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */, B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */,
@@ -357,13 +397,19 @@
B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */, B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */,
B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */, B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */,
B54AAD521AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld in Sources */, B54AAD521AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld in Sources */,
B5EE259B1B3EA4890000406B /* OrganismV3.swift in Sources */,
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */, B503FAE11AFDC71700F90881 /* Palette.swift in Sources */,
B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */, B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */,
B503FADF1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift in Sources */, B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */,
B5125C141B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel in Sources */,
B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */,
B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */, B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */,
B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */, B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */,
B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */, B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */,
B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */,
B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */,
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */, B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */,
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */,
); );
runOnlyForDeploymentPostprocessing = 0; runOnlyForDeploymentPostprocessing = 0;
}; };
@@ -423,6 +469,7 @@
COPY_PHASE_STRIP = NO; COPY_PHASE_STRIP = NO;
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
ENABLE_STRICT_OBJC_MSGSEND = YES; ENABLE_STRICT_OBJC_MSGSEND = YES;
ENABLE_TESTABILITY = YES;
GCC_C_LANGUAGE_STANDARD = gnu99; GCC_C_LANGUAGE_STANDARD = gnu99;
GCC_DYNAMIC_NO_PIC = NO; GCC_DYNAMIC_NO_PIC = NO;
GCC_NO_COMMON_BLOCKS = YES; GCC_NO_COMMON_BLOCKS = YES;
@@ -438,7 +485,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.3; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = YES; MTL_ENABLE_DEBUG_INFO = YES;
ONLY_ACTIVE_ARCH = YES; ONLY_ACTIVE_ARCH = YES;
SDKROOT = iphoneos; SDKROOT = iphoneos;
@@ -476,7 +523,7 @@
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_VARIABLE = YES; GCC_WARN_UNUSED_VARIABLE = YES;
IPHONEOS_DEPLOYMENT_TARGET = 8.3; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
MTL_ENABLE_DEBUG_INFO = NO; MTL_ENABLE_DEBUG_INFO = NO;
SDKROOT = iphoneos; SDKROOT = iphoneos;
VALIDATE_PRODUCT = YES; VALIDATE_PRODUCT = YES;
@@ -487,14 +534,10 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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; INFOPLIST_FILE = CoreStoreDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
}; };
name = Debug; name = Debug;
@@ -503,14 +546,10 @@
isa = XCBuildConfiguration; isa = XCBuildConfiguration;
buildSettings = { buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 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; INFOPLIST_FILE = CoreStoreDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0; IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks"; LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
PRODUCT_NAME = "$(TARGET_NAME)"; PRODUCT_NAME = "$(TARGET_NAME)";
}; };
name = Release; name = Release;
@@ -559,13 +598,14 @@
sourceTree = "<group>"; sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel; versionGroupType = wrapper.xcdatamodel;
}; };
B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */ = { B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */ = {
isa = XCVersionGroup; isa = XCVersionGroup;
children = ( children = (
B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */, B5EE25881B36E2750000406B /* MigrationDemoV3.xcdatamodel */,
B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */, B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */,
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */,
); );
currentVersion = B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */; currentVersion = B5EE25881B36E2750000406B /* MigrationDemoV3.xcdatamodel */;
path = MigrationDemo.xcdatamodeld; path = MigrationDemo.xcdatamodeld;
sourceTree = "<group>"; sourceTree = "<group>";
versionGroupType = wrapper.xcdatamodel; versionGroupType = wrapper.xcdatamodel;

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
<true/>
<key>SnapshotAutomaticallyBeforeSignificantChanges</key>
<true/>
</dict>
</plist>

View File

@@ -1,91 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0630"
version = "1.8">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"
buildConfiguration = "Debug">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
buildConfiguration = "Debug"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
buildConfiguration = "Release"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -1,39 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Bucket
type = "1"
version = "2.0">
<Breakpoints>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../CoreStore/Migrating/DataStack+Migration.swift"
timestampString = "457102815.005196"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "80"
endingLineNumber = "80"
landmarkName = "needsMigrationForSQLiteStore(fileURL:configuration:)"
landmarkType = "5">
</BreakpointContent>
</BreakpointProxy>
<BreakpointProxy
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
<BreakpointContent
shouldBeEnabled = "Yes"
ignoreCount = "0"
continueAfterRunningActions = "No"
filePath = "../CoreStore/Migrating/DataStack+Migration.swift"
timestampString = "457102823.242858"
startingColumnNumber = "9223372036854775807"
endingColumnNumber = "9223372036854775807"
startingLineNumber = "165"
endingLineNumber = "165"
landmarkName = "upgradeSQLiteStoreIfNeeded(fileURL:configuration:completion:)"
landmarkType = "5">
</BreakpointContent>
</BreakpointProxy>
</Breakpoints>
</Bucket>

View File

@@ -1,11 +1,40 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?> <?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB"> <document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8152.3" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB">
<dependencies> <dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/> <deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8124.4"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/> <capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/> <capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/> <capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies> </dependencies>
<customFonts key="customFonts">
<mutableArray key="AppleSDGothicNeo.ttc">
<string>AppleSDGothicNeo-Regular</string>
</mutableArray>
<mutableArray key="HelveticaNeue.ttc">
<string>HelveticaNeue-Bold</string>
<string>HelveticaNeue-Bold</string>
<string>HelveticaNeue</string>
<string>HelveticaNeue</string>
</mutableArray>
<mutableArray key="HelveticaNeueLights.ttc">
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Thin</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
</mutableArray>
</customFonts>
<scenes> <scenes>
<!--SNS Accounts--> <!--SNS Accounts-->
<scene sceneID="3If-81-mNf"> <scene sceneID="3If-81-mNf">
@@ -14,7 +43,8 @@
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="S3A-lm-AuA"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="S3A-lm-AuA">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<view key="tableHeaderView" contentMode="scaleToFill" id="yud-WH-MPa"> <view key="tableHeaderView" contentMode="scaleToFill" id="yud-WH-MPa">
<rect key="frame" x="0.0" y="64" width="600" height="150"/> <rect key="frame" x="0.0" y="64" width="600" height="150"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
@@ -52,17 +82,21 @@
</view> </view>
<prototypes> <prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="8b8-lM-Krq" detailTextLabel="hR1-Zb-BOk" style="IBUITableViewCellStyleValue1" id="dMt-nx-EO5"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="8b8-lM-Krq" detailTextLabel="hR1-Zb-BOk" style="IBUITableViewCellStyleValue1" id="dMt-nx-EO5">
<rect key="frame" x="0.0" y="236" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="dMt-nx-EO5" id="gdK-VV-zNb"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="dMt-nx-EO5" id="gdK-VV-zNb">
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8b8-lM-Krq"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8b8-lM-Krq">
<rect key="frame" x="15" y="13" width="28.5" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hR1-Zb-BOk"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hR1-Zb-BOk">
<rect key="frame" x="533" y="13" width="52" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="calibratedRGB"/>
@@ -88,6 +122,155 @@
</objects> </objects>
<point key="canvasLocation" x="3694" y="650"/> <point key="canvasLocation" x="3694" y="650"/>
</scene> </scene>
<!--Evolution-->
<scene sceneID="iwU-Hv-zNr">
<objects>
<viewController automaticallyAdjustsScrollViewInsets="NO" id="iVv-Vc-nCL" customClass="MigrationsDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="Un6-jI-efh"/>
<viewControllerLayoutGuide type="bottom" id="t9A-zf-Iew"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="75P-2m-6cr">
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="WUc-3Y-Quw">
<rect key="frame" x="0.0" y="324" width="600" height="276"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="calibratedRGB"/>
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="OrganismTableViewCell" id="WVb-th-o8c" customClass="OrganismTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="22" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="WVb-th-o8c" id="JBq-Ml-a9p">
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OQf-Bd-Zze">
<rect key="frame" x="520" y="8" width="72" height="27.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<inset key="contentEdgeInsets" minX="7" minY="0.0" maxX="7" maxY="0.0"/>
<state key="normal" title="mutate!"/>
<connections>
<action selector="mutateButtonTouchUpInside:" destination="WVb-th-o8c" eventType="touchUpInside" id="REw-UX-rJ0"/>
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VZk-6K-4ut">
<rect key="frame" x="15" y="8" width="495" height="27.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="17"/>
<color key="textColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="OQf-Bd-Zze" firstAttribute="top" secondItem="JBq-Ml-a9p" secondAttribute="topMargin" id="39z-i5-EeS"/>
<constraint firstItem="VZk-6K-4ut" firstAttribute="top" secondItem="JBq-Ml-a9p" secondAttribute="topMargin" id="WO1-gf-MIu"/>
<constraint firstItem="VZk-6K-4ut" firstAttribute="leading" secondItem="JBq-Ml-a9p" secondAttribute="leadingMargin" constant="7" id="X0N-Il-F26"/>
<constraint firstAttribute="bottomMargin" secondItem="OQf-Bd-Zze" secondAttribute="bottom" id="YwC-dw-rGI"/>
<constraint firstItem="OQf-Bd-Zze" firstAttribute="leading" secondItem="VZk-6K-4ut" secondAttribute="trailing" constant="10" id="mo1-NZ-iPa"/>
<constraint firstAttribute="bottomMargin" secondItem="VZk-6K-4ut" secondAttribute="bottom" id="ucK-FJ-7v4"/>
<constraint firstItem="OQf-Bd-Zze" firstAttribute="trailing" secondItem="JBq-Ml-a9p" secondAttribute="trailingMargin" id="v3e-PY-3wW"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="dnaLabel" destination="VZk-6K-4ut" id="CSU-Ig-qMu"/>
<outlet property="mutateButton" destination="OQf-Bd-Zze" id="5gd-Jx-tMT"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="iVv-Vc-nCL" id="yp2-X4-fhE"/>
<outlet property="delegate" destination="iVv-Vc-nCL" id="V5b-s8-XOl"/>
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XKA-Ub-c2X">
<rect key="frame" x="0.0" y="64" width="600" height="260"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="i7U-bW-juB" customClass="UIButton">
<rect key="frame" x="0.0" y="0.0" width="600" height="260"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Organism" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zxy-nY-P44">
<rect key="frame" x="20" y="90.5" width="560" height="26.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="22"/>
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="attributes" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6Ac-xl-ldZ">
<rect key="frame" x="20" y="131.5" width="560" height="20.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="17"/>
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="rAZ-eJ-sxy">
<rect key="frame" x="20" y="20" width="560" height="29"/>
<segments>
<segment title="First"/>
<segment title="Second"/>
<segment title=""/>
</segments>
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<connections>
<action selector="segmentedControlValueChanged:" destination="iVv-Vc-nCL" eventType="valueChanged" id="RwG-kW-RPg"/>
</connections>
</segmentedControl>
<progressView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="869-wx-Odb">
<rect key="frame" x="20" y="68" width="560" height="2"/>
<color key="progressTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<color key="trackTintColor" white="1" alpha="0.20000000000000001" colorSpace="calibratedWhite"/>
</progressView>
</subviews>
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="zxy-nY-P44" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="1u1-Tq-hRn"/>
<constraint firstItem="6Ac-xl-ldZ" firstAttribute="top" secondItem="zxy-nY-P44" secondAttribute="bottom" constant="14.5" id="39B-9l-O3g"/>
<constraint firstAttribute="trailing" secondItem="zxy-nY-P44" secondAttribute="trailing" constant="20" id="3d5-Zk-rB1"/>
<constraint firstAttribute="trailing" secondItem="6Ac-xl-ldZ" secondAttribute="trailing" constant="20" id="EJq-uA-8dQ"/>
<constraint firstItem="rAZ-eJ-sxy" firstAttribute="top" secondItem="i7U-bW-juB" secondAttribute="top" constant="20" id="Ey7-mP-RNA"/>
<constraint firstItem="zxy-nY-P44" firstAttribute="top" secondItem="869-wx-Odb" secondAttribute="bottom" constant="20.5" id="GU3-JM-b99"/>
<constraint firstItem="rAZ-eJ-sxy" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="KKJ-gG-pFL"/>
<constraint firstAttribute="trailing" secondItem="869-wx-Odb" secondAttribute="trailing" constant="20" id="Lni-gD-7h0"/>
<constraint firstItem="869-wx-Odb" firstAttribute="top" secondItem="rAZ-eJ-sxy" secondAttribute="bottom" constant="20" id="QBz-cP-SVZ"/>
<constraint firstAttribute="trailing" secondItem="rAZ-eJ-sxy" secondAttribute="trailing" constant="20" id="WoD-cr-aj9"/>
<constraint firstAttribute="height" constant="260" id="XTa-ql-yEW"/>
<constraint firstItem="6Ac-xl-ldZ" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="jkv-ow-f0q"/>
<constraint firstItem="869-wx-Odb" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="qgE-y5-uh8"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="260" id="90R-Mf-iAB"/>
<constraint firstItem="i7U-bW-juB" firstAttribute="leading" secondItem="XKA-Ub-c2X" secondAttribute="leading" id="e9h-f4-c37"/>
<constraint firstItem="i7U-bW-juB" firstAttribute="top" secondItem="XKA-Ub-c2X" secondAttribute="top" id="vJV-dl-RLx"/>
<constraint firstAttribute="trailing" secondItem="i7U-bW-juB" secondAttribute="trailing" id="wYt-hg-yKM"/>
</constraints>
</view>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="XKA-Ub-c2X" secondAttribute="trailing" id="U0P-51-KT9"/>
<constraint firstAttribute="trailing" secondItem="WUc-3Y-Quw" secondAttribute="trailing" id="i7I-7B-97K"/>
<constraint firstItem="WUc-3Y-Quw" firstAttribute="leading" secondItem="75P-2m-6cr" secondAttribute="leading" id="i8b-GU-yFg"/>
<constraint firstItem="XKA-Ub-c2X" firstAttribute="top" secondItem="Un6-jI-efh" secondAttribute="bottom" id="lU2-wP-RQb"/>
<constraint firstItem="XKA-Ub-c2X" firstAttribute="leading" secondItem="75P-2m-6cr" secondAttribute="leading" id="oa7-qv-9am"/>
<constraint firstItem="t9A-zf-Iew" firstAttribute="top" secondItem="WUc-3Y-Quw" secondAttribute="bottom" id="tts-uy-w5J"/>
<constraint firstItem="WUc-3Y-Quw" firstAttribute="top" secondItem="XKA-Ub-c2X" secondAttribute="bottom" id="vjy-iN-Xi7"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="Evolution" id="uc7-DN-PYG"/>
<connections>
<outlet property="headerContainer" destination="i7U-bW-juB" id="hpf-7a-yBd"/>
<outlet property="organismLabel" destination="6Ac-xl-ldZ" id="pgF-ej-ovy"/>
<outlet property="progressView" destination="869-wx-Odb" id="um3-UM-uOP"/>
<outlet property="segmentedControl" destination="rAZ-eJ-sxy" id="K3Q-X2-Jrm"/>
<outlet property="tableView" destination="WUc-3Y-Quw" id="3Ki-v0-lTb"/>
<outlet property="titleLabel" destination="zxy-nY-P44" id="9JO-hP-yVp"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="LzB-gZ-6fG" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3694" y="4113"/>
</scene>
<!--CoreStore Demos--> <!--CoreStore Demos-->
<scene sceneID="0Be-vc-h1W"> <scene sceneID="0Be-vc-h1W">
<objects> <objects>
@@ -100,17 +283,21 @@
<tableViewSection id="wIP-Hn-YfF"> <tableViewSection id="wIP-Hn-YfF">
<cells> <cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Q3n-Df-v1t" detailTextLabel="Hbn-cf-Y7m" style="IBUITableViewCellStyleSubtitle" id="AXm-KE-45G"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Q3n-Df-v1t" detailTextLabel="Hbn-cf-Y7m" style="IBUITableViewCellStyleSubtitle" id="AXm-KE-45G">
<rect key="frame" x="0.0" y="99" width="600" height="50"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="AXm-KE-45G" id="9te-Wx-hkf"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="AXm-KE-45G" id="9te-Wx-hkf">
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Accounts" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Q3n-Df-v1t"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Accounts" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Q3n-Df-v1t">
<rect key="frame" x="15" y="6" width="82" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.17254901960784313" green="0.24313725490196078" blue="0.31372549019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901960784313" green="0.24313725490196078" blue="0.31372549019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Setting up multiple persistent store configurations" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hbn-cf-Y7m"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Setting up multiple persistent store configurations" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hbn-cf-Y7m">
<rect key="frame" x="15" y="30" width="264" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/> <fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -123,17 +310,21 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="vpt-cT-gMo" detailTextLabel="ou9-TZ-8bf" style="IBUITableViewCellStyleSubtitle" id="fsb-zw-8Ii"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="vpt-cT-gMo" detailTextLabel="ou9-TZ-8bf" style="IBUITableViewCellStyleSubtitle" id="fsb-zw-8Ii">
<rect key="frame" x="0.0" y="149" width="600" height="50"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="fsb-zw-8Ii" id="Upm-AO-Fw3"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="fsb-zw-8Ii" id="Upm-AO-Fw3">
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Colors" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="vpt-cT-gMo"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Colors" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="vpt-cT-gMo">
<rect key="frame" x="15" y="6" width="56" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observing list changes and single object changes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ou9-TZ-8bf"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observing list changes and single object changes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ou9-TZ-8bf">
<rect key="frame" x="15" y="30" width="261.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/> <fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -146,17 +337,21 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="UbU-Kd-yrY" detailTextLabel="uP1-Jc-o9v" style="IBUITableViewCellStyleSubtitle" id="ekW-PJ-mbo"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="UbU-Kd-yrY" detailTextLabel="uP1-Jc-o9v" style="IBUITableViewCellStyleSubtitle" id="ekW-PJ-mbo">
<rect key="frame" x="0.0" y="199" width="600" height="50"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ekW-PJ-mbo" id="CYq-mg-PVS"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ekW-PJ-mbo" id="CYq-mg-PVS">
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Placemarks" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="UbU-Kd-yrY"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Placemarks" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="UbU-Kd-yrY">
<rect key="frame" x="15" y="6" width="100.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Making changes with transactions" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="uP1-Jc-o9v"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Making changes with transactions" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="uP1-Jc-o9v">
<rect key="frame" x="15" y="30" width="179.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/> <fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -169,17 +364,21 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="C8Y-0y-lEG" detailTextLabel="jZw-qE-0ws" style="IBUITableViewCellStyleSubtitle" id="ph1-8z-C1m"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="C8Y-0y-lEG" detailTextLabel="jZw-qE-0ws" style="IBUITableViewCellStyleSubtitle" id="ph1-8z-C1m">
<rect key="frame" x="0.0" y="249" width="600" height="50"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ph1-8z-C1m" id="nNz-rd-ksg"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ph1-8z-C1m" id="nNz-rd-ksg">
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Time Zones" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="C8Y-0y-lEG"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Time Zones" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="C8Y-0y-lEG">
<rect key="frame" x="15" y="6" width="101.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws">
<rect key="frame" x="15" y="30" width="169" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/> <fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -192,17 +391,21 @@
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="ZfY-Aq-Ykq" detailTextLabel="QzD-9b-k1j" style="IBUITableViewCellStyleSubtitle" id="wyK-rk-3tI"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="ZfY-Aq-Ykq" detailTextLabel="QzD-9b-k1j" style="IBUITableViewCellStyleSubtitle" id="wyK-rk-3tI">
<rect key="frame" x="0.0" y="299" width="600" height="50"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="wyK-rk-3tI" id="fLd-KK-QcW"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="wyK-rk-3tI" id="fLd-KK-QcW">
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZfY-Aq-Ykq"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZfY-Aq-Ykq">
<rect key="frame" x="15" y="6" width="61" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j">
<rect key="frame" x="15" y="30" width="159.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/> <fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -214,6 +417,33 @@
<segue destination="5yy-0N-QDU" kind="show" id="5D7-Xp-1HT"/> <segue destination="5yy-0N-QDU" kind="show" id="5D7-Xp-1HT"/>
</connections> </connections>
</tableViewCell> </tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="hSG-mG-YBw" detailTextLabel="X9P-TQ-LYh" style="IBUITableViewCellStyleSubtitle" id="xTM-Cf-0if">
<rect key="frame" x="0.0" y="349" width="600" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="xTM-Cf-0if" id="DfO-BW-krd">
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Evolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hSG-mG-YBw">
<rect key="frame" x="15" y="6" width="78.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Migrating and de-migrating stores" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="X9P-TQ-LYh">
<rect key="frame" x="15" y="30" width="179.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="iVv-Vc-nCL" kind="show" id="HbO-rU-Qfj"/>
</connections>
</tableViewCell>
</cells> </cells>
</tableViewSection> </tableViewSection>
</sections> </sections>
@@ -237,18 +467,18 @@
<viewControllerLayoutGuide type="bottom" id="aI4-O3-OCi"/> <viewControllerLayoutGuide type="bottom" id="aI4-O3-OCi"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="w8K-eN-RvU"> <view key="view" contentMode="scaleToFill" id="w8K-eN-RvU">
<rect key="frame" x="0.0" y="0.0" width="600" height="268"/> <rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd"> <view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
<rect key="frame" x="16" y="70" width="568" height="36"/> <rect key="frame" x="20" y="69.5" width="552" height="36.5"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="TIX-qi-B34"/> <constraint firstAttribute="height" relation="greaterThanOrEqual" id="TIX-qi-B34"/>
</constraints> </constraints>
</view> </view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfe-Yq-3Xa"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfe-Yq-3Xa">
<rect key="frame" x="16" y="116" width="568" height="18"/> <rect key="frame" x="20" y="116" width="552" height="18"/>
<constraints> <constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="4h9-ha-EzR"/> <constraint firstAttribute="height" relation="greaterThanOrEqual" id="4h9-ha-EzR"/>
</constraints> </constraints>
@@ -257,43 +487,43 @@
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hue" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sgg-Md-Nf3"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hue" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sgg-Md-Nf3">
<rect key="frame" x="16" y="154" width="74" height="18"/> <rect key="frame" x="20" y="154" width="74" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/> <fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Saturation" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rry-vh-bRK"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Saturation" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rry-vh-bRK">
<rect key="frame" x="16" y="192" width="74" height="18"/> <rect key="frame" x="20" y="192" width="74" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/> <fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Brightness" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vTa-ly-eyO"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Brightness" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vTa-ly-eyO">
<rect key="frame" x="16" y="230" width="74" height="18"/> <rect key="frame" x="20" y="230" width="74" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/> <fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="359" translatesAutoresizingMaskIntoConstraints="NO" id="YQ6-fq-3Wb"> <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="359" translatesAutoresizingMaskIntoConstraints="NO" id="YQ6-fq-3Wb">
<rect key="frame" x="98" y="148" width="488" height="31"/> <rect key="frame" x="102" y="148" width="472" height="31"/>
<connections> <connections>
<action selector="hueSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="9Hy-3h-llE"/> <action selector="hueSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="9Hy-3h-llE"/>
</connections> </connections>
</slider> </slider>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="xXz-78-tAd"> <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="xXz-78-tAd">
<rect key="frame" x="98" y="186" width="488" height="31"/> <rect key="frame" x="102" y="186" width="472" height="31"/>
<connections> <connections>
<action selector="saturationSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="qtU-ua-ZTc"/> <action selector="saturationSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="qtU-ua-ZTc"/>
</connections> </connections>
</slider> </slider>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="hpy-2d-eOP"> <slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="hpy-2d-eOP">
<rect key="frame" x="98" y="224" width="488" height="31"/> <rect key="frame" x="102" y="224" width="472" height="31"/>
<connections> <connections>
<action selector="brightnessSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="F09-EP-2iD"/> <action selector="brightnessSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="F09-EP-2iD"/>
</connections> </connections>
</slider> </slider>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p4O-tf-dgt"> <label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p4O-tf-dgt">
<rect key="frame" x="16" y="49" width="568" height="21"/> <rect key="frame" x="20" y="49" width="552" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/> <fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/> <color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
@@ -361,17 +591,17 @@
<viewControllerLayoutGuide type="bottom" id="LNL-mj-D7l"/> <viewControllerLayoutGuide type="bottom" id="LNL-mj-D7l"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="6x3-vn-Egt"> <view key="view" contentMode="scaleToFill" id="6x3-vn-Egt">
<rect key="frame" x="0.0" y="64" width="600" height="600"/> <rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf"> <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf">
<rect key="frame" x="0.0" y="64" width="600" height="268"/> <rect key="frame" x="4" y="64" width="592" height="268"/>
<connections> <connections>
<segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/> <segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/>
</connections> </connections>
</containerView> </containerView>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp"> <containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp">
<rect key="frame" x="0.0" y="332" width="600" height="268"/> <rect key="frame" x="4" y="332" width="592" height="268"/>
<connections> <connections>
<segue destination="sll-yo-mBc" kind="embed" id="AAl-HS-dq2"/> <segue destination="sll-yo-mBc" kind="embed" id="AAl-HS-dq2"/>
</connections> </connections>
@@ -425,14 +655,16 @@
<!--List Observer--> <!--List Observer-->
<scene sceneID="gkX-bd-Rel"> <scene sceneID="gkX-bd-Rel">
<objects> <objects>
<tableViewController id="3AE-ED-0oj" customClass="ObjectListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController"> <tableViewController id="3AE-ED-0oj" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="DAz-BE-6Ca"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="DAz-BE-6Ca">
<rect key="frame" x="0.0" y="0.0" width="600" height="268"/> <rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<prototypes> <prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="G3X-70-BCD" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="G3X-70-BCD" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="66" width="592" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="G3X-70-BCD" id="aT8-nz-i5l"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="G3X-70-BCD" id="aT8-nz-i5l">
<rect key="frame" x="0.0" y="0.0" width="559" height="43.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uQX-PI-UWF"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uQX-PI-UWF">
@@ -482,15 +714,17 @@
<!--List Observer--> <!--List Observer-->
<scene sceneID="iDB-TD-It9"> <scene sceneID="iDB-TD-It9">
<objects> <objects>
<tableViewController id="lCE-i6-UCT" customClass="ObjectListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController"> <tableViewController id="lCE-i6-UCT" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="Zba-8M-Zd7"> <tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="Zba-8M-Zd7">
<rect key="frame" x="0.0" y="0.0" width="600" height="268"/> <rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/> <color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<prototypes> <prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="zSO-3e-OVq" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="zSO-3e-OVq" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="66" width="592" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="zSO-3e-OVq" id="cHA-by-n4b"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="zSO-3e-OVq" id="cHA-by-n4b">
<rect key="frame" x="0.0" y="0.0" width="559" height="43.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5uq-Yi-XwH"> <view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5uq-Yi-XwH">
@@ -571,7 +805,7 @@
<viewControllerLayoutGuide type="bottom" id="RZg-hi-T8O"/> <viewControllerLayoutGuide type="bottom" id="RZg-hi-T8O"/>
</layoutGuides> </layoutGuides>
<view key="view" contentMode="scaleToFill" id="k4s-iL-Krh"> <view key="view" contentMode="scaleToFill" id="k4s-iL-Krh">
<rect key="frame" x="0.0" y="0.0" width="600" height="536"/> <rect key="frame" x="0.0" y="64" width="600" height="536"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0"> <mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
@@ -724,12 +958,14 @@
</view> </view>
<prototypes> <prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="6db-P0-6Ms" style="IBUITableViewCellStyleDefault" id="vUr-WV-qur"> <tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="6db-P0-6Ms" style="IBUITableViewCellStyleDefault" id="vUr-WV-qur">
<rect key="frame" x="0.0" y="0.0" width="600" height="44"/> <rect key="frame" x="0.0" y="102" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="vUr-WV-qur" id="Vr0-hE-cn9"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="vUr-WV-qur" id="Vr0-hE-cn9">
<rect key="frame" x="0.0" y="0.0" width="567" height="43.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6db-P0-6Ms"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6db-P0-6Ms">
<rect key="frame" x="15" y="0.0" width="550" height="43.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="18"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="18"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -776,17 +1012,21 @@
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes> <prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="RgX-yK-1L2" detailTextLabel="QZ4-A2-x4h" style="IBUITableViewCellStyleSubtitle" id="uBt-Iy-nWP"> <tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="RgX-yK-1L2" detailTextLabel="QZ4-A2-x4h" style="IBUITableViewCellStyleSubtitle" id="uBt-Iy-nWP">
<rect key="frame" x="0.0" y="100" width="600" height="60"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="uBt-Iy-nWP" id="6SD-ur-9zp"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="uBt-Iy-nWP" id="6SD-ur-9zp">
<rect key="frame" x="0.0" y="0.0" width="600" height="59.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RgX-yK-1L2"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RgX-yK-1L2">
<rect key="frame" x="15" y="11" width="48.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QZ4-A2-x4h"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QZ4-A2-x4h">
<rect key="frame" x="15" y="35" width="31" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/> <fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -818,17 +1058,21 @@
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes> <prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="Syt-QJ-KXg" detailTextLabel="yHS-dP-IKS" style="IBUITableViewCellStyleSubtitle" id="q7Q-aF-Ftl"> <tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="Syt-QJ-KXg" detailTextLabel="yHS-dP-IKS" style="IBUITableViewCellStyleSubtitle" id="q7Q-aF-Ftl">
<rect key="frame" x="0.0" y="100" width="600" height="60"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="q7Q-aF-Ftl" id="fc3-eg-yes"> <tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="q7Q-aF-Ftl" id="fc3-eg-yes">
<rect key="frame" x="0.0" y="0.0" width="600" height="59.5"/>
<autoresizingMask key="autoresizingMask"/> <autoresizingMask key="autoresizingMask"/>
<subviews> <subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Syt-QJ-KXg"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Syt-QJ-KXg">
<rect key="frame" x="15" y="11" width="48.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/> <fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/> <nil key="highlightedColor"/>
</label> </label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="yHS-dP-IKS"> <label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="yHS-dP-IKS">
<rect key="frame" x="15" y="35" width="31" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/> <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/> <fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -854,6 +1098,6 @@
<image name="second" width="30" height="30"/> <image name="second" width="30" height="30"/>
</resources> </resources>
<inferredMetricsTieBreakers> <inferredMetricsTieBreakers>
<segue reference="hyN-De-zte"/> <segue reference="fIB-GS-Ppk"/>
</inferredMetricsTieBreakers> </inferredMetricsTieBreakers>
</document> </document>

View File

@@ -15,8 +15,8 @@ private struct Static {
static let timeZonesStack: DataStack = { static let timeZonesStack: DataStack = {
let dataStack = DataStack() let dataStack = DataStack()
dataStack.addSQLiteStoreAndWait( try! dataStack.addSQLiteStoreAndWait(
"TimeZoneDemo.sqlite", fileName: "TimeZoneDemo.sqlite",
configuration: "FetchingAndQueryingDemo", configuration: "FetchingAndQueryingDemo",
resetStoreOnMigrationFailure: true resetStoreOnMigrationFailure: true
) )
@@ -25,7 +25,7 @@ private struct Static {
transaction.deleteAll(From(TimeZone)) transaction.deleteAll(From(TimeZone))
for name in NSTimeZone.knownTimeZoneNames() as! [String] { for name in NSTimeZone.knownTimeZoneNames() {
let rawTimeZone = NSTimeZone(name: name)! let rawTimeZone = NSTimeZone(name: name)!
let cachedTimeZone = transaction.create(Into(TimeZone)) let cachedTimeZone = transaction.create(Into(TimeZone))
@@ -112,7 +112,7 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell") as! UITableViewCell let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell")!
switch self.segmentedControl?.selectedSegmentIndex { switch self.segmentedControl?.selectedSegmentIndex {

View File

@@ -43,7 +43,7 @@ class FetchingResultsViewController: UITableViewController {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath) as! UITableViewCell let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath)
let timeZone = self.timeZones[indexPath.row] let timeZone = self.timeZones[indexPath.row]
cell.textLabel?.text = timeZone.name cell.textLabel?.text = timeZone.name

View File

@@ -62,7 +62,7 @@ class QueryingResultsViewController: UITableViewController {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath) as! UITableViewCell let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath)
let value = self.values[indexPath.row] let value = self.values[indexPath.row]

View File

@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.johnestropia.CoreStoreDemo</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>
@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key> <key>CFBundlePackageType</key>
<string>APPL</string> <string>APPL</string>
<key>CFBundleShortVersionString</key> <key>CFBundleShortVersionString</key>
<string>1.0</string> <string>1.0.0</string>
<key>CFBundleSignature</key> <key>CFBundleSignature</key>
<string>????</string> <string>????</string>
<key>CFBundleVersion</key> <key>CFBundleVersion</key>

View File

@@ -1,5 +1,5 @@
// //
// ObjectListObserverDemoViewController.swift // ListObserverDemoViewController.swift
// CoreStoreDemo // CoreStoreDemo
// //
// Created by John Rommel Estropia on 2015/05/02. // Created by John Rommel Estropia on 2015/05/02.
@@ -12,26 +12,26 @@ import CoreStore
private struct Static { private struct Static {
static let palettes: ManagedObjectListController<Palette> = { static let palettes: ListMonitor<Palette> = {
CoreStore.addSQLiteStoreAndWait( try! CoreStore.addSQLiteStoreAndWait(
"ColorsDemo.sqlite", fileName: "ColorsDemo.sqlite",
configuration: "ObservingDemo", configuration: "ObservingDemo",
resetStoreOnMigrationFailure: true resetStoreOnMigrationFailure: true
) )
return CoreStore.observeSectionedList( return CoreStore.monitorSectionedList(
From(Palette), From(Palette),
SectionedBy("colorName"), SectionBy("colorName"),
OrderBy(.Ascending("hue")) OrderBy(.Ascending("hue"))
) )
}() }()
} }
// MARK: - ObjectListObserverDemoViewController // MARK: - ListObserverDemoViewController
class ObjectListObserverDemoViewController: UITableViewController, ManagedObjectListSectionObserver { class ListObserverDemoViewController: UITableViewController, ListSectionObserver {
// MARK: NSObject // MARK: NSObject
@@ -138,32 +138,32 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
} }
// MARK: ManagedObjectListChangeObserver // MARK: ListObserver
func managedObjectListWillChange(listController: ManagedObjectListController<Palette>) { func listMonitorWillChange(monitor: ListMonitor<Palette>) {
self.tableView.beginUpdates() self.tableView.beginUpdates()
} }
func managedObjectListDidChange(listController: ManagedObjectListController<Palette>) { func listMonitorDidChange(monitor: ListMonitor<Palette>) {
self.tableView.endUpdates() self.tableView.endUpdates()
} }
// MARK: ManagedObjectListObjectObserver // MARK: ListObjectObserver
func managedObjectList(listController: ManagedObjectListController<Palette>, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) { func listMonitor(monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) {
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
} }
func managedObjectList(listController: ManagedObjectListController<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) { func listMonitor(monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) {
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic) self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
} }
func managedObjectList(listController: ManagedObjectListController<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) { func listMonitor(monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) {
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? PaletteTableViewCell { if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? PaletteTableViewCell {
@@ -173,21 +173,21 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
} }
} }
func managedObjectList(listController: ManagedObjectListController<Palette>, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { func listMonitor(monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
self.tableView.deleteRowsAtIndexPaths([fromIndexPath], withRowAnimation: .Automatic) self.tableView.deleteRowsAtIndexPaths([fromIndexPath], withRowAnimation: .Automatic)
self.tableView.insertRowsAtIndexPaths([toIndexPath], withRowAnimation: .Automatic) self.tableView.insertRowsAtIndexPaths([toIndexPath], withRowAnimation: .Automatic)
} }
// MARK: ManagedObjectListSectionObserver // MARK: ListSectionObserver
func managedObjectList(listController: ManagedObjectListController<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { func listMonitor(monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic) self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
} }
func managedObjectList(listController: ManagedObjectListController<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { func listMonitor(monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic) self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
} }
@@ -195,7 +195,7 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
// MARK: Private // MARK: Private
@IBAction dynamic func resetBarButtonItemTouched(sender: AnyObject?) { @IBAction private dynamic func resetBarButtonItemTouched(sender: AnyObject?) {
CoreStore.beginAsynchronous { (transaction) -> Void in 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 CoreStore.beginAsynchronous { (transaction) -> Void in

View File

@@ -12,23 +12,23 @@ import CoreStore
// MARK: - ObjectObserverDemoViewController // MARK: - ObjectObserverDemoViewController
class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver { class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
var palette: Palette? { var palette: Palette? {
get { get {
return self.objectController?.object return self.monitor?.object
} }
set { set {
if let palette = newValue { if let palette = newValue {
self.objectController = CoreStore.observeObject(palette) self.monitor = CoreStore.monitorObject(palette)
} }
else { else {
self.objectController = nil self.monitor = nil
} }
} }
} }
@@ -37,17 +37,17 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
deinit { deinit {
self.objectController?.removeObserver(self) self.monitor?.removeObserver(self)
} }
// MARK: UIViewController // MARK: UIViewController
required init(coder aDecoder: NSCoder) { required init?(coder aDecoder: NSCoder) {
if let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue"))) { if let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue"))) {
self.objectController = CoreStore.observeObject(palette) self.monitor = CoreStore.monitorObject(palette)
} }
else { else {
@@ -60,7 +60,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
} }
let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))! let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))!
self.objectController = CoreStore.observeObject(palette) self.monitor = CoreStore.monitorObject(palette)
} }
super.init(coder: aDecoder) super.init(coder: aDecoder)
@@ -69,28 +69,28 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
override func viewDidLoad() { override func viewDidLoad() {
super.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) self.reloadPaletteInfo(palette, changedKeys: nil)
} }
} }
// MARK: ManagedObjectObserver // MARK: ObjectObserver
func managedObjectWillUpdate(objectController: ManagedObjectController<Palette>, object: Palette) { func objectMonitor(monitor: ObjectMonitor<Palette>, willUpdateObject object: Palette) {
// none // none
} }
func managedObjectWasUpdated(objectController: ManagedObjectController<Palette>, object: Palette, changedPersistentKeys: Set<KeyPath>) { func objectMonitor(monitor: ObjectMonitor<Palette>, didUpdateObject object: Palette, changedPersistentKeys: Set<KeyPath>) {
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys) self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
} }
func managedObjectWasDeleted(objectController: ManagedObjectController<Palette>, object: Palette) { func objectMonitor(monitor: ObjectMonitor<Palette>, didDeleteObject object: Palette) {
self.navigationItem.rightBarButtonItem?.enabled = false self.navigationItem.rightBarButtonItem?.enabled = false
@@ -108,7 +108,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
// MARK: Private // MARK: Private
var objectController: ManagedObjectController<Palette>? var monitor: ObjectMonitor<Palette>?
@IBOutlet weak var colorNameLabel: UILabel? @IBOutlet weak var colorNameLabel: UILabel?
@IBOutlet weak var colorView: UIView? @IBOutlet weak var colorView: UIView?
@@ -123,7 +123,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
let hue = self.hueSlider?.value ?? 0 let hue = self.hueSlider?.value ?? 0
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in 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) palette.hue = Int32(hue)
transaction.commit() transaction.commit()
@@ -136,7 +136,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
let saturation = self.saturationSlider?.value ?? 0 let saturation = self.saturationSlider?.value ?? 0
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in 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 palette.saturation = saturation
transaction.commit() transaction.commit()
@@ -149,7 +149,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
let brightness = self.brightnessSlider?.value ?? 0 let brightness = self.brightnessSlider?.value ?? 0
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in 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 palette.brightness = brightness
transaction.commit() transaction.commit()
@@ -161,7 +161,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
transaction.delete(self?.objectController?.object) transaction.delete(self?.monitor?.object)
transaction.commit() transaction.commit()
} }
} }
@@ -176,10 +176,6 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
self.hsbLabel?.text = palette.colorText self.hsbLabel?.text = palette.colorText
let hue = palette.hue
let saturation = palette.saturation
let brightness = palette.brightness
if changedKeys == nil || changedKeys?.contains("hue") == true { if changedKeys == nil || changedKeys?.contains("hue") == true {
self.hueSlider?.value = Float(palette.hue) self.hueSlider?.value = Float(palette.hue)

View File

@@ -21,7 +21,7 @@ class ObserversViewController: UIViewController {
let alert = UIAlertController( let alert = UIAlertController(
title: "Observers Demo", 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 preferredStyle: .Alert
) )
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil)) alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))

View File

@@ -30,7 +30,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
super.viewDidLoad() super.viewDidLoad()
self.dataStack.addSQLiteStoreAndWait("emptyStore.sqlite") try! self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite")
CoreStore.logger = self CoreStore.logger = self
} }
@@ -50,7 +50,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
// MARK: CoreStoreLogger // MARK: CoreStoreLogger
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) {
GCDQueue.Main.async { [weak self] in GCDQueue.Main.async { [weak self] in
@@ -66,7 +66,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
} }
} }
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) {
GCDQueue.Main.async { [weak self] in GCDQueue.Main.async { [weak self] in
@@ -87,6 +87,11 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
} }
} }
@noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
Swift.fatalError("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)")
}
// MARK: Private // MARK: Private
@@ -100,11 +105,11 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
case .Some(0): case .Some(0):
self.dataStack.beginAsynchronous { (transaction) -> Void in self.dataStack.beginAsynchronous { (transaction) -> Void in
transaction.create(Into(UserAccount)) transaction.create(Into(Palette))
} }
case .Some(1): case .Some(1):
self.dataStack.addSQLiteStoreAndWait("emptyStore.sqlite", configuration: "invalidStore") try! self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite", configuration: "invalidStore")
case .Some(2): case .Some(2):
self.dataStack.beginAsynchronous { (transaction) -> Void in self.dataStack.beginAsynchronous { (transaction) -> Void in

View File

@@ -0,0 +1,379 @@
//
// 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
// MARK: - MigrationsDemoViewController
class MigrationsDemoViewController: UIViewController {
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
if let segmentedControl = self.segmentedControl {
for (index, model) in self.models.enumerate() {
segmentedControl.setTitle(
model.label,
forSegmentAtIndex: index
)
}
}
self.setDataStack(nil, model: nil, scrollToSelection: false)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let modelMetadata = withExtendedLifetime(DataStack(modelName: "MigrationDemo")) {
(dataStack: DataStack) -> ModelMetadata in
let models = self.models
do {
let migrations = try dataStack.requiredMigrationsForSQLiteStore(
fileName: "MigrationDemo.sqlite"
)
let storeVersion = migrations.first?.sourceVersion ?? dataStack.modelVersion
for model in models {
if model.version == storeVersion {
return model
}
}
}
catch _ { }
return models.first!
}
self.selectModelVersion(modelMetadata)
}
// MARK: Private
private typealias ModelMetadata = (label: String, version: String, entityType: AnyClass, migrationChain: MigrationChain)
private let models: [ModelMetadata] = [
(
label: "Model V1",
version: "MigrationDemo",
entityType: OrganismV1.self,
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
),
(
label: "Model V2",
version: "MigrationDemoV2",
entityType: OrganismV2.self,
migrationChain: [
"MigrationDemo": "MigrationDemoV2",
"MigrationDemoV3": "MigrationDemoV2"
]
),
(
label: "Model V3",
version: "MigrationDemoV3",
entityType: OrganismV3.self,
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
)
]
private var _listMonitor: ListMonitor<NSManagedObject>?
private var listMonitor: ListMonitor<NSManagedObject>? {
return self._listMonitor
}
private var _dataStack: DataStack?
private var dataStack: DataStack? {
return self._dataStack
}
private var _lastSelectedIndexPath: NSIndexPath?
private var lastSelectedIndexPath: NSIndexPath? {
return self._lastSelectedIndexPath
}
private func setSelectedIndexPath(indexPath: NSIndexPath, scrollToSelection: Bool) {
self._lastSelectedIndexPath = indexPath
self.updateDisplay(reloadData: false, scrollToSelection: scrollToSelection, animated: true)
}
@IBOutlet private dynamic weak var headerContainer: UIView?
@IBOutlet private dynamic weak var titleLabel: UILabel?
@IBOutlet private dynamic weak var organismLabel: UILabel?
@IBOutlet private dynamic weak var segmentedControl: UISegmentedControl?
@IBOutlet private dynamic weak var progressView: UIProgressView?
@IBOutlet private dynamic weak var tableView: UITableView?
@IBAction private dynamic func segmentedControlValueChanged(sender: AnyObject?) {
guard let index = self.segmentedControl?.selectedSegmentIndex else {
return
}
self.selectModelVersion(self.models[index])
}
private func selectModelVersion(model: ModelMetadata) {
if self.dataStack?.modelVersion == model.version {
return
}
self.setDataStack(nil, model: nil, scrollToSelection: false) // explicitly trigger NSPersistentStore cleanup by deallocating the stack
let dataStack = DataStack(
modelName: "MigrationDemo",
migrationChain: model.migrationChain
)
self.setEnabled(false)
let progress = try! dataStack.addSQLiteStore(
fileName: "MigrationDemo.sqlite",
completion: { [weak self] (result) -> Void in
guard let strongSelf = self else {
return
}
guard case .Success = result else {
strongSelf.setEnabled(true)
return
}
strongSelf.setDataStack(dataStack, model: model, scrollToSelection: true)
let count = dataStack.queryValue(From(model.entityType), Select<Int>(.Count("dna")))
if count > 0 {
strongSelf.setEnabled(true)
}
else {
dataStack.beginAsynchronous { (transaction) -> Void in
for i: Int64 in 1 ..< 10000 {
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
organism.dna = i
organism.mutate()
}
transaction.commit { result -> Void in
self?.setEnabled(true)
}
}
}
}
)
if let progress = progress {
progress.setProgressHandler { [weak self] (progress) -> Void in
self?.reloadTableHeaderWithProgress(progress)
}
}
}
private func setEnabled(enabled: Bool) {
UIView.animateWithDuration(
0.2,
delay: 0,
options: .BeginFromCurrentState,
animations: { () -> Void in
let navigationItem = self.navigationItem
navigationItem.leftBarButtonItem?.enabled = enabled
navigationItem.rightBarButtonItem?.enabled = enabled
navigationItem.hidesBackButton = !enabled
self.segmentedControl?.enabled = enabled
if let tableView = self.tableView {
tableView.alpha = enabled ? 1.0 : 0.5
tableView.userInteractionEnabled = enabled
}
},
completion: nil
)
}
private func setDataStack(dataStack: DataStack?, model: ModelMetadata?, scrollToSelection: Bool) {
if let dataStack = dataStack, let model = model {
self.segmentedControl?.selectedSegmentIndex = self.models.map { $0.version }.indexOf(model.version)!
self._dataStack = dataStack
let listMonitor = dataStack.monitorList(From(model.entityType), OrderBy(.Descending("dna")))
listMonitor.addObserver(self)
self._listMonitor = listMonitor
if self.lastSelectedIndexPath == nil {
if listMonitor.numberOfObjectsInSection(0) > 0 {
self.setSelectedIndexPath(NSIndexPath(forRow: 0, inSection: 0), scrollToSelection: true)
}
}
}
else {
self.segmentedControl?.selectedSegmentIndex = UISegmentedControlNoSegment
self._dataStack = nil
self._listMonitor = nil
}
self.updateDisplay(reloadData: true, scrollToSelection: scrollToSelection, animated: false)
}
private func reloadTableHeaderWithProgress(progress: NSProgress) {
self.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
self.titleLabel?.text = "Migrating: \(progress.localizedDescription)"
self.organismLabel?.text = "Incremental step \(progress.localizedAdditionalDescription)"
}
private func updateDisplay(reloadData reloadData: Bool, scrollToSelection: Bool, animated: Bool) {
var lines = [String]()
var organismType = ""
if let indexPath = self.lastSelectedIndexPath, let organism = self.listMonitor?[indexPath] {
for property in organism.entity.properties {
let value: AnyObject = organism.valueForKey(property.name) ?? NSNull()
lines.append("\(property.name): \(value)")
}
organismType = organism.entity.managedObjectClassName
}
self.titleLabel?.text = organismType
self.organismLabel?.text = "\n".join(lines)
self.progressView?.progress = 0
self.headerContainer?.setNeedsLayout()
guard let tableView = self.tableView else {
return
}
if reloadData {
tableView.reloadData()
}
tableView.layoutIfNeeded()
if let indexPath = self.lastSelectedIndexPath where indexPath.row < tableView.numberOfRowsInSection(0) {
tableView.selectRowAtIndexPath(indexPath,
animated: scrollToSelection && animated,
scrollPosition: scrollToSelection ? .Middle : .None
)
}
}
}
// MARK: - MigrationsDemoViewController: ListObserver
extension MigrationsDemoViewController: ListObserver {
// MARK: ListObserver
func listMonitorWillChange(monitor: ListMonitor<NSManagedObject>) { }
func listMonitorDidChange(monitor: ListMonitor<NSManagedObject>) {
if self.lastSelectedIndexPath == nil && self.listMonitor?.numberOfObjectsInSection(0) > 0 {
self.tableView?.reloadData()
self.setSelectedIndexPath(NSIndexPath(forRow: 0, inSection: 0), scrollToSelection: false)
}
else {
self.updateDisplay(reloadData: true, scrollToSelection: true, animated: true)
}
}
}
// MARK: - MigrationsDemoViewController: UITableViewDataSource, UITableViewDelegate
extension MigrationsDemoViewController: UITableViewDataSource, UITableViewDelegate {
// MARK: UITableViewDataSource
@objc dynamic func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listMonitor?.numberOfObjectsInSection(0) ?? 0
}
@objc dynamic func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("OrganismTableViewCell", forIndexPath: indexPath) as! OrganismTableViewCell
let dna = (self.listMonitor?[indexPath] as? OrganismProtocol)?.dna.description ?? ""
cell.dnaLabel?.text = "DNA: \(dna)"
cell.mutateButtonHandler = { [weak self] _ -> Void in
guard let strongSelf = self,
let dataStack = strongSelf.dataStack,
let organism = strongSelf.listMonitor?[indexPath] else {
return
}
strongSelf.setSelectedIndexPath(indexPath, scrollToSelection: false)
strongSelf.setEnabled(false)
dataStack.beginAsynchronous { (transaction) -> Void in
let organism = transaction.edit(organism) as! OrganismProtocol
organism.mutate()
transaction.commit { _ -> Void in
self?.setEnabled(true)
}
}
}
return cell
}
// MARK: UITableViewDelegate
@objc dynamic func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
}
}

View File

@@ -0,0 +1,16 @@
//
// 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: class {
var dna: Int64 { get set }
func mutate()
}

View File

@@ -0,0 +1,22 @@
//
// OrganismTableViewCell.swift
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/07/12.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
//
import UIKit
class OrganismTableViewCell: UITableViewCell {
@IBOutlet weak dynamic var dnaLabel: UILabel?
@IBOutlet weak dynamic var mutateButton: UIButton?
var mutateButtonHandler: (() -> Void)?
@IBAction dynamic func mutateButtonTouchUpInside(sender: UIButton?) {
self.mutateButtonHandler?()
}
}

View File

@@ -0,0 +1,25 @@
//
// 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 dna: Int64
@NSManaged var hasHead: Bool
@NSManaged var hasTail: Bool
// MARK: OrganismProtocol
func mutate() {
self.hasHead = arc4random_uniform(2) == 1
self.hasTail = arc4random_uniform(2) == 1
}
}

View File

@@ -0,0 +1,27 @@
//
// 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 dna: Int64
@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)
}
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,23 @@
//
// 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) throws {
try super.createDestinationInstancesForSourceInstance(sInstance, entityMapping: mapping, manager: manager)
for dInstance in manager.destinationInstancesForEntityMappingNamed(mapping.name, sourceInstances: [sInstance]) {
dInstance.setValue(false, forKey: "hasVertebrae")
dInstance.setValue(sInstance.valueForKey("numberOfFlippers"), forKey: "numberOfLimbs")
}
}
}

View File

@@ -0,0 +1,29 @@
//
// 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 dna: Int64
@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
}
}

File diff suppressed because one or more lines are too long

View File

@@ -3,6 +3,6 @@
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>_XCCurrentVersionName</key> <key>_XCCurrentVersionName</key>
<string>MigrationDemoV2.xcdatamodel</string> <string>MigrationDemoV3.xcdatamodel</string>
</dict> </dict>
</plist> </plist>

View File

@@ -1,7 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14D136" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic"> <model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14E46" minimumToolsVersion="Xcode 4.3">
<entity name="Organism" syncable="YES"/> <entity name="Organism" representedClassName="CoreStoreDemo.OrganismV1" syncable="YES">
<attribute name="dna" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="hasHead" optional="YES" attributeType="Boolean" syncable="YES"/>
<attribute name="hasTail" optional="YES" attributeType="Boolean" syncable="YES"/>
</entity>
<elements> <elements>
<element name="Organism" positionX="-36" positionY="9" width="128" height="45"/> <element name="Organism" positionX="-36" positionY="9" width="128" height="90"/>
</elements> </elements>
</model> </model>

View File

@@ -1,7 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?> <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14D136" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic"> <model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14E46" minimumToolsVersion="Xcode 4.3">
<entity name="Organism" syncable="YES"/> <entity name="Organism" representedClassName="CoreStoreDemo.OrganismV2" syncable="YES">
<attribute name="dna" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="hasHead" attributeType="Boolean" syncable="YES"/>
<attribute name="hasTail" attributeType="Boolean" syncable="YES"/>
<attribute name="numberOfFlippers" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
</entity>
<elements> <elements>
<element name="Organism" positionX="-36" positionY="9" width="128" height="45"/> <element name="Organism" positionX="-36" positionY="9" width="128" height="105"/>
</elements> </elements>
</model> </model>

View File

@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14E46" minimumToolsVersion="Xcode 4.3">
<entity name="Organism" representedClassName="CoreStoreDemo.OrganismV3" syncable="YES">
<attribute name="dna" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="hasHead" attributeType="Boolean" syncable="YES"/>
<attribute name="hasTail" attributeType="Boolean" syncable="YES"/>
<attribute name="hasVertebrae" attributeType="Boolean" syncable="YES"/>
<attribute name="numberOfLimbs" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
</entity>
<elements>
<element name="Organism" positionX="-36" positionY="9" width="128" height="120"/>
</elements>
</model>

View File

@@ -18,13 +18,13 @@ private struct Static {
static let facebookStack: DataStack = { static let facebookStack: DataStack = {
let dataStack = DataStack(modelName: "StackSetupDemo") let dataStack = DataStack(modelName: "StackSetupDemo")
dataStack.addSQLiteStoreAndWait( try! dataStack.addSQLiteStoreAndWait(
"AccountsDemo_FB_Male.sqlite", fileName: "AccountsDemo_FB_Male.sqlite",
configuration: maleConfiguration, configuration: maleConfiguration,
resetStoreOnMigrationFailure: true resetStoreOnMigrationFailure: true
) )
dataStack.addSQLiteStoreAndWait( try! dataStack.addSQLiteStoreAndWait(
"AccountsDemo_FB_Female.sqlite", fileName: "AccountsDemo_FB_Female.sqlite",
configuration: femaleConfiguration, configuration: femaleConfiguration,
resetStoreOnMigrationFailure: true resetStoreOnMigrationFailure: true
) )
@@ -52,13 +52,13 @@ private struct Static {
static let twitterStack: DataStack = { static let twitterStack: DataStack = {
let dataStack = DataStack(modelName: "StackSetupDemo") let dataStack = DataStack(modelName: "StackSetupDemo")
dataStack.addSQLiteStoreAndWait( try! dataStack.addSQLiteStoreAndWait(
"AccountsDemo_TW_Male.sqlite", fileName: "AccountsDemo_TW_Male.sqlite",
configuration: maleConfiguration, configuration: maleConfiguration,
resetStoreOnMigrationFailure: true resetStoreOnMigrationFailure: true
) )
dataStack.addSQLiteStoreAndWait( try! dataStack.addSQLiteStoreAndWait(
"AccountsDemo_TW_Female.sqlite", fileName: "AccountsDemo_TW_Female.sqlite",
configuration: femaleConfiguration, configuration: femaleConfiguration,
resetStoreOnMigrationFailure: true resetStoreOnMigrationFailure: true
) )
@@ -138,7 +138,7 @@ class StackSetupDemoViewController: UITableViewController {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell { override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell") as! UITableViewCell let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell")!
let account = self.accounts[indexPath.section][indexPath.row] let account = self.accounts[indexPath.section][indexPath.row]
cell.textLabel?.text = account.name cell.textLabel?.text = account.name
@@ -176,11 +176,11 @@ class StackSetupDemoViewController: UITableViewController {
// MARK: Private // MARK: Private
@IBOutlet weak var accountTypeLabel: UILabel? @IBOutlet private dynamic weak var accountTypeLabel: UILabel?
@IBOutlet weak var nameLabel: UILabel? @IBOutlet private dynamic weak var nameLabel: UILabel?
@IBOutlet weak var friendsLabel: UILabel? @IBOutlet private dynamic weak var friendsLabel: UILabel?
func updateDetailsWithAccount(account: UserAccount) { private func updateDetailsWithAccount(account: UserAccount) {
self.accountTypeLabel?.text = account.accountType self.accountTypeLabel?.text = account.accountType
self.nameLabel?.text = account.name self.nameLabel?.text = account.name

View File

@@ -16,10 +16,10 @@ import GCDKit
private struct Static { private struct Static {
static let placeController: ManagedObjectController<Place> = { static let placeController: ObjectMonitor<Place> = {
CoreStore.addSQLiteStoreAndWait( try! CoreStore.addSQLiteStoreAndWait(
"PlaceDemo.sqlite", fileName: "PlaceDemo.sqlite",
configuration: "TransactionsDemo", configuration: "TransactionsDemo",
resetStoreOnMigrationFailure: true resetStoreOnMigrationFailure: true
) )
@@ -37,14 +37,14 @@ private struct Static {
place = CoreStore.fetchOne(From(Place)) place = CoreStore.fetchOne(From(Place))
} }
return CoreStore.observeObject(place!) return CoreStore.monitorObject(place!)
}() }()
} }
// MARK: - TransactionsDemoViewController // MARK: - TransactionsDemoViewController
class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, ManagedObjectObserver { class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, ObjectObserver {
// MARK: NSObject // MARK: NSObject
@@ -100,7 +100,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Manag
// MARK: MKMapViewDelegate // MARK: MKMapViewDelegate
func mapView(mapView: MKMapView!, viewForAnnotation annotation: MKAnnotation!) -> MKAnnotationView! { func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "MKAnnotationView" let identifier = "MKAnnotationView"
var annotationView: MKPinAnnotationView! = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier) as? MKPinAnnotationView var annotationView: MKPinAnnotationView! = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier) as? MKPinAnnotationView
@@ -120,14 +120,14 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Manag
} }
// MARK: ManagedObjectObserver // MARK: ObjectObserver
func managedObjectWillUpdate(objectController: ManagedObjectController<Place>, object: Place) { func objectMonitor(monitor: ObjectMonitor<Place>, willUpdateObject object: Place) {
// none // none
} }
func managedObjectWasUpdated(objectController: ManagedObjectController<Place>, object: Place, changedPersistentKeys: Set<KeyPath>) { func objectMonitor(monitor: ObjectMonitor<Place>, didUpdateObject object: Place, changedPersistentKeys: Set<KeyPath>) {
if let mapView = self.mapView { if let mapView = self.mapView {
@@ -143,7 +143,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Manag
} }
} }
func managedObjectWasDeleted(objectController: ManagedObjectController<Place>, object: Place) { func objectMonitor(monitor: ObjectMonitor<Place>, didDeleteObject object: Place) {
// none // none
} }
@@ -187,17 +187,17 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Manag
self.geocoder?.cancelGeocode() self.geocoder?.cancelGeocode()
var geocoder = CLGeocoder() let geocoder = CLGeocoder()
self.geocoder = geocoder self.geocoder = geocoder
geocoder.reverseGeocodeLocation( geocoder.reverseGeocodeLocation(
CLLocation(latitude: place.latitude, longitude: place.longitude), CLLocation(latitude: place.latitude, longitude: place.longitude),
completionHandler: { [weak self] (placemarks, error) -> Void in completionHandler: { [weak self] (placemarks, error) -> Void in
if let strongSelf = self, let placemark = (placemarks as? [CLPlacemark])?.first { if let placemark = placemarks?.first, let addressDictionary = placemark.addressDictionary {
let place = transaction.edit(Static.placeController.object) let place = transaction.edit(Static.placeController.object)
place?.title = placemark.name place?.title = placemark.name
place?.subtitle = ABCreateStringWithAddressDictionary(placemark.addressDictionary, true) place?.subtitle = ABCreateStringWithAddressDictionary(addressDictionary, true)
transaction.commit { (_) -> Void in } transaction.commit { (_) -> Void in }
} }

View File

@@ -25,6 +25,8 @@
import UIKit import UIKit
import XCTest import XCTest
@testable
import CoreStore import CoreStore
class CoreStoreTests: XCTestCase { class CoreStoreTests: XCTestCase {
@@ -41,28 +43,64 @@ class CoreStoreTests: XCTestCase {
super.tearDown() super.tearDown()
} }
func testMigrationChains() {
let emptyChain: MigrationChain = nil
XCTAssertTrue(emptyChain.valid, "emptyChain.valid")
XCTAssertTrue(emptyChain.empty, "emptyChain.empty")
let normalChain: MigrationChain = "version1"
XCTAssertTrue(normalChain.valid, "normalChain.valid")
XCTAssertTrue(normalChain.empty, "normalChain.empty")
let linearChain: MigrationChain = ["version1", "version2", "version3", "version4"]
XCTAssertTrue(linearChain.valid, "linearChain.valid")
XCTAssertFalse(linearChain.empty, "linearChain.empty")
let treeChain: MigrationChain = [
"version1": "version4",
"version2": "version3",
"version3": "version4"
]
XCTAssertTrue(treeChain.valid, "treeChain.valid")
XCTAssertFalse(treeChain.empty, "treeChain.empty")
// The cases below will trigger assertion failures internally
// let linearLoopChain: MigrationChain = ["version1", "version2", "version1", "version3", "version4"]
// XCTAssertFalse(linearLoopChain.valid, "linearLoopChain.valid")
//
// let treeAmbiguousChain: MigrationChain = [
// "version1": "version4",
// "version2": "version3",
// "version1": "version2",
// "version3": "version4"
// ]
// XCTAssertFalse(treeAmbiguousChain.valid, "treeAmbiguousChain.valid")
}
func testExample() { func testExample() {
let stack = DataStack() let stack = DataStack(modelName: "Model", bundle: NSBundle(forClass: self.dynamicType))
CoreStore.defaultStack = stack CoreStore.defaultStack = stack
XCTAssert(CoreStore.defaultStack === stack, "CoreStore.defaultStack === stack") XCTAssert(CoreStore.defaultStack === stack, "CoreStore.defaultStack === stack")
switch stack.addSQLiteStoreAndWait("ConfigStore1.sqlite", configuration: "Config1", resetStoreOnMigrationFailure: true){ do {
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore1.sqlite", configuration: "Config1", resetStoreOnMigrationFailure: true)
}
catch let error as NSError {
case .Failure(let error):
XCTFail(error.description) XCTFail(error.description)
default:
break
} }
switch stack.addSQLiteStoreAndWait("ConfigStore2.sqlite", configuration: "Config2", resetStoreOnMigrationFailure: true){ do {
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore2.sqlite", configuration: "Config2", resetStoreOnMigrationFailure: true)
}
catch let error as NSError {
case .Failure(let error):
XCTFail(error.description) XCTFail(error.description)
default:
break
} }
let detachedTransaction = CoreStore.beginDetached() let detachedTransaction = CoreStore.beginDetached()
@@ -187,7 +225,7 @@ class CoreStoreTests: XCTestCase {
Select("testString", .Count("testString", As: "count")), Select("testString", .Count("testString", As: "count")),
GroupBy("testString") GroupBy("testString")
) )
println(counts) print(counts)
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()") XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
switch result { switch result {
@@ -231,6 +269,18 @@ class CoreStoreTests: XCTestCase {
transaction.commit() transaction.commit()
} }
CoreStore.beginSynchronous({ (transaction) -> Void in
if let obj = CoreStore.fetchOne(From(TestEntity2)) {
let oldID = obj.testEntityID
obj.testEntityID = 0
obj.testEntityID = oldID
}
transaction.commit()
})
let objs1 = CoreStore.fetchAll(From(TestEntity1)) let objs1 = CoreStore.fetchAll(From(TestEntity1))
XCTAssertNotNil(objs1, "objs1 != nil") XCTAssertNotNil(objs1, "objs1 != nil")
XCTAssertTrue(objs1?.count == 0, "objs1?.count == 0") XCTAssertTrue(objs1?.count == 0, "objs1?.count == 0")
@@ -325,9 +375,12 @@ class CoreStoreTests: XCTestCase {
private func deleteStores() { private func deleteStores() {
NSFileManager.defaultManager().removeItemAtURL( do {
NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL,
error: nil try NSFileManager.defaultManager().removeItemAtURL(
) NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first!
)
}
catch _ { }
} }
} }

View File

@@ -7,7 +7,7 @@
<key>CFBundleExecutable</key> <key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string> <string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key> <key>CFBundleIdentifier</key>
<string>com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)</string> <string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key> <key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string> <string>6.0</string>
<key>CFBundleName</key> <key>CFBundleName</key>

399
README.md
View File

@@ -4,34 +4,91 @@
[![License](https://img.shields.io/cocoapods/l/CoreStore.svg?style=flat)](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE) [![License](https://img.shields.io/cocoapods/l/CoreStore.svg?style=flat)](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
Simple, elegant, and smart Core Data programming with Swift Unleashing the real power of Core Data with the elegance and safety of Swift
(Swift 1.2, iOS 8+) (Swift 2.0, iOS 8+)
Check out the [develop branch](https://github.com/JohnEstropia/CoreStore/tree/develop) for Swift 2.0 and Incremental Migrations support!
[Click here for a wiki version of this README](https://github.com/JohnEstropia/CoreStore/wiki) [Click here for a wiki version of this README](https://github.com/JohnEstropia/CoreStore/wiki)
## What CoreStore does better: ## 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.
- 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.
- **New in 1.0.0:** Incremental Migrations! Just tell the data stack the sequence of model versions and CoreStore will automatically use incremental migrations if needed on stores added to that stack.
- Ability to plug-in your own logging framework - 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. - 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 - 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 - 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. - 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. - Exposes clean and convenient API designed around Swifts code elegance and type safety.
- Clean API designed around Swifts 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
Quick-setup:
## Contents
- [TL;DR (a.k.a. sample codes)](#tldr-aka-sample-codes)
- [Architecture](#architecture)
- CoreStore Tutorials (All of these have demos in the **CoreStoreDemo** app project!)
- [Setting up](#setting-up)
- [Migrations](#migrations)
- [Incremental migrations](#incremental-migrations)
- [Saving and processing transactions](#saving-and-processing-transactions)
- [Transaction types](#transaction-types)
- [Asynchronous transactions](#asynchronous-transactions)
- [Synchronous transactions](#synchronous-transactions)
- [Detached transactions](#detached-transactions)
- [Creating objects](#creating-objects)
- [Updating objects](#updating-objects)
- [Deleting objects](#deleting-objects)
- [Fetching and querying](#fetching-and-querying)
- [`From` clause](#from-clause)
- [Fetching](#fetching)
- [`Where` clause](#where-clause)
- [`OrderBy` clause](#orderby-clause)
- [`Tweak` clause](#tweak-clause)
- [Querying](#querying)
- [`Select<T>` clause](#selectt-clause)
- [`GroupBy` clause](#groupby-clause)
- [Logging and error handling](#logging-and-error-handling)
- [Observing changes and notifications](#observing-changes-and-notifications)
- [Observe a single object](#observe-a-single-object)
- [Observe a list of objects](#observe-a-list-of-objects)
- [Roadmap](#roadmap)
- [Installation](#installation)
- [Changesets](#changesets)
- [Upgrading from v0.2.0 to 1.0.0](#upgrading-from-v020-to-100)
## TL;DR (a.k.a. sample codes)
Setting-up with incremental migration support:
```swift ```swift
CoreStore.addSQLiteStoreAndWait("MyStore.sqlite") CoreStore.defaultStack = DataStack(
modelName: "MyStore",
migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"]
)
``` ```
Simple transactions: Adding a store:
```swift
do {
try CoreStore.addSQLiteStore(
fileName: "MyStore.sqlite",
completion: { (result) -> Void in
// ...
}
)
}
catch {
// ...
}
```
Starting transactions:
```swift ```swift
CoreStore.beginAsynchronous { (transaction) -> Void in CoreStore.beginAsynchronous { (transaction) -> Void in
let person = transaction.create(Into(MyPersonEntity)) let person = transaction.create(Into(MyPersonEntity))
@@ -40,14 +97,14 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
transaction.commit { (result) -> Void in transaction.commit { (result) -> Void in
switch result { switch result {
case .Success(let hasChanges): println("success!") case .Success(let hasChanges): print("success!")
case .Failure(let error): println(error) case .Failure(let error): print(error)
} }
} }
} }
``` ```
Easy fetching: Fetching objects:
```swift ```swift
let people = CoreStore.fetchAll(From(MyPersonEntity)) let people = CoreStore.fetchAll(From(MyPersonEntity))
``` ```
@@ -62,7 +119,7 @@ let people = CoreStore.fetchAll(
) )
``` ```
Simple queries: Querying values:
```swift ```swift
let maxAge = CoreStore.queryValue( let maxAge = CoreStore.queryValue(
From(MyPersonEntity), From(MyPersonEntity),
@@ -70,21 +127,13 @@ 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)
## <a id="architecture"></a>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. 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: If you are already familiar with the inner workings of CoreData, here is a mapping of `CoreStore` abstractions:
@@ -107,10 +156,15 @@ This allows for a butter-smooth main thread, while still taking advantage of saf
## <a id="setup"></a>Setting up ## Setting up
The simplest way to initialize CoreStore is to add a default store to the default stack: The simplest way to initialize CoreStore is to add a default store to the default stack:
```swift ```swift
CoreStore.addSQLiteStoreAndWait() do {
try CoreStore.addSQLiteStoreAndWait()
}
catch {
// ...
}
``` ```
This one-liner does the following: This one-liner does the following:
- Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack` - Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack`
@@ -122,38 +176,48 @@ For most cases, this configuration is usable as it is. But for more hardcore set
```swift ```swift
let dataStack = DataStack(modelName: "MyModel") // loads from the "MyModel.xcdatamodeld" file let dataStack = DataStack(modelName: "MyModel") // loads from the "MyModel.xcdatamodeld" file
switch dataStack.addInMemoryStore(configuration: "Config1") { // creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file do {
case .Success(let persistentStore): // persistentStore is an NSPersistentStore instance // creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file
println("Successfully created an in-memory store: \(persistentStore)" let persistentStore = try dataStack.addInMemoryStore(configuration: "Config1") // persistentStore is an NSPersistentStore instance
case .Failure(let error): // error is an NSError instance print("Successfully created an in-memory store: \(persistentStore)"
println("Failed creating an in-memory store with error: \(error.description)" }
catch let error as NSError {
print("Failed creating an in-memory store with error: \(error.description)"
} }
switch dataStack.addSQLiteStoreAndWait( do {
fileURL: sqliteFileURL, // set the target file URL for the sqlite file try dataStack.addSQLiteStoreAndWait(
configuration: "Config2", // use entities from the "Config2" configuration in the .xcdatamodeld file fileURL: sqliteFileURL, // set the target file URL for the sqlite file
automigrating: true, // automatically run lightweight migrations or entity policy migrations when needed configuration: "Config2", // use entities from the "Config2" configuration in the .xcdatamodeld file
resetStoreOnMigrationFailure: true) { // delete and recreate the sqlite file when migration conflicts occur (useful when debugging) automigrating: true, // automatically run lightweight migrations or entity policy migrations when needed
case .Success(let persistentStore): // persistentStore is an NSPersistentStore instance resetStoreOnMigrationFailure: true)
println("Successfully created an sqlite store: \(persistentStore)" print("Successfully created an sqlite store: \(persistentStore)"
case .Failure(let error): // error is an NSError instance }
println("Failed creating an sqlite store with error: \(error.description)" catch let error as NSError {
print("Failed creating an sqlite store with error: \(error.description)"
} }
CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on 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)
<img src="https://cloud.githubusercontent.com/assets/3029684/8333192/e52cfaac-1acc-11e5-9902-08724f9f1324.png" alt="xcode configurations screenshot" height=212 />
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 ```swift
class MyViewController: UIViewController { class MyViewController: UIViewController {
let dataStack = DataStack(modelName: "MyModel") let dataStack = DataStack(modelName: "MyModel")
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
self.dataStack.addSQLiteStoreAndWait() do {
try self.dataStack.addSQLiteStoreAndWait()
}
catch { // ...
}
} }
func methodToBeCalledLaterOn() { func methodToBeCalledLaterOn() {
let objects = self.dataStack.fetchAll(From(MyEntity)) let objects = self.dataStack.fetchAll(From(MyEntity))
println(objects) print(objects)
} }
} }
``` ```
@@ -162,18 +226,29 @@ The difference is when you set the stack as the `CoreStore.defaultStack`, you ca
class MyViewController: UIViewController { class MyViewController: UIViewController {
override func viewDidLoad() { override func viewDidLoad() {
super.viewDidLoad() super.viewDidLoad()
CoreStore.addSQLiteStoreAndWait() do {
try CoreStore.addSQLiteStoreAndWait()
}
catch { // ...
}
} }
func methodToBeCalledLaterOn() { func methodToBeCalledLaterOn() {
let objects = CoreStore.fetchAll(From(MyEntity)) let objects = CoreStore.fetchAll(From(MyEntity))
println(objects) print(objects)
} }
} }
``` ```
## Migrations
(README pending)
## <a id="transactions">Saving and processing transactions</a> ### Incremental migrations
(README pending)
## 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: 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 ```swift
let dataStack = self.dataStack let dataStack = self.dataStack
@@ -191,9 +266,12 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
``` ```
The `commit()` method saves the changes to the persistent store. If `commit()` is not called when the transaction block completes, all changes within the transaction is discarded. The `commit()` method saves the changes to the persistent store. If `commit()` is not called when the transaction block completes, all changes within the transaction is discarded.
The examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at you disposal: *asynchronous*, *synchronous*, and *detached*. The examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at your 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 ```swift
CoreStore.beginAsynchronous { (transaction) -> Void in CoreStore.beginAsynchronous { (transaction) -> Void in
// make changes // make changes
@@ -202,7 +280,8 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
``` ```
Transactions created from `beginAsynchronous(...)` are instances of `AsynchronousDataTransaction`. 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 ```swift
CoreStore.beginSynchronous { (transaction) -> Void in CoreStore.beginSynchronous { (transaction) -> Void in
// make changes // make changes
@@ -213,7 +292,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. 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 ```swift
let transaction = CoreStore.beginDetached() let transaction = CoreStore.beginDetached()
// make changes // make changes
@@ -236,6 +316,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. 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 ### Creating objects
The `create(...)` method accepts an `Into` clause which specifies the entity for the object you want to create: The `create(...)` method accepts an `Into` clause which specifies the entity for the object you want to create:
```swift ```swift
let person = transaction.create(Into(MyPersonEntity)) let person = transaction.create(Into(MyPersonEntity))
@@ -244,7 +325,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 - 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 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 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: If the entity exists in multiple configurations, you need to provide the configuration name for the destination persistent store:
@@ -254,7 +335,7 @@ or if the persistent store is the auto-generated "Default" configuration, specif
let person = transaction.create(Into<MyPersonEntity>(nil)) let person = transaction.create(Into<MyPersonEntity>(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 ### Updating objects
@@ -278,7 +359,7 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
transaction.commit() transaction.commit()
} }
``` ```
*(For more about fetching, read [Fetching and querying](#fetch_query))* *(For more about fetching, see [Fetching and querying](#fetching-and-querying))*
**Do not update an instance that was not created/fetched from the transaction.** If you have a reference to the object already, use the transaction's `edit(...)` method to get an editable proxy instance for that object: **Do not update an instance that was not created/fetched from the transaction.** If you have a reference to the object already, use the transaction's `edit(...)` method to get an editable proxy instance for that object:
```swift ```swift
@@ -306,9 +387,10 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
transaction.commit() transaction.commit()
} }
``` ```
### Deleting objects ### 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 ```swift
let john: MyPersonEntity = // ... let john: MyPersonEntity = // ...
@@ -338,7 +420,8 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
transaction.commit() transaction.commit()
} }
``` ```
## <a id="fetch_query"></a>Fetching and querying
## Fetching and Querying
Before we dive in, be aware that CoreStore distinguishes between *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: - 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 - results need to be `NSManagedObject` instances
@@ -346,9 +429,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: - 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) - 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. - 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 - 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: 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 ```swift
let people = CoreStore.fetchAll(From(MyPersonEntity)) let people = CoreStore.fetchAll(From(MyPersonEntity))
@@ -356,7 +440,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. `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 ```swift
let people = CoreStore.fetchAll(From<MyPersonEntity>("Config1")) // ignore objects in persistent stores other than the "Config1" configuration let people = CoreStore.fetchAll(From<MyPersonEntity>("Config1")) // ignore objects in persistent stores other than the "Config1" configuration
``` ```
@@ -366,19 +450,19 @@ let person = CoreStore.fetchAll(From<MyPersonEntity>(nil))
``` ```
Now we know how to use a `From` clause, let's move on to fetching and querying. 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. 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. - `fetchAll(...)` - returns an array of all objects that match the criteria.
- `fetchOne(_:_:)` - returns the first object that match the criteria. - `fetchOne(...)` - returns the first object that match the criteria.
- `fetchCount(_:_:)` - returns the number of objects 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. - `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. 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): 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 ```swift
@@ -399,7 +483,7 @@ var people = CoreStore.fetchAll(
Where(predicate) // predicate initializer 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 ```swift
var people = CoreStore.fetchAll( var people = CoreStore.fetchAll(
From(MyPersonEntity), From(MyPersonEntity),
@@ -408,7 +492,7 @@ var people = CoreStore.fetchAll(
``` ```
If you do not provide a `Where` clause, all objects that belong to the specified `From` will be returned. 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. 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 ```swift
@@ -417,7 +501,7 @@ var mostValuablePeople = CoreStore.fetchAll(
OrderBy(.Descending("rating"), .Ascending("surname")) 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: You can use the `+` and `+=` operator to append `OrderBy`s together. This is useful when sorting conditionally:
```swift ```swift
@@ -431,9 +515,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 ```swift
var people = CoreStore.fetchAll( var people = CoreStore.fetchAll(
From(MyPersonEntity), From(MyPersonEntity),
@@ -449,21 +533,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. 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. `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 ### 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:
- `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. 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:
- `queryAttributes(_:_:_:)` - returns an array of dictionaries containing attribute keys with their corresponding values.
- `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<T>` clause, and an optional series of `Where`, `OrderBy`, `GroupBy`, and/or `Tweak` clauses. Both methods above accept the same parameters: a required `From` clause, a required `Select<T>` 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<T>` and `GroupBy` 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<T>` and `GroupBy` clauses.
**`Select<T>` clause** #### `Select<T>` clause
The `Select<T>` clause specifies the target attribute/aggregate key and the return type: The `Select<T>` clause specifies the target attribute/aggregate key, as well as the expected return type:
```swift ```swift
let johnsAge = CoreStore.queryValue( let johnsAge = CoreStore.queryValue(
From(MyPersonEntity), From(MyPersonEntity),
@@ -471,7 +556,7 @@ let johnsAge = CoreStore.queryValue(
Where("name == %@", "John Smith") 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<Int>` generic type. For `queryValue(...)`, the following are allowed as the return type (and as the generic type for `Select<T>`): 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<Int>` generic type. For `queryValue(...)`, the following are allowed as the return type (and therefore as the generic type for `Select<T>`):
- `Bool` - `Bool`
- `Int8` - `Int8`
- `Int16` - `Int16`
@@ -488,7 +573,7 @@ The example above queries the "age" property for the first object that matches t
- `NSManagedObjectID` - `NSManagedObjectID`
- `NSString` - `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 ```swift
let allAges = CoreStore.queryAttributes( let allAges = CoreStore.queryAttributes(
From(MyPersonEntity), From(MyPersonEntity),
@@ -518,7 +603,7 @@ let personJSON = CoreStore.queryAttributes(
) )
``` ```
`personJSON` will then have the value: `personJSON` will then have the value:
```json ```swift
[ [
[ [
"name": "John Smith", "name": "John Smith",
@@ -571,7 +656,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. 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 ```swift
@@ -595,7 +680,7 @@ this returns dictionaries that shows the count for each `"age"`:
] ]
``` ```
## <a id="logging"></a>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. 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 ```swift
final class MyLogger: CoreStoreLogger { final class MyLogger: CoreStoreLogger {
@@ -618,73 +703,84 @@ CoreStore.logger = MyLogger()
``` ```
Doing so channels all logging calls to your logger. 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.
## <a id="observing"></a>Observing changes and notifications ## Observing changes and notifications
CoreStore provides type-safe wrappers for observing managed objects: CoreStore provides type-safe wrappers for observing managed objects:
- `ManagedObjectController`: use to observe changes to a single `NSManagedObject` instance (instead of Key-Value Observing) - `ObjectMonitor`: use to monitor 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`) - `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 ```swift
class MyViewController: UIViewController, ManagedObjectObserver { class MyViewController: UIViewController, ObjectObserver {
func managedObjectWillUpdate(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity) { func objectMonitor(monitor: ObjectMonitor<MyPersonEntity>, willUpdateObject object: MyPersonEntity) {
// ... // ...
} }
func managedObjectWasUpdated(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity, changedPersistentKeys: Set<KeyPath>) { func objectMonitor(monitor: ObjectMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, changedPersistentKeys: Set<KeyPath>) {
// ... // ...
} }
func managedObjectWasDeleted(objectController: ManagedObjectController<MyPersonEntity>, object: MyPersonEntity) { func objectMonitor(monitor: ObjectMonitor<MyPersonEntity>, 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 ```swift
let person: MyPersonEntity = // ... let person: MyPersonEntity = // ...
self.objectController = CoreStore.observeObject(person) self.monitor = CoreStore.monitorObject(person)
self.objectController.addObserver(self) 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 ### Observe a list of objects
To observe a list of objects, implement one of the `ManagedObjectListChangeObserver` protocols and specify the `EntityType`: To observe a list of objects, implement one of the `ListObserver` protocols and specify the `EntityType`:
```swift ```swift
class MyViewController: UIViewController, ManagedObjectListChangeObserver { class MyViewController: UIViewController, ListObserver {
func managedObjectListWillChange(listController: ManagedObjectListController<MyPersonEntity>) { func listMonitorWillChange(monitor: ListMonitor<MyPersonEntity>) {
// ... // ...
} }
func managedObjectListDidChange(listController: ManagedObjectListController<MyPersonEntity>) { func listMonitorDidChange(monitor: ListMonitor<MyPersonEntity>) {
// ... // ...
} }
} }
``` ```
Including `ManagedObjectListChangeObserver`, there are 3 observer protocols you can implement depending on how detailed you need to handle a change notification: Including `ListObserver`, 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: - `ListObserver`: lets you handle these callback methods:
- `func managedObjectListWillChange(listController: ManagedObjectListController<T>)`
- `func managedObjectListDidChange(listController: ManagedObjectListController<T>)`
- `ManagedObjectListObjectObserver`: in addition to `ManagedObjectListChangeObserver` methods, also lets you handle object inserts, updates, and deletes:
- `func managedObjectList(listController: ManagedObjectListController<T>, didInsertObject object: T, toIndexPath indexPath: NSIndexPath)`
- `func managedObjectList(listController: ManagedObjectListController<T>, didDeleteObject object: T, fromIndexPath indexPath: NSIndexPath)`
- `func managedObjectList(listController: ManagedObjectListController<T>, didUpdateObject object: T, atIndexPath indexPath: NSIndexPath)`
- `func managedObjectList(listController: ManagedObjectListController<T>, 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<T>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)`
- `func managedObjectList(listController: ManagedObjectListController<T>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)`
We then need to create a `ManagedObjectListController` instance and register our `ManagedObjectListChangeObserver` as an observer:
```swift ```swift
self.listController = CoreStore.observeObjectList( func listMonitorWillChange(monitor: ListMonitor<MyPersonEntity>)
func listMonitorDidChange(monitor: ListMonitor<MyPersonEntity>)
```
- `ListObjectObserver`: in addition to `ListObserver` methods, also lets you handle object inserts, updates, and deletes:
```swift
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath)
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath)
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath)
func listMonitor(monitor: ListMonitor<MyPersonEntity>, 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<MyPersonEntity>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
func listMonitor(monitor: ListMonitor<MyPersonEntity>, 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), From(MyPersonEntity),
Where("age > 30"), Where("age > 30"),
OrderBy(.Ascending("name")), OrderBy(.Ascending("name")),
@@ -692,22 +788,22 @@ self.listController = CoreStore.observeObjectList(
fetchRequest.fetchBatchSize = 20 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 ```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 ```swift
self.listController = CoreStore.observeSectionedList( self.monitor = CoreStore.monitorSectionedList(
From(MyPersonEntity), From(MyPersonEntity),
SectionedBy("age"), SectionBy("age"),
Where("gender", isEqualTo: "M"), Where("gender", isEqualTo: "M"),
OrderBy(.Ascending("age"), .Ascending("name")), OrderBy(.Ascending("age"), .Ascending("name")),
Tweak { (fetchRequest) -> Void in Tweak { (fetchRequest) -> Void in
@@ -715,13 +811,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 ```swift
self.listController = CoreStore.observeSectionedList( self.monitor = CoreStore.monitorSectionedList(
From(MyPersonEntity), From(MyPersonEntity),
SectionedBy("age") { (sectionName) -> String? in SectionBy("age") { (sectionName) -> String? in
"\(sectionName) years old" "\(sectionName) years old"
}, },
OrderBy(.Ascending("age"), .Ascending("name")) OrderBy(.Ascending("age"), .Ascending("name"))
@@ -730,7 +826,7 @@ self.listController = CoreStore.observeSectionedList(
This is useful when implementing a `UITableViewDelegate`'s section header: This is useful when implementing a `UITableViewDelegate`'s section header:
```swift ```swift
func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? { 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 // sectionInfo is an NSFetchedResultsSectionInfo instance
return sectionInfo.name return sectionInfo.name
} }
@@ -739,20 +835,23 @@ func tableView(tableView: UITableView, titleForHeaderInSection section: Int) ->
To access the objects of a sectioned list, use an `NSIndexPath` or a tuple: To access the objects of a sectioned list, use an `NSIndexPath` or a tuple:
```swift ```swift
let indexPath = NSIndexPath(forRow: 2, inSection: 1) let indexPath = NSIndexPath(forRow: 2, inSection: 1)
let person1 = self.listController[indexPath] let person1 = self.monitor[indexPath]
let person2 = self.listController[1, 2] let person2 = self.monitor[1, 2]
// person1 and person2 are the same object // person1 and person2 are the same object
``` ```
# TODO
# Roadmap
- Data importing utilities for transactions - Data importing utilities for transactions
- Migration utilities
- Support iCloud stores - Support iCloud stores
# Installation # Installation
- Requires iOS 8 SDK and above - Requires:
- Swift 1.2 - iOS 8 SDK and above
- Swift 1.2
- Dependencies:
- [GCDKit](https://github.com/JohnEstropia/GCDKit)
### Install with Cocoapods ### Install with Cocoapods
``` ```
@@ -762,18 +861,38 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
### Install with Carthage ### Install with Carthage
``` ```
github "JohnEstropia/CoreStore" >= 0.2.1 github "JohnEstropia/CoreStore" >= 0.2.0
``` ```
### Install as Git Submodule ### Install as Git Submodule
``` ```
git submodule add https://github.com/JohnEstropia/CoreStore.git <destination directory> git submodule add https://github.com/JohnEstropia/CoreStore.git <destination directory>
``` ```
Drag and drop **CoreStore.xcodeproj** to your project.
#### To install as a framework: #### To install as a framework:
Drag and drop **CoreStore.xcodeproj** to your project. Drag and drop **CoreStore.xcodeproj** to your project.
#### To include directly in your app module: #### To include directly in your app module:
Add all *.swift* files to your project. Add all *.swift* files to your project.
# Changesets
### Upgrading 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.
- Several methods now `throw` errors insted of returning a result `enum`.
- New migration utilities! (README still pending) Check out *DataStack+Migration.swift* and *CoreStore+Migration.swift* for the new methods, as well as *DataStack.swift* for its new initializer.
# Contributions # Contributions
While CoreStore's design is pretty solid and the unit test and demo app work well, CoreStore is pretty much still in its early stage. With more exposure to production code usage and criticisms from the developer community, CoreStore hopes to mature as well. While CoreStore's design is pretty solid and the unit test and demo app work well, CoreStore is pretty much still in its early stage. With more exposure to production code usage and criticisms from the developer community, CoreStore hopes to mature as well.
Please feel free to report any issues, suggestions, or criticisms! Please feel free to report any issues, suggestions, or criticisms!