Compare commits
264 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
65ac069a0b | ||
|
|
862ef27374 | ||
|
|
243b6a76d5 | ||
|
|
8be20370d5 | ||
|
|
ed8c7b35e8 | ||
|
|
4d2ebe4ea8 | ||
|
|
f18d62f643 | ||
|
|
af141d4a31 | ||
|
|
2da659a967 | ||
|
|
e5f162c5e1 | ||
|
|
effa231719 | ||
|
|
6cef8f4b4f | ||
|
|
aa6bceaaf3 | ||
|
|
0dbd05b172 | ||
|
|
243c4044ab | ||
|
|
df835114cb | ||
|
|
f99d3cc21a | ||
|
|
4a34012d58 | ||
|
|
45690a29c6 | ||
|
|
0d4d036a86 | ||
|
|
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 | ||
|
|
e38093342d | ||
|
|
c15afcb381 | ||
|
|
a97001cdbb | ||
|
|
000a357808 | ||
|
|
c8ca16a982 | ||
|
|
9e0493a20d | ||
|
|
77236d66d5 | ||
|
|
8b95d337a3 | ||
|
|
57b66cff34 | ||
|
|
9dae291f62 | ||
|
|
0c2a3ac4e9 | ||
|
|
a6291e116c | ||
|
|
c990f7764a | ||
|
|
26ae6293ca | ||
|
|
b9a2f96d6e | ||
|
|
ae5f737baf | ||
|
|
4eecd80710 | ||
|
|
0073d038e0 | ||
|
|
099dcfab68 | ||
|
|
74fe81bfa7 | ||
|
|
0bbb4118a1 | ||
|
|
3fe9e4ee1d | ||
|
|
bd19326447 | ||
|
|
dccc958ef1 | ||
|
|
ab3095f078 | ||
|
|
1a86510045 | ||
|
|
aa32f5e158 | ||
|
|
b95aaf151d | ||
|
|
114bc71841 | ||
|
|
dff552b95f | ||
|
|
88cac73b20 | ||
|
|
83d0412cb9 | ||
|
|
26e4118355 | ||
|
|
0c564add46 | ||
|
|
784a315fb9 | ||
|
|
44cfbebedb | ||
|
|
0b24072259 | ||
|
|
28b7ba01dc | ||
|
|
b529735269 | ||
|
|
d23121d8ed | ||
|
|
633ab0a249 | ||
|
|
410feda5cd | ||
|
|
06e952af8a | ||
|
|
a4d3d0c0ed | ||
|
|
8b7af86526 | ||
|
|
48a8694720 | ||
|
|
09d844f5df | ||
|
|
e25e198bf8 | ||
|
|
e99d19d2ac | ||
|
|
9d7960e674 | ||
|
|
e9ac8629a1 | ||
|
|
b0b0df2861 | ||
|
|
eda398d758 | ||
|
|
40c320e1ca | ||
|
|
8508dd4f79 | ||
|
|
d3e0f576cf | ||
|
|
1d5cf5a4cc | ||
|
|
9fc0390b45 | ||
|
|
b8ea7ecf01 | ||
|
|
11ac362dcc | ||
|
|
10c20e0d62 | ||
|
|
789028bc58 | ||
|
|
a168ca577a | ||
|
|
781f3988d2 | ||
|
|
b914a4b5ed | ||
|
|
7de13131a4 | ||
|
|
0c1af09a8d | ||
|
|
1ff635d8b5 | ||
|
|
707445a169 | ||
|
|
90369cf994 | ||
|
|
09708e587c | ||
|
|
36e6e4a8b9 | ||
|
|
9f1a351311 | ||
|
|
6e202aa7ca | ||
|
|
783b933294 | ||
|
|
ca49ea3a81 | ||
|
|
ed8891a6d5 | ||
|
|
dd6c6abaf0 | ||
|
|
928585029d | ||
|
|
dc6d22b6ff | ||
|
|
5ca0f657cb | ||
|
|
2c65ac1834 | ||
|
|
24008d62b2 | ||
|
|
e8a9cc9d67 | ||
|
|
1507ac63f9 | ||
|
|
f2df8f7171 | ||
|
|
3ddfd3cccc | ||
|
|
21f57518c8 | ||
|
|
d9422f7f2e | ||
|
|
245ec25ad8 | ||
|
|
0ce89b8460 | ||
|
|
42a889a28e | ||
|
|
456977bf12 | ||
|
|
ea8412ab93 | ||
|
|
603dffffb0 | ||
|
|
8a1144b1be | ||
|
|
c323a28c19 | ||
|
|
4ff7b2d6d9 | ||
|
|
64ad7b3bfd | ||
|
|
df441c5d04 | ||
|
|
fa947faa57 | ||
|
|
6822a4579e | ||
|
|
a89dd76906 | ||
|
|
c85ef16ad0 | ||
|
|
67f1bd9a63 | ||
|
|
39540628df | ||
|
|
c86ca06bd4 | ||
|
|
40c27b9fcb | ||
|
|
2f8c100cb6 | ||
|
|
34495d7163 | ||
|
|
75a4ebb49b | ||
|
|
3c514830d9 | ||
|
|
b283fbf5ab | ||
|
|
c1e087b8c1 | ||
|
|
ad1ebb3501 | ||
|
|
99189d160f | ||
|
|
3c2fb28644 | ||
|
|
f71ad4c577 | ||
|
|
d05fcc5103 | ||
|
|
e9329fc93c | ||
|
|
1ea773f0ec | ||
|
|
c68c2bdc0a | ||
|
|
e66a13632a | ||
|
|
4c78a309bc | ||
|
|
9055daa58c | ||
|
|
3905423038 | ||
|
|
15353268e2 | ||
|
|
91dd4b6cb3 | ||
|
|
0800b706d6 | ||
|
|
2071ce722e | ||
|
|
df866718cf | ||
|
|
f19a0d29eb | ||
|
|
8f09f90294 | ||
|
|
7bddcaa4a2 | ||
|
|
c12331f403 | ||
|
|
a55a8d389c | ||
|
|
6e88b14237 | ||
|
|
53700a267f | ||
|
|
6c95b010e9 | ||
|
|
1af675e8b7 | ||
|
|
b85f16cf68 | ||
|
|
37bf4179c8 | ||
|
|
240dda6ad6 | ||
|
|
ffe73d0f66 | ||
|
|
43c4998e20 | ||
|
|
0c4e55f061 | ||
|
|
8c8953c507 | ||
|
|
66892f22a3 | ||
|
|
def105e73a | ||
|
|
57bf123fb2 | ||
|
|
8efd6572f0 | ||
|
|
405e861907 | ||
|
|
74c48c7486 | ||
|
|
e58b3b0131 | ||
|
|
b8fad66bfa | ||
|
|
ff1155dac3 | ||
|
|
5137649b78 | ||
|
|
7a95267a9c | ||
|
|
c07435e866 | ||
|
|
f5627f0855 | ||
|
|
d3ea655eb9 | ||
|
|
fc83180af8 | ||
|
|
0c6246475a | ||
|
|
087480a3a8 | ||
|
|
83a04e669e | ||
|
|
d05522bb20 | ||
|
|
9081b36cca | ||
|
|
9322371224 | ||
|
|
26ab6aacd7 | ||
|
|
3e601c1328 | ||
|
|
762b877879 | ||
|
|
084bdc431f | ||
|
|
c63bc389b2 | ||
|
|
71c3abc4f3 | ||
|
|
863d4d1d5a | ||
|
|
06d177e8bd | ||
|
|
c2bbd537cf | ||
|
|
761d349b97 | ||
|
|
6c28594e41 | ||
|
|
c229af19a2 | ||
|
|
8b8a7c7b08 | ||
|
|
eb828d8e42 | ||
|
|
88a24540c6 | ||
|
|
74ded8fb7d | ||
|
|
969f4cefb4 | ||
|
|
1d2947ad26 | ||
|
|
49274c3c94 | ||
|
|
c0fbb2655b |
5
.gitignore
vendored
@@ -2,3 +2,8 @@ CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcuserdata
|
||||
CoreStore.xcodeproj/project.xcworkspace/xcuserdata
|
||||
CoreStore.xcodeproj/xcuserdata
|
||||
CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata
|
||||
Carthage/Build
|
||||
CoreStore.xcworkspace/xcuserdata
|
||||
.DS_Store
|
||||
DerivedData
|
||||
*.orig
|
||||
|
||||
4
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "Libraries/GCDKit"]
|
||||
path = Libraries/GCDKit
|
||||
[submodule "Carthage/Checkouts/GCDKit"]
|
||||
path = Carthage/Checkouts/GCDKit
|
||||
url = https://github.com/JohnEstropia/GCDKit.git
|
||||
|
||||
38
.travis.yml
@@ -1,32 +1,44 @@
|
||||
language: objective-c
|
||||
osx_image: xcode7.1
|
||||
osx_image: xcode8
|
||||
sudo: false
|
||||
git:
|
||||
submodules: false
|
||||
notifications:
|
||||
email: false
|
||||
env:
|
||||
global:
|
||||
- LC_CTYPE=en_US.UTF-8
|
||||
- LANG=en_US.UTF-8
|
||||
matrix:
|
||||
- DESTINATION="OS=9.1,name=iPhone 6s" SCHEME="CoreStore iOS" SDK=iphonesimulator9.1 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator9.1 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator9.1 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=8.3,name=iPhone 5S" SCHEME="CoreStore iOS" SDK=iphonesimulator9.1 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=8.2,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator9.1 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="CoreStore iOS" SDK=iphonesimulator9.1 RUN_TESTS="YES" POD_LINT="YES"
|
||||
- DESTINATION="arch=x86_64" SCHEME="CoreStore OSX" SDK=macosx10.11 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=2.0,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator2.0 RUN_TESTS="NO" POD_LINT="NO"
|
||||
- DESTINATION="OS=10.0,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
# iOS 8 testing currently broken on Xcode 8
|
||||
# - DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
# - DESTINATION="OS=8.3,name=iPhone 5s" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
# - DESTINATION="OS=8.2,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
# - DESTINATION="OS=8.1,name=iPhone 4s" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="YES"
|
||||
- DESTINATION="arch=x86_64" SCHEME="CoreStore OSX" SDK=macosx10.12 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=3.0,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator3.0 RUN_TESTS="NO" POD_LINT="NO"
|
||||
- DESTINATION="OS=2.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator3.0 RUN_TESTS="NO" POD_LINT="NO"
|
||||
- DESTINATION="OS=9.2,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator10.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
before_install:
|
||||
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
|
||||
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
|
||||
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.11/Carthage.pkg"
|
||||
- sudo installer -pkg "Carthage.pkg" -target /
|
||||
- rm "Carthage.pkg"
|
||||
before_script:
|
||||
- carthage update --use-submodules
|
||||
script:
|
||||
- set -o pipefail
|
||||
- xcodebuild -version
|
||||
- xcodebuild -showsdks
|
||||
- if [ $RUN_TESTS == "YES" ]; then
|
||||
xcodebuild -project CoreStore.xcodeproj -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c;
|
||||
xcodebuild -project CoreStore.xcodeproj -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO test | xcpretty -c;
|
||||
xcodebuild -workspace CoreStore.xcworkspace -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
|
||||
xcodebuild -workspace CoreStore.xcworkspace -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
|
||||
fi
|
||||
- xcodebuild -project "CoreStoreDemo/CoreStoreDemo.xcodeproj" -scheme "CoreStore iOS" -sdk "iphonesimulator9.1" -destination "OS=9.1,name=iPhone 6s" -configuration Debug ONLY_ACTIVE_ARCH=NO test | xcpretty -c;
|
||||
- xcodebuild -project "CoreStoreDemo/CoreStoreDemo.xcodeproj" -scheme "CoreStore iOS" -sdk "iphonesimulator9.1" -destination "OS=9.1,name=iPhone 6s" -configuration Release ONLY_ACTIVE_ARCH=NO test | xcpretty -c;
|
||||
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator10.0" -destination "OS=10.0,name=iPhone 7" -configuration Debug ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
|
||||
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator10.0" -destination "OS=10.0,name=iPhone 7" -configuration Release ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
|
||||
- if [ $POD_LINT == "YES" ]; then
|
||||
pod lib lint --quick;
|
||||
fi
|
||||
|
||||
1
Cartfile.resolved
Normal file
@@ -0,0 +1 @@
|
||||
github "JohnEstropia/GCDKit" "1.3.0"
|
||||
1
Carthage/Checkouts/GCDKit
vendored
Submodule
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.3.4"
|
||||
s.version = "2.1.2"
|
||||
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"
|
||||
@@ -10,12 +10,15 @@ Pod::Spec.new do |s|
|
||||
s.ios.deployment_target = "8.0"
|
||||
s.osx.deployment_target = "10.10"
|
||||
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/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.dependency "GCDKit", "1.1.4"
|
||||
end
|
||||
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D USE_FRAMEWORKS -D DEBUG',
|
||||
'OTHER_SWIFT_FLAGS[config=Release]' => '-D USE_FRAMEWORKS',
|
||||
'GCC_PREPROCESSOR_DEFINITIONS' => 'USE_FRAMEWORKS=1' }
|
||||
|
||||
s.dependency "GCDKit", "1.3.0"
|
||||
end
|
||||
|
||||
BIN
CoreStore.sketch
Normal file
@@ -10,10 +10,10 @@
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "F347F55F-7F5C-4476-9148-6E902F06E4AD",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStoreLibraries\/GCDKit",
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore"
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore\/"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 203,
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
|
||||
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStore.xcodeproj",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18911C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStoreTests.xctest"
|
||||
BlueprintName = "CoreStoreTests tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
13
CoreStore.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:CoreStore.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:CoreStoreDemo/CoreStoreDemo.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Carthage/Checkouts/GCDKit/GCDKit.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
37
CoreStore.xcworkspace/xcshareddata/CoreStore.xcscmblueprint
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
|
||||
|
||||
},
|
||||
"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",
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
|
||||
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStore.xcworkspace",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/JohnEstropia\/CoreStore",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48"
|
||||
},
|
||||
{
|
||||
"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,395 +0,0 @@
|
||||
//
|
||||
// BaseDataTransaction+Querying.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DataTransaction
|
||||
|
||||
public extension BaseDataTransaction {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter object: a reference to the object created/fetched outside the transaction
|
||||
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.context.existingObjectWithID(object.objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the transaction's context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter objectID: the `NSManagedObjectID` for the object
|
||||
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.context.existingObjectWithID(objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the transaction's context from references created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter objects: an array of `NSManagedObject`s created/fetched outside the transaction
|
||||
- returns: the `NSManagedObject` array for objects that exists in the transaction
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == T>(objects: S) -> [T] {
|
||||
|
||||
return objects.flatMap { (try? self.context.existingObjectWithID($0.objectID)) as? T }
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the transaction's context from a list of `NSManagedObjectID`.
|
||||
|
||||
- parameter objectIDs: the `NSManagedObjectID` array for the objects
|
||||
- returns: the `NSManagedObject` array for objects that exists in the transaction
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == NSManagedObjectID>(objectIDs: S) -> [T] {
|
||||
|
||||
return objectIDs.flatMap { (try? self.context.existingObjectWithID($0)) as? T }
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number of `NSManagedObject`s deleted
|
||||
*/
|
||||
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.deleteAll(from, deleteClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number of `NSManagedObject`s deleted
|
||||
*/
|
||||
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.deleteAll(from, deleteClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries aggregate values or aggregates as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
//
|
||||
// From.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - From
|
||||
|
||||
/**
|
||||
A `Form` clause binds the `NSManagedObject` entity type to the generics type system.
|
||||
*/
|
||||
public struct From<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public init(){
|
||||
|
||||
self.entityClass = T.self
|
||||
self.findPersistentStores = { _ in nil }
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type) {
|
||||
|
||||
self.entityClass = entity
|
||||
self.findPersistentStores = { _ in nil }
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass) {
|
||||
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { _ in nil }
|
||||
}
|
||||
|
||||
public init(_ configurations: String?...) {
|
||||
|
||||
self.init(entityClass: T.self, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ configurations: [String?]) {
|
||||
|
||||
self.init(entityClass: T.self, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ configurations: String?...) {
|
||||
|
||||
self.init(entityClass: entity, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ configurations: [String?]) {
|
||||
|
||||
self.init(entityClass: entity, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ configurations: String?...) {
|
||||
|
||||
self.init(entityClass: entityClass, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ configurations: [String?]) {
|
||||
|
||||
self.init(entityClass: entityClass, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ storeURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: T.self, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: T.self, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ storeURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: entity, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: entity, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ storeURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ persistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: T.self, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: T.self, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ persistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: entity, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: entity, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ persistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) {
|
||||
|
||||
fetchRequest.entity = context.entityDescriptionForEntityClass(self.entityClass)
|
||||
fetchRequest.affectedStores = self.findPersistentStores(context: context)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let entityClass: AnyClass
|
||||
|
||||
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
|
||||
|
||||
private init(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 {
|
||||
|
||||
return configurationsSet.contains($0.configurationName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init(entityClass: AnyClass, storeURLs: [NSURL]) {
|
||||
|
||||
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!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init(entityClass: AnyClass, persistentStores: [NSPersistentStore]) {
|
||||
|
||||
let persistentStores = Set(persistentStores)
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return persistentStores.contains($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,277 +0,0 @@
|
||||
//
|
||||
// CoreStore+Querying.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter object: a reference to the object created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
return self.defaultStack.fetchExisting(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter objectID: the `NSManagedObjectID` for the object
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
return self.defaultStack.fetchExisting(objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == T>(objects: S) -> [T] {
|
||||
|
||||
return self.defaultStack.fetchExisting(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
|
||||
|
||||
- parameter objectIDs: the `NSManagedObjectID` array for the objects
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == NSManagedObjectID>(objectIDs: S) -> [T] {
|
||||
|
||||
return self.defaultStack.fetchExisting(objectIDs)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
return self.defaultStack.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
|
||||
|
||||
return self.defaultStack.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
return self.defaultStack.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
|
||||
|
||||
return self.defaultStack.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
return self.defaultStack.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
|
||||
|
||||
return self.defaultStack.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
return self.defaultStack.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
|
||||
|
||||
return self.defaultStack.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
return self.defaultStack.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
|
||||
|
||||
return self.defaultStack.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
|
||||
|
||||
return self.defaultStack.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
|
||||
|
||||
return self.defaultStack.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, queries a dictionary of attribtue values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
return self.defaultStack.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
return self.defaultStack.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
}
|
||||
@@ -1,364 +0,0 @@
|
||||
//
|
||||
// DataStack+Querying.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
public extension DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter object: a reference to the object created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.mainContext.existingObjectWithID(object.objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter objectID: the `NSManagedObjectID` for the object
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.mainContext.existingObjectWithID(objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == T>(objects: S) -> [T] {
|
||||
|
||||
return objects.flatMap { (try? self.mainContext.existingObjectWithID($0.objectID)) as? T }
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
|
||||
|
||||
- parameter objectIDs: the `NSManagedObjectID` array for the objects
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == NSManagedObjectID>(objectIDs: S) -> [T] {
|
||||
|
||||
return objectIDs.flatMap { (try? self.mainContext.existingObjectWithID($0)) as? T }
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
}
|
||||
@@ -1,136 +0,0 @@
|
||||
//
|
||||
// ImportableUniqueObject.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - ImportableUniqueObject
|
||||
|
||||
/**
|
||||
`NSManagedObject` subclasses that conform to the `ImportableUniqueObject` protocol can be imported from a specified `ImportSource`. This allows transactions to either update existing objects or create new instances this way:
|
||||
|
||||
class MyPersonEntity: NSManagedObject, ImportableUniqueObject {
|
||||
typealias ImportSource = NSDictionary
|
||||
typealias UniqueIDType = NSString
|
||||
// ...
|
||||
}
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
let json: NSDictionary = // ...
|
||||
let person = try! transaction.importUniqueObject(
|
||||
Into(MyPersonEntity),
|
||||
source: json
|
||||
)
|
||||
// ...
|
||||
transaction.commit()
|
||||
}
|
||||
*/
|
||||
public protocol ImportableUniqueObject: ImportableObject {
|
||||
|
||||
/**
|
||||
The data type for the import source. This is most commonly an `NSDictionary` or another external source such as an `NSUserDefaults`.
|
||||
*/
|
||||
typealias ImportSource
|
||||
|
||||
/**
|
||||
The data type for the entity's unique ID attribute
|
||||
*/
|
||||
typealias UniqueIDType: NSObject
|
||||
|
||||
/**
|
||||
The keyPath to the entity's unique ID attribute
|
||||
*/
|
||||
static var uniqueIDKeyPath: String { get }
|
||||
|
||||
/**
|
||||
The object's unique ID value
|
||||
*/
|
||||
var uniqueIDValue: UniqueIDType { get set }
|
||||
|
||||
/**
|
||||
Return `true` if an object should be created from `source`. Return `false` to ignore and skip `source`. The default implementation returns the value returned by the `shouldUpdateFromImportSource(:inTransaction:)` implementation.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
- returns: `true` if an object should be created from `source`. Return `false` to ignore.
|
||||
*/
|
||||
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
|
||||
|
||||
/**
|
||||
Return `true` if an object should be updated from `source`. Return `false` to ignore and skip `source`. The default implementation returns `true`.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
- returns: `true` if an object should be updated from `source`. Return `false` to ignore.
|
||||
*/
|
||||
static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
|
||||
|
||||
/**
|
||||
Return the unique ID as extracted from `source`. This method is called before `shouldInsertFromImportSource(...)` or `shouldUpdateFromImportSource(...)`. Return `nil` to skip importing from `source`. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
- returns: the unique ID as extracted from `source`, or `nil` to skip importing from `source`.
|
||||
*/
|
||||
static func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> UniqueIDType?
|
||||
|
||||
/**
|
||||
Implements the actual importing of data from `source`. This method is called just after the object is created and assigned its unique ID as returned from `uniqueIDFromImportSource(...)`. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled. The default implementation simply calls `updateFromImportSource(...)`.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
*/
|
||||
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
|
||||
|
||||
/**
|
||||
Implements the actual importing of data from `source`. This method is called just after the existing object is fetched using its unique ID. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
*/
|
||||
func updateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ImportableUniqueObject (Default Implementations)
|
||||
|
||||
public extension ImportableUniqueObject {
|
||||
|
||||
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
|
||||
|
||||
return self.shouldUpdateFromImportSource(source, inTransaction: transaction)
|
||||
}
|
||||
|
||||
static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
|
||||
|
||||
try self.updateFromImportSource(source, inTransaction: transaction)
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
//
|
||||
// NSObject+CoreStore.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
internal func 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,106 +0,0 @@
|
||||
//
|
||||
// CoreStoreLogger.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - 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 "'\(_stdlib_getDemangledTypeName(value))'"
|
||||
}
|
||||
|
||||
internal func typeName<T>(value: T.Type) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(value: AnyClass) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(name: String?) -> String {
|
||||
|
||||
let typeName = name ?? "unknown"
|
||||
return "<\(typeName)>"
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
//
|
||||
// DefaultLogger.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - DefaultLogger
|
||||
|
||||
/**
|
||||
The `DefaultLogger` is a basic implementation of the `CoreStoreLogger` protocol.
|
||||
|
||||
- The `log(...)` method calls `print(...)` to print the level, source file name, line number, function name, and the log message.
|
||||
- The `handleError(...)` method calls `print(...)` to print the source file name, line number, function name, and the error message.
|
||||
- The `assert(...)` method calls `assert(...)` on the arguments.
|
||||
*/
|
||||
public final class DefaultLogger: CoreStoreLogger {
|
||||
|
||||
public init() { }
|
||||
|
||||
public func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
let icon: String
|
||||
let levelString: String
|
||||
switch level {
|
||||
|
||||
case .Trace:
|
||||
icon = "🔹"
|
||||
levelString = "Trace"
|
||||
|
||||
case .Notice:
|
||||
icon = "🔸"
|
||||
levelString = "Notice"
|
||||
|
||||
case .Warning:
|
||||
icon = "⚠️"
|
||||
levelString = "Warning"
|
||||
|
||||
case .Fatal:
|
||||
icon = "❗"
|
||||
levelString = "Fatal"
|
||||
}
|
||||
Swift.print("\(icon) [CoreStore: \(levelString)] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
Swift.print("⚠️ [CoreStore: Error] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
if condition() {
|
||||
|
||||
return
|
||||
}
|
||||
Swift.print("❗ [CoreStore: Assertion Failure] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
Swift.fatalError()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
//
|
||||
// CoreStore+Migration.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#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. 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. 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.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
@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,635 +0,0 @@
|
||||
//
|
||||
// DataStack+Migration.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#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.performBlock {
|
||||
|
||||
do {
|
||||
|
||||
let store = try self.coordinator.addPersistentStoreWithType(
|
||||
NSInMemoryStoreType,
|
||||
configuration: configuration,
|
||||
URL: nil,
|
||||
options: nil
|
||||
)
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
let storeError = error as NSError
|
||||
CoreStore.handleError(
|
||||
storeError,
|
||||
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(storeError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously adds to the stack an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.addSQLiteStore(
|
||||
fileURL: applicationSupportDirectory.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. 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.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
let metadata: [String: AnyObject]
|
||||
do {
|
||||
|
||||
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
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.
|
||||
- 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: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
@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()
|
||||
migrationOperation.qualityOfService = .Utility
|
||||
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,110 +0,0 @@
|
||||
//
|
||||
// MigrationResult.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - MigrationResult
|
||||
|
||||
/**
|
||||
The `MigrationResult` indicates the result of a migration.
|
||||
The `MigrationResult` can be treated as a boolean:
|
||||
|
||||
CoreStore.upgradeSQLiteStoreIfNeeded { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
if result {
|
||||
// succeeded
|
||||
}
|
||||
else {
|
||||
// failed
|
||||
}
|
||||
}
|
||||
|
||||
or as an `enum`, where the resulting associated object can also be inspected:
|
||||
|
||||
CoreStore.beginAsynchronous { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
switch result {
|
||||
case .Success(let hasChanges):
|
||||
// hasChanges indicates if there were changes or not
|
||||
case .Failure(let error):
|
||||
// error is the NSError instance for the failure
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public enum MigrationResult {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
`MigrationResult.Success` indicates either the migration succeeded, or there were no migrations needed. The associated value is an array of `MigrationType`s reflecting the migration steps completed.
|
||||
*/
|
||||
case Success([MigrationType])
|
||||
|
||||
/**
|
||||
`SaveResult.Failure` indicates that the migration failed. The associated object for this value is the related `NSError` instance.
|
||||
*/
|
||||
case Failure(NSError)
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(_ migrationTypes: [MigrationType]) {
|
||||
|
||||
self = .Success(migrationTypes)
|
||||
}
|
||||
|
||||
internal init(_ error: NSError) {
|
||||
|
||||
self = .Failure(error)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode) {
|
||||
|
||||
self.init(errorCode, userInfo: nil)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MigrationResult: BooleanType
|
||||
|
||||
extension MigrationResult: BooleanType {
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
case .Success: return true
|
||||
case .Failure: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,99 +0,0 @@
|
||||
//
|
||||
// NSError+CoreStore.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
/**
|
||||
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+CoreStore
|
||||
|
||||
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,235 +0,0 @@
|
||||
//
|
||||
// ListObserver.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - ListObserver
|
||||
|
||||
/**
|
||||
Implement the `ListObserver` protocol to observe changes to a list of `NSManagedObject`s. `ListObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorList(
|
||||
From(MyPersonEntity),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
monitor.addObserver(self)
|
||||
*/
|
||||
@available(OSX, unavailable)
|
||||
public protocol ListObserver: class {
|
||||
|
||||
/**
|
||||
The `NSManagedObject` type for the observed list
|
||||
*/
|
||||
typealias ListEntityType: NSManagedObject
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed list occurs
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
*/
|
||||
func listMonitorWillChange(monitor: ListMonitor<ListEntityType>)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed list occurs
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
*/
|
||||
func listMonitorDidChange(monitor: ListMonitor<ListEntityType>)
|
||||
|
||||
/**
|
||||
This method is broadcast from within the `ListMonitor`'s `refetch(...)` method to let observers prepare for the internal `NSFetchedResultsController`'s pending change to its predicate, sort descriptors, etc. Note that the actual refetch will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
*/
|
||||
func listMonitorWillRefetch(monitor: ListMonitor<ListEntityType>)
|
||||
|
||||
/**
|
||||
After the `ListMonitor`'s `refetch(...)` method is called, this method is broadcast after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
*/
|
||||
func listMonitorDidRefetch(monitor: ListMonitor<ListEntityType>)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListObserver (Default Implementations)
|
||||
|
||||
@available(OSX, unavailable)
|
||||
public extension ListObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitorWillChange(monitor: ListMonitor<ListEntityType>) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitorDidChange(monitor: ListMonitor<ListEntityType>) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitorWillRefetch(monitor: ListMonitor<ListEntityType>) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitorDidRefetch(monitor: ListMonitor<ListEntityType>) { }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListObjectObserver
|
||||
|
||||
/**
|
||||
Implement the `ListObjectObserver` protocol to observe detailed changes to a list's object. `ListObjectObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorList(
|
||||
From(MyPersonEntity),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
monitor.addObserver(self)
|
||||
*/
|
||||
@available(OSX, unavailable)
|
||||
public protocol ListObjectObserver: ListObserver {
|
||||
|
||||
/**
|
||||
Notifies that an object was inserted to the specified `NSIndexPath` in the list
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the inserted object
|
||||
- parameter indexPath: the new `NSIndexPath` for the inserted object
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: NSIndexPath)
|
||||
|
||||
/**
|
||||
Notifies that an object was deleted from the specified `NSIndexPath` in the list
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the deleted object
|
||||
- parameter indexPath: the `NSIndexPath` for the deleted object
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: NSIndexPath)
|
||||
|
||||
/**
|
||||
Notifies that an object at the specified `NSIndexPath` was updated
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the updated object
|
||||
- parameter indexPath: the `NSIndexPath` for the updated object
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: NSIndexPath)
|
||||
|
||||
/**
|
||||
Notifies that an object's index changed
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the moved object
|
||||
- parameter fromIndexPath: the previous `NSIndexPath` for the moved object
|
||||
- parameter toIndexPath: the new `NSIndexPath` for the moved object
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListObjectObserver (Default Implementations)
|
||||
|
||||
@available(OSX, unavailable)
|
||||
public extension ListObjectObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: NSIndexPath) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: NSIndexPath) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: NSIndexPath) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListSectionObserver
|
||||
|
||||
/**
|
||||
Implement the `ListSectionObserver` protocol to observe changes to a list's section info. `ListSectionObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionBy("age") { "Age \($0)" },
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
monitor.addObserver(self)
|
||||
*/
|
||||
@available(OSX, unavailable)
|
||||
public protocol ListSectionObserver: ListObjectObserver {
|
||||
|
||||
/**
|
||||
Notifies that a section was inserted at the specified index
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section
|
||||
- parameter sectionIndex: the new section index for the new section
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
|
||||
|
||||
/**
|
||||
Notifies that a section was inserted at the specified index
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section
|
||||
- parameter sectionIndex: the previous section index for the deleted section
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListSectionObserver (Default Implementations)
|
||||
|
||||
@available(OSX, unavailable)
|
||||
public extension ListSectionObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { }
|
||||
}
|
||||
@@ -1,226 +0,0 @@
|
||||
//
|
||||
// BaseDataTransaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - BaseDataTransaction
|
||||
|
||||
/**
|
||||
The `BaseDataTransaction` is an abstract interface for `NSManagedObject` creates, updates, and deletes. All `BaseDataTransaction` subclasses manage a private `NSManagedObjectContext` which are direct children of the `NSPersistentStoreCoordinator`'s root `NSManagedObjectContext`. This means that all updates are saved first to the persistent store, and then propagated up to the read-only `NSManagedObjectContext`.
|
||||
*/
|
||||
public /*abstract*/ class BaseDataTransaction {
|
||||
|
||||
// MARK: Object management
|
||||
|
||||
/**
|
||||
Indicates if the transaction has pending changes
|
||||
*/
|
||||
public var hasChanges: Bool {
|
||||
|
||||
return self.context.hasChanges
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new `NSManagedObject` with the specified entity type.
|
||||
|
||||
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
|
||||
- returns: a new `NSManagedObject` instance of the specified entity type.
|
||||
*/
|
||||
public func create<T: NSManagedObject>(into: Into<T>) -> T {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to create an entity of type \(typeName(T)) outside its designated queue."
|
||||
)
|
||||
|
||||
let context = self.context
|
||||
let entityClass = (into.entityClass as! NSManagedObject.Type)
|
||||
if into.inferStoreIfPossible {
|
||||
|
||||
switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: nil, inferStoreIfPossible: true) {
|
||||
|
||||
case (let persistentStore?, _):
|
||||
let object = entityClass.createInContext(context) as! T
|
||||
context.assignObject(object, toPersistentStore: persistentStore)
|
||||
return object
|
||||
|
||||
case (.None, true):
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.")
|
||||
|
||||
default:
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)), but a destination persistent store containing the entity type could not be found.")
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: into.configuration, inferStoreIfPossible: false) {
|
||||
|
||||
case (let persistentStore?, _):
|
||||
let object = entityClass.createInContext(context) as! T
|
||||
context.assignObject(object, toPersistentStore: persistentStore)
|
||||
return object
|
||||
|
||||
default:
|
||||
if let configuration = into.configuration {
|
||||
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the configuration \"\(configuration)\", which it doesn't belong to.")
|
||||
}
|
||||
else {
|
||||
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the default configuration, which it doesn't belong to.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject`.
|
||||
|
||||
- parameter object: the `NSManagedObject` type to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func edit<T: NSManagedObject>(object: T?) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to update an entity of type \(typeName(object)) outside its designated queue."
|
||||
)
|
||||
guard let object = object else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return self.context.fetchExisting(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter objectID: the `NSManagedObjectID` for the object to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to update an entity of type \(typeName(T)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
into.inferStoreIfPossible
|
||||
|| (into.configuration ?? Into.defaultConfigurationName) == objectID.persistentStore?.configurationName,
|
||||
"Attempted to update an entity of type \(typeName(T)) but the specified persistent store do not match the `NSManagedObjectID`."
|
||||
)
|
||||
return self.fetchExisting(objectID) as? T
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes a specified `NSManagedObject`.
|
||||
|
||||
- parameter object: the `NSManagedObject` to be deleted
|
||||
*/
|
||||
public func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete an entity outside its designated queue."
|
||||
)
|
||||
guard let object = object else {
|
||||
|
||||
return
|
||||
}
|
||||
self.context.fetchExisting(object)?.deleteFromContext()
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter object1: the `NSManagedObject` to be deleted
|
||||
- parameter object2: another `NSManagedObject` to be deleted
|
||||
- parameter objects: other `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
|
||||
|
||||
self.delete(([object1, object2] + objects).flatMap { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter objects: the `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete entities outside their designated queue."
|
||||
)
|
||||
|
||||
let context = self.context
|
||||
objects.forEach { context.fetchExisting($0)?.deleteFromContext() }
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let context: NSManagedObjectContext
|
||||
internal let transactionQueue: GCDQueue
|
||||
internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue")
|
||||
internal let supportsUndo: Bool
|
||||
internal let bypassesQueueing: Bool
|
||||
|
||||
|
||||
internal var isCommitted = false
|
||||
internal var result: SaveResult?
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, supportsUndo: Bool, bypassesQueueing: Bool) {
|
||||
|
||||
let context = mainContext.temporaryContextInTransactionWithConcurrencyType(
|
||||
queue == .Main
|
||||
? .MainQueueConcurrencyType
|
||||
: .PrivateQueueConcurrencyType
|
||||
)
|
||||
self.transactionQueue = queue
|
||||
self.context = context
|
||||
self.supportsUndo = supportsUndo
|
||||
self.bypassesQueueing = bypassesQueueing
|
||||
|
||||
context.parentTransaction = self
|
||||
if !supportsUndo {
|
||||
|
||||
context.undoManager = nil
|
||||
}
|
||||
else if context.undoManager == nil {
|
||||
|
||||
context.undoManager = NSUndoManager()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
//
|
||||
// Into.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - Into
|
||||
|
||||
/**
|
||||
A `Into` clause contains the destination entity and destination persistent store for a `create(...)` method. A common usage is to just indicate the entity:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
|
||||
For cases where multiple `NSPersistentStore`s contain the same entity, the destination configuration's name needs to be specified as well:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
|
||||
This helps the `NSManagedObjectContext` to determine which
|
||||
*/
|
||||
public struct Into<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>())
|
||||
*/
|
||||
public init(){
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
self.entityClass = T.self
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity type.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
|
||||
- parameter entity: the `NSManagedObject` type to be created
|
||||
*/
|
||||
public init(_ entity: T.Type) {
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
self.entityClass = entity
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity class.
|
||||
|
||||
- parameter entityClass: the `NSManagedObject` class type to be created
|
||||
*/
|
||||
public init(_ entityClass: AnyClass) {
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
self.entityClass = entityClass
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
self.entityClass = T.self
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity type and configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity.self, "Configuration1"))
|
||||
|
||||
- parameter entity: the `NSManagedObject` type to be created
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ entity: T.Type, _ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
self.entityClass = entity
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity class and configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity.self, "Configuration1"))
|
||||
|
||||
- parameter entityClass: the `NSManagedObject` class type to be created
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
self.entityClass = entityClass
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static var defaultConfigurationName: String {
|
||||
|
||||
return "PF_DEFAULT_CONFIGURATION_NAME"
|
||||
}
|
||||
|
||||
internal let entityClass: AnyClass
|
||||
internal let configuration: String?
|
||||
internal let inferStoreIfPossible: Bool
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
//
|
||||
// SaveResult.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - SaveResult
|
||||
|
||||
/**
|
||||
The `SaveResult` indicates the result of a `commit(...)` for a transaction.
|
||||
The `SaveResult` can be treated as a boolean:
|
||||
|
||||
CoreStore.beginAsynchronous { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
if result {
|
||||
// succeeded
|
||||
}
|
||||
else {
|
||||
// failed
|
||||
}
|
||||
}
|
||||
|
||||
or as an `enum`, where the resulting associated object can also be inspected:
|
||||
|
||||
CoreStore.beginAsynchronous { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
switch result {
|
||||
case .Success(let hasChanges):
|
||||
// hasChanges indicates if there were changes or not
|
||||
case .Failure(let error):
|
||||
// error is the NSError instance for the failure
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public enum SaveResult {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
`SaveResult.Success` indicates that the `commit()` for the transaction succeeded, either because the save succeeded or because there were no changes to save. The associated value `hasChanges` indicates if there were saved changes or not.
|
||||
*/
|
||||
case Success(hasChanges: Bool)
|
||||
|
||||
/**
|
||||
`SaveResult.Failure` indicates that the `commit()` for the transaction failed. The associated object for this value is the related `NSError` instance.
|
||||
*/
|
||||
case Failure(NSError)
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(hasChanges: Bool) {
|
||||
|
||||
self = .Success(hasChanges: hasChanges)
|
||||
}
|
||||
|
||||
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: - SaveResult: BooleanType
|
||||
|
||||
extension SaveResult: BooleanType {
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
case .Success: return true
|
||||
case .Failure: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,233 +0,0 @@
|
||||
//
|
||||
// SynchronousDataTransaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - SynchronousDataTransaction
|
||||
|
||||
/**
|
||||
The `SynchronousDataTransaction` provides an interface for `NSManagedObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.beginSynchronous(_:)`, or from `CoreStore.beginSynchronous(_:)`.
|
||||
*/
|
||||
public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
public func commit() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to commit a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to commit a \(typeName(self)) more than once."
|
||||
)
|
||||
|
||||
self.isCommitted = true
|
||||
self.result = self.context.saveSynchronously()
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
*/
|
||||
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to begin a child transaction from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return SynchronousDataTransaction(
|
||||
mainContext: self.context,
|
||||
queue: self.childTransactionQueue,
|
||||
closure: closure).performAndWait()
|
||||
}
|
||||
|
||||
|
||||
// MARK: BaseDataTransaction
|
||||
|
||||
/**
|
||||
Creates a new `NSManagedObject` with the specified entity type.
|
||||
|
||||
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
|
||||
- returns: a new `NSManagedObject` instance of the specified entity type.
|
||||
*/
|
||||
public override func create<T: NSManagedObject>(into: Into<T>) -> T {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.create(into)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter object: the `NSManagedObject` type to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public override func edit<T: NSManagedObject>(object: T?) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter objectID: the `NSManagedObjectID` for the object to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public override func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(into, objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter object: the `NSManagedObject` type to be deleted
|
||||
*/
|
||||
public override func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter object1: the `NSManagedObject` to be deleted
|
||||
- parameter object2: another `NSManagedObject` to be deleted
|
||||
- parameter objects: other `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(([object1, object2] + objects).flatMap { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter objects: the `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public override func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
@available(*, deprecated=1.3.4, message="Resetting the context is inherently unsafe. This method will be removed in the near future. Use `beginUnsafe()` to create transactions with `undo` support.")
|
||||
public func rollback() {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to rollback an already committed \(typeName(self))."
|
||||
)
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to rollback a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
self.context.reset()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: SynchronousDataTransaction) -> Void) {
|
||||
|
||||
self.closure = closure
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
|
||||
}
|
||||
|
||||
internal func performAndWait() -> SaveResult? {
|
||||
|
||||
self.transactionQueue.sync {
|
||||
|
||||
self.closure(transaction: self)
|
||||
|
||||
if !self.isCommitted && self.hasChanges {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
|
||||
)
|
||||
}
|
||||
}
|
||||
return self.result
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let closure: (transaction: SynchronousDataTransaction) -> Void
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
//
|
||||
// CoreStore+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#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. 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.
|
||||
- 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,387 +0,0 @@
|
||||
//
|
||||
// DataStack.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
internal let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first!
|
||||
|
||||
internal let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
|
||||
|
||||
internal let defaultSQLiteStoreURL = applicationSupportDirectory.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 {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
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 incremental migration. 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.performBlockAndWait {
|
||||
|
||||
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. 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: applicationSupportDirectory.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. 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.performBlockAndWait {
|
||||
|
||||
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.performBlockAndWait {
|
||||
|
||||
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"
|
||||
migrationQueue.qualityOfService = .Utility
|
||||
migrationQueue.underlyingQueue = dispatch_queue_create("com.coreStore.migrationQueue", DISPATCH_QUEUE_SERIAL)
|
||||
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) ?? []) {
|
||||
|
||||
if self.entityConfigurationsMapping[entityDescription.managedObjectClassName] == nil {
|
||||
|
||||
self.entityConfigurationsMapping[entityDescription.managedObjectClassName] = []
|
||||
}
|
||||
self.entityConfigurationsMapping[entityDescription.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 {
|
||||
|
||||
for store in self.coordinator.persistentStores {
|
||||
|
||||
_ = try? self.coordinator.removePersistentStore(store)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
//
|
||||
// PersistentStoreResult.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - 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 {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
`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 {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .Success: return true
|
||||
case .Failure: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,11 +35,11 @@
|
||||
B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; };
|
||||
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; };
|
||||
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */; };
|
||||
B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; };
|
||||
B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; };
|
||||
B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */; };
|
||||
B5E89ACD1C52929C003B04A9 /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9241C202429008147CD /* GCDKit.framework */; };
|
||||
B5E89ACE1C52929C003B04A9 /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9241C202429008147CD /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; };
|
||||
B5E89AD11C5292A2003B04A9 /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25841B36E23C0000406B /* OrganismV1.swift */; };
|
||||
B5EE25871B36E2520000406B /* OrganismV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25861B36E2520000406B /* OrganismV2.swift */; };
|
||||
B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */; };
|
||||
@@ -47,67 +47,15 @@
|
||||
B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
B5202CF11C044CC800DED140 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = B52DD1741BE1F8CC00949AFE;
|
||||
remoteInfo = "CoreStore OSX";
|
||||
};
|
||||
B5202CF31C044CC800DED140 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = B52DD17D1BE1F8CC00949AFE;
|
||||
remoteInfo = "CoreStoreTests OSX";
|
||||
};
|
||||
B56321C51BD65965006C9394 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = B563216F1BD65082006C9394;
|
||||
remoteInfo = "CoreStore watchOS";
|
||||
};
|
||||
B583A91A1AF5F4F4001F76AF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 2F03A53019C5C6DA005002A5;
|
||||
remoteInfo = CoreStore;
|
||||
};
|
||||
B583A91C1AF5F4F4001F76AF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 2F03A53B19C5C6DA005002A5;
|
||||
remoteInfo = CoreStoreTests;
|
||||
};
|
||||
B583A91E1AF5F512001F76AF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 2F03A52F19C5C6DA005002A5;
|
||||
remoteInfo = CoreStore;
|
||||
};
|
||||
B583A9221AF5F542001F76AF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 2F03A52F19C5C6DA005002A5;
|
||||
remoteInfo = CoreStore;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
B583A9241AF5F542001F76AF /* Embed Frameworks */ = {
|
||||
B5E89ACF1C52929C003B04A9 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */,
|
||||
B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */,
|
||||
B5E89ACE1C52929C003B04A9 /* GCDKit.framework in Embed Frameworks */,
|
||||
B5E89AD11C5292A2003B04A9 /* CoreStore.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -145,8 +93,8 @@
|
||||
B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingResultsViewController.swift; sourceTree = "<group>"; };
|
||||
B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = "<group>"; };
|
||||
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
|
||||
B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreStore.xcodeproj; path = ../CoreStore.xcodeproj; sourceTree = "<group>"; };
|
||||
B5D9C9181B20AB1900E64F0E /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5BDC9211C202429008147CD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5BDC9241C202429008147CD /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OrganismTableViewCell.swift; path = "CoreStoreDemo/MIgrations Demo/OrganismTableViewCell.swift"; sourceTree = SOURCE_ROOT; };
|
||||
B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5EE25841B36E23C0000406B /* OrganismV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV1.swift; sourceTree = "<group>"; };
|
||||
@@ -162,10 +110,10 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5E89ACD1C52929C003B04A9 /* GCDKit.framework in Frameworks */,
|
||||
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */,
|
||||
B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */,
|
||||
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */,
|
||||
B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */,
|
||||
B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -196,9 +144,9 @@
|
||||
B52977E21B120F90003D50A5 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */,
|
||||
B5D9C9181B20AB1900E64F0E /* GCDKit.framework */,
|
||||
B52977E01B120F8A003D50A5 /* CoreLocation.framework */,
|
||||
B5BDC9211C202429008147CD /* CoreStore.framework */,
|
||||
B5BDC9241C202429008147CD /* GCDKit.framework */,
|
||||
B52977DE1B120F83003D50A5 /* MapKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
@@ -296,18 +244,6 @@
|
||||
path = "Migrations Demo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B583A9151AF5F4F3001F76AF /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B583A91B1AF5F4F4001F76AF /* CoreStore.framework */,
|
||||
B583A91D1AF5F4F4001F76AF /* CoreStoreTests.xctest */,
|
||||
B56321C61BD65965006C9394 /* CoreStore.framework */,
|
||||
B5202CF21C044CC800DED140 /* CoreStore.framework */,
|
||||
B5202CF41C044CC800DED140 /* CoreStoreTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -318,13 +254,11 @@
|
||||
B54AAD451AF4D26E00848AE0 /* Sources */,
|
||||
B54AAD461AF4D26E00848AE0 /* Frameworks */,
|
||||
B54AAD471AF4D26E00848AE0 /* Resources */,
|
||||
B583A9241AF5F542001F76AF /* Embed Frameworks */,
|
||||
B5E89ACF1C52929C003B04A9 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
B583A91F1AF5F512001F76AF /* PBXTargetDependency */,
|
||||
B583A9231AF5F542001F76AF /* PBXTargetDependency */,
|
||||
);
|
||||
name = CoreStoreDemo;
|
||||
productName = CoreStoreDemo;
|
||||
@@ -343,6 +277,7 @@
|
||||
TargetAttributes = {
|
||||
B54AAD481AF4D26E00848AE0 = {
|
||||
CreatedOnToolsVersion = 6.3;
|
||||
LastSwiftMigration = 0800;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -357,12 +292,6 @@
|
||||
mainGroup = B54AAD401AF4D26E00848AE0;
|
||||
productRefGroup = B54AAD4A1AF4D26E00848AE0 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = B583A9151AF5F4F3001F76AF /* Products */;
|
||||
ProjectRef = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
B54AAD481AF4D26E00848AE0 /* CoreStoreDemo */,
|
||||
@@ -370,44 +299,6 @@
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
B5202CF21C044CC800DED140 /* CoreStore.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = CoreStore.framework;
|
||||
remoteRef = B5202CF11C044CC800DED140 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
B5202CF41C044CC800DED140 /* CoreStoreTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = CoreStoreTests.xctest;
|
||||
remoteRef = B5202CF31C044CC800DED140 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
B56321C61BD65965006C9394 /* CoreStore.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = CoreStore.framework;
|
||||
remoteRef = B56321C51BD65965006C9394 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
B583A91B1AF5F4F4001F76AF /* CoreStore.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = CoreStore.framework;
|
||||
remoteRef = B583A91A1AF5F4F4001F76AF /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
B583A91D1AF5F4F4001F76AF /* CoreStoreTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = CoreStoreTests.xctest;
|
||||
remoteRef = B583A91C1AF5F4F4001F76AF /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
B54AAD471AF4D26E00848AE0 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
@@ -460,19 +351,6 @@
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
B583A91F1AF5F512001F76AF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = CoreStore;
|
||||
targetProxy = B583A91E1AF5F512001F76AF /* PBXContainerItemProxy */;
|
||||
};
|
||||
B583A9231AF5F542001F76AF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = CoreStore;
|
||||
targetProxy = B583A9221AF5F542001F76AF /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
B54AAD571AF4D26E00848AE0 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@@ -582,8 +460,9 @@
|
||||
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)";
|
||||
SWIFT_VERSION = 2.3;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -594,8 +473,9 @@
|
||||
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)";
|
||||
SWIFT_VERSION = 2.3;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/02.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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"/>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/12.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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
|
||||
@@ -37,7 +39,7 @@ private struct Static {
|
||||
cachedTimeZone.daylightSavingTimeOffset = rawTimeZone.daylightSavingTimeOffset
|
||||
}
|
||||
|
||||
transaction.commit()
|
||||
transaction.commitAndWait()
|
||||
}
|
||||
|
||||
return dataStack
|
||||
@@ -97,12 +99,13 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
switch self.segmentedControl?.selectedSegmentIndex {
|
||||
|
||||
case .Some(Section.Fetching.rawValue):
|
||||
case Section.Fetching.rawValue?:
|
||||
return self.fetchingItems.count
|
||||
|
||||
case .Some(Section.Querying.rawValue):
|
||||
case Section.Querying.rawValue?:
|
||||
return self.queryingItems.count
|
||||
|
||||
default:
|
||||
@@ -116,10 +119,10 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
|
||||
switch self.segmentedControl?.selectedSegmentIndex {
|
||||
|
||||
case .Some(Section.Fetching.rawValue):
|
||||
case Section.Fetching.rawValue?:
|
||||
cell.textLabel?.text = self.fetchingItems[indexPath.row].title
|
||||
|
||||
case .Some(Section.Querying.rawValue):
|
||||
case Section.Querying.rawValue?:
|
||||
cell.textLabel?.text = self.queryingItems[indexPath.row].title
|
||||
|
||||
default:
|
||||
@@ -138,10 +141,10 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
|
||||
switch self.segmentedControl?.selectedSegmentIndex {
|
||||
|
||||
case .Some(Section.Fetching.rawValue):
|
||||
case Section.Fetching.rawValue?:
|
||||
self.performSegueWithIdentifier("FetchingResultsViewController", sender: indexPath)
|
||||
|
||||
case .Some(Section.Querying.rawValue):
|
||||
case Section.Querying.rawValue?:
|
||||
self.performSegueWithIdentifier("QueryingResultsViewController", sender: indexPath)
|
||||
|
||||
default:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/17.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/17.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -16,19 +16,19 @@ class QueryingResultsViewController: UITableViewController {
|
||||
|
||||
switch value {
|
||||
|
||||
case .Some(let array as [AnyObject]):
|
||||
case (let array as [AnyObject])?:
|
||||
self.values = array.map { (item: AnyObject) -> (title: String, detail: String) in
|
||||
(
|
||||
title: item.description,
|
||||
detail: _stdlib_getDemangledTypeName(item)
|
||||
detail: String(reflecting: item.dynamicType)
|
||||
)
|
||||
}
|
||||
|
||||
case .Some(let item):
|
||||
case let item?:
|
||||
self.values = [
|
||||
(
|
||||
title: item.description,
|
||||
detail: _stdlib_getDemangledTypeName(item)
|
||||
detail: String(reflecting: item.dynamicType)
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/15.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/02.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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(
|
||||
@@ -88,7 +90,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .Trash,
|
||||
target: self,
|
||||
action: "resetBarButtonItemTouched:"
|
||||
action: #selector(self.resetBarButtonItemTouched(_:))
|
||||
)
|
||||
]
|
||||
|
||||
@@ -96,13 +98,13 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
title: Static.filter.rawValue,
|
||||
style: .Plain,
|
||||
target: self,
|
||||
action: "filterBarButtonItemTouched:"
|
||||
action: #selector(self.filterBarButtonItemTouched(_:))
|
||||
)
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .Add,
|
||||
target: self,
|
||||
action: "addBarButtonItemTouched:"
|
||||
action: #selector(self.addBarButtonItemTouched(_:))
|
||||
),
|
||||
filterBarButton
|
||||
]
|
||||
@@ -119,7 +121,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
|
||||
switch (segue.identifier, segue.destinationViewController, sender) {
|
||||
|
||||
case (.Some("ObjectObserverDemoViewController"), let destinationViewController as ObjectObserverDemoViewController, let palette as Palette):
|
||||
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as Palette):
|
||||
destinationViewController.palette = palette
|
||||
|
||||
default:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/06.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -61,7 +61,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
let palette = transaction.create(Into(Palette))
|
||||
palette.setInitialValues()
|
||||
|
||||
transaction.commit()
|
||||
transaction.commitAndWait()
|
||||
}
|
||||
|
||||
let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))!
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/24.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/05.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/05.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/05.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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
|
||||
|
||||
@@ -102,20 +98,21 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
switch self.segmentedControl?.selectedSegmentIndex {
|
||||
|
||||
case .Some(0):
|
||||
case 0?:
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
transaction.create(Into(Palette))
|
||||
}
|
||||
|
||||
case .Some(1):
|
||||
do {
|
||||
|
||||
try self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite", configuration: "invalidStore")
|
||||
}
|
||||
catch _ { }
|
||||
case 1?:
|
||||
_ = try? dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "emptyStore.sqlite",
|
||||
configuration: "invalidStore"
|
||||
)
|
||||
)
|
||||
|
||||
case .Some(2):
|
||||
case 2?:
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
transaction.commit()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/21.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -39,7 +39,7 @@ class MigrationsDemoViewController: UIViewController {
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Migrations Demo",
|
||||
message: "This demo shows how to run incremental migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
|
||||
message: "This demo shows how to run progressive migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
|
||||
preferredStyle: .Alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
@@ -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,40 +157,50 @@ 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 strongSelf = self else {
|
||||
guard let `self` = self else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
guard case .Success = result else {
|
||||
|
||||
strongSelf.setEnabled(true)
|
||||
self.setEnabled(true)
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.setDataStack(dataStack, model: model, scrollToSelection: true)
|
||||
self.setDataStack(dataStack, model: model, scrollToSelection: true)
|
||||
|
||||
let count = dataStack.queryValue(From(model.entityType), Select<Int>(.Count("dna")))
|
||||
let count = dataStack.queryValue(
|
||||
From(model.entityType),
|
||||
Select<Int>(.Count("dna"))
|
||||
)
|
||||
if count > 0 {
|
||||
|
||||
strongSelf.setEnabled(true)
|
||||
self.setEnabled(true)
|
||||
}
|
||||
else {
|
||||
|
||||
dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
for i: Int64 in 0 ..< 20 {
|
||||
|
||||
for i: Int64 in 1 ..< 10000 {
|
||||
dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
|
||||
organism.dna = i
|
||||
organism.mutate()
|
||||
for j: Int64 in 0 ..< 500 {
|
||||
|
||||
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
|
||||
organism.dna = (i * 500) + j + 1
|
||||
organism.mutate()
|
||||
}
|
||||
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
dataStack.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
transaction.commit { result -> Void in
|
||||
transaction.commit { _ in
|
||||
|
||||
self?.setEnabled(true)
|
||||
}
|
||||
@@ -256,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)
|
||||
@@ -267,7 +276,7 @@ class MigrationsDemoViewController: UIViewController {
|
||||
|
||||
self.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
|
||||
self.titleLabel?.text = "Migrating: \(progress.localizedDescription)"
|
||||
self.organismLabel?.text = "Incremental step \(progress.localizedAdditionalDescription)"
|
||||
self.organismLabel?.text = "Progressive step \(progress.localizedAdditionalDescription)"
|
||||
}
|
||||
|
||||
private func updateDisplay(reloadData reloadData: Bool, scrollToSelection: Bool, animated: Bool) {
|
||||
@@ -355,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()
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/27.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/21.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/21.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/27.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/27.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/06.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/06.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/24.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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
|
||||
@@ -43,7 +47,7 @@ private struct Static {
|
||||
account2.name = "Jane Doe HCD"
|
||||
account2.friends = 314
|
||||
|
||||
transaction.commit()
|
||||
transaction.commitAndWait()
|
||||
}
|
||||
|
||||
return dataStack
|
||||
@@ -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
|
||||
@@ -77,7 +85,7 @@ private struct Static {
|
||||
account2.name = "#janedoe_hcd"
|
||||
account2.friends = 100
|
||||
|
||||
transaction.commit()
|
||||
transaction.commitAndWait()
|
||||
}
|
||||
|
||||
return dataStack
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/24.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/24.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/24.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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))
|
||||
@@ -32,7 +34,7 @@ private struct Static {
|
||||
let place = transaction.create(Into(Place))
|
||||
place.setInitialValues()
|
||||
|
||||
transaction.commit()
|
||||
transaction.commitAndWait()
|
||||
}
|
||||
place = CoreStore.fetchOne(From(Place))
|
||||
}
|
||||
@@ -60,7 +62,10 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
let longPressGesture = UILongPressGestureRecognizer(target: self, action: "longPressGestureRecognized:")
|
||||
let longPressGesture = UILongPressGestureRecognizer(
|
||||
target: self,
|
||||
action: #selector(self.longPressGestureRecognized(_:))
|
||||
)
|
||||
self.mapView?.addGestureRecognizer(longPressGesture)
|
||||
|
||||
Static.placeController.addObserver(self)
|
||||
@@ -68,7 +73,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
barButtonSystemItem: .Refresh,
|
||||
target: self,
|
||||
action: "refreshButtonTapped:"
|
||||
action: #selector(self.refreshButtonTapped(_:))
|
||||
)
|
||||
}
|
||||
|
||||
@@ -178,7 +183,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
|
||||
|
||||
let place = transaction.edit(Static.placeController.object)
|
||||
place?.setInitialValues()
|
||||
transaction.commit()
|
||||
transaction.commitAndWait()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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
|
||||
237
CoreStoreTests/BridgingTests.m
Normal file
@@ -0,0 +1,237 @@
|
||||
//
|
||||
// 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);
|
||||
}
|
||||
|
||||
- (void)test_ThatTransactions_BridgeCorrectly {
|
||||
|
||||
[CSCoreStore
|
||||
setDefaultStack:[[CSDataStack alloc]
|
||||
initWithModelName:@"Model"
|
||||
bundle:[NSBundle bundleForClass:[self class]]
|
||||
versionChain:nil]];
|
||||
[CSCoreStore
|
||||
addInMemoryStorageAndWait:[CSInMemoryStore new]
|
||||
error:nil];
|
||||
|
||||
{
|
||||
CSUnsafeDataTransaction *transaction = [CSCoreStore beginUnsafe];
|
||||
XCTAssertNotNil(transaction);
|
||||
XCTAssert([transaction isKindOfClass:[CSUnsafeDataTransaction class]]);
|
||||
}
|
||||
{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"sync"];
|
||||
[CSCoreStore beginSynchronous:^(CSSynchronousDataTransaction * _Nonnull transaction) {
|
||||
|
||||
XCTAssertNotNil(transaction);
|
||||
XCTAssert([transaction isKindOfClass:[CSSynchronousDataTransaction class]]);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}
|
||||
{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"async"];
|
||||
[CSCoreStore beginAsynchronous:^(CSAsynchronousDataTransaction * _Nonnull transaction) {
|
||||
|
||||
XCTAssertNotNil(transaction);
|
||||
XCTAssert([transaction isKindOfClass:[CSAsynchronousDataTransaction class]]);
|
||||
[expectation fulfill];
|
||||
}];
|
||||
}
|
||||
[self waitForExpectationsWithTimeout:10 handler:nil];
|
||||
}
|
||||
|
||||
@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,386 +0,0 @@
|
||||
//
|
||||
// CoreStoreTests.swift
|
||||
// CoreStoreTests
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import 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.commit()
|
||||
}
|
||||
|
||||
CoreStore.beginSynchronous({ (transaction) -> Void in
|
||||
|
||||
if let obj = CoreStore.fetchOne(From(TestEntity2)) {
|
||||
|
||||
let oldID = obj.testEntityID
|
||||
obj.testEntityID = 0
|
||||
obj.testEntityID = oldID
|
||||
}
|
||||
|
||||
transaction.commit()
|
||||
})
|
||||
|
||||
let objs1 = CoreStore.fetchAll(From(TestEntity1))
|
||||
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()
|
||||
|
||||
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")
|
||||
|
||||
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()
|
||||
|
||||
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.commit()
|
||||
}
|
||||
|
||||
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(.ApplicationSupportDirectory, 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 = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.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 = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.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 = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(Set(affectedConfigurations), ["PF_DEFAULT_CONFIGURATION_NAME", "Config1"] as Set)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.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 = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config2"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.LogWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.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 = CoreStoreFetchRequest()
|
||||
_ = 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 = CoreStoreFetchRequest()
|
||||
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!))
|
||||
}
|
||||
}
|
||||
}
|
||||
217
CoreStoreTests/StorageInterfaceTests.swift
Normal file
@@ -0,0 +1,217 @@
|
||||
//
|
||||
// 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])
|
||||
}
|
||||
}
|
||||