Compare commits

..

41 Commits
1.0.0 ... 1.2.1

Author SHA1 Message Date
John Rommel Estropia
682b13a8d3 version bump 2015-08-23 14:25:18 +09:00
John Rommel Estropia
2f935de04a temporarily fix an Xcode 7 bug (still present as of beta 5) (temporarily fixes #12) 2015-08-23 14:21:12 +09:00
John Rommel Estropia
ba4fb5e5cb version bump 2015-08-21 08:03:14 +09:00
John Rommel Estropia
d860da2507 Merge branch 'develop' 2015-08-21 08:03:01 +09:00
John Estropia
2bcf8008c5 let transaction fetch existing objects from external contexts 2015-08-20 17:20:38 +09:00
John Estropia
71477c0839 allow equality comparison on ListMonitor and ObjectMonitor to help distinguish senders when observing multiple monitors 2015-08-20 14:25:14 +09:00
John Estropia
3348aa0bef added utility to check if objects exist in a ListMonitor 2015-08-20 13:51:14 +09:00
John Estropia
2ed61fdb17 added utilities to ListMonitor to extract all objects in specified sections 2015-08-20 12:15:20 +09:00
John Estropia
9cfad8a17a oops, GroupBy should not be needed for monitorList() 2015-08-19 21:00:27 +09:00
John Estropia
3bf34f33dc added utilities for ListMonitor to optionally extract objects with potentially invalid indexes/indexPaths 2015-08-19 20:56:50 +09:00
John Estropia
90bfaeaae8 removed unused key tuple 2015-08-19 18:30:39 +09:00
John Estropia
a29a4b38fe added utilities to get existing NSManagedObject instances using object IDs 2015-08-19 15:53:20 +09:00
John Estropia
5a96ef13f6 added utility to get NSManagedObjectID from an object URI 2015-08-19 15:07:15 +09:00
John Estropia
b92ee76907 added utility to refresh an object 2015-08-19 12:19:14 +09:00
John Estropia
67ccae4ef6 added missing parameters 2015-08-19 11:21:39 +09:00
John Estropia
62b11309f3 expose DataStack vars to CoreStore 2015-08-18 21:49:40 +09:00
John Estropia
8c6a7df731 provide a way to enumerate entities managed by the DataStack 2015-08-18 21:40:33 +09:00
John Rommel Estropia
b475afe79f updated Readme 2015-08-17 23:51:08 +09:00
John Rommel Estropia
a263851266 import array of ImportableObjects 2015-08-17 23:50:03 +09:00
John Estropia
cbc3eb8887 woops, develop branch should be Swift 2.0, not 1.2 2015-08-11 15:37:44 +08:00
John Rommel Estropia
2fb3263aa1 refactored autoreleasepool calls 2015-08-09 18:27:21 +09:00
John Rommel Estropia
64aa97264e added utilities for importing data 2015-08-09 05:06:42 +09:00
John Rommel Estropia
8066bf2a5a fixed assertion failures when fetching from detached data transactions 2015-08-09 05:05:26 +09:00
John Rommel Estropia
283104af3f thanks to protocol extensions, you can now omit ListObserver and ObjectObserver methods you don't need to implement 2015-08-09 05:04:47 +09:00
John Rommel Estropia
83c724f584 updated default logger to still run assertions even on optimized (-O) builds 2015-08-08 22:12:08 +09:00
John Rommel Estropia
59ad525786 fixed for Xcode 7 beta 5 2015-08-08 10:24:40 +09:00
John Rommel Estropia
b85d7521e1 version bump 2015-07-29 22:24:11 +09:00
John Rommel Estropia
4dbc4558e3 display explanation dialog for migration demo 2015-07-29 22:21:22 +09:00
John Rommel Estropia
2a56c097f2 Merge branch 'master' into develop 2015-07-29 21:32:17 +09:00
John Estropia
d4b95aed64 Merge pull request #10 from bddckr/patch-1
Fix building with Carthage
2015-07-28 12:44:43 +09:00
Christopher - Marcel Böddecker
0d2650c54b Fix building with Carthage 2015-07-27 19:26:19 +02:00
John Rommel Estropia
6a3885edda aesthetic 2015-07-26 21:21:19 +09:00
John Rommel Estropia
1c6085ad82 expose DetachedDataTransaction's context and allow creating children detached transactions (https://github.com/JohnEstropia/CoreStore/issues/9) 2015-07-26 21:20:48 +09:00
John Rommel Estropia
2dae2ae39c update GCDKit submodule version 2015-07-26 17:11:53 +09:00
John Rommel Estropia
a1e37975cc updated project settings for Xcode 7 beta 4 2015-07-26 17:08:56 +09:00
John Rommel Estropia
0c8a43c3b0 added shorthand vars for inspecting MigrationType values. updated readme 2015-07-26 17:04:19 +09:00
John Rommel Estropia
9676e3aca2 addSQLiteStoreAndWait() does not support auto-migrating stores anymore; use the asynchronous addSQLiteStore(..., completion:) method instead. 2015-07-26 10:07:42 +09:00
John Rommel Estropia
a34d2795af remove queue asserts for detached transactions 2015-07-26 09:27:00 +09:00
John Rommel Estropia
106789d592 refactor some internal methods. renamed addInMemoryStore to addInMemoryStoreAndWait to reflect synchronicity. 2015-07-25 23:05:48 +09:00
John Rommel Estropia
edc51de030 catch intentional error in logger demo 2015-07-24 08:06:59 +09:00
John Rommel Estropia
f85eb2e057 merge changes to the main context asynchronously (fixes https://github.com/JohnEstropia/CoreStore/issues/7) 2015-07-24 08:05:13 +09:00
44 changed files with 1420 additions and 274 deletions

View File

@@ -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

View File

@@ -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;
};

View File

@@ -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

View File

@@ -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)
}
}

View File

@@ -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."
)

View File

@@ -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.

View File

@@ -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.

View 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] })
}
}
}

View File

@@ -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>

View 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
}
}

View 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 _ { }
}
}

View File

@@ -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
}
)

View File

@@ -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 {

View File

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

View File

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

View File

@@ -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
)
}

View File

@@ -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 {

View File

@@ -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
/**

View 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
}
}
}

View File

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

View File

@@ -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)
}

View File

@@ -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)
}

View File

@@ -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) { }
}

View File

@@ -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)
}

View File

@@ -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) { }
}

View File

@@ -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.

View File

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

View File

@@ -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
)
)
}
}

View File

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

View File

@@ -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
)
}
}

View File

@@ -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()

View File

@@ -18,7 +18,7 @@ private struct Static {
try! dataStack.addSQLiteStoreAndWait(
fileName: "TimeZoneDemo.sqlite",
configuration: "FetchingAndQueryingDemo",
resetStoreOnMigrationFailure: true
resetStoreOnModelMismatch: true
)
dataStack.beginSynchronous { (transaction) -> Void in

View File

@@ -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>

View File

@@ -17,7 +17,7 @@ private struct Static {
try! CoreStore.addSQLiteStoreAndWait(
fileName: "ColorsDemo.sqlite",
configuration: "ObservingDemo",
resetStoreOnMigrationFailure: true
resetStoreOnModelMismatch: true
)
return CoreStore.monitorSectionedList(

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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>

View File

@@ -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>

View File

@@ -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

View File

@@ -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))

View File

@@ -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 {

137
README.md
View File

@@ -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)