mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-03-02 22:30:07 +01:00
Compare commits
41 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
682b13a8d3 | ||
|
|
2f935de04a | ||
|
|
ba4fb5e5cb | ||
|
|
d860da2507 | ||
|
|
2bcf8008c5 | ||
|
|
71477c0839 | ||
|
|
3348aa0bef | ||
|
|
2ed61fdb17 | ||
|
|
9cfad8a17a | ||
|
|
3bf34f33dc | ||
|
|
90bfaeaae8 | ||
|
|
a29a4b38fe | ||
|
|
5a96ef13f6 | ||
|
|
b92ee76907 | ||
|
|
67ccae4ef6 | ||
|
|
62b11309f3 | ||
|
|
8c6a7df731 | ||
|
|
b475afe79f | ||
|
|
a263851266 | ||
|
|
cbc3eb8887 | ||
|
|
2fb3263aa1 | ||
|
|
64aa97264e | ||
|
|
8066bf2a5a | ||
|
|
283104af3f | ||
|
|
83c724f584 | ||
|
|
59ad525786 | ||
|
|
b85d7521e1 | ||
|
|
4dbc4558e3 | ||
|
|
2a56c097f2 | ||
|
|
d4b95aed64 | ||
|
|
0d2650c54b | ||
|
|
6a3885edda | ||
|
|
1c6085ad82 | ||
|
|
2dae2ae39c | ||
|
|
a1e37975cc | ||
|
|
0c8a43c3b0 | ||
|
|
9676e3aca2 | ||
|
|
a34d2795af | ||
|
|
106789d592 | ||
|
|
edc51de030 | ||
|
|
f85eb2e057 |
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CoreStore"
|
||||
s.version = "1.0.0"
|
||||
s.version = "1.2.1"
|
||||
s.license = "MIT"
|
||||
s.summary = "Simple, elegant, and smart Core Data programming with Swift"
|
||||
s.homepage = "https://github.com/JohnEstropia/CoreStore"
|
||||
@@ -12,5 +12,5 @@ Pod::Spec.new do |s|
|
||||
s.source_files = "CoreStore", "CoreStore/**/*.{swift}"
|
||||
s.frameworks = "Foundation", "UIKit", "CoreData"
|
||||
s.requires_arc = true
|
||||
s.dependency "GCDKit", "1.1.0"
|
||||
s.dependency "GCDKit", "1.1.1"
|
||||
end
|
||||
@@ -18,6 +18,8 @@
|
||||
B56007161B4018AB00A9A8F9 /* MigrationChain.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56007151B4018AB00A9A8F9 /* MigrationChain.swift */; };
|
||||
B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */; };
|
||||
B56965241B356B820075EE4A /* MigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965231B356B820075EE4A /* MigrationResult.swift */; };
|
||||
B59D5C221B5BA34B00453479 /* NSFileManager+Setup.swift in Sources */ = {isa = PBXBuildFile; fileRef = B59D5C211B5BA34B00453479 /* NSFileManager+Setup.swift */; };
|
||||
B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A261201B64BFDB006EB6D3 /* MigrationType.swift */; };
|
||||
B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D1E22B19FA9FBC003B2874 /* NSError+CoreStore.swift */; };
|
||||
B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; };
|
||||
B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D372851A39CDDB00F583D9 /* TestEntity1.swift */; };
|
||||
@@ -25,6 +27,8 @@
|
||||
B5D39A0419FD00DE000E91BB /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0319FD00DE000E91BB /* UIKit.framework */; };
|
||||
B5D5E0CF1A4D6AAB006468AF /* TestEntity2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */; };
|
||||
B5D8080E1A3471A500A44484 /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D808021A34715700A44484 /* GCDKit.framework */; };
|
||||
B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */; };
|
||||
B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834BA1B7691F3001D3D50 /* Functions.swift */; };
|
||||
B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDB1AFF84500064E85B /* DataStack.swift */; };
|
||||
B5E84EE11AFF84500064E85B /* PersistentStoreResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EDE1AFF84500064E85B /* PersistentStoreResult.swift */; };
|
||||
B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E84EE31AFF84610064E85B /* DefaultLogger.swift */; };
|
||||
@@ -114,6 +118,8 @@
|
||||
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>"; };
|
||||
B56965231B356B820075EE4A /* MigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationResult.swift; sourceTree = "<group>"; };
|
||||
B59D5C211B5BA34B00453479 /* NSFileManager+Setup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSFileManager+Setup.swift"; sourceTree = "<group>"; };
|
||||
B5A261201B64BFDB006EB6D3 /* MigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationType.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>"; };
|
||||
B5D372851A39CDDB00F583D9 /* TestEntity1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity1.swift; sourceTree = "<group>"; };
|
||||
@@ -122,6 +128,8 @@
|
||||
B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity2.swift; sourceTree = "<group>"; };
|
||||
B5D806C51A34715700A44484 /* GCDKit.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; path = GCDKit.xcodeproj; sourceTree = "<group>"; };
|
||||
B5D9C8F61B160ED200E64F0E /* CoreStore.podspec */ = {isa = PBXFileReference; lastKnownFileType = text; path = CoreStore.podspec; sourceTree = SOURCE_ROOT; };
|
||||
B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseDataTransaction+Importing.swift"; sourceTree = "<group>"; };
|
||||
B5E834BA1B7691F3001D3D50 /* Functions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Functions.swift; sourceTree = "<group>"; };
|
||||
B5E84ED81AFF82360064E85B /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; };
|
||||
B5E84ED91AFF82360064E85B /* LICENSE */ = {isa = PBXFileReference; lastKnownFileType = text; path = LICENSE; sourceTree = SOURCE_ROOT; };
|
||||
B5E84EDB1AFF84500064E85B /* DataStack.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DataStack.swift; sourceTree = "<group>"; };
|
||||
@@ -217,6 +225,7 @@
|
||||
B5E84EDA1AFF84500064E85B /* Setting Up */,
|
||||
B5E84EE21AFF84610064E85B /* Logging */,
|
||||
B5E84EE91AFF846E0064E85B /* Saving and Processing */,
|
||||
B5E834B61B7630BD001D3D50 /* Importing Data */,
|
||||
B5E84EFD1AFF847B0064E85B /* Fetching and Querying */,
|
||||
B5E84F191AFF84860064E85B /* Observing */,
|
||||
B56964D11B22FF700075EE4A /* Migrating */,
|
||||
@@ -274,6 +283,7 @@
|
||||
B56964D31B22FFAD0075EE4A /* DataStack+Migration.swift */,
|
||||
B5FAD6AD1B518DCB00714891 /* CoreStore+Migration.swift */,
|
||||
B56007151B4018AB00A9A8F9 /* MigrationChain.swift */,
|
||||
B5A261201B64BFDB006EB6D3 /* MigrationType.swift */,
|
||||
B56965231B356B820075EE4A /* MigrationResult.swift */,
|
||||
);
|
||||
path = Migrating;
|
||||
@@ -304,6 +314,14 @@
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5E834B61B7630BD001D3D50 /* Importing Data */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */,
|
||||
);
|
||||
path = "Importing Data";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B5E84EDA1AFF84500064E85B /* Setting Up */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -330,11 +348,11 @@
|
||||
B56007101B3F6BD500A9A8F9 /* Into.swift */,
|
||||
B5E84EEB1AFF846E0064E85B /* BaseDataTransaction.swift */,
|
||||
B5E84EEA1AFF846E0064E85B /* AsynchronousDataTransaction.swift */,
|
||||
B5E84EEC1AFF846E0064E85B /* DataStack+Transaction.swift */,
|
||||
B5E84EF31AFF846E0064E85B /* SynchronousDataTransaction.swift */,
|
||||
B5E84EED1AFF846E0064E85B /* DetachedDataTransaction.swift */,
|
||||
B5E84EEC1AFF846E0064E85B /* DataStack+Transaction.swift */,
|
||||
B5E84EEE1AFF846E0064E85B /* CoreStore+Transaction.swift */,
|
||||
B5E84EF21AFF846E0064E85B /* SaveResult.swift */,
|
||||
B5E84EF31AFF846E0064E85B /* SynchronousDataTransaction.swift */,
|
||||
);
|
||||
path = "Saving and Processing";
|
||||
sourceTree = "<group>";
|
||||
@@ -401,6 +419,7 @@
|
||||
B5E84F2A1AFF849C0064E85B /* AssociatedObjects.swift */,
|
||||
B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */,
|
||||
B5FAD6AB1B51285300714891 /* MigrationManager.swift */,
|
||||
B59D5C211B5BA34B00453479 /* NSFileManager+Setup.swift */,
|
||||
B5E84F341AFF85470064E85B /* NSManagedObject+Transaction.swift */,
|
||||
B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */,
|
||||
B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */,
|
||||
@@ -408,6 +427,7 @@
|
||||
B5E84F331AFF85470064E85B /* NSManagedObjectContext+Transaction.swift */,
|
||||
B51BE0691B47FC4B0069F532 /* NSManagedObjectModel+Setup.swift */,
|
||||
B5E84F2D1AFF849C0064E85B /* WeakObject.swift */,
|
||||
B5E834BA1B7691F3001D3D50 /* Functions.swift */,
|
||||
);
|
||||
path = Internal;
|
||||
sourceTree = "<group>";
|
||||
@@ -549,6 +569,7 @@
|
||||
B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */,
|
||||
B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */,
|
||||
B5E84F131AFF847B0064E85B /* Where.swift in Sources */,
|
||||
B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */,
|
||||
B5E84F141AFF847B0064E85B /* DataStack+Querying.swift in Sources */,
|
||||
B56007141B3F6C2800A9A8F9 /* SectionBy.swift in Sources */,
|
||||
B5E84F371AFF85470064E85B /* NSManagedObjectContext+Transaction.swift in Sources */,
|
||||
@@ -571,12 +592,14 @@
|
||||
B5FAD6AC1B51285300714891 /* MigrationManager.swift in Sources */,
|
||||
B5E84EF61AFF846E0064E85B /* DataStack+Transaction.swift in Sources */,
|
||||
B5E84EDF1AFF84500064E85B /* DataStack.swift in Sources */,
|
||||
B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */,
|
||||
B5E84F231AFF84860064E85B /* ListMonitor.swift in Sources */,
|
||||
B5E84EF71AFF846E0064E85B /* DetachedDataTransaction.swift in Sources */,
|
||||
B56964D41B22FFAD0075EE4A /* DataStack+Migration.swift in Sources */,
|
||||
B5E84EF51AFF846E0064E85B /* BaseDataTransaction.swift in Sources */,
|
||||
B5E84EFB1AFF846E0064E85B /* SaveResult.swift in Sources */,
|
||||
B5E84F0F1AFF847B0064E85B /* From.swift in Sources */,
|
||||
B59D5C221B5BA34B00453479 /* NSFileManager+Setup.swift in Sources */,
|
||||
B5FAD6A91B50A4B400714891 /* NSProgress+Convenience.swift in Sources */,
|
||||
B5E84EFC1AFF846E0064E85B /* SynchronousDataTransaction.swift in Sources */,
|
||||
B5E84F281AFF84920064E85B /* NSManagedObject+Convenience.swift in Sources */,
|
||||
@@ -589,6 +612,7 @@
|
||||
B5E84EF81AFF846E0064E85B /* CoreStore+Transaction.swift in Sources */,
|
||||
B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */,
|
||||
B5E84F211AFF84860064E85B /* CoreStore+Observing.swift in Sources */,
|
||||
B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */,
|
||||
B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */,
|
||||
B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */,
|
||||
B5E84F151AFF847B0064E85B /* CoreStore+Querying.swift in Sources */,
|
||||
@@ -728,8 +752,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_WHOLE_MODULE_OPTIMIZATION = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -749,8 +772,7 @@
|
||||
PRODUCT_BUNDLE_IDENTIFIER = "com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)";
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
SKIP_INSTALL = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||
SWIFT_WHOLE_MODULE_OPTIMIZATION = YES;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -22,10 +22,10 @@
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForRunning = "NO"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
buildForAnalyzing = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
|
||||
@@ -37,10 +37,10 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -66,11 +66,11 @@
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
@@ -88,10 +88,10 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
|
||||
@@ -58,4 +58,20 @@ public extension NSManagedObject {
|
||||
self.setPrimitiveValue(value, forKey: KVCKey)
|
||||
self.didChangeValueForKey(KVCKey)
|
||||
}
|
||||
|
||||
/**
|
||||
Re-faults the object to use the latest values from the persistent store
|
||||
*/
|
||||
public func refreshAsFault() {
|
||||
|
||||
self.managedObjectContext?.refreshObject(self, mergeChanges: false)
|
||||
}
|
||||
|
||||
/**
|
||||
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
|
||||
*/
|
||||
public func refreshAndMerge() {
|
||||
|
||||
self.managedObjectContext?.refreshObject(self, mergeChanges: true)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,84 @@ public extension BaseDataTransaction {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter object: a reference to the object created/fetched outside the transaction
|
||||
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.context.existingObjectWithID(object.objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the transaction's context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter objectID: the `NSManagedObjectID` for the object
|
||||
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.context.existingObjectWithID(objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the transaction's context from references created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter objects: an array of `NSManagedObject`s created/fetched outside the transaction
|
||||
- returns: the `NSManagedObject` array for objects that exists in the transaction
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objects: [T]) -> [T] {
|
||||
|
||||
var existingObjects = [T]()
|
||||
for object in objects {
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.context.existingObjectWithID(object.objectID) as! T
|
||||
existingObjects.append(existingObject)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
return existingObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the transaction's context from a list of `NSManagedObjectID`.
|
||||
|
||||
- parameter objectIDs: the `NSManagedObjectID` array for the objects
|
||||
- returns: the `NSManagedObject` array for objects that exists in the transaction
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objectIDs: [NSManagedObjectID]) -> [T] {
|
||||
|
||||
var existingObjects = [T]()
|
||||
for objectID in objectIDs {
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.context.existingObjectWithID(objectID) as! T
|
||||
existingObjects.append(existingObject)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
return existingObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
@@ -43,7 +121,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -60,7 +138,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -77,7 +155,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -94,7 +172,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -111,7 +189,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -128,7 +206,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -145,7 +223,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -162,7 +240,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -179,7 +257,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -196,7 +274,7 @@ public extension BaseDataTransaction {
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -213,7 +291,7 @@ public extension BaseDataTransaction {
|
||||
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -230,7 +308,7 @@ public extension BaseDataTransaction {
|
||||
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -250,7 +328,7 @@ public extension BaseDataTransaction {
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -270,7 +348,7 @@ public extension BaseDataTransaction {
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -290,7 +368,7 @@ public extension BaseDataTransaction {
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -310,7 +388,7 @@ public extension BaseDataTransaction {
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
|
||||
@@ -31,6 +31,50 @@ public extension CoreStore {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter object: a reference to the object created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
public static func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
return self.defaultStack.fetchExisting(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter objectID: the `NSManagedObjectID` for the object
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
public static func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
return self.defaultStack.fetchExisting(objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
public static func fetchExisting<T: NSManagedObject>(objects: [T]) -> [T] {
|
||||
|
||||
return self.defaultStack.fetchExisting(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
|
||||
|
||||
- parameter objectIDs: the `NSManagedObjectID` array for the objects
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
public static func fetchExisting<T: NSManagedObject>(objectIDs: [NSManagedObjectID]) -> [T] {
|
||||
|
||||
return self.defaultStack.fetchExisting(objectIDs)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
|
||||
@@ -34,6 +34,84 @@ public extension DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter object: a reference to the object created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.mainContext.existingObjectWithID(object.objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter objectID: the `NSManagedObjectID` for the object
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.mainContext.existingObjectWithID(objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objects: [T]) -> [T] {
|
||||
|
||||
var existingObjects = [T]()
|
||||
for object in objects {
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.mainContext.existingObjectWithID(object.objectID) as! T
|
||||
existingObjects.append(existingObject)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
return existingObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
|
||||
|
||||
- parameter objectIDs: the `NSManagedObjectID` array for the objects
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objectIDs: [NSManagedObjectID]) -> [T] {
|
||||
|
||||
var existingObjects = [T]()
|
||||
for objectID in objectIDs {
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.mainContext.existingObjectWithID(objectID) as! T
|
||||
existingObjects.append(existingObject)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
return existingObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
|
||||
289
CoreStore/Importing Data/BaseDataTransaction+Importing.swift
Normal file
289
CoreStore/Importing Data/BaseDataTransaction+Importing.swift
Normal file
@@ -0,0 +1,289 @@
|
||||
//
|
||||
// BaseDataTransaction+Importing.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
|
||||
|
||||
|
||||
public protocol ImportableObject: class {
|
||||
|
||||
typealias ImportSource
|
||||
|
||||
static func shouldImportFromSource(source: ImportSource) -> Bool
|
||||
|
||||
func didInsertFromImportSource(source: ImportSource) throws
|
||||
|
||||
func updateFromImportSource(source: ImportSource) throws
|
||||
}
|
||||
|
||||
public extension ImportableObject {
|
||||
|
||||
static func shouldImportFromSource(source: ImportSource) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public protocol ImportableUniqueObject: ImportableObject {
|
||||
|
||||
typealias UniqueIDType: NSObject
|
||||
|
||||
static var uniqueIDKeyPath: String { get }
|
||||
|
||||
var uniqueIDValue: UniqueIDType { get set }
|
||||
|
||||
static func uniqueIDFromImportSource(source: ImportSource) throws -> UniqueIDType
|
||||
}
|
||||
|
||||
|
||||
public extension BaseDataTransaction {
|
||||
|
||||
func importObject<T where T: NSManagedObject, T: ImportableObject>(
|
||||
into: Into<T>,
|
||||
source: T.ImportSource) throws -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
|
||||
if !T.shouldImportFromSource(source) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let object = self.create(into)
|
||||
try object.didInsertFromImportSource(source)
|
||||
return object
|
||||
}
|
||||
}
|
||||
|
||||
func importObjects<T where T: NSManagedObject, T: ImportableObject>(
|
||||
into: Into<T>,
|
||||
sourceArray: [T.ImportSource]) throws {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
for source in sourceArray {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
try object.didInsertFromImportSource(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importObjects<T where T: NSManagedObject, T: ImportableObject>(
|
||||
into: Into<T>,
|
||||
sourceArray: [T.ImportSource],
|
||||
postProcess: (sorted: [T]) -> Void) throws {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
var objects = [T]()
|
||||
for source in sourceArray {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
try object.didInsertFromImportSource(source)
|
||||
|
||||
objects.append(object)
|
||||
}
|
||||
}
|
||||
postProcess(sorted: objects)
|
||||
}
|
||||
}
|
||||
|
||||
func importUniqueObject<T where T: NSManagedObject, T: ImportableUniqueObject>(
|
||||
into: Into<T>,
|
||||
source: T.ImportSource) throws -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
|
||||
if !T.shouldImportFromSource(source) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let uniqueIDKeyPath = T.uniqueIDKeyPath
|
||||
let uniqueIDValue = try T.uniqueIDFromImportSource(source)
|
||||
|
||||
if let object = self.fetchOne(From(T), Where(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) {
|
||||
|
||||
try object.updateFromImportSource(source)
|
||||
return object
|
||||
}
|
||||
else {
|
||||
|
||||
let object = self.create(into)
|
||||
object.uniqueIDValue = uniqueIDValue
|
||||
try object.didInsertFromImportSource(source)
|
||||
return object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importUniqueObjects<T where T: NSManagedObject, T: ImportableUniqueObject>(
|
||||
into: Into<T>,
|
||||
sourceArray: [T.ImportSource],
|
||||
preProcess: ((mapping: [T.UniqueIDType: T.ImportSource]) throws -> Void)? = nil) throws {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
var mapping = Dictionary<T.UniqueIDType, T.ImportSource>()
|
||||
for source in sourceArray {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
if !T.shouldImportFromSource(source) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let uniqueIDValue = try T.uniqueIDFromImportSource(source)
|
||||
mapping[uniqueIDValue] = source
|
||||
}
|
||||
}
|
||||
|
||||
if let preProcess = preProcess {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
try preProcess(mapping: mapping)
|
||||
}
|
||||
}
|
||||
|
||||
for object in self.fetchAll(From(T), Where("%K IN %@", T.uniqueIDKeyPath, mapping.keys.array)) ?? [] {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let uniqueIDValue = object.uniqueIDValue
|
||||
try object.updateFromImportSource(mapping.removeValueForKey(uniqueIDValue)!)
|
||||
}
|
||||
}
|
||||
|
||||
for (uniqueIDValue, source) in mapping {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
object.uniqueIDValue = uniqueIDValue
|
||||
try object.didInsertFromImportSource(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importUniqueObjects<T where T: NSManagedObject, T: ImportableUniqueObject>(
|
||||
into: Into<T>,
|
||||
sourceArray: [T.ImportSource],
|
||||
preProcess: ((mapping: [T.UniqueIDType: T.ImportSource]) throws -> Void)? = nil,
|
||||
postProcess: (sorted: [T]) -> Void) throws {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
var sortedIDs = Array<T.UniqueIDType>()
|
||||
var mapping = Dictionary<T.UniqueIDType, T.ImportSource>()
|
||||
for source in sourceArray {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
if !T.shouldImportFromSource(source) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let uniqueIDValue = try T.uniqueIDFromImportSource(source)
|
||||
mapping[uniqueIDValue] = source
|
||||
sortedIDs.append(uniqueIDValue)
|
||||
}
|
||||
}
|
||||
|
||||
if let preProcess = preProcess {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
try preProcess(mapping: mapping)
|
||||
}
|
||||
}
|
||||
|
||||
var objects = Dictionary<T.UniqueIDType, T>()
|
||||
for object in self.fetchAll(From(T), Where("%K IN %@", T.uniqueIDKeyPath, mapping.keys.array)) ?? [] {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let uniqueIDValue = object.uniqueIDValue
|
||||
try object.updateFromImportSource(mapping.removeValueForKey(uniqueIDValue)!)
|
||||
objects[uniqueIDValue] = object
|
||||
}
|
||||
}
|
||||
|
||||
for (uniqueIDValue, source) in mapping {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
object.uniqueIDValue = uniqueIDValue
|
||||
try object.didInsertFromImportSource(source)
|
||||
|
||||
objects[uniqueIDValue] = object
|
||||
}
|
||||
}
|
||||
|
||||
postProcess(sorted: sortedIDs.flatMap { objects[$0] })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>1.2.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
81
CoreStore/Internal/Functions.swift
Normal file
81
CoreStore/Internal/Functions.swift
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Functions.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
|
||||
|
||||
internal func autoreleasepool<T>(@noescape closure: () -> T?) -> T? {
|
||||
|
||||
var closureValue: T?
|
||||
ObjectiveC.autoreleasepool {
|
||||
|
||||
closureValue = closure()
|
||||
}
|
||||
|
||||
return closureValue
|
||||
}
|
||||
|
||||
internal func autoreleasepool<T>(@noescape closure: () throws -> T?) throws -> T? {
|
||||
|
||||
var closureValue: T?
|
||||
var closureError: ErrorType?
|
||||
ObjectiveC.autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
closureValue = try closure()
|
||||
}
|
||||
catch {
|
||||
|
||||
closureError = error
|
||||
}
|
||||
}
|
||||
|
||||
if let closureError = closureError {
|
||||
|
||||
throw closureError
|
||||
}
|
||||
return closureValue
|
||||
}
|
||||
|
||||
internal func autoreleasepool(@noescape closure: () throws -> Void) throws {
|
||||
|
||||
var closureError: ErrorType?
|
||||
ObjectiveC.autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
try closure()
|
||||
}
|
||||
catch {
|
||||
|
||||
closureError = error
|
||||
}
|
||||
}
|
||||
|
||||
if let closureError = closureError {
|
||||
|
||||
throw closureError
|
||||
}
|
||||
}
|
||||
38
CoreStore/Internal/NSFileManager+Setup.swift
Normal file
38
CoreStore/Internal/NSFileManager+Setup.swift
Normal file
@@ -0,0 +1,38 @@
|
||||
//
|
||||
// NSFileManager+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/07/19.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - NSFileManager
|
||||
|
||||
internal extension NSFileManager {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func removeSQLiteStoreAtURL(fileURL: NSURL) {
|
||||
|
||||
do {
|
||||
|
||||
try self.removeItemAtURL(fileURL)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
do {
|
||||
|
||||
try self.removeItemAtPath(fileURL.path!.stringByAppendingString("-shm"))
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
do {
|
||||
|
||||
try self.removeItemAtPath(fileURL.path!.stringByAppendingString("-wal"))
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
}
|
||||
@@ -75,7 +75,6 @@ internal extension NSManagedObjectContext {
|
||||
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
|
||||
context.parentContext = rootContext
|
||||
context.mergePolicy = NSRollbackMergePolicy
|
||||
context.shouldCascadeSavesToParent = false
|
||||
context.undoManager = nil
|
||||
context.setupForCoreStoreWithContextName("com.corestore.maincontext")
|
||||
context.observerForDidSaveNotification = NotificationObserver(
|
||||
@@ -83,11 +82,10 @@ internal extension NSManagedObjectContext {
|
||||
object: rootContext,
|
||||
closure: { [weak context] (note) -> Void in
|
||||
|
||||
context?.performBlockAndWait { () -> Void in
|
||||
context?.performBlock { () -> Void in
|
||||
|
||||
context?.mergeChangesFromContextDidSaveNotification(note)
|
||||
}
|
||||
return
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -150,14 +150,7 @@ internal extension NSManagedObjectContext {
|
||||
|
||||
if let parentContext = self.parentContext where self.shouldCascadeSavesToParent {
|
||||
|
||||
let result = parentContext.saveSynchronously()
|
||||
if let completion = completion {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: result)
|
||||
}
|
||||
}
|
||||
parentContext.saveAsynchronouslyWithCompletion(completion)
|
||||
}
|
||||
else if let completion = completion {
|
||||
|
||||
|
||||
@@ -150,6 +150,15 @@ internal extension NSManagedObjectModel {
|
||||
return self.entityNameMapping[NSStringFromClass(entityClass)]!
|
||||
}
|
||||
|
||||
@nonobjc internal func entityMapping() -> [String: NSManagedObject.Type] {
|
||||
|
||||
return self.entityNameMapping.reduce([:]) { (var mapping, pair) in
|
||||
|
||||
mapping[pair.0] = (NSClassFromString(pair.1)! as! NSManagedObject.Type)
|
||||
return mapping
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc internal func mergedModels() -> [NSManagedObjectModel] {
|
||||
|
||||
return self.modelVersions?.map { self[$0] }.flatMap { $0 == nil ? [] : [$0!] } ?? [self]
|
||||
|
||||
@@ -62,21 +62,26 @@ public final class DefaultLogger: CoreStoreLogger {
|
||||
icon = "❗"
|
||||
levelString = "Fatal"
|
||||
}
|
||||
Swift.print("\(icon) [CoreStore: \(levelString)] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
Swift.print("\(icon) [CoreStore: \(levelString)] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
Swift.print("⚠️ [CoreStore: Error] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n")
|
||||
Swift.print("⚠️ [CoreStore: Error] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
Swift.assert(condition, "❗ [CoreStore: Assertion Failure] \(message)", file: fileName, line: numericCast(lineNumber))
|
||||
if condition() {
|
||||
|
||||
return
|
||||
}
|
||||
Swift.print("❗ [CoreStore: Assertion Failure] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
Swift.fatalError()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
@@ -38,15 +38,17 @@ public extension CoreStore {
|
||||
- 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 resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report 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 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? {
|
||||
public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.addSQLiteStore(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
@@ -57,15 +59,17 @@ public extension CoreStore {
|
||||
- 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 resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report 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 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? {
|
||||
public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.addSQLiteStore(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
@@ -32,16 +32,58 @@ import GCDKit
|
||||
|
||||
public extension DataStack {
|
||||
|
||||
/**
|
||||
Asynchronously adds an in-memory store to the stack.
|
||||
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
|
||||
*/
|
||||
public func addInMemoryStore(configuration configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
|
||||
|
||||
self.coordinator.performBlock {
|
||||
|
||||
do {
|
||||
|
||||
let store = try self.coordinator.addPersistentStoreWithType(
|
||||
NSInMemoryStoreType,
|
||||
configuration: configuration,
|
||||
URL: nil,
|
||||
options: nil
|
||||
)
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
let storeError = error as NSError
|
||||
CoreStore.handleError(
|
||||
storeError,
|
||||
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(storeError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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`.)
|
||||
|
||||
- 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 resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report 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 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 fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.addSQLiteStore(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
@@ -50,6 +92,7 @@ public extension DataStack {
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
@@ -60,10 +103,11 @@ public extension DataStack {
|
||||
- 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 resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report 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 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 fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
public func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
CoreStore.assert(
|
||||
fileURL.fileURL,
|
||||
@@ -73,10 +117,7 @@ public extension DataStack {
|
||||
let coordinator = self.coordinator;
|
||||
if let store = coordinator.persistentStoreForURL(fileURL) {
|
||||
|
||||
let isExistingStoreAutomigrating = store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool == true
|
||||
|
||||
if store.type == NSSQLiteStoreType
|
||||
&& isExistingStoreAutomigrating
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
@@ -94,9 +135,10 @@ public extension DataStack {
|
||||
throw error
|
||||
}
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
do {
|
||||
|
||||
try NSFileManager.defaultManager().createDirectoryAtURL(
|
||||
try fileManager.createDirectoryAtURL(
|
||||
fileURL.URLByDeletingLastPathComponent!,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
@@ -120,6 +162,29 @@ public extension DataStack {
|
||||
|
||||
if case .Failure(let error) = result {
|
||||
|
||||
if resetStoreOnModelMismatch && error.isCoreDataMigrationError {
|
||||
|
||||
fileManager.removeSQLiteStoreAtURL(fileURL)
|
||||
do {
|
||||
|
||||
let store = try self.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: false
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
completion(PersistentStoreResult(error as NSError))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
completion(PersistentStoreResult(error))
|
||||
return
|
||||
}
|
||||
@@ -129,8 +194,7 @@ public extension DataStack {
|
||||
let store = try self.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
automigrating: false,
|
||||
resetStoreOnMigrationFailure: false
|
||||
resetStoreOnModelMismatch: false
|
||||
)
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
@@ -148,8 +212,7 @@ public extension DataStack {
|
||||
let store = try self.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
automigrating: false,
|
||||
resetStoreOnMigrationFailure: false
|
||||
resetStoreOnModelMismatch: false
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// MigrationResult.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
@@ -26,81 +26,6 @@
|
||||
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
|
||||
|
||||
/**
|
||||
|
||||
125
CoreStore/Migrating/MigrationType.swift
Normal file
125
CoreStore/Migrating/MigrationType.swift
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// MigrationType.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
|
||||
|
||||
|
||||
// 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
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `MigrationType` is a lightweight migration. Used as syntactic sugar.
|
||||
*/
|
||||
public var isLightweightMigration: Bool {
|
||||
|
||||
if case .Lightweight = self {
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `MigrationType` is a heavyweight migration. Used as syntactic sugar.
|
||||
*/
|
||||
public var isHeavyweightMigration: Bool {
|
||||
|
||||
if case .Heavyweight = self {
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// MARK: BooleanType
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .None: return false
|
||||
case .Lightweight: return true
|
||||
case .Heavyweight: return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -86,4 +86,13 @@ public extension NSError {
|
||||
code: coreStoreErrorCode.rawValue,
|
||||
userInfo: userInfo)
|
||||
}
|
||||
|
||||
internal var isCoreDataMigrationError: Bool {
|
||||
|
||||
let code = self.code
|
||||
return (code == NSPersistentStoreIncompatibleVersionHashError
|
||||
|| code == NSMigrationMissingSourceModelError
|
||||
|| code == NSMigrationError)
|
||||
&& self.domain == NSCocoaErrorDomain
|
||||
}
|
||||
}
|
||||
|
||||
@@ -51,7 +51,7 @@ public extension CoreStore {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
public static func monitorList<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ListMonitor<T> {
|
||||
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: FetchClause...) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.monitorList(from, queryClauses)
|
||||
}
|
||||
@@ -63,7 +63,7 @@ public extension CoreStore {
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
public static func monitorList<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.monitorList(from, queryClauses)
|
||||
}
|
||||
|
||||
@@ -72,9 +72,10 @@ public final class ListMonitor<T: NSManagedObject> {
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Accesses the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `addObserver(_:)`.
|
||||
Returns the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`.
|
||||
|
||||
- parameter index: the index of the object. Using an index above the valid range will throw an exception.
|
||||
- returns: the `NSManagedObject` at the specified index
|
||||
*/
|
||||
public subscript(index: Int) -> T {
|
||||
|
||||
@@ -82,10 +83,22 @@ public final class ListMonitor<T: NSManagedObject> {
|
||||
}
|
||||
|
||||
/**
|
||||
Accesses the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`.
|
||||
Returns the object at the given index, or `nil` if out of bounds. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`.
|
||||
|
||||
- parameter index: the index for the object. Using an index above the valid range will return `nil`.
|
||||
- returns: the `NSManagedObject` at the specified index, or `nil` if out of bounds
|
||||
*/
|
||||
public subscript(safeIndex index: Int) -> T? {
|
||||
|
||||
return self[safeSectionIndex: 0, safeItemIndex: index]
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`.
|
||||
|
||||
- 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.
|
||||
- returns: the `NSManagedObject` at the specified section and item index
|
||||
*/
|
||||
public subscript(sectionIndex: Int, itemIndex: Int) -> T {
|
||||
|
||||
@@ -93,43 +106,173 @@ public final class ListMonitor<T: NSManagedObject> {
|
||||
}
|
||||
|
||||
/**
|
||||
Accesses the object at the given `NSIndexPath`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`.
|
||||
Returns the object at the given section and item index, or `nil` if out of bounds. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`.
|
||||
|
||||
- parameter sectionIndex: the section index for the object. Using a `sectionIndex` with an invalid range will return `nil`.
|
||||
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will return `nil`.
|
||||
- returns: the `NSManagedObject` at the specified section and item index, or `nil` if out of bounds
|
||||
*/
|
||||
public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> T? {
|
||||
|
||||
guard let sections = self.fetchedResultsController.sections
|
||||
where sectionIndex < sections.count else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let section = sections[sectionIndex]
|
||||
guard itemIndex < section.numberOfObjects else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return sections[sectionIndex].objects?[itemIndex] as? T
|
||||
}
|
||||
|
||||
/**
|
||||
Returns 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.
|
||||
- returns: the `NSManagedObject` at the specified index path
|
||||
*/
|
||||
public subscript(indexPath: NSIndexPath) -> T {
|
||||
|
||||
return self.fetchedResultsController.objectAtIndexPath(indexPath) as! T
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object at the given `NSIndexPath`, or `nil` if out of bounds. 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 return `nil`.
|
||||
- returns: the `NSManagedObject` at the specified index path, or `nil` if out of bounds
|
||||
*/
|
||||
public subscript(safeIndexPath indexPath: NSIndexPath) -> T? {
|
||||
|
||||
return self[safeSectionIndex: indexPath.section, safeItemIndex: indexPath.item]
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the `ListMonitor` has at least one object in any section.
|
||||
|
||||
- returns: `true` if at least one object in any section exists, `false` otherwise
|
||||
*/
|
||||
public func hasObjects() -> Bool {
|
||||
|
||||
return self.numberOfObjects() > 0
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the `ListMonitor` has at least one object the specified section.
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will return `false`.
|
||||
- returns: `true` if at least one object in the specified section exists, `false` otherwise
|
||||
*/
|
||||
public func hasObjectsInSection(section: Int) -> Bool {
|
||||
|
||||
return self.numberOfObjectsInSection(safeSectionIndex: section) > 0
|
||||
}
|
||||
|
||||
/**
|
||||
Returns all objects in all sections
|
||||
|
||||
- returns: all objects in all sections
|
||||
*/
|
||||
public func objectsInAllSections() -> [T] {
|
||||
|
||||
return (self.fetchedResultsController.fetchedObjects as? [T]) ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
Returns all objects in the specified section
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will throw an exception.
|
||||
- returns: all objects in the specified section
|
||||
*/
|
||||
public func objectsInSection(section: Int) -> [T] {
|
||||
|
||||
return (self.fetchedResultsController.sections?[section].objects as? [T]) ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
Returns all objects in the specified section, or `nil` if out of bounds.
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will return `nil`.
|
||||
- returns: all objects in the specified section
|
||||
*/
|
||||
public func objectsInSection(safeSectionIndex section: Int) -> [T]? {
|
||||
|
||||
return (self.fetchedResultsController.sections?[section].objects as? [T]) ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of sections
|
||||
|
||||
- returns: the number of sections
|
||||
*/
|
||||
public func numberOfSections() -> Int {
|
||||
|
||||
return self.fetchedResultsController.sections?.count ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of objects in all sections
|
||||
|
||||
- returns: the number of objects in all sections
|
||||
*/
|
||||
public func numberOfObjects() -> Int {
|
||||
|
||||
return self.fetchedResultsController.fetchedObjects?.count ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of objects in the specified section
|
||||
|
||||
- parameter section: the section index
|
||||
- parameter section: the section index. Using an index outside the valid range will throw an exception.
|
||||
- returns: the number of objects in the specified section
|
||||
*/
|
||||
public func numberOfObjectsInSection(section: Int) -> Int {
|
||||
|
||||
return self.fetchedResultsController.sections?[section].numberOfObjects ?? 0
|
||||
return self.sectionInfoAtIndex(section).numberOfObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of objects in the specified section, or `nil` if out of bounds.
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will return `nil`.
|
||||
- returns: the number of objects in the specified section
|
||||
*/
|
||||
public func numberOfObjectsInSection(safeSectionIndex section: Int) -> Int? {
|
||||
|
||||
return self.sectionInfoAtIndex(safeSectionIndex: section)?.numberOfObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSFetchedResultsSectionInfo` for the specified section
|
||||
|
||||
- parameter section: the section index
|
||||
- parameter section: the section index. Using an index outside the valid range will throw an exception.
|
||||
- returns: the `NSFetchedResultsSectionInfo` for the specified section
|
||||
*/
|
||||
public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo {
|
||||
|
||||
return self.fetchedResultsController.sections![section]
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSFetchedResultsSectionInfo` for the specified section, or `nil` if out of bounds.
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will return `nil`.
|
||||
- returns: the `NSFetchedResultsSectionInfo` for the specified section, or `nil` if the section index is out of bounds.
|
||||
*/
|
||||
public func sectionInfoAtIndex(safeSectionIndex section: Int) -> NSFetchedResultsSectionInfo? {
|
||||
|
||||
guard let sections = self.fetchedResultsController.sections
|
||||
where section < sections.count else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return sections[section]
|
||||
}
|
||||
|
||||
/**
|
||||
Registers a `ListObserver` to be notified when changes to the receiver's list occur.
|
||||
|
||||
@@ -586,6 +729,16 @@ public final class ListMonitor<T: NSManagedObject> {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListMonitor: Equatable
|
||||
|
||||
public func ==<T: NSManagedObject>(lhs: ListMonitor<T>, rhs: ListMonitor<T>) -> Bool {
|
||||
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
extension ListMonitor: Equatable { }
|
||||
|
||||
|
||||
// MARK: - ListMonitor: FetchedResultsControllerHandler
|
||||
|
||||
extension ListMonitor: FetchedResultsControllerHandler {
|
||||
@@ -626,7 +779,7 @@ extension ListMonitor: FetchedResultsControllerHandler {
|
||||
]
|
||||
)
|
||||
|
||||
case .Move:
|
||||
case .Move where indexPath != newIndexPath:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ListMonitorDidMoveObjectNotification,
|
||||
object: self,
|
||||
@@ -636,6 +789,9 @@ extension ListMonitor: FetchedResultsControllerHandler {
|
||||
UserInfoKeyNewIndexPath: newIndexPath!
|
||||
]
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
@@ -723,7 +879,7 @@ private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResults
|
||||
self.handler?.controllerDidChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath)
|
||||
}
|
||||
|
||||
@@ -60,6 +60,19 @@ public protocol ListObserver: class {
|
||||
func listMonitorDidChange(monitor: ListMonitor<EntityType>)
|
||||
}
|
||||
|
||||
public extension ListObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitorWillChange(monitor: ListMonitor<EntityType>) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitorDidChange(monitor: ListMonitor<EntityType>) { }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListObjectObserver
|
||||
|
||||
@@ -112,6 +125,29 @@ public protocol ListObjectObserver: ListObserver {
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
|
||||
}
|
||||
|
||||
public extension ListObjectObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListSectionObserver
|
||||
|
||||
@@ -145,3 +181,16 @@ public protocol ListSectionObserver: ListObjectObserver {
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
|
||||
}
|
||||
|
||||
public extension ListSectionObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { }
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ public final class ObjectMonitor<T: NSManagedObject> {
|
||||
let fetchRequest = NSFetchRequest()
|
||||
fetchRequest.entity = object.entity
|
||||
|
||||
fetchRequest.fetchLimit = 1
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchRequest.sortDescriptors = []
|
||||
|
||||
@@ -261,6 +261,16 @@ public final class ObjectMonitor<T: NSManagedObject> {
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectMonitor: Equatable
|
||||
|
||||
public func ==<T: NSManagedObject>(lhs: ObjectMonitor<T>, rhs: ObjectMonitor<T>) -> Bool {
|
||||
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
extension ObjectMonitor: Equatable { }
|
||||
|
||||
|
||||
// MARK: - ObjectMonitor: FetchedResultsControllerHandler
|
||||
|
||||
extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
@@ -278,7 +288,7 @@ extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
userInfo: [UserInfoKeyObject: anObject]
|
||||
)
|
||||
|
||||
case .Update:
|
||||
case .Update, .Move where indexPath == newIndexPath:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorDidUpdateObjectNotification,
|
||||
object: self,
|
||||
@@ -321,7 +331,7 @@ private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResults
|
||||
self.handler?.controllerWillChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath)
|
||||
}
|
||||
|
||||
@@ -67,3 +67,22 @@ public protocol ObjectObserver: class {
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, didDeleteObject object: EntityType)
|
||||
}
|
||||
|
||||
|
||||
public extension ObjectObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, willUpdateObject object: EntityType) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, didUpdateObject object: EntityType, changedPersistentKeys: Set<KeyPath>) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, didDeleteObject object: EntityType) { }
|
||||
}
|
||||
|
||||
@@ -38,11 +38,11 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Saves the transaction changes asynchronously. This method should not be used after the `commit()` method was already called once.
|
||||
Saves the transaction changes. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- 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 = { _ in }) {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
@@ -64,24 +64,6 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
semaphore.wait()
|
||||
}
|
||||
|
||||
/**
|
||||
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
public func commit() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to commit a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to commit a \(typeName(self)) more than once."
|
||||
)
|
||||
|
||||
self.isCommitted = true
|
||||
self.result = self.context.saveSynchronously()
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
|
||||
@@ -54,7 +54,7 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
public func create<T: NSManagedObject>(into: Into<T>) -> T {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to create an entity of type \(typeName(T)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -107,7 +107,7 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
public func edit<T: NSManagedObject>(object: T?) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to update an entity of type \(typeName(object)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -124,7 +124,7 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
public func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to update an entity of type \(typeName(T)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
@@ -144,7 +144,7 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
public func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete an entity outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -171,7 +171,7 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
public func delete(objects: [NSManagedObject?]) {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete entities outside their designated queue."
|
||||
)
|
||||
|
||||
@@ -190,7 +190,7 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
public func rollback() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to rollback a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
@@ -220,4 +220,9 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
|
||||
context.parentTransaction = self
|
||||
}
|
||||
|
||||
internal var bypassesQueueing: Bool {
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,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.
|
||||
|
||||
- returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@@ -70,6 +70,10 @@ public extension DataStack {
|
||||
|
||||
return DetachedDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: .Main)
|
||||
queue: .createSerial(
|
||||
"com.coreStore.dataStack.detachedTransactionQueue",
|
||||
targetQueue: .UserInitiated
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -43,16 +43,44 @@ public final class DetachedDataTransaction: BaseDataTransaction {
|
||||
*/
|
||||
public func commit(completion: (result: SaveResult) -> Void) {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to commit a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
|
||||
|
||||
self.result = result
|
||||
completion(result: result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
public func beginDetached() -> DetachedDataTransaction {
|
||||
|
||||
return DetachedDataTransaction(
|
||||
mainContext: self.context,
|
||||
queue: self.transactionQueue
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObjectContext` for this detached transaction. Use only for cases where external frameworks need an `NSManagedObjectContext` instance to work with.
|
||||
|
||||
Note that it is the developer's responsibility to ensure the following:
|
||||
- that the `DetachedDataTransaction` that owns this context should be strongly referenced and prevented from being deallocated during the context's lifetime
|
||||
- that all saves will be done either through the `DetachedDataTransaction`'s `commit(...)` method, or by calling `save()` manually on the context, its parent, and all other ancestor contexts if there are any.
|
||||
*/
|
||||
public var internalContext: NSManagedObjectContext {
|
||||
|
||||
return self.context
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal override var bypassesQueueing: Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -32,15 +32,31 @@ import GCDKit
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
/**
|
||||
Returns the `defaultStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
|
||||
*/
|
||||
public static var modelVersion: String {
|
||||
|
||||
return self.defaultStack.modelVersion
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the entity name-to-class type mapping from the `defaultStack`'s model.
|
||||
*/
|
||||
public static var entitiesByName: [String: NSManagedObject.Type] {
|
||||
|
||||
return self.defaultStack.entitiesByName
|
||||
}
|
||||
|
||||
/**
|
||||
Adds an in-memory store to the `defaultStack`.
|
||||
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addInMemoryStore(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
public static func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addInMemoryStore(configuration: configuration)
|
||||
return try self.defaultStack.addInMemoryStoreAndWait(configuration: configuration)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -48,17 +64,15 @@ public extension CoreStore {
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
- parameter automigrating: Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
|
||||
- parameter resetStoreOnMigrationFailure: Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; 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: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) throws -> NSPersistentStore {
|
||||
public static func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
automigrating: automigrating,
|
||||
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
@@ -67,17 +81,15 @@ public extension CoreStore {
|
||||
|
||||
- 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.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
- parameter automigrating: Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
|
||||
- parameter resetStoreOnMigrationFailure: Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; 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: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) throws -> NSPersistentStore {
|
||||
public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
automigrating: automigrating,
|
||||
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,13 +81,29 @@ public final class DataStack {
|
||||
return self.model.currentModelVersion!
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the entity name-to-class type mapping from the `DataStack`'s model.
|
||||
*/
|
||||
public var entitiesByName: [String: NSManagedObject.Type] {
|
||||
|
||||
return self.model.entityMapping()
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObjectID` for the specified object URI if it exists in the persistent store.
|
||||
*/
|
||||
public func objectIDForURIRepresentation(url: NSURL) -> NSManagedObjectID? {
|
||||
|
||||
return self.coordinator.managedObjectIDForURIRepresentation(url)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds an in-memory store to the stack.
|
||||
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public func addInMemoryStore(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
public func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
|
||||
@@ -129,11 +145,10 @@ public final class DataStack {
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter automigrating: Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
|
||||
- parameter resetStoreOnMigrationFailure: Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; 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: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) throws -> NSPersistentStore {
|
||||
public func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.addSQLiteStoreAndWait(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
@@ -141,8 +156,7 @@ public final class DataStack {
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
automigrating: automigrating,
|
||||
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,11 +165,10 @@ public final class DataStack {
|
||||
|
||||
- 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 automigrating: Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
|
||||
- parameter resetStoreOnMigrationFailure: Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; 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: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public func addSQLiteStoreAndWait(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) throws -> NSPersistentStore {
|
||||
public func addSQLiteStoreAndWait(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
CoreStore.assert(
|
||||
fileURL.fileURL,
|
||||
@@ -165,10 +178,7 @@ public final class DataStack {
|
||||
let coordinator = self.coordinator;
|
||||
if let store = coordinator.persistentStoreForURL(fileURL) {
|
||||
|
||||
let isExistingStoreAutomigrating = (store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) == true
|
||||
|
||||
if store.type == NSSQLiteStoreType
|
||||
&& isExistingStoreAutomigrating == automigrating
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
|
||||
|
||||
return store
|
||||
@@ -204,11 +214,7 @@ public final class DataStack {
|
||||
NSSQLiteStoreType,
|
||||
configuration: configuration,
|
||||
URL: fileURL,
|
||||
options: [
|
||||
NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSInferMappingModelAutomaticallyOption: true,
|
||||
NSMigratePersistentStoresAutomaticallyOption: automigrating
|
||||
]
|
||||
options: [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
|
||||
)
|
||||
}
|
||||
catch {
|
||||
@@ -224,31 +230,9 @@ public final class DataStack {
|
||||
}
|
||||
|
||||
if let error = storeError
|
||||
where (
|
||||
resetStoreOnMigrationFailure
|
||||
&& (error.code == NSPersistentStoreIncompatibleVersionHashError
|
||||
|| error.code == NSMigrationMissingSourceModelError
|
||||
|| error.code == NSMigrationError)
|
||||
&& error.domain == NSCocoaErrorDomain
|
||||
) {
|
||||
where (resetStoreOnModelMismatch && error.isCoreDataMigrationError) {
|
||||
|
||||
do {
|
||||
|
||||
try fileManager.removeItemAtURL(fileURL)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
do {
|
||||
|
||||
try fileManager.removeItemAtPath(fileURL.path!.stringByAppendingString("-shm"))
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
do {
|
||||
|
||||
try fileManager.removeItemAtPath(fileURL.path!.stringByAppendingString("-wal"))
|
||||
}
|
||||
catch _ { }
|
||||
fileManager.removeSQLiteStoreAtURL(fileURL)
|
||||
|
||||
var store: NSPersistentStore?
|
||||
coordinator.performBlockAndWait {
|
||||
@@ -259,11 +243,7 @@ public final class DataStack {
|
||||
NSSQLiteStoreType,
|
||||
configuration: configuration,
|
||||
URL: fileURL,
|
||||
options: [
|
||||
NSSQLitePragmasOption: ["journal_mode": "WAL"],
|
||||
NSInferMappingModelAutomaticallyOption: true,
|
||||
NSMigratePersistentStoresAutomaticallyOption: automigrating
|
||||
]
|
||||
options: [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
|
||||
)
|
||||
}
|
||||
catch {
|
||||
@@ -295,7 +275,7 @@ public final class DataStack {
|
||||
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 = {
|
||||
|
||||
let migrationQueue = NSOperationQueue()
|
||||
|
||||
@@ -18,7 +18,7 @@ private struct Static {
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "TimeZoneDemo.sqlite",
|
||||
configuration: "FetchingAndQueryingDemo",
|
||||
resetStoreOnMigrationFailure: true
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
|
||||
dataStack.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.0</string>
|
||||
<string>1.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -17,7 +17,7 @@ private struct Static {
|
||||
try! CoreStore.addSQLiteStoreAndWait(
|
||||
fileName: "ColorsDemo.sqlite",
|
||||
configuration: "ObservingDemo",
|
||||
resetStoreOnMigrationFailure: true
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
|
||||
return CoreStore.monitorSectionedList(
|
||||
|
||||
@@ -80,11 +80,6 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
|
||||
// MARK: ObjectObserver
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<Palette>, willUpdateObject object: Palette) {
|
||||
|
||||
// none
|
||||
}
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<Palette>, didUpdateObject object: Palette, changedPersistentKeys: Set<KeyPath>) {
|
||||
|
||||
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
|
||||
|
||||
@@ -62,7 +62,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
case .Warning: levelString = "Warning"
|
||||
case .Fatal: levelString = "Fatal"
|
||||
}
|
||||
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,13 +83,13 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
@noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
Swift.fatalError("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)")
|
||||
Swift.fatalError("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)")
|
||||
}
|
||||
|
||||
|
||||
@@ -109,7 +109,11 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
}
|
||||
|
||||
case .Some(1):
|
||||
try! self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite", configuration: "invalidStore")
|
||||
do {
|
||||
|
||||
try self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite", configuration: "invalidStore")
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
case .Some(2):
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
@@ -37,6 +37,15 @@ class MigrationsDemoViewController: UIViewController {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Migrations Demo",
|
||||
message: "This demo shows how to run incremental migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
|
||||
preferredStyle: .Alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
self.presentViewController(alert, animated: true, completion: nil)
|
||||
|
||||
|
||||
let modelMetadata = withExtendedLifetime(DataStack(modelName: "MigrationDemo")) {
|
||||
(dataStack: DataStack) -> ModelMetadata in
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
<?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/>
|
||||
</plist>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="8166.2" systemVersion="14E46" minimumToolsVersion="Xcode 7.0">
|
||||
<elements/>
|
||||
</model>
|
||||
@@ -21,12 +21,12 @@ private struct Static {
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_FB_Male.sqlite",
|
||||
configuration: maleConfiguration,
|
||||
resetStoreOnMigrationFailure: true
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_FB_Female.sqlite",
|
||||
configuration: femaleConfiguration,
|
||||
resetStoreOnMigrationFailure: true
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
|
||||
dataStack.beginSynchronous { (transaction) -> Void in
|
||||
@@ -55,12 +55,12 @@ private struct Static {
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_TW_Male.sqlite",
|
||||
configuration: maleConfiguration,
|
||||
resetStoreOnMigrationFailure: true
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_TW_Female.sqlite",
|
||||
configuration: femaleConfiguration,
|
||||
resetStoreOnMigrationFailure: true
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
|
||||
dataStack.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
@@ -21,7 +21,7 @@ private struct Static {
|
||||
try! CoreStore.addSQLiteStoreAndWait(
|
||||
fileName: "PlaceDemo.sqlite",
|
||||
configuration: "TransactionsDemo",
|
||||
resetStoreOnMigrationFailure: true
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
|
||||
var place = CoreStore.fetchOne(From(Place))
|
||||
|
||||
@@ -87,7 +87,7 @@ class CoreStoreTests: XCTestCase {
|
||||
|
||||
do {
|
||||
|
||||
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore1.sqlite", configuration: "Config1", resetStoreOnMigrationFailure: true)
|
||||
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore1.sqlite", configuration: "Config1", resetStoreOnModelMismatch: true)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
@@ -96,7 +96,7 @@ class CoreStoreTests: XCTestCase {
|
||||
|
||||
do {
|
||||
|
||||
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore2.sqlite", configuration: "Config2", resetStoreOnMigrationFailure: true)
|
||||
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore2.sqlite", configuration: "Config2", resetStoreOnModelMismatch: true)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
|
||||
Submodule Libraries/GCDKit updated: 50c517ccc8...154d4ccc64
137
README.md
137
README.md
@@ -35,6 +35,7 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
|
||||
- [Setting up](#setting-up)
|
||||
- [Migrations](#migrations)
|
||||
- [Incremental migrations](#incremental-migrations)
|
||||
- [Forecasting migrations](#forecasting-migrations)
|
||||
- [Saving and processing transactions](#saving-and-processing-transactions)
|
||||
- [Transaction types](#transaction-types)
|
||||
- [Asynchronous transactions](#asynchronous-transactions)
|
||||
@@ -169,32 +170,42 @@ catch {
|
||||
This one-liner does the following:
|
||||
- Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack`
|
||||
- Sets up the stack's `NSPersistentStoreCoordinator`, the root saving `NSManagedObjectContext`, and the read-only main `NSManagedObjectContext`
|
||||
- Adds an automigrating SQLite store in the *"Application Support"* directory with the file name *"[App bundle name].sqlite"*
|
||||
- Adds an SQLite store in the *"Application Support"* directory with the file name *"[App bundle name].sqlite"*
|
||||
- Creates and returns the `NSPersistentStore` instance on success, or an `NSError` on failure
|
||||
|
||||
For most cases, this configuration is usable as it is. But for more hardcore settings, refer to this extensive example:
|
||||
```swift
|
||||
let dataStack = DataStack(modelName: "MyModel") // loads from the "MyModel.xcdatamodeld" file
|
||||
let dataStack = DataStack(
|
||||
modelName: "MyModel", // loads from the "MyModel.xcdatamodeld" file
|
||||
migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"] // model versions for incremental migrations
|
||||
)
|
||||
|
||||
do {
|
||||
// creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file
|
||||
let persistentStore = try dataStack.addInMemoryStore(configuration: "Config1") // persistentStore is an NSPersistentStore instance
|
||||
let persistentStore = try dataStack.addInMemoryStoreAndWait(configuration: "Config1") // persistentStore is an NSPersistentStore instance
|
||||
print("Successfully created an in-memory store: \(persistentStore)"
|
||||
}
|
||||
catch let error as NSError {
|
||||
print("Failed creating an in-memory store with error: \(error.description)"
|
||||
catch {
|
||||
print("Failed creating an in-memory store with error: \(error as NSError)"
|
||||
}
|
||||
|
||||
do {
|
||||
try dataStack.addSQLiteStoreAndWait(
|
||||
try dataStack.addSQLiteStore(
|
||||
fileURL: sqliteFileURL, // set the target file URL for the sqlite file
|
||||
configuration: "Config2", // use entities from the "Config2" configuration in the .xcdatamodeld file
|
||||
automigrating: true, // automatically run lightweight migrations or entity policy migrations when needed
|
||||
resetStoreOnMigrationFailure: true)
|
||||
print("Successfully created an sqlite store: \(persistentStore)"
|
||||
resetStoreOnModelMismatch: true,
|
||||
completion: { (result) -> Void in
|
||||
switch result {
|
||||
case .Success(let persistentStore):
|
||||
print("Successfully added sqlite store: \(persistentStore)"
|
||||
case .Failure(let error):
|
||||
print("Failed adding sqlite store with error: \(error)"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
catch let error as NSError {
|
||||
print("Failed creating an sqlite store with error: \(error.description)"
|
||||
catch {
|
||||
print("Failed adding sqlite store with error: \(error as NSError)"
|
||||
}
|
||||
|
||||
CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on
|
||||
@@ -203,7 +214,7 @@ CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier
|
||||
(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:
|
||||
In our sample code above, note that you don't need to do the `CoreStore.defaultStack = dataStack` line. You can just as well hold a reference to the `DataStack` like below and call all its instance methods directly:
|
||||
```swift
|
||||
class MyViewController: UIViewController {
|
||||
let dataStack = DataStack(modelName: "MyModel")
|
||||
@@ -241,10 +252,106 @@ class MyViewController: UIViewController {
|
||||
|
||||
|
||||
## Migrations
|
||||
(README pending)
|
||||
So far we have only seen `addSQLiteStoreAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method will block, even if a migration occurs. If migrations are expected, the asynchronous variant `addSQLiteStore(... completion:)` method is recommended:
|
||||
```swift
|
||||
do {
|
||||
let progress: NSProgress = try dataStack.addSQLiteStore(
|
||||
fileName: "MyStore.sqlite",
|
||||
configuration: "Config2",
|
||||
completion: { (result) -> Void in
|
||||
switch result {
|
||||
case .Success(let persistentStore):
|
||||
print("Successfully added sqlite store: \(persistentStore)"
|
||||
case .Failure(let error):
|
||||
print("Failed adding sqlite store with error: \(error)"
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
catch {
|
||||
print("Failed adding sqlite store with error: \(error as NSError)"
|
||||
}
|
||||
```
|
||||
The `completion` block reports a `PersistentStoreResult` that indicates success or failure.
|
||||
|
||||
`addSQLiteStore(...)` throws an error if the store at the specified URL conflicts with an existing store in the `DataStack`, or if an existing sqlite file could not be read. If an error is thrown, the `completion` block will not be executed.
|
||||
|
||||
Notice that this method also returns an optional `NSProgress`. If `nil`, no migrations are needed, thus progress reporting is unnecessary as well. If not `nil`, you can use this to track migration progress by using standard KVO on the "fractionCompleted" key, or by using a closure-based utility exposed in *NSProgress+Convenience.swift*:
|
||||
```swift
|
||||
progress?.setProgressHandler { [weak self] (progress) -> Void in
|
||||
self?.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
|
||||
self?.percentLabel?.text = progress.localizedDescription // "50% completed"
|
||||
self?.stepLabel?.text = progress.localizedAdditionalDescription // "0 of 2"
|
||||
}
|
||||
```
|
||||
This closure is executed on the main thread so UIKit calls can be done safely.
|
||||
|
||||
|
||||
### Incremental migrations
|
||||
(README pending)
|
||||
By default, CoreStore uses Core Data's default automatic migration mechanism. In other words, CoreStore will try to migrate the existing persistent store to the *.xcdatamodeld* file's current model version. If no mapping model is found from the store's version to the data model's version, CoreStore gives up and reports an error.
|
||||
|
||||
The `DataStack` lets you specify hints on how to break a migration into several sub-migrations using a `MigrationChain`. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants:
|
||||
```swift
|
||||
let dataStack = DataStack(migrationChain:
|
||||
["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"])
|
||||
```
|
||||
The most common usage is to pass in the *.xcdatamodeld* version names in increasing order as above.
|
||||
|
||||
For more complex migration paths, you can also pass in a version tree that maps the key-values to the source-destination versions:
|
||||
```swift
|
||||
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
|
||||
|
||||
Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to disable incremental migrations and revert to the default migration behavior (i.e. use the .xcdatamodel's current version as the final version):
|
||||
```swift
|
||||
let dataStack = DataStack(migrationChain: nil)
|
||||
```
|
||||
|
||||
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
|
||||
|
||||
One important thing to remember is that **if a `MigrationChain` is specified, the *.xcdatamodeld*'s "Current Version" will be bypassed** and the `MigrationChain`'s leafmost version will be the `DataStack`'s base model version.
|
||||
|
||||
|
||||
### Forecasting migrations
|
||||
|
||||
Sometimes migrations are huge and you may want prior information so your app could display a loading screen, or to display a confirmation dialog to the user. For this, CoreStore provides a `requiredMigrationsForSQLiteStore(...)` method you can use to inspect a persistent store before you actually call `addSQLiteStore(...)`:
|
||||
```swift
|
||||
do {
|
||||
let migrationTypes: [MigrationType] = CoreStore.requiredMigrationsForSQLiteStore(fileName: "MyStore.sqlite")
|
||||
if migrationTypes.count > 1
|
||||
|| (migrationTypes.filter { $0.isHeavyweightMigration }.count) > 0 {
|
||||
// ... Show special waiting screen
|
||||
}
|
||||
else if migrationTypes.count > 0 {
|
||||
// ... Show simple activity indicator
|
||||
}
|
||||
else {
|
||||
// ... Do nothing
|
||||
}
|
||||
|
||||
CoreStore.addSQLiteStore(/* ... */)
|
||||
}
|
||||
catch {
|
||||
// ...
|
||||
}
|
||||
```
|
||||
`requiredMigrationsForSQLiteStore(...)` returns an array of `MigrationType`s, where each item in the array may be either of the following values:
|
||||
```swift
|
||||
case Lightweight(sourceVersion: String, destinationVersion: String)
|
||||
case Heavyweight(sourceVersion: String, destinationVersion: String)
|
||||
```
|
||||
Each `MigrationType` indicates the migration type for each step in the `MigrationChain`. Use these information as fit for your app.
|
||||
|
||||
|
||||
|
||||
@@ -849,7 +956,7 @@ let person2 = self.monitor[1, 2]
|
||||
# Installation
|
||||
- Requires:
|
||||
- iOS 8 SDK and above
|
||||
- Swift 1.2
|
||||
- Swift 2.0 (XCode 7 beta 5)
|
||||
- Dependencies:
|
||||
- [GCDKit](https://github.com/JohnEstropia/GCDKit)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user