Compare commits
150 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ed0fdc76fe | ||
|
|
58f4907575 | ||
|
|
1950224863 | ||
|
|
f0cd288657 | ||
|
|
3344e42d7c | ||
|
|
e4b6c06401 | ||
|
|
7fc3ad2890 | ||
|
|
556f6d13b3 | ||
|
|
a088fe3817 | ||
|
|
c4391a8d0c | ||
|
|
f486ace437 | ||
|
|
edb2aa2463 | ||
|
|
7b48d825a3 | ||
|
|
921b85d91b | ||
|
|
4f46cbded9 | ||
|
|
a8af91b2f8 | ||
|
|
8a8b3fa0b1 | ||
|
|
4196ed8085 | ||
|
|
0458de5d65 | ||
|
|
90ae17e904 | ||
|
|
3c0d4d648d | ||
|
|
b60bcf8da6 | ||
|
|
f9014e65e0 | ||
|
|
82887b1dd2 | ||
|
|
11d428c05c | ||
|
|
0b48bb3347 | ||
|
|
aa5cd51da6 | ||
|
|
e50dd9881d | ||
|
|
3ccbce5c29 | ||
|
|
e5a199489c | ||
|
|
3d5c4f8121 | ||
|
|
2dc09289bf | ||
|
|
76a2bc1da2 | ||
|
|
8e5c7ec9b2 | ||
|
|
101ab69861 | ||
|
|
ea2a65cf57 | ||
|
|
f59b1b6320 | ||
|
|
141e977f9c | ||
|
|
788cce0df6 | ||
|
|
4304d8687e | ||
|
|
d25f751089 | ||
|
|
6df78a0595 | ||
|
|
c0ed2f23ce | ||
|
|
dcf562fb26 | ||
|
|
b9353238e8 | ||
|
|
b98805e489 | ||
|
|
02a89accc8 | ||
|
|
2e4c9a7a25 | ||
|
|
fcca6b205e | ||
|
|
b199f38b0c | ||
|
|
0c8731c610 | ||
|
|
ee4eb178ed | ||
|
|
f617f2cc53 | ||
|
|
f396b01043 | ||
|
|
7942cf411e | ||
|
|
02be72b0b7 | ||
|
|
000a357808 | ||
|
|
77236d66d5 | ||
|
|
8b95d337a3 | ||
|
|
57b66cff34 | ||
|
|
9dae291f62 | ||
|
|
0c2a3ac4e9 | ||
|
|
a6291e116c | ||
|
|
ae5f737baf | ||
|
|
4eecd80710 | ||
|
|
0073d038e0 | ||
|
|
099dcfab68 | ||
|
|
0bbb4118a1 | ||
|
|
3fe9e4ee1d | ||
|
|
bd19326447 | ||
|
|
dccc958ef1 | ||
|
|
ab3095f078 | ||
|
|
1a86510045 | ||
|
|
aa32f5e158 | ||
|
|
b95aaf151d | ||
|
|
114bc71841 | ||
|
|
83d0412cb9 | ||
|
|
26e4118355 | ||
|
|
28b7ba01dc | ||
|
|
b529735269 | ||
|
|
d23121d8ed | ||
|
|
a4d3d0c0ed | ||
|
|
8b7af86526 | ||
|
|
09d844f5df | ||
|
|
e25e198bf8 | ||
|
|
e99d19d2ac | ||
|
|
9d7960e674 | ||
|
|
e9ac8629a1 | ||
|
|
b0b0df2861 | ||
|
|
eda398d758 | ||
|
|
40c320e1ca | ||
|
|
8508dd4f79 | ||
|
|
d3e0f576cf | ||
|
|
1d5cf5a4cc | ||
|
|
9fc0390b45 | ||
|
|
b8ea7ecf01 | ||
|
|
789028bc58 | ||
|
|
a168ca577a | ||
|
|
781f3988d2 | ||
|
|
1ff635d8b5 | ||
|
|
707445a169 | ||
|
|
90369cf994 | ||
|
|
09708e587c | ||
|
|
36e6e4a8b9 | ||
|
|
ed8891a6d5 | ||
|
|
dd6c6abaf0 | ||
|
|
2c65ac1834 | ||
|
|
24008d62b2 | ||
|
|
e8a9cc9d67 | ||
|
|
1507ac63f9 | ||
|
|
f2df8f7171 | ||
|
|
3ddfd3cccc | ||
|
|
21f57518c8 | ||
|
|
d9422f7f2e | ||
|
|
245ec25ad8 | ||
|
|
0ce89b8460 | ||
|
|
42a889a28e | ||
|
|
456977bf12 | ||
|
|
ea8412ab93 | ||
|
|
603dffffb0 | ||
|
|
8a1144b1be | ||
|
|
4ff7b2d6d9 | ||
|
|
fa947faa57 | ||
|
|
6822a4579e | ||
|
|
a89dd76906 | ||
|
|
c85ef16ad0 | ||
|
|
67f1bd9a63 | ||
|
|
39540628df | ||
|
|
c86ca06bd4 | ||
|
|
40c27b9fcb | ||
|
|
2f8c100cb6 | ||
|
|
34495d7163 | ||
|
|
75a4ebb49b | ||
|
|
3c514830d9 | ||
|
|
b283fbf5ab | ||
|
|
c1e087b8c1 | ||
|
|
ad1ebb3501 | ||
|
|
99189d160f | ||
|
|
f71ad4c577 | ||
|
|
d05fcc5103 | ||
|
|
e9329fc93c | ||
|
|
1ea773f0ec | ||
|
|
c68c2bdc0a | ||
|
|
91dd4b6cb3 | ||
|
|
0800b706d6 | ||
|
|
2071ce722e | ||
|
|
df866718cf | ||
|
|
f19a0d29eb | ||
|
|
8f09f90294 | ||
|
|
7bddcaa4a2 |
2
Cartfile
@@ -1 +1 @@
|
||||
github "JohnEstropia/GCDKit" == 1.2.4
|
||||
github "JohnEstropia/GCDKit" == 1.2.6
|
||||
|
||||
@@ -1 +1 @@
|
||||
github "JohnEstropia/GCDKit" "1.2.4"
|
||||
github "JohnEstropia/GCDKit" "1.2.6"
|
||||
|
||||
2
Carthage/Checkouts/GCDKit
vendored
BIN
CoreStore.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CoreStore"
|
||||
s.version = "1.6.6"
|
||||
s.version = "2.0.4"
|
||||
s.license = "MIT"
|
||||
s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift"
|
||||
s.homepage = "https://github.com/JohnEstropia/CoreStore"
|
||||
@@ -12,11 +12,12 @@ Pod::Spec.new do |s|
|
||||
s.watchos.deployment_target = "2.0"
|
||||
s.tvos.deployment_target = "9.0"
|
||||
|
||||
s.source_files = "CoreStore", "CoreStore/**/*.{swift}"
|
||||
s.osx.exclude_files = "CoreStore/Observing/*.{swift}", "CoreStore/Internal/FetchedResultsControllerDelegate.swift", "CoreStore/Internal/CoreStoreFetchedResultsController.swift", "CoreStore/Convenience Helpers/NSFetchedResultsController+Convenience.swift"
|
||||
s.source_files = "Sources", "Sources/**/*.{swift,h,m}"
|
||||
s.public_header_files = "Sources/**/*.h"
|
||||
s.frameworks = "Foundation", "CoreData"
|
||||
s.requires_arc = true
|
||||
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-D USE_FRAMEWORKS' }
|
||||
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-D USE_FRAMEWORKS',
|
||||
'GCC_PREPROCESSOR_DEFINITIONS' => 'USE_FRAMEWORKS=1' }
|
||||
|
||||
s.dependency "GCDKit", "1.2.5"
|
||||
s.dependency "GCDKit", "1.2.6"
|
||||
end
|
||||
BIN
CoreStore.sketch
Normal file
@@ -11,7 +11,8 @@
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
buildForAnalyzing = "YES"
|
||||
hideIssues = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
@@ -25,7 +26,8 @@
|
||||
buildForRunning = "NO"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "NO">
|
||||
buildForAnalyzing = "NO"
|
||||
hideIssues = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0,
|
||||
"95438028B10BBB846574013D29F154A00556A9D1" : 0,
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "EBFDEFFE-8BA0-441A-862A-1DE28AA5CD21",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStore\/Carthage\/Checkouts\/GCDKit\/",
|
||||
"95438028B10BBB846574013D29F154A00556A9D1" : "CoreStore\/Carthage\/Checkouts\/Nimble\/",
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore\/"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
|
||||
@@ -25,6 +27,11 @@
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/JohnEstropia\/GCDKit.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97"
|
||||
},
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Quick\/Nimble.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "95438028B10BBB846574013D29F154A00556A9D1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1,92 +0,0 @@
|
||||
//
|
||||
// NSManagedObject+Convenience.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - NSFetchedResultsController
|
||||
|
||||
public extension NSFetchedResultsController {
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful to partially support Objective-C classes by passing an `NSFetchedResultsController` instance instead of a `ListMonitor`.
|
||||
*/
|
||||
public static func createForStack<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
|
||||
|
||||
return CoreStoreFetchedResultsController<T>(
|
||||
context: dataStack.mainContext,
|
||||
fetchRequest: fetchRequest,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
@available(*, deprecated=1.5.2, message="Use NSFetchedResultsController.createForStack(_:fetchRequest:from:sectionBy:fetchClauses:) to create NSFetchedResultsControllers directly")
|
||||
public convenience init<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
|
||||
|
||||
let context = dataStack.mainContext
|
||||
from?.applyToFetchRequest(fetchRequest, context: context, applyAffectedStores: false)
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
if let from = from {
|
||||
|
||||
from.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
|
||||
}
|
||||
else {
|
||||
|
||||
guard let from = (fetchRequest.entity.flatMap { $0.managedObjectClassName }).flatMap(NSClassFromString).flatMap(From.init) else {
|
||||
|
||||
fatalError("Attempted to create an \(typeName(NSFetchedResultsController)) without a From clause or an NSEntityDescription.")
|
||||
}
|
||||
from.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
|
||||
}
|
||||
|
||||
self.init(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: context,
|
||||
sectionNameKeyPath: sectionBy?.sectionKeyPath,
|
||||
cacheName: nil
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static func createFromContext<T: NSManagedObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
|
||||
|
||||
return CoreStoreFetchedResultsController<T>(
|
||||
context: context,
|
||||
fetchRequest: fetchRequest,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
//
|
||||
// NSObject+CoreStore.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 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 getAssociatedObjectForKey<T: AnyObject>(key: UnsafePointer<Void>, inObject object: AnyObject) -> T? {
|
||||
|
||||
switch objc_getAssociatedObject(object, key) {
|
||||
|
||||
case let associatedObject as T:
|
||||
return associatedObject
|
||||
|
||||
case let associatedObject as WeakObject:
|
||||
return associatedObject.object as? T
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
internal func setAssociatedRetainedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
|
||||
internal func setAssociatedCopiedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_COPY_NONATOMIC)
|
||||
}
|
||||
|
||||
internal func setAssociatedAssignedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_ASSIGN)
|
||||
}
|
||||
|
||||
internal func setAssociatedWeakObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
if let associatedObject = associatedObject {
|
||||
|
||||
objc_setAssociatedObject(object, key, WeakObject(associatedObject), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
else {
|
||||
|
||||
objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// 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) {
|
||||
|
||||
_ = try? self.removeItemAtURL(fileURL)
|
||||
|
||||
let filePath = fileURL.path!
|
||||
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-shm"))
|
||||
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-wal"))
|
||||
}
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
//
|
||||
// NSPersistentStoreCoordinator+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
// 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
|
||||
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - NSPersistentStoreCoordinator
|
||||
|
||||
internal extension NSPersistentStoreCoordinator {
|
||||
|
||||
internal func performAsynchronously(closure: () -> Void) {
|
||||
|
||||
#if USE_FRAMEWORKS
|
||||
|
||||
self.performBlock(closure)
|
||||
#else
|
||||
|
||||
if #available(iOS 8.0, *) {
|
||||
|
||||
self.performBlock(closure)
|
||||
}
|
||||
else {
|
||||
|
||||
self.lock()
|
||||
GCDQueue.Default.async {
|
||||
|
||||
closure()
|
||||
self.unlock()
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
internal func performSynchronously(closure: () -> Void) {
|
||||
|
||||
#if USE_FRAMEWORKS
|
||||
|
||||
self.performBlockAndWait(closure)
|
||||
#else
|
||||
|
||||
if #available(iOS 8.0, *) {
|
||||
|
||||
self.performBlockAndWait(closure)
|
||||
}
|
||||
else {
|
||||
|
||||
self.lock()
|
||||
autoreleasepool(closure)
|
||||
self.unlock()
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
//
|
||||
// CoreStoreLogger.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 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: - LogLevel
|
||||
|
||||
/**
|
||||
The `LogLevel` indicates the severity of a log message.
|
||||
*/
|
||||
public enum LogLevel {
|
||||
|
||||
case Trace
|
||||
case Notice
|
||||
case Warning
|
||||
case Fatal
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CoreStoreLogger
|
||||
|
||||
/**
|
||||
Custom loggers should implement the `CoreStoreLogger` protocol and pass its instance to `CoreStore.logger`. Calls to `log(...)`, `handleError(...)`, and `assert(...)` are not tied to a specific queue/thread, so it is the implementer's job to handle thread-safety.
|
||||
*/
|
||||
public protocol CoreStoreLogger {
|
||||
|
||||
/**
|
||||
Handles log messages sent by the `CoreStore` framework.
|
||||
|
||||
:level: the severity of the log message
|
||||
:message: the log message
|
||||
:fileName: the source file name
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
|
||||
/**
|
||||
Handles errors sent by the `CoreStore` framework.
|
||||
|
||||
:error: the error
|
||||
:message: the error message
|
||||
:fileName: the source file name
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
|
||||
/**
|
||||
Handles assertions made throughout the `CoreStore` framework.
|
||||
|
||||
:condition: the assertion condition
|
||||
:message: the assertion message
|
||||
:fileName: the source file name
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Utilities
|
||||
|
||||
internal func typeName<T>(value: T) -> String {
|
||||
|
||||
return "'\(String(reflecting: value.dynamicType))'"
|
||||
}
|
||||
|
||||
internal func typeName<T>(value: T.Type) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(value: AnyClass) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(name: String?) -> String {
|
||||
|
||||
return "<\(name ?? "unknown")>"
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
//
|
||||
// CoreStore+Migration.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 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
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
/**
|
||||
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). 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, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.addSQLiteStore(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). 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(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.addSQLiteStore(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
|
||||
|
||||
return try self.defaultStack.requiredMigrationsForSQLiteStore(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
|
||||
|
||||
return try self.defaultStack.requiredMigrationsForSQLiteStore(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,644 +0,0 @@
|
||||
//
|
||||
// DataStack+Migration.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 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
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
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.performAsynchronously {
|
||||
|
||||
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 (or the "Caches" directory on tvOS). 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, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.addSQLiteStore(
|
||||
fileURL: defaultDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously adds to the stack an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- 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 (or the "Caches" directory on tvOS). 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(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
CoreStore.assert(
|
||||
fileURL.fileURL,
|
||||
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
|
||||
)
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
if let store = coordinator.persistentStoreForURL(fileURL) {
|
||||
|
||||
guard store.type == NSSQLiteStoreType
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
|
||||
|
||||
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
_ = try? fileManager.createDirectoryAtURL(
|
||||
fileURL.URLByDeletingLastPathComponent!,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
|
||||
do {
|
||||
|
||||
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
options: self.optionsForSQLiteStore()
|
||||
)
|
||||
|
||||
return self.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: fileURL,
|
||||
metadata: metadata,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: { (result) -> Void in
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
let store = try self.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: false
|
||||
)
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
catch {
|
||||
|
||||
completion(PersistentStoreResult(error as NSError))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
catch let error as NSError
|
||||
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
|
||||
|
||||
let store = try self.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: false
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: defaultDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
let metadata: [String: AnyObject]
|
||||
do {
|
||||
|
||||
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
options: self.optionsForSQLiteStore()
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
return self.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: fileURL,
|
||||
metadata: metadata,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
|
||||
|
||||
return try requiredMigrationsForSQLiteStore(
|
||||
fileURL: defaultDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
|
||||
|
||||
let metadata: [String : AnyObject]
|
||||
do {
|
||||
|
||||
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
options: self.optionsForSQLiteStore()
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else {
|
||||
|
||||
let error = NSError(coreStoreErrorCode: .MappingModelNotFound)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(self.modelVersion)\"."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
return migrationSteps.map { $0.migrationType }
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL, metadata: [String: AnyObject], configuration: String?, mappingModelBundles: [NSBundle]?, completion: (MigrationResult) -> Void) -> NSProgress? {
|
||||
|
||||
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else {
|
||||
|
||||
CoreStore.handleError(
|
||||
NSError(coreStoreErrorCode: .MappingModelNotFound),
|
||||
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(model)\"."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(MigrationResult(.MappingModelNotFound))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let numberOfMigrations: Int64 = Int64(migrationSteps.count)
|
||||
if numberOfMigrations == 0 {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(MigrationResult([]))
|
||||
return
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let migrationTypes = migrationSteps.map { $0.migrationType }
|
||||
var migrationResult: MigrationResult?
|
||||
var operations = [NSOperation]()
|
||||
var cancelled = false
|
||||
|
||||
let progress = NSProgress(parent: nil, userInfo: nil)
|
||||
progress.totalUnitCount = numberOfMigrations
|
||||
|
||||
// todo nsprogress crashing sometimes
|
||||
for (sourceModel, destinationModel, mappingModel, _) in migrationSteps {
|
||||
|
||||
progress.becomeCurrentWithPendingUnitCount(1)
|
||||
|
||||
let childProgress = NSProgress(parent: progress, userInfo: nil)
|
||||
childProgress.totalUnitCount = 100
|
||||
|
||||
operations.append(
|
||||
NSBlockOperation { [weak self] in
|
||||
|
||||
guard let strongSelf = self where !cancelled else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
try strongSelf.startMigrationForSQLiteStore(
|
||||
fileURL: fileURL,
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
mappingModel: mappingModel,
|
||||
progress: childProgress
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
migrationResult = MigrationResult(error as NSError)
|
||||
cancelled = true
|
||||
}
|
||||
}
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
withExtendedLifetime(childProgress) { (_: NSProgress) -> Void in }
|
||||
return
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
progress.resignCurrent()
|
||||
}
|
||||
|
||||
let migrationOperation = NSBlockOperation()
|
||||
#if USE_FRAMEWORKS
|
||||
|
||||
migrationOperation.qualityOfService = .Utility
|
||||
#else
|
||||
|
||||
if #available(iOS 8.0, *) {
|
||||
|
||||
migrationOperation.qualityOfService = .Utility
|
||||
}
|
||||
#endif
|
||||
operations.forEach { migrationOperation.addDependency($0) }
|
||||
migrationOperation.addExecutionBlock { () -> Void in
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
progress.setProgressHandler(nil)
|
||||
completion(migrationResult ?? MigrationResult(migrationTypes))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
operations.append(migrationOperation)
|
||||
|
||||
self.migrationQueue.addOperations(operations, waitUntilFinished: false)
|
||||
|
||||
return progress
|
||||
}
|
||||
|
||||
private func computeMigrationFromStoreMetadata(metadata: [String: AnyObject], configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? {
|
||||
|
||||
let model = self.model
|
||||
if model.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) {
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
guard let initialModel = model[metadata],
|
||||
var currentVersion = initialModel.currentModelVersion else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let migrationChain: MigrationChain = self.migrationChain.empty
|
||||
? [currentVersion: model.currentModelVersion!]
|
||||
: self.migrationChain
|
||||
|
||||
var migrationSteps = [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]()
|
||||
|
||||
while let nextVersion = migrationChain.nextVersionFrom(currentVersion),
|
||||
let sourceModel = model[currentVersion],
|
||||
let destinationModel = model[nextVersion] where sourceModel != model {
|
||||
|
||||
if let mappingModel = NSMappingModel(
|
||||
fromBundles: mappingModelBundles,
|
||||
forSourceModel: sourceModel,
|
||||
destinationModel: destinationModel) {
|
||||
|
||||
migrationSteps.append(
|
||||
(
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
mappingModel: mappingModel,
|
||||
migrationType: .Heavyweight(
|
||||
sourceVersion: currentVersion,
|
||||
destinationVersion: nextVersion
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
do {
|
||||
|
||||
let mappingModel = try NSMappingModel.inferredMappingModelForSourceModel(
|
||||
sourceModel,
|
||||
destinationModel: destinationModel
|
||||
)
|
||||
|
||||
migrationSteps.append(
|
||||
(
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
mappingModel: mappingModel,
|
||||
migrationType: .Lightweight(
|
||||
sourceVersion: currentVersion,
|
||||
destinationVersion: nextVersion
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
currentVersion = nextVersion
|
||||
}
|
||||
|
||||
if migrationSteps.last?.destinationModel == model {
|
||||
|
||||
return migrationSteps
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func startMigrationForSQLiteStore(fileURL fileURL: NSURL, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, progress: NSProgress) throws {
|
||||
|
||||
autoreleasepool {
|
||||
|
||||
let journalUpdatingCoordinator = NSPersistentStoreCoordinator(managedObjectModel: sourceModel)
|
||||
let store = try! journalUpdatingCoordinator.addPersistentStoreWithType(
|
||||
NSSQLiteStoreType,
|
||||
configuration: nil,
|
||||
URL: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
|
||||
)
|
||||
try! journalUpdatingCoordinator.removePersistentStore(store)
|
||||
}
|
||||
|
||||
let migrationManager = MigrationManager(
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
progress: progress
|
||||
)
|
||||
|
||||
let temporaryDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString)
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
try! fileManager.createDirectoryAtURL(
|
||||
temporaryDirectoryURL,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
|
||||
let temporaryFileURL = temporaryDirectoryURL.URLByAppendingPathComponent(fileURL.lastPathComponent!, isDirectory: false)
|
||||
|
||||
do {
|
||||
|
||||
try migrationManager.migrateStoreFromURL(
|
||||
fileURL,
|
||||
type: NSSQLiteStoreType,
|
||||
options: nil,
|
||||
withMappingModel: mappingModel,
|
||||
toDestinationURL: temporaryFileURL,
|
||||
destinationType: NSSQLiteStoreType,
|
||||
destinationOptions: nil
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
do {
|
||||
|
||||
try fileManager.removeItemAtURL(temporaryDirectoryURL)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
|
||||
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to migrate from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
|
||||
)
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try fileManager.replaceItemAtURL(
|
||||
fileURL,
|
||||
withItemAtURL: temporaryFileURL,
|
||||
backupItemName: nil,
|
||||
options: [],
|
||||
resultingItemURL: nil
|
||||
)
|
||||
|
||||
progress.completedUnitCount = progress.totalUnitCount
|
||||
|
||||
do {
|
||||
|
||||
try fileManager.removeItemAtPath(fileURL.path! + "-shm")
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
catch {
|
||||
|
||||
do {
|
||||
|
||||
try fileManager.removeItemAtURL(temporaryDirectoryURL)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
|
||||
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to save store after migrating from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
|
||||
)
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
//
|
||||
// NSError+CoreStore.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - CoreStoreError
|
||||
|
||||
/**
|
||||
The `NSError` error domain for `CoreStore`.
|
||||
*/
|
||||
public let CoreStoreErrorDomain = "com.corestore.error"
|
||||
|
||||
/**
|
||||
The `NSError` error codes for `CoreStoreErrorDomain`.
|
||||
*/
|
||||
public enum CoreStoreErrorCode: Int {
|
||||
|
||||
/**
|
||||
A failure occured because of an unknown error.
|
||||
*/
|
||||
case UnknownError
|
||||
|
||||
/**
|
||||
The `NSPersistentStore` could note be initialized because another store existed at the specified `NSURL`.
|
||||
*/
|
||||
case DifferentPersistentStoreExistsAtURL
|
||||
|
||||
/**
|
||||
The `NSPersistentStore` specified could not be found.
|
||||
*/
|
||||
case PersistentStoreNotFound
|
||||
|
||||
/**
|
||||
An `NSMappingModel` could not be found for a specific source and destination model versions.
|
||||
*/
|
||||
case MappingModelNotFound
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSError
|
||||
|
||||
public extension NSError {
|
||||
|
||||
/**
|
||||
If the error's domain is equal to `CoreStoreErrorDomain`, returns the associated `CoreStoreErrorCode`. For other domains, returns `nil`.
|
||||
*/
|
||||
public var coreStoreErrorCode: CoreStoreErrorCode? {
|
||||
|
||||
return (self.domain == CoreStoreErrorDomain
|
||||
? CoreStoreErrorCode(rawValue: self.code)
|
||||
: nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode) {
|
||||
|
||||
self.init(coreStoreErrorCode: coreStoreErrorCode, userInfo: nil)
|
||||
}
|
||||
|
||||
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(
|
||||
domain: CoreStoreErrorDomain,
|
||||
code: coreStoreErrorCode.rawValue,
|
||||
userInfo: userInfo)
|
||||
}
|
||||
|
||||
internal var isCoreDataMigrationError: Bool {
|
||||
|
||||
let code = self.code
|
||||
return (code == NSPersistentStoreIncompatibleVersionHashError
|
||||
|| code == NSMigrationMissingSourceModelError
|
||||
|| code == NSMigrationError)
|
||||
&& self.domain == NSCocoaErrorDomain
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
//
|
||||
// CoreStore+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 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
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
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 entityTypesByName: [String: NSManagedObject.Type] {
|
||||
|
||||
return self.defaultStack.entityTypesByName
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `defaultStack`'s model.
|
||||
*/
|
||||
public static func entityDescriptionForType(type: NSManagedObject.Type) -> NSEntityDescription? {
|
||||
|
||||
return self.defaultStack.entityDescriptionForType(type)
|
||||
}
|
||||
|
||||
/**
|
||||
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 addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addInMemoryStoreAndWait(configuration: configuration)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the `defaultStack` an SQLite store from the given SQLite file name.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). 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 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, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the `defaultStack` an SQLite store from the given SQLite file URL.
|
||||
|
||||
- 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 (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
- 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, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,414 +0,0 @@
|
||||
//
|
||||
// DataStack.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
#if os(tvOS)
|
||||
internal let deviceDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
|
||||
#else
|
||||
internal let deviceDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
|
||||
#endif
|
||||
|
||||
internal let defaultDirectory = NSFileManager.defaultManager().URLsForDirectory(deviceDirectorySearchPath, inDomains: .UserDomainMask).first!
|
||||
|
||||
internal let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
|
||||
|
||||
internal let defaultSQLiteStoreURL = defaultDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite")
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
/**
|
||||
The `DataStack` encapsulates the data model for the Core Data stack. Each `DataStack` can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own `NSPersistentStoreCoordinator`, a root `NSManagedObjectContext` for disk saves, and a shared `NSManagedObjectContext` designed as a read-only model interface for `NSManagedObjects`.
|
||||
*/
|
||||
public final class DataStack {
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from an `NSManagedObjectModel`.
|
||||
|
||||
- parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name will be used.
|
||||
- parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used.
|
||||
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
public required init(modelName: String = applicationName, bundle: NSBundle = NSBundle.mainBundle(), migrationChain: MigrationChain = nil) {
|
||||
|
||||
CoreStore.assert(
|
||||
migrationChain.valid,
|
||||
"Invalid migration chain passed to the \(typeName(DataStack)). Check that the model versions' order is correct and that no repetitions or ambiguities exist."
|
||||
)
|
||||
|
||||
let model = NSManagedObjectModel.fromBundle(
|
||||
bundle,
|
||||
modelName: modelName,
|
||||
modelVersionHints: migrationChain.leafVersions
|
||||
)
|
||||
|
||||
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
|
||||
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
|
||||
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
|
||||
self.model = model
|
||||
self.migrationChain = migrationChain
|
||||
|
||||
self.rootSavingContext.parentStack = self
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `DataStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
|
||||
*/
|
||||
public var modelVersion: String {
|
||||
|
||||
return self.model.currentModelVersion!
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the entity name-to-class type mapping from the `DataStack`'s model.
|
||||
*/
|
||||
public var entityTypesByName: [String: NSManagedObject.Type] {
|
||||
|
||||
return self.model.entityTypesMapping()
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass.
|
||||
*/
|
||||
public func entityDescriptionForType(type: NSManagedObject.Type) -> NSEntityDescription? {
|
||||
|
||||
return NSEntityDescription.entityForName(
|
||||
self.model.entityNameForClass(type),
|
||||
inManagedObjectContext: self.mainContext
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
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 addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
|
||||
var store: NSPersistentStore?
|
||||
var storeError: NSError?
|
||||
coordinator.performSynchronously {
|
||||
|
||||
do {
|
||||
|
||||
store = try coordinator.addPersistentStoreWithType(
|
||||
NSInMemoryStoreType,
|
||||
configuration: configuration,
|
||||
URL: nil,
|
||||
options: nil
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
storeError = error as NSError
|
||||
}
|
||||
}
|
||||
|
||||
if let store = store {
|
||||
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
return store
|
||||
}
|
||||
|
||||
let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the stack an SQLite store from the given SQLite file name.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). 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 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, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.addSQLiteStoreAndWait(
|
||||
fileURL: defaultDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the stack an SQLite store from the given SQLite file URL.
|
||||
|
||||
- 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 (or the "Caches" directory on tvOS). 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 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, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
CoreStore.assert(
|
||||
fileURL.fileURL,
|
||||
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
|
||||
)
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
if let store = coordinator.persistentStoreForURL(fileURL) {
|
||||
|
||||
guard store.type == NSSQLiteStoreType
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
|
||||
|
||||
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
|
||||
)
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
_ = try? fileManager.createDirectoryAtURL(
|
||||
fileURL.URLByDeletingLastPathComponent!,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
|
||||
var store: NSPersistentStore?
|
||||
var storeError: NSError?
|
||||
let options = self.optionsForSQLiteStore()
|
||||
coordinator.performSynchronously {
|
||||
|
||||
do {
|
||||
|
||||
store = try coordinator.addPersistentStoreWithType(
|
||||
NSSQLiteStoreType,
|
||||
configuration: configuration,
|
||||
URL: fileURL,
|
||||
options: options
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
storeError = error as NSError
|
||||
}
|
||||
}
|
||||
|
||||
if let store = store {
|
||||
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
return store
|
||||
}
|
||||
|
||||
if let error = storeError
|
||||
where (resetStoreOnModelMismatch && error.isCoreDataMigrationError) {
|
||||
|
||||
fileManager.removeSQLiteStoreAtURL(fileURL)
|
||||
|
||||
var store: NSPersistentStore?
|
||||
coordinator.performSynchronously {
|
||||
|
||||
do {
|
||||
|
||||
store = try coordinator.addPersistentStoreWithType(
|
||||
NSSQLiteStoreType,
|
||||
configuration: configuration,
|
||||
URL: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
storeError = error as NSError
|
||||
}
|
||||
}
|
||||
|
||||
if let store = store {
|
||||
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
return store
|
||||
}
|
||||
}
|
||||
|
||||
let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\"."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let coordinator: NSPersistentStoreCoordinator
|
||||
internal let rootSavingContext: NSManagedObjectContext
|
||||
internal let mainContext: NSManagedObjectContext
|
||||
internal let model: NSManagedObjectModel
|
||||
internal let migrationChain: MigrationChain
|
||||
internal let childTransactionQueue: GCDQueue = .createSerial("com.coreStore.dataStack.childTransactionQueue")
|
||||
internal let migrationQueue: NSOperationQueue = {
|
||||
|
||||
let migrationQueue = NSOperationQueue()
|
||||
migrationQueue.maxConcurrentOperationCount = 1
|
||||
migrationQueue.name = "com.coreStore.migrationOperationQueue"
|
||||
#if USE_FRAMEWORKS
|
||||
|
||||
migrationQueue.qualityOfService = .Utility
|
||||
migrationQueue.underlyingQueue = dispatch_queue_create("com.coreStore.migrationQueue", DISPATCH_QUEUE_SERIAL)
|
||||
#else
|
||||
|
||||
if #available(iOS 8.0, *) {
|
||||
|
||||
migrationQueue.qualityOfService = .Utility
|
||||
migrationQueue.underlyingQueue = dispatch_queue_create("com.coreStore.migrationQueue", DISPATCH_QUEUE_SERIAL)
|
||||
}
|
||||
#endif
|
||||
return migrationQueue
|
||||
}()
|
||||
|
||||
internal func optionsForSQLiteStore() -> [String: AnyObject] {
|
||||
|
||||
return [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
|
||||
}
|
||||
|
||||
internal func entityNameForEntityClass(entityClass: AnyClass) -> String? {
|
||||
|
||||
return self.model.entityNameForClass(entityClass)
|
||||
}
|
||||
|
||||
internal func persistentStoresForEntityClass(entityClass: AnyClass) -> [NSPersistentStore]? {
|
||||
|
||||
var returnValue: [NSPersistentStore]? = nil
|
||||
self.storeMetadataUpdateQueue.barrierSync {
|
||||
|
||||
returnValue = self.entityConfigurationsMapping[NSStringFromClass(entityClass)]?.map {
|
||||
|
||||
return self.configurationStoreMapping[$0]!
|
||||
} ?? []
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
|
||||
internal func persistentStoreForEntityClass(entityClass: AnyClass, configuration: String?, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
|
||||
|
||||
var returnValue: (store: NSPersistentStore?, isAmbiguous: Bool) = (store: nil, isAmbiguous: false)
|
||||
self.storeMetadataUpdateQueue.barrierSync {
|
||||
|
||||
let configurationsForEntity = self.entityConfigurationsMapping[NSStringFromClass(entityClass)] ?? []
|
||||
if let configuration = configuration {
|
||||
|
||||
if configurationsForEntity.contains(configuration) {
|
||||
|
||||
returnValue = (store: self.configurationStoreMapping[configuration], isAmbiguous: false)
|
||||
return
|
||||
}
|
||||
else if !inferStoreIfPossible {
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch configurationsForEntity.count {
|
||||
|
||||
case 0:
|
||||
return
|
||||
|
||||
case 1 where inferStoreIfPossible:
|
||||
returnValue = (store: self.configurationStoreMapping[configurationsForEntity.first!], isAmbiguous: false)
|
||||
|
||||
default:
|
||||
returnValue = (store: nil, isAmbiguous: true)
|
||||
}
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
|
||||
internal func updateMetadataForPersistentStore(persistentStore: NSPersistentStore) {
|
||||
|
||||
self.storeMetadataUpdateQueue.barrierAsync {
|
||||
|
||||
let configurationName = persistentStore.configurationName
|
||||
self.configurationStoreMapping[configurationName] = persistentStore
|
||||
for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) ?? []) {
|
||||
|
||||
let managedObjectClassName = entityDescription.managedObjectClassName
|
||||
CoreStore.assert(
|
||||
NSClassFromString(managedObjectClassName) != nil,
|
||||
"The class \(typeName(managedObjectClassName)) for the entity \(typeName(entityDescription.name)) does not exist. Check if the subclass type and module name are properly configured."
|
||||
)
|
||||
|
||||
if self.entityConfigurationsMapping[managedObjectClassName] == nil {
|
||||
|
||||
self.entityConfigurationsMapping[managedObjectClassName] = []
|
||||
}
|
||||
self.entityConfigurationsMapping[managedObjectClassName]?.insert(configurationName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let storeMetadataUpdateQueue = GCDQueue.createConcurrent("com.coreStore.persistentStoreBarrierQueue")
|
||||
private var configurationStoreMapping = [String: NSPersistentStore]()
|
||||
private var entityConfigurationsMapping = [String: Set<String>]()
|
||||
|
||||
deinit {
|
||||
|
||||
let coordinator = self.coordinator
|
||||
coordinator.performAsynchronously {
|
||||
|
||||
withExtendedLifetime(coordinator) { coordinator in
|
||||
|
||||
coordinator.persistentStores.forEach {
|
||||
|
||||
_ = try? coordinator.removePersistentStore($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,106 +0,0 @@
|
||||
//
|
||||
// PersistentStoreResult.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - PersistentStoreResult
|
||||
|
||||
/**
|
||||
The `PersistentStoreResult` indicates the result of an asynchronous initialization of a persistent store.
|
||||
The `PersistentStoreResult` can be treated as a boolean:
|
||||
```
|
||||
try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
|
||||
if result {
|
||||
// succeeded
|
||||
}
|
||||
else {
|
||||
// failed
|
||||
}
|
||||
})
|
||||
```
|
||||
or as an `enum`, where the resulting associated object can also be inspected:
|
||||
```
|
||||
try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
|
||||
switch result {
|
||||
case .Success(let persistentStore):
|
||||
// persistentStore is the related NSPersistentStore instance
|
||||
case .Failure(let error):
|
||||
// error is the NSError instance for the failure
|
||||
}
|
||||
})
|
||||
```
|
||||
*/
|
||||
public enum PersistentStoreResult {
|
||||
|
||||
/**
|
||||
`PersistentStoreResult.Success` indicates that the persistent store process succeeded. The associated object for this `enum` value is the related `NSPersistentStore` instance.
|
||||
*/
|
||||
case Success(NSPersistentStore)
|
||||
|
||||
/**
|
||||
`PersistentStoreResult.Failure` indicates that the persistent store process failed. The associated object for this value is the related `NSError` instance.
|
||||
*/
|
||||
case Failure(NSError)
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(_ store: NSPersistentStore) {
|
||||
|
||||
self = .Success(store)
|
||||
}
|
||||
|
||||
internal init(_ error: NSError) {
|
||||
|
||||
self = .Failure(error)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode) {
|
||||
|
||||
self.init(errorCode, userInfo: nil)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - PersistentStoreResult: BooleanType
|
||||
|
||||
extension PersistentStoreResult: BooleanType {
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .Success: return true
|
||||
case .Failure: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -459,7 +459,7 @@
|
||||
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -471,7 +471,7 @@
|
||||
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
|
||||
@@ -21,6 +21,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
|
||||
application.statusBarStyle = .LightContent
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10112" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10083"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -11,14 +12,17 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="CoreStoreIcon" translatesAutoresizingMaskIntoConstraints="NO" id="q8C-V6-gXH">
|
||||
<rect key="frame" x="155" y="83" width="170" height="170"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="273" width="440" height="57.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -26,16 +30,21 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstItem="q8C-V6-gXH" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" multiplier="0.7" id="QW6-8Y-w15"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="q8C-V6-gXH" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="fRb-1V-9iD"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="top" secondItem="q8C-V6-gXH" secondAttribute="bottom" constant="20" id="s63-MP-ush"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="CoreStoreIcon" width="170" height="170"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
53
CoreStoreDemo/CoreStoreDemo/Base.lproj/LaunchScreen.xib.orig
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<<<<<<< Updated upstream
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
=======
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
|
||||
>>>>>>> Stashed changes
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<<<<<<< Updated upstream
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
=======
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439.5" width="441" height="20.5"/>
|
||||
>>>>>>> Stashed changes
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="133" width="441" height="57.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8152.3" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10112" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8124.4"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10083"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
@@ -297,7 +297,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Setting up multiple persistent store configurations" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hbn-cf-Y7m">
|
||||
<rect key="frame" x="15" y="30" width="264" height="13.5"/>
|
||||
<rect key="frame" x="15" y="30" width="263.5" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -324,7 +324,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observing list changes and single object changes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ou9-TZ-8bf">
|
||||
<rect key="frame" x="15" y="30" width="261.5" height="13.5"/>
|
||||
<rect key="frame" x="15" y="30" width="260.5" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -351,7 +351,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Making changes with transactions" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="uP1-Jc-o9v">
|
||||
<rect key="frame" x="15" y="30" width="179.5" height="13.5"/>
|
||||
<rect key="frame" x="15" y="30" width="179" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -378,7 +378,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws">
|
||||
<rect key="frame" x="15" y="30" width="169" height="13.5"/>
|
||||
<rect key="frame" x="15" y="30" width="168.5" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -405,7 +405,7 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j">
|
||||
<rect key="frame" x="15" y="30" width="159.5" height="13.5"/>
|
||||
<rect key="frame" x="15" y="30" width="159" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14D136" minimumToolsVersion="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10169.1" systemVersion="15D21" minimumToolsVersion="Automatic">
|
||||
<entity name="Palette" representedClassName="CoreStoreDemo.Palette">
|
||||
<attribute name="brightness" optional="YES" attributeType="Float" defaultValueString="0.0" syncable="YES"/>
|
||||
<attribute name="colorName" optional="YES" transient="YES" attributeType="String" syncable="YES"/>
|
||||
|
||||
@@ -15,10 +15,12 @@ private struct Static {
|
||||
static let timeZonesStack: DataStack = {
|
||||
|
||||
let dataStack = DataStack()
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "TimeZoneDemo.sqlite",
|
||||
configuration: "FetchingAndQueryingDemo",
|
||||
resetStoreOnModelMismatch: true
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "TimeZoneDemo.sqlite",
|
||||
configuration: "FetchingAndQueryingDemo",
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
|
||||
dataStack.beginSynchronous { (transaction) -> Void in
|
||||
@@ -97,6 +99,7 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
switch self.segmentedControl?.selectedSegmentIndex {
|
||||
|
||||
case Section.Fetching.rawValue?:
|
||||
|
||||
@@ -21,13 +21,33 @@
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@3x-1.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "car",
|
||||
"filename" : "Icon-60@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
21
CoreStoreDemo/CoreStoreDemo/Images.xcassets/CoreStoreIcon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "CoreStoreIcon@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
CoreStoreDemo/CoreStoreDemo/Images.xcassets/CoreStoreIcon.imageset/CoreStoreIcon@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
@@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>CoreStore</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
|
||||
@@ -49,10 +49,12 @@ private struct Static {
|
||||
|
||||
static let palettes: ListMonitor<Palette> = {
|
||||
|
||||
try! CoreStore.addSQLiteStoreAndWait(
|
||||
fileName: "ColorsDemo.sqlite",
|
||||
configuration: "ObservingDemo",
|
||||
resetStoreOnModelMismatch: true
|
||||
try! CoreStore.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "ColorsDemo.sqlite",
|
||||
configuration: "ObservingDemo",
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
|
||||
return CoreStore.monitorSectionedList(
|
||||
|
||||
@@ -30,7 +30,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
try! self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite")
|
||||
try! self.dataStack.addStorageAndWait(SQLiteStore(fileName: "emptyStore.sqlite"))
|
||||
CoreStore.logger = self
|
||||
}
|
||||
|
||||
@@ -66,7 +66,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
}
|
||||
}
|
||||
|
||||
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
func log(error error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
@@ -74,24 +74,20 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
}
|
||||
}
|
||||
|
||||
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
if condition() {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let messageString = message()
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(messageString)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
@noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
Swift.fatalError("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)")
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@@ -109,11 +105,12 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
}
|
||||
|
||||
case 1?:
|
||||
do {
|
||||
|
||||
try self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite", configuration: "invalidStore")
|
||||
}
|
||||
catch _ { }
|
||||
_ = try? dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "emptyStore.sqlite",
|
||||
configuration: "invalidStore"
|
||||
)
|
||||
)
|
||||
|
||||
case 2?:
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
@@ -50,22 +50,21 @@ class MigrationsDemoViewController: UIViewController {
|
||||
(dataStack: DataStack) -> ModelMetadata in
|
||||
|
||||
let models = self.models
|
||||
do {
|
||||
let migrations = try! dataStack.requiredMigrationsForStorage(
|
||||
SQLiteStore(fileName: "MigrationDemo.sqlite")
|
||||
)
|
||||
|
||||
guard let storeVersion = migrations.first?.sourceVersion else {
|
||||
|
||||
return models.first!
|
||||
}
|
||||
for model in models {
|
||||
|
||||
let migrations = try dataStack.requiredMigrationsForSQLiteStore(
|
||||
fileName: "MigrationDemo.sqlite"
|
||||
)
|
||||
|
||||
let storeVersion = migrations.first?.sourceVersion ?? dataStack.modelVersion
|
||||
for model in models {
|
||||
if model.version == storeVersion {
|
||||
|
||||
if model.version == storeVersion {
|
||||
|
||||
return model
|
||||
}
|
||||
return model
|
||||
}
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
return models.first!
|
||||
}
|
||||
@@ -158,8 +157,8 @@ class MigrationsDemoViewController: UIViewController {
|
||||
)
|
||||
|
||||
self.setEnabled(false)
|
||||
let progress = try! dataStack.addSQLiteStore(
|
||||
fileName: "MigrationDemo.sqlite",
|
||||
let progress = dataStack.addStorage(
|
||||
SQLiteStore(fileName: "MigrationDemo.sqlite"),
|
||||
completion: { [weak self] (result) -> Void in
|
||||
|
||||
guard let `self` = self else {
|
||||
@@ -266,8 +265,8 @@ class MigrationsDemoViewController: UIViewController {
|
||||
else {
|
||||
|
||||
self.segmentedControl?.selectedSegmentIndex = UISegmentedControlNoSegment
|
||||
self._dataStack = nil
|
||||
self._listMonitor = nil
|
||||
self._dataStack = nil
|
||||
}
|
||||
|
||||
self.updateDisplay(reloadData: true, scrollToSelection: scrollToSelection, animated: false)
|
||||
@@ -365,16 +364,16 @@ extension MigrationsDemoViewController: UITableViewDataSource, UITableViewDelega
|
||||
cell.dnaLabel?.text = "DNA: \(dna)"
|
||||
cell.mutateButtonHandler = { [weak self] _ -> Void in
|
||||
|
||||
guard let strongSelf = self,
|
||||
let dataStack = strongSelf.dataStack,
|
||||
let organism = strongSelf.listMonitor?[indexPath] else {
|
||||
guard let `self` = self,
|
||||
let dataStack = self.dataStack,
|
||||
let organism = self.listMonitor?[indexPath] else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.setSelectedIndexPath(indexPath, scrollToSelection: false)
|
||||
strongSelf.setEnabled(false)
|
||||
dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
|
||||
self.setEnabled(false)
|
||||
dataStack.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
let organism = transaction.edit(organism) as! OrganismProtocol
|
||||
organism.mutate()
|
||||
|
||||
@@ -18,15 +18,19 @@ private struct Static {
|
||||
static let facebookStack: DataStack = {
|
||||
|
||||
let dataStack = DataStack(modelName: "StackSetupDemo")
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_FB_Male.sqlite",
|
||||
configuration: maleConfiguration,
|
||||
resetStoreOnModelMismatch: true
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "AccountsDemo_FB_Male.sqlite",
|
||||
configuration: maleConfiguration,
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_FB_Female.sqlite",
|
||||
configuration: femaleConfiguration,
|
||||
resetStoreOnModelMismatch: true
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "AccountsDemo_FB_Female.sqlite",
|
||||
configuration: femaleConfiguration,
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
|
||||
dataStack.beginSynchronous { (transaction) -> Void in
|
||||
@@ -52,15 +56,19 @@ private struct Static {
|
||||
static let twitterStack: DataStack = {
|
||||
|
||||
let dataStack = DataStack(modelName: "StackSetupDemo")
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_TW_Male.sqlite",
|
||||
configuration: maleConfiguration,
|
||||
resetStoreOnModelMismatch: true
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "AccountsDemo_TW_Male.sqlite",
|
||||
configuration: maleConfiguration,
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_TW_Female.sqlite",
|
||||
configuration: femaleConfiguration,
|
||||
resetStoreOnModelMismatch: true
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "AccountsDemo_TW_Female.sqlite",
|
||||
configuration: femaleConfiguration,
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
|
||||
dataStack.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
@@ -18,10 +18,12 @@ private struct Static {
|
||||
|
||||
static let placeController: ObjectMonitor<Place> = {
|
||||
|
||||
try! CoreStore.addSQLiteStoreAndWait(
|
||||
fileName: "PlaceDemo.sqlite",
|
||||
configuration: "TransactionsDemo",
|
||||
resetStoreOnModelMismatch: true
|
||||
try! CoreStore.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "PlaceDemo.sqlite",
|
||||
configuration: "TransactionsDemo",
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
|
||||
var place = CoreStore.fetchOne(From(Place))
|
||||
@@ -60,7 +62,10 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
let longPressGesture = UILongPressGestureRecognizer(target: self, action: #selector(self.longPressGestureRecognized(_:)))
|
||||
let longPressGesture = UILongPressGestureRecognizer(
|
||||
target: self,
|
||||
action: #selector(self.longPressGestureRecognized(_:))
|
||||
)
|
||||
self.mapView?.addGestureRecognizer(longPressGesture)
|
||||
|
||||
Static.placeController.addObserver(self)
|
||||
|
||||
BIN
CoreStoreDemo/appIcons.sketch
Normal file
201
CoreStoreTests/BaseTests/BaseTestCase.swift
Normal file
@@ -0,0 +1,201 @@
|
||||
//
|
||||
// BaseTestCase.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - BaseTestCase
|
||||
|
||||
class BaseTestCase: XCTestCase {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc
|
||||
func prepareStack<T>(configurations configurations: [String?] = [nil], @noescape _ closure: (dataStack: DataStack) -> T) -> T {
|
||||
|
||||
let stack = DataStack(
|
||||
modelName: "Model",
|
||||
bundle: NSBundle(forClass: self.dynamicType)
|
||||
)
|
||||
do {
|
||||
|
||||
try configurations.forEach {
|
||||
|
||||
try stack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileURL: SQLiteStore.defaultRootDirectory
|
||||
.URLByAppendingPathComponent(NSUUID().UUIDString)
|
||||
.URLByAppendingPathComponent("\(self.dynamicType)_\(($0 ?? "-null-")).sqlite"),
|
||||
configuration: $0,
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.coreStoreDumpString)
|
||||
}
|
||||
return closure(dataStack: stack)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func expectLogger<T>(expectations: [TestLogger.Expectation], @noescape closure: () -> T) -> T {
|
||||
|
||||
CoreStore.logger = TestLogger(self.prepareLoggerExpectations(expectations))
|
||||
defer {
|
||||
|
||||
self.checkExpectationsImmediately()
|
||||
CoreStore.logger = TestLogger([:])
|
||||
}
|
||||
return closure()
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func expectLogger(expectations: [TestLogger.Expectation: XCTestExpectation]) {
|
||||
|
||||
CoreStore.logger = TestLogger(expectations)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func prepareLoggerExpectations(expectations: [TestLogger.Expectation]) -> [TestLogger.Expectation: XCTestExpectation] {
|
||||
|
||||
var testExpectations: [TestLogger.Expectation: XCTestExpectation] = [:]
|
||||
for expectation in expectations {
|
||||
|
||||
testExpectations[expectation] = self.expectationWithDescription("Logger Expectation: \(expectation)")
|
||||
}
|
||||
return testExpectations
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func checkExpectationsImmediately() {
|
||||
|
||||
self.waitForExpectationsWithTimeout(0, handler: nil)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func waitAndCheckExpectations() {
|
||||
|
||||
self.waitForExpectationsWithTimeout(10, handler: nil)
|
||||
}
|
||||
|
||||
// MARK: XCTestCase
|
||||
|
||||
override func setUp() {
|
||||
|
||||
super.setUp()
|
||||
self.deleteStores()
|
||||
CoreStore.logger = TestLogger([:])
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
CoreStore.logger = DefaultLogger()
|
||||
self.deleteStores()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func deleteStores() {
|
||||
|
||||
_ = try? NSFileManager.defaultManager().removeItemAtURL(SQLiteStore.defaultRootDirectory)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - TestLogger
|
||||
|
||||
class TestLogger: CoreStoreLogger {
|
||||
|
||||
enum Expectation {
|
||||
|
||||
case LogWarning
|
||||
case LogFatal
|
||||
case LogError
|
||||
case AssertionFailure
|
||||
case FatalError
|
||||
}
|
||||
|
||||
init(_ expectations: [Expectation: XCTestExpectation]) {
|
||||
|
||||
self.expectations = expectations
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreLogger
|
||||
|
||||
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
switch level {
|
||||
|
||||
case .Warning: self.fulfill(.LogWarning)
|
||||
case .Fatal: self.fulfill(.LogFatal)
|
||||
default: break
|
||||
}
|
||||
}
|
||||
|
||||
func log(error error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
self.fulfill(.LogError)
|
||||
}
|
||||
|
||||
func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
if condition() {
|
||||
|
||||
return
|
||||
}
|
||||
self.fulfill(.AssertionFailure)
|
||||
}
|
||||
|
||||
func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
self.fulfill(.FatalError)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var expectations: [Expectation: XCTestExpectation]
|
||||
|
||||
private func fulfill(expectation: Expectation) {
|
||||
|
||||
if let instance = self.expectations[expectation] {
|
||||
|
||||
instance.fulfill()
|
||||
self.expectations[expectation] = nil
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail("Unexpected Logger Action: \(expectation)")
|
||||
}
|
||||
}
|
||||
}
|
||||
76
CoreStoreTests/BaseTests/BaseTestDataTestCase.swift
Normal file
@@ -0,0 +1,76 @@
|
||||
//
|
||||
// BaseTestDataTestCase.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Created by John Rommel Estropia on 2016/06/11.
|
||||
// Copyright © 2016 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - BaseTestDataTestCase
|
||||
|
||||
class BaseTestDataTestCase: BaseTestCase {
|
||||
|
||||
@nonobjc
|
||||
let dateFormatter: NSDateFormatter = {
|
||||
|
||||
let formatter = NSDateFormatter()
|
||||
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
|
||||
formatter.timeZone = NSTimeZone(name: "UTC")
|
||||
formatter.calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)
|
||||
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
|
||||
return formatter
|
||||
}()
|
||||
|
||||
@nonobjc
|
||||
func prepareTestDataForStack(stack: DataStack, configurations: [String?] = [nil]) {
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
for (configurationIndex, configuration) in configurations.enumerate() {
|
||||
|
||||
let configurationOrdinal = configurationIndex + 1
|
||||
if configuration == nil || configuration == "Config1" {
|
||||
|
||||
for idIndex in 1 ... 5 {
|
||||
|
||||
let object = transaction.create(Into<TestEntity1>(configuration))
|
||||
object.testEntityID = NSNumber(integer: (configurationOrdinal * 100) + idIndex)
|
||||
|
||||
object.testNumber = idIndex
|
||||
object.testDate = self.dateFormatter.dateFromString("2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
|
||||
object.testBoolean = (idIndex % 2) == 1
|
||||
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
|
||||
|
||||
let string = "\(configuration ?? "nil"):TestEntity1:\(idIndex)"
|
||||
object.testString = string
|
||||
object.testData = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
|
||||
}
|
||||
}
|
||||
if configuration == nil || configuration == "Config2" {
|
||||
|
||||
for idIndex in 1 ... 5 {
|
||||
|
||||
let object = transaction.create(Into<TestEntity2>(configuration))
|
||||
object.testEntityID = NSNumber(integer: (configurationOrdinal * 200) + idIndex)
|
||||
|
||||
object.testNumber = idIndex
|
||||
object.testDate = self.dateFormatter.dateFromString("2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
|
||||
object.testBoolean = (idIndex % 2) == 1
|
||||
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
|
||||
|
||||
let string = "\(configuration ?? "nil"):TestEntity2:\(idIndex)"
|
||||
object.testString = string
|
||||
object.testData = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
|
||||
}
|
||||
}
|
||||
}
|
||||
transaction.commitAndWait()
|
||||
}
|
||||
}
|
||||
}
|
||||
30
CoreStoreTests/BridgingTests.h
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// BridgingTests.h
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 <XCTest/XCTest.h>
|
||||
|
||||
@interface BridgingTests : XCTestCase
|
||||
|
||||
@end
|
||||
200
CoreStoreTests/BridgingTests.m
Normal file
@@ -0,0 +1,200 @@
|
||||
//
|
||||
// BridgingTests.m
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 "BridgingTests.h"
|
||||
#import <CoreStore/CoreStore.h>
|
||||
#import <CoreStore/CoreStore-Swift.h>
|
||||
#import "CoreStoreTests-Swift.h"
|
||||
|
||||
@import CoreData;
|
||||
|
||||
// MARK: - BridgingTests
|
||||
|
||||
@implementation BridgingTests
|
||||
|
||||
- (void)test_ThatFlags_HaveCorrectValues {
|
||||
|
||||
XCTAssertEqual(CSLocalStorageOptionsNone, 0);
|
||||
XCTAssertEqual(CSLocalStorageOptionsRecreateStoreOnModelMismatch, 1);
|
||||
XCTAssertEqual(CSLocalStorageOptionsPreventProgressiveMigration, 2);
|
||||
XCTAssertEqual(CSLocalStorageOptionsAllowSynchronousLightweightMigration, 4);
|
||||
}
|
||||
|
||||
- (void)test_ThatKeyPaths_AreCorrect {
|
||||
|
||||
XCTAssertEqualObjects(CSKeyPath(TestEntity1, testNumber), @"testNumber");
|
||||
XCTAssertEqualObjects(CSKeyPath(TestEntity1, testString), @"testString");
|
||||
XCTAssertEqualObjects(CSKeyPathOperator(count, TestEntity1, testString), @"@count.testString");
|
||||
XCTAssertEqualObjects(CSKeyPathOperator(max, TestEntity1, testNumber), @"@max.testNumber");
|
||||
}
|
||||
|
||||
- (void)test_ThatFromClauses_BridgeCorrectly {
|
||||
|
||||
{
|
||||
CSFrom *from = CSFromClass([TestEntity1 class]);
|
||||
XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]);
|
||||
XCTAssertNil(from.configurations);
|
||||
}
|
||||
{
|
||||
CSFrom *from = CSFromClass([TestEntity1 class], [NSNull null]);
|
||||
XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]);
|
||||
|
||||
NSArray *configurations = @[[NSNull null]];
|
||||
XCTAssertEqualObjects(from.configurations, configurations);
|
||||
}
|
||||
{
|
||||
CSFrom *from = CSFromClass([TestEntity1 class], @"Config1");
|
||||
XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]);
|
||||
|
||||
NSArray *configurations = @[@"Config1"];
|
||||
XCTAssertEqualObjects(from.configurations, configurations);
|
||||
}
|
||||
{
|
||||
CSFrom *from = CSFromClass([TestEntity1 class], @[[NSNull null], @"Config2"]);
|
||||
XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]);
|
||||
|
||||
NSArray *configurations = @[[NSNull null], @"Config2"];
|
||||
XCTAssertEqualObjects(from.configurations, configurations);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_ThatWhereClauses_BridgeCorrectly {
|
||||
|
||||
{
|
||||
CSWhere *where = CSWhereFormat(@"%K == %@", @"key", @"value");
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"key", @"value"];
|
||||
XCTAssertEqualObjects(where.predicate, predicate);
|
||||
}
|
||||
{
|
||||
CSWhere *where = CSWhereValue(YES);
|
||||
NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
|
||||
XCTAssertEqualObjects(where.predicate, predicate);
|
||||
}
|
||||
{
|
||||
CSWhere *where = CSWherePredicate([NSPredicate predicateWithFormat:@"%K == %@", @"key", @"value"]);
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"key", @"value"];
|
||||
XCTAssertEqualObjects(where.predicate, predicate);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_ThatOrderByClauses_BridgeCorrectly {
|
||||
|
||||
{
|
||||
CSOrderBy *orderBy = CSOrderByKey(CSSortAscending(@"key"));
|
||||
XCTAssertEqualObjects(orderBy.sortDescriptors, @[[NSSortDescriptor sortDescriptorWithKey:@"key" ascending:YES]]);
|
||||
}
|
||||
{
|
||||
CSOrderBy *orderBy = CSOrderByKey(CSSortDescending(@"key"));
|
||||
XCTAssertEqualObjects(orderBy.sortDescriptors, @[[NSSortDescriptor sortDescriptorWithKey:@"key" ascending:NO]]);
|
||||
}
|
||||
{
|
||||
CSOrderBy *orderBy = CSOrderByKeys(CSSortAscending(@"key1"), CSSortDescending(@"key2"), nil);
|
||||
NSArray *sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"key1" ascending:YES],
|
||||
[NSSortDescriptor sortDescriptorWithKey:@"key2" ascending:NO]];
|
||||
XCTAssertEqualObjects(orderBy.sortDescriptors, sortDescriptors);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_ThatGroupByClauses_BridgeCorrectly {
|
||||
|
||||
{
|
||||
CSGroupBy *groupBy = CSGroupByKeyPath(@"key");
|
||||
XCTAssertEqualObjects(groupBy.keyPaths, @[@"key"]);
|
||||
}
|
||||
{
|
||||
CSGroupBy *groupBy = CSGroupByKeyPaths(@[@"key1", @"key2"]);
|
||||
|
||||
NSArray *keyPaths = @[@"key1", @"key2"];
|
||||
XCTAssertEqualObjects(groupBy.keyPaths, keyPaths);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_ThatTweakClauses_BridgeCorrectly {
|
||||
|
||||
CSTweak *tweak = CSTweakRequest(^(NSFetchRequest * _Nonnull fetchRequest) {
|
||||
|
||||
fetchRequest.fetchLimit = 100;
|
||||
});
|
||||
NSFetchRequest *request = [NSFetchRequest new];
|
||||
tweak.block(request);
|
||||
XCTAssertEqual(request.fetchLimit, 100);
|
||||
}
|
||||
|
||||
- (void)test_ThatIntoClauses_BridgeCorrectly {
|
||||
|
||||
{
|
||||
CSInto *into = CSIntoClass([TestEntity1 class]);
|
||||
XCTAssertEqualObjects(into.entityClass, [TestEntity1 class]);
|
||||
}
|
||||
{
|
||||
CSInto *into = CSIntoClass([TestEntity1 class], [NSNull null]);
|
||||
XCTAssertEqualObjects(into.entityClass, [TestEntity1 class]);
|
||||
XCTAssertNil(into.configuration);
|
||||
}
|
||||
{
|
||||
CSInto *into = CSIntoClass([TestEntity1 class], @"Config1");
|
||||
XCTAssertEqualObjects(into.entityClass, [TestEntity1 class]);
|
||||
XCTAssertEqualObjects(into.configuration, @"Config1");
|
||||
}
|
||||
}
|
||||
|
||||
- (void)test_ThatDataStacks_BridgeCorrectly {
|
||||
|
||||
CSDataStack *dataStack = [[CSDataStack alloc]
|
||||
initWithModelName:@"Model"
|
||||
bundle:[NSBundle bundleForClass:[self class]]
|
||||
versionChain:nil];
|
||||
XCTAssertNotNil(dataStack);
|
||||
|
||||
[CSCoreStore setDefaultStack:dataStack];
|
||||
XCTAssertTrue([dataStack isEqual:[CSCoreStore defaultStack]]);
|
||||
}
|
||||
|
||||
- (void)test_ThatStorages_BridgeCorrectly {
|
||||
|
||||
NSError *memoryError;
|
||||
CSInMemoryStore *memoryStorage = [CSCoreStore
|
||||
addInMemoryStorageAndWait:[CSInMemoryStore new]
|
||||
error:&memoryError];
|
||||
XCTAssertNotNil(memoryStorage);
|
||||
XCTAssertEqualObjects([[memoryStorage class] storeType], [CSInMemoryStore storeType]);
|
||||
XCTAssertEqualObjects([[memoryStorage class] storeType], NSInMemoryStoreType);
|
||||
XCTAssertNil(memoryStorage.configuration);
|
||||
XCTAssertNil(memoryStorage.storeOptions);
|
||||
XCTAssertNil(memoryError);
|
||||
|
||||
NSError *sqliteError;
|
||||
CSSQLiteStore *sqliteStorage = [CSCoreStore
|
||||
addSQLiteStorageAndWait:[CSSQLiteStore new]
|
||||
error:&sqliteError];
|
||||
XCTAssertNotNil(sqliteStorage);
|
||||
XCTAssertEqualObjects([[sqliteStorage class] storeType], [CSSQLiteStore storeType]);
|
||||
XCTAssertEqualObjects([[sqliteStorage class] storeType], NSSQLiteStoreType);
|
||||
XCTAssertNil(sqliteStorage.configuration);
|
||||
XCTAssertEqualObjects(sqliteStorage.storeOptions, @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" } });
|
||||
XCTAssertNil(sqliteError);
|
||||
}
|
||||
|
||||
@end
|
||||
5
CoreStoreTests/CoreStoreTests-Bridging-Header.h
Normal file
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "BridgingTests.h"
|
||||
@@ -1,389 +0,0 @@
|
||||
//
|
||||
// CoreStoreTests.swift
|
||||
// CoreStoreTests
|
||||
//
|
||||
// Copyright © 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
class CoreStoreTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
|
||||
super.setUp()
|
||||
self.deleteStores()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
self.deleteStores()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testMigrationChains() {
|
||||
|
||||
let emptyChain: MigrationChain = nil
|
||||
XCTAssertTrue(emptyChain.valid, "emptyChain.valid")
|
||||
XCTAssertTrue(emptyChain.empty, "emptyChain.empty")
|
||||
|
||||
let normalChain: MigrationChain = "version1"
|
||||
XCTAssertTrue(normalChain.valid, "normalChain.valid")
|
||||
XCTAssertTrue(normalChain.empty, "normalChain.empty")
|
||||
|
||||
let linearChain: MigrationChain = ["version1", "version2", "version3", "version4"]
|
||||
XCTAssertTrue(linearChain.valid, "linearChain.valid")
|
||||
XCTAssertFalse(linearChain.empty, "linearChain.empty")
|
||||
|
||||
let treeChain: MigrationChain = [
|
||||
"version1": "version4",
|
||||
"version2": "version3",
|
||||
"version3": "version4"
|
||||
]
|
||||
XCTAssertTrue(treeChain.valid, "treeChain.valid")
|
||||
XCTAssertFalse(treeChain.empty, "treeChain.empty")
|
||||
|
||||
// The cases below will trigger assertion failures internally
|
||||
|
||||
// let linearLoopChain: MigrationChain = ["version1", "version2", "version1", "version3", "version4"]
|
||||
// XCTAssertFalse(linearLoopChain.valid, "linearLoopChain.valid")
|
||||
//
|
||||
// let treeAmbiguousChain: MigrationChain = [
|
||||
// "version1": "version4",
|
||||
// "version2": "version3",
|
||||
// "version1": "version2",
|
||||
// "version3": "version4"
|
||||
// ]
|
||||
// XCTAssertFalse(treeAmbiguousChain.valid, "treeAmbiguousChain.valid")
|
||||
}
|
||||
|
||||
func testExample() {
|
||||
|
||||
let stack = DataStack(modelName: "Model", bundle: NSBundle(forClass: self.dynamicType))
|
||||
CoreStore.defaultStack = stack
|
||||
XCTAssert(CoreStore.defaultStack === stack, "CoreStore.defaultStack === stack")
|
||||
|
||||
do {
|
||||
|
||||
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore1.sqlite", configuration: "Config1", resetStoreOnModelMismatch: true)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore2.sqlite", configuration: "Config2", resetStoreOnModelMismatch: true)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
|
||||
let unsafeTransaction = CoreStore.beginUnsafe()
|
||||
|
||||
let createExpectation = self.expectationWithDescription("Entity creation")
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let obj1 = transaction.create(Into(TestEntity1))
|
||||
obj1.testEntityID = 1
|
||||
obj1.testString = "lololol"
|
||||
obj1.testNumber = 42
|
||||
obj1.testDate = NSDate()
|
||||
|
||||
let count = transaction.queryValue(
|
||||
From<TestEntity1>(),
|
||||
Select<Int>(.Count("testNumber"))
|
||||
)
|
||||
XCTAssertTrue(count == 0, "count == 0 (actual: \(count))") // counts only objects in store
|
||||
|
||||
let obj2 = transaction.create(Into<TestEntity2>())
|
||||
obj2.testEntityID = 2
|
||||
obj2.testString = "hahaha"
|
||||
obj2.testNumber = 100
|
||||
obj2.testDate = NSDate()
|
||||
|
||||
let obj3 = transaction.create(Into<TestEntity2>("Config2"))
|
||||
obj3.testEntityID = 3
|
||||
obj3.testString = "hahaha"
|
||||
obj3.testNumber = 90
|
||||
obj3.testDate = NSDate()
|
||||
|
||||
let obj4 = transaction.create(Into(TestEntity2.self, "Config2"))
|
||||
obj4.testEntityID = 5
|
||||
obj4.testString = "hohoho"
|
||||
obj4.testNumber = 80
|
||||
obj4.testDate = NSDate()
|
||||
|
||||
|
||||
transaction.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let obj4 = transaction.create(Into<TestEntity2>())
|
||||
obj4.testEntityID = 4
|
||||
obj4.testString = "hehehehe"
|
||||
obj4.testNumber = 80
|
||||
obj4.testDate = NSDate()
|
||||
|
||||
let objs4test = transaction.fetchOne(
|
||||
From<TestEntity2>("Config2"),
|
||||
Where("testEntityID", isEqualTo: 4),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
|
||||
fetchRequest.includesPendingChanges = true
|
||||
}
|
||||
)
|
||||
XCTAssertNotNil(objs4test, "objs4test != nil")
|
||||
|
||||
let objs5test = transaction.fetchOne(
|
||||
From(TestEntity2),
|
||||
Where("testEntityID", isEqualTo: 4),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
|
||||
fetchRequest.includesPendingChanges = false
|
||||
}
|
||||
)
|
||||
XCTAssertNil(objs5test, "objs5test == nil")
|
||||
|
||||
// Dont commit1
|
||||
}
|
||||
|
||||
transaction.commit { (result) -> Void in
|
||||
|
||||
let objs4test = CoreStore.fetchOne(
|
||||
From(TestEntity2),
|
||||
Where("testEntityID", isEqualTo: 4),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
|
||||
fetchRequest.includesPendingChanges = false
|
||||
}
|
||||
)
|
||||
XCTAssertNil(objs4test, "objs4test == nil")
|
||||
|
||||
let objs5test = unsafeTransaction.fetchCount(From(TestEntity2))
|
||||
XCTAssertTrue(objs5test == 3, "objs5test == 3")
|
||||
|
||||
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges, "hasChanges == true")
|
||||
createExpectation.fulfill()
|
||||
|
||||
case .Failure(let error):
|
||||
XCTFail(error.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let queryExpectation = self.expectationWithDescription("Query creation")
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let obj1 = transaction.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(obj1, "obj1 != nil")
|
||||
|
||||
var orderBy = OrderBy(.Ascending("testEntityID"))
|
||||
orderBy += OrderBy(.Descending("testString"))
|
||||
let objs2 = transaction.fetchAll(
|
||||
From(TestEntity2),
|
||||
Where("testNumber", isEqualTo: 100) || Where("%K == %@", "testNumber", 90),
|
||||
orderBy,
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
|
||||
fetchRequest.includesPendingChanges = true
|
||||
}
|
||||
)
|
||||
XCTAssertNotNil(objs2, "objs2 != nil")
|
||||
XCTAssertTrue(objs2?.count == 2, "objs2?.count == 2")
|
||||
|
||||
transaction.commit { (result) -> Void in
|
||||
|
||||
let counts = CoreStore.queryAttributes(
|
||||
From(TestEntity2),
|
||||
Select("testString", .Count("testString", As: "count")),
|
||||
GroupBy("testString")
|
||||
)
|
||||
print(counts)
|
||||
|
||||
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertFalse(hasChanges, "hasChanges == false")
|
||||
queryExpectation.fulfill()
|
||||
|
||||
case .Failure(let error):
|
||||
XCTFail(error.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.waitForExpectationsWithTimeout(100, handler: nil)
|
||||
|
||||
let max1 = CoreStore.queryValue(
|
||||
From(TestEntity2),
|
||||
Select<Int>(.Maximum("testNumber"))
|
||||
)
|
||||
XCTAssertTrue(max1 == 100, "max == 100 (actual: \(max1))")
|
||||
|
||||
let max2 = CoreStore.queryValue(
|
||||
From(TestEntity2),
|
||||
Select<NSNumber>(.Maximum("testNumber")),
|
||||
Where("%K > %@", "testEntityID", 2)
|
||||
)
|
||||
XCTAssertTrue(max2 == 90, "max == 90 (actual: \(max2))")
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let numberOfDeletedObjects1 = transaction.deleteAll(From(TestEntity1))
|
||||
XCTAssertTrue(numberOfDeletedObjects1 == 1, "numberOfDeletedObjects1 == 1 (actual: \(numberOfDeletedObjects1))")
|
||||
|
||||
let numberOfDeletedObjects2 = transaction.deleteAll(
|
||||
From(TestEntity2),
|
||||
Where("%K > %@", "testEntityID", 2)
|
||||
)
|
||||
XCTAssertTrue(numberOfDeletedObjects2 == 2, "numberOfDeletedObjects2 == 2 (actual: \(numberOfDeletedObjects2))")
|
||||
|
||||
transaction.commitAndWait()
|
||||
}
|
||||
|
||||
CoreStore.beginSynchronous({ (transaction) -> Void in
|
||||
|
||||
if let obj = CoreStore.fetchOne(From(TestEntity2)) {
|
||||
|
||||
let oldID = obj.testEntityID
|
||||
obj.testEntityID = 0
|
||||
obj.testEntityID = oldID
|
||||
}
|
||||
|
||||
transaction.commitAndWait()
|
||||
})
|
||||
|
||||
let objs1 = CoreStore.fetchAll(From(TestEntity1))
|
||||
XCTAssertNotNil(objs1, "objs1 != nil")
|
||||
XCTAssertTrue(objs1?.count == 0, "objs1?.count == 0")
|
||||
|
||||
let objs2 = CoreStore.fetchAll(From(TestEntity2))
|
||||
XCTAssertNotNil(objs2, "objs2 != nil")
|
||||
XCTAssertTrue(objs2?.count == 1, "objs2?.count == 1")
|
||||
|
||||
let unsafeExpectation = self.expectationWithDescription("Query creation")
|
||||
|
||||
let obj5 = unsafeTransaction.create(Into<TestEntity1>("Config1"))
|
||||
obj5.testEntityID = 5
|
||||
obj5.testString = "hihihi"
|
||||
obj5.testNumber = 70
|
||||
obj5.testDate = NSDate()
|
||||
XCTAssert(unsafeTransaction === obj5.unsafeDataTransaction, "unsafeTransaction === obj5.unsafeDataTransaction")
|
||||
|
||||
unsafeTransaction.commit { (result) -> Void in
|
||||
|
||||
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges, "hasChanges == true")
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let obj5Copy1 = transaction.edit(obj5)
|
||||
XCTAssertTrue(obj5.objectID == obj5Copy1?.objectID, "obj5.objectID == obj5Copy1?.objectID")
|
||||
XCTAssertFalse(obj5 == obj5Copy1, "obj5 == obj5Copy1")
|
||||
XCTAssertNil(obj5Copy1?.unsafeDataTransaction)
|
||||
|
||||
let obj5Copy2 = transaction.edit(Into(TestEntity1), obj5.objectID)
|
||||
XCTAssertTrue(obj5.objectID == obj5Copy2?.objectID, "obj5.objectID == obj5Copy2?.objectID")
|
||||
XCTAssertFalse(obj5 == obj5Copy2, "obj5 == obj5Copy2")
|
||||
}
|
||||
|
||||
let count: Int? = CoreStore.queryValue(
|
||||
From(TestEntity1),
|
||||
Select(.Count("testNumber"))
|
||||
)
|
||||
XCTAssertTrue(count == 1, "count == 1 (actual: \(count))")
|
||||
|
||||
let obj6 = unsafeTransaction.create(Into<TestEntity1>())
|
||||
obj6.testEntityID = 6
|
||||
obj6.testString = "huehuehue"
|
||||
obj6.testNumber = 130
|
||||
obj6.testDate = NSDate()
|
||||
XCTAssert(unsafeTransaction === obj6.unsafeDataTransaction, "unsafeTransaction === obj6.unsafeDataTransaction")
|
||||
|
||||
unsafeTransaction.commit { (result) -> Void in
|
||||
|
||||
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges, "hasChanges == true")
|
||||
|
||||
let count = CoreStore.queryValue(
|
||||
From(TestEntity1),
|
||||
Select<Int>(.Count("testNumber"))
|
||||
)
|
||||
XCTAssertTrue(count == 2, "count == 2 (actual: \(count))")
|
||||
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let obj6 = transaction.edit(obj6)
|
||||
let obj5 = transaction.edit(obj5)
|
||||
transaction.delete(obj5, obj6)
|
||||
|
||||
transaction.commitAndWait()
|
||||
}
|
||||
|
||||
let count2 = CoreStore.queryValue(
|
||||
From(TestEntity1),
|
||||
Select<Int>(.Count("testNumber"))
|
||||
)
|
||||
XCTAssertTrue(count2 == 0, "count == 0 (actual: \(count2))")
|
||||
|
||||
unsafeExpectation.fulfill()
|
||||
|
||||
case .Failure(let error):
|
||||
XCTFail(error.description)
|
||||
}
|
||||
}
|
||||
|
||||
case .Failure(let error):
|
||||
XCTFail(error.description)
|
||||
}
|
||||
}
|
||||
|
||||
self.waitForExpectationsWithTimeout(100, handler: nil)
|
||||
}
|
||||
|
||||
private func deleteStores() {
|
||||
|
||||
do {
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
try fileManager.removeItemAtURL(
|
||||
fileManager.URLsForDirectory(deviceDirectorySearchPath, inDomains: .UserDomainMask).first!
|
||||
)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
}
|
||||
169
CoreStoreTests/ErrorTests.swift
Normal file
@@ -0,0 +1,169 @@
|
||||
//
|
||||
// ErrorTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - ErrorTests
|
||||
|
||||
final class ErrorTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatUnknownErrors_BridgeCorrectly() {
|
||||
|
||||
let error = CoreStoreError.Unknown
|
||||
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.UnknownError.rawValue)
|
||||
|
||||
let userInfo: NSDictionary = [:]
|
||||
|
||||
let objcError = error.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError.bridgeToSwift)
|
||||
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError.code, CoreStoreErrorCode.UnknownError.rawValue)
|
||||
XCTAssertEqual(objcError.userInfo, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.UnknownError.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo, userInfo)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDifferentStorageExistsAtURLErrors_BridgeCorrectly() {
|
||||
|
||||
let dummyURL = NSURL(string: "file:///test1/test2.sqlite")!
|
||||
|
||||
let error = CoreStoreError.DifferentStorageExistsAtURL(existingPersistentStoreURL: dummyURL)
|
||||
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue)
|
||||
|
||||
let userInfo: NSDictionary = [
|
||||
"existingPersistentStoreURL": dummyURL
|
||||
]
|
||||
let objcError = error.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError.bridgeToSwift)
|
||||
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError.code, CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue)
|
||||
XCTAssertEqual(objcError.userInfo, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo, userInfo)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatMappingModelNotFoundErrors_BridgeCorrectly() {
|
||||
|
||||
let dummyURL = NSURL(string: "file:///test1/test2.sqlite")!
|
||||
|
||||
let model = NSManagedObjectModel.fromBundle(NSBundle(forClass: self.dynamicType), modelName: "Model")
|
||||
let version = "1.0.0"
|
||||
|
||||
let error = CoreStoreError.MappingModelNotFound(localStoreURL: dummyURL, targetModel: model, targetModelVersion: version)
|
||||
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.MappingModelNotFound.rawValue)
|
||||
|
||||
let userInfo: NSDictionary = [
|
||||
"localStoreURL": dummyURL,
|
||||
"targetModel": model,
|
||||
"targetModelVersion": version
|
||||
]
|
||||
let objcError = error.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError.bridgeToSwift)
|
||||
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError.code, CoreStoreErrorCode.MappingModelNotFound.rawValue)
|
||||
XCTAssertEqual(objcError.userInfo, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.MappingModelNotFound.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo, userInfo)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatProgressiveMigrationRequiredErrors_BridgeCorrectly() {
|
||||
|
||||
let dummyURL = NSURL(string: "file:///test1/test2.sqlite")!
|
||||
|
||||
let error = CoreStoreError.ProgressiveMigrationRequired(localStoreURL: dummyURL)
|
||||
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue)
|
||||
|
||||
let userInfo: NSDictionary = [
|
||||
"localStoreURL": dummyURL
|
||||
]
|
||||
let objcError = error.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError.bridgeToSwift)
|
||||
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError.code, CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue)
|
||||
XCTAssertEqual(objcError.userInfo, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo, userInfo)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatInternalErrorErrors_BridgeCorrectly() {
|
||||
|
||||
let internalError = NSError(
|
||||
domain: "com.dummy",
|
||||
code: 123,
|
||||
userInfo: [
|
||||
"key1": "value1",
|
||||
"key2": 2,
|
||||
"key3": NSDate()
|
||||
]
|
||||
)
|
||||
let error = CoreStoreError(internalError)
|
||||
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.InternalError.rawValue)
|
||||
|
||||
let userInfo: NSDictionary = [
|
||||
"NSError": internalError
|
||||
]
|
||||
let objcError = error.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError.bridgeToSwift)
|
||||
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError.code, CoreStoreErrorCode.InternalError.rawValue)
|
||||
XCTAssertEqual(objcError.userInfo, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.InternalError.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo, userInfo)
|
||||
}
|
||||
}
|
||||
3211
CoreStoreTests/FetchTests.swift
Normal file
429
CoreStoreTests/FromTests.swift
Normal file
@@ -0,0 +1,429 @@
|
||||
//
|
||||
// FromTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - FromTests
|
||||
|
||||
final class FromTests: BaseTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let from = From()
|
||||
XCTAssert(from.entityClass === NSManagedObject.self)
|
||||
XCTAssertNil(from.configurations)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
XCTAssert(from.entityClass === TestEntity1.self)
|
||||
XCTAssertNil(from.configurations)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
XCTAssert(from.entityClass === TestEntity1.self)
|
||||
XCTAssertEqual(from.configurations?.count, 1)
|
||||
XCTAssertEqual(from.configurations?[0], "Config1")
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>(nil, "Config1")
|
||||
XCTAssert(from.entityClass === TestEntity1.self)
|
||||
XCTAssertEqual(from.configurations?.count, 2)
|
||||
XCTAssertEqual(from.configurations?[0], nil)
|
||||
XCTAssertEqual(from.configurations?[1], "Config1")
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultConfigurations() {
|
||||
|
||||
self.prepareStack { (dataStack) in
|
||||
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForSingleConfigurations() {
|
||||
|
||||
self.prepareStack(configurations: ["Config1"]) { (dataStack) in
|
||||
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config2")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>()
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config1")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config2")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultAndCustomConfigurations() {
|
||||
|
||||
self.prepareStack(configurations: [nil, "Config1"]) { (dataStack) in
|
||||
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(Set(affectedConfigurations), ["PF_DEFAULT_CONFIGURATION_NAME", "Config1"] as Set)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config2")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>()
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config1")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config2")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForMultipleConfigurations() {
|
||||
|
||||
self.prepareStack(configurations: ["Config1", "Config2"]) { (dataStack) in
|
||||
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config2")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>()
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config2"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config1")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config2")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.affectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config2"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
CoreStoreTests/GroupByTests.swift
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// GroupByTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - GroupByTests
|
||||
|
||||
final class GroupByTests: BaseTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatGroupByClauses_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let groupBy = GroupBy()
|
||||
XCTAssertEqual(groupBy, GroupBy([] as [String]))
|
||||
XCTAssertNotEqual(groupBy, GroupBy("key"))
|
||||
XCTAssertTrue(groupBy.keyPaths.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let groupBy = GroupBy("key1")
|
||||
XCTAssertEqual(groupBy, GroupBy("key1"))
|
||||
XCTAssertEqual(groupBy, GroupBy(["key1"]))
|
||||
XCTAssertNotEqual(groupBy, GroupBy("key2"))
|
||||
XCTAssertEqual(groupBy.keyPaths, ["key1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let groupBy = GroupBy("key1", "key2")
|
||||
XCTAssertEqual(groupBy, GroupBy("key1", "key2"))
|
||||
XCTAssertEqual(groupBy, GroupBy(["key1", "key2"]))
|
||||
XCTAssertNotEqual(groupBy, GroupBy("key2", "key1"))
|
||||
XCTAssertEqual(groupBy.keyPaths, ["key1", "key2"])
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatGroupByClauses_ApplyToFetchRequestsCorrectly() {
|
||||
|
||||
self.prepareStack { (dataStack) in
|
||||
|
||||
let groupBy = GroupBy("testString")
|
||||
|
||||
let request = NSFetchRequest()
|
||||
_ = From(TestEntity1).applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
groupBy.applyToFetchRequest(request)
|
||||
|
||||
XCTAssertNotNil(request.propertiesToGroupBy)
|
||||
|
||||
let attributes = (request.propertiesToGroupBy ?? []) as! [NSAttributeDescription]
|
||||
XCTAssertEqual(attributes.map { $0.name }, groupBy.keyPaths)
|
||||
}
|
||||
}
|
||||
}
|
||||
982
CoreStoreTests/ImportTests.swift
Normal file
@@ -0,0 +1,982 @@
|
||||
//
|
||||
// ImportTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - ImportTests
|
||||
|
||||
class ImportTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportObject_CanSkipImport() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
do {
|
||||
|
||||
let object = try transaction.importObject(
|
||||
Into(TestEntity1),
|
||||
source: [
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 1),
|
||||
"testDecimal": NSDecimalNumber(string: "1"),
|
||||
"testString": "nil:TestEntity1:1",
|
||||
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!,
|
||||
"skip_insert": ""
|
||||
]
|
||||
)
|
||||
XCTAssertNil(object)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 0)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportObject_CanThrowError() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let errorExpectation = self.expectationWithDescription("error")
|
||||
do {
|
||||
|
||||
let _ = try transaction.importObject(
|
||||
Into(TestEntity1),
|
||||
source: [
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 1),
|
||||
"testDecimal": NSDecimalNumber(string: "1"),
|
||||
"testString": "nil:TestEntity1:1",
|
||||
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!,
|
||||
"throw_on_insert": ""
|
||||
]
|
||||
)
|
||||
XCTFail()
|
||||
}
|
||||
catch _ as TestInsertError {
|
||||
|
||||
errorExpectation.fulfill()
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = transaction.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertNil(object?.testEntityID)
|
||||
XCTAssertNil(object?.testBoolean)
|
||||
XCTAssertNil(object?.testNumber)
|
||||
XCTAssertNil(object?.testDecimal)
|
||||
XCTAssertNil(object?.testString)
|
||||
XCTAssertNil(object?.testData)
|
||||
XCTAssertNil(object?.testDate)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportObject_CanImportCorrectly() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
do {
|
||||
|
||||
let object = try transaction.importObject(
|
||||
Into(TestEntity1),
|
||||
source: [
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 1),
|
||||
"testDecimal": NSDecimalNumber(string: "1"),
|
||||
"testString": "nil:TestEntity1:1",
|
||||
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!
|
||||
]
|
||||
)
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
|
||||
XCTAssertNil(object?.testEntityID)
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!)
|
||||
|
||||
try transaction.importObject(
|
||||
object!,
|
||||
source: [
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 2),
|
||||
"testDecimal": NSDecimalNumber(string: "2"),
|
||||
"testString": "nil:TestEntity1:2",
|
||||
"testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!
|
||||
]
|
||||
)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
|
||||
XCTAssertNil(object?.testEntityID)
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(bool: false))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(integer: 2))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "2"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:2")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportObjects_CanSkipImport() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
do {
|
||||
|
||||
let sourceArray: [TestEntity1.ImportSource] = [
|
||||
[
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 1),
|
||||
"testDecimal": NSDecimalNumber(string: "1"),
|
||||
"testString": "nil:TestEntity1:1",
|
||||
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!,
|
||||
"skip_insert": ""
|
||||
],
|
||||
[
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 2),
|
||||
"testDecimal": NSDecimalNumber(string: "2"),
|
||||
"testString": "nil:TestEntity1:2",
|
||||
"testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!
|
||||
]
|
||||
]
|
||||
let objects = try transaction.importObjects(
|
||||
Into(TestEntity1),
|
||||
sourceArray: sourceArray
|
||||
)
|
||||
XCTAssertEqual(objects.count, 1)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = objects[0]
|
||||
let dictionary = sourceArray[1]
|
||||
XCTAssertNil(object.testEntityID)
|
||||
XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber)
|
||||
XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber)
|
||||
XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber)
|
||||
XCTAssertEqual(object.testString, dictionary["testString"] as? String)
|
||||
XCTAssertEqual(object.testData, dictionary["testData"] as? NSData)
|
||||
XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportObjects_CanThrowError() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let errorExpectation = self.expectationWithDescription("error")
|
||||
do {
|
||||
|
||||
let sourceArray: [TestEntity1.ImportSource] = [
|
||||
[
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 1),
|
||||
"testDecimal": NSDecimalNumber(string: "1"),
|
||||
"testString": "nil:TestEntity1:1",
|
||||
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!,
|
||||
"throw_on_insert": ""
|
||||
],
|
||||
[
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 2),
|
||||
"testDecimal": NSDecimalNumber(string: "2"),
|
||||
"testString": "nil:TestEntity1:2",
|
||||
"testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!
|
||||
]
|
||||
]
|
||||
let _ = try transaction.importObjects(
|
||||
Into(TestEntity1),
|
||||
sourceArray: sourceArray
|
||||
)
|
||||
XCTFail()
|
||||
}
|
||||
catch _ as TestInsertError {
|
||||
|
||||
errorExpectation.fulfill()
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = transaction.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertNil(object?.testEntityID)
|
||||
XCTAssertNil(object?.testBoolean)
|
||||
XCTAssertNil(object?.testNumber)
|
||||
XCTAssertNil(object?.testDecimal)
|
||||
XCTAssertNil(object?.testString)
|
||||
XCTAssertNil(object?.testData)
|
||||
XCTAssertNil(object?.testDate)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportObjects_CanImportCorrectly() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
do {
|
||||
|
||||
let sourceArray: [TestEntity1.ImportSource] = [
|
||||
[
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 1),
|
||||
"testDecimal": NSDecimalNumber(string: "1"),
|
||||
"testString": "nil:TestEntity1:1",
|
||||
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!
|
||||
],
|
||||
[
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 2),
|
||||
"testDecimal": NSDecimalNumber(string: "2"),
|
||||
"testString": "nil:TestEntity1:2",
|
||||
"testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!
|
||||
]
|
||||
]
|
||||
let objects = try transaction.importObjects(
|
||||
Into(TestEntity1),
|
||||
sourceArray: sourceArray
|
||||
)
|
||||
XCTAssertEqual(objects.count, sourceArray.count)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 2)
|
||||
|
||||
for i in 0 ..< sourceArray.count {
|
||||
|
||||
let object = objects[i]
|
||||
let dictionary = sourceArray[i]
|
||||
|
||||
XCTAssertNil(object.testEntityID)
|
||||
XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber)
|
||||
XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber)
|
||||
XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber)
|
||||
XCTAssertEqual(object.testString, dictionary["testString"] as? String)
|
||||
XCTAssertEqual(object.testData, dictionary["testData"] as? NSData)
|
||||
XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate)
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportUniqueObject_CanSkipImport() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
do {
|
||||
|
||||
let object = try transaction.importUniqueObject(
|
||||
Into(TestEntity1),
|
||||
source: [
|
||||
"testEntityID": NSNumber(integer: 106),
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
|
||||
"skip_insert": ""
|
||||
]
|
||||
)
|
||||
XCTAssertNil(object)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
do {
|
||||
|
||||
let object = try transaction.importUniqueObject(
|
||||
Into(TestEntity1),
|
||||
source: [
|
||||
"testEntityID": NSNumber(integer: 105),
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
|
||||
"skip_update": ""
|
||||
]
|
||||
)
|
||||
XCTAssertNil(object)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5)
|
||||
|
||||
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
|
||||
XCTAssertNotNil(existingObjects)
|
||||
XCTAssertEqual(existingObjects?.count, 1)
|
||||
|
||||
let existingObject = existingObjects?[0]
|
||||
XCTAssertEqual(existingObject?.testEntityID, NSNumber(integer: 105))
|
||||
XCTAssertEqual(existingObject?.testBoolean, NSNumber(bool: true))
|
||||
XCTAssertEqual(existingObject?.testNumber, NSNumber(integer: 5))
|
||||
XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5"))
|
||||
XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5")
|
||||
XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(existingObject?.testDate, self.dateFormatter.dateFromString("2000-01-05T00:00:00Z")!)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportUniqueObject_CanThrowError() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
do {
|
||||
|
||||
let errorExpectation = self.expectationWithDescription("error")
|
||||
do {
|
||||
|
||||
let _ = try transaction.importUniqueObject(
|
||||
Into(TestEntity1),
|
||||
source: [
|
||||
"testEntityID": NSNumber(integer: 106),
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
|
||||
"throw_on_insert": ""
|
||||
]
|
||||
)
|
||||
XCTFail()
|
||||
}
|
||||
catch _ as TestInsertError {
|
||||
|
||||
errorExpectation.fulfill()
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
|
||||
|
||||
let object = transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 106))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106))
|
||||
XCTAssertNil(object?.testBoolean)
|
||||
XCTAssertNil(object?.testNumber)
|
||||
XCTAssertNil(object?.testDecimal)
|
||||
XCTAssertNil(object?.testString)
|
||||
XCTAssertNil(object?.testData)
|
||||
XCTAssertNil(object?.testDate)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
}
|
||||
do {
|
||||
|
||||
let errorExpectation = self.expectationWithDescription("error")
|
||||
do {
|
||||
|
||||
let _ = try transaction.importUniqueObject(
|
||||
Into(TestEntity1),
|
||||
source: [
|
||||
"testEntityID": NSNumber(integer: 105),
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
|
||||
"throw_on_update": ""
|
||||
]
|
||||
)
|
||||
XCTFail()
|
||||
}
|
||||
catch _ as TestUpdateError {
|
||||
|
||||
errorExpectation.fulfill()
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
|
||||
|
||||
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
|
||||
XCTAssertNotNil(existingObjects)
|
||||
XCTAssertEqual(existingObjects?.count, 1)
|
||||
|
||||
let existingObject = existingObjects?[0]
|
||||
XCTAssertNotNil(existingObject)
|
||||
XCTAssertEqual(existingObject?.testEntityID, NSNumber(integer: 105))
|
||||
XCTAssertEqual(existingObject?.testBoolean, NSNumber(bool: true))
|
||||
XCTAssertEqual(existingObject?.testNumber, NSNumber(integer: 5))
|
||||
XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5"))
|
||||
XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5")
|
||||
XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(existingObject?.testDate, self.dateFormatter.dateFromString("2000-01-05T00:00:00Z")!)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
}
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportUniqueObject_CanImportCorrectly() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
do {
|
||||
|
||||
let object = try transaction.importUniqueObject(
|
||||
Into(TestEntity1),
|
||||
source: [
|
||||
"testEntityID": NSNumber(integer: 106),
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!
|
||||
]
|
||||
)
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
|
||||
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106))
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(integer: 6))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "6"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:6")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
do {
|
||||
|
||||
let object = try transaction.importUniqueObject(
|
||||
Into(TestEntity1),
|
||||
source: [
|
||||
"testEntityID": NSNumber(integer: 106),
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 7),
|
||||
"testDecimal": NSDecimalNumber(string: "7"),
|
||||
"testString": "nil:TestEntity1:7",
|
||||
"testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!,
|
||||
]
|
||||
)
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
|
||||
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106))
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(bool: false))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(integer: 7))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "7"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:7")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!)
|
||||
|
||||
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 106))
|
||||
XCTAssertNotNil(existingObjects)
|
||||
XCTAssertEqual(existingObjects?.count, 1)
|
||||
|
||||
let existingObject = existingObjects?[0]
|
||||
XCTAssertEqual(existingObject, object)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportUniqueObjects_CanSkipImport() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
do {
|
||||
|
||||
let sourceArray: [TestEntity1.ImportSource] = [
|
||||
[
|
||||
"testEntityID": NSNumber(integer: 106),
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
|
||||
"skip_insert": ""
|
||||
],
|
||||
[
|
||||
"testEntityID": NSNumber(integer: 107),
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 7),
|
||||
"testDecimal": NSDecimalNumber(string: "7"),
|
||||
"testString": "nil:TestEntity1:7",
|
||||
"testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!
|
||||
]
|
||||
]
|
||||
let objects = try transaction.importUniqueObjects(
|
||||
Into(TestEntity1),
|
||||
sourceArray: sourceArray
|
||||
)
|
||||
XCTAssertEqual(objects.count, 1)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
|
||||
|
||||
let object = objects[0]
|
||||
let dictionary = sourceArray[1]
|
||||
XCTAssertEqual(object.testEntityID, dictionary["testEntityID"] as? NSNumber)
|
||||
XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber)
|
||||
XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber)
|
||||
XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber)
|
||||
XCTAssertEqual(object.testString, dictionary["testString"] as? String)
|
||||
XCTAssertEqual(object.testData, dictionary["testData"] as? NSData)
|
||||
XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportUniqueObjects_CanThrowError() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let errorExpectation = self.expectationWithDescription("error")
|
||||
do {
|
||||
|
||||
let sourceArray: [TestEntity1.ImportSource] = [
|
||||
[
|
||||
"testEntityID": NSNumber(integer: 106),
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
|
||||
"throw_on_id": ""
|
||||
],
|
||||
[
|
||||
"testEntityID": NSNumber(integer: 107),
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 7),
|
||||
"testDecimal": NSDecimalNumber(string: "7"),
|
||||
"testString": "nil:TestEntity1:7",
|
||||
"testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!
|
||||
]
|
||||
]
|
||||
let _ = try transaction.importUniqueObjects(
|
||||
Into(TestEntity1),
|
||||
sourceArray: sourceArray
|
||||
)
|
||||
XCTFail()
|
||||
}
|
||||
catch _ as TestIDError {
|
||||
|
||||
errorExpectation.fulfill()
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5)
|
||||
|
||||
XCTAssertNil(transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 106)))
|
||||
XCTAssertNil(transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 107)))
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
transaction.context.reset()
|
||||
}
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let errorExpectation = self.expectationWithDescription("error")
|
||||
do {
|
||||
|
||||
let sourceArray: [TestEntity1.ImportSource] = [
|
||||
[
|
||||
"testEntityID": NSNumber(integer: 106),
|
||||
"testBoolean": NSNumber(bool: true),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
|
||||
"throw_on_insert": ""
|
||||
],
|
||||
[
|
||||
"testEntityID": NSNumber(integer: 107),
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 7),
|
||||
"testDecimal": NSDecimalNumber(string: "7"),
|
||||
"testString": "nil:TestEntity1:7",
|
||||
"testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!
|
||||
]
|
||||
]
|
||||
let _ = try transaction.importUniqueObjects(
|
||||
Into(TestEntity1),
|
||||
sourceArray: sourceArray
|
||||
)
|
||||
XCTFail()
|
||||
}
|
||||
catch _ as TestInsertError {
|
||||
|
||||
errorExpectation.fulfill()
|
||||
|
||||
let object = transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 106))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106))
|
||||
XCTAssertNil(object?.testBoolean)
|
||||
XCTAssertNil(object?.testNumber)
|
||||
XCTAssertNil(object?.testDecimal)
|
||||
XCTAssertNil(object?.testString)
|
||||
XCTAssertNil(object?.testData)
|
||||
XCTAssertNil(object?.testDate)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
transaction.context.reset()
|
||||
}
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let errorExpectation = self.expectationWithDescription("error")
|
||||
do {
|
||||
|
||||
let sourceArray: [TestEntity1.ImportSource] = [
|
||||
[
|
||||
"testEntityID": NSNumber(integer: 105),
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
|
||||
"throw_on_update": ""
|
||||
]
|
||||
]
|
||||
let _ = try transaction.importUniqueObjects(
|
||||
Into(TestEntity1),
|
||||
sourceArray: sourceArray
|
||||
)
|
||||
XCTFail()
|
||||
}
|
||||
catch _ as TestUpdateError {
|
||||
|
||||
errorExpectation.fulfill()
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5)
|
||||
|
||||
let object = transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 105))
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(integer: 5))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "5"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:5")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:5" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-05T00:00:00Z")!)
|
||||
|
||||
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
|
||||
XCTAssertNotNil(existingObjects)
|
||||
XCTAssertEqual(existingObjects?.count, 1)
|
||||
|
||||
let existingObject = existingObjects?[0]
|
||||
XCTAssertEqual(existingObject, object)
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatImportUniqueObjects_CanImportCorrectly() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
do {
|
||||
|
||||
let sourceArray: [TestEntity1.ImportSource] = [
|
||||
[
|
||||
"testEntityID": NSNumber(integer: 105),
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 15),
|
||||
"testDecimal": NSDecimalNumber(string: "15"),
|
||||
"testString": "nil:TestEntity1:15",
|
||||
"testData": ("nil:TestEntity1:15" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-15T00:00:00Z")!
|
||||
],
|
||||
[
|
||||
"testEntityID": NSNumber(integer: 106),
|
||||
"testBoolean": NSNumber(bool: false),
|
||||
"testNumber": NSNumber(integer: 6),
|
||||
"testDecimal": NSDecimalNumber(string: "6"),
|
||||
"testString": "nil:TestEntity1:6",
|
||||
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
|
||||
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!
|
||||
]
|
||||
]
|
||||
let objects = try transaction.importUniqueObjects(
|
||||
Into(TestEntity1),
|
||||
sourceArray: sourceArray
|
||||
)
|
||||
XCTAssertEqual(objects.count, sourceArray.count)
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
|
||||
for i in 0 ..< sourceArray.count {
|
||||
|
||||
let object = objects[i]
|
||||
let dictionary = sourceArray[i]
|
||||
|
||||
XCTAssertEqual(object.testEntityID, dictionary["testEntityID"] as? NSNumber)
|
||||
XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber)
|
||||
XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber)
|
||||
XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber)
|
||||
XCTAssertEqual(object.testString, dictionary["testString"] as? String)
|
||||
XCTAssertEqual(object.testData, dictionary["testData"] as? NSData)
|
||||
XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate)
|
||||
}
|
||||
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
|
||||
XCTAssertNotNil(existingObjects)
|
||||
XCTAssertEqual(existingObjects?.count, 1)
|
||||
|
||||
let existingObject = existingObjects?[0]
|
||||
XCTAssertEqual(existingObject, objects[0])
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
transaction.context.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - TestInsertError
|
||||
|
||||
private struct TestInsertError: ErrorType {}
|
||||
|
||||
|
||||
// MARK: - TestUpdateError
|
||||
|
||||
private struct TestUpdateError: ErrorType {}
|
||||
|
||||
|
||||
// MARK: - TestIDError
|
||||
|
||||
private struct TestIDError: ErrorType {}
|
||||
|
||||
|
||||
// MARK: - TestEntity1
|
||||
|
||||
extension TestEntity1: ImportableUniqueObject {
|
||||
|
||||
// MARK: ImportableObject
|
||||
|
||||
typealias ImportSource = [String: AnyObject]
|
||||
|
||||
static func shouldInsertFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) -> Bool {
|
||||
|
||||
return source["skip_insert"] == nil
|
||||
}
|
||||
|
||||
func didInsertFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) throws {
|
||||
|
||||
if let _ = source["throw_on_insert"] {
|
||||
|
||||
throw TestInsertError()
|
||||
}
|
||||
self.testBoolean = source["testBoolean"] as? NSNumber
|
||||
self.testNumber = source["testNumber"] as? NSNumber
|
||||
self.testDecimal = source["testDecimal"] as? NSDecimalNumber
|
||||
self.testString = source["testString"] as? String
|
||||
self.testData = source["testData"] as? NSData
|
||||
self.testDate = source["testDate"] as? NSDate
|
||||
self.testNil = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: ImportableUniqueObject
|
||||
|
||||
typealias UniqueIDType = NSNumber
|
||||
|
||||
static var uniqueIDKeyPath: String {
|
||||
|
||||
return "testEntityID"
|
||||
}
|
||||
|
||||
var uniqueIDValue: NSNumber {
|
||||
|
||||
get {
|
||||
|
||||
guard let ID = self.testEntityID else {
|
||||
|
||||
XCTFail()
|
||||
return 0
|
||||
}
|
||||
return ID
|
||||
}
|
||||
set {
|
||||
|
||||
self.testEntityID = newValue
|
||||
}
|
||||
}
|
||||
|
||||
static func shouldUpdateFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) -> Bool {
|
||||
|
||||
return source["skip_update"] == nil
|
||||
}
|
||||
|
||||
static func uniqueIDFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) throws -> NSNumber? {
|
||||
|
||||
if let _ = source["throw_on_id"] {
|
||||
|
||||
throw TestIDError()
|
||||
}
|
||||
return source["testEntityID"] as? NSNumber
|
||||
}
|
||||
|
||||
func updateFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) throws {
|
||||
|
||||
if let _ = source["throw_on_update"] {
|
||||
|
||||
throw TestUpdateError()
|
||||
}
|
||||
self.testBoolean = source["testBoolean"] as? NSNumber
|
||||
self.testNumber = source["testNumber"] as? NSNumber
|
||||
self.testDecimal = source["testDecimal"] as? NSDecimalNumber
|
||||
self.testString = source["testString"] as? String
|
||||
self.testData = source["testData"] as? NSData
|
||||
self.testDate = source["testDate"] as? NSDate
|
||||
self.testNil = nil
|
||||
}
|
||||
}
|
||||
204
CoreStoreTests/IntoTests.swift
Normal file
@@ -0,0 +1,204 @@
|
||||
//
|
||||
// IntoTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - IntoTests
|
||||
|
||||
final class IntoTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatIntoClauseConstants_AreCorrect() {
|
||||
|
||||
XCTAssertEqual(Into<NSManagedObject>.defaultConfigurationName, "PF_DEFAULT_CONFIGURATION_NAME")
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatIntoClauses_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let into = Into()
|
||||
XCTAssert(into.entityClass === NSManagedObject.self)
|
||||
XCTAssertNil(into.configuration)
|
||||
XCTAssertTrue(into.inferStoreIfPossible)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into<TestEntity1>()
|
||||
XCTAssert(into.entityClass === TestEntity1.self)
|
||||
XCTAssertNil(into.configuration)
|
||||
XCTAssertTrue(into.inferStoreIfPossible)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1)
|
||||
XCTAssert(into.entityClass === TestEntity1.self)
|
||||
XCTAssertNil(into.configuration)
|
||||
XCTAssertTrue(into.inferStoreIfPossible)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self as AnyClass)
|
||||
XCTAssert(into.entityClass === TestEntity1.self)
|
||||
XCTAssertNil(into.configuration)
|
||||
XCTAssertTrue(into.inferStoreIfPossible)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into<TestEntity1>("Config1")
|
||||
XCTAssert(into.entityClass === TestEntity1.self)
|
||||
XCTAssertEqual(into.configuration, "Config1")
|
||||
XCTAssertFalse(into.inferStoreIfPossible)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self, "Config1")
|
||||
XCTAssert(into.entityClass === TestEntity1.self)
|
||||
XCTAssertEqual(into.configuration, "Config1")
|
||||
XCTAssertFalse(into.inferStoreIfPossible)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self as AnyClass, "Config1")
|
||||
XCTAssert(into.entityClass === TestEntity1.self)
|
||||
XCTAssertEqual(into.configuration, "Config1")
|
||||
XCTAssertFalse(into.inferStoreIfPossible)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatIntoClauses_AreEquatable() {
|
||||
|
||||
do {
|
||||
|
||||
let into = Into()
|
||||
XCTAssertEqual(into, Into())
|
||||
XCTAssertEqual(into, Into<NSManagedObject>())
|
||||
XCTAssertEqual(into, Into(NSManagedObject.self as AnyClass))
|
||||
XCTAssertFalse(into == Into<TestEntity1>())
|
||||
XCTAssertNotEqual(into, Into<NSManagedObject>("Config1"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into<TestEntity1>()
|
||||
XCTAssertEqual(into, Into(TestEntity1))
|
||||
XCTAssertEqual(into, Into(TestEntity1.self as AnyClass))
|
||||
XCTAssertFalse(into == Into<TestEntity2>())
|
||||
XCTAssertNotEqual(into, Into<TestEntity1>("Config1"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1)
|
||||
XCTAssertEqual(into, Into<TestEntity1>())
|
||||
XCTAssertEqual(into, Into(TestEntity1.self as AnyClass))
|
||||
XCTAssertFalse(into == Into<TestEntity2>())
|
||||
XCTAssertNotEqual(into, Into<TestEntity1>("Config1"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self as AnyClass)
|
||||
XCTAssert(into == Into<TestEntity1>())
|
||||
XCTAssertEqual(into, Into(TestEntity1))
|
||||
XCTAssertFalse(into == Into<TestEntity2>())
|
||||
XCTAssertFalse(into == Into<TestEntity1>("Config1"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into<TestEntity1>("Config1")
|
||||
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
|
||||
XCTAssertEqual(into, Into(TestEntity1.self as AnyClass, "Config1"))
|
||||
XCTAssertFalse(into == Into<TestEntity2>("Config1"))
|
||||
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self, "Config1")
|
||||
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
|
||||
XCTAssertEqual(into, Into<TestEntity1>("Config1"))
|
||||
XCTAssertFalse(into == Into<TestEntity2>("Config1"))
|
||||
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self as AnyClass, "Config1")
|
||||
XCTAssert(into == Into<TestEntity1>("Config1"))
|
||||
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
|
||||
XCTAssertFalse(into == Into<TestEntity2>("Config1"))
|
||||
XCTAssertFalse(into == Into<TestEntity1>("Config2"))
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatIntoClauses_BridgeCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let into = Into()
|
||||
let objcInto = into.bridgeToObjectiveC
|
||||
XCTAssertEqual(into, objcInto.bridgeToSwift)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into<TestEntity1>()
|
||||
let objcInto = into.bridgeToObjectiveC
|
||||
XCTAssertTrue(into == objcInto.bridgeToSwift)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self as AnyClass)
|
||||
let objcInto = into.bridgeToObjectiveC
|
||||
XCTAssertEqual(into, objcInto.bridgeToSwift)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self as AnyClass)
|
||||
let objcInto = into.bridgeToObjectiveC
|
||||
XCTAssertEqual(into, objcInto.bridgeToSwift)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into<TestEntity1>("Config1")
|
||||
let objcInto = into.bridgeToObjectiveC
|
||||
XCTAssertTrue(into == objcInto.bridgeToSwift)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self, "Config1")
|
||||
let objcInto = into.bridgeToObjectiveC
|
||||
XCTAssertTrue(into == objcInto.bridgeToSwift)
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self as AnyClass, "Config1")
|
||||
let objcInto = into.bridgeToObjectiveC
|
||||
XCTAssertTrue(into == objcInto.bridgeToSwift)
|
||||
}
|
||||
}
|
||||
}
|
||||
682
CoreStoreTests/ListObserverTests.swift
Normal file
@@ -0,0 +1,682 @@
|
||||
//
|
||||
// ListObserverTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
#if os(iOS) || os(watchOS) || os(tvOS)
|
||||
|
||||
// MARK: - ListObserverTests
|
||||
|
||||
class ListObserverTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatListObservers_CanReceiveInsertNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
let observer = TestListObserver()
|
||||
let monitor = stack.monitorSectionedList(
|
||||
From(TestEntity1),
|
||||
SectionBy("testBoolean"),
|
||||
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
|
||||
)
|
||||
monitor.addObserver(observer)
|
||||
|
||||
XCTAssertFalse(monitor.hasSections())
|
||||
XCTAssertFalse(monitor.hasObjects())
|
||||
XCTAssertTrue(monitor.objectsInAllSections().isEmpty)
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectationForNotification(
|
||||
"listMonitorWillChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didInsertSectionExpectation = self.expectationForNotification(
|
||||
"listMonitor:didInsertSection:toSectionIndex:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 1)
|
||||
XCTAssertEqual(
|
||||
(note.userInfo ?? [:]),
|
||||
[
|
||||
"sectionInfo": monitor.sectionInfoAtIndex(0),
|
||||
"sectionIndex": 0
|
||||
] as NSDictionary
|
||||
)
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 1
|
||||
}
|
||||
)
|
||||
let didInsertObjectExpectation = self.expectationForNotification(
|
||||
"listMonitor:didInsertObject:toIndexPath:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 2)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
|
||||
["indexPath", "object"]
|
||||
)
|
||||
|
||||
let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
XCTAssertEqual(indexPath?.section, 0)
|
||||
XCTAssertEqual(indexPath?.row, 0)
|
||||
|
||||
let object = userInfo?["object"] as? TestEntity1
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!)
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 2
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectationForNotification(
|
||||
"listMonitorDidChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 3
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectationWithDescription("save")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testBoolean = NSNumber(bool: true)
|
||||
object.testNumber = NSNumber(integer: 1)
|
||||
object.testDecimal = NSDecimalNumber(string: "1")
|
||||
object.testString = "nil:TestEntity1:1"
|
||||
object.testData = ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
|
||||
object.testDate = self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
|
||||
case .Failure:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatListObservers_CanReceiveUpdateNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
let observer = TestListObserver()
|
||||
let monitor = stack.monitorSectionedList(
|
||||
From(TestEntity1),
|
||||
SectionBy("testBoolean"),
|
||||
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
|
||||
)
|
||||
monitor.addObserver(observer)
|
||||
|
||||
XCTAssertTrue(monitor.hasSections())
|
||||
XCTAssertEqual(monitor.numberOfSections(), 2)
|
||||
XCTAssertTrue(monitor.hasObjects())
|
||||
XCTAssertTrue(monitor.hasObjectsInSection(0))
|
||||
XCTAssertEqual(monitor.numberOfObjectsInSection(0), 2)
|
||||
XCTAssertEqual(monitor.numberOfObjectsInSection(1), 3)
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectationForNotification(
|
||||
"listMonitorWillChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
for _ in 1 ... 2 {
|
||||
|
||||
let didUpdateObjectExpectation = self.expectationForNotification(
|
||||
"listMonitor:didUpdateObject:atIndexPath:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssert(events == 1 || events == 2)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
|
||||
["indexPath", "object"]
|
||||
)
|
||||
|
||||
let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
let object = userInfo?["object"] as? TestEntity1
|
||||
|
||||
switch object?.testEntityID {
|
||||
|
||||
case NSNumber(integer: 101)?:
|
||||
XCTAssertEqual(indexPath?.section, 1)
|
||||
XCTAssertEqual(indexPath?.row, 0)
|
||||
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(integer: 11))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:11")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-11T00:00:00Z")!)
|
||||
|
||||
case NSNumber(integer: 102)?:
|
||||
XCTAssertEqual(indexPath?.section, 0)
|
||||
XCTAssertEqual(indexPath?.row, 0)
|
||||
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(bool: false))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(integer: 22))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:22")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-22T00:00:00Z")!)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 1 || events == 2
|
||||
}
|
||||
)
|
||||
}
|
||||
let didChangeExpectation = self.expectationForNotification(
|
||||
"listMonitorDidChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 3)
|
||||
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 3
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectationWithDescription("save")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
if let object = transaction.fetchOne(
|
||||
From(TestEntity1),
|
||||
Where("testEntityID", isEqualTo: 101)) {
|
||||
|
||||
object.testNumber = NSNumber(integer: 11)
|
||||
object.testDecimal = NSDecimalNumber(string: "11")
|
||||
object.testString = "nil:TestEntity1:11"
|
||||
object.testData = ("nil:TestEntity1:11" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
|
||||
object.testDate = self.dateFormatter.dateFromString("2000-01-11T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
if let object = transaction.fetchOne(
|
||||
From(TestEntity1),
|
||||
Where("testEntityID", isEqualTo: 102)) {
|
||||
|
||||
object.testNumber = NSNumber(integer: 22)
|
||||
object.testDecimal = NSDecimalNumber(string: "22")
|
||||
object.testString = "nil:TestEntity1:22"
|
||||
object.testData = ("nil:TestEntity1:22" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
|
||||
object.testDate = self.dateFormatter.dateFromString("2000-01-22T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
|
||||
case .Failure:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatListObservers_CanReceiveMoveNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
let observer = TestListObserver()
|
||||
let monitor = stack.monitorSectionedList(
|
||||
From(TestEntity1),
|
||||
SectionBy("testBoolean"),
|
||||
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
|
||||
)
|
||||
monitor.addObserver(observer)
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectationForNotification(
|
||||
"listMonitorWillChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didMoveObjectExpectation = self.expectationForNotification(
|
||||
"listMonitor:didMoveObject:fromIndexPath:toIndexPath:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 1)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
|
||||
["fromIndexPath", "toIndexPath", "object"]
|
||||
)
|
||||
|
||||
let fromIndexPath = userInfo?["fromIndexPath"] as? NSIndexPath
|
||||
XCTAssertEqual(fromIndexPath?.section, 0)
|
||||
XCTAssertEqual(fromIndexPath?.row, 0)
|
||||
|
||||
let toIndexPath = userInfo?["toIndexPath"] as? NSIndexPath
|
||||
XCTAssertEqual(toIndexPath?.section, 1)
|
||||
XCTAssertEqual(toIndexPath?.row, 1)
|
||||
|
||||
let object = userInfo?["object"] as? TestEntity1
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 102))
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
|
||||
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 1
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectationForNotification(
|
||||
"listMonitorDidChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 2)
|
||||
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 2
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectationWithDescription("save")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
if let object = transaction.fetchOne(
|
||||
From(TestEntity1),
|
||||
Where("testEntityID", isEqualTo: 102)) {
|
||||
|
||||
object.testBoolean = NSNumber(bool: true)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
|
||||
case .Failure:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatListObservers_CanReceiveDeleteNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
let observer = TestListObserver()
|
||||
let monitor = stack.monitorSectionedList(
|
||||
From(TestEntity1),
|
||||
SectionBy("testBoolean"),
|
||||
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
|
||||
)
|
||||
monitor.addObserver(observer)
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectationForNotification(
|
||||
"listMonitorWillChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
for _ in 1 ... 2 {
|
||||
|
||||
let didUpdateObjectExpectation = self.expectationForNotification(
|
||||
"listMonitor:didDeleteObject:fromIndexPath:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssert(events == 1 || events == 2)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
|
||||
["indexPath", "object"]
|
||||
)
|
||||
|
||||
let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
|
||||
XCTAssertEqual(indexPath?.section, 0)
|
||||
XCTAssert(indexPath?.row == 0 || indexPath?.row == 1)
|
||||
|
||||
let object = userInfo?["object"] as? TestEntity1
|
||||
XCTAssertEqual(object?.deleted, true)
|
||||
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 1 || events == 2
|
||||
}
|
||||
)
|
||||
}
|
||||
let didDeleteSectionExpectation = self.expectationForNotification(
|
||||
"listMonitor:didDeleteSection:fromSectionIndex:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 3)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
|
||||
["sectionInfo", "sectionIndex"]
|
||||
)
|
||||
|
||||
let sectionInfo = userInfo?["sectionInfo"]
|
||||
XCTAssertNotNil(sectionInfo)
|
||||
XCTAssertEqual(sectionInfo?.name, "0")
|
||||
|
||||
let sectionIndex = userInfo?["sectionIndex"]
|
||||
XCTAssertEqual(sectionIndex as? NSNumber, NSNumber(integer: 0))
|
||||
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 3
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectationForNotification(
|
||||
"listMonitorDidChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 4)
|
||||
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 4
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectationWithDescription("save")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
transaction.deleteAll(
|
||||
From(TestEntity1),
|
||||
Where("testBoolean", isEqualTo: false)
|
||||
)
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
|
||||
case .Failure:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: TestListObserver
|
||||
|
||||
class TestListObserver: ListSectionObserver {
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
typealias ListEntityType = TestEntity1
|
||||
|
||||
func listMonitorWillChange(monitor: ListMonitor<TestEntity1>) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitorWillChange:",
|
||||
object: self,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitorDidChange(monitor: ListMonitor<TestEntity1>) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitorDidChange:",
|
||||
object: self,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitorWillRefetch(monitor: ListMonitor<TestEntity1>) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitorWillRefetch:",
|
||||
object: self,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitorDidRefetch(monitor: ListMonitor<TestEntity1>) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitorDidRefetch:",
|
||||
object: self,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObjectObserver
|
||||
|
||||
func listMonitor(monitor: ListMonitor<TestEntity1>, didInsertObject object: TestEntity1, toIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitor:didInsertObject:toIndexPath:",
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object,
|
||||
"indexPath": indexPath
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<TestEntity1>, didDeleteObject object: TestEntity1, fromIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitor:didDeleteObject:fromIndexPath:",
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object,
|
||||
"indexPath": indexPath
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<TestEntity1>, didUpdateObject object: TestEntity1, atIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitor:didUpdateObject:atIndexPath:",
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object,
|
||||
"indexPath": indexPath
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func listMonitor(monitor: ListMonitor<TestEntity1>, didMoveObject object: TestEntity1, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitor:didMoveObject:fromIndexPath:toIndexPath:",
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object,
|
||||
"fromIndexPath": fromIndexPath,
|
||||
"toIndexPath": toIndexPath
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListSectionObserver
|
||||
|
||||
func listMonitor(monitor: ListMonitor<TestEntity1>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitor:didInsertSection:toSectionIndex:",
|
||||
object: self,
|
||||
userInfo: [
|
||||
"sectionInfo": sectionInfo,
|
||||
"sectionIndex": sectionIndex
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<TestEntity1>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"listMonitor:didDeleteSection:fromSectionIndex:",
|
||||
object: self,
|
||||
userInfo: [
|
||||
"sectionInfo": sectionInfo,
|
||||
"sectionIndex": sectionIndex
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
147
CoreStoreTests/MigrationChainTests.swift
Normal file
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// MigrationChainTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - MigrationChainTests
|
||||
|
||||
final class MigrationChainTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatNilMigrationChains_HaveNoVersions() {
|
||||
|
||||
let chain: MigrationChain = nil
|
||||
XCTAssertTrue(chain.valid)
|
||||
XCTAssertTrue(chain.empty)
|
||||
|
||||
XCTAssertFalse(chain.contains("version1"))
|
||||
XCTAssertNil(chain.nextVersionFrom("version1"))
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatStringMigrationChains_HaveOneVersion() {
|
||||
|
||||
let chain: MigrationChain = "version1"
|
||||
XCTAssertTrue(chain.valid)
|
||||
XCTAssertTrue(chain.empty)
|
||||
|
||||
XCTAssertTrue(chain.contains("version1"))
|
||||
XCTAssertFalse(chain.contains("version2"))
|
||||
|
||||
XCTAssertNil(chain.nextVersionFrom("version1"))
|
||||
XCTAssertNil(chain.nextVersionFrom("version2"))
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatArrayMigrationChains_HaveLinearVersions() {
|
||||
|
||||
let chain: MigrationChain = ["version1", "version2", "version3", "version4"]
|
||||
XCTAssertTrue(chain.valid)
|
||||
XCTAssertFalse(chain.empty)
|
||||
|
||||
XCTAssertTrue(chain.contains("version1"))
|
||||
XCTAssertTrue(chain.contains("version2"))
|
||||
XCTAssertTrue(chain.contains("version3"))
|
||||
XCTAssertTrue(chain.contains("version4"))
|
||||
XCTAssertFalse(chain.contains("version5"))
|
||||
|
||||
XCTAssertEqual(chain.nextVersionFrom("version1"), "version2")
|
||||
XCTAssertEqual(chain.nextVersionFrom("version2"), "version3")
|
||||
XCTAssertEqual(chain.nextVersionFrom("version3"), "version4")
|
||||
XCTAssertNil(chain.nextVersionFrom("version4"))
|
||||
XCTAssertNil(chain.nextVersionFrom("version5"))
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDictionaryMigrationChains_HaveTreeVersions() {
|
||||
|
||||
let chain: MigrationChain = [
|
||||
"version1": "version4",
|
||||
"version2": "version3",
|
||||
"version3": "version4"
|
||||
]
|
||||
XCTAssertTrue(chain.valid)
|
||||
XCTAssertFalse(chain.empty)
|
||||
|
||||
XCTAssertTrue(chain.contains("version1"))
|
||||
XCTAssertTrue(chain.contains("version2"))
|
||||
XCTAssertTrue(chain.contains("version3"))
|
||||
XCTAssertTrue(chain.contains("version4"))
|
||||
XCTAssertFalse(chain.contains("version5"))
|
||||
|
||||
XCTAssertEqual(chain.nextVersionFrom("version1"), "version4")
|
||||
XCTAssertEqual(chain.nextVersionFrom("version2"), "version3")
|
||||
XCTAssertEqual(chain.nextVersionFrom("version3"), "version4")
|
||||
XCTAssertNil(chain.nextVersionFrom("version4"))
|
||||
XCTAssertNil(chain.nextVersionFrom("version5"))
|
||||
|
||||
// The cases below will trigger assertion failures internally
|
||||
|
||||
// let linearLoopChain: MigrationChain = ["version1", "version2", "version1", "version3", "version4"]
|
||||
// XCTAssertFalse(linearLoopChain.valid, "linearLoopChain.valid")
|
||||
//
|
||||
// let treeAmbiguousChain: MigrationChain = [
|
||||
// "version1": "version4",
|
||||
// "version2": "version3",
|
||||
// "version1": "version2",
|
||||
// "version3": "version4"
|
||||
// ]
|
||||
// XCTAssertFalse(treeAmbiguousChain.valid, "treeAmbiguousChain.valid")
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatMigrationChains_AreEquatable() {
|
||||
|
||||
do {
|
||||
|
||||
let chain1: MigrationChain = nil
|
||||
let chain2: MigrationChain = []
|
||||
let chain3: MigrationChain = [:]
|
||||
XCTAssertEqual(chain1, chain2)
|
||||
XCTAssertEqual(chain2, chain3)
|
||||
XCTAssertEqual(chain3, chain1)
|
||||
}
|
||||
do {
|
||||
|
||||
let chain1: MigrationChain = "version1"
|
||||
let chain2: MigrationChain = ["version1"]
|
||||
XCTAssertEqual(chain1, chain2)
|
||||
}
|
||||
do {
|
||||
|
||||
let chain1: MigrationChain = ["version1", "version2", "version3", "version4"]
|
||||
let chain2: MigrationChain = [
|
||||
"version1": "version2",
|
||||
"version2": "version3",
|
||||
"version3": "version4"
|
||||
]
|
||||
XCTAssertEqual(chain1, chain2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Xcode 4.3">
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10174" systemVersion="15F34" minimumToolsVersion="Xcode 4.3">
|
||||
<entity name="TestEntity1AAA" representedClassName="CoreStoreTests.TestEntity1" syncable="YES">
|
||||
<attribute name="testBoolean" optional="YES" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="testData" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="testDate" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="testEntityID" attributeType="Integer 64" syncable="YES"/>
|
||||
<attribute name="testNumber" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="testDecimal" optional="YES" attributeType="Decimal" syncable="YES"/>
|
||||
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" syncable="YES"/>
|
||||
<attribute name="testNil" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="testNumber" optional="YES" attributeType="Integer 32" syncable="YES"/>
|
||||
<attribute name="testString" optional="YES" attributeType="String" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="TestEntity2" representedClassName="CoreStoreTests.TestEntity2" syncable="YES">
|
||||
<attribute name="testBoolean" optional="YES" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="testData" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="testDate" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="testEntityID" attributeType="Integer 64" syncable="YES"/>
|
||||
<attribute name="testNumber" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="testDecimal" optional="YES" attributeType="Decimal" syncable="YES"/>
|
||||
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" syncable="YES"/>
|
||||
<attribute name="testNil" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="testNumber" optional="YES" attributeType="Integer 32" syncable="YES"/>
|
||||
<attribute name="testString" optional="YES" attributeType="String" syncable="YES"/>
|
||||
</entity>
|
||||
<configuration name="Config1">
|
||||
@@ -19,7 +27,7 @@
|
||||
<memberEntity name="TestEntity2"/>
|
||||
</configuration>
|
||||
<elements>
|
||||
<element name="TestEntity1AAA" positionX="-63" positionY="-18" width="128" height="105"/>
|
||||
<element name="TestEntity2" positionX="-63" positionY="9" width="128" height="105"/>
|
||||
<element name="TestEntity1AAA" positionX="-63" positionY="-18" width="128" height="165"/>
|
||||
<element name="TestEntity2" positionX="-63" positionY="9" width="128" height="165"/>
|
||||
</elements>
|
||||
</model>
|
||||
246
CoreStoreTests/ObjectObserverTests.swift
Normal file
@@ -0,0 +1,246 @@
|
||||
//
|
||||
// ObjectObserverTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
#if os(iOS) || os(watchOS) || os(tvOS)
|
||||
|
||||
// MARK: - ObjectObserverTests
|
||||
|
||||
class ObjectObserverTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatObjectObservers_CanReceiveUpdateNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
guard let object = stack.fetchOne(
|
||||
From(TestEntity1),
|
||||
Where("testEntityID", isEqualTo: 101)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
let observer = TestObjectObserver()
|
||||
let monitor = stack.monitorObject(object)
|
||||
monitor.addObserver(observer)
|
||||
|
||||
XCTAssertEqual(monitor.object, object)
|
||||
XCTAssertFalse(monitor.isObjectDeleted)
|
||||
|
||||
var events = 0
|
||||
|
||||
let willUpdateExpectation = self.expectationForNotification(
|
||||
"objectMonitor:willUpdateObject:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual(
|
||||
(note.userInfo ?? [:]),
|
||||
["object": object] as NSDictionary
|
||||
)
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didUpdateExpectation = self.expectationForNotification(
|
||||
"objectMonitor:didUpdateObject:changedPersistentKeys:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 1)
|
||||
XCTAssertEqual(
|
||||
(note.userInfo ?? [:]),
|
||||
[
|
||||
"object": object,
|
||||
"changedPersistentKeys": Set(
|
||||
[
|
||||
"testNumber",
|
||||
"testString"
|
||||
]
|
||||
)
|
||||
] as NSDictionary
|
||||
)
|
||||
let object = note.userInfo?["object"] as? TestEntity1
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(integer: 10))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:10")
|
||||
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 1
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectationWithDescription("save")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.edit(object) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testNumber = NSNumber(integer: 10)
|
||||
object.testString = "nil:TestEntity1:10"
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
|
||||
case .Failure:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatObjectObservers_CanReceiveDeleteNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
self.prepareTestDataForStack(stack)
|
||||
|
||||
guard let object = stack.fetchOne(
|
||||
From(TestEntity1),
|
||||
Where("testEntityID", isEqualTo: 101)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
let observer = TestObjectObserver()
|
||||
let monitor = stack.monitorObject(object)
|
||||
monitor.addObserver(observer)
|
||||
|
||||
XCTAssertEqual(monitor.object, object)
|
||||
XCTAssertFalse(monitor.isObjectDeleted)
|
||||
|
||||
var events = 0
|
||||
|
||||
let didDeleteExpectation = self.expectationForNotification(
|
||||
"objectMonitor:didDeleteObject:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual(
|
||||
(note.userInfo ?? [:]),
|
||||
["object": object] as NSDictionary
|
||||
)
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectationWithDescription("save")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.edit(object) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
transaction.delete(object)
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
XCTAssertTrue(monitor.isObjectDeleted)
|
||||
saveExpectation.fulfill()
|
||||
|
||||
case .Failure:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: TestObjectObserver
|
||||
|
||||
class TestObjectObserver: ObjectObserver {
|
||||
|
||||
typealias ObjectEntityType = TestEntity1
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<TestEntity1>, willUpdateObject object: TestEntity1) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"objectMonitor:willUpdateObject:",
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<TestEntity1>, didUpdateObject object: TestEntity1, changedPersistentKeys: Set<KeyPath>) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"objectMonitor:didUpdateObject:changedPersistentKeys:",
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object,
|
||||
"changedPersistentKeys": changedPersistentKeys
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<TestEntity1>, didDeleteObject object: TestEntity1) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
"objectMonitor:didDeleteObject:",
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
187
CoreStoreTests/OrderByTests.swift
Normal file
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// OrderByTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - OrderByTests
|
||||
|
||||
final class OrderByTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatOrderByClauses_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let orderBy = OrderBy()
|
||||
XCTAssertEqual(orderBy, OrderBy([] as [NSSortDescriptor]))
|
||||
XCTAssertNotEqual(orderBy, OrderBy(NSSortDescriptor(key: "key", ascending: false)))
|
||||
XCTAssertTrue(orderBy.sortDescriptors.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let sortDescriptor = NSSortDescriptor(key: "key1", ascending: true)
|
||||
let orderBy = OrderBy(sortDescriptor)
|
||||
XCTAssertEqual(orderBy, OrderBy(sortDescriptor))
|
||||
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1")))
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key2")))
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Descending("key1")))
|
||||
XCTAssertNotEqual(orderBy, OrderBy(NSSortDescriptor(key: "key1", ascending: false)))
|
||||
XCTAssertEqual(orderBy, OrderBy([sortDescriptor]))
|
||||
XCTAssertEqual(orderBy.sortDescriptors, [sortDescriptor])
|
||||
}
|
||||
do {
|
||||
|
||||
let sortDescriptors = [
|
||||
NSSortDescriptor(key: "key1", ascending: true),
|
||||
NSSortDescriptor(key: "key2", ascending: false)
|
||||
]
|
||||
let orderBy = OrderBy(sortDescriptors)
|
||||
XCTAssertEqual(orderBy, OrderBy(sortDescriptors))
|
||||
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
|
||||
XCTAssertNotEqual(
|
||||
orderBy,
|
||||
OrderBy(
|
||||
[
|
||||
NSSortDescriptor(key: "key1", ascending: false),
|
||||
NSSortDescriptor(key: "key2", ascending: false)
|
||||
]
|
||||
)
|
||||
)
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Ascending("key2")))
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key3")))
|
||||
XCTAssertEqual(orderBy.sortDescriptors, sortDescriptors)
|
||||
}
|
||||
do {
|
||||
|
||||
let orderBy = OrderBy(.Ascending("key1"))
|
||||
let sortDescriptor = NSSortDescriptor(key: "key1", ascending: true)
|
||||
XCTAssertEqual(orderBy, OrderBy(sortDescriptor))
|
||||
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1")))
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Descending("key1")))
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key2")))
|
||||
XCTAssertEqual(orderBy, OrderBy([sortDescriptor]))
|
||||
XCTAssertEqual(orderBy.sortDescriptors, [sortDescriptor])
|
||||
}
|
||||
do {
|
||||
|
||||
let orderBy = OrderBy(.Ascending("key1"), .Descending("key2"))
|
||||
let sortDescriptors = [
|
||||
NSSortDescriptor(key: "key1", ascending: true),
|
||||
NSSortDescriptor(key: "key2", ascending: false)
|
||||
]
|
||||
XCTAssertEqual(orderBy, OrderBy(sortDescriptors))
|
||||
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
|
||||
XCTAssertNotEqual(
|
||||
orderBy,
|
||||
OrderBy(
|
||||
[
|
||||
NSSortDescriptor(key: "key1", ascending: false),
|
||||
NSSortDescriptor(key: "key2", ascending: false)
|
||||
]
|
||||
)
|
||||
)
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Ascending("key2")))
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key3")))
|
||||
XCTAssertEqual(orderBy.sortDescriptors, sortDescriptors)
|
||||
}
|
||||
do {
|
||||
|
||||
let sortKeys: [SortKey] = [.Ascending("key1"), .Descending("key2")]
|
||||
let orderBy = OrderBy(sortKeys)
|
||||
let sortDescriptors = [
|
||||
NSSortDescriptor(key: "key1", ascending: true),
|
||||
NSSortDescriptor(key: "key2", ascending: false)
|
||||
]
|
||||
XCTAssertEqual(orderBy, OrderBy(sortDescriptors))
|
||||
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
|
||||
XCTAssertNotEqual(
|
||||
orderBy,
|
||||
OrderBy(
|
||||
[
|
||||
NSSortDescriptor(key: "key1", ascending: false),
|
||||
NSSortDescriptor(key: "key2", ascending: false)
|
||||
]
|
||||
)
|
||||
)
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Ascending("key2")))
|
||||
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key3")))
|
||||
XCTAssertEqual(orderBy.sortDescriptors, sortDescriptors)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatOrderByClauseOperations_ComputeCorrectly() {
|
||||
|
||||
let orderBy1 = OrderBy(.Ascending("key1"))
|
||||
let orderBy2 = OrderBy(.Descending("key2"))
|
||||
let orderBy3 = OrderBy(.Ascending("key3"))
|
||||
|
||||
do {
|
||||
|
||||
let plusOrderBy = orderBy1 + orderBy2 + orderBy3
|
||||
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2"), .Ascending("key3")))
|
||||
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1")) + OrderBy(.Descending("key2"), .Ascending("key3")))
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy1 + orderBy3 + orderBy2)
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy1 + orderBy3)
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy3 + orderBy1)
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy3 + orderBy1 + orderBy2)
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy3 + orderBy2 + orderBy1)
|
||||
XCTAssertEqual(plusOrderBy.sortDescriptors, orderBy1.sortDescriptors + orderBy2.sortDescriptors + orderBy3.sortDescriptors)
|
||||
}
|
||||
do {
|
||||
|
||||
var plusOrderBy = orderBy1
|
||||
plusOrderBy += orderBy2
|
||||
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
|
||||
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1")) + OrderBy(.Descending("key2")))
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy1)
|
||||
XCTAssertEqual(plusOrderBy.sortDescriptors, orderBy1.sortDescriptors + orderBy2.sortDescriptors)
|
||||
|
||||
plusOrderBy += orderBy3
|
||||
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2"), .Ascending("key3")))
|
||||
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2")) + OrderBy(.Ascending("key3")))
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy1 + orderBy3 + orderBy2)
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy1 + orderBy3)
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy3 + orderBy1)
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy3 + orderBy1 + orderBy2)
|
||||
XCTAssertNotEqual(plusOrderBy, orderBy3 + orderBy2 + orderBy1)
|
||||
XCTAssertEqual(plusOrderBy.sortDescriptors, orderBy1.sortDescriptors + orderBy2.sortDescriptors + orderBy3.sortDescriptors)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatOrderByClauses_ApplyToFetchRequestsCorrectly() {
|
||||
|
||||
let orderBy = OrderBy(.Ascending("key"))
|
||||
let request = NSFetchRequest()
|
||||
orderBy.applyToFetchRequest(request)
|
||||
XCTAssertNotNil(request.sortDescriptors)
|
||||
XCTAssertEqual(request.sortDescriptors ?? [], orderBy.sortDescriptors)
|
||||
}
|
||||
}
|
||||
1278
CoreStoreTests/QueryTests.swift
Normal file
57
CoreStoreTests/SectionByTests.swift
Normal file
@@ -0,0 +1,57 @@
|
||||
//
|
||||
// SectionByTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
#if os(iOS) || os(watchOS) || os(tvOS)
|
||||
|
||||
//MARK: - SectionByTests
|
||||
|
||||
final class SectionByTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatSectionByClauses_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let sectionBy = SectionBy("key")
|
||||
XCTAssertEqual(sectionBy.sectionKeyPath, "key")
|
||||
XCTAssertEqual(sectionBy.sectionIndexTransformer(sectionName: "key"), "key")
|
||||
}
|
||||
do {
|
||||
|
||||
let sectionBy = SectionBy("key") { $0.flatMap { "\($0):suffix" } }
|
||||
XCTAssertEqual(sectionBy.sectionKeyPath, "key")
|
||||
XCTAssertEqual(sectionBy.sectionIndexTransformer(sectionName: "key"), "key:suffix")
|
||||
XCTAssertNil(sectionBy.sectionIndexTransformer(sectionName: nil))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
420
CoreStoreTests/SelectTests.swift
Normal file
@@ -0,0 +1,420 @@
|
||||
//
|
||||
// SelectTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - SelectTests
|
||||
|
||||
final class SelectTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatAttributeSelectTerms_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let term: SelectTerm = "attribute"
|
||||
XCTAssertEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Attribute(let key):
|
||||
XCTAssertEqual(key, "attribute")
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Attribute("attribute")
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Attribute(let key):
|
||||
XCTAssertEqual(key, "attribute")
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatAverageSelectTerms_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Average("attribute")
|
||||
XCTAssertEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "average:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "average(attribute)")
|
||||
XCTAssertTrue(nativeType == .DecimalAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Average("attribute", As: "alias")
|
||||
XCTAssertEqual(term, SelectTerm.Average("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute", As: "alias2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "average:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "alias")
|
||||
XCTAssertTrue(nativeType == .DecimalAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatCountSelectTerms_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Count("attribute")
|
||||
XCTAssertEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "count:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "count(attribute)")
|
||||
XCTAssertTrue(nativeType == .Integer64AttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Count("attribute", As: "alias")
|
||||
XCTAssertEqual(term, SelectTerm.Count("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute", As: "alias2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "count:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "alias")
|
||||
XCTAssertTrue(nativeType == .Integer64AttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatMaximumSelectTerms_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Maximum("attribute")
|
||||
XCTAssertEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "max:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "max(attribute)")
|
||||
XCTAssertTrue(nativeType == .UndefinedAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Maximum("attribute", As: "alias")
|
||||
XCTAssertEqual(term, SelectTerm.Maximum("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute", As: "alias2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "max:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "alias")
|
||||
XCTAssertTrue(nativeType == .UndefinedAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatMinimumSelectTerms_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Minimum("attribute")
|
||||
XCTAssertEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "min:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "min(attribute)")
|
||||
XCTAssertTrue(nativeType == .UndefinedAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Minimum("attribute", As: "alias")
|
||||
XCTAssertEqual(term, SelectTerm.Minimum("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute", As: "alias2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "min:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "alias")
|
||||
XCTAssertTrue(nativeType == .UndefinedAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatSumSelectTerms_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Sum("attribute")
|
||||
XCTAssertEqual(term, SelectTerm.Sum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "sum:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "sum(attribute)")
|
||||
XCTAssertTrue(nativeType == .DecimalAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let term = SelectTerm.Sum("attribute", As: "alias")
|
||||
XCTAssertEqual(term, SelectTerm.Sum("attribute", As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute", As: "alias2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
switch term {
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
XCTAssertEqual(function, "sum:")
|
||||
XCTAssertEqual(keyPath, "attribute")
|
||||
XCTAssertEqual(alias, "alias")
|
||||
XCTAssertTrue(nativeType == .DecimalAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatObjectIDSelectTerms_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let term = SelectTerm.ObjectID()
|
||||
XCTAssertEqual(term, SelectTerm.ObjectID())
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID(As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
switch term {
|
||||
|
||||
case ._Identity(let alias, let nativeType):
|
||||
XCTAssertEqual(alias, "objectID")
|
||||
XCTAssertTrue(nativeType == .ObjectIDAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let term = SelectTerm.ObjectID(As: "alias")
|
||||
XCTAssertEqual(term, SelectTerm.ObjectID(As: "alias"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID(As: "alias2"))
|
||||
XCTAssertNotEqual(term, SelectTerm.ObjectID())
|
||||
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
|
||||
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
|
||||
switch term {
|
||||
|
||||
case ._Identity(let alias, let nativeType):
|
||||
XCTAssertEqual(alias, "alias")
|
||||
XCTAssertTrue(nativeType == .ObjectIDAttributeType)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatSelectClauses_ConfigureCorrectly() {
|
||||
|
||||
let term1 = SelectTerm.Attribute("attribute1")
|
||||
let term2 = SelectTerm.Attribute("attribute2")
|
||||
let term3 = SelectTerm.Attribute("attribute3")
|
||||
do {
|
||||
|
||||
let select = Select<Int>(term1, term2, term3)
|
||||
XCTAssertEqual(select.selectTerms, [term1, term2, term3])
|
||||
XCTAssertNotEqual(select.selectTerms, [term1, term3, term2])
|
||||
XCTAssertNotEqual(select.selectTerms, [term2, term1, term3])
|
||||
XCTAssertNotEqual(select.selectTerms, [term2, term3, term1])
|
||||
XCTAssertNotEqual(select.selectTerms, [term3, term1, term2])
|
||||
XCTAssertNotEqual(select.selectTerms, [term3, term2, term1])
|
||||
}
|
||||
do {
|
||||
|
||||
let select = Select<Int>([term1, term2, term3])
|
||||
XCTAssertEqual(select.selectTerms, [term1, term2, term3])
|
||||
XCTAssertNotEqual(select.selectTerms, [term1, term3, term2])
|
||||
XCTAssertNotEqual(select.selectTerms, [term2, term1, term3])
|
||||
XCTAssertNotEqual(select.selectTerms, [term2, term3, term1])
|
||||
XCTAssertNotEqual(select.selectTerms, [term3, term1, term2])
|
||||
XCTAssertNotEqual(select.selectTerms, [term3, term2, term1])
|
||||
}
|
||||
}
|
||||
}
|
||||
253
CoreStoreTests/SetupTests.swift
Normal file
@@ -0,0 +1,253 @@
|
||||
//
|
||||
// SetupTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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.
|
||||
//
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - SetupTests
|
||||
|
||||
class SetupTests: BaseTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDataStacks_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let model = NSManagedObjectModel.mergedModelFromBundles([NSBundle(forClass: self.dynamicType)])!
|
||||
|
||||
let stack = DataStack(model: model, migrationChain: nil)
|
||||
XCTAssertEqual(stack.coordinator.managedObjectModel, model)
|
||||
XCTAssertEqual(stack.rootSavingContext.persistentStoreCoordinator, stack.coordinator)
|
||||
XCTAssertNil(stack.rootSavingContext.parentContext)
|
||||
XCTAssertEqual(stack.mainContext.parentContext, stack.rootSavingContext)
|
||||
XCTAssertEqual(stack.model, model)
|
||||
XCTAssertTrue(stack.migrationChain.valid)
|
||||
XCTAssertTrue(stack.migrationChain.empty)
|
||||
XCTAssertTrue(stack.migrationChain.rootVersions.isEmpty)
|
||||
XCTAssertTrue(stack.migrationChain.leafVersions.isEmpty)
|
||||
|
||||
CoreStore.defaultStack = stack
|
||||
XCTAssertEqual(CoreStore.defaultStack, stack)
|
||||
}
|
||||
do {
|
||||
|
||||
let migrationChain: MigrationChain = ["version1", "version2", "version3"]
|
||||
|
||||
let stack = self.expectLogger([.LogWarning]) {
|
||||
|
||||
DataStack(
|
||||
modelName: "Model",
|
||||
bundle: NSBundle(forClass: self.dynamicType),
|
||||
migrationChain: migrationChain
|
||||
)
|
||||
}
|
||||
XCTAssertEqual(stack.modelVersion, "Model")
|
||||
XCTAssertEqual(stack.migrationChain, migrationChain)
|
||||
|
||||
CoreStore.defaultStack = stack
|
||||
XCTAssertEqual(CoreStore.defaultStack, stack)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatInMemoryStores_SetupCorrectly() {
|
||||
|
||||
let stack = DataStack(
|
||||
modelName: "Model",
|
||||
bundle: NSBundle(forClass: self.dynamicType)
|
||||
)
|
||||
do {
|
||||
|
||||
let inMemoryStore = InMemoryStore()
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(inMemoryStore)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
let persistentStore = stack.persistentStoreForStorage(inMemoryStore)
|
||||
XCTAssertNotNil(persistentStore)
|
||||
}
|
||||
do {
|
||||
|
||||
let inMemoryStore = InMemoryStore(
|
||||
configuration: "Config1"
|
||||
)
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(inMemoryStore)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
let persistentStore = stack.persistentStoreForStorage(inMemoryStore)
|
||||
XCTAssertNotNil(persistentStore)
|
||||
}
|
||||
do {
|
||||
|
||||
let inMemoryStore = InMemoryStore(
|
||||
configuration: "Config2"
|
||||
)
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(inMemoryStore)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
let persistentStore = stack.persistentStoreForStorage(inMemoryStore)
|
||||
XCTAssertNotNil(persistentStore)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatSQLiteStores_SetupCorrectly() {
|
||||
|
||||
let stack = DataStack(
|
||||
modelName: "Model",
|
||||
bundle: NSBundle(forClass: self.dynamicType)
|
||||
)
|
||||
do {
|
||||
|
||||
let sqliteStore = SQLiteStore()
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(sqliteStore)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
|
||||
XCTAssertNotNil(persistentStore)
|
||||
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
|
||||
}
|
||||
do {
|
||||
|
||||
let sqliteStore = SQLiteStore(
|
||||
fileName: "ConfigStore1.sqlite",
|
||||
configuration: "Config1",
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(sqliteStore)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
|
||||
XCTAssertNotNil(persistentStore)
|
||||
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
|
||||
}
|
||||
do {
|
||||
|
||||
let sqliteStore = SQLiteStore(
|
||||
fileName: "ConfigStore2.sqlite",
|
||||
configuration: "Config2",
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(sqliteStore)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
|
||||
XCTAssertNotNil(persistentStore)
|
||||
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatLegacySQLiteStores_SetupCorrectly() {
|
||||
|
||||
let stack = DataStack(
|
||||
modelName: "Model",
|
||||
bundle: NSBundle(forClass: self.dynamicType)
|
||||
)
|
||||
do {
|
||||
|
||||
let sqliteStore = SQLiteStore()
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(sqliteStore)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
|
||||
XCTAssertNotNil(persistentStore)
|
||||
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
|
||||
}
|
||||
do {
|
||||
|
||||
let sqliteStore = SQLiteStore(
|
||||
fileName: "ConfigStore1.sqlite",
|
||||
configuration: "Config1",
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(sqliteStore)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
|
||||
XCTAssertNotNil(persistentStore)
|
||||
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
|
||||
}
|
||||
do {
|
||||
|
||||
let sqliteStore = SQLiteStore(
|
||||
fileName: "ConfigStore2.sqlite",
|
||||
configuration: "Config2",
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(sqliteStore)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
|
||||
XCTAssertNotNil(persistentStore)
|
||||
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
|
||||
}
|
||||
}
|
||||
}
|
||||
218
CoreStoreTests/StorageInterfaceTests.swift
Normal file
@@ -0,0 +1,218 @@
|
||||
//
|
||||
// StorageInterfaceTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - StorageInterfaceTests
|
||||
|
||||
final class StorageInterfaceTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDefaultInMemoryStores_ConfigureCorrectly() {
|
||||
|
||||
let store = InMemoryStore()
|
||||
XCTAssertEqual(store.dynamicType.storeType, NSInMemoryStoreType)
|
||||
XCTAssertNil(store.configuration)
|
||||
XCTAssertNil(store.storeOptions)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatCustomInMemoryStores_ConfigureCorrectly() {
|
||||
|
||||
let store = InMemoryStore(configuration: "config1")
|
||||
XCTAssertEqual(store.dynamicType.storeType, NSInMemoryStoreType)
|
||||
XCTAssertEqual(store.configuration, "config1")
|
||||
XCTAssertNil(store.storeOptions)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatSQLiteStoreDefaultDirectories_AreCorrect() {
|
||||
|
||||
#if os(tvOS)
|
||||
let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
|
||||
#else
|
||||
let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
|
||||
#endif
|
||||
|
||||
let defaultSystemDirectory = NSFileManager
|
||||
.defaultManager()
|
||||
.URLsForDirectory(systemDirectorySearchPath, inDomains: .UserDomainMask).first!
|
||||
|
||||
let defaultRootDirectory = defaultSystemDirectory.URLByAppendingPathComponent(
|
||||
NSBundle.mainBundle().bundleIdentifier ?? "com.CoreStore.DataStack",
|
||||
isDirectory: true
|
||||
)
|
||||
let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
|
||||
|
||||
let defaultFileURL = defaultRootDirectory
|
||||
.URLByAppendingPathComponent(applicationName, isDirectory: false)
|
||||
.URLByAppendingPathExtension("sqlite")
|
||||
|
||||
XCTAssertEqual(SQLiteStore.defaultRootDirectory, defaultRootDirectory)
|
||||
XCTAssertEqual(SQLiteStore.defaultFileURL, defaultFileURL)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDefaultSQLiteStores_ConfigureCorrectly() {
|
||||
|
||||
let store = SQLiteStore()
|
||||
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
|
||||
XCTAssertNil(store.configuration)
|
||||
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
|
||||
|
||||
XCTAssertEqual(store.fileURL, SQLiteStore.defaultFileURL)
|
||||
XCTAssertEqual(store.mappingModelBundles, NSBundle.allBundles())
|
||||
XCTAssertEqual(store.localStorageOptions, [.None])
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFileURLSQLiteStores_ConfigureCorrectly() {
|
||||
|
||||
let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
|
||||
.URLByAppendingPathComponent(NSUUID().UUIDString, isDirectory: false)
|
||||
.URLByAppendingPathExtension("db")
|
||||
let bundles = [NSBundle(forClass: self.dynamicType)]
|
||||
|
||||
let store = SQLiteStore(
|
||||
fileURL: fileURL,
|
||||
configuration: "config1",
|
||||
mappingModelBundles: bundles,
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
|
||||
XCTAssertEqual(store.configuration, "config1")
|
||||
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
|
||||
|
||||
XCTAssertEqual(store.fileURL, fileURL)
|
||||
XCTAssertEqual(store.mappingModelBundles, bundles)
|
||||
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFileNameSQLiteStores_ConfigureCorrectly() {
|
||||
|
||||
let fileName = NSUUID().UUIDString + ".db"
|
||||
let bundles = [NSBundle(forClass: self.dynamicType)]
|
||||
|
||||
let store = SQLiteStore(
|
||||
fileName: fileName,
|
||||
configuration: "config1",
|
||||
mappingModelBundles: bundles,
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
|
||||
XCTAssertEqual(store.configuration, "config1")
|
||||
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
|
||||
|
||||
XCTAssertEqual(store.fileURL.URLByDeletingLastPathComponent, SQLiteStore.defaultRootDirectory)
|
||||
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
|
||||
XCTAssertEqual(store.mappingModelBundles, bundles)
|
||||
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatLegacySQLiteStoreDefaultDirectories_AreCorrect() {
|
||||
|
||||
#if os(tvOS)
|
||||
let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
|
||||
#else
|
||||
let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
|
||||
#endif
|
||||
|
||||
let legacyDefaultRootDirectory = NSFileManager.defaultManager().URLsForDirectory(
|
||||
systemDirectorySearchPath,
|
||||
inDomains: .UserDomainMask
|
||||
).first!
|
||||
|
||||
let legacyDefaultFileURL = legacyDefaultRootDirectory
|
||||
.URLByAppendingPathComponent(DataStack.applicationName, isDirectory: false)
|
||||
.URLByAppendingPathExtension("sqlite")
|
||||
|
||||
XCTAssertEqual(LegacySQLiteStore.defaultRootDirectory, legacyDefaultRootDirectory)
|
||||
XCTAssertEqual(LegacySQLiteStore.defaultFileURL, legacyDefaultFileURL)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDefaultLegacySQLiteStores_ConfigureCorrectly() {
|
||||
|
||||
let store = LegacySQLiteStore()
|
||||
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
|
||||
XCTAssertNil(store.configuration)
|
||||
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
|
||||
|
||||
XCTAssertEqual(store.fileURL, LegacySQLiteStore.defaultFileURL)
|
||||
XCTAssertEqual(store.mappingModelBundles, NSBundle.allBundles())
|
||||
XCTAssertEqual(store.localStorageOptions, [.None])
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFileURLLegacySQLiteStores_ConfigureCorrectly() {
|
||||
|
||||
let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
|
||||
.URLByAppendingPathComponent(NSUUID().UUIDString, isDirectory: false)
|
||||
.URLByAppendingPathExtension("db")
|
||||
let bundles = [NSBundle(forClass: self.dynamicType)]
|
||||
|
||||
let store = LegacySQLiteStore(
|
||||
fileURL: fileURL,
|
||||
configuration: "config1",
|
||||
mappingModelBundles: bundles,
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
|
||||
XCTAssertEqual(store.configuration, "config1")
|
||||
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
|
||||
|
||||
XCTAssertEqual(store.fileURL, fileURL)
|
||||
XCTAssertEqual(store.mappingModelBundles, bundles)
|
||||
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFileNameLegacySQLiteStores_ConfigureCorrectly() {
|
||||
|
||||
let fileName = NSUUID().UUIDString + ".db"
|
||||
let bundles = [NSBundle(forClass: self.dynamicType)]
|
||||
|
||||
let store = LegacySQLiteStore(
|
||||
fileName: fileName,
|
||||
configuration: "config1",
|
||||
mappingModelBundles: bundles,
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch
|
||||
)
|
||||
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
|
||||
XCTAssertEqual(store.configuration, "config1")
|
||||
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
|
||||
|
||||
XCTAssertEqual(store.fileURL.URLByDeletingLastPathComponent, LegacySQLiteStore.defaultRootDirectory)
|
||||
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
|
||||
XCTAssertEqual(store.mappingModelBundles, bundles)
|
||||
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
|
||||
}
|
||||
}
|
||||
@@ -32,4 +32,8 @@ class TestEntity1: NSManagedObject {
|
||||
@NSManaged var testString: String?
|
||||
@NSManaged var testNumber: NSNumber?
|
||||
@NSManaged var testDate: NSDate?
|
||||
@NSManaged var testBoolean: NSNumber?
|
||||
@NSManaged var testDecimal: NSDecimalNumber?
|
||||
@NSManaged var testData: NSData?
|
||||
@NSManaged var testNil: String?
|
||||
}
|
||||
@@ -32,6 +32,8 @@ class TestEntity2: NSManagedObject {
|
||||
@NSManaged var testString: String?
|
||||
@NSManaged var testNumber: NSNumber?
|
||||
@NSManaged var testDate: NSDate?
|
||||
|
||||
var testProperty: NSNumber?
|
||||
@NSManaged var testBoolean: NSNumber?
|
||||
@NSManaged var testDecimal: NSDecimalNumber?
|
||||
@NSManaged var testData: NSData?
|
||||
@NSManaged var testNil: String?
|
||||
}
|
||||
958
CoreStoreTests/TransactionTests.swift
Normal file
@@ -0,0 +1,958 @@
|
||||
//
|
||||
// TransactionTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
import GCDKit
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - TransactionTests
|
||||
|
||||
final class TransactionTests: BaseTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatSynchronousTransactions_CanPerformCRUDs() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
let testDate = NSDate()
|
||||
do {
|
||||
|
||||
let createExpectation = self.expectationWithDescription("create")
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
createExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1")
|
||||
XCTAssertEqual(object?.testNumber, 100)
|
||||
XCTAssertEqual(object?.testDate, testDate)
|
||||
}
|
||||
do {
|
||||
|
||||
let updateExpectation = self.expectationWithDescription("update")
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.fetchOne(From(TestEntity1)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = NSDate.distantFuture()
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
updateExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1_edit")
|
||||
XCTAssertEqual(object?.testNumber, 200)
|
||||
XCTAssertEqual(object?.testDate, NSDate.distantFuture())
|
||||
}
|
||||
do {
|
||||
|
||||
let deleteExpectation = self.expectationWithDescription("delete")
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let object = transaction.fetchOne(From(TestEntity1))
|
||||
transaction.delete(object)
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
deleteExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 0)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNil(object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatSynchronousTransactions_CanPerformCRUDsInCorrectConfiguration() {
|
||||
|
||||
self.prepareStack(configurations: [nil, "Config1"]) { (stack) in
|
||||
|
||||
let testDate = NSDate()
|
||||
do {
|
||||
|
||||
let createExpectation = self.expectationWithDescription("create")
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let object = transaction.create(Into<TestEntity1>("Config1"))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
createExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>("Config1")), 1)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>(nil)), 0)
|
||||
|
||||
let object = stack.fetchOne(From<TestEntity1>("Config1"))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1")
|
||||
XCTAssertEqual(object?.testNumber, 100)
|
||||
XCTAssertEqual(object?.testDate, testDate)
|
||||
}
|
||||
do {
|
||||
|
||||
let updateExpectation = self.expectationWithDescription("update")
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.fetchOne(From<TestEntity1>("Config1")) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = NSDate.distantFuture()
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
updateExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>("Config1")), 1)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>(nil)), 0)
|
||||
|
||||
let object = stack.fetchOne(From<TestEntity1>("Config1"))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1_edit")
|
||||
XCTAssertEqual(object?.testNumber, 200)
|
||||
XCTAssertEqual(object?.testDate, NSDate.distantFuture())
|
||||
}
|
||||
do {
|
||||
|
||||
let deleteExpectation = self.expectationWithDescription("delete")
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let object = transaction.fetchOne(From<TestEntity1>("Config1"))
|
||||
transaction.delete(object)
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
deleteExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>("Config1")), 0)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>(nil)), 0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatSynchronousTransactions_CanDiscardUncommittedChanges() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
do {
|
||||
|
||||
let createDiscardExpectation = self.expectationWithDescription("create-discard")
|
||||
let loggerExpectations = self.prepareLoggerExpectations([.LogWarning])
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = NSDate()
|
||||
|
||||
createDiscardExpectation.fulfill()
|
||||
self.expectLogger(loggerExpectations)
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 0)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNil(object)
|
||||
}
|
||||
let testDate = NSDate()
|
||||
do {
|
||||
|
||||
let createExpectation = self.expectationWithDescription("create")
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(true):
|
||||
createExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
}
|
||||
do {
|
||||
|
||||
let updateDiscardExpectation = self.expectationWithDescription("update-discard")
|
||||
let loggerExpectations = self.prepareLoggerExpectations([.LogWarning])
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.fetchOne(From(TestEntity1)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = NSDate.distantFuture()
|
||||
|
||||
updateDiscardExpectation.fulfill()
|
||||
self.expectLogger(loggerExpectations)
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1")
|
||||
XCTAssertEqual(object?.testNumber, 100)
|
||||
XCTAssertEqual(object?.testDate, testDate)
|
||||
}
|
||||
do {
|
||||
|
||||
let deleteDiscardExpectation = self.expectationWithDescription("delete-discard")
|
||||
let loggerExpectations = self.prepareLoggerExpectations([.LogWarning])
|
||||
stack.beginSynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.fetchOne(From(TestEntity1)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
transaction.delete(object)
|
||||
|
||||
deleteDiscardExpectation.fulfill()
|
||||
self.expectLogger(loggerExpectations)
|
||||
}
|
||||
self.checkExpectationsImmediately()
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1")
|
||||
XCTAssertEqual(object?.testNumber, 100)
|
||||
XCTAssertEqual(object?.testDate, testDate)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatAsynchronousTransactions_CanPerformCRUDs() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
let testDate = NSDate()
|
||||
do {
|
||||
|
||||
let createExpectation = self.expectationWithDescription("create")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1")
|
||||
XCTAssertEqual(object?.testNumber, 100)
|
||||
XCTAssertEqual(object?.testDate, testDate)
|
||||
createExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let updateExpectation = self.expectationWithDescription("update")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.fetchOne(From(TestEntity1)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = NSDate.distantFuture()
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1_edit")
|
||||
XCTAssertEqual(object?.testNumber, 200)
|
||||
XCTAssertEqual(object?.testDate, NSDate.distantFuture())
|
||||
updateExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let deleteExpectation = self.expectationWithDescription("delete")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
let object = transaction.fetchOne(From(TestEntity1))
|
||||
transaction.delete(object)
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 0)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNil(object)
|
||||
deleteExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatAsynchronousTransactions_CanPerformCRUDsInCorrectConfiguration() {
|
||||
|
||||
self.prepareStack(configurations: [nil, "Config1"]) { (stack) in
|
||||
|
||||
let testDate = NSDate()
|
||||
do {
|
||||
|
||||
let createExpectation = self.expectationWithDescription("create")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
let object = transaction.create(Into<TestEntity1>("Config1"))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>("Config1")), 1)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>(nil)), 0)
|
||||
|
||||
let object = stack.fetchOne(From<TestEntity1>("Config1"))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1")
|
||||
XCTAssertEqual(object?.testNumber, 100)
|
||||
XCTAssertEqual(object?.testDate, testDate)
|
||||
createExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let updateExpectation = self.expectationWithDescription("update")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.fetchOne(From<TestEntity1>("Config1")) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = NSDate.distantFuture()
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>("Config1")), 1)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>(nil)), 0)
|
||||
|
||||
let object = stack.fetchOne(From<TestEntity1>("Config1"))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1_edit")
|
||||
XCTAssertEqual(object?.testNumber, 200)
|
||||
XCTAssertEqual(object?.testDate, NSDate.distantFuture())
|
||||
updateExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let deleteExpectation = self.expectationWithDescription("delete")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
let object = transaction.fetchOne(From<TestEntity1>("Config1"))
|
||||
transaction.delete(object)
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>("Config1")), 0)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>(nil)), 0)
|
||||
|
||||
deleteExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatAsynchronousTransactions_CanDiscardUncommittedChanges() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
do {
|
||||
|
||||
let createDiscardExpectation = self.expectationWithDescription("create-discard")
|
||||
let loggerExpectations = self.prepareLoggerExpectations([.LogWarning])
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = NSDate()
|
||||
|
||||
createDiscardExpectation.fulfill()
|
||||
self.expectLogger(loggerExpectations)
|
||||
}
|
||||
}
|
||||
let testDate = NSDate()
|
||||
do {
|
||||
|
||||
let createExpectation = self.expectationWithDescription("create")
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 0)
|
||||
XCTAssertNil(transaction.fetchOne(From(TestEntity1)))
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
transaction.commit { (result) in
|
||||
|
||||
switch result {
|
||||
|
||||
case .Success(true):
|
||||
createExpectation.fulfill()
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let updateDiscardExpectation = self.expectationWithDescription("update-discard")
|
||||
let loggerExpectations = self.prepareLoggerExpectations([.LogWarning])
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
guard let object = transaction.fetchOne(From(TestEntity1)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = NSDate.distantFuture()
|
||||
|
||||
updateDiscardExpectation.fulfill()
|
||||
self.expectLogger(loggerExpectations)
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let deleteDiscardExpectation = self.expectationWithDescription("delete-discard")
|
||||
let loggerExpectations = self.prepareLoggerExpectations([.LogWarning])
|
||||
stack.beginAsynchronous { (transaction) in
|
||||
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
guard let object = transaction.fetchOne(From(TestEntity1)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object.testString, "string1")
|
||||
XCTAssertEqual(object.testNumber, 100)
|
||||
XCTAssertEqual(object.testDate, testDate)
|
||||
|
||||
transaction.delete(object)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1")
|
||||
XCTAssertEqual(object?.testNumber, 100)
|
||||
XCTAssertEqual(object?.testDate, testDate)
|
||||
deleteDiscardExpectation.fulfill()
|
||||
}
|
||||
self.expectLogger(loggerExpectations)
|
||||
}
|
||||
}
|
||||
}
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatUnsafeTransactions_CanPerformCRUDs() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
let transaction = stack.beginUnsafe()
|
||||
|
||||
let testDate = NSDate()
|
||||
do {
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1")
|
||||
XCTAssertEqual(object?.testNumber, 100)
|
||||
XCTAssertEqual(object?.testDate, testDate)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
guard let object = transaction.fetchOne(From(TestEntity1)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = NSDate.distantFuture()
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
|
||||
let object = stack.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1_edit")
|
||||
XCTAssertEqual(object?.testNumber, 200)
|
||||
XCTAssertEqual(object?.testDate, NSDate.distantFuture())
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let object = transaction.fetchOne(From(TestEntity1))
|
||||
transaction.delete(object)
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 0)
|
||||
XCTAssertNil(stack.fetchOne(From(TestEntity1)))
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatUnsafeTransactions_CanPerformCRUDsInCorrectConfiguration() {
|
||||
|
||||
self.prepareStack(configurations: [nil, "Config1"]) { (stack) in
|
||||
|
||||
let transaction = stack.beginUnsafe()
|
||||
|
||||
let testDate = NSDate()
|
||||
do {
|
||||
|
||||
let object = transaction.create(Into<TestEntity1>("Config1"))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>("Config1")), 1)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>(nil)), 0)
|
||||
|
||||
let object = stack.fetchOne(From<TestEntity1>("Config1"))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1")
|
||||
XCTAssertEqual(object?.testNumber, 100)
|
||||
XCTAssertEqual(object?.testDate, testDate)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
guard let object = transaction.fetchOne(From<TestEntity1>("Config1")) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = NSDate.distantFuture()
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>("Config1")), 1)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>(nil)), 0)
|
||||
|
||||
let object = stack.fetchOne(From<TestEntity1>("Config1"))
|
||||
XCTAssertNotNil(object)
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object?.testString, "string1_edit")
|
||||
XCTAssertEqual(object?.testNumber, 200)
|
||||
XCTAssertEqual(object?.testDate, NSDate.distantFuture())
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
do {
|
||||
|
||||
let object = transaction.fetchOne(From<TestEntity1>("Config1"))
|
||||
transaction.delete(object)
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges)
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>("Config1")), 0)
|
||||
XCTAssertEqual(stack.fetchCount(From<TestEntity1>(nil)), 0)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatUnsafeTransactions_CanRollbackChanges() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
let transaction = stack.beginUnsafe(supportsUndo: true)
|
||||
do {
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = NSDate()
|
||||
|
||||
transaction.rollback()
|
||||
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 0)
|
||||
XCTAssertNil(transaction.fetchOne(From(TestEntity1)))
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 0)
|
||||
XCTAssertNil(stack.fetchOne(From(TestEntity1)))
|
||||
}
|
||||
|
||||
let testDate = NSDate()
|
||||
do {
|
||||
|
||||
let object = transaction.create(Into(TestEntity1))
|
||||
object.testEntityID = NSNumber(integer: 1)
|
||||
object.testString = "string1"
|
||||
object.testNumber = 100
|
||||
object.testDate = testDate
|
||||
|
||||
switch transaction.commitAndWait() {
|
||||
|
||||
case .Success(true):
|
||||
break
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
guard let object = transaction.fetchOne(From(TestEntity1)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
object.testString = "string1_edit"
|
||||
object.testNumber = 200
|
||||
object.testDate = NSDate.distantFuture()
|
||||
|
||||
transaction.rollback()
|
||||
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
|
||||
if let object = transaction.fetchOne(From(TestEntity1)) {
|
||||
|
||||
XCTAssertEqual(object.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object.testString, "string1")
|
||||
XCTAssertEqual(object.testNumber, 100)
|
||||
XCTAssertEqual(object.testDate, testDate)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
if let object = stack.fetchOne(From(TestEntity1)) {
|
||||
|
||||
XCTAssertEqual(object.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object.testString, "string1")
|
||||
XCTAssertEqual(object.testNumber, 100)
|
||||
XCTAssertEqual(object.testDate, testDate)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
guard let object = transaction.fetchOne(From(TestEntity1)) else {
|
||||
|
||||
XCTFail()
|
||||
return
|
||||
}
|
||||
transaction.delete(object)
|
||||
|
||||
transaction.rollback()
|
||||
|
||||
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
|
||||
if let object = transaction.fetchOne(From(TestEntity1)) {
|
||||
|
||||
XCTAssertEqual(object.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object.testString, "string1")
|
||||
XCTAssertEqual(object.testNumber, 100)
|
||||
XCTAssertEqual(object.testDate, testDate)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
|
||||
XCTAssertEqual(stack.fetchCount(From(TestEntity1)), 1)
|
||||
if let object = stack.fetchOne(From(TestEntity1)) {
|
||||
|
||||
XCTAssertEqual(object.testEntityID, NSNumber(integer: 1))
|
||||
XCTAssertEqual(object.testString, "string1")
|
||||
XCTAssertEqual(object.testNumber, 100)
|
||||
XCTAssertEqual(object.testDate, testDate)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
53
CoreStoreTests/TweakTests.swift
Normal file
@@ -0,0 +1,53 @@
|
||||
//
|
||||
// TweakTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - TweakTests
|
||||
|
||||
final class TweakTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatTweakClauses_ApplyToFetchRequestsCorrectly() {
|
||||
|
||||
let predicate = NSPredicate(format: "%K == %@", "key", "value")
|
||||
let tweak = Tweak {
|
||||
|
||||
$0.fetchOffset = 100
|
||||
$0.fetchLimit = 200
|
||||
$0.predicate = predicate
|
||||
}
|
||||
let request = NSFetchRequest()
|
||||
tweak.applyToFetchRequest(request)
|
||||
XCTAssertEqual(request.fetchOffset, 100)
|
||||
XCTAssertEqual(request.fetchLimit, 200)
|
||||
XCTAssertNotNil(request.predicate)
|
||||
XCTAssertEqual(request.predicate, predicate)
|
||||
}
|
||||
}
|
||||
150
CoreStoreTests/WhereTests.swift
Normal file
@@ -0,0 +1,150 @@
|
||||
//
|
||||
// WhereTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 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 XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - WhereTests
|
||||
|
||||
final class WhereTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatWhereClauses_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let whereClause = Where()
|
||||
XCTAssertEqual(whereClause, Where(true))
|
||||
XCTAssertNotEqual(whereClause, Where(false))
|
||||
XCTAssertEqual(whereClause.predicate, NSPredicate(value: true))
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause = Where(true)
|
||||
XCTAssertEqual(whereClause, Where())
|
||||
XCTAssertNotEqual(whereClause, Where(false))
|
||||
XCTAssertEqual(whereClause.predicate, NSPredicate(value: true))
|
||||
}
|
||||
do {
|
||||
|
||||
let predicate = NSPredicate(format: "%K == %@", "key", "value")
|
||||
let whereClause = Where(predicate)
|
||||
XCTAssertEqual(whereClause, Where(predicate))
|
||||
XCTAssertEqual(whereClause.predicate, predicate)
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause = Where("%K == %@", "key", "value")
|
||||
let predicate = NSPredicate(format: "%K == %@", "key", "value")
|
||||
XCTAssertEqual(whereClause, Where(predicate))
|
||||
XCTAssertEqual(whereClause.predicate, predicate)
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause = Where("%K == %@", argumentArray: ["key", "value"])
|
||||
let predicate = NSPredicate(format: "%K == %@", "key", "value")
|
||||
XCTAssertEqual(whereClause, Where(predicate))
|
||||
XCTAssertEqual(whereClause.predicate, predicate)
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause = Where("key", isEqualTo: "value")
|
||||
let predicate = NSPredicate(format: "%K == %@", "key", "value")
|
||||
XCTAssertEqual(whereClause, Where(predicate))
|
||||
XCTAssertEqual(whereClause.predicate, predicate)
|
||||
}
|
||||
do {
|
||||
|
||||
let whereClause = Where("key", isMemberOf: ["value1", "value2", "value3"])
|
||||
let predicate = NSPredicate(format: "%K IN %@", "key", ["value1", "value2", "value3"])
|
||||
XCTAssertEqual(whereClause, Where(predicate))
|
||||
XCTAssertEqual(whereClause.predicate, predicate)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatWhereClauseOperations_ComputeCorrectly() {
|
||||
|
||||
let whereClause1 = Where("key1", isEqualTo: "value1")
|
||||
let whereClause2 = Where("key2", isEqualTo: "value2")
|
||||
let whereClause3 = Where("key3", isEqualTo: "value3")
|
||||
|
||||
do {
|
||||
|
||||
let notWhere = !whereClause1
|
||||
let notPredicate = NSCompoundPredicate(
|
||||
type: .NotPredicateType,
|
||||
subpredicates: [whereClause1.predicate]
|
||||
)
|
||||
XCTAssertEqual(notWhere.predicate, notPredicate)
|
||||
XCTAssertEqual(notWhere, !whereClause1)
|
||||
}
|
||||
do {
|
||||
|
||||
let andWhere = whereClause1 && whereClause2 && whereClause3
|
||||
let andPredicate = NSCompoundPredicate(
|
||||
type: .AndPredicateType,
|
||||
subpredicates: [
|
||||
NSCompoundPredicate(
|
||||
type: .AndPredicateType,
|
||||
subpredicates: [whereClause1.predicate, whereClause2.predicate]
|
||||
),
|
||||
whereClause3.predicate
|
||||
]
|
||||
)
|
||||
XCTAssertEqual(andWhere.predicate, andPredicate)
|
||||
XCTAssertEqual(andWhere, whereClause1 && whereClause2 && whereClause3)
|
||||
}
|
||||
do {
|
||||
|
||||
let orWhere = whereClause1 || whereClause2 || whereClause3
|
||||
let orPredicate = NSCompoundPredicate(
|
||||
type: .OrPredicateType,
|
||||
subpredicates: [
|
||||
NSCompoundPredicate(
|
||||
type: .OrPredicateType,
|
||||
subpredicates: [whereClause1.predicate, whereClause2.predicate]
|
||||
),
|
||||
whereClause3.predicate
|
||||
]
|
||||
)
|
||||
XCTAssertEqual(orWhere.predicate, orPredicate)
|
||||
XCTAssertEqual(orWhere, whereClause1 || whereClause2 || whereClause3)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatWhereClauses_ApplyToFetchRequestsCorrectly() {
|
||||
|
||||
let whereClause = Where("key", isEqualTo: "value")
|
||||
let request = NSFetchRequest()
|
||||
whereClause.applyToFetchRequest(request)
|
||||
XCTAssertNotNil(request.predicate)
|
||||
XCTAssertEqual(request.predicate, whereClause.predicate)
|
||||
}
|
||||
}
|
||||
@@ -25,6 +25,27 @@
|
||||
|
||||
import PackageDescription
|
||||
|
||||
let targets: [Target]
|
||||
#if os(iOS)
|
||||
targets = [Target(name: "CoreStore iOS")]
|
||||
#elseif os(OSX)
|
||||
targets = [Target(name: "CoreStore OSX")]
|
||||
#elseif os(watchOS)
|
||||
targets = [Target(name: "CoreStore watchOS")]
|
||||
#elseif os(tvOS)
|
||||
targets = [Target(name: "CoreStore tvOS")]
|
||||
#else
|
||||
targets = []
|
||||
#endif
|
||||
|
||||
let package = Package(
|
||||
name: "CoreStore"
|
||||
name: "CoreStore",
|
||||
targets: targets,
|
||||
dependencies: [
|
||||
.Package(
|
||||
url: "https://github.com/JohnEstropia/GCDKit.git",
|
||||
"1.2.6"
|
||||
)
|
||||
],
|
||||
exclude: ["Carthage", "CoreStoreDemo", "Sources/libA/images"]
|
||||
)
|
||||
|
||||
547
README.md
@@ -6,33 +6,48 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
|
||||
<br />
|
||||
<br />
|
||||
<a href="https://travis-ci.org/JohnEstropia/CoreStore"><img alt="Build Status" src="https://img.shields.io/travis/JohnEstropia/CoreStore/master.svg?style=flat" /></a>
|
||||
<a href="http://cocoadocs.org/docsets/CoreStore"><img alt="Version" src="https://img.shields.io/cocoapods/v/CoreStore.svg?style=flat" /></a>
|
||||
<a href="http://cocoadocs.org/docsets/CoreStore"><img alt="Platform" src="https://img.shields.io/cocoapods/p/CoreStore.svg?style=flat" /></a>
|
||||
<a href="https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE"><img alt="License" src="https://img.shields.io/cocoapods/l/CoreStore.svg?style=flat" /></a>
|
||||
<a href="https://github.com/Carthage/Carthage"><img alt="Carthage compatible" src="https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat" /></a>
|
||||
<br /><br />Dependency managers<br />
|
||||
<a href="https://cocoapods.org/pods/CoreStore"><img alt="Cocoapods compatible" src="https://img.shields.io/cocoapods/v/CoreStore.svg?style=flat&label=Cocoapods" /></a>
|
||||
<a href="https://github.com/Carthage/Carthage"><img alt="Carthage compatible" src="https://img.shields.io/badge/Carthage-compatible-16a085.svg?style=flat" /></a>
|
||||
<a href="https://swiftpkgs.ng.bluemix.net/package/JohnEstropia/CoreStore"><img alt="Swift Package Manager compatible" src="https://img.shields.io/badge/Swift_Package_Manager-compatible-orange.svg?style=flat" /></a>
|
||||
<br /><br />Contact<br />
|
||||
<a href="http://swift-corestore-slack.herokuapp.com/"><img alt="Join us on Slack!" src="http://swift-corestore-slack.herokuapp.com/badge.svg" /></a>
|
||||
<a href="https://twitter.com/JohnEstropia"><img alt="Reach me on Twitter!" src="https://img.shields.io/badge/twitter-%40JohnEstropia-3498db.svg" /></a>
|
||||
<br />
|
||||
</p>
|
||||
* Swift 2.2 (Xcode 7.3)
|
||||
* iOS 7+ / OSX 10.10+ / watchOS 2.0+ / tvOS 9.0+
|
||||
* iOS 7+ / macOS 10.10+ / watchOS 2.0+ / tvOS 9.0+
|
||||
* **New in CoreStore 2.0:** Objective-C support! All CoreStore types now have their corresponding Objective-C "bridging classes". Perfect for projects transitioning from Objective-C to Swift!
|
||||
|
||||
Upgrading from CoreStore 1.x to 2.x? Check out the [new features](#new-in-corestore-20) and make sure to read the [Migration guide](#upgrading-from-1xx-to-2xx).
|
||||
|
||||
|
||||
## Why use CoreStore?
|
||||
I was [MagicalRecord](https://github.com/magicalpanda/MagicalRecord)'s heavy user back then, but I took the promising opportunity to create CoreStore when Swift came around. Part of the inspiration is to address the trend of developers [avoiding](http://inessential.com/2010/02/26/on_switching_away_from_core_data) [Core Data](http://bsktapp.com/blog/why-is-realm-great-and-why-are-we-not-using-it/) [for](https://www.quora.com/Why-would-you-use-Realm-over-Core-Data) [perplexing](http://sebastiandobrincu.com/blog/5-reasons-why-you-should-choose-realm-over-coredata) [reasons](https://medium.com/the-way-north/ditching-core-data-865c1bb5564c#.a5h8ou6ri).
|
||||
|
||||
## What CoreStore does better:
|
||||
CoreStore was (and is) heavily shaped by real-world needs of developing data-dependent apps. It enforces safe and convenient Core Data usage while letting you take advantage of the industry's encouraged best practices. And with Core Data and Swift continuously being improved by Apple, CoreStore will just get better and better!
|
||||
|
||||
- **Heavily supports multiple persistent stores per data stack**, just the way *.xcdatamodeld* files are designed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need.
|
||||
- **Progressive Migrations!** Just tell the data stack the sequence of model versions and CoreStore will automatically use progressive migrations if needed on stores added to that stack.
|
||||
- Ability to **plug-in your own logging framework**
|
||||
- Gets around a limitation with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you are **free to name entities and their class names independently**.
|
||||
- Provides type-safe, easy to configure **observers to replace `NSFetchedResultsController` and KVO**
|
||||
- Exposes **API not just for fetching, but also for querying aggregates and property values**
|
||||
- Makes it hard to fall into common concurrency mistakes. All `NSManagedObjectContext` tasks are encapsulated into **safer, higher-level abstractions** without sacrificing flexibility and customizability.
|
||||
- Exposes clean and convenient API designed around **Swift’s code elegance and type safety**.
|
||||
- **Documentation!** No magic here; all public classes, functions, properties, etc. have detailed Apple Docs. This README also introduces a lot of concepts and explains a lot of CoreStore's behavior.
|
||||
- **Efficient importing utilities!**
|
||||
|
||||
**[Vote for the next feature!](http://goo.gl/RIiHMP)**
|
||||
### Features
|
||||
- **Heavy support for multiple persistent stores per data stack.** CoreStore lets you manage separate stores in a single `DataStack`, just the way *.xcdatamodeld* configurations are designed to. CoreStore will also manage one stack by default, but you can create and manage as many as you need. *(See [Setting up](#setting-up))*
|
||||
- **Progressive migrations.** No need to think how to migrate from all previous model versions to your latest model. Just tell the `DataStack` the sequence of version strings (`MigrationChain`s) and CoreStore will automatically use progressive migrations when needed. *(See [Migrations](#migrations))*
|
||||
- **Plug-in your own logging framework.** Although a default logger is built-in, all logging, asserting, and error reporting can be funneled to `CoreStoreLogger` protocol implementations. *(See [Logging and error reporting](#logging-and-error-reporting))*
|
||||
- **Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign different names for the entities and their class names.
|
||||
- **Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, `ListMonitor`s and `ObjectMonitor`s can have multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
|
||||
- **Clean fetching and querying API.** Fetching objects is easy, but querying for raw aggregates (min, max, etc.) and raw property values is now just as convenient. *(See [Fetching and querying](#fetching-and-querying))*
|
||||
- **Safer concurrency architecture.** CoreStore makes it hard to fall into common concurrency mistakes. The main `NSManagedObjectContext` is strictly read-only, while all updates are done through serial *transactions*. *(See [Saving and processing transactions](#saving-and-processing-transactions))*
|
||||
- **Efficient importing utilities.** Map your entities once with their corresponding import source (JSON for example), and importing from *transactions* becomes elegant. Uniquing is also done with an efficient find-and-replace algorithm. *(See [Importing data](#importing-data))*
|
||||
- **Tight design around Swift’s code elegance and type safety.** CoreStore fully utilizes Swift's community-driven language features.
|
||||
- **Full Documentation.** No magic here; all public classes, functions, properties, etc. have detailed *Apple Docs*. This *README* also introduces a lot of concepts and explains a lot of CoreStore's behavior.
|
||||
|
||||
### New in CoreStore 2.0
|
||||
- **Informative (and pretty) logs.** All CoreStore and Core Data-related types now have very informative and pretty print outputs! *(See [Logging and error reporting](#logging-and-error-reporting))*
|
||||
- **Objective-C support!** Is your project transitioning from Objective-C to Swift but still can't quite fully convert some huge classes to Swift yet? CoreStore 2.0 is the answer to the ever-increasing Swift adoption. While still written in pure Swift, all CoreStore types now have their corresponding Objective-C-visible "bridging classes". *(See [Objective-C support](#objective-c-support))*
|
||||
- **iCloud storage (beta) support.** CoreStore now allows creation of iCloud persistent stores, as well as observing of iCloud-related events through the `ICloudStoreObserver`. *(See [iCloud storage](#icloud-storages))*
|
||||
- **More extensive Unit Tests.** Extending CoreStore is now safer without having to worry about breaking old behavior.
|
||||
|
||||
*Have ideas that may benefit other Core Data users? [Feature Request](https://github.com/JohnEstropia/CoreStore/issues)s are welcome!*
|
||||
|
||||
## Contents
|
||||
|
||||
@@ -40,6 +55,9 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
|
||||
- [Architecture](#architecture)
|
||||
- CoreStore Tutorials (All of these have demos in the **CoreStoreDemo** app project!)
|
||||
- [Setting up](#setting-up)
|
||||
- [In-memory store](#in-memory-store)
|
||||
- [Local store](#local-store)
|
||||
- [iCloud store](#icloud-store)
|
||||
- [Migrations](#migrations)
|
||||
- [Progressive migrations](#progressive-migrations)
|
||||
- [Forecasting migrations](#forecasting-migrations)
|
||||
@@ -62,14 +80,15 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
|
||||
- [Querying](#querying)
|
||||
- [`Select<T>` clause](#selectt-clause)
|
||||
- [`GroupBy` clause](#groupby-clause)
|
||||
- [Logging and error handling](#logging-and-error-handling)
|
||||
- [Observing changes and notifications](#observing-changes-and-notifications) (unavailable on OSX)
|
||||
- [Logging and error reporting](#logging-and-error-reporting)
|
||||
- [Observing changes and notifications](#observing-changes-and-notifications) (unavailable on macOS)
|
||||
- [Observe a single object](#observe-a-single-object)
|
||||
- [Observe a list of objects](#observe-a-list-of-objects)
|
||||
- [Objective-C support](#objective-c-support)
|
||||
- [Roadmap](#roadmap)
|
||||
- [Installation](#installation)
|
||||
- [Changesets](#changesets)
|
||||
- [Upgrading from v0.2.0 to 1.0.0](#upgrading-from-v020-to-100)
|
||||
- [Upgrading from 1.x.x to 2.x.x](#upgrading-from-1xx-to-2xx)
|
||||
- [Contact](#contact)
|
||||
- [Who uses CoreStore?](#who-uses-corestore)
|
||||
- [License](#license)
|
||||
@@ -88,8 +107,8 @@ CoreStore.defaultStack = DataStack(
|
||||
|
||||
Adding a store:
|
||||
```swift
|
||||
try CoreStore.addSQLiteStore(
|
||||
fileName: "MyStore.sqlite",
|
||||
CoreStore.addStorage(
|
||||
SQLiteStore(fileName: "MyStore.sqlite"),
|
||||
completion: { (result) -> Void in
|
||||
// ...
|
||||
}
|
||||
@@ -112,10 +131,12 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
}
|
||||
```
|
||||
|
||||
Fetching objects:
|
||||
Fetching objects (simple):
|
||||
```swift
|
||||
let people = CoreStore.fetchAll(From(MyPersonEntity))
|
||||
```
|
||||
|
||||
Fetching objects (complex):
|
||||
```swift
|
||||
let people = CoreStore.fetchAll(
|
||||
From(MyPersonEntity),
|
||||
@@ -135,7 +156,7 @@ let maxAge = CoreStore.queryValue(
|
||||
)
|
||||
```
|
||||
|
||||
But really, there's a reason I wrote this huge README. Read up on the details!
|
||||
But really, there's a reason I wrote this huge *README*. Read up on the details!
|
||||
|
||||
Check out the **CoreStoreDemo** app project for sample codes as well!
|
||||
|
||||
@@ -149,16 +170,16 @@ If you are already familiar with the inner workings of CoreData, here is a mappi
|
||||
| *Core Data* | *CoreStore* |
|
||||
| --- | --- |
|
||||
| `NSManagedObjectModel` / `NSPersistentStoreCoordinator`<br />(.xcdatamodeld file) | `DataStack` |
|
||||
| `NSPersistentStore`<br />("Configuration"s in the .xcdatamodeld file) | `DataStack` configuration<br />(multiple sqlite / in-memory stores per stack) |
|
||||
| `NSPersistentStore`<br />("Configuration"s in the .xcdatamodeld file) | `StorageInterface` implementations<br />(`InMemoryStore`, `SQLiteStore`, `ICloudStore`) |
|
||||
| `NSManagedObjectContext` | `BaseDataTransaction` subclasses<br />(`SynchronousDataTransaction`, `AsynchronousDataTransaction`, `UnsafeDataTransaction`) |
|
||||
|
||||
Popular libraries [RestKit](https://github.com/RestKit/RestKit) and [MagicalRecord](https://github.com/magicalpanda/MagicalRecord) set up their `NSManagedObjectContext`s this way:
|
||||
A lot of Core Data wrapper libraries set up their `NSManagedObjectContext`s this way:
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/3029684/6734049/40579660-ce99-11e4-9d38-829877386afb.png" alt="nested contexts" height=271 />
|
||||
<img src="https://cloud.githubusercontent.com/assets/3029684/16707160/984ef25c-4600-11e6-869f-8db7d2c63668.png" alt="nested contexts" height=380 />
|
||||
|
||||
Nesting context saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But as <a href="http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/">Florian Kugler's investigation</a> found out, merging contexts is still by far faster than saving nested contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context, and only allows changes to be made within *transactions* on the child context:
|
||||
Nesting saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But <a href="http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/">in reality</a>, merging contexts is still by far faster than saving contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context, and only allows changes to be made within *transactions* on the child context:
|
||||
|
||||
<img src="https://cloud.githubusercontent.com/assets/3029684/6734050/4078b642-ce99-11e4-95ea-c0c1d24fbe80.png" alt="nested contexts and merge hybrid" height=212 />
|
||||
<img src="https://cloud.githubusercontent.com/assets/3029684/16707161/9adeb962-4600-11e6-8bc8-4ec85764dba4.png" alt="nested contexts and merge hybrid" height=292 />
|
||||
|
||||
This allows for a butter-smooth main thread, while still taking advantage of safe nested contexts.
|
||||
|
||||
@@ -167,53 +188,35 @@ This allows for a butter-smooth main thread, while still taking advantage of saf
|
||||
## Setting up
|
||||
The simplest way to initialize CoreStore is to add a default store to the default stack:
|
||||
```swift
|
||||
do {
|
||||
try CoreStore.addSQLiteStoreAndWait()
|
||||
}
|
||||
catch {
|
||||
// ...
|
||||
}
|
||||
try CoreStore.addStorageAndWait()
|
||||
```
|
||||
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 SQLite store in the *"Application Support"* directory (or the *"Caches"* directory on tvOS) with the file name *"[App bundle name].sqlite"*
|
||||
- Adds an `SQLiteStore` in the *"Application Support/<bundle id>"* directory (or the *"Caches/<bundle id>"* directory on tvOS) 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:
|
||||
For most cases, this configuration is enough 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
|
||||
migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"] // model versions for progressive migrations
|
||||
)
|
||||
|
||||
do {
|
||||
// creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file
|
||||
let persistentStore = try dataStack.addInMemoryStoreAndWait(configuration: "Config1") // persistentStore is an NSPersistentStore instance
|
||||
print("Successfully created an in-memory store: \(persistentStore)"
|
||||
}
|
||||
catch {
|
||||
print("Failed creating an in-memory store with error: \(error as NSError)"
|
||||
}
|
||||
|
||||
do {
|
||||
try dataStack.addSQLiteStore(
|
||||
let migrationProgress = dataStack.addStorage(
|
||||
SQLiteStore(
|
||||
fileURL: sqliteFileURL, // set the target file URL for the sqlite file
|
||||
configuration: "Config2", // use entities from the "Config2" configuration in the .xcdatamodeld file
|
||||
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)"
|
||||
}
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch // if migration paths cannot be resolved, recreate the sqlite file
|
||||
),
|
||||
completion: { (result) -> Void in
|
||||
switch result {
|
||||
case .Success(let storage):
|
||||
print("Successfully added sqlite store: \(storage)"
|
||||
case .Failure(let error):
|
||||
print("Failed adding sqlite store with error: \(error)"
|
||||
}
|
||||
)
|
||||
}
|
||||
catch {
|
||||
print("Failed adding sqlite store with error: \(error as NSError)"
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on
|
||||
```
|
||||
@@ -224,11 +227,11 @@ CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier
|
||||
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")
|
||||
let dataStack = DataStack(modelName: "MyModel") // keep reference to the stack
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
do {
|
||||
try self.dataStack.addSQLiteStoreAndWait()
|
||||
try self.dataStack.addStorageAndWait(SQLiteStore)
|
||||
}
|
||||
catch { // ...
|
||||
}
|
||||
@@ -242,10 +245,11 @@ class MyViewController: UIViewController {
|
||||
The difference is when you set the stack as the `CoreStore.defaultStack`, you can call the stack's methods directly from `CoreStore` itself:
|
||||
```swift
|
||||
class MyViewController: UIViewController {
|
||||
// elsewhere: CoreStore.defaultStack = DataStack(modelName: "MyModel")
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
do {
|
||||
try CoreStore.addSQLiteStoreAndWait()
|
||||
try CoreStore.addStorageAndWait(SQLiteStore)
|
||||
}
|
||||
catch { // ...
|
||||
}
|
||||
@@ -257,41 +261,159 @@ class MyViewController: UIViewController {
|
||||
}
|
||||
```
|
||||
|
||||
Notice that in our previous examples, `addStorageAndWait(_:)` and `addStorage(_:completion:)` both accept either `InMemoryStore`, `SQLiteStore`, or `ICloudStore`. These implement the `StorageInterface` protocol.
|
||||
|
||||
## Migrations
|
||||
So far we have only seen `addSQLiteStoreAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method blocks so it should not do long tasks such as store migrations (in fact CoreStore won't even attempt to, and any model mismatch will be reported as an error). If migrations are expected, the asynchronous variant `addSQLiteStore(... completion:)` method should be used instead:
|
||||
### In-memory store
|
||||
The most basic `StorageInterface` concrete type is the `InMemoryStore`, which just stores objects in memory. Since `InMemoryStore`s always start with a fresh empty data, they do not need any migration information.
|
||||
```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)")
|
||||
}
|
||||
}
|
||||
try CoreStore.addStorageAndWait(
|
||||
InMemoryStore(
|
||||
configuration: "Config2" // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
|
||||
)
|
||||
}
|
||||
catch {
|
||||
print("Failed adding sqlite store with error: \(error as NSError)"
|
||||
)
|
||||
```
|
||||
`InMemoryStore`s also implement the `DefaultInitializableStore` sugar protocol which tells CoreStore that this store can initialize without any arguments (`init()`). This lets us provide just the type instead of an instance:
|
||||
```swift
|
||||
try CoreStore.addStorageAndWait(InMemoryStore)
|
||||
```
|
||||
|
||||
### Local Store
|
||||
The most common `StorageInterface` you will probably use is the `SQLiteStore`, which saves data in a local SQLite file.
|
||||
```swift
|
||||
let migrationProgress = CoreStore.addStorage(
|
||||
SQLiteStore(
|
||||
fileName: "MyStore.sqlite",
|
||||
configuration: "Config2", // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
|
||||
mappingModelBundles: [NSBundle.mainBundle()], // optional. The bundles that contain required .xcmappingmodel files, if any
|
||||
localStorageOptions: .RecreateStoreOnModelMismatch // optional. Provides settings that tells the DataStack how to setup the persistent store
|
||||
),
|
||||
completion: { /* ... */ }
|
||||
)
|
||||
```
|
||||
Refer to the *SQLiteStore.swift* source documentation for detailed explanations for each of the default values.
|
||||
|
||||
CoreStore can decide the default values for these properties, so `SQLiteStore`s also implement the `DefaultInitializableStore` sugar protocol which lets us write:
|
||||
```swift
|
||||
try CoreStore.addStorageAndWait(SQLiteStore)
|
||||
```
|
||||
or
|
||||
```swift
|
||||
let migrationProgress = CoreStore.addStorage(SQLiteStore.self, completion: { /* ... */ })
|
||||
```
|
||||
|
||||
The file-related properties above are actually requirements of another protocol that `SQLiteStore` implements, the `LocalStorage` protocol:
|
||||
```swift
|
||||
public protocol LocalStorage: StorageInterface {
|
||||
var fileURL: NSURL { get }
|
||||
var mappingModelBundles: [NSBundle] { get }
|
||||
var localStorageOptions: LocalStorageOptions { get }
|
||||
func storeOptionsForOptions(options: LocalStorageOptions) -> [String: AnyObject]?
|
||||
func eraseStorageAndWait(soureModel soureModel: NSManagedObjectModel) throws
|
||||
}
|
||||
```
|
||||
The `completion` block reports a `PersistentStoreResult` that indicates success or failure.
|
||||
If you have custom `NSIncrementalStore` or `NSAtomicStore` subclasses, you can implement this protocol and use it similarly to `SQLiteStore`.
|
||||
|
||||
`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.
|
||||
### iCloud Store
|
||||
> The iCloud Store is currently in beta. Please use with caution. If you have any concerns please do send me a message on [Twitter](https://twitter.com/JohnEstropia) or on the [CoreStore Slack Team](http://swift-corestore-slack.herokuapp.com/)
|
||||
|
||||
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*:
|
||||
As a counterpart to `LocalStorage`, the `CloudStorage` protocol abstracts stores managed in the cloud. CoreStore currently provides the concrete class `ICloudStore`. Unlike `InMemoryStore` and `SQLiteStore` though, the `ICloudStore`'s initializer may return `nil` if the iCloud container could not be located or if iCloud is not available on the device:
|
||||
```swift
|
||||
progress?.setProgressHandler { [weak self] (progress) -> Void in
|
||||
guard let storage = ICloudStore(
|
||||
ubiquitousContentName: "MyAppCloudData", // the name of the store in iCloud
|
||||
ubiquitousContentTransactionLogsSubdirectory: "logs/config1", // optional. Subdirectory path for the transaction logs
|
||||
ubiquitousContainerID: "iCloud.com.mycompany.myapp.containername", // optional. The container if your app has multiple ubiquity container identifiers in its entitlements
|
||||
ubiquitousPeerToken: "9614d658014f4151a95d8048fb717cf0", // optional. A per-application salt to allow multiple apps on the same device to share a Core Data store integrated with iCloud
|
||||
configuration: "Config1", // optional. Use entities from the "Config1" configuration in the .xcdatamodeld file
|
||||
cloudStorageOptions: .RecreateLocalStoreOnModelMismatch // optional. Provides settings that tells the DataStack how to setup the persistent store
|
||||
) else {
|
||||
// The iCloud container could not be located or if iCloud is not available on the device.
|
||||
// Handle appropriately
|
||||
return
|
||||
}
|
||||
CoreStore.addStorage(,
|
||||
storage,
|
||||
completion: { result in
|
||||
switch result {
|
||||
case .Success(let storage): // ...
|
||||
case .Failure(let error): // ...
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
If your app is using iCloud stores, you may want to be notified of particular iCloud events. The `ICloudStoreObserver` functions are all optional, so you may implement only the ones your app is interested in:
|
||||
```swift
|
||||
public protocol ICloudStoreObserver: class {
|
||||
func iCloudStoreWillFinishUbiquitousStoreInitialImport(storage storage: ICloudStore, dataStack: DataStack)
|
||||
func iCloudStoreDidFinishUbiquitousStoreInitialImport(storage storage: ICloudStore, dataStack: DataStack)
|
||||
func iCloudStoreWillAddAccount(storage storage: ICloudStore, dataStack: DataStack)
|
||||
func iCloudStoreDidAddAccount(storage storage: ICloudStore, dataStack: DataStack)
|
||||
func iCloudStoreWillRemoveAccount(storage storage: ICloudStore, dataStack: DataStack)
|
||||
func iCloudStoreDidRemoveAccount(storage storage: ICloudStore, dataStack: DataStack)
|
||||
func iCloudStoreWillRemoveContent(storage storage: ICloudStore, dataStack: DataStack)
|
||||
func iCloudStoreDidRemoveContent(storage storage: ICloudStore, dataStack: DataStack)
|
||||
}
|
||||
```
|
||||
To register your `ICloudStoreObserver`, call `addObserver(_:)` on the `ICloudStore` instance:
|
||||
|
||||
```swift
|
||||
guard let storage = ICloudStore(/* ... */) else {
|
||||
return
|
||||
}
|
||||
storage.addObserver(self) // assuming self implements ICloudStoreObserver
|
||||
CoreStore.addStorage(,
|
||||
storage,
|
||||
completion: { result in
|
||||
switch result {
|
||||
case .Success(let storage): // ... You may also call storage.addObserver(_:) here
|
||||
case .Failure(let error): // ...
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
The `ICloudStore` only keeps weak references of the registered observers. You may call `removeObserver(_:)` for precise deregistration, but `ICloudStore` automatically removes deallocated observers.
|
||||
|
||||
|
||||
## Migrations
|
||||
We have seen `addStorageAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests though, this method blocks so it should not do long tasks such as store migrations. In fact CoreStore will only attempt a synchronous **lightweight** migration if you explicitly provide the `.AllowSynchronousLightweightMigration` option:
|
||||
```swift
|
||||
try dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileURL: sqliteFileURL,
|
||||
localStorageOptions: .AllowSynchronousLightweightMigration
|
||||
)
|
||||
}
|
||||
```
|
||||
if you do so, any model mismatch will be thrown as an error.
|
||||
|
||||
In general though, if migrations are expected the asynchronous variant `addStorage(_:completion:)` method is recommended instead:
|
||||
```swift
|
||||
let migrationProgress: NSProgress? = try dataStack.addStorage(
|
||||
SQLiteStore(
|
||||
fileName: "MyStore.sqlite",
|
||||
configuration: "Config2"
|
||||
),
|
||||
completion: { (result) -> Void in
|
||||
switch result {
|
||||
case .Success(let storage):
|
||||
print("Successfully added sqlite store: \(storage)")
|
||||
case .Failure(let error):
|
||||
print("Failed adding sqlite store with error: \(error)")
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
The `completion` block reports a `SetupResult` that indicates success or failure.
|
||||
|
||||
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
|
||||
migrationProgress?.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.
|
||||
This closure is executed on the main thread so UIKit and AppKit calls can be done safely.
|
||||
|
||||
|
||||
### Progressive migrations
|
||||
@@ -304,7 +426,7 @@ let dataStack = DataStack(migrationChain:
|
||||
```
|
||||
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:
|
||||
For more complex, non-linear 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",
|
||||
@@ -332,28 +454,28 @@ One important thing to remember is that **if a `MigrationChain` is specified, th
|
||||
|
||||
### 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(...)`:
|
||||
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 `requiredMigrationsForStorage(_:)` method you can use to inspect a persistent store before you actually call `addStorageAndWait(_:)` or `addStorage(_:completion:)`:
|
||||
```swift
|
||||
do {
|
||||
let migrationTypes: [MigrationType] = CoreStore.requiredMigrationsForSQLiteStore(fileName: "MyStore.sqlite")
|
||||
let storage = SQLiteStorage(fileName: "MyStore.sqlite")
|
||||
let migrationTypes: [MigrationType] = try CoreStore.requiredMigrationsForStorage(storage)
|
||||
if migrationTypes.count > 1
|
||||
|| (migrationTypes.filter { $0.isHeavyweightMigration }.count) > 0 {
|
||||
// ... Show special waiting screen
|
||||
// ... will migrate more than once. Show special waiting screen
|
||||
}
|
||||
else if migrationTypes.count > 0 {
|
||||
// ... Show simple activity indicator
|
||||
// ... will migrate just once. Show simple activity indicator
|
||||
}
|
||||
else {
|
||||
// ... Do nothing
|
||||
}
|
||||
|
||||
CoreStore.addSQLiteStore(/* ... */)
|
||||
CoreStore.addStorage(storage, completion: { /* ... */ })
|
||||
}
|
||||
catch {
|
||||
// ...
|
||||
// ... either inspection of the store failed, or if no mapping model was found/inferred
|
||||
}
|
||||
```
|
||||
`requiredMigrationsForSQLiteStore(...)` returns an array of `MigrationType`s, where each item in the array may be either of the following values:
|
||||
`requiredMigrationsForStorage(_:)` 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)
|
||||
@@ -1003,24 +1125,17 @@ this returns dictionaries that shows the count for each `"age"`:
|
||||
]
|
||||
```
|
||||
|
||||
## Logging and error handling
|
||||
## Logging and error reporting
|
||||
One unfortunate thing when using some third-party libraries is that they usually pollute the console with their own logging mechanisms. CoreStore provides its own default logging class, but you can plug-in your own favorite logger by implementing the `CoreStoreLogger` protocol.
|
||||
```swift
|
||||
final class MyLogger: CoreStoreLogger {
|
||||
func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
// pass to your logger
|
||||
}
|
||||
|
||||
func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
// pass to your logger
|
||||
}
|
||||
|
||||
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
// pass to your logger
|
||||
}
|
||||
public protocol CoreStoreLogger {
|
||||
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
func log(error error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
}
|
||||
```
|
||||
Then pass an instance of this class to `CoreStore`:
|
||||
Implement this protocol with your custom class then pass the instance to `CoreStore.logger`:
|
||||
```swift
|
||||
CoreStore.logger = MyLogger()
|
||||
```
|
||||
@@ -1028,7 +1143,23 @@ Doing so channels all logging calls to your logger.
|
||||
|
||||
Note that to keep the call stack information intact, all calls to these methods are **NOT** thread-managed. Therefore you have to make sure that your logger is thread-safe or you may otherwise have to dispatch your logging implementation to a serial queue.
|
||||
|
||||
## Observing changes and notifications (unavailable on OSX)
|
||||
Take special care when implementing `CoreStoreLogger`'s `assert(...)` and `abort(...)` functions:
|
||||
- `assert(...)`: The behavior between `DEBUG` and release builds, or `-O` and `-Onone`, are all left to the implementers' responsibility. CoreStore calls `CoreStoreLogger.assert(...)` only for invalid but usually recoverable errors (for example, early validation failures that may cause an error thrown and handled somewhere else)
|
||||
- `abort(...)`: This method is *the* last-chance for your app to *synchronously* log a fatal error within CoreStore. The app will be terminated right after this function is called (CoreStore calls `fatalError()` internally)
|
||||
|
||||
Starting CoreStore 2.0, all CoreStore types now have very useful (and pretty formatted!) `print(...)` outputs.
|
||||
|
||||
A couple of examples, `ListMonitor`:
|
||||
|
||||
<img width="369" alt="screen shot 2016-07-10 at 22 56 44" src="https://cloud.githubusercontent.com/assets/3029684/16713994/ae06e702-46f1-11e6-83a8-dee48b480bab.png" />
|
||||
|
||||
`CoreStoreError.MappingModelNotFoundError`:
|
||||
|
||||
<img width="506" alt="MappingModelNotFoundError" src="https://cloud.githubusercontent.com/assets/3029684/16713962/e021f548-46f0-11e6-8100-f9b5ea6b4a08.png" />
|
||||
|
||||
These are all implemented with `CustomDebugStringConvertible.debugDescription`, so they work with lldb's `po` command as well.
|
||||
|
||||
## Observing changes and notifications (unavailable on macOS)
|
||||
CoreStore provides type-safe wrappers for observing managed objects:
|
||||
|
||||
- `ObjectMonitor`: use to monitor changes to a single `NSManagedObject` instance (instead of Key-Value Observing)
|
||||
@@ -1081,23 +1212,20 @@ Including `ListObserver`, there are 3 observer protocols you can implement depen
|
||||
- `ListObserver`: lets you handle these callback methods:
|
||||
```swift
|
||||
func listMonitorWillChange(monitor: ListMonitor<MyPersonEntity>)
|
||||
|
||||
func listMonitorDidChange(monitor: ListMonitor<MyPersonEntity>)
|
||||
func listMonitorWillRefetch(monitor: ListMonitor<MyPersonEntity>)
|
||||
func listMonitorDidRefetch(monitor: ListMonitor<MyPersonEntity>)
|
||||
```
|
||||
- `ListObjectObserver`: in addition to `ListObserver` methods, also lets you handle object inserts, updates, and deletes:
|
||||
```swift
|
||||
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath)
|
||||
|
||||
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath)
|
||||
|
||||
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath)
|
||||
|
||||
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didMoveObject object: MyPersonEntity, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
|
||||
```
|
||||
- `ListSectionObserver`: in addition to `ListObjectObserver` methods, also lets you handle section inserts and deletes:
|
||||
```swift
|
||||
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
|
||||
|
||||
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
|
||||
```
|
||||
|
||||
@@ -1163,10 +1291,140 @@ let person2 = self.monitor[1, 2]
|
||||
// person1 and person2 are the same object
|
||||
```
|
||||
|
||||
## Objective-C support
|
||||
CoreStore 2.0 was a big move to address the large number of apps starting to convert from Objective-C to Swift. The basic problem is this: The cost of converting all code base to Swift is very big, so most apps are forced to do undergo a *transitional* ObjC-Swift hybrid phase. This used to mean that these apps could not use the Swifty-est libraries out there yet, or that they may have to write their own bridging methods just to make new code usable in their old Objective-C code.
|
||||
|
||||
With 2.0, all CoreStore types are still written in pure Swift, but they now have Objective-C "bridging classes" that are visible to Objective-C code. To show a couple of usage examples:
|
||||
|
||||
<table>
|
||||
<tr><th>Swift</th><th>Objective-C</th></tr>
|
||||
<tr>
|
||||
<td><pre lang=swift>
|
||||
try CoreStore.addStorageAndWait(SQLiteStore)
|
||||
</pre></td>
|
||||
<td><pre lang=objc>
|
||||
NSError *error
|
||||
[CSCoreStore addSQLiteStorageAndWait:[CSSQLiteStore new] error:&error]
|
||||
</pre></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><pre lang=swift>
|
||||
CoreStore.beginAsynchronous { (transaction) in
|
||||
// ...
|
||||
transaction.commit { (result) in
|
||||
switch result {
|
||||
case .Success(let hasChanges): print(hasChanges)
|
||||
case .Failure(let error): print(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
</pre></td>
|
||||
<td><pre lang=objc>
|
||||
[CSCoreStore beginAsynchronous:^(CSAsynchronousDataTransaction *transaction) {
|
||||
// ...
|
||||
[transaction commitWithCompletion:^(CSSaveResult *result) {
|
||||
if (result.isSuccess) {
|
||||
NSLog(@"hasChanges: %d", result.hasChanges);
|
||||
}
|
||||
else if (result.isFailure) {
|
||||
NSLog(@"error: %@", result.error);
|
||||
}
|
||||
}];
|
||||
}];
|
||||
</pre></td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
All of these `CS`-prefixed bridging classes have very similar usage to the existing CoreStore APIs, and ironically *none of them are written in Objective-C*. The secret is all in *CoreStoreBridge.swift*, where we see the signature of these bridging classes, the `CoreStoreObjectiveCType` protocol:
|
||||
```swift
|
||||
public protocol CoreStoreObjectiveCType: class, AnyObject {
|
||||
associatedtype SwiftType
|
||||
var bridgeToSwift: SwiftType { get }
|
||||
init(_ swiftValue: SwiftType)
|
||||
}
|
||||
```
|
||||
Notice that these bridging classes all hold a reference to their corresponding `SwiftType`.
|
||||
|
||||
Conversely, CoreStore original types implement the `CoreStoreSwiftType` protocol:
|
||||
```swift
|
||||
public protocol CoreStoreSwiftType {
|
||||
associatedtype ObjectiveCType
|
||||
var bridgeToObjectiveC: ObjectiveCType { get }
|
||||
}
|
||||
```
|
||||
These two protocols let CoreStore types free to bridge instances between Objective-C and Swift.
|
||||
|
||||
This is very different to the common approach where apps and libraries write Objective-C APIs just to support both Objective-C and Swift. The advantage with CoreStore's approach is that your Swift codebase can already use the purely-Swift API without further changes in the future, but your "hybrid" codebase can still bridge instances back and forth from Objective-C to Swift.
|
||||
|
||||
For example, you may have a new, modern Swift class that holds a `ListMonitor`:
|
||||
```swift
|
||||
class MyViewController: UIViewController {
|
||||
let monitor = CoreStore.monitorList(From(MyEntity), ...)
|
||||
// ...
|
||||
}
|
||||
```
|
||||
Now let's say you have a legacy Objective-C class that previously uses `NSFetchedResultsController`. It's easy to switch from `NSFetchedResultsController` to `CSListMonitor`, but converting the rest of this huge class is impractical. You end up with
|
||||
```objc
|
||||
@interface MYOldViewController: UIViewController
|
||||
@property (nonatomic, readonly, strong) CSListMonitor* monitor;
|
||||
- (instancetype)initWithMonitor:(CSListMonitor *)monitor;
|
||||
@end
|
||||
```
|
||||
When you need to instantiate this class from Swift, you just call `bridgeToObjectiveC`:
|
||||
```swift
|
||||
class MyViewController: UIViewController {
|
||||
let monitor = CoreStore.monitorList(From(MyEntity), ...)
|
||||
func showOldController() {
|
||||
let controller = MYOldViewController(monitor: self.monitor.bridgeToObjectiveC)
|
||||
self.presentViewController(controller, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
```
|
||||
Note that the `CSListMonitor` holds the exact same `ListMonitor` instance, which means that no copies and no extra fetching occur.
|
||||
|
||||
### Objective-C syntax sugars
|
||||
Objective-C tends to be verbose, so some method calls are long and unreadable. For example, fetching looks like this:
|
||||
```objc
|
||||
NSArray<MYPerson *> *objects =
|
||||
[CSCoreStore
|
||||
fetchAllFrom:[[CSFrom alloc] initWithEntityClass:[MYPerson class]]
|
||||
fetchClauses:@[[[CSWhere alloc] initWithFormat:@"%K == %@", @"isHidden", @NO],
|
||||
[[CSOrderBy alloc] initWithSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES],
|
||||
[NSSortDescriptor sortDescriptorWithKey:@"firstName" ascending:YES]]]]];
|
||||
```
|
||||
Although it works, it looks terrible. For this, CoreStore provides *CoreStoreBridge.h* where these Objective-C calls are wrapped in readable, convenient macros and global functions. The call above becomes
|
||||
```objc
|
||||
NSArray<MYPerson *> *objects =
|
||||
[CSCoreStore
|
||||
fetchAllFrom:CSFromClass([MYPerson class])
|
||||
fetchClauses:@[CSWhereFormat(@"%K == %@", @"isHidden", @NO),
|
||||
CSOrderByKeys(CSSortAscending(@"lastName"),
|
||||
CSSortAscending(@"firstName"), nil)]];
|
||||
```
|
||||
That's much shorter now. But we can still do better. Notice that we have strings being used as key paths. The `CSKeyPath(...)` macro gives us compile-time checking so keys that don't exist in a class will generate errors. Our key-safe code now looks like this:
|
||||
```objc
|
||||
NSArray<MYPerson *> *objects =
|
||||
[CSCoreStore
|
||||
fetchAllFrom:CSFromClass([MYPerson class])
|
||||
fetchClauses:@[CSWhereFormat(@"%K == %@", CSKeyPath(MYPerson, isHidden), @NO),
|
||||
CSOrderByKeys(CSSortAscending(CSKeyPath(MYPerson, lastName)),
|
||||
CSSortAscending(CSKeyPath(MYPerson, firstName)), nil)]];
|
||||
```
|
||||
|
||||
To use these syntax sugars, include *CoreStoreBridge.h* in your Objective-C source files. For projects that support iOS 7 (and thus cannot build CoreStore as a module), you will need to add
|
||||
```
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME)
|
||||
```
|
||||
to your target's `GCC_PREPROCESSOR_DEFINITIONS` build setting.
|
||||
|
||||
<img width="797" alt="GCC_PREPROCESSOR_DEFINITIONS" src="https://cloud.githubusercontent.com/assets/3029684/16714547/92497fc4-4701-11e6-81db-6b1a11743cc5.png" />
|
||||
|
||||
|
||||
# Roadmap
|
||||
- Support iCloud stores
|
||||
- CoreSpotlight auto-indexing (experimental)
|
||||
- Built-in "singleton objects" support
|
||||
- Built-in "readonly" stores
|
||||
- CoreSpotlight auto-indexing (experimenting, still some roadblocks ahead)
|
||||
- Synching
|
||||
|
||||
|
||||
# Installation
|
||||
@@ -1175,6 +1433,8 @@ let person2 = self.monitor[1, 2]
|
||||
- Swift 2.2 (Xcode 7.3)
|
||||
- Dependencies:
|
||||
- [GCDKit](https://github.com/JohnEstropia/GCDKit)
|
||||
- Other notes:
|
||||
- The `com.apple.CoreData.ConcurrencyDebug` debug argument should be turned off for the app. CoreStore already guarantees safety for you by making the main context read-only, and by only executing transactions serially.
|
||||
|
||||
### Install with CocoaPods (iOS 7 not supported)
|
||||
```
|
||||
@@ -1185,8 +1445,8 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
|
||||
### Install with Carthage
|
||||
In your `Cartfile`, add
|
||||
```
|
||||
github "JohnEstropia/CoreStore" >= 1.6.0
|
||||
github "JohnEstropia/GCDKit" >= 1.2.2
|
||||
github "JohnEstropia/CoreStore" >= 2.0.0
|
||||
github "JohnEstropia/GCDKit" >= 1.2.5
|
||||
```
|
||||
and run
|
||||
```
|
||||
@@ -1206,20 +1466,41 @@ Drag and drop **CoreStore.xcodeproj** to your project.
|
||||
Add all *.swift* files to your project.
|
||||
|
||||
|
||||
### Objective-C support
|
||||
|
||||
To use the Objective-C syntax sugars, import *CoreStoreBridge.h* in your *.m* source files.
|
||||
|
||||
For projects that support iOS 7 (and thus cannot build CoreStore as a module), you will need to add
|
||||
```
|
||||
SWIFT_OBJC_INTERFACE_HEADER_NAME=$(SWIFT_OBJC_INTERFACE_HEADER_NAME)
|
||||
```
|
||||
to your target's `GCC_PREPROCESSOR_DEFINITIONS` build setting:
|
||||
|
||||
<img width="797" alt="GCC_PREPROCESSOR_DEFINITIONS" src="https://cloud.githubusercontent.com/assets/3029684/16714547/92497fc4-4701-11e6-81db-6b1a11743cc5.png" />
|
||||
|
||||
|
||||
# Changesets
|
||||
### Upgrading from v0.2.0 to 1.0.0
|
||||
- Renamed some classes/protocols to shorter, more relevant, easier to remember names:
|
||||
- `ManagedObjectController` to `ObjectMonitor`
|
||||
- `ManagedObjectObserver` to `ObjectObserver`
|
||||
- `ManagedObjectListController` to `ListMonitor`
|
||||
- `ManagedObjectListChangeObserver` to `ListObserver`
|
||||
- `ManagedObjectListObjectObserver` to `ListObjectObserver`
|
||||
- `ManagedObjectListSectionObserver` to `ListSectionObserver`
|
||||
- `SectionedBy` to `SectionBy` (match tense with `OrderBy` and `GroupBy`)
|
||||
The protocols above had their methods renamed as well, to retain the natural language semantics.
|
||||
- Several methods now `throw` errors insted of returning a result `enum`.
|
||||
- New migration utilities! (README still pending) Check out *DataStack+Migration.swift* and *CoreStore+Migration.swift* for the new methods, as well as *DataStack.swift* for its new initializer.
|
||||
### Upgrading from 1.x.x to 2.x.x
|
||||
**Obsoleted**
|
||||
- `AsynchronousDataTransaction.rollback()` was removed. Undo and rollback functionality are now only allowed on `UnsafeDataTransaction`s
|
||||
- `DetachedDataTransaction` was renamed to `UnsafeDataTransaction`
|
||||
- `beginDetached()` was renamed to `beginUnsafe()`
|
||||
- `PersistentStoreResult` was removed in favor of `SetupResult<T>`
|
||||
- `SynchronousDataTransaction.commit()` was renamed to `SynchronousDataTransaction.commitAndWait()`
|
||||
- `From` initializers that accepted `NSURL`s and `NSPersistentStore` were removed.
|
||||
|
||||
**Deprecated**
|
||||
The following methods are still available, but will be removed in a future update.
|
||||
- `add*Store(...)` method variants. It is strongly recommended to convert to the new API. Refer to [Local store](#local-store)) usage then use `LegacySQLiteStore` instead of `SQLiteStore` to maintain the old default file names and directory values
|
||||
- `addInMemoryStoreAndWait(...)` → `addStorageAndWait(InMemoryStore(...))`
|
||||
- `addSQLiteStoreAndWait(...)` → `addStorageAndWait(LegacySQLiteStore(...))`
|
||||
- `addInMemoryStore(...)` → `addStorage(InMemoryStore(...), ...)`
|
||||
- `addSQLiteStore(...)` → `addStorage(LegacySQLiteStore(...), ...)`
|
||||
- `requiredMigrationsForSQLiteStore(...)` → `requiredMigrationsForStorage(...)`
|
||||
- `upgradeSQLiteStoreIfNeeded(...)` → `upgradeStorageIfNeeded(...)`
|
||||
- The `resetStoreOnModelMismatch: Bool` argument for the methods above are now provided to the `LegacySQLiteStore` and `SQLiteStore` initializers as a `LocalStorageOptions` option set
|
||||
- `NSError` used to have a `coreStoreErrorCode` property that returns `CoreStoreErrorCode` enum, but all CoreStore errors are now guaranteed to be `CoreStoreError` enum type in swift, and `CSError` type on Objective-C.
|
||||
- `CoreStoreLogger.handleError(...)` was deprecated in favor of `CoreStoreLogger.log(error:...)`. `CoreStoreLogger` may also implement `CoreStoreLogger.abort(...)`, which is called just before CoreStore executes `fatalError()` due to critical runtime errors.
|
||||
|
||||
|
||||
# Contact
|
||||
@@ -1227,7 +1508,7 @@ Questions? Suggestions?
|
||||
|
||||
Reach me on Twitter [@JohnEstropia](https://twitter.com/JohnEstropia)
|
||||
|
||||
or tag your Stackoverflow question with **corestore**
|
||||
or join our Slack team at [swift-corestore.slack.com](http://swift-corestore-slack.herokuapp.com/)
|
||||
|
||||
日本語の対応も可能なので是非!
|
||||
|
||||
@@ -1237,5 +1518,5 @@ I'd love to hear about apps using CoreStore. Send me a message and I'll feature
|
||||
|
||||
|
||||
# License
|
||||
CoreStore is released under an MIT license. See the LICENSE file for more information
|
||||
CoreStore is released under an MIT license. See the [LICENSE](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE) file for more information
|
||||
|
||||
|
||||
223
Sources/Convenience/NSFetchedResultsController+Convenience.swift
Normal file
@@ -0,0 +1,223 @@
|
||||
//
|
||||
// NSManagedObject+Convenience.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 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
|
||||
|
||||
|
||||
#if os(iOS) || os(watchOS) || os(tvOS)
|
||||
|
||||
// MARK: - NSFetchedResultsController
|
||||
|
||||
public extension NSFetchedResultsController {
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
|
||||
|
||||
- parameter dataStack: the `DataStack` to observe objects from
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: an `NSFetchedResultsController` that observes a `DataStack`
|
||||
*/
|
||||
@nonobjc
|
||||
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
|
||||
|
||||
return self.createFromContext(
|
||||
dataStack.mainContext,
|
||||
fetchRequest: CoreStoreFetchRequest(),
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
|
||||
|
||||
- parameter dataStack: the `DataStack` to observe objects from
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: an `NSFetchedResultsController` that observes a `DataStack`
|
||||
*/
|
||||
@nonobjc
|
||||
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
|
||||
|
||||
return self.createFromContext(
|
||||
dataStack.mainContext,
|
||||
fetchRequest: CoreStoreFetchRequest(),
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
|
||||
|
||||
- parameter dataStack: the `DataStack` to observe objects from
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: an `NSFetchedResultsController` that observes a `DataStack`
|
||||
*/
|
||||
@nonobjc
|
||||
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
|
||||
|
||||
return self.createFromContext(
|
||||
dataStack.mainContext,
|
||||
fetchRequest: CoreStoreFetchRequest(),
|
||||
from: from,
|
||||
sectionBy: nil,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
|
||||
|
||||
- parameter dataStack: the `DataStack` to observe objects from
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: an `NSFetchedResultsController` that observes a `DataStack`
|
||||
*/
|
||||
@nonobjc
|
||||
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
|
||||
|
||||
return self.createFromContext(
|
||||
dataStack.mainContext,
|
||||
fetchRequest: CoreStoreFetchRequest(),
|
||||
from: from,
|
||||
sectionBy: nil,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
|
||||
|
||||
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction`
|
||||
*/
|
||||
@nonobjc
|
||||
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
|
||||
|
||||
return self.createFromContext(
|
||||
transaction.context,
|
||||
fetchRequest: CoreStoreFetchRequest(),
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
|
||||
|
||||
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction`
|
||||
*/
|
||||
@nonobjc
|
||||
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
|
||||
|
||||
return self.createFromContext(
|
||||
transaction.context,
|
||||
fetchRequest: CoreStoreFetchRequest(),
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
|
||||
|
||||
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction`
|
||||
*/
|
||||
@nonobjc
|
||||
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
|
||||
|
||||
return self.createFromContext(
|
||||
transaction.context,
|
||||
fetchRequest: CoreStoreFetchRequest(),
|
||||
from: from,
|
||||
sectionBy: nil,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
|
||||
|
||||
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
@nonobjc
|
||||
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
|
||||
|
||||
return self.createFromContext(
|
||||
transaction.context,
|
||||
fetchRequest: CoreStoreFetchRequest(),
|
||||
from: from,
|
||||
sectionBy: nil,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc
|
||||
internal static func createFromContext<T: NSManagedObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
|
||||
|
||||
return CoreStoreFetchedResultsController(
|
||||
context: context,
|
||||
fetchRequest: fetchRequest,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
applyFetchClauses: { fetchRequest in
|
||||
|
||||
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
|
||||
CoreStore.assert(
|
||||
fetchRequest.sortDescriptors?.isEmpty == false,
|
||||
"An \(cs_typeName(NSFetchedResultsController)) requires a sort information. Specify from a \(cs_typeName(OrderBy)) clause or any custom \(cs_typeName(FetchClause)) that provides a sort descriptor."
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -37,6 +37,7 @@ public extension NSManagedObject {
|
||||
- parameter KVCKey: the KVC key
|
||||
- returns: the primitive value for the KVC key
|
||||
*/
|
||||
@nonobjc
|
||||
@warn_unused_result
|
||||
public func accessValueForKVCKey(KVCKey: KeyPath) -> AnyObject? {
|
||||
|
||||
@@ -53,6 +54,7 @@ public extension NSManagedObject {
|
||||
- parameter value: the value to set the KVC key with
|
||||
- parameter KVCKey: the KVC key
|
||||
*/
|
||||
@nonobjc
|
||||
public func setValue(value: AnyObject?, forKVCKey KVCKey: KeyPath) {
|
||||
|
||||
self.willChangeValueForKey(KVCKey)
|
||||
@@ -63,6 +65,7 @@ public extension NSManagedObject {
|
||||
/**
|
||||
Re-faults the object to use the latest values from the persistent store
|
||||
*/
|
||||
@nonobjc
|
||||
public func refreshAsFault() {
|
||||
|
||||
self.managedObjectContext?.refreshObject(self, mergeChanges: false)
|
||||
@@ -71,6 +74,7 @@ public extension NSManagedObject {
|
||||
/**
|
||||
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
|
||||
*/
|
||||
@nonobjc
|
||||
public func refreshAndMerge() {
|
||||
|
||||
self.managedObjectContext?.refreshObject(self, mergeChanges: true)
|
||||
@@ -35,8 +35,10 @@ public extension NSProgress {
|
||||
|
||||
/**
|
||||
Sets a closure that the `NSProgress` calls whenever its `fractionCompleted` changes. You can use this instead of setting up KVO.
|
||||
|
||||
- parameter closure: the closure to execute on progress change
|
||||
*/
|
||||
@nonobjc
|
||||
public func setProgressHandler(closure: ((progress: NSProgress) -> Void)?) {
|
||||
|
||||
self.progressObserver.progressHandler = closure
|
||||
@@ -50,18 +52,19 @@ public extension NSProgress {
|
||||
static var progressObserver: Void?
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
private var progressObserver: ProgressObserver {
|
||||
|
||||
get {
|
||||
|
||||
let object: ProgressObserver? = getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
|
||||
let object: ProgressObserver? = cs_getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
|
||||
if let observer = object {
|
||||
|
||||
return observer
|
||||
}
|
||||
|
||||
let observer = ProgressObserver(self)
|
||||
setAssociatedRetainedObject(
|
||||
cs_setAssociatedRetainedObject(
|
||||
observer,
|
||||
forKey: &PropertyKeys.progressObserver,
|
||||
inObject: self
|
||||
@@ -73,7 +76,10 @@ public extension NSProgress {
|
||||
}
|
||||
|
||||
|
||||
@objc private final class ProgressObserver: NSObject {
|
||||
// MARK: - ProgressObserver
|
||||
|
||||
@objc
|
||||
private final class ProgressObserver: NSObject {
|
||||
|
||||
private unowned let progress: NSProgress
|
||||
private var progressHandler: ((progress: NSProgress) -> Void)? {
|
||||
@@ -28,3 +28,5 @@
|
||||
|
||||
FOUNDATION_EXPORT double CoreStoreVersionNumber;
|
||||
FOUNDATION_EXPORT const unsigned char CoreStoreVersionString[];
|
||||
|
||||
#import "CoreStoreBridge.h"
|
||||
@@ -38,8 +38,8 @@ public enum CoreStore {
|
||||
|
||||
/**
|
||||
The default `DataStack` instance to be used. If `defaultStack` is not set before the first time accessed, a default-configured `DataStack` will be created.
|
||||
|
||||
Changing the `defaultStack` is thread safe, but it is recommended to setup `DataStacks` on a common queue (e.g. the main queue).
|
||||
- SeeAlso: `DataStack`
|
||||
- Note: Changing the `defaultStack` is thread safe, but it is recommended to setup `DataStacks` on a common queue (e.g. the main queue).
|
||||
*/
|
||||
public static var defaultStack: DataStack {
|
||||
|
||||
227
Sources/CoreStoreError.swift
Normal file
@@ -0,0 +1,227 @@
|
||||
//
|
||||
// CoreStoreError.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - CoreStoreError
|
||||
|
||||
/**
|
||||
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
|
||||
*/
|
||||
public enum CoreStoreError: ErrorType, Hashable {
|
||||
|
||||
/**
|
||||
A failure occured because of an unknown error.
|
||||
*/
|
||||
case Unknown
|
||||
|
||||
/**
|
||||
The `NSPersistentStore` could not be initialized because another store existed at the specified `NSURL`.
|
||||
*/
|
||||
case DifferentStorageExistsAtURL(existingPersistentStoreURL: NSURL)
|
||||
|
||||
/**
|
||||
An `NSMappingModel` could not be found for a specific source and destination model versions.
|
||||
*/
|
||||
case MappingModelNotFound(localStoreURL: NSURL, targetModel: NSManagedObjectModel, targetModelVersion: String)
|
||||
|
||||
/**
|
||||
Progressive migrations are disabled for a store, but an `NSMappingModel` could not be found for a specific source and destination model versions.
|
||||
*/
|
||||
case ProgressiveMigrationRequired(localStoreURL: NSURL)
|
||||
|
||||
/**
|
||||
An internal SDK call failed with the specified `NSError`.
|
||||
*/
|
||||
case InternalError(NSError: NSError)
|
||||
|
||||
|
||||
// MARK: ErrorType
|
||||
|
||||
public var _domain: String {
|
||||
|
||||
return CoreStoreErrorDomain
|
||||
}
|
||||
|
||||
public var _code: Int {
|
||||
|
||||
switch self {
|
||||
|
||||
case .Unknown:
|
||||
return CoreStoreErrorCode.UnknownError.rawValue
|
||||
|
||||
case .DifferentStorageExistsAtURL:
|
||||
return CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue
|
||||
|
||||
case .MappingModelNotFound:
|
||||
return CoreStoreErrorCode.MappingModelNotFound.rawValue
|
||||
|
||||
case .ProgressiveMigrationRequired:
|
||||
return CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue
|
||||
|
||||
case .InternalError:
|
||||
return CoreStoreErrorCode.InternalError.rawValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
let code = self._code
|
||||
switch self {
|
||||
|
||||
case .Unknown:
|
||||
return code.hashValue
|
||||
|
||||
case .DifferentStorageExistsAtURL(let existingPersistentStoreURL):
|
||||
return code.hashValue ^ existingPersistentStoreURL.hashValue
|
||||
|
||||
case .MappingModelNotFound(let localStoreURL, let targetModel, let targetModelVersion):
|
||||
return code.hashValue ^ localStoreURL.hashValue ^ targetModel.hashValue ^ targetModelVersion.hashValue
|
||||
|
||||
case .ProgressiveMigrationRequired(let localStoreURL):
|
||||
return code.hashValue ^ localStoreURL.hashValue
|
||||
|
||||
case .InternalError(let NSError):
|
||||
return code.hashValue ^ NSError.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(_ error: ErrorType?) {
|
||||
|
||||
self = error.flatMap { $0.bridgeToSwift } ?? .Unknown
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CoreStoreError: Equatable
|
||||
|
||||
@warn_unused_result
|
||||
public func == (lhs: CoreStoreError, rhs: CoreStoreError) -> Bool {
|
||||
|
||||
switch (lhs, rhs) {
|
||||
|
||||
case (.Unknown, .Unknown):
|
||||
return true
|
||||
|
||||
case (.DifferentStorageExistsAtURL(let url1), .DifferentStorageExistsAtURL(let url2)):
|
||||
return url1 == url2
|
||||
|
||||
case (.MappingModelNotFound(let url1, let model1, let version1), .MappingModelNotFound(let url2, let model2, let version2)):
|
||||
return url1 == url2 && model1 == model2 && version1 == version2
|
||||
|
||||
case (.ProgressiveMigrationRequired(let url1), .ProgressiveMigrationRequired(let url2)):
|
||||
return url1 == url2
|
||||
|
||||
case (.InternalError(let NSError1), .InternalError(let NSError2)):
|
||||
return NSError1 == NSError2
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CoreStoreErrorDomain
|
||||
|
||||
/**
|
||||
The `NSError` error domain string for `CSError`.
|
||||
*/
|
||||
@nonobjc
|
||||
public let CoreStoreErrorDomain = "com.corestore.error"
|
||||
|
||||
|
||||
// MARK: - CoreStoreErrorCode
|
||||
|
||||
/**
|
||||
The `NSError` error codes for `CoreStoreErrorDomain`.
|
||||
*/
|
||||
public enum CoreStoreErrorCode: Int {
|
||||
|
||||
/**
|
||||
A failure occured because of an unknown error.
|
||||
*/
|
||||
case UnknownError
|
||||
|
||||
/**
|
||||
The `NSPersistentStore` could note be initialized because another store existed at the specified `NSURL`.
|
||||
*/
|
||||
case DifferentPersistentStoreExistsAtURL
|
||||
|
||||
/**
|
||||
An `NSMappingModel` could not be found for a specific source and destination model versions.
|
||||
*/
|
||||
case MappingModelNotFound
|
||||
|
||||
/**
|
||||
Progressive migrations are disabled for a store, but an `NSMappingModel` could not be found for a specific source and destination model versions.
|
||||
*/
|
||||
case ProgressiveMigrationRequired
|
||||
|
||||
/**
|
||||
An internal SDK call failed with the specified "NSError" userInfo key.
|
||||
*/
|
||||
case InternalError
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSError
|
||||
|
||||
public extension NSError {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal var isCoreDataMigrationError: Bool {
|
||||
|
||||
let code = self.code
|
||||
return (code == NSPersistentStoreIncompatibleVersionHashError
|
||||
|| code == NSMigrationMissingSourceModelError
|
||||
|| code == NSMigrationError)
|
||||
&& self.domain == NSCocoaErrorDomain
|
||||
}
|
||||
|
||||
|
||||
// MARK: Deprecated
|
||||
|
||||
/**
|
||||
Deprecated. Use `CoreStoreError` enum values instead.
|
||||
|
||||
If the error's domain is equal to `CoreStoreErrorDomain`, returns the associated `CoreStoreErrorCode`. For other domains, returns `nil`.
|
||||
*/
|
||||
@available(*, deprecated=2.0.0, message="Use CoreStoreError enum values instead.")
|
||||
public var coreStoreErrorCode: CoreStoreErrorCode? {
|
||||
|
||||
return (self.domain == CoreStoreErrorDomain
|
||||
? CoreStoreErrorCode(rawValue: self.code)
|
||||
: nil)
|
||||
}
|
||||
}
|
||||
@@ -103,12 +103,7 @@ public extension BaseDataTransaction {
|
||||
@warn_unused_result
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchOne(from, fetchClauses)
|
||||
return self.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,9 +118,8 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -139,12 +133,7 @@ public extension BaseDataTransaction {
|
||||
@warn_unused_result
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchAll(from, fetchClauses)
|
||||
return self.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -159,9 +148,8 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -175,12 +163,7 @@ public extension BaseDataTransaction {
|
||||
@warn_unused_result
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchCount(from, fetchClauses)
|
||||
return self.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -195,7 +178,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchCount(from, fetchClauses)
|
||||
@@ -211,12 +194,7 @@ public extension BaseDataTransaction {
|
||||
@warn_unused_result
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectID(from, fetchClauses)
|
||||
return self.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -231,9 +209,8 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -247,12 +224,7 @@ public extension BaseDataTransaction {
|
||||
@warn_unused_result
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectIDs(from, fetchClauses)
|
||||
return self.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -267,9 +239,8 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -284,7 +255,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to delete from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to delete from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.deleteAll(from, deleteClauses)
|
||||
@@ -301,7 +272,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to delete from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to delete from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.deleteAll(from, deleteClauses)
|
||||
@@ -322,7 +293,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryValue(from, selectClause, queryClauses)
|
||||
@@ -343,7 +314,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryValue(from, selectClause, queryClauses)
|
||||
@@ -364,7 +335,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryAttributes(from, selectClause, queryClauses)
|
||||
@@ -385,7 +356,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryAttributes(from, selectClause, queryClauses)
|
||||
@@ -41,64 +41,78 @@ import CoreData
|
||||
*/
|
||||
public struct From<T: NSManagedObject> {
|
||||
|
||||
/**
|
||||
The associated `NSManagedObject` entity class
|
||||
*/
|
||||
public let entityClass: AnyClass
|
||||
|
||||
/**
|
||||
The `NSPersistentStore` configuration names to associate objects from.
|
||||
May contain `String`s to pertain to named configurations, or `nil` to pertain to the default configuration
|
||||
*/
|
||||
public let configurations: [String?]?
|
||||
|
||||
/**
|
||||
Initializes a `From` clause.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From<MyPersonEntity>())
|
||||
```
|
||||
*/
|
||||
public init(){
|
||||
|
||||
self.init(entityClass: T.self)
|
||||
self.init(entityClass: T.self, configurations: nil)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified entity type.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From<MyPersonEntity>())
|
||||
```
|
||||
- parameter entity: the `NSManagedObject` type to be created
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
*/
|
||||
public init(_ entity: T.Type) {
|
||||
|
||||
self.init(entityClass: entity)
|
||||
self.init(entityClass: entity, configurations: nil)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified entity class.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From<MyPersonEntity>())
|
||||
```
|
||||
- parameter entityClass: the `NSManagedObject` class type to be created
|
||||
|
||||
- parameter entityClass: the associated `NSManagedObject` entity class
|
||||
*/
|
||||
public init(_ entityClass: AnyClass) {
|
||||
|
||||
self.init(entityClass: entityClass)
|
||||
CoreStore.assert(
|
||||
entityClass is T.Type,
|
||||
"Attempted to create generic type \(cs_typeName(From<T>)) with entity class \(cs_typeName(entityClass))"
|
||||
)
|
||||
self.init(entityClass: entityClass, configurations: nil)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified configurations.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From<MyPersonEntity>(nil, "Configuration1"))
|
||||
```
|
||||
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
|
||||
*/
|
||||
public init(_ configuration: String?, otherConfigurations: String?...) {
|
||||
public init(_ configuration: String?, _ otherConfigurations: String?...) {
|
||||
|
||||
self.init(entityClass: T.self, configurations: [configuration] + otherConfigurations)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified configurations.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From<MyPersonEntity>(["Configuration1", "Configuration2"]))
|
||||
```
|
||||
|
||||
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ configurations: [String?]) {
|
||||
@@ -108,10 +122,10 @@ public struct From<T: NSManagedObject> {
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified configurations.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From(MyPersonEntity.self, nil, "Configuration1"))
|
||||
```
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
|
||||
@@ -123,10 +137,10 @@ public struct From<T: NSManagedObject> {
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified configurations.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From(MyPersonEntity.self, ["Configuration1", "Configuration1"]))
|
||||
```
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
@@ -137,177 +151,61 @@ public struct From<T: NSManagedObject> {
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified configurations.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From(MyPersonEntity.self, nil, "Configuration1"))
|
||||
```
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ configuration: String?, _ otherConfigurations: String?...) {
|
||||
|
||||
CoreStore.assert(
|
||||
entityClass is T.Type,
|
||||
"Attempted to create generic type \(cs_typeName(From<T>)) with entity class \(cs_typeName(entityClass))"
|
||||
)
|
||||
self.init(entityClass: entityClass, configurations: [configuration] + otherConfigurations)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified configurations.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From(MyPersonEntity.self, ["Configuration1", "Configuration1"]))
|
||||
```
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ configurations: [String?]) {
|
||||
|
||||
CoreStore.assert(
|
||||
entityClass is T.Type,
|
||||
"Attempted to create generic type \(cs_typeName(From<T>)) with entity class \(cs_typeName(entityClass))"
|
||||
)
|
||||
self.init(entityClass: entityClass, configurations: configurations)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter storeURL: the persistent store URL to associate objects from.
|
||||
- parameter otherStoreURLs: an optional list of other persistent store URLs to associate objects from (see `storeURL` parameter)
|
||||
*/
|
||||
public init(_ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: T.self, storeURLs: [storeURL] + otherStoreURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter storeURLs: the persistent store URLs to associate objects from.
|
||||
*/
|
||||
public init(_ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: T.self, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter storeURL: the persistent store URL to associate objects from.
|
||||
- parameter otherStoreURLs: an optional list of other persistent store URLs to associate objects from (see `storeURL` parameter)
|
||||
*/
|
||||
public init(_ entity: T.Type, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: entity, storeURLs: [storeURL] + otherStoreURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter storeURLs: the persistent store URLs to associate objects from.
|
||||
*/
|
||||
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: entity, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter storeURL: the persistent store URL to associate objects from.
|
||||
- parameter otherStoreURLs: an optional list of other persistent store URLs to associate objects from (see `storeURL` parameter)
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: [storeURL] + otherStoreURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter storeURLs: the persistent store URLs to associate objects from.
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter persistentStore: the `NSPersistentStore` to associate objects from.
|
||||
- parameter otherPersistentStores: an optional list of other `NSPersistentStore`s to associate objects from (see `persistentStore` parameter)
|
||||
*/
|
||||
public init(_ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: T.self, persistentStores: [persistentStore] + otherPersistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter persistentStores: the `NSPersistentStore`s to associate objects from.
|
||||
*/
|
||||
public init(_ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: T.self, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter persistentStore: the `NSPersistentStore` to associate objects from.
|
||||
- parameter otherPersistentStores: an optional list of other `NSPersistentStore`s to associate objects from (see `persistentStore` parameter)
|
||||
*/
|
||||
public init(_ entity: T.Type, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: entity, persistentStores: [persistentStore] + otherPersistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter persistentStores: the `NSPersistentStore`s to associate objects from.
|
||||
*/
|
||||
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: entity, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter persistentStore: the `NSPersistentStore` to associate objects from.
|
||||
- parameter otherPersistentStores: an optional list of other `NSPersistentStore`s to associate objects from (see `persistentStore` parameter)
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: [persistentStore] + otherPersistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter persistentStores: the `NSPersistentStore`s to associate objects from.
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext, applyAffectedStores: Bool = true) {
|
||||
@warn_unused_result
|
||||
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext, applyAffectedStores: Bool = true) -> Bool {
|
||||
|
||||
fetchRequest.entity = context.entityDescriptionForEntityClass(self.entityClass)
|
||||
if applyAffectedStores {
|
||||
guard applyAffectedStores else {
|
||||
|
||||
self.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
|
||||
return true
|
||||
}
|
||||
if self.applyAffectedStoresForFetchedRequest(fetchRequest, context: context) {
|
||||
|
||||
return true
|
||||
}
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "Attempted to perform a fetch but could not find any persistent store for the entity \(cs_typeName(fetchRequest.entityName))"
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
internal func applyAffectedStoresForFetchedRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) -> Bool {
|
||||
@@ -317,58 +215,159 @@ public struct From<T: NSManagedObject> {
|
||||
return stores?.isEmpty == false
|
||||
}
|
||||
|
||||
internal func upcast() -> From<NSManagedObject> {
|
||||
|
||||
return From<NSManagedObject>(
|
||||
entityClass: self.entityClass,
|
||||
configurations: self.configurations,
|
||||
findPersistentStores: self.findPersistentStores
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let entityClass: AnyClass
|
||||
|
||||
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
|
||||
|
||||
private init(entityClass: AnyClass) {
|
||||
private init(entityClass: AnyClass, configurations: [String?]?) {
|
||||
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
self.configurations = configurations
|
||||
if let configurations = configurations {
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)
|
||||
}
|
||||
}
|
||||
|
||||
private init(entityClass: AnyClass, configurations: [String?]) {
|
||||
|
||||
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return configurationsSet.contains($0.configurationName)
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return configurationsSet.contains($0.configurationName)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init(entityClass: AnyClass, storeURLs: [NSURL]) {
|
||||
private init(entityClass: AnyClass, configurations: [String?]?, findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?) {
|
||||
|
||||
let storeURLsSet = Set(storeURLs)
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return $0.URL != nil && storeURLsSet.contains($0.URL!)
|
||||
}
|
||||
}
|
||||
self.configurations = configurations
|
||||
self.findPersistentStores = findPersistentStores
|
||||
}
|
||||
|
||||
private init(entityClass: AnyClass, persistentStores: [NSPersistentStore]) {
|
||||
|
||||
// MARK: Obsolete
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
|
||||
|
||||
let persistentStores = Set(persistentStores)
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return persistentStores.contains($0)
|
||||
}
|
||||
}
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ storeURLs: [NSURL]) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ entity: T.Type, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ entityClass: AnyClass, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ entity: T.Type, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ entityClass: AnyClass, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
|
||||
/**
|
||||
Obsolete. Use initializers that accept configuration names.
|
||||
*/
|
||||
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
|
||||
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
CoreStore.abort("Use initializers that accept configuration names.")
|
||||
}
|
||||
}
|
||||
@@ -32,17 +32,12 @@ import CoreData
|
||||
/**
|
||||
The `GroupBy` clause specifies that the result of a query be grouped accoording to the specified key path.
|
||||
*/
|
||||
public struct GroupBy: QueryClause {
|
||||
public struct GroupBy: QueryClause, Hashable {
|
||||
|
||||
/**
|
||||
Initializes a `GroupBy` clause with a list of key path strings
|
||||
|
||||
- parameter keyPaths: a list of key path strings to group results with
|
||||
The list of key path strings to group results with
|
||||
*/
|
||||
public init(_ keyPaths: [KeyPath]) {
|
||||
|
||||
self.keyPaths = keyPaths
|
||||
}
|
||||
public let keyPaths: [KeyPath]
|
||||
|
||||
/**
|
||||
Initializes a `GroupBy` clause with an empty list of key path strings
|
||||
@@ -63,7 +58,15 @@ public struct GroupBy: QueryClause {
|
||||
self.init([keyPath] + keyPaths)
|
||||
}
|
||||
|
||||
public let keyPaths: [KeyPath]
|
||||
/**
|
||||
Initializes a `GroupBy` clause with a list of key path strings
|
||||
|
||||
- parameter keyPaths: a list of key path strings to group results with
|
||||
*/
|
||||
public init(_ keyPaths: [KeyPath]) {
|
||||
|
||||
self.keyPaths = keyPaths
|
||||
}
|
||||
|
||||
|
||||
// MARK: QueryClause
|
||||
@@ -74,10 +77,27 @@ public struct GroupBy: QueryClause {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "An existing \"propertiesToGroupBy\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
message: "An existing \"propertiesToGroupBy\" for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.propertiesToGroupBy = self.keyPaths
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
return (self.keyPaths as NSArray).hashValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - GroupBy: Equatable
|
||||
|
||||
@warn_unused_result
|
||||
public func == (lhs: GroupBy, rhs: GroupBy) -> Bool {
|
||||
|
||||
return lhs.keyPaths == rhs.keyPaths
|
||||
}
|
||||
@@ -67,17 +67,12 @@ public enum SortKey {
|
||||
/**
|
||||
The `OrderBy` clause specifies the sort order for results for a fetch or a query.
|
||||
*/
|
||||
public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
public struct OrderBy: FetchClause, QueryClause, DeleteClause, Hashable {
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a list of sort descriptors
|
||||
|
||||
- parameter sortDescriptors: a series of `NSSortDescriptor`s
|
||||
The list of sort descriptors
|
||||
*/
|
||||
public init(_ sortDescriptors: [NSSortDescriptor]) {
|
||||
|
||||
self.sortDescriptors = sortDescriptors
|
||||
}
|
||||
public let sortDescriptors: [NSSortDescriptor]
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with an empty list of sort descriptors
|
||||
@@ -97,6 +92,16 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
self.init([sortDescriptor])
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a list of sort descriptors
|
||||
|
||||
- parameter sortDescriptors: a series of `NSSortDescriptor`s
|
||||
*/
|
||||
public init(_ sortDescriptors: [NSSortDescriptor]) {
|
||||
|
||||
self.sortDescriptors = sortDescriptors
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a series of `SortKey`s
|
||||
|
||||
@@ -130,8 +135,6 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
self.init([sortKey] + sortKeys)
|
||||
}
|
||||
|
||||
public let sortDescriptors: [NSSortDescriptor]
|
||||
|
||||
|
||||
// MARK: FetchClause, QueryClause, DeleteClause
|
||||
|
||||
@@ -141,10 +144,27 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "Existing sortDescriptors for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
message: "Existing sortDescriptors for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.sortDescriptors = self.sortDescriptors
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
return (self.sortDescriptors as NSArray).hashValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - OrderBy: Equatable
|
||||
|
||||
@warn_unused_result
|
||||
public func == (lhs: OrderBy, rhs: OrderBy) -> Bool {
|
||||
|
||||
return lhs.sortDescriptors == rhs.sortDescriptors
|
||||
}
|
||||
@@ -32,7 +32,7 @@ import CoreData
|
||||
/**
|
||||
The `SelectResultType` protocol is implemented by return types supported by the `Select` clause.
|
||||
*/
|
||||
public protocol SelectResultType { }
|
||||
public protocol SelectResultType {}
|
||||
|
||||
|
||||
// MARK: - SelectValueResultType
|
||||
@@ -42,6 +42,8 @@ public protocol SelectResultType { }
|
||||
*/
|
||||
public protocol SelectValueResultType: SelectResultType {
|
||||
|
||||
static var attributeType: NSAttributeType { get }
|
||||
|
||||
static func fromResultObject(result: AnyObject) -> Self?
|
||||
}
|
||||
|
||||
@@ -62,7 +64,7 @@ public protocol SelectAttributesResultType: SelectResultType {
|
||||
/**
|
||||
The `SelectTerm` is passed to the `Select` clause to indicate the attributes/aggregate keys to be queried.
|
||||
*/
|
||||
public enum SelectTerm: StringLiteralConvertible {
|
||||
public enum SelectTerm: StringLiteralConvertible, Hashable {
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying an entity attribute. A shorter way to do the same is to assign from the string keypath directly:
|
||||
@@ -81,6 +83,7 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Where("employeeID", isEqualTo: 1111)
|
||||
)
|
||||
```
|
||||
|
||||
- parameter keyPath: the attribute name
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
|
||||
*/
|
||||
@@ -97,6 +100,7 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Average("age"))
|
||||
)
|
||||
```
|
||||
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
|
||||
@@ -105,8 +109,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "average:",
|
||||
keyPath,
|
||||
As: alias ?? "average(\(keyPath))",
|
||||
keyPath: keyPath,
|
||||
alias: alias ?? "average(\(keyPath))",
|
||||
nativeType: .DecimalAttributeType
|
||||
)
|
||||
}
|
||||
@@ -119,6 +123,7 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Count("employeeID"))
|
||||
)
|
||||
```
|
||||
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for a count query
|
||||
@@ -127,8 +132,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "count:",
|
||||
keyPath,
|
||||
As: alias ?? "count(\(keyPath))",
|
||||
keyPath: keyPath,
|
||||
alias: alias ?? "count(\(keyPath))",
|
||||
nativeType: .Integer64AttributeType
|
||||
)
|
||||
}
|
||||
@@ -141,6 +146,7 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Maximum("age"))
|
||||
)
|
||||
```
|
||||
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
|
||||
@@ -149,8 +155,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "max:",
|
||||
keyPath,
|
||||
As: alias ?? "max(\(keyPath))",
|
||||
keyPath: keyPath,
|
||||
alias: alias ?? "max(\(keyPath))",
|
||||
nativeType: .UndefinedAttributeType
|
||||
)
|
||||
}
|
||||
@@ -163,6 +169,7 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Minimum("age"))
|
||||
)
|
||||
```
|
||||
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
|
||||
@@ -171,8 +178,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "min:",
|
||||
keyPath,
|
||||
As: alias ?? "min(\(keyPath))",
|
||||
keyPath: keyPath,
|
||||
alias: alias ?? "min(\(keyPath))",
|
||||
nativeType: .UndefinedAttributeType
|
||||
)
|
||||
}
|
||||
@@ -185,6 +192,7 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Sum("age"))
|
||||
)
|
||||
```
|
||||
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
|
||||
@@ -193,12 +201,34 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "sum:",
|
||||
keyPath,
|
||||
As: alias ?? "sum(\(keyPath))",
|
||||
keyPath: keyPath,
|
||||
alias: alias ?? "sum(\(keyPath))",
|
||||
nativeType: .DecimalAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the `NSManagedObjectID`.
|
||||
```
|
||||
let objectID = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<NSManagedObjectID>(),
|
||||
Where("employeeID", isEqualTo: 1111)
|
||||
)
|
||||
```
|
||||
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "objecID" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
|
||||
*/
|
||||
public static func ObjectID(As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
return ._Identity(
|
||||
alias: alias ?? "objectID",
|
||||
nativeType: .ObjectIDAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: StringLiteralConvertible
|
||||
|
||||
@@ -218,10 +248,55 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
switch self {
|
||||
|
||||
case ._Attribute(let keyPath):
|
||||
return 0 ^ keyPath.hashValue
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
return 1 ^ function.hashValue ^ keyPath.hashValue ^ alias.hashValue ^ nativeType.hashValue
|
||||
|
||||
case ._Identity(let alias, let nativeType):
|
||||
return 3 ^ alias.hashValue ^ nativeType.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
case _Attribute(KeyPath)
|
||||
case _Aggregate(function: String, KeyPath, As: String, nativeType: NSAttributeType)
|
||||
case _Aggregate(function: String, keyPath: KeyPath, alias: String, nativeType: NSAttributeType)
|
||||
case _Identity(alias: String, nativeType: NSAttributeType)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SelectTerm: Equatable
|
||||
|
||||
@warn_unused_result
|
||||
public func == (lhs: SelectTerm, rhs: SelectTerm) -> Bool {
|
||||
|
||||
switch (lhs, rhs) {
|
||||
|
||||
case (._Attribute(let keyPath1), ._Attribute(let keyPath2)):
|
||||
return keyPath1 == keyPath2
|
||||
|
||||
case (._Aggregate(let function1, let keyPath1, let alias1, let nativeType1),
|
||||
._Aggregate(let function2, let keyPath2, let alias2, let nativeType2)):
|
||||
return function1 == function2
|
||||
&& keyPath1 == keyPath2
|
||||
&& alias1 == alias2
|
||||
&& nativeType1 == nativeType2
|
||||
|
||||
case (._Identity(let alias1, let nativeType1), ._Identity(let alias2, let nativeType2)):
|
||||
return alias1 == alias2 && nativeType1 == nativeType2
|
||||
|
||||
default:
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -267,7 +342,7 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
|
||||
- parameter sortDescriptors: a series of `NSSortDescriptor`s
|
||||
*/
|
||||
public struct Select<T: SelectResultType> {
|
||||
public struct Select<T: SelectResultType>: Hashable {
|
||||
|
||||
/**
|
||||
The `SelectResultType` type for the query's return value
|
||||
@@ -285,93 +360,45 @@ public struct Select<T: SelectResultType> {
|
||||
self.selectTerms = [selectTerm] + selectTerms
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Select` clause with a list of `SelectTerm`s
|
||||
|
||||
- parameter selectTerms: a series of `SelectTerm`s
|
||||
*/
|
||||
public init(_ selectTerms: [SelectTerm]) {
|
||||
|
||||
self.selectTerms = selectTerms
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
return self.selectTerms.map { $0.hashValue }.reduce(0, combine: ^)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func applyToFetchRequest(fetchRequest: NSFetchRequest) {
|
||||
internal let selectTerms: [SelectTerm]
|
||||
}
|
||||
|
||||
public extension Select where T: NSManagedObjectID {
|
||||
|
||||
public init() {
|
||||
|
||||
if fetchRequest.propertiesToFetch != nil {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "An existing \"propertiesToFetch\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.includesPendingChanges = false
|
||||
fetchRequest.resultType = .DictionaryResultType
|
||||
|
||||
let entityDescription = fetchRequest.entity!
|
||||
let propertiesByName = entityDescription.propertiesByName
|
||||
let attributesByName = entityDescription.attributesByName
|
||||
|
||||
var propertiesToFetch = [AnyObject]()
|
||||
for term in self.selectTerms {
|
||||
|
||||
switch term {
|
||||
|
||||
case ._Attribute(let keyPath):
|
||||
if let propertyDescription = propertiesByName[keyPath] {
|
||||
|
||||
propertiesToFetch.append(propertyDescription)
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The property \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
if let attributeDescription = attributesByName[keyPath] {
|
||||
|
||||
let expressionDescription = NSExpressionDescription()
|
||||
expressionDescription.name = alias
|
||||
if nativeType == .UndefinedAttributeType {
|
||||
|
||||
expressionDescription.expressionResultType = attributeDescription.attributeType
|
||||
}
|
||||
else {
|
||||
|
||||
expressionDescription.expressionResultType = nativeType
|
||||
}
|
||||
expressionDescription.expression = NSExpression(
|
||||
forFunction: function,
|
||||
arguments: [NSExpression(forKeyPath: keyPath)]
|
||||
)
|
||||
|
||||
propertiesToFetch.append(expressionDescription)
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The attribute \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fetchRequest.propertiesToFetch = propertiesToFetch
|
||||
self.init(.ObjectID())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Select: Equatable
|
||||
|
||||
@warn_unused_result
|
||||
public func == <T: SelectResultType, U: SelectResultType>(lhs: Select<T>, rhs: Select<U>) -> Bool {
|
||||
|
||||
internal func keyPathForFirstSelectTerm() -> KeyPath {
|
||||
|
||||
switch self.selectTerms.first! {
|
||||
|
||||
case ._Attribute(let keyPath):
|
||||
return keyPath
|
||||
|
||||
case ._Aggregate(_, _, let alias, _):
|
||||
return alias
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let selectTerms: [SelectTerm]
|
||||
return lhs.selectTerms == rhs.selectTerms
|
||||
}
|
||||
|
||||
|
||||
@@ -385,8 +412,19 @@ extension Bool: SelectValueResultType {
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Bool? {
|
||||
|
||||
return (result as? NSNumber)?.boolValue
|
||||
switch result {
|
||||
|
||||
case let decimal as NSDecimalNumber:
|
||||
// iOS: NSDecimalNumber(string: "0.5").boolValue // true
|
||||
// OSX: NSDecimalNumber(string: "0.5").boolValue // false
|
||||
return NSNumber(double: decimal.doubleValue).boolValue
|
||||
|
||||
case let number as NSNumber:
|
||||
return number.boolValue
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -666,3 +704,119 @@ extension NSDictionary: SelectAttributesResultType {
|
||||
return result as! [[NSString: AnyObject]]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Internal
|
||||
|
||||
internal extension CollectionType where Generator.Element == SelectTerm {
|
||||
|
||||
internal func applyToFetchRequest<T>(fetchRequest: NSFetchRequest, owner: T) {
|
||||
|
||||
fetchRequest.includesPendingChanges = false
|
||||
fetchRequest.resultType = .DictionaryResultType
|
||||
|
||||
func attributeDescriptionForKeyPath(keyPath: String, inEntity entity: NSEntityDescription) -> NSAttributeDescription? {
|
||||
|
||||
let components = keyPath.componentsSeparatedByString(".")
|
||||
switch components.count {
|
||||
|
||||
case 0:
|
||||
return nil
|
||||
|
||||
case 1:
|
||||
return entity.attributesByName[components[0]]
|
||||
|
||||
default:
|
||||
guard let relationship = entity.relationshipsByName[components[0]] else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return attributeDescriptionForKeyPath(
|
||||
components.dropFirst().joinWithSeparator("."),
|
||||
inEntity: relationship.entity
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
var propertiesToFetch = [AnyObject]()
|
||||
for term in self {
|
||||
|
||||
switch term {
|
||||
|
||||
case ._Attribute(let keyPath):
|
||||
let entityDescription = fetchRequest.entity!
|
||||
if let attributeDescription = attributeDescriptionForKeyPath(keyPath, inEntity: entityDescription) {
|
||||
|
||||
propertiesToFetch.append(attributeDescription)
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The key path \"\(keyPath)\" could not be resolved in entity \(cs_typeName(entityDescription.managedObjectClassName)) as an attribute and will be ignored by \(cs_typeName(owner)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
let entityDescription = fetchRequest.entity!
|
||||
if let attributeDescription = attributeDescriptionForKeyPath(keyPath, inEntity: entityDescription) {
|
||||
|
||||
let expressionDescription = NSExpressionDescription()
|
||||
expressionDescription.name = alias
|
||||
if nativeType == .UndefinedAttributeType {
|
||||
|
||||
expressionDescription.expressionResultType = attributeDescription.attributeType
|
||||
}
|
||||
else {
|
||||
|
||||
expressionDescription.expressionResultType = nativeType
|
||||
}
|
||||
expressionDescription.expression = NSExpression(
|
||||
forFunction: function,
|
||||
arguments: [NSExpression(forKeyPath: keyPath)]
|
||||
)
|
||||
propertiesToFetch.append(expressionDescription)
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The key path \"\(keyPath)\" could not be resolved in entity \(cs_typeName(entityDescription.managedObjectClassName)) as an attribute and will be ignored by \(cs_typeName(owner)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
case ._Identity(let alias, let nativeType):
|
||||
let expressionDescription = NSExpressionDescription()
|
||||
expressionDescription.name = alias
|
||||
if nativeType == .UndefinedAttributeType {
|
||||
|
||||
expressionDescription.expressionResultType = .ObjectIDAttributeType
|
||||
}
|
||||
else {
|
||||
|
||||
expressionDescription.expressionResultType = nativeType
|
||||
}
|
||||
expressionDescription.expression = NSExpression.expressionForEvaluatedObject()
|
||||
|
||||
propertiesToFetch.append(expressionDescription)
|
||||
}
|
||||
}
|
||||
|
||||
fetchRequest.propertiesToFetch = propertiesToFetch
|
||||
}
|
||||
|
||||
internal func keyPathForFirstSelectTerm() -> KeyPath {
|
||||
|
||||
switch self.first! {
|
||||
|
||||
case ._Attribute(let keyPath):
|
||||
return keyPath
|
||||
|
||||
case ._Aggregate(_, _, let alias, _):
|
||||
return alias
|
||||
|
||||
case ._Identity(let alias, _):
|
||||
return alias
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -44,14 +44,20 @@ import CoreData
|
||||
*/
|
||||
public struct Tweak: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
/**
|
||||
The block to customize the `NSFetchRequest`
|
||||
*/
|
||||
public let closure: (fetchRequest: NSFetchRequest) -> Void
|
||||
|
||||
/**
|
||||
Initializes a `Tweak` clause with a closure where the `NSFetchRequest` may be configured.
|
||||
|
||||
- parameter customization: a list of key path strings to group results with
|
||||
- Important: `Tweak`'s closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions. Also, some utilities (such as `ListMonitor`s) may keep `FetchClause`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
|
||||
- parameter closure: the block to customize the `NSFetchRequest`
|
||||
*/
|
||||
public init(_ customization: (fetchRequest: NSFetchRequest) -> Void) {
|
||||
public init(_ closure: (fetchRequest: NSFetchRequest) -> Void) {
|
||||
|
||||
self.customization = customization
|
||||
self.closure = closure
|
||||
}
|
||||
|
||||
|
||||
@@ -59,11 +65,6 @@ public struct Tweak: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
|
||||
|
||||
self.customization(fetchRequest: fetchRequest)
|
||||
self.closure(fetchRequest: fetchRequest)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let customization: (fetchRequest: NSFetchRequest) -> Void
|
||||
}
|
||||
@@ -48,17 +48,12 @@ public prefix func !(clause: Where) -> Where {
|
||||
/**
|
||||
The `Where` clause specifies the conditions for a fetch or a query.
|
||||
*/
|
||||
public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
public struct Where: FetchClause, QueryClause, DeleteClause, Hashable {
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause with an `NSPredicate`
|
||||
|
||||
- parameter predicate: the `NSPredicate` for the fetch or query
|
||||
The `NSPredicate` for the fetch or query
|
||||
*/
|
||||
public init(_ predicate: NSPredicate) {
|
||||
|
||||
self.predicate = predicate
|
||||
}
|
||||
public let predicate: NSPredicate
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause with a predicate that always evaluates to `true`
|
||||
@@ -119,7 +114,7 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter list: the array to check membership of
|
||||
*/
|
||||
public init(_ keyPath: KeyPath, isMemberOf list: NSArray) {
|
||||
public init(_ keyPath: KeyPath, isMemberOf list: [NSObject]) {
|
||||
|
||||
self.init(NSPredicate(format: "\(keyPath) IN %@", list))
|
||||
}
|
||||
@@ -135,7 +130,15 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
self.init(NSPredicate(format: "\(keyPath) IN %@", Array(list) as NSArray))
|
||||
}
|
||||
|
||||
public let predicate: NSPredicate
|
||||
/**
|
||||
Initializes a `Where` clause with an `NSPredicate`
|
||||
|
||||
- parameter predicate: the `NSPredicate` for the fetch or query
|
||||
*/
|
||||
public init(_ predicate: NSPredicate) {
|
||||
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
|
||||
// MARK: FetchClause, QueryClause, DeleteClause
|
||||
@@ -146,10 +149,27 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "An existing predicate for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
message: "An existing predicate for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.predicate = self.predicate
|
||||
}
|
||||
|
||||
|
||||
// MARK: Hashable
|
||||
|
||||
public var hashValue: Int {
|
||||
|
||||
return self.predicate.hashValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Where: Equatable
|
||||
|
||||
@warn_unused_result
|
||||
public func == (lhs: Where, rhs: Where) -> Bool {
|
||||
|
||||
return lhs.predicate == rhs.predicate
|
||||
}
|
||||
@@ -108,9 +108,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -126,9 +125,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -144,9 +142,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -162,9 +159,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -180,9 +176,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -198,9 +193,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -216,9 +210,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -234,9 +227,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -252,9 +244,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -270,9 +261,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
@@ -291,9 +281,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to query from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
@@ -312,9 +301,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to query from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
@@ -333,9 +321,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to query from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
@@ -354,9 +341,8 @@ public extension DataStack {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
"Attempted to query from a \(cs_typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
}
|
||||
@@ -36,6 +36,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter source: the object to import values from
|
||||
- throws: an `ErrorType` thrown from any of the `ImportableObject` methods
|
||||
- returns: the created `ImportableObject` instance, or `nil` if the import was ignored
|
||||
*/
|
||||
public func importObject<T where T: NSManagedObject, T: ImportableObject>(
|
||||
@@ -44,10 +45,10 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
return try cs_autoreleasepool {
|
||||
|
||||
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
@@ -65,6 +66,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
- parameter object: the `NSManagedObject` to update
|
||||
- parameter source: the object to import values from
|
||||
- throws: an `ErrorType` thrown from any of the `ImportableObject` methods
|
||||
*/
|
||||
public func importObject<T where T: NSManagedObject, T: ImportableObject>(
|
||||
object: T,
|
||||
@@ -72,10 +74,10 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to import an object of type \(typeName(object)) outside the transaction's designated queue."
|
||||
"Attempted to import an object of type \(cs_typeName(object)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
try autoreleasepool {
|
||||
try cs_autoreleasepool {
|
||||
|
||||
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
@@ -91,6 +93,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter sourceArray: the array of objects to import values from
|
||||
- throws: an `ErrorType` thrown from any of the `ImportableObject` methods
|
||||
- returns: the array of created `ImportableObject` instances
|
||||
*/
|
||||
public func importObjects<T, S: SequenceType where T: NSManagedObject, T: ImportableObject, S.Generator.Element == T.ImportSource>(
|
||||
@@ -99,10 +102,10 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
return try cs_autoreleasepool {
|
||||
|
||||
return try sourceArray.flatMap { (source) -> T? in
|
||||
|
||||
@@ -111,7 +114,7 @@ public extension BaseDataTransaction {
|
||||
return nil
|
||||
}
|
||||
|
||||
return try autoreleasepool {
|
||||
return try cs_autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
try object.didInsertFromImportSource(source, inTransaction: self)
|
||||
@@ -126,6 +129,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter source: the object to import values from
|
||||
- throws: an `ErrorType` thrown from any of the `ImportableUniqueObject` methods
|
||||
- returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored
|
||||
*/
|
||||
public func importUniqueObject<T where T: NSManagedObject, T: ImportableUniqueObject>(
|
||||
@@ -134,10 +138,10 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
return try cs_autoreleasepool {
|
||||
|
||||
let uniqueIDKeyPath = T.uniqueIDKeyPath
|
||||
guard let uniqueIDValue = try T.uniqueIDFromImportSource(source, inTransaction: self) else {
|
||||
@@ -172,10 +176,12 @@ public extension BaseDataTransaction {
|
||||
|
||||
/**
|
||||
Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources.
|
||||
- Warning: While the array returned from `importUniqueObjects(...)` correctly maps to the order of `sourceArray`, the order of objects called with `ImportableUniqueObject` methods is arbitrary. Do not make assumptions that any particular object will be imported ahead or after another object.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter sourceArray: the array of objects to import values from
|
||||
- parameter preProcess: a closure that lets the caller tweak the internal `UniqueIDType`-to-`ImportSource` mapping to be used for importing. Callers can remove from/add to/update `mapping` and return the updated array from the closure.
|
||||
- throws: an `ErrorType` thrown from any of the `ImportableUniqueObject` methods
|
||||
- returns: the array of created/updated `ImportableUniqueObject` instances
|
||||
*/
|
||||
public func importUniqueObjects<T, S: SequenceType where T: NSManagedObject, T: ImportableUniqueObject, S.Generator.Element == T.ImportSource>(
|
||||
@@ -185,13 +191,13 @@ public extension BaseDataTransaction {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
return try cs_autoreleasepool {
|
||||
|
||||
var mapping = Dictionary<T.UniqueIDType, T.ImportSource>()
|
||||
let sortedIDs = try autoreleasepool {
|
||||
let sortedIDs = try cs_autoreleasepool {
|
||||
|
||||
return try sourceArray.flatMap { (source) -> T.UniqueIDType? in
|
||||
|
||||
@@ -205,12 +211,12 @@ public extension BaseDataTransaction {
|
||||
}
|
||||
}
|
||||
|
||||
mapping = try autoreleasepool { try preProcess(mapping: mapping) }
|
||||
mapping = try cs_autoreleasepool { try preProcess(mapping: mapping) }
|
||||
|
||||
var objects = Dictionary<T.UniqueIDType, T>()
|
||||
for object in self.fetchAll(From(T), Where(T.uniqueIDKeyPath, isMemberOf: mapping.keys)) ?? [] {
|
||||
for object in self.fetchAll(From(T), Where(T.uniqueIDKeyPath, isMemberOf: sortedIDs)) ?? [] {
|
||||
|
||||
try autoreleasepool {
|
||||
try cs_autoreleasepool {
|
||||
|
||||
let uniqueIDValue = object.uniqueIDValue
|
||||
|
||||
@@ -227,7 +233,7 @@ public extension BaseDataTransaction {
|
||||
|
||||
for (uniqueIDValue, source) in mapping {
|
||||
|
||||
try autoreleasepool {
|
||||
try cs_autoreleasepool {
|
||||
|
||||
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.6.6</string>
|
||||
<string>2.0.4</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
@@ -33,11 +33,10 @@ import CoreData
|
||||
// http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified
|
||||
internal final class CoreStoreFetchRequest: NSFetchRequest {
|
||||
|
||||
@objc
|
||||
override var affectedStores: [NSPersistentStore]? {
|
||||
|
||||
get { return self.safeAffectedStores }
|
||||
set { self.safeAffectedStores = newValue }
|
||||
get { return super.affectedStores }
|
||||
set { super.affectedStores = newValue }
|
||||
}
|
||||
|
||||
private var safeAffectedStores: [NSPersistentStore]?
|
||||
}
|
||||
@@ -27,32 +27,35 @@ import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
#if os(iOS) || os(watchOS) || os(tvOS)
|
||||
|
||||
// MARK: - CoreStoreFetchedResultsController
|
||||
|
||||
@available(OSX, unavailable)
|
||||
internal final class CoreStoreFetchedResultsController<T: NSManagedObject>: NSFetchedResultsController {
|
||||
|
||||
internal final class CoreStoreFetchedResultsController: NSFetchedResultsController {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal convenience init<T>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
|
||||
@nonobjc
|
||||
internal convenience init<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, applyFetchClauses: (fetchRequest: NSFetchRequest) -> Void) {
|
||||
|
||||
self.init(
|
||||
context: dataStack.mainContext,
|
||||
fetchRequest: fetchRequest,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
applyFetchClauses: applyFetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
internal init<T>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
|
||||
@nonobjc
|
||||
internal init<T: NSManagedObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, applyFetchClauses: (fetchRequest: NSFetchRequest) -> Void) {
|
||||
|
||||
from?.applyToFetchRequest(fetchRequest, context: context, applyAffectedStores: false)
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
_ = from?.applyToFetchRequest(
|
||||
fetchRequest,
|
||||
context: context,
|
||||
applyAffectedStores: false
|
||||
)
|
||||
applyFetchClauses(fetchRequest: fetchRequest)
|
||||
|
||||
if let from = from {
|
||||
|
||||
@@ -65,7 +68,7 @@ internal final class CoreStoreFetchedResultsController<T: NSManagedObject>: NSFe
|
||||
|
||||
guard let from = (fetchRequest.entity.flatMap { $0.managedObjectClassName }).flatMap(NSClassFromString).flatMap(From.init) else {
|
||||
|
||||
fatalError("Attempted to create an \(typeName(NSFetchedResultsController)) without a From clause or an NSEntityDescription.")
|
||||
CoreStore.abort("Attempted to create an \(cs_typeName(NSFetchedResultsController)) without a \(cs_typeName(From)) clause or an \(cs_typeName(NSEntityDescription)).")
|
||||
}
|
||||
|
||||
self.reapplyAffectedStores = { fetchRequest, context in
|
||||
@@ -82,13 +85,14 @@ internal final class CoreStoreFetchedResultsController<T: NSManagedObject>: NSFe
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func performFetchFromSpecifiedStores() throws {
|
||||
|
||||
if !self.reapplyAffectedStores(fetchRequest: self.fetchRequest, context: self.managedObjectContext) {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "Attempted to perform a fetch on an \(typeName(NSFetchedResultsController)) but could not find any persistent store for the entity \(typeName(self.fetchRequest.entityName))"
|
||||
message: "Attempted to perform a fetch on an \(cs_typeName(NSFetchedResultsController)) but could not find any persistent store for the entity \(cs_typeName(self.fetchRequest.entityName))"
|
||||
)
|
||||
}
|
||||
try self.performFetch()
|
||||
@@ -102,5 +106,8 @@ internal final class CoreStoreFetchedResultsController<T: NSManagedObject>: NSFe
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@nonobjc
|
||||
private let reapplyAffectedStores: (fetchRequest: NSFetchRequest, context: NSManagedObjectContext) -> Bool
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -27,9 +27,10 @@ import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
#if os(iOS) || os(watchOS) || os(tvOS)
|
||||
|
||||
// MARK: - FetchedResultsControllerHandler
|
||||
|
||||
@available(OSX, unavailable)
|
||||
internal protocol FetchedResultsControllerHandler: class {
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
|
||||
@@ -46,14 +47,17 @@ internal protocol FetchedResultsControllerHandler: class {
|
||||
|
||||
// MARK: - FetchedResultsControllerDelegate
|
||||
|
||||
@available(OSX, unavailable)
|
||||
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc
|
||||
internal var enabled = true
|
||||
|
||||
@nonobjc
|
||||
internal weak var handler: FetchedResultsControllerHandler?
|
||||
|
||||
@nonobjc
|
||||
internal weak var fetchedResultsController: NSFetchedResultsController? {
|
||||
|
||||
didSet {
|
||||
@@ -71,7 +75,8 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
|
||||
|
||||
// MARK: NSFetchedResultsControllerDelegate
|
||||
|
||||
@objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
@objc
|
||||
dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
guard self.enabled else {
|
||||
|
||||
@@ -84,7 +89,8 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
|
||||
self.handler?.controllerWillChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
|
||||
@objc
|
||||
dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
guard self.enabled else {
|
||||
|
||||
@@ -94,7 +100,8 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
|
||||
self.handler?.controllerDidChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, 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?) {
|
||||
|
||||
guard self.enabled else {
|
||||
|
||||
@@ -181,7 +188,8 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
|
||||
)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
@objc
|
||||
dynamic func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
|
||||
guard self.enabled else {
|
||||
|
||||
@@ -203,7 +211,8 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
|
||||
)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
|
||||
@objc
|
||||
dynamic func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
|
||||
|
||||
return self.handler?.controller(
|
||||
controller,
|
||||
@@ -214,6 +223,11 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@nonobjc
|
||||
private var deletedSections = Set<Int>()
|
||||
|
||||
@nonobjc
|
||||
private var insertedSections = Set<Int>()
|
||||
}
|
||||
|
||||
#endif
|
||||
149
Sources/Internal/Functions.swift
Normal file
@@ -0,0 +1,149 @@
|
||||
//
|
||||
// Functions.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - Custom AutoreleasePool
|
||||
|
||||
internal func cs_autoreleasepool(@noescape closure: () -> Void) {
|
||||
|
||||
autoreleasepool(closure)
|
||||
}
|
||||
|
||||
internal func cs_autoreleasepool<T>(@noescape closure: () -> T) -> T {
|
||||
|
||||
var closureValue: T!
|
||||
autoreleasepool {
|
||||
|
||||
closureValue = closure()
|
||||
}
|
||||
|
||||
return closureValue
|
||||
}
|
||||
|
||||
internal func cs_autoreleasepool<T>(@noescape closure: () throws -> T) throws -> T {
|
||||
|
||||
var closureValue: T!
|
||||
var closureError: ErrorType?
|
||||
autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
closureValue = try closure()
|
||||
}
|
||||
catch {
|
||||
|
||||
closureError = error
|
||||
}
|
||||
}
|
||||
|
||||
if let closureError = closureError {
|
||||
|
||||
throw closureError
|
||||
}
|
||||
return closureValue
|
||||
}
|
||||
|
||||
internal func cs_autoreleasepool(@noescape closure: () throws -> Void) throws {
|
||||
|
||||
var closureError: ErrorType?
|
||||
autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
try closure()
|
||||
}
|
||||
catch {
|
||||
|
||||
closureError = error
|
||||
}
|
||||
}
|
||||
|
||||
if let closureError = closureError {
|
||||
|
||||
throw closureError
|
||||
}
|
||||
}
|
||||
|
||||
internal func cs_getAssociatedObjectForKey<T: AnyObject>(key: UnsafePointer<Void>, inObject object: AnyObject) -> T? {
|
||||
|
||||
switch objc_getAssociatedObject(object, key) {
|
||||
|
||||
case let associatedObject as T:
|
||||
return associatedObject
|
||||
|
||||
case let associatedObject as WeakObject:
|
||||
return associatedObject.object as? T
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
internal func cs_setAssociatedRetainedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
|
||||
internal func cs_setAssociatedCopiedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_COPY_NONATOMIC)
|
||||
}
|
||||
|
||||
internal func cs_setAssociatedWeakObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
if let associatedObject = associatedObject {
|
||||
|
||||
objc_setAssociatedObject(object, key, WeakObject(associatedObject), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
else {
|
||||
|
||||
objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Printing Utilities
|
||||
|
||||
internal func cs_typeName<T>(value: T) -> String {
|
||||
|
||||
return "'\(String(reflecting: value.dynamicType))'"
|
||||
}
|
||||
|
||||
internal func cs_typeName<T>(value: T.Type) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func cs_typeName(value: AnyClass) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func cs_typeName(name: String?) -> String {
|
||||
|
||||
return "<\(name ?? "unknown")>"
|
||||
}
|
||||
@@ -36,11 +36,12 @@ internal extension NSManagedObjectContext {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc
|
||||
internal var shouldCascadeSavesToParent: Bool {
|
||||
|
||||
get {
|
||||
|
||||
let number: NSNumber? = getAssociatedObjectForKey(
|
||||
let number: NSNumber? = cs_getAssociatedObjectForKey(
|
||||
&PropertyKeys.shouldCascadeSavesToParent,
|
||||
inObject: self
|
||||
)
|
||||
@@ -48,7 +49,7 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedCopiedObject(
|
||||
cs_setAssociatedCopiedObject(
|
||||
NSNumber(bool: newValue),
|
||||
forKey: &PropertyKeys.shouldCascadeSavesToParent,
|
||||
inObject: self
|
||||
@@ -56,11 +57,13 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func entityDescriptionForEntityType(entity: NSManagedObject.Type) -> NSEntityDescription? {
|
||||
|
||||
return self.entityDescriptionForEntityClass(entity)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func entityDescriptionForEntityClass(entity: AnyClass) -> NSEntityDescription? {
|
||||
|
||||
guard let entityName = self.parentStack?.entityNameForEntityClass(entity) else {
|
||||
@@ -73,6 +76,7 @@ internal extension NSManagedObjectContext {
|
||||
)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func setupForCoreStoreWithContextName(contextName: String) {
|
||||
|
||||
#if USE_FRAMEWORKS
|
||||
@@ -105,8 +109,8 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
CoreStore.log(
|
||||
CoreStoreError(error),
|
||||
"Failed to obtain permanent ID(s) for \(numberOfInsertedObjects) inserted object(s)."
|
||||
)
|
||||
}
|
||||
@@ -123,18 +127,19 @@ internal extension NSManagedObjectContext {
|
||||
static var shouldCascadeSavesToParent: Void?
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
private var observerForWillSaveNotification: NotificationObserver? {
|
||||
|
||||
get {
|
||||
|
||||
return getAssociatedObjectForKey(
|
||||
return cs_getAssociatedObjectForKey(
|
||||
&PropertyKeys.observerForWillSaveNotification,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
cs_setAssociatedRetainedObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.observerForWillSaveNotification,
|
||||
inObject: self
|
||||
@@ -31,8 +31,9 @@ import CoreData
|
||||
|
||||
internal extension NSManagedObjectContext {
|
||||
|
||||
// MARK: Internal
|
||||
// MARK: Internal: Fetch Existing
|
||||
|
||||
@nonobjc
|
||||
internal func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
if object.objectID.temporaryID {
|
||||
@@ -46,8 +47,8 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
CoreStore.log(
|
||||
CoreStoreError(error),
|
||||
"Failed to obtain permanent ID for object."
|
||||
)
|
||||
return nil
|
||||
@@ -61,34 +62,45 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load existing \(typeName(object)) in context."
|
||||
CoreStore.log(
|
||||
CoreStoreError(error),
|
||||
"Failed to load existing \(cs_typeName(object)) in context."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal: Fetch One
|
||||
|
||||
@nonobjc
|
||||
internal func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
return self.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
|
||||
|
||||
let fetchRequest = CoreStoreFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 1
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
|
||||
for clause in fetchClauses {
|
||||
guard storeFound else {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
return nil
|
||||
}
|
||||
return self.fetchOne(fetchRequest)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchOne<T: NSManagedObject>(fetchRequest: NSFetchRequest) -> T? {
|
||||
|
||||
var fetchResults: [T]?
|
||||
var fetchError: NSError?
|
||||
var fetchError: ErrorType?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
@@ -97,13 +109,13 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
CoreStore.log(
|
||||
CoreStoreError(fetchError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
@@ -112,26 +124,37 @@ internal extension NSManagedObjectContext {
|
||||
return fetchResults?.first
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal: Fetch All
|
||||
|
||||
@nonobjc
|
||||
internal func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
return self.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
|
||||
|
||||
let fetchRequest = CoreStoreFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
|
||||
for clause in fetchClauses {
|
||||
guard storeFound else {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
return nil
|
||||
}
|
||||
return self.fetchAll(fetchRequest)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchAll<T: NSManagedObject>(fetchRequest: NSFetchRequest) -> [T]? {
|
||||
|
||||
var fetchResults: [T]?
|
||||
var fetchError: NSError?
|
||||
var fetchError: ErrorType?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
@@ -140,13 +163,13 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
CoreStore.log(
|
||||
CoreStoreError(fetchError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
@@ -155,20 +178,31 @@ internal extension NSManagedObjectContext {
|
||||
return fetchResults
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal: Count
|
||||
|
||||
@nonobjc
|
||||
internal func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
return self.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
|
||||
|
||||
let fetchRequest = CoreStoreFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
|
||||
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
|
||||
for clause in fetchClauses {
|
||||
guard storeFound else {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
return nil
|
||||
}
|
||||
return self.fetchCount(fetchRequest)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchCount(fetchRequest: NSFetchRequest) -> Int? {
|
||||
|
||||
var count = 0
|
||||
var error: NSError?
|
||||
@@ -178,8 +212,8 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
if count == NSNotFound {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
CoreStore.log(
|
||||
CoreStoreError(error),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
@@ -188,26 +222,37 @@ internal extension NSManagedObjectContext {
|
||||
return count
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal: Object ID
|
||||
|
||||
@nonobjc
|
||||
internal func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
return self.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
|
||||
|
||||
let fetchRequest = CoreStoreFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 1
|
||||
fetchRequest.resultType = .ManagedObjectIDResultType
|
||||
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
|
||||
for clause in fetchClauses {
|
||||
guard storeFound else {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
return nil
|
||||
}
|
||||
return self.fetchObjectID(fetchRequest)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchObjectID(fetchRequest: NSFetchRequest) -> NSManagedObjectID? {
|
||||
|
||||
var fetchResults: [NSManagedObjectID]?
|
||||
var fetchError: NSError?
|
||||
var fetchError: ErrorType?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
@@ -216,13 +261,13 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
CoreStore.log(
|
||||
CoreStoreError(fetchError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
@@ -231,26 +276,37 @@ internal extension NSManagedObjectContext {
|
||||
return fetchResults?.first
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal: Object IDs
|
||||
|
||||
@nonobjc
|
||||
internal func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
return self.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
|
||||
|
||||
let fetchRequest = CoreStoreFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectIDResultType
|
||||
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
|
||||
for clause in fetchClauses {
|
||||
guard storeFound else {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
return nil
|
||||
}
|
||||
return self.fetchObjectIDs(fetchRequest)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func fetchObjectIDs(fetchRequest: NSFetchRequest) -> [NSManagedObjectID]? {
|
||||
|
||||
var fetchResults: [NSManagedObjectID]?
|
||||
var fetchError: NSError?
|
||||
var fetchError: ErrorType?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
@@ -259,13 +315,13 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
CoreStore.log(
|
||||
CoreStoreError(fetchError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
@@ -274,35 +330,46 @@ internal extension NSManagedObjectContext {
|
||||
return fetchResults
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal: Delete All
|
||||
|
||||
@nonobjc
|
||||
internal func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
|
||||
|
||||
return self.deleteAll(from, deleteClauses)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
|
||||
|
||||
let fetchRequest = CoreStoreFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchRequest.returnsObjectsAsFaults = true
|
||||
fetchRequest.includesPropertyValues = false
|
||||
deleteClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
|
||||
for clause in deleteClauses {
|
||||
guard storeFound else {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
return nil
|
||||
}
|
||||
return self.deleteAll(fetchRequest)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func deleteAll(fetchRequest: NSFetchRequest) -> Int? {
|
||||
|
||||
var numberOfDeletedObjects: Int?
|
||||
var fetchError: NSError?
|
||||
var fetchError: ErrorType?
|
||||
self.performBlockAndWait {
|
||||
|
||||
autoreleasepool {
|
||||
cs_autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
let fetchResults = try self.executeFetchRequest(fetchRequest) as? [T] ?? []
|
||||
let fetchResults = try self.executeFetchRequest(fetchRequest) as? [NSManagedObject] ?? []
|
||||
for object in fetchResults {
|
||||
|
||||
self.deleteObject(object)
|
||||
@@ -311,14 +378,14 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
}
|
||||
if numberOfDeletedObjects == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
CoreStore.log(
|
||||
CoreStoreError(fetchError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
@@ -327,27 +394,39 @@ internal extension NSManagedObjectContext {
|
||||
return numberOfDeletedObjects
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal: Value
|
||||
|
||||
@nonobjc
|
||||
internal func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
|
||||
|
||||
return self.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
|
||||
|
||||
let fetchRequest = CoreStoreFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
|
||||
selectClause.applyToFetchRequest(fetchRequest)
|
||||
let selectTerms = selectClause.selectTerms
|
||||
selectTerms.applyToFetchRequest(fetchRequest, owner: selectClause)
|
||||
queryClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
|
||||
for clause in queryClauses {
|
||||
guard storeFound else {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
return nil
|
||||
}
|
||||
return self.queryValue(selectTerms, fetchRequest: fetchRequest)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func queryValue<U: SelectValueResultType>(selectTerms: [SelectTerm], fetchRequest: NSFetchRequest) -> U? {
|
||||
|
||||
var fetchResults: [AnyObject]?
|
||||
var fetchError: NSError?
|
||||
var fetchError: ErrorType?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
@@ -356,47 +435,31 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
if let rawResult = fetchResults.first as? NSDictionary,
|
||||
let rawObject: AnyObject = rawResult[selectClause.keyPathForFirstSelectTerm()] {
|
||||
|
||||
return Select<U>.ReturnType.fromResultObject(rawObject)
|
||||
let rawObject: AnyObject = rawResult[selectTerms.keyPathForFirstSelectTerm()] {
|
||||
|
||||
return Select<U>.ReturnType.fromResultObject(rawObject)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
CoreStore.log(
|
||||
CoreStoreError(fetchError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
internal func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
return self.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
internal func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
let fetchRequest = CoreStoreFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
|
||||
selectClause.applyToFetchRequest(fetchRequest)
|
||||
|
||||
for clause in queryClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
@nonobjc
|
||||
internal func queryValue(selectTerms: [SelectTerm], fetchRequest: NSFetchRequest) -> AnyObject? {
|
||||
|
||||
var fetchResults: [AnyObject]?
|
||||
var fetchError: NSError?
|
||||
var fetchError: ErrorType?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
@@ -405,7 +468,67 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
if let rawResult = fetchResults.first as? NSDictionary,
|
||||
let rawObject: AnyObject = rawResult[selectTerms.keyPathForFirstSelectTerm()] {
|
||||
|
||||
return rawObject
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
CoreStore.log(
|
||||
CoreStoreError(fetchError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal: Attributes
|
||||
|
||||
@nonobjc
|
||||
internal func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
return self.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
let fetchRequest = CoreStoreFetchRequest()
|
||||
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
|
||||
selectClause.selectTerms.applyToFetchRequest(fetchRequest, owner: selectClause)
|
||||
queryClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
|
||||
|
||||
guard storeFound else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.queryAttributes(fetchRequest)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
internal func queryAttributes(fetchRequest: NSFetchRequest) -> [[NSString: AnyObject]]? {
|
||||
|
||||
var fetchResults: [AnyObject]?
|
||||
var fetchError: ErrorType?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest)
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
@@ -413,8 +536,8 @@ internal extension NSManagedObjectContext {
|
||||
return Select<NSDictionary>.ReturnType.fromResultObjects(fetchResults)
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
CoreStore.log(
|
||||
CoreStoreError(fetchError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
@@ -33,6 +33,7 @@ internal extension NSManagedObjectContext {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc
|
||||
internal weak var parentStack: DataStack? {
|
||||
|
||||
get {
|
||||
@@ -42,7 +43,7 @@ internal extension NSManagedObjectContext {
|
||||
return parentContext.parentStack
|
||||
}
|
||||
|
||||
return getAssociatedObjectForKey(&PropertyKeys.parentStack, inObject: self)
|
||||
return cs_getAssociatedObjectForKey(&PropertyKeys.parentStack, inObject: self)
|
||||
}
|
||||
set {
|
||||
|
||||
@@ -51,7 +52,7 @@ internal extension NSManagedObjectContext {
|
||||
return
|
||||
}
|
||||
|
||||
setAssociatedWeakObject(
|
||||
cs_setAssociatedWeakObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.parentStack,
|
||||
inObject: self
|
||||
@@ -59,7 +60,8 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
}
|
||||
|
||||
internal class func rootSavingContextForCoordinator(coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
|
||||
@nonobjc
|
||||
internal static func rootSavingContextForCoordinator(coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
|
||||
context.persistentStoreCoordinator = coordinator
|
||||
@@ -67,10 +69,32 @@ internal extension NSManagedObjectContext {
|
||||
context.undoManager = nil
|
||||
context.setupForCoreStoreWithContextName("com.corestore.rootcontext")
|
||||
|
||||
#if os(iOS) || os(OSX)
|
||||
|
||||
context.observerForDidImportUbiquitousContentChangesNotification = NotificationObserver(
|
||||
notificationName: NSPersistentStoreDidImportUbiquitousContentChangesNotification,
|
||||
object: coordinator,
|
||||
closure: { [weak context] (note) -> Void in
|
||||
|
||||
context?.performBlock { () -> Void in
|
||||
|
||||
let updatedObjectIDs = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObjectID>) ?? []
|
||||
for objectID in updatedObjectIDs {
|
||||
|
||||
context?.objectWithID(objectID).willAccessValueForKey(nil)
|
||||
}
|
||||
context?.mergeChangesFromContextDidSaveNotification(note)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
#endif
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
internal class func mainContextForRootContext(rootContext: NSManagedObjectContext) -> NSManagedObjectContext {
|
||||
@nonobjc
|
||||
internal static func mainContextForRootContext(rootContext: NSManagedObjectContext) -> NSManagedObjectContext {
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
|
||||
context.parentContext = rootContext
|
||||
@@ -107,7 +131,6 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
@@ -118,24 +141,46 @@ internal extension NSManagedObjectContext {
|
||||
|
||||
static var parentStack: Void?
|
||||
static var observerForDidSaveNotification: Void?
|
||||
static var observerForDidImportUbiquitousContentChangesNotification: Void?
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
private var observerForDidSaveNotification: NotificationObserver? {
|
||||
|
||||
get {
|
||||
|
||||
return getAssociatedObjectForKey(
|
||||
return cs_getAssociatedObjectForKey(
|
||||
&PropertyKeys.observerForDidSaveNotification,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
cs_setAssociatedRetainedObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.observerForDidSaveNotification,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
private var observerForDidImportUbiquitousContentChangesNotification: NotificationObserver? {
|
||||
|
||||
get {
|
||||
|
||||
return cs_getAssociatedObjectForKey(
|
||||
&PropertyKeys.observerForDidImportUbiquitousContentChangesNotification,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
set {
|
||||
|
||||
cs_setAssociatedRetainedObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.observerForDidImportUbiquitousContentChangesNotification,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||