mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-12 04:10:36 +01:00
Compare commits
180 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
26ab6aacd7 | ||
|
|
3e601c1328 | ||
|
|
762b877879 | ||
|
|
084bdc431f | ||
|
|
c63bc389b2 | ||
|
|
71c3abc4f3 | ||
|
|
863d4d1d5a | ||
|
|
06d177e8bd | ||
|
|
c2bbd537cf | ||
|
|
761d349b97 | ||
|
|
6c28594e41 | ||
|
|
c229af19a2 | ||
|
|
8b8a7c7b08 | ||
|
|
eb828d8e42 | ||
|
|
88a24540c6 | ||
|
|
74ded8fb7d | ||
|
|
969f4cefb4 | ||
|
|
1d2947ad26 | ||
|
|
49274c3c94 | ||
|
|
c0fbb2655b | ||
|
|
cefd6f6cbc | ||
|
|
2653f7b977 | ||
|
|
c1163283f5 | ||
|
|
529347b6a2 | ||
|
|
eba7e99374 | ||
|
|
3c4350bd5d | ||
|
|
88b13189e5 | ||
|
|
03da3544f6 | ||
|
|
648b59e1ee | ||
|
|
ab3c8ca812 | ||
|
|
900a31c541 | ||
|
|
656a107767 | ||
|
|
4ce3d5de3c | ||
|
|
dec9757dc2 | ||
|
|
578e4966fc | ||
|
|
b741626574 | ||
|
|
f79a77ab78 | ||
|
|
1f2a70fd42 | ||
|
|
718d2c9b7d | ||
|
|
c5ff02335e | ||
|
|
66b57faa44 | ||
|
|
eef5a3d80b | ||
|
|
bc0757cf06 | ||
|
|
0c0a2a382c | ||
|
|
05b4a7092a | ||
|
|
6aed070e7c | ||
|
|
fcb1d7cbbc | ||
|
|
d074aad111 | ||
|
|
b7685dc747 | ||
|
|
a185bc96c0 | ||
|
|
91aef44803 | ||
|
|
dcfb09eda7 | ||
|
|
9ca83d9c5d | ||
|
|
b00eaf2d0b | ||
|
|
dc8bdf3bad | ||
|
|
4792c4462e | ||
|
|
524757a7cf | ||
|
|
0b6298a802 | ||
|
|
ae77558ae8 | ||
|
|
d33aa9c5cf | ||
|
|
d89319d324 | ||
|
|
cf9af6eef5 | ||
|
|
b9ec66f425 | ||
|
|
2a8df0596d | ||
|
|
83e6a41d88 | ||
|
|
c6fe494fe1 | ||
|
|
7771e047e2 | ||
|
|
5b0439835b | ||
|
|
622c5aa652 | ||
|
|
114b7ce605 | ||
|
|
7451fbe026 | ||
|
|
ee2398fcdf | ||
|
|
8d26501040 | ||
|
|
0709fe95cf | ||
|
|
656a99fe12 | ||
|
|
726e0eabe9 | ||
|
|
032764b9b7 | ||
|
|
5461bb0736 | ||
|
|
4c6bc4f494 | ||
|
|
63a43a6487 | ||
|
|
6a006d5d7c | ||
|
|
399517e357 | ||
|
|
f47adc12b3 | ||
|
|
0de1733efe | ||
|
|
e627cf8161 | ||
|
|
22943d2c76 | ||
|
|
56ea14d53c | ||
|
|
21a524d725 | ||
|
|
d5e769be6c | ||
|
|
ea6b6f2c37 | ||
|
|
c3ef8a4172 | ||
|
|
16aabe1f3b | ||
|
|
10e0cf8d2c | ||
|
|
3a0f53321a | ||
|
|
0da43d5132 | ||
|
|
69f902ef20 | ||
|
|
d04b4ca085 | ||
|
|
8ed6a78609 | ||
|
|
0c9e6afe0e | ||
|
|
ff4629908a | ||
|
|
d3ffe7a8fc | ||
|
|
3919ada428 | ||
|
|
682b13a8d3 | ||
|
|
2f935de04a | ||
|
|
006d5e1402 | ||
|
|
093c1d410f | ||
|
|
7555ff3ad0 | ||
|
|
4c16a961ba | ||
|
|
1121d44d7b | ||
|
|
ba4fb5e5cb | ||
|
|
d860da2507 | ||
|
|
2bcf8008c5 | ||
|
|
71477c0839 | ||
|
|
3348aa0bef | ||
|
|
2ed61fdb17 | ||
|
|
9cfad8a17a | ||
|
|
3bf34f33dc | ||
|
|
90bfaeaae8 | ||
|
|
a29a4b38fe | ||
|
|
5a96ef13f6 | ||
|
|
b92ee76907 | ||
|
|
67ccae4ef6 | ||
|
|
62b11309f3 | ||
|
|
8c6a7df731 | ||
|
|
b475afe79f | ||
|
|
a263851266 | ||
|
|
cbc3eb8887 | ||
|
|
2fb3263aa1 | ||
|
|
64aa97264e | ||
|
|
8066bf2a5a | ||
|
|
283104af3f | ||
|
|
83c724f584 | ||
|
|
59ad525786 | ||
|
|
b85d7521e1 | ||
|
|
4dbc4558e3 | ||
|
|
2a56c097f2 | ||
|
|
d4b95aed64 | ||
|
|
0d2650c54b | ||
|
|
6a3885edda | ||
|
|
1c6085ad82 | ||
|
|
2dae2ae39c | ||
|
|
a1e37975cc | ||
|
|
0c8a43c3b0 | ||
|
|
9676e3aca2 | ||
|
|
a34d2795af | ||
|
|
106789d592 | ||
|
|
edc51de030 | ||
|
|
f85eb2e057 | ||
|
|
f0161b50ed | ||
|
|
6c780c1282 | ||
|
|
b711ec9644 | ||
|
|
a581f91507 | ||
|
|
ebd4df5c2f | ||
|
|
2780fd7682 | ||
|
|
982e75a36e | ||
|
|
79d5598abe | ||
|
|
b88679c507 | ||
|
|
858dd0962d | ||
|
|
e7bcb501fd | ||
|
|
f99349f99d | ||
|
|
a64bcc474e | ||
|
|
8cfe8e2500 | ||
|
|
33268eeea1 | ||
|
|
b2a190ea33 | ||
|
|
35fbaf9513 | ||
|
|
450afb00fa | ||
|
|
e0048f589c | ||
|
|
929fdfd9a9 | ||
|
|
7e0d3120e1 | ||
|
|
acfe5dd7a7 | ||
|
|
9da323b0b8 | ||
|
|
4f3e679aa1 | ||
|
|
c1967ecdd3 | ||
|
|
5f04f4c496 | ||
|
|
5b85b0749e | ||
|
|
bf0eebe057 | ||
|
|
261c3a6001 | ||
|
|
91444b6dd3 | ||
|
|
e0b5da27f8 | ||
|
|
40f22761bf |
3
.gitignore
vendored
3
.gitignore
vendored
@@ -2,3 +2,6 @@ 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
|
||||
|
||||
6
.gitmodules
vendored
6
.gitmodules
vendored
@@ -1,3 +1,3 @@
|
||||
[submodule "Libraries/GCDKit"]
|
||||
path = Libraries/GCDKit
|
||||
url = git@github.com:JohnEstropia/GCDKit.git
|
||||
[submodule "Carthage/Checkouts/GCDKit"]
|
||||
path = Carthage/Checkouts/GCDKit
|
||||
url = https://github.com/JohnEstropia/GCDKit.git
|
||||
|
||||
40
.travis.yml
Normal file
40
.travis.yml
Normal file
@@ -0,0 +1,40 @@
|
||||
language: objective-c
|
||||
osx_image: xcode7.1
|
||||
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"
|
||||
before_install:
|
||||
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
|
||||
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
|
||||
- brew update
|
||||
- brew install carthage
|
||||
before_script:
|
||||
- carthage update --use-submodules
|
||||
script:
|
||||
- set -o pipefail
|
||||
- xcodebuild -version
|
||||
- xcodebuild -showsdks
|
||||
- if [ $RUN_TESTS == "YES" ]; then
|
||||
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 -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator9.1" -destination "OS=9.1,name=iPhone 6s" -configuration Debug ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
|
||||
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator9.1" -destination "OS=9.1,name=iPhone 6s" -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
1
Cartfile.resolved
Normal file
@@ -0,0 +1 @@
|
||||
github "JohnEstropia/GCDKit" "1.1.5"
|
||||
1
Carthage/Checkouts/GCDKit
vendored
Submodule
1
Carthage/Checkouts/GCDKit
vendored
Submodule
Submodule Carthage/Checkouts/GCDKit added at f0ed14911f
@@ -1,16 +1,21 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CoreStore"
|
||||
s.version = "0.2.0"
|
||||
s.version = "1.4.3"
|
||||
s.license = "MIT"
|
||||
s.summary = "Simple, elegant, and smart Core Data programming with Swift"
|
||||
s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift"
|
||||
s.homepage = "https://github.com/JohnEstropia/CoreStore"
|
||||
s.author = { "John Rommel Estropia" => "rommel.estropia@gmail.com" }
|
||||
s.source = { :git => "https://github.com/JohnEstropia/CoreStore.git", :tag => s.version.to_s }
|
||||
|
||||
s.ios.deployment_target = "8.0"
|
||||
s.osx.deployment_target = "10.10"
|
||||
s.watchos.deployment_target = "2.0"
|
||||
|
||||
s.source_files = "CoreStore", "CoreStore/**/*.{swift}"
|
||||
s.frameworks = "Foundation", "UIKit", "CoreData"
|
||||
s.osx.exclude_files = "CoreStore/Observing/*.{swift}", "CoreStore/Internal/FetchedResultsControllerDelegate.swift", "CoreStore/Convenience Helpers/NSFetchedResultsController+Convenience.swift"
|
||||
s.frameworks = "Foundation", "CoreData"
|
||||
s.requires_arc = true
|
||||
s.dependency "GCDKit", "1.0.1"
|
||||
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-D USE_FRAMEWORKS' }
|
||||
|
||||
s.dependency "GCDKit", "1.1.5"
|
||||
end
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
|
||||
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0,
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "F347F55F-7F5C-4476-9148-6E902F06E4AD",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStoreLibraries\/GCDKit",
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 203,
|
||||
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStore.xcodeproj",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/CoreStore.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48"
|
||||
},
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/GCDKit.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B52DD1731BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore OSX"
|
||||
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 = "B52DD17C1BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStoreTests.xctest"
|
||||
BlueprintName = "CoreStoreTests OSX"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B52DD1731BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore OSX"
|
||||
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 = "B52DD1731BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore OSX"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B52DD1731BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore OSX"
|
||||
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 = "0630"
|
||||
LastUpgradeVersion = "0700"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -16,31 +16,31 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore"
|
||||
BlueprintName = "CoreStore iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForRunning = "NO"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
buildForAnalyzing = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
|
||||
BuildableName = "CoreStoreTests.xctest"
|
||||
BlueprintName = "CoreStoreTests"
|
||||
BlueprintName = "CoreStoreTests iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -48,7 +48,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
|
||||
BuildableName = "CoreStoreTests.xctest"
|
||||
BlueprintName = "CoreStoreTests"
|
||||
BlueprintName = "CoreStoreTests iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
@@ -58,26 +58,29 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore"
|
||||
BlueprintName = "CoreStore iOS"
|
||||
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"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore"
|
||||
BlueprintName = "CoreStore iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -85,17 +88,17 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore"
|
||||
BlueprintName = "CoreStore iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0630"
|
||||
LastUpgradeVersion = "0700"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -14,72 +14,61 @@
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
|
||||
BuildableName = "CoreStoreDemo.app"
|
||||
BlueprintName = "CoreStoreDemo"
|
||||
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
|
||||
BlueprintIdentifier = "B563216E1BD65082006C9394"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore watchOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
|
||||
BuildableName = "CoreStoreDemo.app"
|
||||
BlueprintName = "CoreStoreDemo"
|
||||
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
|
||||
BuildableName = "CoreStoreDemo.app"
|
||||
BlueprintName = "CoreStoreDemo"
|
||||
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
|
||||
BlueprintIdentifier = "B563216E1BD65082006C9394"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore watchOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
|
||||
BuildableName = "CoreStoreDemo.app"
|
||||
BlueprintName = "CoreStoreDemo"
|
||||
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
|
||||
BlueprintIdentifier = "B563216E1BD65082006C9394"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore watchOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
13
CoreStore.xcworkspace/contents.xcworkspacedata
generated
Normal file
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>
|
||||
30
CoreStore.xcworkspace/xcshareddata/CoreStore.xcscmblueprint
Normal file
30
CoreStore.xcworkspace/xcshareddata/CoreStore.xcscmblueprint
Normal file
@@ -0,0 +1,30 @@
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
|
||||
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0,
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "EBFDEFFE-8BA0-441A-862A-1DE28AA5CD21",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStore\/Carthage\/Checkouts\/GCDKit\/",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
1
CoreStore/CartFile
Normal file
1
CoreStore/CartFile
Normal file
@@ -0,0 +1 @@
|
||||
github "JohnEstropia/GCDKit" == 1.1.5
|
||||
@@ -0,0 +1,50 @@
|
||||
//
|
||||
// NSManagedObject+Convenience.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - NSFetchedResultsController
|
||||
|
||||
public extension NSFetchedResultsController {
|
||||
|
||||
public convenience init<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
|
||||
|
||||
let context = dataStack.mainContext
|
||||
from?.applyToFetchRequest(fetchRequest, context: context)
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
self.init(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: context,
|
||||
sectionNameKeyPath: sectionBy?.sectionKeyPath,
|
||||
cacheName: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -31,6 +31,13 @@ import CoreData
|
||||
|
||||
public extension NSManagedObject {
|
||||
|
||||
/**
|
||||
Provides a convenience wrapper for accessing `primitiveValueForKey(...)` with proper calls to `willAccessValueForKey(...)` and `didAccessValueForKey(...)`. This is useful when implementing accessor methods for transient attributes.
|
||||
|
||||
- parameter KVCKey: the KVC key
|
||||
- returns: the primitive value for the KVC key
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func accessValueForKVCKey(KVCKey: KeyPath) -> AnyObject? {
|
||||
|
||||
self.willAccessValueForKey(KVCKey)
|
||||
@@ -40,10 +47,32 @@ public extension NSManagedObject {
|
||||
return primitiveValue
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a convenience wrapper for setting `setPrimitiveValue(...)` with proper calls to `willChangeValueForKey(...)` and `didChangeValueForKey(...)`. This is useful when implementing mutator methods for transient attributes.
|
||||
|
||||
- parameter value: the value to set the KVC key with
|
||||
- parameter KVCKey: the KVC key
|
||||
*/
|
||||
public func setValue(value: AnyObject?, forKVCKey KVCKey: KeyPath) {
|
||||
|
||||
self.willChangeValueForKey(KVCKey)
|
||||
self.setPrimitiveValue(value, forKey: KVCKey)
|
||||
self.didChangeValueForKey(KVCKey)
|
||||
}
|
||||
|
||||
/**
|
||||
Re-faults the object to use the latest values from the persistent store
|
||||
*/
|
||||
public func refreshAsFault() {
|
||||
|
||||
self.managedObjectContext?.refreshObject(self, mergeChanges: false)
|
||||
}
|
||||
|
||||
/**
|
||||
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
|
||||
*/
|
||||
public func refreshAndMerge() {
|
||||
|
||||
self.managedObjectContext?.refreshObject(self, mergeChanges: true)
|
||||
}
|
||||
}
|
||||
|
||||
134
CoreStore/Convenience Helpers/NSProgress+Convenience.swift
Normal file
134
CoreStore/Convenience Helpers/NSProgress+Convenience.swift
Normal file
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// NSProgress+Convenience.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - NSProgress
|
||||
|
||||
public extension NSProgress {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Sets a closure that the `NSProgress` calls whenever its `fractionCompleted` changes. You can use this instead of setting up KVO.
|
||||
- parameter closure: the closure to execute on progress change
|
||||
*/
|
||||
public func setProgressHandler(closure: ((progress: NSProgress) -> Void)?) {
|
||||
|
||||
self.progressObserver.progressHandler = closure
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var progressObserver: Void?
|
||||
}
|
||||
|
||||
private var progressObserver: ProgressObserver {
|
||||
|
||||
get {
|
||||
|
||||
let object: ProgressObserver? = getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
|
||||
if let observer = object {
|
||||
|
||||
return observer
|
||||
}
|
||||
|
||||
let observer = ProgressObserver(self)
|
||||
setAssociatedRetainedObject(
|
||||
observer,
|
||||
forKey: &PropertyKeys.progressObserver,
|
||||
inObject: self
|
||||
)
|
||||
|
||||
return observer
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@objc private final class ProgressObserver: NSObject {
|
||||
|
||||
private unowned let progress: NSProgress
|
||||
private var progressHandler: ((progress: NSProgress) -> Void)? {
|
||||
|
||||
didSet {
|
||||
|
||||
let progressHandler = self.progressHandler
|
||||
if (progressHandler == nil) == (oldValue == nil) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if let _ = progressHandler {
|
||||
|
||||
self.progress.addObserver(
|
||||
self,
|
||||
forKeyPath: "fractionCompleted",
|
||||
options: [.Initial, .New],
|
||||
context: nil
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
self.progress.removeObserver(self, forKeyPath: "fractionCompleted")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init(_ progress: NSProgress) {
|
||||
|
||||
self.progress = progress
|
||||
super.init()
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
if let _ = self.progressHandler {
|
||||
|
||||
self.progressHandler = nil
|
||||
self.progress.removeObserver(self, forKeyPath: "fractionCompleted")
|
||||
}
|
||||
}
|
||||
|
||||
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
|
||||
|
||||
guard let progress = object as? NSProgress where progress == self.progress && keyPath == "fractionCompleted" else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
GCDQueue.Main.async { [weak self] () -> Void in
|
||||
|
||||
self?.progressHandler?(progress: progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <CoreData/CoreData.h>
|
||||
|
||||
FOUNDATION_EXPORT double CoreStoreVersionNumber;
|
||||
|
||||
@@ -24,7 +24,9 @@
|
||||
//
|
||||
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
@@ -33,16 +33,82 @@ 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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchOne(from, fetchClauses)
|
||||
}
|
||||
@@ -50,13 +116,17 @@ public extension BaseDataTransaction {
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchOne(from, fetchClauses)
|
||||
}
|
||||
@@ -64,13 +134,17 @@ public extension BaseDataTransaction {
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchAll(from, fetchClauses)
|
||||
}
|
||||
@@ -78,41 +152,53 @@ public extension BaseDataTransaction {
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchCount(from, fetchClauses)
|
||||
}
|
||||
@@ -120,13 +206,17 @@ public extension BaseDataTransaction {
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
@@ -134,137 +224,171 @@ public extension BaseDataTransaction {
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to fetch from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: the number of `NSManagedObject`'s deleted
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: deleteClauses a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: the number of `NSManagedObject`'s deleted
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to delete from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to query from a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
@@ -38,72 +38,110 @@ public struct From<T: NSManagedObject> {
|
||||
|
||||
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(configurations: configurations)
|
||||
self.init(entityClass: T.self, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ configurations: [String?]) {
|
||||
|
||||
self.init(configurations: configurations)
|
||||
self.init(entityClass: T.self, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ configurations: String?...) {
|
||||
|
||||
self.init(configurations: configurations)
|
||||
self.init(entityClass: entity, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ configurations: [String?]) {
|
||||
|
||||
self.init(configurations: configurations)
|
||||
self.init(entityClass: entity, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ configurations: String?...) {
|
||||
|
||||
self.init(entityClass: entityClass, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ configurations: [String?]) {
|
||||
|
||||
self.init(entityClass: entityClass, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ storeURLs: NSURL...) {
|
||||
|
||||
self.init(storeURLs: storeURLs)
|
||||
self.init(entityClass: T.self, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(storeURLs: storeURLs)
|
||||
self.init(entityClass: T.self, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ storeURLs: NSURL...) {
|
||||
|
||||
self.init(storeURLs: storeURLs)
|
||||
self.init(entityClass: entity, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(storeURLs: storeURLs)
|
||||
self.init(entityClass: entity, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ storeURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ persistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(persistentStores: persistentStores)
|
||||
self.init(entityClass: T.self, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(persistentStores: persistentStores)
|
||||
self.init(entityClass: T.self, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ persistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(persistentStores: persistentStores)
|
||||
self.init(entityClass: entity, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(persistentStores: persistentStores)
|
||||
self.init(entityClass: entity, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ persistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
|
||||
@@ -111,45 +149,50 @@ public struct From<T: NSManagedObject> {
|
||||
|
||||
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) {
|
||||
|
||||
fetchRequest.entity = context.entityDescriptionForEntityClass(T.self)
|
||||
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(configurations: [String?]) {
|
||||
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(T.self)?.filter {
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return configurationsSet.contains($0.configurationName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init(storeURLs: [NSURL]) {
|
||||
private init(entityClass: AnyClass, storeURLs: [NSURL]) {
|
||||
|
||||
let storeURLsSet = Set(storeURLs)
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter {
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return $0.URL != nil && storeURLsSet.contains($0.URL!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init(persistentStores: [NSPersistentStore]) {
|
||||
private init(entityClass: AnyClass, persistentStores: [NSPersistentStore]) {
|
||||
|
||||
let persistentStores = Set(persistentStores)
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(T.self)?.filter {
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return persistentStores.contains($0)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ public struct GroupBy: QueryClause {
|
||||
/**
|
||||
Initializes a `GroupBy` clause with a list of key path strings
|
||||
|
||||
:param: keyPaths a list of key path strings to group results with
|
||||
- parameter keyPaths: a list of key path strings to group results with
|
||||
*/
|
||||
public init(_ keyPaths: [KeyPath]) {
|
||||
|
||||
@@ -57,8 +57,8 @@ public struct GroupBy: QueryClause {
|
||||
/**
|
||||
Initializes a `GroupBy` clause with a list of key path strings
|
||||
|
||||
:param: keyPath a key path string to group results with
|
||||
:param: keyPaths a series of key path strings to group results with
|
||||
- parameter keyPath: a key path string to group results with
|
||||
- parameter keyPaths: a series of key path strings to group results with
|
||||
*/
|
||||
public init(_ keyPath: KeyPath, _ keyPaths: KeyPath...) {
|
||||
|
||||
@@ -74,7 +74,10 @@ public struct GroupBy: QueryClause {
|
||||
|
||||
if fetchRequest.propertiesToGroupBy != nil {
|
||||
|
||||
CoreStore.log(.Warning, message: "An existing \"propertiesToGroupBy\" for the <\(NSFetchRequest.self)> was overwritten by \(typeName(self)) query clause.")
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "An existing \"propertiesToGroupBy\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.propertiesToGroupBy = self.keyPaths
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
public func +(left: OrderBy, right: OrderBy) -> OrderBy {
|
||||
@@ -73,7 +74,7 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a list of sort descriptors
|
||||
|
||||
:param: sortDescriptors a series of `NSSortDescriptor`'s
|
||||
- parameter sortDescriptors: a series of `NSSortDescriptor`s
|
||||
*/
|
||||
public init(_ sortDescriptors: [NSSortDescriptor]) {
|
||||
|
||||
@@ -91,7 +92,7 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a single sort descriptor
|
||||
|
||||
:param: sortDescriptor a `NSSortDescriptor`
|
||||
- parameter sortDescriptor: a `NSSortDescriptor`
|
||||
*/
|
||||
public init(_ sortDescriptor: NSSortDescriptor) {
|
||||
|
||||
@@ -99,16 +100,16 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a series of `SortKey`'s
|
||||
Initializes a `OrderBy` clause with a series of `SortKey`s
|
||||
|
||||
:param: sortKey a series of `SortKey`'s
|
||||
- parameter sortKey: a series of `SortKey`s
|
||||
*/
|
||||
public init(_ sortKey: [SortKey]) {
|
||||
|
||||
self.init(
|
||||
sortKey.map { SortKey -> NSSortDescriptor in
|
||||
sortKey.map { sortKey -> NSSortDescriptor in
|
||||
|
||||
switch SortKey {
|
||||
switch sortKey {
|
||||
|
||||
case .Ascending(let keyPath):
|
||||
return NSSortDescriptor(key: keyPath, ascending: true)
|
||||
@@ -121,10 +122,10 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a series of `SortKey`'s
|
||||
Initializes a `OrderBy` clause with a series of `SortKey`s
|
||||
|
||||
:param: sortKey a single `SortKey`
|
||||
:param: sortKeys a series of `SortKey`'s
|
||||
- parameter sortKey: a single `SortKey`
|
||||
- parameter sortKeys: a series of `SortKey`s
|
||||
*/
|
||||
public init(_ sortKey: SortKey, _ sortKeys: SortKey...) {
|
||||
|
||||
@@ -140,7 +141,10 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
if fetchRequest.sortDescriptors != nil {
|
||||
|
||||
CoreStore.log(.Warning, message: "Existing sortDescriptors for the <\(NSFetchRequest.self)> was overwritten by \(typeName(self)) query clause.")
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "Existing sortDescriptors for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.sortDescriptors = self.sortDescriptors
|
||||
|
||||
@@ -83,8 +83,8 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Where("employeeID", isEqualTo: 1111)
|
||||
)
|
||||
|
||||
:param: keyPath the attribute name
|
||||
:returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
|
||||
- parameter keyPath: the attribute name
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
|
||||
*/
|
||||
public static func Attribute(keyPath: KeyPath) -> SelectTerm {
|
||||
|
||||
@@ -99,9 +99,9 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Average("age"))
|
||||
)
|
||||
|
||||
:param: keyPath the attribute name
|
||||
:param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
|
||||
:returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
|
||||
*/
|
||||
public static func Average(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
@@ -121,9 +121,9 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Count("employeeID"))
|
||||
)
|
||||
|
||||
:param: keyPath the attribute name
|
||||
:param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
|
||||
:returns: a `SelectTerm` to a `Select` clause for a count query
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for a count query
|
||||
*/
|
||||
public static func Count(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
@@ -143,9 +143,9 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Maximum("age"))
|
||||
)
|
||||
|
||||
:param: keyPath the attribute name
|
||||
:param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
|
||||
:returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
|
||||
*/
|
||||
public static func Maximum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
@@ -165,9 +165,9 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Minimum("age"))
|
||||
)
|
||||
|
||||
:param: keyPath the attribute name
|
||||
:param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
|
||||
:returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
|
||||
*/
|
||||
public static func Minimum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
@@ -187,9 +187,9 @@ public enum SelectTerm: StringLiteralConvertible {
|
||||
Select<Int>(.Sum("age"))
|
||||
)
|
||||
|
||||
:param: keyPath the attribute name
|
||||
:param: alias the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
|
||||
:returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
|
||||
- parameter keyPath: the attribute name
|
||||
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
|
||||
*/
|
||||
public static func Sum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
@@ -267,7 +267,7 @@ Valid return types depend on the query:
|
||||
- for `queryAttributes(...)` methods:
|
||||
- `NSDictionary`
|
||||
|
||||
:param: sortDescriptors a series of `NSSortDescriptor`'s
|
||||
- parameter sortDescriptors: a series of `NSSortDescriptor`s
|
||||
*/
|
||||
public struct Select<T: SelectResultType> {
|
||||
|
||||
@@ -279,10 +279,10 @@ public struct Select<T: SelectResultType> {
|
||||
public typealias ReturnType = T
|
||||
|
||||
/**
|
||||
Initializes a `Select` clause with a list of `SelectTerm`'s
|
||||
Initializes a `Select` clause with a list of `SelectTerm`s
|
||||
|
||||
:param: selectTerm a `SelectTerm`
|
||||
:param: selectTerms a series of `SelectTerm`'s
|
||||
- parameter selectTerm: a `SelectTerm`
|
||||
- parameter selectTerms: a series of `SelectTerm`s
|
||||
*/
|
||||
public init(_ selectTerm: SelectTerm, _ selectTerms: SelectTerm...) {
|
||||
|
||||
@@ -296,7 +296,10 @@ public struct Select<T: SelectResultType> {
|
||||
|
||||
if fetchRequest.propertiesToFetch != nil {
|
||||
|
||||
CoreStore.log(.Warning, message: "An existing \"propertiesToFetch\" for the <\(NSFetchRequest.self)> was overwritten by \(typeName(self)) query clause.")
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "An existing \"propertiesToFetch\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.includesPendingChanges = false
|
||||
@@ -312,17 +315,20 @@ public struct Select<T: SelectResultType> {
|
||||
switch term {
|
||||
|
||||
case ._Attribute(let keyPath):
|
||||
if let propertyDescription = propertiesByName[keyPath] as? NSPropertyDescription {
|
||||
if let propertyDescription = propertiesByName[keyPath] {
|
||||
|
||||
propertiesToFetch.append(propertyDescription)
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.log(.Warning, message: "The property \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by \(typeName(self)) query clause.")
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The property \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
|
||||
if let attributeDescription = attributesByName[keyPath] as? NSAttributeDescription {
|
||||
if let attributeDescription = attributesByName[keyPath] {
|
||||
|
||||
let expressionDescription = NSExpressionDescription()
|
||||
expressionDescription.name = alias
|
||||
@@ -343,7 +349,10 @@ public struct Select<T: SelectResultType> {
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.log(.Warning, message: "The attribute \"\(keyPath)\" does not exist in entity <\(entityDescription.managedObjectClassName)> and will be ignored by \(typeName(self)) query clause.")
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The attribute \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -397,11 +406,11 @@ extension Int8: SelectValueResultType {
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int8? {
|
||||
|
||||
if let value = (result as? NSNumber)?.longLongValue {
|
||||
guard let value = (result as? NSNumber)?.longLongValue else {
|
||||
|
||||
return numericCast(value) as Int8
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return numericCast(value) as Int8
|
||||
}
|
||||
}
|
||||
|
||||
@@ -417,11 +426,11 @@ extension Int16: SelectValueResultType {
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int16? {
|
||||
|
||||
if let value = (result as? NSNumber)?.longLongValue {
|
||||
guard let value = (result as? NSNumber)?.longLongValue else {
|
||||
|
||||
return numericCast(value) as Int16
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return numericCast(value) as Int16
|
||||
}
|
||||
}
|
||||
|
||||
@@ -437,11 +446,11 @@ extension Int32: SelectValueResultType {
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int32? {
|
||||
|
||||
if let value = (result as? NSNumber)?.longLongValue {
|
||||
guard let value = (result as? NSNumber)?.longLongValue else {
|
||||
|
||||
return numericCast(value) as Int32
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return numericCast(value) as Int32
|
||||
}
|
||||
}
|
||||
|
||||
@@ -473,11 +482,11 @@ extension Int: SelectValueResultType {
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int? {
|
||||
|
||||
if let value = (result as? NSNumber)?.longLongValue {
|
||||
guard let value = (result as? NSNumber)?.longLongValue else {
|
||||
|
||||
return numericCast(value) as Int
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return numericCast(value) as Int
|
||||
}
|
||||
}
|
||||
|
||||
@@ -572,7 +581,7 @@ extension NSString: SelectValueResultType {
|
||||
|
||||
// MARK: - NSDecimalNumber: SelectValueResultType
|
||||
|
||||
extension NSDecimalNumber: SelectValueResultType {
|
||||
extension NSDecimalNumber {
|
||||
|
||||
public override class var attributeType: NSAttributeType {
|
||||
|
||||
|
||||
@@ -49,7 +49,7 @@ public struct Tweak: FetchClause, QueryClause, DeleteClause {
|
||||
/**
|
||||
Initializes a `Tweak` clause with a closure where the `NSFetchRequest` may be configured.
|
||||
|
||||
:param: customization a list of key path strings to group results with
|
||||
- parameter customization: a list of key path strings to group results with
|
||||
*/
|
||||
public init(_ customization: (fetchRequest: NSFetchRequest) -> Void) {
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
public func &&(left: Where, right: Where) -> Where {
|
||||
@@ -54,7 +55,7 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
/**
|
||||
Initializes a `Where` clause with an `NSPredicate`
|
||||
|
||||
:param: predicate the `NSPredicate` for the fetch or query
|
||||
- parameter predicate: the `NSPredicate` for the fetch or query
|
||||
*/
|
||||
public init(_ predicate: NSPredicate) {
|
||||
|
||||
@@ -72,7 +73,7 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
/**
|
||||
Initializes a `Where` clause with a predicate that always evaluates to the specified boolean value
|
||||
|
||||
:param: value the boolean value for the predicate
|
||||
- parameter value: the boolean value for the predicate
|
||||
*/
|
||||
public init(_ value: Bool) {
|
||||
|
||||
@@ -82,8 +83,8 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
/**
|
||||
Initializes a `Where` clause with a predicate using the specified string format and arguments
|
||||
|
||||
:param: format the format string for the predicate
|
||||
:param: args the arguments for `format`
|
||||
- parameter format: the format string for the predicate
|
||||
- parameter args: the arguments for `format`
|
||||
*/
|
||||
public init(_ format: String, _ args: NSObject...) {
|
||||
|
||||
@@ -93,8 +94,8 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
/**
|
||||
Initializes a `Where` clause with a predicate using the specified string format and arguments
|
||||
|
||||
:param: format the format string for the predicate
|
||||
:param: argumentArray the arguments for `format`
|
||||
- parameter format: the format string for the predicate
|
||||
- parameter argumentArray: the arguments for `format`
|
||||
*/
|
||||
public init(_ format: String, argumentArray: [NSObject]?) {
|
||||
|
||||
@@ -102,16 +103,38 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause with a predicate using the specified string format and arguments
|
||||
Initializes a `Where` clause that compares equality
|
||||
|
||||
:param: format the format string for the predicate
|
||||
:param: argumentArray the arguments for `format`
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter value: the arguments for the `==` operator
|
||||
*/
|
||||
public init(_ keyPath: KeyPath, isEqualTo value: NSObject?) {
|
||||
|
||||
self.init(value == nil
|
||||
? NSPredicate(format: "\(keyPath) == nil")
|
||||
: NSPredicate(format: "\(keyPath) == %@", value!))
|
||||
: NSPredicate(format: "\(keyPath) == %@", argumentArray: [value!]))
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares membership
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter list: the array to check membership of
|
||||
*/
|
||||
public init(_ keyPath: KeyPath, isMemberOf list: NSArray) {
|
||||
|
||||
self.init(NSPredicate(format: "\(keyPath) IN %@", list))
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares membership
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter list: the sequence to check membership of
|
||||
*/
|
||||
public init<S: SequenceType where S.Generator.Element: NSObject>(_ keyPath: KeyPath, isMemberOf list: S) {
|
||||
|
||||
self.init(NSPredicate(format: "\(keyPath) IN %@", Array(list) as NSArray))
|
||||
}
|
||||
|
||||
public let predicate: NSPredicate
|
||||
@@ -123,7 +146,10 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
if fetchRequest.predicate != nil {
|
||||
|
||||
CoreStore.log(.Warning, message: "An existing predicate for the <\(NSFetchRequest.self)> was overwritten by \(typeName(self)) query clause.")
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "An existing predicate for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.predicate = self.predicate
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
@@ -31,13 +33,62 @@ 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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
- 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)
|
||||
@@ -46,10 +97,11 @@ public extension CoreStore {
|
||||
/**
|
||||
Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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)
|
||||
@@ -58,10 +110,11 @@ public extension CoreStore {
|
||||
/**
|
||||
Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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)
|
||||
@@ -70,34 +123,37 @@ public extension CoreStore {
|
||||
/**
|
||||
Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.
|
||||
Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.
|
||||
Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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)
|
||||
@@ -106,10 +162,11 @@ public extension CoreStore {
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
- 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)
|
||||
@@ -118,94 +175,101 @@ public extension CoreStore {
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
- 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.
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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)
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
@@ -34,16 +36,82 @@ 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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: fetchClauses a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
- 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.")
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchOne(from, fetchClauses)
|
||||
}
|
||||
@@ -51,13 +119,17 @@ public extension DataStack {
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.")
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchOne(from, fetchClauses)
|
||||
}
|
||||
@@ -65,13 +137,17 @@ public extension DataStack {
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.")
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchAll(from, fetchClauses)
|
||||
}
|
||||
@@ -79,41 +155,53 @@ public extension DataStack {
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.")
|
||||
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.
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.")
|
||||
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.
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.")
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchCount(from, fetchClauses)
|
||||
}
|
||||
@@ -121,13 +209,17 @@ public extension DataStack {
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.")
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
@@ -135,109 +227,137 @@ public extension DataStack {
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.")
|
||||
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.
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.")
|
||||
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.
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: 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
|
||||
- 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.")
|
||||
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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.")
|
||||
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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.")
|
||||
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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.")
|
||||
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.
|
||||
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.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: selectClause a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
:param: 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.
|
||||
- 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.")
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
224
CoreStore/Importing Data/BaseDataTransaction+Importing.swift
Normal file
224
CoreStore/Importing Data/BaseDataTransaction+Importing.swift
Normal file
@@ -0,0 +1,224 @@
|
||||
//
|
||||
// BaseDataTransaction+Importing.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - BaseDataTransaction
|
||||
|
||||
public extension BaseDataTransaction {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Creates an `ImportableObject` by importing from the specified import source.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter source: the object to import values from
|
||||
- returns: the created `ImportableObject` instance, or `nil` if the import was ignored
|
||||
*/
|
||||
public func importObject<T where T: NSManagedObject, T: ImportableObject>(
|
||||
into: Into<T>,
|
||||
source: T.ImportSource) throws -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
|
||||
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let object = self.create(into)
|
||||
try object.didInsertFromImportSource(source, inTransaction: self)
|
||||
return object
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Creates multiple `ImportableObject`s by importing from the specified array of import sources.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter sourceArray: the array of objects to import values from
|
||||
- returns: the array of created `ImportableObject` instances
|
||||
*/
|
||||
public func importObjects<T, S: SequenceType where T: NSManagedObject, T: ImportableObject, S.Generator.Element == T.ImportSource>(
|
||||
into: Into<T>,
|
||||
sourceArray: S) throws -> [T] {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
|
||||
return try sourceArray.flatMap { (source) -> T? in
|
||||
|
||||
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return try autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
try object.didInsertFromImportSource(source, inTransaction: self)
|
||||
return object
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Updates an existing `ImportableUniqueObject` or creates a new instance by importing from the specified import source.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter source: the object to import values from
|
||||
- returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored
|
||||
*/
|
||||
public func importUniqueObject<T where T: NSManagedObject, T: ImportableUniqueObject>(
|
||||
into: Into<T>,
|
||||
source: T.ImportSource) throws -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
|
||||
let uniqueIDKeyPath = T.uniqueIDKeyPath
|
||||
guard let uniqueIDValue = try T.uniqueIDFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if let object = self.fetchOne(From(T), Where(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) {
|
||||
|
||||
guard T.shouldUpdateFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
try object.updateFromImportSource(source, inTransaction: self)
|
||||
return object
|
||||
}
|
||||
else {
|
||||
|
||||
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let object = self.create(into)
|
||||
object.uniqueIDValue = uniqueIDValue
|
||||
try object.didInsertFromImportSource(source, inTransaction: self)
|
||||
return object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter sourceArray: the array of objects to import values from
|
||||
- parameter preProcess: a closure that lets the caller tweak the internal `UniqueIDType`-to-`ImportSource` mapping to be used for importing. Callers can remove from/add to/update `mapping` and return the updated array from the closure.
|
||||
- returns: the array of created/updated `ImportableUniqueObject` instances
|
||||
*/
|
||||
public func importUniqueObjects<T, S: SequenceType where T: NSManagedObject, T: ImportableUniqueObject, S.Generator.Element == T.ImportSource>(
|
||||
into: Into<T>,
|
||||
sourceArray: S,
|
||||
@noescape preProcess: (mapping: [T.UniqueIDType: T.ImportSource]) throws -> [T.UniqueIDType: T.ImportSource] = { $0 }) throws -> [T] {
|
||||
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
|
||||
var mapping = Dictionary<T.UniqueIDType, T.ImportSource>()
|
||||
let sortedIDs = try autoreleasepool {
|
||||
|
||||
return try sourceArray.flatMap { (source) -> T.UniqueIDType? in
|
||||
|
||||
guard let uniqueIDValue = try T.uniqueIDFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
mapping[uniqueIDValue] = source
|
||||
return uniqueIDValue
|
||||
}
|
||||
}
|
||||
|
||||
mapping = try autoreleasepool { try preProcess(mapping: mapping) }
|
||||
|
||||
var objects = Dictionary<T.UniqueIDType, T>()
|
||||
for object in self.fetchAll(From(T), Where(T.uniqueIDKeyPath, isMemberOf: mapping.keys)) ?? [] {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let uniqueIDValue = object.uniqueIDValue
|
||||
|
||||
guard let source = mapping.removeValueForKey(uniqueIDValue)
|
||||
where T.shouldUpdateFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
try object.updateFromImportSource(source, inTransaction: self)
|
||||
objects[uniqueIDValue] = object
|
||||
}
|
||||
}
|
||||
|
||||
for (uniqueIDValue, source) in mapping {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let object = self.create(into)
|
||||
object.uniqueIDValue = uniqueIDValue
|
||||
try object.didInsertFromImportSource(source, inTransaction: self)
|
||||
|
||||
objects[uniqueIDValue] = object
|
||||
}
|
||||
}
|
||||
|
||||
return sortedIDs.flatMap { objects[$0] }
|
||||
}
|
||||
}
|
||||
}
|
||||
84
CoreStore/Importing Data/ImportableObject.swift
Normal file
84
CoreStore/Importing Data/ImportableObject.swift
Normal file
@@ -0,0 +1,84 @@
|
||||
//
|
||||
// ImportableObject.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: - ImportableObject
|
||||
|
||||
/**
|
||||
`NSManagedObject` subclasses that conform to the `ImportableObject` protocol can be imported from a specified `ImportSource`. This allows transactions to create and insert instances this way:
|
||||
|
||||
class MyPersonEntity: NSManagedObject, ImportableObject {
|
||||
typealias ImportSource = NSDictionary
|
||||
// ...
|
||||
}
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
let json: NSDictionary = // ...
|
||||
let person = try! transaction.importObject(
|
||||
Into(MyPersonEntity),
|
||||
source: json
|
||||
)
|
||||
// ...
|
||||
transaction.commit()
|
||||
}
|
||||
*/
|
||||
public protocol ImportableObject: class {
|
||||
|
||||
/**
|
||||
The data type for the import source. This is most commonly an `NSDictionary` or another external source such as an `NSUserDefaults`.
|
||||
*/
|
||||
typealias ImportSource
|
||||
|
||||
/**
|
||||
Return `true` if an object should be created 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 created from `source`. Return `false` to ignore.
|
||||
*/
|
||||
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
|
||||
|
||||
/**
|
||||
Implements the actual importing of data from `source`. 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 `importObjects(: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 didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ImportableObject (Default Implementations)
|
||||
|
||||
public extension ImportableObject {
|
||||
|
||||
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
136
CoreStore/Importing Data/ImportableUniqueObject.swift
Normal file
136
CoreStore/Importing Data/ImportableUniqueObject.swift
Normal file
@@ -0,0 +1,136 @@
|
||||
//
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.johnestropia.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>FMWK</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>0.2.0</string>
|
||||
<string>1.4.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -42,27 +42,27 @@ internal func getAssociatedObjectForKey<T: AnyObject>(key: UnsafePointer<Void>,
|
||||
|
||||
internal func setAssociatedRetainedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
|
||||
internal func setAssociatedCopiedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, UInt(OBJC_ASSOCIATION_COPY_NONATOMIC))
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_COPY_NONATOMIC)
|
||||
}
|
||||
|
||||
internal func setAssociatedAssignedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, UInt(OBJC_ASSOCIATION_ASSIGN))
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_ASSIGN)
|
||||
}
|
||||
|
||||
internal func setAssociatedWeakObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
if let associatedObject = associatedObject {
|
||||
|
||||
objc_setAssociatedObject(object, key, WeakObject(associatedObject), UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
|
||||
objc_setAssociatedObject(object, key, WeakObject(associatedObject), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
else {
|
||||
|
||||
objc_setAssociatedObject(object, key, nil, UInt(OBJC_ASSOCIATION_RETAIN_NONATOMIC))
|
||||
objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
|
||||
219
CoreStore/Internal/FetchedResultsControllerDelegate.swift
Normal file
219
CoreStore/Internal/FetchedResultsControllerDelegate.swift
Normal file
@@ -0,0 +1,219 @@
|
||||
//
|
||||
// FetchedResultsControllerDelegate.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: - FetchedResultsControllerHandler
|
||||
|
||||
@available(OSX, unavailable)
|
||||
internal protocol FetchedResultsControllerHandler: class {
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
|
||||
|
||||
func controllerWillChangeContent(controller: NSFetchedResultsController)
|
||||
|
||||
func controllerDidChangeContent(controller: NSFetchedResultsController)
|
||||
|
||||
func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FetchedResultsControllerDelegate
|
||||
|
||||
@available(OSX, unavailable)
|
||||
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal var enabled = true
|
||||
|
||||
internal weak var handler: FetchedResultsControllerHandler?
|
||||
internal weak var fetchedResultsController: NSFetchedResultsController? {
|
||||
|
||||
didSet {
|
||||
|
||||
oldValue?.delegate = nil
|
||||
self.fetchedResultsController?.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.fetchedResultsController?.delegate = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSFetchedResultsControllerDelegate
|
||||
|
||||
@objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
guard self.enabled else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.deletedSections = []
|
||||
self.insertedSections = []
|
||||
|
||||
self.handler?.controllerWillChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
guard self.enabled else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.handler?.controllerDidChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
guard self.enabled else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
guard let actualType = NSFetchedResultsChangeType(rawValue: type.rawValue) else {
|
||||
|
||||
// This fix is for a bug where iOS passes 0 for NSFetchedResultsChangeType, but this is not a valid enum case.
|
||||
// Swift will then always execute the first case of the switch causing strange behaviour.
|
||||
// https://forums.developer.apple.com/thread/12184#31850
|
||||
return
|
||||
}
|
||||
|
||||
// This whole dance is a workaround for a nasty bug introduced in XCode 7 targeted at iOS 8 devices
|
||||
// http://stackoverflow.com/questions/31383760/ios-9-attempt-to-delete-and-reload-the-same-index-path/31384014#31384014
|
||||
// https://forums.developer.apple.com/message/9998#9998
|
||||
// https://forums.developer.apple.com/message/31849#31849
|
||||
|
||||
switch actualType {
|
||||
|
||||
case .Update:
|
||||
guard let section = indexPath?.indexAtPosition(0) else {
|
||||
|
||||
return
|
||||
}
|
||||
if self.deletedSections.contains(section)
|
||||
|| self.insertedSections.contains(section) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
case .Move:
|
||||
guard let indexPath = indexPath, let newIndexPath = newIndexPath else {
|
||||
|
||||
return
|
||||
}
|
||||
guard indexPath == newIndexPath else {
|
||||
|
||||
break
|
||||
}
|
||||
if self.insertedSections.contains(indexPath.indexAtPosition(0)) {
|
||||
|
||||
// Observers that handle the .Move change are advised to delete then reinsert the object instead of just moving. This is especially true when indexPath and newIndexPath are equal. For example, calling tableView.moveRowAtIndexPath(_:toIndexPath) when both indexPaths are the same will crash the tableView.
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeObject: anObject,
|
||||
atIndexPath: indexPath,
|
||||
forChangeType: .Move,
|
||||
newIndexPath: newIndexPath
|
||||
)
|
||||
return
|
||||
}
|
||||
if self.deletedSections.contains(indexPath.indexAtPosition(0)) {
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeObject: anObject,
|
||||
atIndexPath: nil,
|
||||
forChangeType: .Insert,
|
||||
newIndexPath: indexPath
|
||||
)
|
||||
return
|
||||
}
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeObject: anObject,
|
||||
atIndexPath: indexPath,
|
||||
forChangeType: .Update,
|
||||
newIndexPath: nil
|
||||
)
|
||||
return
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeObject: anObject,
|
||||
atIndexPath: indexPath,
|
||||
forChangeType: actualType,
|
||||
newIndexPath: newIndexPath
|
||||
)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
|
||||
guard self.enabled else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
switch type {
|
||||
|
||||
case .Delete: self.deletedSections.insert(sectionIndex)
|
||||
case .Insert: self.insertedSections.insert(sectionIndex)
|
||||
default: break
|
||||
}
|
||||
|
||||
self.handler?.controller(
|
||||
controller,
|
||||
didChangeSection: sectionInfo,
|
||||
atIndex: sectionIndex,
|
||||
forChangeType: type
|
||||
)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
|
||||
|
||||
return self.handler?.controller(
|
||||
controller,
|
||||
sectionIndexTitleForSectionName: sectionName
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var deletedSections = Set<Int>()
|
||||
private var insertedSections = Set<Int>()
|
||||
}
|
||||
81
CoreStore/Internal/Functions.swift
Normal file
81
CoreStore/Internal/Functions.swift
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// Functions.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
internal func autoreleasepool<T>(@noescape closure: () -> T) -> T {
|
||||
|
||||
var closureValue: T!
|
||||
ObjectiveC.autoreleasepool {
|
||||
|
||||
closureValue = closure()
|
||||
}
|
||||
|
||||
return closureValue
|
||||
}
|
||||
|
||||
internal func autoreleasepool<T>(@noescape closure: () throws -> T) throws -> T {
|
||||
|
||||
var closureValue: T!
|
||||
var closureError: ErrorType?
|
||||
ObjectiveC.autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
closureValue = try closure()
|
||||
}
|
||||
catch {
|
||||
|
||||
closureError = error
|
||||
}
|
||||
}
|
||||
|
||||
if let closureError = closureError {
|
||||
|
||||
throw closureError
|
||||
}
|
||||
return closureValue
|
||||
}
|
||||
|
||||
internal func autoreleasepool(@noescape closure: () throws -> Void) throws {
|
||||
|
||||
var closureError: ErrorType?
|
||||
ObjectiveC.autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
try closure()
|
||||
}
|
||||
catch {
|
||||
|
||||
closureError = error
|
||||
}
|
||||
}
|
||||
|
||||
if let closureError = closureError {
|
||||
|
||||
throw closureError
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// DetachedDataTransaction.swift
|
||||
// MigrationManager.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
@@ -24,32 +24,39 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GCDKit
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DetachedDataTransaction
|
||||
// MARK: - MigrationManager
|
||||
|
||||
/**
|
||||
The `DetachedDataTransaction` provides an interface for non-contiguous `NSManagedObject` creates, updates, and deletes. This is useful for making temporary changes, such as partially filled forms. A detached transaction object should typically be only used from the main queue.
|
||||
*/
|
||||
public final class DetachedDataTransaction: BaseDataTransaction {
|
||||
internal final class MigrationManager: NSMigrationManager, NSProgressReporting {
|
||||
|
||||
// MARK: Public
|
||||
// MARK: NSObject
|
||||
|
||||
/**
|
||||
Saves the transaction changes asynchronously. For a `DetachedDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
|
||||
|
||||
:param: completion the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
|
||||
*/
|
||||
public func commit(completion: (result: SaveResult) -> Void) {
|
||||
override func didChangeValueForKey(key: String) {
|
||||
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue.")
|
||||
super.didChangeValueForKey(key)
|
||||
|
||||
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
|
||||
guard key == "migrationProgress" else {
|
||||
|
||||
self.result = result
|
||||
completion(result: result)
|
||||
return
|
||||
}
|
||||
let progress = self.progress
|
||||
progress.completedUnitCount = Int64(Float(progress.totalUnitCount) * self.migrationProgress)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSMigrationManager
|
||||
|
||||
init(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, progress: NSProgress) {
|
||||
|
||||
self.progress = progress
|
||||
|
||||
super.init(sourceModel: sourceModel, destinationModel: destinationModel)
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSProgressReporting
|
||||
|
||||
let progress: NSProgress
|
||||
}
|
||||
26
CoreStore/Internal/NSFileManager+Setup.swift
Normal file
26
CoreStore/Internal/NSFileManager+Setup.swift
Normal file
@@ -0,0 +1,26 @@
|
||||
//
|
||||
// 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 @@
|
||||
//
|
||||
// NSManagedObject+Transaction.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: - NSManagedObject
|
||||
|
||||
internal extension NSManagedObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal class func createInContext(context: NSManagedObjectContext) -> Self {
|
||||
|
||||
return self(
|
||||
entity: context.entityDescriptionForEntityClass(self)!,
|
||||
insertIntoManagedObjectContext: context
|
||||
)
|
||||
}
|
||||
|
||||
internal class func inContext(context: NSManagedObjectContext, withObjectID objectID: NSManagedObjectID) -> Self? {
|
||||
|
||||
return self.typedObjectInContext(context, objectID: objectID)
|
||||
}
|
||||
|
||||
internal func inContext(context: NSManagedObjectContext) -> Self? {
|
||||
|
||||
return self.typedObjectInContext(context)
|
||||
}
|
||||
|
||||
internal func deleteFromContext() {
|
||||
|
||||
self.managedObjectContext?.deleteObject(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private class func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext, objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
var error: NSError?
|
||||
if let existingObject = context.existingObjectWithID(objectID, error: &error) {
|
||||
|
||||
return (existingObject as! T)
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to load existing \(typeName(self)) in context.")
|
||||
return nil;
|
||||
}
|
||||
|
||||
private func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext) -> T? {
|
||||
|
||||
let objectID = self.objectID
|
||||
if objectID.temporaryID {
|
||||
|
||||
var error: NSError?
|
||||
let didSucceed = withExtendedLifetime(self.managedObjectContext) {
|
||||
|
||||
return $0?.obtainPermanentIDsForObjects([self], error: &error)
|
||||
}
|
||||
if didSucceed != true {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to obtain permanent ID for object.")
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
var error: NSError?
|
||||
if let existingObject = context.existingObjectWithID(objectID, error: &error) {
|
||||
|
||||
return (existingObject as! T)
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to load existing \(typeName(self)) in context.")
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
@@ -25,7 +25,9 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectContext
|
||||
@@ -54,24 +56,26 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
}
|
||||
|
||||
internal func entityDescriptionForEntityClass(entity: NSManagedObject.Type) -> NSEntityDescription? {
|
||||
internal func entityDescriptionForEntityType(entity: NSManagedObject.Type) -> NSEntityDescription? {
|
||||
|
||||
if let entityName = self.parentStack?.entityNameForEntityClass(entity) {
|
||||
|
||||
return NSEntityDescription.entityForName(
|
||||
entityName,
|
||||
inManagedObjectContext: self
|
||||
)
|
||||
return self.entityDescriptionForEntityClass(entity)
|
||||
}
|
||||
|
||||
internal func entityDescriptionForEntityClass(entity: AnyClass) -> NSEntityDescription? {
|
||||
|
||||
guard let entityName = self.parentStack?.entityNameForEntityClass(entity) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
return NSEntityDescription.entityForName(
|
||||
entityName,
|
||||
inManagedObjectContext: self
|
||||
)
|
||||
}
|
||||
|
||||
internal func setupForCoreStoreWithContextName(contextName: String) {
|
||||
|
||||
if self.respondsToSelector("setName:") {
|
||||
|
||||
self.name = contextName
|
||||
}
|
||||
self.name = contextName
|
||||
|
||||
self.observerForWillSaveNotification = NotificationObserver(
|
||||
notificationName: NSManagedObjectContextWillSaveNotification,
|
||||
@@ -81,21 +85,22 @@ internal extension NSManagedObjectContext {
|
||||
let context = note.object as! NSManagedObjectContext
|
||||
let insertedObjects = context.insertedObjects
|
||||
let numberOfInsertedObjects = insertedObjects.count
|
||||
if numberOfInsertedObjects <= 0 {
|
||||
guard numberOfInsertedObjects > 0 else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var error: NSError?
|
||||
if context.obtainPermanentIDsForObjects(Array(insertedObjects), error: &error) {
|
||||
do {
|
||||
|
||||
return
|
||||
try context.obtainPermanentIDsForObjects(Array(insertedObjects))
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to obtain permanent ID(s) for \(numberOfInsertedObjects) inserted object(s)."
|
||||
)
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to obtain permanent ID(s) for \(numberOfInsertedObjects) inserted object(s)."
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@@ -31,7 +31,43 @@ import CoreData
|
||||
|
||||
internal extension NSManagedObjectContext {
|
||||
|
||||
// MARK: Public
|
||||
// MARK: Internal
|
||||
|
||||
internal func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
if object.objectID.temporaryID {
|
||||
|
||||
do {
|
||||
|
||||
try withExtendedLifetime(self) { (context: NSManagedObjectContext) -> Void in
|
||||
|
||||
try context.obtainPermanentIDsForObjects([object])
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to obtain permanent ID for object."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.existingObjectWithID(object.objectID)
|
||||
return (existingObject as! T)
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load existing \(typeName(object)) in context."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
internal func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
@@ -52,16 +88,24 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
var fetchResults: [T]?
|
||||
var error: NSError?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T]
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest) as? [T]
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request.")
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -87,16 +131,24 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
var fetchResults: [T]?
|
||||
var error: NSError?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T]
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest) as? [T]
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request.")
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -128,7 +180,8 @@ internal extension NSManagedObjectContext {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request.")
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -154,16 +207,24 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
var fetchResults: [NSManagedObjectID]?
|
||||
var error: NSError?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObjectID]
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest) as? [NSManagedObjectID]
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request.")
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -189,16 +250,24 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
var fetchResults: [NSManagedObjectID]?
|
||||
var error: NSError?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [NSManagedObjectID]
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest) as? [NSManagedObjectID]
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request.")
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -218,6 +287,7 @@ internal extension NSManagedObjectContext {
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchRequest.returnsObjectsAsFaults = true
|
||||
fetchRequest.includesPropertyValues = false
|
||||
|
||||
for clause in deleteClauses {
|
||||
|
||||
@@ -225,26 +295,32 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
var numberOfDeletedObjects: Int?
|
||||
var error: NSError?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
autoreleasepool {
|
||||
|
||||
if let fetchResults = self.executeFetchRequest(fetchRequest, error: &error) as? [T] {
|
||||
do {
|
||||
|
||||
numberOfDeletedObjects = fetchResults.count
|
||||
let fetchResults = try self.executeFetchRequest(fetchRequest) as? [T] ?? []
|
||||
for object in fetchResults {
|
||||
|
||||
self.deleteObject(object)
|
||||
}
|
||||
numberOfDeletedObjects = fetchResults.count
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
}
|
||||
if numberOfDeletedObjects == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request.")
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -271,10 +347,17 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
var fetchResults: [AnyObject]?
|
||||
var error: NSError?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
fetchResults = self.executeFetchRequest(fetchRequest, error: &error)
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest)
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
@@ -287,8 +370,9 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request.")
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -312,10 +396,17 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
var fetchResults: [AnyObject]?
|
||||
var error: NSError?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
fetchResults = self.executeFetchRequest(fetchRequest, error: &error)
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest)
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
@@ -323,8 +414,9 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request.")
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
set {
|
||||
|
||||
if self.parentContext != nil {
|
||||
guard self.parentContext == nil else {
|
||||
|
||||
return
|
||||
}
|
||||
@@ -74,7 +74,7 @@ internal extension NSManagedObjectContext {
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
|
||||
context.parentContext = rootContext
|
||||
context.shouldCascadeSavesToParent = true
|
||||
context.mergePolicy = NSRollbackMergePolicy
|
||||
context.undoManager = nil
|
||||
context.setupForCoreStoreWithContextName("com.corestore.maincontext")
|
||||
context.observerForDidSaveNotification = NotificationObserver(
|
||||
@@ -82,11 +82,15 @@ internal extension NSManagedObjectContext {
|
||||
object: rootContext,
|
||||
closure: { [weak context] (note) -> Void in
|
||||
|
||||
context?.performBlockAndWait { () -> Void in
|
||||
context?.performBlock { () -> Void in
|
||||
|
||||
let updatedObjects = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? []
|
||||
for object in updatedObjects {
|
||||
|
||||
context?.objectWithID(object.objectID).willAccessValueForKey(nil)
|
||||
}
|
||||
context?.mergeChangesFromContextDidSaveNotification(note)
|
||||
}
|
||||
return
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectContext
|
||||
@@ -53,6 +55,15 @@ internal extension NSManagedObjectContext {
|
||||
}
|
||||
}
|
||||
|
||||
internal func isRunningInAllowedQueue() -> Bool {
|
||||
|
||||
guard let parentTransaction = self.parentTransaction else {
|
||||
|
||||
return false
|
||||
}
|
||||
return parentTransaction.isRunningInAllowedQueue()
|
||||
}
|
||||
|
||||
internal func temporaryContextInTransactionWithConcurrencyType(concurrencyType: NSManagedObjectContextConcurrencyType) -> NSManagedObjectContext {
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: concurrencyType)
|
||||
@@ -68,112 +79,90 @@ internal extension NSManagedObjectContext {
|
||||
internal func saveSynchronously() -> SaveResult {
|
||||
|
||||
var result = SaveResult(hasChanges: false)
|
||||
self.performBlockAndWait {
|
||||
[unowned self] () -> Void in
|
||||
|
||||
self.performBlockAndWait { [unowned self] () -> Void in
|
||||
|
||||
if !self.hasChanges {
|
||||
guard self.hasChanges else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
var saveError: NSError?
|
||||
if self.save(&saveError) {
|
||||
do {
|
||||
|
||||
if self.shouldCascadeSavesToParent {
|
||||
|
||||
if let parentContext = self.parentContext {
|
||||
|
||||
switch parentContext.saveSynchronously() {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
result = SaveResult(hasChanges: true)
|
||||
case .Failure(let error):
|
||||
result = SaveResult(error)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
result = SaveResult(hasChanges: true)
|
||||
try self.save()
|
||||
}
|
||||
else if let error = saveError {
|
||||
catch {
|
||||
|
||||
let saveError = error as NSError
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to save <\(NSManagedObjectContext.self)>.")
|
||||
result = SaveResult(error)
|
||||
saveError,
|
||||
"Failed to save \(typeName(NSManagedObjectContext))."
|
||||
)
|
||||
result = SaveResult(saveError)
|
||||
return
|
||||
}
|
||||
|
||||
if let parentContext = self.parentContext where self.shouldCascadeSavesToParent {
|
||||
|
||||
switch parentContext.saveSynchronously() {
|
||||
|
||||
case .Success:
|
||||
result = SaveResult(hasChanges: true)
|
||||
|
||||
case .Failure(let error):
|
||||
result = SaveResult(error)
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
result = SaveResult(hasChanges: false)
|
||||
result = SaveResult(hasChanges: true)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
internal func saveAsynchronouslyWithCompletion(completion: ((result: SaveResult) -> Void)?) {
|
||||
internal func saveAsynchronouslyWithCompletion(completion: ((result: SaveResult) -> Void) = { _ in }) {
|
||||
|
||||
self.performBlock { () -> Void in
|
||||
|
||||
if !self.hasChanges {
|
||||
|
||||
if let completion = completion {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(hasChanges: false))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var saveError: NSError?
|
||||
if self.save(&saveError) {
|
||||
|
||||
if self.shouldCascadeSavesToParent {
|
||||
|
||||
if let parentContext = self.parentContext {
|
||||
|
||||
let result = parentContext.saveSynchronously()
|
||||
if let completion = completion {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: result)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if let completion = completion {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(hasChanges: true))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if let error = saveError {
|
||||
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to save <\(NSManagedObjectContext.self)>.")
|
||||
if let completion = completion {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
else if let completion = completion {
|
||||
guard self.hasChanges else {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(hasChanges: false))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try self.save()
|
||||
}
|
||||
catch {
|
||||
|
||||
let saveError = error as NSError
|
||||
CoreStore.handleError(
|
||||
saveError,
|
||||
"Failed to save \(typeName(NSManagedObjectContext))."
|
||||
)
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(saveError))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if let parentContext = self.parentContext where self.shouldCascadeSavesToParent {
|
||||
|
||||
parentContext.saveAsynchronouslyWithCompletion(completion)
|
||||
}
|
||||
else {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(hasChanges: true))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
279
CoreStore/Internal/NSManagedObjectModel+Setup.swift
Normal file
279
CoreStore/Internal/NSManagedObjectModel+Setup.swift
Normal file
@@ -0,0 +1,279 @@
|
||||
//
|
||||
// NSManagedObjectModel+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectModel
|
||||
|
||||
internal extension NSManagedObjectModel {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc internal class func fromBundle(bundle: NSBundle, modelName: String, modelVersionHints: Set<String> = []) -> NSManagedObjectModel {
|
||||
|
||||
guard let modelFilePath = bundle.pathForResource(modelName, ofType: "momd") else {
|
||||
|
||||
fatalError("Could not find \"\(modelName).momd\" from the bundle. \(bundle)")
|
||||
}
|
||||
|
||||
let modelFileURL = NSURL(fileURLWithPath: modelFilePath)
|
||||
let versionInfoPlistURL = modelFileURL.URLByAppendingPathComponent("VersionInfo.plist", isDirectory: false)
|
||||
|
||||
guard let versionInfo = NSDictionary(contentsOfURL: versionInfoPlistURL),
|
||||
let versionHashes = versionInfo["NSManagedObjectModel_VersionHashes"] as? [String: AnyObject] else {
|
||||
|
||||
fatalError("Could not load \(typeName(NSManagedObjectModel)) metadata from path \"\(versionInfoPlistURL)\".")
|
||||
}
|
||||
|
||||
let modelVersions = Set(versionHashes.keys)
|
||||
let currentModelVersion: String
|
||||
if let plistModelVersion = versionInfo["NSManagedObjectModel_CurrentVersionName"] as? String where modelVersionHints.isEmpty || modelVersionHints.contains(plistModelVersion) {
|
||||
|
||||
currentModelVersion = plistModelVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.intersect(modelVersionHints).first {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The MigrationChain leaf versions do not include the model file's current version. Resolving to version \"\(resolvedVersion)\"."
|
||||
)
|
||||
currentModelVersion = resolvedVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.first ?? modelVersionHints.first {
|
||||
|
||||
if !modelVersionHints.isEmpty {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The MigrationChain leaf versions do not include any of the model file's embedded versions. Resolving to version \"\(resolvedVersion)\"."
|
||||
)
|
||||
}
|
||||
currentModelVersion = resolvedVersion
|
||||
}
|
||||
else {
|
||||
|
||||
fatalError("No model files were found in URL \"\(modelFileURL)\".")
|
||||
}
|
||||
|
||||
var modelVersionFileURL: NSURL?
|
||||
for modelVersion in modelVersions {
|
||||
|
||||
let fileURL = modelFileURL.URLByAppendingPathComponent("\(modelVersion).mom", isDirectory: false)
|
||||
|
||||
if modelVersion == currentModelVersion {
|
||||
|
||||
modelVersionFileURL = fileURL
|
||||
continue
|
||||
}
|
||||
|
||||
precondition(
|
||||
NSManagedObjectModel(contentsOfURL: fileURL) != nil,
|
||||
"Could not find the \"\(modelVersion).mom\" version file for the model at URL \"\(modelFileURL)\"."
|
||||
)
|
||||
}
|
||||
|
||||
if let modelVersionFileURL = modelVersionFileURL,
|
||||
let rootModel = NSManagedObjectModel(contentsOfURL: modelVersionFileURL) {
|
||||
|
||||
rootModel.modelVersionFileURL = modelVersionFileURL
|
||||
rootModel.modelVersions = modelVersions
|
||||
rootModel.currentModelVersion = currentModelVersion
|
||||
return rootModel
|
||||
}
|
||||
|
||||
fatalError("Could not create an \(typeName(NSManagedObjectModel)) from the model at URL \"\(modelFileURL)\".")
|
||||
}
|
||||
|
||||
@nonobjc private(set) internal var currentModelVersion: String? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSString? = getAssociatedObjectForKey(
|
||||
&PropertyKeys.currentModelVersion,
|
||||
inObject: self
|
||||
)
|
||||
return value as? String
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedCopiedObject(
|
||||
newValue == nil ? nil : (newValue! as NSString),
|
||||
forKey: &PropertyKeys.currentModelVersion,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc private(set) internal var modelVersions: Set<String>? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSSet? = getAssociatedObjectForKey(
|
||||
&PropertyKeys.modelVersions,
|
||||
inObject: self
|
||||
)
|
||||
return value as? Set<String>
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedCopiedObject(
|
||||
newValue == nil ? nil : (newValue! as NSSet),
|
||||
forKey: &PropertyKeys.modelVersions,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc internal func entityNameForClass(entityClass: AnyClass) -> String {
|
||||
|
||||
return self.entityNameMapping[NSStringFromClass(entityClass)]!
|
||||
}
|
||||
|
||||
@nonobjc internal func entityTypesMapping() -> [String: NSManagedObject.Type] {
|
||||
|
||||
return self.entityNameMapping.reduce([:]) { (var mapping, pair) in
|
||||
|
||||
mapping[pair.1] = (NSClassFromString(pair.0)! as! NSManagedObject.Type)
|
||||
return mapping
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc internal func mergedModels() -> [NSManagedObjectModel] {
|
||||
|
||||
return self.modelVersions?.map { self[$0] }.flatMap { $0 == nil ? [] : [$0!] } ?? [self]
|
||||
}
|
||||
|
||||
@nonobjc internal subscript(modelVersion: String) -> NSManagedObjectModel? {
|
||||
|
||||
if modelVersion == self.currentModelVersion {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
guard let modelFileURL = self.modelFileURL,
|
||||
let modelVersions = self.modelVersions
|
||||
where modelVersions.contains(modelVersion) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let versionModelFileURL = modelFileURL.URLByAppendingPathComponent("\(modelVersion).mom", isDirectory: false)
|
||||
guard let model = NSManagedObjectModel(contentsOfURL: versionModelFileURL) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
model.currentModelVersion = modelVersion
|
||||
model.modelVersionFileURL = versionModelFileURL
|
||||
model.modelVersions = modelVersions
|
||||
return model
|
||||
}
|
||||
|
||||
@nonobjc internal subscript(metadata: [String: AnyObject]) -> NSManagedObjectModel? {
|
||||
|
||||
guard let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : NSData] else {
|
||||
|
||||
return nil
|
||||
}
|
||||
for modelVersion in self.modelVersions ?? [] {
|
||||
|
||||
if let versionModel = self[modelVersion] where modelHashes == versionModel.entityVersionHashesByName {
|
||||
|
||||
return versionModel
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var modelFileURL: NSURL? {
|
||||
|
||||
get {
|
||||
|
||||
return self.modelVersionFileURL?.URLByDeletingLastPathComponent
|
||||
}
|
||||
}
|
||||
|
||||
private var modelVersionFileURL: NSURL? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSURL? = getAssociatedObjectForKey(
|
||||
&PropertyKeys.modelVersionFileURL,
|
||||
inObject: self
|
||||
)
|
||||
return value
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedCopiedObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.modelVersionFileURL,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var entityNameMapping: [String: String] {
|
||||
|
||||
get {
|
||||
|
||||
if let mapping: NSDictionary = getAssociatedObjectForKey(&PropertyKeys.entityNameMapping, inObject: self) {
|
||||
|
||||
return mapping as! [String: String]
|
||||
}
|
||||
|
||||
let mapping = self.entities.reduce([String: String]()) {
|
||||
(var mapping, entityDescription) -> [String: String] in
|
||||
|
||||
if let entityName = entityDescription.name {
|
||||
|
||||
let className = entityDescription.managedObjectClassName
|
||||
mapping[className] = entityName
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
setAssociatedCopiedObject(
|
||||
mapping as NSDictionary,
|
||||
forKey: &PropertyKeys.entityNameMapping,
|
||||
inObject: self
|
||||
)
|
||||
return mapping
|
||||
}
|
||||
}
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var entityNameMapping: Void?
|
||||
|
||||
static var modelVersionFileURL: Void?
|
||||
static var modelVersions: Void?
|
||||
static var currentModelVersion: Void?
|
||||
}
|
||||
}
|
||||
@@ -36,7 +36,7 @@ internal final class NotificationObserver {
|
||||
let object: AnyObject?
|
||||
let observer: NSObjectProtocol
|
||||
|
||||
init(notificationName: String, object: AnyObject?, closure: (note: NSNotification!) -> Void) {
|
||||
init(notificationName: String, object: AnyObject?, closure: (note: NSNotification) -> Void) {
|
||||
|
||||
self.notificationName = notificationName
|
||||
self.object = object
|
||||
|
||||
@@ -46,10 +46,8 @@ public extension CoreStore {
|
||||
level: level,
|
||||
message: message,
|
||||
fileName: fileName,
|
||||
lineNumber:
|
||||
lineNumber,
|
||||
functionName:
|
||||
functionName
|
||||
lineNumber: lineNumber,
|
||||
functionName: functionName
|
||||
)
|
||||
}
|
||||
|
||||
@@ -60,7 +58,8 @@ public extension CoreStore {
|
||||
message: message,
|
||||
fileName: fileName,
|
||||
lineNumber: lineNumber,
|
||||
functionName: functionName)
|
||||
functionName: functionName
|
||||
)
|
||||
}
|
||||
|
||||
internal static func assert(@autoclosure condition: () -> Bool, _ message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) {
|
||||
@@ -70,6 +69,7 @@ public extension CoreStore {
|
||||
message: message,
|
||||
fileName: fileName,
|
||||
lineNumber: lineNumber,
|
||||
functionName: functionName)
|
||||
functionName: functionName
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public protocol CoreStoreLogger {
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
|
||||
/**
|
||||
Handles errors sent by the `CoreStore` framework.
|
||||
@@ -67,7 +67,7 @@ public protocol CoreStoreLogger {
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
|
||||
/**
|
||||
Handles assertions made throughout the `CoreStore` framework.
|
||||
@@ -86,5 +86,21 @@ public protocol CoreStoreLogger {
|
||||
|
||||
internal func typeName<T>(value: T) -> String {
|
||||
|
||||
return "<\(_stdlib_getDemangledTypeName(value))>"
|
||||
return "'\(_stdlib_getDemangledTypeName(value))'"
|
||||
}
|
||||
|
||||
internal func typeName<T>(value: T.Type) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(value: AnyClass) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(name: String?) -> String {
|
||||
|
||||
let typeName = name ?? "unknown"
|
||||
return "<\(typeName)>"
|
||||
}
|
||||
|
||||
@@ -31,39 +31,57 @@ import Foundation
|
||||
/**
|
||||
The `DefaultLogger` is a basic implementation of the `CoreStoreLogger` protocol.
|
||||
|
||||
- The `log(...)` method calls `println(...)` to print the level, source file name, line number, function name, and the log message.
|
||||
- The `handleError(...)` method calls `println(...)` to print the source file name, line number, function name, and the error message.
|
||||
- The `log(...)` method calls `print(...)` to print the level, source file name, line number, function name, and the log message.
|
||||
- The `handleError(...)` method calls `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: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
public func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
let icon: String
|
||||
let levelString: String
|
||||
switch level {
|
||||
case .Trace: levelString = "Trace"
|
||||
case .Notice: levelString = "Notice"
|
||||
case .Warning: levelString = "Warning"
|
||||
case .Fatal: levelString = "Fatal"
|
||||
|
||||
case .Trace:
|
||||
icon = "🔹"
|
||||
levelString = "Trace"
|
||||
|
||||
case .Notice:
|
||||
icon = "🔸"
|
||||
levelString = "Notice"
|
||||
|
||||
case .Warning:
|
||||
icon = "⚠️"
|
||||
levelString = "Warning"
|
||||
|
||||
case .Fatal:
|
||||
icon = "❗"
|
||||
levelString = "Fatal"
|
||||
}
|
||||
Swift.println("[CoreStore:\(levelString)] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
Swift.print("\(icon) [CoreStore: \(levelString)] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
public func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
Swift.println("[CoreStore:Error] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message): \(error)\n")
|
||||
Swift.print("⚠️ [CoreStore: Error] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
Swift.assert(condition, message, file: fileName, line: numericCast(lineNumber))
|
||||
if condition() {
|
||||
|
||||
return
|
||||
}
|
||||
Swift.print("❗ [CoreStore: Assertion Failure] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
Swift.fatalError()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
152
CoreStore/Migrating/CoreStore+Migration.swift
Normal file
152
CoreStore/Migrating/CoreStore+Migration.swift
Normal file
@@ -0,0 +1,152 @@
|
||||
//
|
||||
// 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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -25,225 +25,76 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
public extension DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from the specified model name and a version-specific model name.
|
||||
Asynchronously adds an in-memory store to the stack.
|
||||
|
||||
:param: rootModelName the name of the (.xcdatamodeld) model file
|
||||
:param: versionModelName the name of the version-specific (.xcdatamodeld) model file
|
||||
- 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 convenience init(rootModelName: String, versionModelName: String) {
|
||||
public func addInMemoryStore(configuration configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
|
||||
|
||||
let modelVersionURL: NSURL! = NSBundle.mainBundle().URLForResource(
|
||||
rootModelName.stringByAppendingPathExtension("momd")!.stringByAppendingPathComponent(versionModelName),
|
||||
withExtension: "mom"
|
||||
)
|
||||
CoreStore.assert(modelVersionURL != nil, "Could not find a \"mom\" resource from the main bundle.")
|
||||
|
||||
let managedObjectModel: NSManagedObjectModel! = NSManagedObjectModel(contentsOfURL: modelVersionURL)
|
||||
CoreStore.assert(managedObjectModel != nil, "Could not create an <\(NSManagedObjectModel.self)> from the resource at URL \"\(modelVersionURL)\".")
|
||||
|
||||
self.init(managedObjectModel: managedObjectModel)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the store at the specified filename and configuration needs to be migrated to the `DataStack`'s managed object model version.
|
||||
|
||||
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration.
|
||||
*/
|
||||
public func needsMigrationForSQLiteStore(fileName: String, configuration: String? = nil) -> Bool? {
|
||||
|
||||
return needsMigrationForSQLiteStore(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
|
||||
|
||||
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration.
|
||||
*/
|
||||
public func needsMigrationForSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil) -> Bool? {
|
||||
|
||||
var error: NSError?
|
||||
let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
error: &error
|
||||
)
|
||||
if metadata == nil {
|
||||
self.coordinator.performBlock {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".")
|
||||
return nil
|
||||
}
|
||||
|
||||
return !self.coordinator.managedObjectModel.isConfiguration(
|
||||
configuration,
|
||||
compatibleWithStoreMetadata: metadata
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
EXPERIMENTAL
|
||||
*/
|
||||
private func upgradeSQLiteStoreIfNeeded(fileName: String, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
|
||||
|
||||
self.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
EXPERIMENTAL
|
||||
*/
|
||||
private func upgradeSQLiteStoreIfNeeded(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
|
||||
|
||||
var metadataError: NSError?
|
||||
let metadata: [NSObject: AnyObject]! = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
error: &metadataError
|
||||
)
|
||||
if metadata == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
metadataError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\".")
|
||||
|
||||
GCDQueue.Main.async {
|
||||
do {
|
||||
|
||||
// TODO: inspect valid errors for metadataForPersistentStoreOfType()
|
||||
completion(PersistentStoreResult(.PersistentStoreNotFound))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
if let store = coordinator.persistentStoreForURL(fileURL) {
|
||||
|
||||
let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false)
|
||||
|
||||
if store.type == NSSQLiteStoreType
|
||||
&& isExistingStoreAutomigrating
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL),
|
||||
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists.")
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(.DifferentPersistentStoreExistsAtURL))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let managedObjectModel = self.coordinator.managedObjectModel
|
||||
let migrationManager = NSMigrationManager(
|
||||
sourceModel: NSManagedObjectModel(
|
||||
byMergingModels: [managedObjectModel],
|
||||
forStoreMetadata: metadata!
|
||||
)!,
|
||||
destinationModel: managedObjectModel
|
||||
)
|
||||
|
||||
var mappingModel: NSMappingModel! = NSMappingModel(
|
||||
fromBundles: nil, // TODO: parametize
|
||||
forSourceModel: migrationManager.sourceModel,
|
||||
destinationModel: migrationManager.destinationModel
|
||||
)
|
||||
var modelError: NSError?
|
||||
if mappingModel == nil {
|
||||
|
||||
mappingModel = NSMappingModel.inferredMappingModelForSourceModel(
|
||||
migrationManager.sourceModel,
|
||||
destinationModel: migrationManager.destinationModel,
|
||||
error: &modelError
|
||||
)
|
||||
}
|
||||
if mappingModel == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to load an <\(NSMappingModel.self)> for migration from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\".")
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(.MappingModelNotFound))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let temporaryFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)!.URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString)
|
||||
|
||||
var migrationError: NSError?
|
||||
if !migrationManager.migrateStoreFromURL(
|
||||
fileURL,
|
||||
type: NSSQLiteStoreType,
|
||||
options: nil,
|
||||
withMappingModel: mappingModel,
|
||||
toDestinationURL: temporaryFileURL,
|
||||
destinationType: NSSQLiteStoreType,
|
||||
destinationOptions: nil,
|
||||
error: &migrationError
|
||||
) {
|
||||
|
||||
CoreStore.handleError(
|
||||
migrationError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to prepare for migration from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\".")
|
||||
let store = try self.coordinator.addPersistentStoreWithType(
|
||||
NSInMemoryStoreType,
|
||||
configuration: configuration,
|
||||
URL: nil,
|
||||
options: nil
|
||||
)
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(.MigrationFailed))
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
return
|
||||
}
|
||||
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`.)
|
||||
|
||||
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
:param: completion the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
|
||||
- parameter 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: String, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
|
||||
public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
self.addSQLiteStore(
|
||||
return try self.addSQLiteStore(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
@@ -251,107 +102,534 @@ public extension DataStack {
|
||||
/**
|
||||
Asynchronously adds to the stack an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
:param: fileURL the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
:param: completion the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
|
||||
- parameter 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: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
|
||||
public func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
var error: NSError?
|
||||
let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
error: &error
|
||||
CoreStore.assert(
|
||||
fileURL.fileURL,
|
||||
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
|
||||
)
|
||||
if metadata == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\".")
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(.UnknownError))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
if let store = coordinator.persistentStoreForURL(fileURL) {
|
||||
|
||||
let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false)
|
||||
|
||||
if store.type == NSSQLiteStoreType
|
||||
&& isExistingStoreAutomigrating
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
|
||||
guard store.type == NSSQLiteStoreType
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
return
|
||||
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
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL),
|
||||
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists.")
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(.DifferentPersistentStoreExistsAtURL))
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
return
|
||||
return nil
|
||||
}
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
var directoryError: NSError?
|
||||
if !fileManager.createDirectoryAtURL(
|
||||
_ = try? fileManager.createDirectoryAtURL(
|
||||
fileURL.URLByDeletingLastPathComponent!,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil,
|
||||
error: &directoryError) {
|
||||
|
||||
CoreStore.handleError(
|
||||
directoryError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to create directory for SQLite store at \"\(fileURL)\".")
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(directoryError!))
|
||||
}
|
||||
return
|
||||
}
|
||||
attributes: nil
|
||||
)
|
||||
|
||||
coordinator.performBlock {
|
||||
do {
|
||||
|
||||
var persistentStoreError: NSError?
|
||||
let store = coordinator.addPersistentStoreWithType(
|
||||
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
configuration: configuration,
|
||||
URL: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["WAL": "journal_mode"],
|
||||
NSInferMappingModelAutomaticallyOption: true,
|
||||
NSMigratePersistentStoresAutomaticallyOption: true],
|
||||
error: &persistentStoreError)
|
||||
options: self.optionsForSQLiteStore()
|
||||
)
|
||||
|
||||
if let store = store {
|
||||
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 {
|
||||
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
}
|
||||
else {
|
||||
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 {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
CoreStore.handleError(
|
||||
persistentStoreError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".")
|
||||
|
||||
completion(PersistentStoreResult(.UnknownError))
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
243
CoreStore/Migrating/MigrationChain.swift
Normal file
243
CoreStore/Migrating/MigrationChain.swift
Normal file
@@ -0,0 +1,243 @@
|
||||
//
|
||||
// MigrationChain.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - MigrationChain
|
||||
|
||||
/**
|
||||
A `MigrationChain` indicates the sequence of model versions to be used as the order for incremental migration. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants.
|
||||
|
||||
Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to use the .xcdatamodel's current version as the final version, and to disable incremental migrations:
|
||||
|
||||
let dataStack = DataStack(migrationChain: nil)
|
||||
|
||||
This means that the mapping model will be computed from the store's version straight to the `DataStack`'s model version.
|
||||
To support incremental migrations, specify the linear order of versions:
|
||||
|
||||
let dataStack = DataStack(migrationChain:
|
||||
["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"])
|
||||
|
||||
or for more complex migration paths, a version tree that maps the key-values to the source-destination versions:
|
||||
|
||||
let dataStack = DataStack(migrationChain: [
|
||||
"MyAppModel": "MyAppModelV3",
|
||||
"MyAppModelV2": "MyAppModelV4",
|
||||
"MyAppModelV3": "MyAppModelV4"
|
||||
])
|
||||
|
||||
This allows for different migration paths depending on the starting version. The example above resolves to the following paths:
|
||||
- MyAppModel-MyAppModelV3-MyAppModelV4
|
||||
- MyAppModelV2-MyAppModelV4
|
||||
- MyAppModelV3-MyAppModelV4
|
||||
|
||||
The `MigrationChain` is validated when passed to the `DataStack` and unless it is empty, will raise an assertion if any of the following conditions are met:
|
||||
- a version appears twice in an array
|
||||
- a version appears twice as a key in a dictionary literal
|
||||
- a loop is found in any of the paths
|
||||
*/
|
||||
public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, DictionaryLiteralConvertible, ArrayLiteralConvertible {
|
||||
|
||||
// MARK: NilLiteralConvertible
|
||||
|
||||
public init(nilLiteral: ()) {
|
||||
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = []
|
||||
self.leafVersions = []
|
||||
self.valid = true
|
||||
}
|
||||
|
||||
|
||||
// MARK: StringLiteralConvertible
|
||||
|
||||
public init(stringLiteral value: String) {
|
||||
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = [value]
|
||||
self.leafVersions = [value]
|
||||
self.valid = true
|
||||
}
|
||||
|
||||
|
||||
// MARK: ExtendedGraphemeClusterLiteralConvertible
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: String) {
|
||||
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = [value]
|
||||
self.leafVersions = [value]
|
||||
self.valid = true
|
||||
}
|
||||
|
||||
|
||||
// MARK: UnicodeScalarLiteralConvertible
|
||||
|
||||
public init(unicodeScalarLiteral value: String) {
|
||||
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = [value]
|
||||
self.leafVersions = [value]
|
||||
self.valid = true
|
||||
}
|
||||
|
||||
|
||||
// MARK: DictionaryLiteralConvertible
|
||||
|
||||
public init(dictionaryLiteral elements: (String, String)...) {
|
||||
|
||||
var valid = true
|
||||
let versionTree = elements.reduce([String: String]()) { (var versionTree, tuple: (String, String)) -> [String: String] in
|
||||
|
||||
if let _ = versionTree.updateValue(tuple.1, forKey: tuple.0) {
|
||||
|
||||
CoreStore.assert(false, "\(typeName(MigrationChain))'s migration chain could not be created due to ambiguous version paths.")
|
||||
|
||||
valid = false
|
||||
}
|
||||
return versionTree
|
||||
}
|
||||
let leafVersions = Set(
|
||||
elements.filter { (tuple: (String, String)) -> Bool in
|
||||
|
||||
return versionTree[tuple.1] == nil
|
||||
|
||||
}.map { $1 }
|
||||
)
|
||||
|
||||
let isVersionAmbiguous = { (start: String) -> Bool in
|
||||
|
||||
var checklist: Set<String> = [start]
|
||||
var version = start
|
||||
while let nextVersion = versionTree[version] where nextVersion != version {
|
||||
|
||||
if checklist.contains(nextVersion) {
|
||||
|
||||
CoreStore.assert(false, "\(typeName(MigrationChain))'s migration chain could not be created due to looping version paths.")
|
||||
|
||||
return true
|
||||
}
|
||||
checklist.insert(nextVersion)
|
||||
version = nextVersion
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
self.versionTree = versionTree
|
||||
self.rootVersions = Set(versionTree.keys).subtract(versionTree.values)
|
||||
self.leafVersions = leafVersions
|
||||
self.valid = valid && Set(versionTree.keys).union(versionTree.values).filter { isVersionAmbiguous($0) }.count <= 0
|
||||
}
|
||||
|
||||
|
||||
// MARK: ArrayLiteralConvertible
|
||||
|
||||
public init(arrayLiteral elements: String...) {
|
||||
|
||||
CoreStore.assert(Set(elements).count == elements.count, "\(typeName(MigrationChain))'s migration chain could not be created due to duplicate version strings.")
|
||||
|
||||
var lastVersion: String?
|
||||
var versionTree = [String: String]()
|
||||
var valid = true
|
||||
for version in elements {
|
||||
|
||||
if let lastVersion = lastVersion,
|
||||
let _ = versionTree.updateValue(version, forKey: lastVersion) {
|
||||
|
||||
valid = false
|
||||
}
|
||||
lastVersion = version
|
||||
}
|
||||
|
||||
self.versionTree = versionTree
|
||||
self.rootVersions = Set([elements.first].flatMap { $0 == nil ? [] : [$0!] })
|
||||
self.leafVersions = Set([elements.last].flatMap { $0 == nil ? [] : [$0!] })
|
||||
self.valid = valid
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let rootVersions: Set<String>
|
||||
internal let leafVersions: Set<String>
|
||||
internal let valid: Bool
|
||||
|
||||
internal var empty: Bool {
|
||||
|
||||
return self.versionTree.count <= 0
|
||||
}
|
||||
|
||||
internal func contains(version: String) -> Bool {
|
||||
|
||||
return self.rootVersions.contains(version)
|
||||
|| self.leafVersions.contains(version)
|
||||
|| self.versionTree[version] != nil
|
||||
}
|
||||
|
||||
internal func nextVersionFrom(version: String) -> String? {
|
||||
|
||||
guard let nextVersion = self.versionTree[version] where nextVersion != version else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return nextVersion
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let versionTree: [String: String]
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MigrationChain: CustomDebugStringConvertible
|
||||
|
||||
extension MigrationChain: CustomDebugStringConvertible {
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
guard self.valid else {
|
||||
|
||||
return "<invalid migration chain>"
|
||||
}
|
||||
|
||||
var paths = [String]()
|
||||
for var version in self.rootVersions {
|
||||
|
||||
var steps = [version]
|
||||
while let nextVersion = self.nextVersionFrom(version) {
|
||||
|
||||
steps.append(nextVersion)
|
||||
version = nextVersion
|
||||
}
|
||||
paths.append(steps.joinWithSeparator(" → "))
|
||||
}
|
||||
|
||||
return "[" + paths.joinWithSeparator("], [") + "]"
|
||||
}
|
||||
}
|
||||
110
CoreStore/Migrating/MigrationResult.swift
Normal file
110
CoreStore/Migrating/MigrationResult.swift
Normal file
@@ -0,0 +1,110 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
125
CoreStore/Migrating/MigrationType.swift
Normal file
125
CoreStore/Migrating/MigrationType.swift
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// MigrationType.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - MigrationType
|
||||
|
||||
/**
|
||||
The `MigrationType` specifies the type of migration required for a store.
|
||||
*/
|
||||
public enum MigrationType: BooleanType {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Indicates that the persistent store matches the latest model version and no migration is needed
|
||||
*/
|
||||
case None(version: String)
|
||||
|
||||
/**
|
||||
Indicates that the persistent store does not match the latest model version but Core Data can infer the mapping model, so a lightweight migration is needed
|
||||
*/
|
||||
case Lightweight(sourceVersion: String, destinationVersion: String)
|
||||
|
||||
/**
|
||||
Indicates that the persistent store does not match the latest model version and Core Data could not infer a mapping model, so a custom migration is needed
|
||||
*/
|
||||
case Heavyweight(sourceVersion: String, destinationVersion: String)
|
||||
|
||||
/**
|
||||
Returns the source model version for the migration type. If no migration is required, `sourceVersion` will be equal to the `destinationVersion`.
|
||||
*/
|
||||
public var sourceVersion: String {
|
||||
|
||||
switch self {
|
||||
|
||||
case .None(let version):
|
||||
return version
|
||||
|
||||
case .Lightweight(let sourceVersion, _):
|
||||
return sourceVersion
|
||||
|
||||
case .Heavyweight(let sourceVersion, _):
|
||||
return sourceVersion
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the destination model version for the migration type. If no migration is required, `destinationVersion` will be equal to the `sourceVersion`.
|
||||
*/
|
||||
public var destinationVersion: String {
|
||||
|
||||
switch self {
|
||||
|
||||
case .None(let version):
|
||||
return version
|
||||
|
||||
case .Lightweight(_, let destinationVersion):
|
||||
return destinationVersion
|
||||
|
||||
case .Heavyweight(_, let destinationVersion):
|
||||
return destinationVersion
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `MigrationType` is a lightweight migration. Used as syntactic sugar.
|
||||
*/
|
||||
public var isLightweightMigration: Bool {
|
||||
|
||||
if case .Lightweight = self {
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `MigrationType` is a heavyweight migration. Used as syntactic sugar.
|
||||
*/
|
||||
public var isHeavyweightMigration: Bool {
|
||||
|
||||
if case .Heavyweight = self {
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// MARK: BooleanType
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .None: return false
|
||||
case .Lightweight: return true
|
||||
case .Heavyweight: return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
/**
|
||||
The `NSError` error domain for `CoreStore`.
|
||||
@@ -54,11 +55,6 @@ public enum CoreStoreErrorCode: Int {
|
||||
An `NSMappingModel` could not be found for a specific source and destination model versions.
|
||||
*/
|
||||
case MappingModelNotFound
|
||||
|
||||
/**
|
||||
An `NSMigrationManager` prepared to migrate the store.
|
||||
*/
|
||||
case MigrationFailed
|
||||
}
|
||||
|
||||
|
||||
@@ -91,4 +87,13 @@ public extension NSError {
|
||||
code: coreStoreErrorCode.rawValue,
|
||||
userInfo: userInfo)
|
||||
}
|
||||
|
||||
internal var isCoreDataMigrationError: Bool {
|
||||
|
||||
let code = self.code
|
||||
return (code == NSPersistentStoreIncompatibleVersionHashError
|
||||
|| code == NSMigrationMissingSourceModelError
|
||||
|| code == NSMigrationError)
|
||||
&& self.domain == NSCocoaErrorDomain
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,68 +29,124 @@ import CoreData
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
@available(OSX, unavailable)
|
||||
public extension CoreStore {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ManagedObjectController` for the specified `NSManagedObject`. Multiple `ManagedObjectObserver`'s may then register themselves to be notified when changes are made to the `NSManagedObject`.
|
||||
Using the `defaultStack`, creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
|
||||
|
||||
:param: object the `NSManagedObject` to observe changes from
|
||||
:returns: a `ManagedObjectController` that monitors changes to `object`
|
||||
- parameter object: the `NSManagedObject` to observe changes from
|
||||
- returns: a `ObjectMonitor` that monitors changes to `object`
|
||||
*/
|
||||
public static func observeObject<T: NSManagedObject>(object: T) -> ManagedObjectController<T> {
|
||||
@warn_unused_result
|
||||
public static func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
|
||||
|
||||
return self.defaultStack.observeObject(object)
|
||||
return self.defaultStack.monitorObject(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ManagedObjectListController` for a list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list.
|
||||
Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: a `ManagedObjectListController` instance that monitors changes to the list
|
||||
*/
|
||||
public static func observeObjectList<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ManagedObjectListController<T> {
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: FetchClause...) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.observeObjectList(from, queryClauses)
|
||||
return self.defaultStack.monitorList(from, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ManagedObjectListController` for a list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list.
|
||||
Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: a `ManagedObjectListController` instance that monitors changes to the list
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
public static func observeObjectList<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: [FetchClause]) -> ManagedObjectListController<T> {
|
||||
@warn_unused_result
|
||||
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.observeObjectList(from, queryClauses)
|
||||
return self.defaultStack.monitorList(from, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ManagedObjectListController` for a sectioned list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: sectionedBy a `SectionedBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
:param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: a `ManagedObjectListController` instance that monitors changes to the list
|
||||
*/
|
||||
public static func observeSectionedList<T: NSManagedObject>(from: From<T>, _ sectionedBy: SectionedBy, _ fetchClauses: FetchClause...) -> ManagedObjectListController<T> {
|
||||
Using the `defaultStack`, asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public static func monitorList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
|
||||
|
||||
return self.defaultStack.observeSectionedList(from, sectionedBy, fetchClauses)
|
||||
self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ManagedObjectListController` for a sectioned list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: sectionedBy a `SectionedBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
:param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: a `ManagedObjectListController` instance that monitors changes to the list
|
||||
*/
|
||||
public static func observeSectionedList<T: NSManagedObject>(from: From<T>, _ sectionedBy: SectionedBy, _ fetchClauses: [FetchClause]) -> ManagedObjectListController<T> {
|
||||
Using the `defaultStack`, asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public static func monitorList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
|
||||
|
||||
return self.defaultStack.observeSectionedList(from, sectionedBy, fetchClauses)
|
||||
self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public static func monitorSectionedList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
|
||||
|
||||
self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public static func monitorSectionedList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
|
||||
|
||||
self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,92 +25,197 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
@available(OSX, unavailable)
|
||||
public extension DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Creates a `ManagedObjectController` for the specified `NSManagedObject`. Multiple `ManagedObjectObserver`'s may then register themselves to be notified when changes are made to the `NSManagedObject`.
|
||||
Creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
|
||||
|
||||
:param: object the `NSManagedObject` to observe changes from
|
||||
:returns: a `ManagedObjectController` that monitors changes to `object`
|
||||
- parameter object: the `NSManagedObject` to observe changes from
|
||||
- returns: a `ObjectMonitor` that monitors changes to `object`
|
||||
*/
|
||||
public func observeObject<T: NSManagedObject>(object: T) -> ManagedObjectController<T> {
|
||||
@warn_unused_result
|
||||
public func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.")
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return ManagedObjectController(
|
||||
return ObjectMonitor(
|
||||
dataStack: self,
|
||||
object: object
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ManagedObjectListController` for a list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list.
|
||||
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: a `ManagedObjectListController` instance that monitors changes to the list
|
||||
*/
|
||||
public func observeObjectList<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> ManagedObjectListController<T> {
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
|
||||
|
||||
return self.observeObjectList(from, fetchClauses)
|
||||
return self.monitorList(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ManagedObjectListController` for a list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list.
|
||||
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: a `ManagedObjectListController` instance that monitors changes to the list
|
||||
*/
|
||||
public func observeObjectList<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> ManagedObjectListController<T> {
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.")
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
CoreStore.assert(
|
||||
fetchClauses.filter { $0 is OrderBy }.count > 0,
|
||||
"A ListMonitor requires an OrderBy clause."
|
||||
)
|
||||
|
||||
return ManagedObjectListController(
|
||||
return ListMonitor(
|
||||
dataStack: self,
|
||||
from: from,
|
||||
sectionedBy: nil,
|
||||
sectionBy: nil,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ManagedObjectListController` for a sectioned list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: sectionedBy a `SectionedBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
:param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: a `ManagedObjectListController` instance that monitors changes to the list
|
||||
*/
|
||||
public func observeSectionedList<T: NSManagedObject>(from: From<T>, _ sectionedBy: SectionedBy, _ fetchClauses: FetchClause...) -> ManagedObjectListController<T> {
|
||||
Asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
return self.observeSectionedList(from, sectionedBy, fetchClauses)
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
|
||||
|
||||
self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ManagedObjectListController` for a sectioned list of `NSManagedObject`'s that satisfy the specified fetch clauses. Multiple `ManagedObjectListObserver`'s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
:param: from a `From` clause indicating the entity type
|
||||
:param: sectionedBy a `SectionedBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
:param: fetchClauses a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
:returns: a `ManagedObjectListController` instance that monitors changes to the list
|
||||
*/
|
||||
public func observeSectionedList<T: NSManagedObject>(from: From<T>, _ sectionedBy: SectionedBy, _ fetchClauses: [FetchClause]) -> ManagedObjectListController<T> {
|
||||
Asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to observe objects from \(typeName(self)) outside the main thread.")
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
CoreStore.assert(
|
||||
fetchClauses.filter { $0 is OrderBy }.count > 0,
|
||||
"A ListMonitor requires an OrderBy clause."
|
||||
)
|
||||
|
||||
return ManagedObjectListController(
|
||||
_ = ListMonitor(
|
||||
dataStack: self,
|
||||
from: from,
|
||||
sectionedBy: sectionedBy,
|
||||
sectionBy: nil,
|
||||
fetchClauses: fetchClauses,
|
||||
createAsynchronously: createAsynchronously
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
|
||||
|
||||
return self.monitorSectionedList(from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
CoreStore.assert(
|
||||
fetchClauses.filter { $0 is OrderBy }.count > 0,
|
||||
"A ListMonitor requires an OrderBy clause."
|
||||
)
|
||||
|
||||
return ListMonitor(
|
||||
dataStack: self,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
|
||||
|
||||
self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
CoreStore.assert(
|
||||
fetchClauses.filter { $0 is OrderBy }.count > 0,
|
||||
"A ListMonitor requires an OrderBy clause."
|
||||
)
|
||||
|
||||
_ = ListMonitor(
|
||||
dataStack: self,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses,
|
||||
createAsynchronously: createAsynchronously
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
1249
CoreStore/Observing/ListMonitor.swift
Normal file
1249
CoreStore/Observing/ListMonitor.swift
Normal file
File diff suppressed because it is too large
Load Diff
235
CoreStore/Observing/ListObserver.swift
Normal file
235
CoreStore/Observing/ListObserver.swift
Normal file
@@ -0,0 +1,235 @@
|
||||
//
|
||||
// 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,346 +0,0 @@
|
||||
//
|
||||
// ManagedObjectController.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
private let ManagedObjectListControllerWillChangeObjectNotification = "ManagedObjectListControllerWillChangeObjectNotification"
|
||||
private let ManagedObjectListControllerDidDeleteObjectNotification = "ManagedObjectListControllerDidDeleteObjectNotification"
|
||||
private let ManagedObjectListControllerDidUpdateObjectNotification = "ManagedObjectListControllerDidUpdateObjectNotification"
|
||||
|
||||
private let UserInfoKeyObject = "UserInfoKeyObject"
|
||||
|
||||
private struct NotificationKey {
|
||||
|
||||
static var willChangeObject: Void?
|
||||
static var didDeleteObject: Void?
|
||||
static var didUpdateObject: Void?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ManagedObjectController
|
||||
|
||||
/**
|
||||
The `ManagedObjectController` monitors changes to a single `NSManagedObject` instance. Observers that implement the `ManagedObjectObserver` protocol may then register themselves to the `ManagedObjectController`'s `addObserver(_:)` method:
|
||||
|
||||
let objectController = CoreStore.observeObject(object)
|
||||
objectController.addObserver(self)
|
||||
|
||||
The created `ManagedObjectController` instance needs to be held on (retained) for as long as the object needs to be observed.
|
||||
|
||||
Observers registered via `addObserver(_:)` are not retained. `ManagedObjectController` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
|
||||
*/
|
||||
public final class ManagedObjectController<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted.
|
||||
*/
|
||||
public var object: T? {
|
||||
|
||||
return self.fetchedResultsController.fetchedObjects?.first as? T
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `NSManagedObject` instance being observed still exists, or `false` if the object was already deleted.
|
||||
*/
|
||||
public var isObjectDeleted: Bool {
|
||||
|
||||
return self.object?.managedObjectContext == nil
|
||||
}
|
||||
|
||||
/**
|
||||
Registers a `ManagedObjectObserver` to be notified when changes to the receiver's `object` are made.
|
||||
|
||||
To prevent retain-cycles, `ManagedObjectController` only keeps `weak` references to its observers.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
Calling `addObserver(_:)` multiple times on the same observer is safe, as `ManagedObjectController` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
:param: observer a `ManagedObjectObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ManagedObjectObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.")
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.willChangeObject,
|
||||
name: ManagedObjectListControllerWillChangeObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak self, weak observer] (objectController) -> Void in
|
||||
|
||||
if let strongSelf = self, let object = strongSelf.object, let observer = observer {
|
||||
|
||||
observer.managedObjectWillUpdate(objectController, object: object)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didDeleteObject,
|
||||
name: ManagedObjectListControllerDidDeleteObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak self, weak observer] (objectController, object) -> Void in
|
||||
|
||||
if let strongSelf = self, let observer = observer {
|
||||
|
||||
observer.managedObjectWasDeleted(objectController, object: object)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didUpdateObject,
|
||||
name: ManagedObjectListControllerDidUpdateObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak self, weak observer] (objectController, object) -> Void in
|
||||
|
||||
if let strongSelf = self, let observer = observer {
|
||||
|
||||
let previousCommitedAttributes = strongSelf.lastCommittedAttributes
|
||||
let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [NSString: NSObject]
|
||||
|
||||
var changedKeys = Set<String>()
|
||||
for key in currentCommitedAttributes.keys {
|
||||
|
||||
if previousCommitedAttributes[key] != currentCommitedAttributes[key] {
|
||||
|
||||
changedKeys.insert(key as String)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.lastCommittedAttributes = currentCommitedAttributes
|
||||
observer.managedObjectWasUpdated(
|
||||
objectController,
|
||||
object: object,
|
||||
changedPersistentKeys: changedKeys
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Unregisters a `ManagedObjectObserver` from receiving notifications for changes to the receiver's `object`.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
:param: observer a `ManagedObjectObserver` to unregister notifications to
|
||||
*/
|
||||
public func removeObserver<U: ManagedObjectObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to remove an observer of type \(typeName(observer)) outside the main thread.")
|
||||
|
||||
let nilValue: AnyObject? = nil
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didDeleteObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didUpdateObject, inObject: observer)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(dataStack: DataStack, object: T) {
|
||||
|
||||
let context = dataStack.mainContext
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
fetchRequest.entity = context.entityDescriptionForEntityClass(T.self)
|
||||
fetchRequest.fetchLimit = 1
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchRequest.sortDescriptors = []
|
||||
|
||||
let originalObjectID = object.objectID
|
||||
Where("SELF", isEqualTo: originalObjectID).applyToFetchRequest(fetchRequest)
|
||||
|
||||
let fetchedResultsController = NSFetchedResultsController(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: context,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
|
||||
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
|
||||
|
||||
self.originalObjectID = originalObjectID
|
||||
self.fetchedResultsController = fetchedResultsController
|
||||
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
|
||||
self.parentStack = dataStack
|
||||
|
||||
fetchedResultsControllerDelegate.handler = self
|
||||
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
|
||||
|
||||
var error: NSError?
|
||||
if !fetchedResultsController.performFetch(&error) {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to perform fetch on <\(NSFetchedResultsController.self)>.")
|
||||
}
|
||||
|
||||
self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [NSString: NSObject]) ?? [:]
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let originalObjectID: NSManagedObjectID
|
||||
private let fetchedResultsController: NSFetchedResultsController
|
||||
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
|
||||
private var lastCommittedAttributes = [NSString: NSObject]()
|
||||
private weak var parentStack: DataStack?
|
||||
|
||||
private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (objectController: ManagedObjectController<T>) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
if let strongSelf = self {
|
||||
|
||||
callback(objectController: strongSelf)
|
||||
}
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
|
||||
private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (objectController: ManagedObjectController<T>, object: T) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
if let strongSelf = self,
|
||||
let userInfo = note.userInfo,
|
||||
let object = userInfo[UserInfoKeyObject] as? T {
|
||||
|
||||
callback(
|
||||
objectController: strongSelf,
|
||||
object: object
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ManagedObjectController: FetchedResultsControllerHandler
|
||||
|
||||
extension ManagedObjectController: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: FetchedResultsControllerHandler
|
||||
|
||||
private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
switch type {
|
||||
|
||||
case .Delete:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerDidDeleteObjectNotification,
|
||||
object: self,
|
||||
userInfo: [UserInfoKeyObject: anObject]
|
||||
)
|
||||
|
||||
case .Update:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerDidUpdateObjectNotification,
|
||||
object: self,
|
||||
userInfo: [UserInfoKeyObject: anObject]
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerWillChangeObjectNotification,
|
||||
object: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FetchedResultsControllerHandler
|
||||
|
||||
private protocol FetchedResultsControllerHandler: class {
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
|
||||
|
||||
func controllerWillChangeContent(controller: NSFetchedResultsController)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FetchedResultsControllerDelegate
|
||||
|
||||
private final class FetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate {
|
||||
|
||||
// MARK: NSFetchedResultsControllerDelegate
|
||||
|
||||
@objc func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
self.handler?.controllerWillChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
weak var handler: FetchedResultsControllerHandler?
|
||||
weak var fetchedResultsController: NSFetchedResultsController? {
|
||||
|
||||
didSet {
|
||||
|
||||
oldValue?.delegate = nil
|
||||
self.fetchedResultsController?.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.fetchedResultsController?.delegate = nil
|
||||
}
|
||||
}
|
||||
@@ -1,832 +0,0 @@
|
||||
//
|
||||
// ManagedObjectListController.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - SectionedBy
|
||||
|
||||
/**
|
||||
The `SectionedBy` clause indicates the key path to use to group the `ManagedObjectListController` objects into sections. An optional closure can also be provided to transform the value into an appropriate section name:
|
||||
|
||||
let listController = CoreStore.observeSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionedBy("age") { "Age \($0)" },
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
*/
|
||||
public struct SectionedBy {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes a `SectionedBy` clause with the key path to use to group `ManagedObjectListController` objects into sections
|
||||
|
||||
:param: sectionKeyPath the key path to use to group the objects into sections
|
||||
*/
|
||||
public init(_ sectionKeyPath: KeyPath) {
|
||||
|
||||
self.init(sectionKeyPath, { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `SectionedBy` clause with the key path to use to group `ManagedObjectListController` objects into sections, and a closure to transform the value for the key path to an appropriate section name
|
||||
|
||||
:param: sectionKeyPath the key path to use to group the objects into sections
|
||||
:param: sectionIndexTransformer a closure to transform the value for the key path to an appropriate section name
|
||||
*/
|
||||
public init(_ sectionKeyPath: KeyPath, _ sectionIndexTransformer: (sectionName: String?) -> String?) {
|
||||
|
||||
self.sectionKeyPath = sectionKeyPath
|
||||
self.sectionIndexTransformer = sectionIndexTransformer
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let sectionKeyPath: KeyPath
|
||||
internal let sectionIndexTransformer: (sectionName: KeyPath?) -> String?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ManagedObjectListController
|
||||
|
||||
/**
|
||||
The `ManagedObjectListController` monitors changes to a list of `NSManagedObject` instances. Observers that implement the `ManagedObjectListChangeObserver` protocol may then register themselves to the `ManagedObjectListController`'s `addObserver(_:)` method:
|
||||
|
||||
let listController = CoreStore.observeObjectList(
|
||||
From(MyPersonEntity),
|
||||
Where("title", isEqualTo: "Engineer"),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
listController.addObserver(self)
|
||||
|
||||
The `ManagedObjectListController` instance needs to be held on (retained) for as long as the list needs to be observed.
|
||||
Observers registered via `addObserver(_:)` are not retained. `ManagedObjectListController` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
|
||||
|
||||
Lists created with `observeObjectList(...)` keep a single-section list of objects, where each object can be accessed by index:
|
||||
|
||||
let firstPerson: MyPersonEntity = listController[0]
|
||||
|
||||
Accessing the list with an index above the valid range will throw an exception.
|
||||
|
||||
Creating a sectioned-list is also possible with the `observeSectionedList(...)` method:
|
||||
|
||||
let listController = CoreStore.observeSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionedBy("age") { "Age \($0)" },
|
||||
Where("title", isEqualTo: "Engineer"),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
listController.addObserver(self)
|
||||
|
||||
Objects from `ManagedObjectListController`'s created this way can be accessed either by an `NSIndexPath` or a tuple:
|
||||
|
||||
let indexPath = NSIndexPath(forItem: 3, inSection: 2)
|
||||
let person1 = listController[indexPath]
|
||||
let person2 = listController[2, 3]
|
||||
|
||||
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
|
||||
*/
|
||||
public final class ManagedObjectListController<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Accesses the object at the given index within the first section. This subscript indexer is typically used for `ManagedObjectListController`'s created with `addObserver(_:)`.
|
||||
|
||||
:param: index the index of the object. Using an index above the valid range will throw an exception.
|
||||
*/
|
||||
public subscript(index: Int) -> T {
|
||||
|
||||
return self.fetchedResultsController.objectAtIndexPath(NSIndexPath(forItem: index, inSection: 0)) as! T
|
||||
}
|
||||
|
||||
/**
|
||||
Accesses the object at the given `NSIndexPath`. This subscript indexer is typically used for `ManagedObjectListController`'s created with `observeSectionedList(_:)`.
|
||||
|
||||
:param: indexPath the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will throw an exception.
|
||||
*/
|
||||
public subscript(indexPath: NSIndexPath) -> T {
|
||||
|
||||
return self.fetchedResultsController.objectAtIndexPath(indexPath) as! T
|
||||
}
|
||||
|
||||
/**
|
||||
Accesses the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ManagedObjectListController`'s created with `observeSectionedList(_:)`.
|
||||
|
||||
:param: sectionIndex the section index for the object. Using a `sectionIndex` with an invalid range will throw an exception.
|
||||
:param: itemIndex the index for the object within the section. Using an `itemIndex` with an invalid range will throw an exception.
|
||||
*/
|
||||
public subscript(sectionIndex: Int, itemIndex: Int) -> T {
|
||||
|
||||
return self.fetchedResultsController.objectAtIndexPath(NSIndexPath(forItem: itemIndex, inSection: sectionIndex)) as! T
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of sections
|
||||
*/
|
||||
public func numberOfSections() -> Int {
|
||||
|
||||
return self.fetchedResultsController.sections?.count ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of objects in the specified section
|
||||
|
||||
:param: section the section index
|
||||
*/
|
||||
public func numberOfObjectsInSection(section: Int) -> Int {
|
||||
|
||||
return (self.fetchedResultsController.sections?[section] as? NSFetchedResultsSectionInfo)?.numberOfObjects ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSFetchedResultsSectionInfo` for the specified section
|
||||
|
||||
:param: section the section index
|
||||
*/
|
||||
public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo {
|
||||
|
||||
return self.fetchedResultsController.sections![section] as! NSFetchedResultsSectionInfo
|
||||
}
|
||||
|
||||
/**
|
||||
Registers a `ManagedObjectListChangeObserver` to be notified when changes to the receiver's list occur.
|
||||
|
||||
To prevent retain-cycles, `ManagedObjectListController` only keeps `weak` references to its observers.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
Calling `addObserver(_:)` multiple times on the same observer is safe, as `ManagedObjectListController` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
:param: observer a `ManagedObjectListChangeObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ManagedObjectListChangeObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.")
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.willChangeList,
|
||||
name: ManagedObjectListControllerWillChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectListWillChange(listController)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.didChangeList,
|
||||
name: ManagedObjectListControllerDidChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectListDidChange(listController)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Registers a `ManagedObjectListObjectObserver` to be notified when changes to the receiver's list occur.
|
||||
|
||||
To prevent retain-cycles, `ManagedObjectListController` only keeps `weak` references to its observers.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
Calling `addObserver(_:)` multiple times on the same observer is safe, as `ManagedObjectListController` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
:param: observer a `ManagedObjectListObjectObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ManagedObjectListObjectObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.")
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.willChangeList,
|
||||
name: ManagedObjectListControllerWillChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectListWillChange(listController)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.didChangeList,
|
||||
name: ManagedObjectListControllerDidChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectListDidChange(listController)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didInsertObject,
|
||||
name: ManagedObjectListControllerDidInsertObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didInsertObject: object,
|
||||
toIndexPath: newIndexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didDeleteObject,
|
||||
name: ManagedObjectListControllerDidDeleteObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didDeleteObject: object,
|
||||
fromIndexPath: indexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didUpdateObject,
|
||||
name: ManagedObjectListControllerDidUpdateObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didUpdateObject: object,
|
||||
atIndexPath: indexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didMoveObject,
|
||||
name: ManagedObjectListControllerDidMoveObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didMoveObject: object,
|
||||
fromIndexPath: indexPath!,
|
||||
toIndexPath: newIndexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Registers a `ManagedObjectListSectionObserver` to be notified when changes to the receiver's list occur.
|
||||
|
||||
To prevent retain-cycles, `ManagedObjectListController` only keeps `weak` references to its observers.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
Calling `addObserver(_:)` multiple times on the same observer is safe, as `ManagedObjectListController` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
:param: observer a `ManagedObjectListSectionObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ManagedObjectListSectionObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to add an observer of type \(typeName(observer)) outside the main thread.")
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.willChangeList,
|
||||
name: ManagedObjectListControllerWillChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectListWillChange(listController)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.didChangeList,
|
||||
name: ManagedObjectListControllerDidChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectListDidChange(listController)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didInsertObject,
|
||||
name: ManagedObjectListControllerDidInsertObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didInsertObject: object,
|
||||
toIndexPath: newIndexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didDeleteObject,
|
||||
name: ManagedObjectListControllerDidDeleteObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didDeleteObject: object,
|
||||
fromIndexPath: indexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didUpdateObject,
|
||||
name: ManagedObjectListControllerDidUpdateObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didUpdateObject: object,
|
||||
atIndexPath: indexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didMoveObject,
|
||||
name: ManagedObjectListControllerDidMoveObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didMoveObject: object,
|
||||
fromIndexPath: indexPath!,
|
||||
toIndexPath: newIndexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self.registerSectionNotification(
|
||||
&NotificationKey.didInsertSection,
|
||||
name: ManagedObjectListControllerDidInsertSectionNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, sectionInfo, sectionIndex) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didInsertSection: sectionInfo,
|
||||
toSectionIndex: sectionIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerSectionNotification(
|
||||
&NotificationKey.didDeleteSection,
|
||||
name: ManagedObjectListControllerDidDeleteSectionNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (listController, sectionInfo, sectionIndex) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.managedObjectList(
|
||||
listController,
|
||||
didDeleteSection: sectionInfo,
|
||||
fromSectionIndex: sectionIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Unregisters a `ManagedObjectListChangeObserver` from receiving notifications for changes to the receiver's list.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
:param: observer a `ManagedObjectListChangeObserver` to unregister notifications to
|
||||
*/
|
||||
public func removeObserver<U: ManagedObjectListChangeObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to remove an observer of type \(typeName(observer)) outside the main thread.")
|
||||
|
||||
let nilValue: AnyObject? = nil
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeList, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didChangeList, inObject: observer)
|
||||
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didInsertObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didDeleteObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didUpdateObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didMoveObject, inObject: observer)
|
||||
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didInsertSection, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didDeleteSection, inObject: observer)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(dataStack: DataStack, from: From<T>, sectionedBy: SectionedBy?, fetchClauses: [FetchClause]) {
|
||||
|
||||
let context = dataStack.mainContext
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: context)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
let fetchedResultsController = NSFetchedResultsController(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: context,
|
||||
sectionNameKeyPath: sectionedBy?.sectionKeyPath,
|
||||
cacheName: nil
|
||||
)
|
||||
|
||||
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
|
||||
|
||||
self.fetchedResultsController = fetchedResultsController
|
||||
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
|
||||
self.parentStack = dataStack
|
||||
|
||||
if let sectionIndexTransformer = sectionedBy?.sectionIndexTransformer {
|
||||
|
||||
self.sectionIndexTransformer = sectionIndexTransformer
|
||||
}
|
||||
else {
|
||||
|
||||
self.sectionIndexTransformer = { $0 }
|
||||
}
|
||||
|
||||
|
||||
fetchedResultsControllerDelegate.handler = self
|
||||
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
|
||||
|
||||
var error: NSError?
|
||||
if !fetchedResultsController.performFetch(&error) {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to perform fetch on <\(NSFetchedResultsController.self)>.")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let fetchedResultsController: NSFetchedResultsController
|
||||
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
|
||||
private let sectionIndexTransformer: (sectionName: KeyPath?) -> String?
|
||||
private weak var parentStack: DataStack?
|
||||
|
||||
private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController<T>) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
if let strongSelf = self {
|
||||
|
||||
callback(listController: strongSelf)
|
||||
}
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
|
||||
private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController<T>, object: T, indexPath: NSIndexPath?, newIndexPath: NSIndexPath?) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
if let strongSelf = self,
|
||||
let userInfo = note.userInfo,
|
||||
let object = userInfo[UserInfoKeyObject] as? T {
|
||||
|
||||
callback(
|
||||
listController: strongSelf,
|
||||
object: object,
|
||||
indexPath: userInfo[UserInfoKeyIndexPath] as? NSIndexPath,
|
||||
newIndexPath: userInfo[UserInfoKeyNewIndexPath] as? NSIndexPath
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
|
||||
private func registerSectionNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (listController: ManagedObjectListController<T>, sectionInfo: NSFetchedResultsSectionInfo, sectionIndex: Int) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
if let strongSelf = self,
|
||||
let userInfo = note.userInfo,
|
||||
let sectionInfo = userInfo[UserInfoKeySectionInfo] as? NSFetchedResultsSectionInfo,
|
||||
let sectionIndex = (userInfo[UserInfoKeySectionIndex] as? NSNumber)?.integerValue {
|
||||
|
||||
callback(
|
||||
listController: strongSelf,
|
||||
sectionInfo: sectionInfo,
|
||||
sectionIndex: sectionIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ManagedObjectListController: FetchedResultsControllerHandler
|
||||
|
||||
extension ManagedObjectListController: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: FetchedResultsControllerHandler
|
||||
|
||||
private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
switch type {
|
||||
|
||||
case .Insert:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerDidInsertObjectNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeyObject: anObject,
|
||||
UserInfoKeyNewIndexPath: newIndexPath!
|
||||
]
|
||||
)
|
||||
|
||||
case .Delete:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerDidDeleteObjectNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeyObject: anObject,
|
||||
UserInfoKeyIndexPath: indexPath!
|
||||
]
|
||||
)
|
||||
|
||||
case .Update:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerDidUpdateObjectNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeyObject: anObject,
|
||||
UserInfoKeyIndexPath: indexPath!
|
||||
]
|
||||
)
|
||||
|
||||
case .Move:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerDidMoveObjectNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeyObject: anObject,
|
||||
UserInfoKeyIndexPath: indexPath!,
|
||||
UserInfoKeyNewIndexPath: newIndexPath!
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
|
||||
switch type {
|
||||
|
||||
case .Insert:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerDidInsertSectionNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeySectionInfo: sectionInfo,
|
||||
UserInfoKeySectionIndex: NSNumber(integer: sectionIndex)
|
||||
]
|
||||
)
|
||||
|
||||
case .Delete:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerDidDeleteSectionNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeySectionInfo: sectionInfo,
|
||||
UserInfoKeySectionIndex: NSNumber(integer: sectionIndex)
|
||||
]
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerWillChangeListNotification,
|
||||
object: self
|
||||
)
|
||||
}
|
||||
|
||||
private func controllerDidChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ManagedObjectListControllerDidChangeListNotification,
|
||||
object: self
|
||||
)
|
||||
}
|
||||
|
||||
private func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? {
|
||||
|
||||
return self.sectionIndexTransformer(sectionName: sectionName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FetchedResultsControllerHandler
|
||||
|
||||
private protocol FetchedResultsControllerHandler: class {
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
|
||||
|
||||
func controllerWillChangeContent(controller: NSFetchedResultsController)
|
||||
|
||||
func controllerDidChangeContent(controller: NSFetchedResultsController)
|
||||
|
||||
func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FetchedResultsControllerDelegate
|
||||
|
||||
private final class FetchedResultsControllerDelegate: NSFetchedResultsControllerDelegate {
|
||||
|
||||
// MARK: NSFetchedResultsControllerDelegate
|
||||
|
||||
@objc func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
self.handler?.controllerWillChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc func controllerDidChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
self.handler?.controllerDidChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath)
|
||||
}
|
||||
|
||||
@objc func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
|
||||
self.handler?.controller(controller, didChangeSection: sectionInfo, atIndex: sectionIndex, forChangeType: type)
|
||||
}
|
||||
|
||||
@objc func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? {
|
||||
|
||||
return self.handler?.controller(controller, sectionIndexTitleForSectionName: sectionName)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
weak var handler: FetchedResultsControllerHandler?
|
||||
weak var fetchedResultsController: NSFetchedResultsController? {
|
||||
|
||||
didSet {
|
||||
|
||||
oldValue?.delegate = nil
|
||||
self.fetchedResultsController?.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.fetchedResultsController?.delegate = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private let ManagedObjectListControllerWillChangeListNotification = "ManagedObjectListControllerWillChangeListNotification"
|
||||
private let ManagedObjectListControllerDidChangeListNotification = "ManagedObjectListControllerDidChangeListNotification"
|
||||
|
||||
private let ManagedObjectListControllerDidInsertObjectNotification = "ManagedObjectListControllerDidInsertObjectNotification"
|
||||
private let ManagedObjectListControllerDidDeleteObjectNotification = "ManagedObjectListControllerDidDeleteObjectNotification"
|
||||
private let ManagedObjectListControllerDidUpdateObjectNotification = "ManagedObjectListControllerDidUpdateObjectNotification"
|
||||
private let ManagedObjectListControllerDidMoveObjectNotification = "ManagedObjectListControllerDidMoveObjectNotification"
|
||||
|
||||
private let ManagedObjectListControllerDidInsertSectionNotification = "ManagedObjectListControllerDidInsertSectionNotification"
|
||||
private let ManagedObjectListControllerDidDeleteSectionNotification = "ManagedObjectListControllerDidDeleteSectionNotification"
|
||||
|
||||
private let UserInfoKeyObject = "UserInfoKeyObject"
|
||||
private let UserInfoKeyIndexPath = "UserInfoKeyIndexPath"
|
||||
private let UserInfoKeyNewIndexPath = "UserInfoKeyNewIndexPath"
|
||||
|
||||
private let UserInfoKeySectionInfo = "UserInfoKeySectionInfo"
|
||||
private let UserInfoKeySectionIndex = "UserInfoKeySectionIndex"
|
||||
|
||||
private struct NotificationKey {
|
||||
|
||||
static var willChangeList: Void?
|
||||
static var didChangeList: Void?
|
||||
|
||||
static var didInsertObject: Void?
|
||||
static var didDeleteObject: Void?
|
||||
static var didUpdateObject: Void?
|
||||
static var didMoveObject: Void?
|
||||
|
||||
static var didInsertSection: Void?
|
||||
static var didDeleteSection: Void?
|
||||
}
|
||||
@@ -1,147 +0,0 @@
|
||||
//
|
||||
// ManagedObjectListObserver.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - ManagedObjectListChangeObserver
|
||||
|
||||
/**
|
||||
Implement the `ManagedObjectListChangeObserver` protocol to observe changes to a list of `NSManagedObject`'s. `ManagedObjectListChangeObserver`'s may register themselves to a `ManagedObjectListController`'s `addObserver(_:)` method:
|
||||
|
||||
let listController = CoreStore.observeObjectList(
|
||||
From(MyPersonEntity),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
listController.addObserver(self)
|
||||
*/
|
||||
public protocol ManagedObjectListChangeObserver: class {
|
||||
|
||||
/**
|
||||
The `NSManagedObject` type for the observed list
|
||||
*/
|
||||
typealias EntityType: NSManagedObject
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed list occurs
|
||||
|
||||
:param: listController the `ManagedObjectListController` monitoring the list being observed
|
||||
*/
|
||||
func managedObjectListWillChange(listController: ManagedObjectListController<EntityType>)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed list occurs
|
||||
|
||||
:param: listController the `ManagedObjectListController` monitoring the object being observed
|
||||
*/
|
||||
func managedObjectListDidChange(listController: ManagedObjectListController<EntityType>)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ManagedObjectListObjectObserver
|
||||
|
||||
/**
|
||||
Implement the `ManagedObjectListObjectObserver` protocol to observe detailed changes to a list's object. `ManagedObjectListObjectObserver`'s may register themselves to a `ManagedObjectListController`'s `addObserver(_:)` method:
|
||||
|
||||
let listController = CoreStore.observeObjectList(
|
||||
From(MyPersonEntity),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
listController.addObserver(self)
|
||||
*/
|
||||
public protocol ManagedObjectListObjectObserver: ManagedObjectListChangeObserver {
|
||||
|
||||
/**
|
||||
Notifies that an object was inserted to the specified `NSIndexPath` in the list
|
||||
|
||||
:param: listController the `ManagedObjectListController` monitoring the list being observed
|
||||
:param: object the entity type for the inserted object
|
||||
:param: indexPath the new `NSIndexPath` for the inserted object
|
||||
*/
|
||||
func managedObjectList(listController: ManagedObjectListController<EntityType>, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath)
|
||||
|
||||
/**
|
||||
Notifies that an object was deleted from the specified `NSIndexPath` in the list
|
||||
|
||||
:param: listController the `ManagedObjectListController` monitoring the list being observed
|
||||
:param: object the entity type for the deleted object
|
||||
:param: indexPath the `NSIndexPath` for the deleted object
|
||||
*/
|
||||
func managedObjectList(listController: ManagedObjectListController<EntityType>, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath)
|
||||
|
||||
/**
|
||||
Notifies that an object at the specified `NSIndexPath` was updated
|
||||
|
||||
:param: listController the `ManagedObjectListController` monitoring the list being observed
|
||||
:param: object the entity type for the updated object
|
||||
:param: indexPath the `NSIndexPath` for the updated object
|
||||
*/
|
||||
func managedObjectList(listController: ManagedObjectListController<EntityType>, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath)
|
||||
|
||||
/**
|
||||
Notifies that an object's index changed
|
||||
|
||||
:param: listController the `ManagedObjectListController` monitoring the list being observed
|
||||
:param: object the entity type for the moved object
|
||||
:param: fromIndexPath the previous `NSIndexPath` for the moved object
|
||||
:param: toIndexPath the new `NSIndexPath` for the moved object
|
||||
*/
|
||||
func managedObjectList(listController: ManagedObjectListController<EntityType>, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ManagedObjectListSectionObserver
|
||||
|
||||
/**
|
||||
Implement the `ManagedObjectListSectionObserver` protocol to observe changes to a list's section info. `ManagedObjectListSectionObserver`'s may register themselves to a `ManagedObjectListController`'s `addObserver(_:)` method:
|
||||
|
||||
let listController = CoreStore.observeSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionedBy("age") { "Age \($0)" },
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
listController.addObserver(self)
|
||||
*/
|
||||
public protocol ManagedObjectListSectionObserver: ManagedObjectListObjectObserver {
|
||||
|
||||
/**
|
||||
Notifies that a section was inserted at the specified index
|
||||
|
||||
:param: listController the `ManagedObjectListController` monitoring the list being observed
|
||||
:param: sectionInfo the `NSFetchedResultsSectionInfo` for the inserted section
|
||||
:param: sectionIndex the new section index for the new section
|
||||
*/
|
||||
func managedObjectList(listController: ManagedObjectListController<EntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
|
||||
|
||||
/**
|
||||
Notifies that a section was inserted at the specified index
|
||||
|
||||
:param: listController the `ManagedObjectListController` monitoring the list being observed
|
||||
:param: sectionInfo the `NSFetchedResultsSectionInfo` for the deleted section
|
||||
:param: sectionIndex the previous section index for the deleted section
|
||||
*/
|
||||
func managedObjectList(listController: ManagedObjectListController<EntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
|
||||
}
|
||||
@@ -1,69 +0,0 @@
|
||||
//
|
||||
// ManagedObjectObserver.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: - ManagedObjectObserver
|
||||
|
||||
/**
|
||||
Implement the `ManagedObjectObserver` protocol to observe changes to a single `NSManagedObject` instance. `ManagedObjectObserver`'s may register themselves to a `ManagedObjectController`'s `addObserver(_:)` method:
|
||||
|
||||
let objectController = CoreStore.observeObject(object)
|
||||
objectController.addObserver(self)
|
||||
*/
|
||||
public protocol ManagedObjectObserver: class {
|
||||
|
||||
/**
|
||||
The `NSManagedObject` type for the observed object
|
||||
*/
|
||||
typealias EntityType: NSManagedObject
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed `object` occurs
|
||||
|
||||
:param: objectController the `ManagedObjectController` monitoring the object being observed
|
||||
:param: object the `NSManagedObject` instance being observed
|
||||
*/
|
||||
func managedObjectWillUpdate(objectController: ManagedObjectController<EntityType>, object: EntityType)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed `object` occurs
|
||||
|
||||
:param: objectController the `ManagedObjectController` monitoring the object being observed
|
||||
:param: object the `NSManagedObject` instance being observed
|
||||
:param: changedPersistentKeys a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported.
|
||||
*/
|
||||
func managedObjectWasUpdated(objectController: ManagedObjectController<EntityType>, object: EntityType, changedPersistentKeys: Set<KeyPath>)
|
||||
|
||||
/**
|
||||
Handles processing right after `object` is deleted
|
||||
|
||||
:param: objectController the `ManagedObjectController` monitoring the object being observed
|
||||
:param: object the `NSManagedObject` instance being observed
|
||||
*/
|
||||
func managedObjectWasDeleted(objectController: ManagedObjectController<EntityType>, object: EntityType)
|
||||
}
|
||||
324
CoreStore/Observing/ObjectMonitor.swift
Normal file
324
CoreStore/Observing/ObjectMonitor.swift
Normal file
@@ -0,0 +1,324 @@
|
||||
//
|
||||
// ObjectMonitor.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: - ObjectMonitor
|
||||
|
||||
/**
|
||||
The `ObjectMonitor` monitors changes to a single `NSManagedObject` instance. Observers that implement the `ObjectObserver` protocol may then register themselves to the `ObjectMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorObject(object)
|
||||
monitor.addObserver(self)
|
||||
|
||||
The created `ObjectMonitor` instance needs to be held on (retained) for as long as the object needs to be observed.
|
||||
|
||||
Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
|
||||
*/
|
||||
@available(OSX, unavailable)
|
||||
public final class ObjectMonitor<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted.
|
||||
*/
|
||||
public var object: T? {
|
||||
|
||||
return self.fetchedResultsController.fetchedObjects?.first as? T
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `NSManagedObject` instance being observed still exists, or `false` if the object was already deleted.
|
||||
*/
|
||||
public var isObjectDeleted: Bool {
|
||||
|
||||
return self.object?.managedObjectContext == nil
|
||||
}
|
||||
|
||||
/**
|
||||
Registers an `ObjectObserver` to be notified when changes to the receiver's `object` are made.
|
||||
|
||||
To prevent retain-cycles, `ObjectMonitor` only keeps `weak` references to its observers.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
Calling `addObserver(_:)` multiple times on the same observer is safe, as `ObjectMonitor` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
- parameter observer: an `ObjectObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ObjectObserver where U.ObjectEntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to add an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&self.willChangeObjectKey,
|
||||
name: ObjectMonitorWillChangeObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor) -> Void in
|
||||
|
||||
guard let object = monitor.object, let observer = observer else {
|
||||
|
||||
return
|
||||
}
|
||||
observer.objectMonitor(monitor, willUpdateObject: object)
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&self.didDeleteObjectKey,
|
||||
name: ObjectMonitorDidDeleteObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object) -> Void in
|
||||
|
||||
guard let observer = observer else {
|
||||
|
||||
return
|
||||
}
|
||||
observer.objectMonitor(monitor, didDeleteObject: object)
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&self.didUpdateObjectKey,
|
||||
name: ObjectMonitorDidUpdateObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak self, weak observer] (monitor, object) -> Void in
|
||||
|
||||
guard let strongSelf = self, let observer = observer else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let previousCommitedAttributes = strongSelf.lastCommittedAttributes
|
||||
let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [String: NSObject]
|
||||
|
||||
var changedKeys = Set<String>()
|
||||
for key in currentCommitedAttributes.keys {
|
||||
|
||||
if previousCommitedAttributes[key] != currentCommitedAttributes[key] {
|
||||
|
||||
changedKeys.insert(key)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.lastCommittedAttributes = currentCommitedAttributes
|
||||
observer.objectMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
changedPersistentKeys: changedKeys
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Unregisters an `ObjectObserver` from receiving notifications for changes to the receiver's `object`.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
- parameter observer: an `ObjectObserver` to unregister notifications to
|
||||
*/
|
||||
public func removeObserver<U: ObjectObserver where U.ObjectEntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to remove an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
let nilValue: AnyObject? = nil
|
||||
setAssociatedRetainedObject(nilValue, forKey: &self.willChangeObjectKey, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteObjectKey, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &self.didUpdateObjectKey, inObject: observer)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(dataStack: DataStack, object: T) {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
fetchRequest.entity = object.entity
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchRequest.sortDescriptors = []
|
||||
fetchRequest.includesPendingChanges = false
|
||||
fetchRequest.shouldRefreshRefetchedObjects = true
|
||||
|
||||
let fetchedResultsController = NSFetchedResultsController(
|
||||
dataStack: dataStack,
|
||||
fetchRequest: fetchRequest,
|
||||
fetchClauses: [Where("SELF", isEqualTo: object.objectID)]
|
||||
)
|
||||
|
||||
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
|
||||
|
||||
self.fetchedResultsController = fetchedResultsController
|
||||
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
|
||||
self.parentStack = dataStack
|
||||
|
||||
fetchedResultsControllerDelegate.handler = self
|
||||
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
|
||||
try! fetchedResultsController.performFetch()
|
||||
|
||||
self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [String: NSObject]) ?? [:]
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.fetchedResultsControllerDelegate.fetchedResultsController = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let fetchedResultsController: NSFetchedResultsController
|
||||
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
|
||||
private var lastCommittedAttributes = [String: NSObject]()
|
||||
private weak var parentStack: DataStack?
|
||||
|
||||
private var willChangeObjectKey: Void?
|
||||
private var didDeleteObjectKey: Void?
|
||||
private var didUpdateObjectKey: Void?
|
||||
|
||||
private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor<T>) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
guard let strongSelf = self else {
|
||||
|
||||
return
|
||||
}
|
||||
callback(monitor: strongSelf)
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
|
||||
private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor<T>, object: T) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
guard let strongSelf = self,
|
||||
let userInfo = note.userInfo,
|
||||
let object = userInfo[UserInfoKeyObject] as? T else {
|
||||
|
||||
return
|
||||
}
|
||||
callback(monitor: strongSelf, object: object)
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectMonitor: Equatable
|
||||
|
||||
@available(OSX, unavailable)
|
||||
public func ==<T: NSManagedObject>(lhs: ObjectMonitor<T>, rhs: ObjectMonitor<T>) -> Bool {
|
||||
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
@available(OSX, unavailable)
|
||||
extension ObjectMonitor: Equatable { }
|
||||
|
||||
|
||||
// MARK: - ObjectMonitor: FetchedResultsControllerHandler
|
||||
|
||||
@available(OSX, unavailable)
|
||||
extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: FetchedResultsControllerHandler
|
||||
|
||||
internal func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorWillChangeObjectNotification,
|
||||
object: self
|
||||
)
|
||||
}
|
||||
|
||||
internal func controllerDidChangeContent(controller: NSFetchedResultsController) { }
|
||||
|
||||
internal func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
switch type {
|
||||
|
||||
case .Delete:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorDidDeleteObjectNotification,
|
||||
object: self,
|
||||
userInfo: [UserInfoKeyObject: anObject]
|
||||
)
|
||||
|
||||
case .Update:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorDidUpdateObjectNotification,
|
||||
object: self,
|
||||
userInfo: [UserInfoKeyObject: anObject]
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { }
|
||||
|
||||
internal func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? {
|
||||
|
||||
return sectionName
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private let ObjectMonitorWillChangeObjectNotification = "ObjectMonitorWillChangeObjectNotification"
|
||||
private let ObjectMonitorDidDeleteObjectNotification = "ObjectMonitorDidDeleteObjectNotification"
|
||||
private let ObjectMonitorDidUpdateObjectNotification = "ObjectMonitorDidUpdateObjectNotification"
|
||||
|
||||
private let UserInfoKeyObject = "UserInfoKeyObject"
|
||||
|
||||
92
CoreStore/Observing/ObjectObserver.swift
Normal file
92
CoreStore/Observing/ObjectObserver.swift
Normal file
@@ -0,0 +1,92 @@
|
||||
//
|
||||
// ObjectObserver.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: - ObjectObserver
|
||||
|
||||
/**
|
||||
Implement the `ObjectObserver` protocol to observe changes to a single `NSManagedObject` instance. `ObjectObserver`s may register themselves to a `ObjectMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorObject(object)
|
||||
monitor.addObserver(self)
|
||||
*/
|
||||
@available(OSX, unavailable)
|
||||
public protocol ObjectObserver: class {
|
||||
|
||||
/**
|
||||
The `NSManagedObject` type for the observed object
|
||||
*/
|
||||
typealias ObjectEntityType: NSManagedObject
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed `object` occurs
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `NSManagedObject` instance being observed
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed `object` occurs
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `NSManagedObject` instance being observed
|
||||
- parameter changedPersistentKeys: a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set<KeyPath>)
|
||||
|
||||
/**
|
||||
Handles processing right after `object` is deleted
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `NSManagedObject` instance being observed
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, didDeleteObject object: ObjectEntityType)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectObserver (Default Implementations)
|
||||
|
||||
@available(OSX, unavailable)
|
||||
public extension ObjectObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set<KeyPath>) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, didDeleteObject object: ObjectEntityType) { }
|
||||
}
|
||||
73
CoreStore/Observing/SectionBy.swift
Normal file
73
CoreStore/Observing/SectionBy.swift
Normal file
@@ -0,0 +1,73 @@
|
||||
//
|
||||
// SectionBy.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - SectionBy
|
||||
|
||||
/**
|
||||
The `SectionBy` clause indicates the key path to use to group the `ListMonitor` objects into sections. An optional closure can also be provided to transform the value into an appropriate section name:
|
||||
|
||||
let monitor = CoreStore.monitorSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionBy("age") { "Age \($0)" },
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
*/
|
||||
@available(OSX, unavailable)
|
||||
public struct SectionBy {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections
|
||||
|
||||
- parameter sectionKeyPath: the key path to use to group the objects into sections
|
||||
*/
|
||||
public init(_ sectionKeyPath: KeyPath) {
|
||||
|
||||
self.init(sectionKeyPath, { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
|
||||
|
||||
- parameter sectionKeyPath: the key path to use to group the objects into sections
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
|
||||
*/
|
||||
public init(_ sectionKeyPath: KeyPath, _ sectionIndexTransformer: (sectionName: String?) -> String?) {
|
||||
|
||||
self.sectionKeyPath = sectionKeyPath
|
||||
self.sectionIndexTransformer = sectionIndexTransformer
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let sectionKeyPath: KeyPath
|
||||
internal let sectionIndexTransformer: (sectionName: KeyPath?) -> String?
|
||||
}
|
||||
@@ -25,7 +25,9 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - AsynchronousDataTransaction
|
||||
@@ -38,48 +40,49 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Saves the transaction changes asynchronously. This method should not be used after the `commit()` method was already called once.
|
||||
Saves the transaction changes. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: completion the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
|
||||
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
|
||||
*/
|
||||
public func commit(completion: (result: SaveResult) -> Void) {
|
||||
public func commit(completion: (result: SaveResult) -> Void = { _ in }) {
|
||||
|
||||
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.")
|
||||
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
|
||||
let semaphore = GCDSemaphore(0)
|
||||
let group = GCDGroup()
|
||||
group.enter()
|
||||
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
|
||||
|
||||
self.result = result
|
||||
completion(result: result)
|
||||
semaphore.signal()
|
||||
group.leave()
|
||||
}
|
||||
semaphore.wait()
|
||||
}
|
||||
|
||||
/**
|
||||
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
public func commit() {
|
||||
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to commit a \(typeName(self)) outside its designated queue.")
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to commit a \(typeName(self)) more than once.")
|
||||
|
||||
self.isCommitted = true
|
||||
self.result = self.context.saveSynchronously()
|
||||
group.wait()
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
:returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
- 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)).")
|
||||
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,
|
||||
@@ -93,12 +96,15 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Creates a new `NSManagedObject` with the specified entity type.
|
||||
|
||||
:param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
|
||||
:returns: a new `NSManagedObject` instance of 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 <\(T.self)> from an already committed \(typeName(self)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.create(into)
|
||||
}
|
||||
@@ -106,12 +112,16 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: object the `NSManagedObject` type to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
- 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)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(object)
|
||||
}
|
||||
@@ -119,13 +129,17 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: into an `Into` clause specifying the entity type
|
||||
:param: objectID the `NSManagedObjectID` for the object to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
- 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 <\(T.self)> from an already committed \(typeName(self)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(into, objectID)
|
||||
}
|
||||
@@ -133,49 +147,66 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: object the `NSManagedObject` type to be deleted
|
||||
- parameter object: the `NSManagedObject` type to be deleted
|
||||
*/
|
||||
public override func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
:param: object1 the `NSManagedObject` type to be deleted
|
||||
:param: object2 another `NSManagedObject` type to be deleted
|
||||
:param: objects other `NSManagedObject`s type to be deleted
|
||||
- parameter object1: the `NSManagedObject` type to be deleted
|
||||
- parameter object2: another `NSManagedObject` type to be deleted
|
||||
- parameter objects: other `NSManagedObject`s type 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)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete([object1, object2] + objects)
|
||||
super.delete(([object1, object2] + objects).flatMap { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
:param: objects the `NSManagedObject`'s type to be deleted
|
||||
- parameter objects: the `NSManagedObject`s type to be deleted
|
||||
*/
|
||||
public override func delete(objects: [NSManagedObject?]) {
|
||||
public override func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
public override func rollback() {
|
||||
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.isCommitted,
|
||||
"Attempted to rollback an already committed \(typeName(self))."
|
||||
)
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to rollback a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
super.rollback()
|
||||
self.context.reset()
|
||||
}
|
||||
|
||||
|
||||
@@ -185,7 +216,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
|
||||
self.closure = closure
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue)
|
||||
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
|
||||
}
|
||||
|
||||
internal func perform() {
|
||||
@@ -195,7 +226,10 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
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.")
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -205,9 +239,13 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
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.")
|
||||
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
|
||||
|
||||
@@ -25,92 +25,9 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - Into
|
||||
|
||||
/**
|
||||
A `Into` clause contains the destination entity and destination persistent store for a `create(...)` method. A common usage is to just indicate the entity:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
|
||||
For cases where multiple `NSPersistentStore`'s contain the same entity, the destination configuration's name needs to be specified as well:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
|
||||
This helps the `NSManagedObjectContext` to determine which
|
||||
*/
|
||||
public struct Into<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
internal static var defaultConfigurationName: String {
|
||||
|
||||
return "PF_DEFAULT_CONFIGURATION_NAME"
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>())
|
||||
*/
|
||||
public init(){
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity type.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
|
||||
:param: entity the `NSManagedObject` type to be created
|
||||
*/
|
||||
public init(_ entity: T.Type) {
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
|
||||
:param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity type and configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity.self, "Configuration1"))
|
||||
|
||||
:param: entity the `NSManagedObject` type to be created
|
||||
:param: configuration the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ entity: T.Type, _ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let configuration: String?
|
||||
internal let inferStoreIfPossible: Bool
|
||||
}
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - BaseDataTransaction
|
||||
@@ -133,130 +50,142 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
/**
|
||||
Creates a new `NSManagedObject` with the specified entity type.
|
||||
|
||||
:param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
|
||||
:returns: a new `NSManagedObject` instance of 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.transactionQueue.isCurrentExecutionContext(), "Attempted to create an entity of type <\(T.self)> outside its designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to create an entity of type \(typeName(T)) outside its designated queue."
|
||||
)
|
||||
|
||||
let context = self.context
|
||||
let object = T.createInContext(context)
|
||||
|
||||
let entityClass = (into.entityClass as! NSManagedObject.Type)
|
||||
if into.inferStoreIfPossible {
|
||||
|
||||
switch context.parentStack!.persistentStoreForEntityClass(T.self, configuration: nil, inferStoreIfPossible: true) {
|
||||
switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: nil, inferStoreIfPossible: true) {
|
||||
|
||||
case (.Some(let persistentStore), _):
|
||||
case (let persistentStore?, _):
|
||||
let object = entityClass.createInContext(context) as! T
|
||||
context.assignObject(object, toPersistentStore: persistentStore)
|
||||
return object
|
||||
|
||||
case (.None, true):
|
||||
CoreStore.assert(false, "Attempted to create an entity of type \(typeName(object)) with ambiguous destination persistent store, but the configuration name was not specified.")
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.")
|
||||
|
||||
default:
|
||||
CoreStore.assert(false, "Attempted to create an entity of type \(typeName(object)), but a destination persistent store containing the entity type could not be found.")
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)), but a destination persistent store containing the entity type could not be found.")
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
switch context.parentStack!.persistentStoreForEntityClass(T.self, configuration: into.configuration, inferStoreIfPossible: false) {
|
||||
switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: into.configuration, inferStoreIfPossible: false) {
|
||||
|
||||
case (.Some(let persistentStore), _):
|
||||
case (let persistentStore?, _):
|
||||
let object = entityClass.createInContext(context) as! T
|
||||
context.assignObject(object, toPersistentStore: persistentStore)
|
||||
return object
|
||||
|
||||
default:
|
||||
if let configuration = into.configuration {
|
||||
|
||||
CoreStore.assert(false, "Attempted to create an entity of type \(typeName(object)) into the configuration \"\(configuration)\", which it doesn't belong to.")
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the configuration \"\(configuration)\", which it doesn't belong to.")
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.assert(false, "Attempted to create an entity of type \(typeName(object)) into the default configuration, which it doesn't belong to.")
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the default configuration, which it doesn't belong to.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return object
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject`.
|
||||
|
||||
:param: object the `NSManagedObject` type to be edited
|
||||
:returns: an editable proxy for the 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.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type \(typeName(object)) outside its designated queue.")
|
||||
|
||||
return object?.inContext(self.context)
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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`.
|
||||
|
||||
:param: into an `Into` clause specifying the entity type
|
||||
:param: objectID the `NSManagedObjectID` for the object to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
- 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.transactionQueue.isCurrentExecutionContext(), "Attempted to update an entity of type <\(T.self)> outside its designated queue.")
|
||||
CoreStore.assert(into.inferStoreIfPossible || (into.configuration ?? Into.defaultConfigurationName) == objectID.persistentStore?.configurationName, "Attempted to update an entity of type <\(T.self)> but the specified persistent store do not match the `NSManagedObjectID`.")
|
||||
|
||||
return T.inContext(self.context, withObjectID: objectID)
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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`.
|
||||
|
||||
:param: object the `NSManagedObject` type to be deleted
|
||||
- parameter object: the `NSManagedObject` to be deleted
|
||||
*/
|
||||
public func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete an entity outside its designated queue.")
|
||||
|
||||
object?.inContext(self.context)?.deleteFromContext()
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"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.
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
:param: object1 the `NSManagedObject` type to be deleted
|
||||
:param: object2 another `NSManagedObject` type to be deleted
|
||||
:param: objects other `NSManagedObject`s type to be deleted
|
||||
- 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)
|
||||
self.delete(([object1, object2] + objects).flatMap { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
:param: objects the `NSManagedObject`'s type to be deleted
|
||||
- parameter objects: the `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public func delete(objects: [NSManagedObject?]) {
|
||||
public func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
|
||||
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to delete entities outside their designated queue.")
|
||||
CoreStore.assert(
|
||||
self.isRunningInAllowedQueue(),
|
||||
"Attempted to delete entities outside their designated queue."
|
||||
)
|
||||
|
||||
let context = self.context
|
||||
for object in objects {
|
||||
|
||||
object?.inContext(context)?.deleteFromContext()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Saving changes
|
||||
|
||||
/**
|
||||
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid.
|
||||
*/
|
||||
public func rollback() {
|
||||
|
||||
CoreStore.assert(self.transactionQueue.isCurrentExecutionContext(), "Attempted to rollback a \(typeName(self)) outside its designated queue.")
|
||||
|
||||
self.context.reset()
|
||||
objects.forEach { context.fetchExisting($0)?.deleteFromContext() }
|
||||
}
|
||||
|
||||
|
||||
@@ -265,21 +194,38 @@ public /*abstract*/ class BaseDataTransaction {
|
||||
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) {
|
||||
|
||||
self.transactionQueue = queue
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
internal func isRunningInAllowedQueue() -> Bool {
|
||||
|
||||
return self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +35,7 @@ public extension CoreStore {
|
||||
/**
|
||||
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
*/
|
||||
public static func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) {
|
||||
|
||||
@@ -43,23 +43,32 @@ public extension CoreStore {
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
:returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
*/
|
||||
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
- 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 static func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
|
||||
|
||||
return self.defaultStack.beginSynchronous(closure)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. A detached transaction object should typically be only used from the main queue.
|
||||
|
||||
:returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
public static func beginDetached() -> DetachedDataTransaction {
|
||||
Using the `defaultStack`, begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. An unsafe transaction object should typically be only used from the main queue.
|
||||
|
||||
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction {
|
||||
|
||||
return self.defaultStack.beginDetached()
|
||||
return self.defaultStack.beginUnsafe(supportsUndo: supportsUndo)
|
||||
}
|
||||
|
||||
@available(*, deprecated=1.3.1, renamed="beginUnsafe")
|
||||
@warn_unused_result
|
||||
public static func beginDetached() -> UnsafeDataTransaction {
|
||||
|
||||
return self.beginUnsafe()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
@@ -37,12 +39,10 @@ public extension DataStack {
|
||||
/**
|
||||
Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
*/
|
||||
public func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to begin a transaction from a \(typeName(self)) outside the main thread.")
|
||||
|
||||
AsynchronousDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: self.childTransactionQueue,
|
||||
@@ -50,15 +50,13 @@ public extension DataStack {
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
:returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
*/
|
||||
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
- 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(NSThread.isMainThread(), "Attempted to begin a transaction from a \(typeName(self)) outside the main thread.")
|
||||
|
||||
return SynchronousDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: self.childTransactionQueue,
|
||||
@@ -66,16 +64,28 @@ public extension DataStack {
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. A detached transaction object should typically be only used from the main queue.
|
||||
|
||||
:returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
public func beginDetached() -> DetachedDataTransaction {
|
||||
Begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction {
|
||||
|
||||
CoreStore.assert(NSThread.isMainThread(), "Attempted to begin a transaction from a \(typeName(self)) outside the main thread.")
|
||||
|
||||
return DetachedDataTransaction(
|
||||
return UnsafeDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: .Main)
|
||||
queue: .createSerial(
|
||||
"com.coreStore.dataStack.unsafeTransactionQueue",
|
||||
targetQueue: .UserInitiated
|
||||
),
|
||||
supportsUndo: supportsUndo
|
||||
)
|
||||
}
|
||||
|
||||
@available(*, deprecated=1.3.1, renamed="beginUnsafe")
|
||||
@warn_unused_result
|
||||
public func beginDetached() -> UnsafeDataTransaction {
|
||||
|
||||
return self.beginUnsafe()
|
||||
}
|
||||
}
|
||||
|
||||
145
CoreStore/Saving and Processing/Into.swift
Normal file
145
CoreStore/Saving and Processing/Into.swift
Normal file
@@ -0,0 +1,145 @@
|
||||
//
|
||||
// Into.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - Into
|
||||
|
||||
/**
|
||||
A `Into` clause contains the destination entity and destination persistent store for a `create(...)` method. A common usage is to just indicate the entity:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
|
||||
For cases where multiple `NSPersistentStore`s contain the same entity, the destination configuration's name needs to be specified as well:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
|
||||
This helps the `NSManagedObjectContext` to determine which
|
||||
*/
|
||||
public struct Into<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>())
|
||||
*/
|
||||
public init(){
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
self.entityClass = T.self
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity type.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
|
||||
- parameter entity: the `NSManagedObject` type to be created
|
||||
*/
|
||||
public init(_ entity: T.Type) {
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
self.entityClass = entity
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity class.
|
||||
|
||||
- parameter entityClass: the `NSManagedObject` class type to be created
|
||||
*/
|
||||
public init(_ entityClass: AnyClass) {
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
self.entityClass = entityClass
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
self.entityClass = T.self
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity type and configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity.self, "Configuration1"))
|
||||
|
||||
- parameter entity: the `NSManagedObject` type to be created
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ entity: T.Type, _ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
self.entityClass = entity
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity class and configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity.self, "Configuration1"))
|
||||
|
||||
- parameter entityClass: the `NSManagedObject` class type to be created
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
self.entityClass = entityClass
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static var defaultConfigurationName: String {
|
||||
|
||||
return "PF_DEFAULT_CONFIGURATION_NAME"
|
||||
}
|
||||
|
||||
internal let entityClass: AnyClass
|
||||
internal let configuration: String?
|
||||
internal let inferStoreIfPossible: Bool
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
//
|
||||
// NSManagedObject+Transaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 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 Foundation
|
||||
import CoreData
|
||||
|
||||
// MARK: - NSManagedObject
|
||||
|
||||
public extension NSManagedObject {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Returns this object's parent `UnsafeDataTransaction` instance if it was created from one. Returns `nil` if the parent transaction is either an `AsynchronousDataTransaction` or a `SynchronousDataTransaction`, or if the object is not managed by CoreStore.
|
||||
|
||||
When using an `UnsafeDataTransaction` and passing around a temporary object, you can use this property to execute fetches and updates to the transaction without having to pass around both the object and the transaction instances.
|
||||
|
||||
Note that the internal reference to the transaction is `weak`, and it is still the developer's responsibility to retain a strong reference to the `UnsafeDataTransaction`.
|
||||
*/
|
||||
public var unsafeDataTransaction: UnsafeDataTransaction? {
|
||||
|
||||
return self.managedObjectContext?.parentTransaction as? UnsafeDataTransaction
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal dynamic class func createInContext(context: NSManagedObjectContext) -> Self {
|
||||
|
||||
return self.init(
|
||||
entity: context.entityDescriptionForEntityType(self)!,
|
||||
insertIntoManagedObjectContext: context
|
||||
)
|
||||
}
|
||||
|
||||
internal func deleteFromContext() {
|
||||
|
||||
self.managedObjectContext?.deleteObject(self)
|
||||
}
|
||||
}
|
||||
@@ -91,9 +91,7 @@ public enum SaveResult {
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(NSError(
|
||||
coreStoreErrorCode: errorCode,
|
||||
userInfo: userInfo))
|
||||
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,7 +25,9 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - SynchronousDataTransaction
|
||||
@@ -42,8 +44,14 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
*/
|
||||
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.")
|
||||
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()
|
||||
@@ -52,13 +60,19 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: closure the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
:returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
- 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)).")
|
||||
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,
|
||||
@@ -72,12 +86,15 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Creates a new `NSManagedObject` with the specified entity type.
|
||||
|
||||
:param: into the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
|
||||
:returns: a new `NSManagedObject` instance of 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 <\(T.self)> from an already committed \(typeName(self)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.create(into)
|
||||
}
|
||||
@@ -85,12 +102,16 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: object the `NSManagedObject` type to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
- 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)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(object)
|
||||
}
|
||||
@@ -98,13 +119,17 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: into an `Into` clause specifying the entity type
|
||||
:param: objectID the `NSManagedObjectID` for the object to be edited
|
||||
:returns: an editable proxy for the specified `NSManagedObject`.
|
||||
- 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 <\(T.self)> from an already committed \(typeName(self)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(into, objectID)
|
||||
}
|
||||
@@ -112,74 +137,95 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
/**
|
||||
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
:param: object the `NSManagedObject` type to be deleted
|
||||
- parameter object: the `NSManagedObject` type to be deleted
|
||||
*/
|
||||
public override func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
:param: object1 the `NSManagedObject` type to be deleted
|
||||
:param: object2 another `NSManagedObject` type to be deleted
|
||||
:param: objects other `NSManagedObject`s type to be deleted
|
||||
- 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)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete([object1, object2] + objects)
|
||||
super.delete(([object1, object2] + objects).flatMap { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`'s.
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
:param: objects the `NSManagedObject`'s type to be deleted
|
||||
- parameter objects: the `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public override func delete(objects: [NSManagedObject?]) {
|
||||
public override func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
|
||||
|
||||
CoreStore.assert(!self.isCommitted, "Attempted to delete an entities from an already committed \(typeName(self)).")
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
*/
|
||||
public override func rollback() {
|
||||
*/
|
||||
@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.isCommitted,
|
||||
"Attempted to rollback an already committed \(typeName(self))."
|
||||
)
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to rollback a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
super.rollback()
|
||||
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.")
|
||||
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
|
||||
}
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: SynchronousDataTransaction) -> Void) {
|
||||
|
||||
self.closure = closure
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
|
||||
138
CoreStore/Saving and Processing/UnsafeDataTransaction.swift
Normal file
138
CoreStore/Saving and Processing/UnsafeDataTransaction.swift
Normal file
@@ -0,0 +1,138 @@
|
||||
//
|
||||
// UnsafeDataTransaction.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
|
||||
|
||||
|
||||
@available(*, deprecated=1.3.1, renamed="UnsafeDataTransaction")
|
||||
public typealias DetachedDataTransaction = UnsafeDataTransaction
|
||||
|
||||
|
||||
// MARK: - UnsafeDataTransaction
|
||||
|
||||
/**
|
||||
The `UnsafeDataTransaction` provides an interface for non-contiguous `NSManagedObject` creates, updates, and deletes. This is useful for making temporary changes, such as partially filled forms. An unsafe transaction object should typically be only used from the main queue.
|
||||
*/
|
||||
public final class UnsafeDataTransaction: BaseDataTransaction {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Saves the transaction changes asynchronously. For a `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
|
||||
|
||||
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
|
||||
*/
|
||||
public func commit(completion: (result: SaveResult) -> Void) {
|
||||
|
||||
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
|
||||
|
||||
self.result = result
|
||||
completion(result: result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Rolls back the transaction.
|
||||
*/
|
||||
public func rollback() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.supportsUndo,
|
||||
"Attempted to rollback a \(typeName(self)) with Undo support disabled."
|
||||
)
|
||||
self.context.rollback()
|
||||
}
|
||||
|
||||
/**
|
||||
Undo's the last change made to the transaction.
|
||||
*/
|
||||
public func undo() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.supportsUndo,
|
||||
"Attempted to undo a \(typeName(self)) with Undo support disabled."
|
||||
)
|
||||
self.context.undo()
|
||||
}
|
||||
|
||||
/**
|
||||
Redo's the last undone change to the transaction.
|
||||
*/
|
||||
public func redo() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.supportsUndo,
|
||||
"Attempted to redo a \(typeName(self)) with Undo support disabled."
|
||||
)
|
||||
self.context.redo()
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction {
|
||||
|
||||
return UnsafeDataTransaction(
|
||||
mainContext: self.context,
|
||||
queue: self.transactionQueue,
|
||||
supportsUndo: supportsUndo
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObjectContext` for this unsafe transaction. Use only for cases where external frameworks need an `NSManagedObjectContext` instance to work with.
|
||||
|
||||
Note that it is the developer's responsibility to ensure the following:
|
||||
- that the `UnsafeDataTransaction` that owns this context should be strongly referenced and prevented from being deallocated during the context's lifetime
|
||||
- that all saves will be done either through the `UnsafeDataTransaction`'s `commit(...)` method, or by calling `save()` manually on the context, its parent, and all other ancestor contexts if there are any.
|
||||
*/
|
||||
public var internalContext: NSManagedObjectContext {
|
||||
|
||||
return self.context
|
||||
}
|
||||
|
||||
@available(*, deprecated=1.3.1, renamed="beginUnsafe")
|
||||
@warn_unused_result
|
||||
public func beginDetached() -> UnsafeDataTransaction {
|
||||
|
||||
return self.beginUnsafe()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, supportsUndo: Bool) {
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue, supportsUndo: supportsUndo, bypassesQueueing: true)
|
||||
}
|
||||
}
|
||||
@@ -25,60 +25,81 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#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`.
|
||||
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
:returns: a `PersistentStoreResult` indicating success or failure.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addInMemoryStore(configuration: String? = nil) -> PersistentStoreResult {
|
||||
public static func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
return self.defaultStack.addInMemoryStore(configuration: configuration)
|
||||
return try self.defaultStack.addInMemoryStoreAndWait(configuration: configuration)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the `defaultStack` an SQLite store from the given SQLite file name.
|
||||
|
||||
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
:param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
|
||||
:param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
|
||||
:returns: a `PersistentStoreResult` indicating success or failure.
|
||||
- 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: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult {
|
||||
public static func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileName,
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
automigrating: automigrating,
|
||||
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the `defaultStack` an SQLite store from the given SQLite file URL.
|
||||
|
||||
:param: fileURL the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
:param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
|
||||
:param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
:returns: a `PersistentStoreResult` indicating success or failure.
|
||||
- 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, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult {
|
||||
public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return self.defaultStack.addSQLiteStoreAndWait(
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
automigrating: automigrating,
|
||||
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -25,12 +25,14 @@
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
internal let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first as! NSURL
|
||||
internal let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first!
|
||||
|
||||
internal let applicationName = ((NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData")
|
||||
internal let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
|
||||
|
||||
internal let defaultSQLiteStoreURL = applicationSupportDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite")
|
||||
|
||||
@@ -44,233 +46,235 @@ public final class DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from a model created by merging all the models found in all bundles.
|
||||
*/
|
||||
public convenience init() {
|
||||
|
||||
let mergedModel: NSManagedObjectModel! = NSManagedObjectModel.mergedModelFromBundles(NSBundle.allBundles())
|
||||
CoreStore.assert(mergedModel != nil, "Could not create a merged <\(NSManagedObjectModel.self)> from all bundles.")
|
||||
|
||||
self.init(managedObjectModel: mergedModel)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from the specified model name.
|
||||
|
||||
:param: modelName the name of the (.xcdatamodeld) model file.
|
||||
*/
|
||||
public convenience init(modelName: String) {
|
||||
|
||||
let modelFilePath: String! = NSBundle.mainBundle().pathForResource(modelName, ofType: "momd")
|
||||
CoreStore.assert(modelFilePath != nil, "Could not find a \"momd\" resource from the main bundle.")
|
||||
|
||||
let managedObjectModel: NSManagedObjectModel! = NSManagedObjectModel(contentsOfURL: NSURL(fileURLWithPath: modelFilePath)!)
|
||||
CoreStore.assert(managedObjectModel != nil, "Could not create an <\(NSManagedObjectModel.self)> from the resource at path \"\(modelFilePath)\".")
|
||||
|
||||
self.init(managedObjectModel: managedObjectModel)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from an `NSManagedObjectModel`.
|
||||
|
||||
:param: managedObjectModel the `NSManagedObjectModel` of the (.xcdatamodeld) model file.
|
||||
- parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name will be used.
|
||||
- parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used.
|
||||
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for incremental migration. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
public required init(managedObjectModel: NSManagedObjectModel) {
|
||||
public required init(modelName: String = applicationName, bundle: NSBundle = NSBundle.mainBundle(), migrationChain: MigrationChain = nil) {
|
||||
|
||||
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: managedObjectModel)
|
||||
CoreStore.assert(
|
||||
migrationChain.valid,
|
||||
"Invalid migration chain passed to the \(typeName(DataStack)). Check that the model versions' order is correct and that no repetitions or ambiguities exist."
|
||||
)
|
||||
|
||||
let model = NSManagedObjectModel.fromBundle(
|
||||
bundle,
|
||||
modelName: modelName,
|
||||
modelVersionHints: migrationChain.leafVersions
|
||||
)
|
||||
|
||||
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
|
||||
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
|
||||
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
|
||||
|
||||
var entityNameMapping = [EntityClassNameType: EntityNameType]()
|
||||
var entityConfigurationsMapping = [EntityClassNameType: Set<String>]()
|
||||
for entityDescription in managedObjectModel.entities as! [NSEntityDescription] {
|
||||
|
||||
let managedObjectClassName = entityDescription.managedObjectClassName
|
||||
entityConfigurationsMapping[managedObjectClassName] = []
|
||||
if let entityName = entityDescription.name {
|
||||
|
||||
entityNameMapping[managedObjectClassName] = entityName
|
||||
}
|
||||
}
|
||||
self.entityNameMapping = entityNameMapping
|
||||
self.entityConfigurationsMapping = entityConfigurationsMapping
|
||||
self.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.
|
||||
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
:returns: a `PersistentStoreResult` indicating success or failure.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public func addInMemoryStore(configuration: String? = nil) -> PersistentStoreResult {
|
||||
public func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
var error: NSError?
|
||||
|
||||
var store: NSPersistentStore?
|
||||
var storeError: NSError?
|
||||
coordinator.performBlockAndWait {
|
||||
|
||||
store = coordinator.addPersistentStoreWithType(
|
||||
NSInMemoryStoreType,
|
||||
configuration: configuration,
|
||||
URL: nil,
|
||||
options: nil,
|
||||
error: &error)
|
||||
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 PersistentStoreResult(store)
|
||||
return store
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to add in-memory <\(NSPersistentStore.self)>.")
|
||||
return PersistentStoreResult(error)
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.handleError(
|
||||
NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to add in-memory <\(NSPersistentStore.self)>.")
|
||||
return PersistentStoreResult(.UnknownError)
|
||||
}
|
||||
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.
|
||||
|
||||
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
:param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
|
||||
:param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
|
||||
:returns: a `PersistentStoreResult` indicating success or failure.
|
||||
- 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: String, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult {
|
||||
public func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return self.addSQLiteStoreAndWait(
|
||||
return try self.addSQLiteStoreAndWait(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
automigrating: automigrating,
|
||||
resetStoreOnMigrationFailure: resetStoreOnMigrationFailure
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the stack an SQLite store from the given SQLite file URL.
|
||||
|
||||
:param: fileURL the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
:param: automigrating Set to true to configure Core Data auto-migration, or false to disable. If not specified, defaults to true.
|
||||
:param: resetStoreOnMigrationFailure Set to true to delete the store on migration failure; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
:returns: a `PersistentStoreResult` indicating success or failure.
|
||||
- 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: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, automigrating: Bool = true, resetStoreOnMigrationFailure: Bool = false) -> PersistentStoreResult {
|
||||
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) {
|
||||
|
||||
let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false)
|
||||
|
||||
if store.type == NSSQLiteStoreType
|
||||
&& isExistingStoreAutomigrating == automigrating
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
|
||||
guard store.type == NSSQLiteStoreType
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
|
||||
|
||||
return PersistentStoreResult(store)
|
||||
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
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL),
|
||||
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists.")
|
||||
|
||||
return PersistentStoreResult(.DifferentPersistentStoreExistsAtURL)
|
||||
return store
|
||||
}
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
var directoryError: NSError?
|
||||
if !fileManager.createDirectoryAtURL(
|
||||
_ = try? fileManager.createDirectoryAtURL(
|
||||
fileURL.URLByDeletingLastPathComponent!,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil,
|
||||
error: &directoryError) {
|
||||
|
||||
CoreStore.handleError(
|
||||
directoryError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to create directory for SQLite store at \"\(fileURL)\".")
|
||||
return PersistentStoreResult(directoryError!)
|
||||
}
|
||||
attributes: nil
|
||||
)
|
||||
|
||||
var store: NSPersistentStore?
|
||||
var persistentStoreError: NSError?
|
||||
var storeError: NSError?
|
||||
let options = self.optionsForSQLiteStore()
|
||||
coordinator.performBlockAndWait {
|
||||
|
||||
store = coordinator.addPersistentStoreWithType(
|
||||
NSSQLiteStoreType,
|
||||
configuration: configuration,
|
||||
URL: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["WAL": "journal_mode"],
|
||||
NSInferMappingModelAutomaticallyOption: true,
|
||||
NSMigratePersistentStoresAutomaticallyOption: automigrating],
|
||||
error: &persistentStoreError)
|
||||
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 PersistentStoreResult(store)
|
||||
return store
|
||||
}
|
||||
|
||||
if let error = persistentStoreError
|
||||
where (
|
||||
resetStoreOnMigrationFailure
|
||||
&& (error.code == NSPersistentStoreIncompatibleVersionHashError
|
||||
|| error.code == NSMigrationMissingSourceModelError
|
||||
|| error.code == NSMigrationError)
|
||||
&& error.domain == NSCocoaErrorDomain
|
||||
) {
|
||||
if let error = storeError
|
||||
where (resetStoreOnModelMismatch && error.isCoreDataMigrationError) {
|
||||
|
||||
fileManager.removeItemAtURL(fileURL, error: nil)
|
||||
fileManager.removeItemAtPath(
|
||||
fileURL.path!.stringByAppendingString("-shm"),
|
||||
error: nil)
|
||||
fileManager.removeItemAtPath(
|
||||
fileURL.path!.stringByAppendingString("-wal"),
|
||||
error: nil)
|
||||
fileManager.removeSQLiteStoreAtURL(fileURL)
|
||||
|
||||
var store: NSPersistentStore?
|
||||
coordinator.performBlockAndWait {
|
||||
|
||||
store = coordinator.addPersistentStoreWithType(
|
||||
NSSQLiteStoreType,
|
||||
configuration: configuration,
|
||||
URL: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["WAL": "journal_mode"],
|
||||
NSInferMappingModelAutomaticallyOption: true,
|
||||
NSMigratePersistentStoresAutomaticallyOption: automigrating],
|
||||
error: &persistentStoreError)
|
||||
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 PersistentStoreResult(store)
|
||||
return store
|
||||
}
|
||||
}
|
||||
|
||||
let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
|
||||
CoreStore.handleError(
|
||||
persistentStoreError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\".")
|
||||
|
||||
return PersistentStoreResult(.UnknownError)
|
||||
error,
|
||||
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\"."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
|
||||
@@ -279,28 +283,43 @@ public final class DataStack {
|
||||
internal let coordinator: NSPersistentStoreCoordinator
|
||||
internal let rootSavingContext: NSManagedObjectContext
|
||||
internal let mainContext: NSManagedObjectContext
|
||||
internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue")
|
||||
|
||||
internal func entityNameForEntityClass(entityClass: NSManagedObject.Type) -> String? {
|
||||
internal let model: NSManagedObjectModel
|
||||
internal let migrationChain: MigrationChain
|
||||
internal let childTransactionQueue: GCDQueue = .createSerial("com.coreStore.dataStack.childTransactionQueue")
|
||||
internal let migrationQueue: NSOperationQueue = {
|
||||
|
||||
return self.entityNameMapping[NSStringFromClass(entityClass)]
|
||||
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 persistentStoresForEntityClass(entityClass: NSManagedObject.Type) -> [NSPersistentStore]? {
|
||||
internal func entityNameForEntityClass(entityClass: AnyClass) -> String? {
|
||||
|
||||
return self.model.entityNameForClass(entityClass)
|
||||
}
|
||||
|
||||
internal func persistentStoresForEntityClass(entityClass: AnyClass) -> [NSPersistentStore]? {
|
||||
|
||||
var returnValue: [NSPersistentStore]? = nil
|
||||
self.storeMetadataUpdateQueue.barrierSync {
|
||||
|
||||
let configurationsForEntity = self.entityConfigurationsMapping[NSStringFromClass(entityClass)] ?? []
|
||||
returnValue = map(configurationsForEntity) {
|
||||
returnValue = self.entityConfigurationsMapping[NSStringFromClass(entityClass)]?.map {
|
||||
|
||||
return self.configurationStoreMapping[$0]!
|
||||
}
|
||||
} ?? []
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
|
||||
internal func persistentStoreForEntityClass(entityClass: NSManagedObject.Type, configuration: String?, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
|
||||
internal func persistentStoreForEntityClass(entityClass: AnyClass, configuration: String?, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
|
||||
|
||||
var returnValue: (store: NSPersistentStore?, isAmbiguous: Bool) = (store: nil, isAmbiguous: false)
|
||||
self.storeMetadataUpdateQueue.barrierSync {
|
||||
@@ -340,8 +359,12 @@ public final class DataStack {
|
||||
|
||||
let configurationName = persistentStore.configurationName
|
||||
self.configurationStoreMapping[configurationName] = persistentStore
|
||||
for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) as? [NSEntityDescription] ?? []) {
|
||||
for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) ?? []) {
|
||||
|
||||
if self.entityConfigurationsMapping[entityDescription.managedObjectClassName] == nil {
|
||||
|
||||
self.entityConfigurationsMapping[entityDescription.managedObjectClassName] = []
|
||||
}
|
||||
self.entityConfigurationsMapping[entityDescription.managedObjectClassName]?.insert(configurationName)
|
||||
}
|
||||
}
|
||||
@@ -350,12 +373,15 @@ public final class DataStack {
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private typealias EntityClassNameType = String
|
||||
private typealias EntityNameType = String
|
||||
private typealias ConfigurationNameType = String
|
||||
|
||||
private let entityNameMapping: [EntityClassNameType: EntityNameType]
|
||||
private let storeMetadataUpdateQueue = GCDQueue.createConcurrent("com.coreStore.persistentStoreBarrierQueue")
|
||||
private var configurationStoreMapping = [ConfigurationNameType: NSPersistentStore]()
|
||||
private var entityConfigurationsMapping = [EntityClassNameType: Set<String>]()
|
||||
private var configurationStoreMapping = [String: NSPersistentStore]()
|
||||
private var entityConfigurationsMapping = [String: Set<String>]()
|
||||
|
||||
deinit {
|
||||
|
||||
for store in self.coordinator.persistentStores {
|
||||
|
||||
_ = try? self.coordinator.removePersistentStore(store)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,26 +30,28 @@ import CoreData
|
||||
// MARK: - PersistentStoreResult
|
||||
|
||||
/**
|
||||
The `PersistentStoreResult` indicates the result of initializing the persistent store.
|
||||
The `PersistentStoreResult` indicates the result of an asynchronous initialization of a persistent store.
|
||||
The `PersistentStoreResult` can be treated as a boolean:
|
||||
|
||||
let result = CoreStore.addSQLiteStoreAndWait()
|
||||
if result {
|
||||
// succeeded
|
||||
}
|
||||
else {
|
||||
// failed
|
||||
}
|
||||
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:
|
||||
|
||||
let result = CoreStore.addSQLiteStoreAndWait()
|
||||
switch result {
|
||||
case .Success(let persistentStore):
|
||||
// persistentStore is the related NSPersistentStore instance
|
||||
case .Failure(let error):
|
||||
// error is the NSError instance for the failure
|
||||
}
|
||||
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 {
|
||||
|
||||
@@ -7,10 +7,12 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
B503FADF1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADB1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift */; };
|
||||
B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */; };
|
||||
B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */; };
|
||||
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADD1AFDC71700F90881 /* Palette.swift */; };
|
||||
B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */; };
|
||||
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C111B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel */; };
|
||||
B5125C141B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C131B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel */; };
|
||||
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977D81B120B80003D50A5 /* ObserversViewController.swift */; };
|
||||
B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */; };
|
||||
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52977DE1B120F83003D50A5 /* MapKit.framework */; };
|
||||
@@ -21,54 +23,30 @@
|
||||
B54AAD591AF4D26E00848AE0 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD571AF4D26E00848AE0 /* Main.storyboard */; };
|
||||
B54AAD5B1AF4D26E00848AE0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */; };
|
||||
B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; };
|
||||
B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */; };
|
||||
B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; };
|
||||
B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; };
|
||||
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */; };
|
||||
B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */; };
|
||||
B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964D91B231BCA0075EE4A /* MaleAccount.swift */; };
|
||||
B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */; };
|
||||
B56964DF1B2321E30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */; };
|
||||
B569650C1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */; };
|
||||
B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965171B2E20CC0075EE4A /* TimeZone.swift */; };
|
||||
B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; };
|
||||
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; };
|
||||
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, ); }; };
|
||||
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */; };
|
||||
B5BDC9221C202429008147CD /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; };
|
||||
B5BDC9231C202429008147CD /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5BDC9251C202429008147CD /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9241C202429008147CD /* GCDKit.framework */; };
|
||||
B5BDC9261C202429008147CD /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9241C202429008147CD /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */; };
|
||||
B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25841B36E23C0000406B /* OrganismV1.swift */; };
|
||||
B5EE25871B36E2520000406B /* OrganismV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25861B36E2520000406B /* OrganismV2.swift */; };
|
||||
B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */; };
|
||||
B5EE259B1B3EA4890000406B /* OrganismV3.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE259A1B3EA4890000406B /* OrganismV3.swift */; };
|
||||
B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
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 */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
@@ -76,8 +54,8 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */,
|
||||
B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */,
|
||||
B5BDC9261C202429008147CD /* GCDKit.framework in Embed Frameworks */,
|
||||
B5BDC9231C202429008147CD /* CoreStore.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -85,10 +63,12 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
B503FADB1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectListObserverDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListObserverDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserverDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B503FADD1AFDC71700F90881 /* Palette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palette.swift; sourceTree = "<group>"; };
|
||||
B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PaletteTableViewCell.swift; sourceTree = "<group>"; };
|
||||
B5125C111B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = OrganismV2ToV3.xcmappingmodel; sourceTree = "<group>"; };
|
||||
B5125C131B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcmappingmodel; path = OrganismV3ToV2.xcmappingmodel; sourceTree = "<group>"; };
|
||||
B52977D81B120B80003D50A5 /* ObserversViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObserversViewController.swift; sourceTree = "<group>"; };
|
||||
B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TransactionsDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B52977DE1B120F83003D50A5 /* MapKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MapKit.framework; path = System/Library/Frameworks/MapKit.framework; sourceTree = SDKROOT; };
|
||||
@@ -101,20 +81,28 @@
|
||||
B54AAD581AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||
B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV2ToV3MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = "<group>"; };
|
||||
B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = "<group>"; };
|
||||
B56964D61B231AE90075EE4A /* StackSetupDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = StackSetupDemo.xcdatamodel; sourceTree = "<group>"; };
|
||||
B56964D91B231BCA0075EE4A /* MaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MaleAccount.swift; sourceTree = "<group>"; };
|
||||
B56964DB1B231BCB0075EE4A /* FemaleAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FemaleAccount.swift; sourceTree = "<group>"; };
|
||||
B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
|
||||
B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = "<group>"; };
|
||||
B569650B1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingAndQueryingDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B56965171B2E20CC0075EE4A /* TimeZone.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TimeZone.swift; sourceTree = "<group>"; };
|
||||
B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingResultsViewController.swift; sourceTree = "<group>"; };
|
||||
B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; 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; };
|
||||
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5BDC9211C202429008147CD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = CoreStore.framework; path = "/Users/JohnEstropia/Library/Developer/Xcode/DerivedData/Build/Products/Debug-iphoneos/CoreStore.framework"; sourceTree = "<absolute>"; };
|
||||
B5BDC9241C202429008147CD /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; name = GCDKit.framework; path = "/Users/JohnEstropia/Library/Developer/Xcode/DerivedData/Build/Products/Debug-iphoneos/GCDKit.framework"; sourceTree = "<absolute>"; };
|
||||
B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OrganismTableViewCell.swift; path = "CoreStoreDemo/MIgrations Demo/OrganismTableViewCell.swift"; sourceTree = SOURCE_ROOT; };
|
||||
B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5EE25841B36E23C0000406B /* OrganismV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV1.swift; sourceTree = "<group>"; };
|
||||
B5EE25861B36E2520000406B /* OrganismV2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV2.swift; sourceTree = "<group>"; };
|
||||
B5EE25881B36E2750000406B /* MigrationDemoV3.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV3.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MigrationsDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B5EE259A1B3EA4890000406B /* OrganismV3.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV3.swift; sourceTree = "<group>"; };
|
||||
B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismProtocol.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -123,9 +111,9 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */,
|
||||
B5BDC9221C202429008147CD /* CoreStore.framework in Frameworks */,
|
||||
B5BDC9251C202429008147CD /* GCDKit.framework in Frameworks */,
|
||||
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */,
|
||||
B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */,
|
||||
B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -136,7 +124,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B52977D81B120B80003D50A5 /* ObserversViewController.swift */,
|
||||
B503FADB1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift */,
|
||||
B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */,
|
||||
B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */,
|
||||
B503FADD1AFDC71700F90881 /* Palette.swift */,
|
||||
B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */,
|
||||
@@ -156,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;
|
||||
@@ -189,13 +177,14 @@
|
||||
B503FADA1AFDC71700F90881 /* List and Object Observers Demo */,
|
||||
B52977DB1B120F2C003D50A5 /* Transactions Demo */,
|
||||
B56965091B2B35370075EE4A /* Fetching and Querying Demo */,
|
||||
B569652F1B3591460075EE4A /* Migrations Demo */,
|
||||
B56964C61B20AC200075EE4A /* Loggers Demo */,
|
||||
B54AAD571AF4D26E00848AE0 /* Main.storyboard */,
|
||||
B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */,
|
||||
B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */,
|
||||
B54AAD501AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld */,
|
||||
B56964D51B231AE90075EE4A /* StackSetupDemo.xcdatamodeld */,
|
||||
B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */,
|
||||
B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */,
|
||||
B54AAD4C1AF4D26E00848AE0 /* Supporting Files */,
|
||||
);
|
||||
path = CoreStoreDemo;
|
||||
@@ -239,13 +228,20 @@
|
||||
path = "Fetching and Querying Demo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B583A9151AF5F4F3001F76AF /* Products */ = {
|
||||
B569652F1B3591460075EE4A /* Migrations Demo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B583A91B1AF5F4F4001F76AF /* CoreStore.framework */,
|
||||
B583A91D1AF5F4F4001F76AF /* CoreStoreTests.xctest */,
|
||||
B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */,
|
||||
B5EE259A1B3EA4890000406B /* OrganismV3.swift */,
|
||||
B5EE25861B36E2520000406B /* OrganismV2.swift */,
|
||||
B5EE25841B36E23C0000406B /* OrganismV1.swift */,
|
||||
B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */,
|
||||
B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */,
|
||||
B5125C111B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel */,
|
||||
B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */,
|
||||
B5125C131B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel */,
|
||||
);
|
||||
name = Products;
|
||||
path = "Migrations Demo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
@@ -263,8 +259,6 @@
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
B583A91F1AF5F512001F76AF /* PBXTargetDependency */,
|
||||
B583A9231AF5F542001F76AF /* PBXTargetDependency */,
|
||||
);
|
||||
name = CoreStoreDemo;
|
||||
productName = CoreStoreDemo;
|
||||
@@ -277,7 +271,8 @@
|
||||
B54AAD411AF4D26E00848AE0 /* Project object */ = {
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastUpgradeCheck = 0630;
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0700;
|
||||
ORGANIZATIONNAME = "John Rommel Estropia";
|
||||
TargetAttributes = {
|
||||
B54AAD481AF4D26E00848AE0 = {
|
||||
@@ -296,12 +291,6 @@
|
||||
mainGroup = B54AAD401AF4D26E00848AE0;
|
||||
productRefGroup = B54AAD4A1AF4D26E00848AE0 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = B583A9151AF5F4F3001F76AF /* Products */;
|
||||
ProjectRef = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
B54AAD481AF4D26E00848AE0 /* CoreStoreDemo */,
|
||||
@@ -309,23 +298,6 @@
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
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;
|
||||
@@ -345,11 +317,14 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */,
|
||||
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */,
|
||||
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */,
|
||||
B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */,
|
||||
B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */,
|
||||
B56964DF1B2321E30075EE4A /* MigrationDemo.xcdatamodeld in Sources */,
|
||||
B52977E41B121635003D50A5 /* Place.swift in Sources */,
|
||||
B569650C1B2B36E10075EE4A /* FetchingAndQueryingDemoViewController.swift in Sources */,
|
||||
B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */,
|
||||
B5EE25871B36E2520000406B /* OrganismV2.swift in Sources */,
|
||||
B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */,
|
||||
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */,
|
||||
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */,
|
||||
@@ -357,31 +332,24 @@
|
||||
B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */,
|
||||
B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */,
|
||||
B54AAD521AF4D26E00848AE0 /* CoreStoreDemo.xcdatamodeld in Sources */,
|
||||
B5EE259B1B3EA4890000406B /* OrganismV3.swift in Sources */,
|
||||
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */,
|
||||
B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */,
|
||||
B503FADF1AFDC71700F90881 /* ObjectListObserverDemoViewController.swift in Sources */,
|
||||
B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */,
|
||||
B5125C141B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel in Sources */,
|
||||
B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */,
|
||||
B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */,
|
||||
B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */,
|
||||
B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */,
|
||||
B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */,
|
||||
B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */,
|
||||
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */,
|
||||
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* 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;
|
||||
@@ -423,6 +391,7 @@
|
||||
COPY_PHASE_STRIP = NO;
|
||||
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||
ENABLE_TESTABILITY = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||
GCC_DYNAMIC_NO_PIC = NO;
|
||||
GCC_NO_COMMON_BLOCKS = YES;
|
||||
@@ -438,7 +407,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
@@ -476,7 +445,7 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.3;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
@@ -487,14 +456,10 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos",
|
||||
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-bnajhooxbfnxepepdtkvkfplrqyq/Build/Products/Debug-iphoneos",
|
||||
);
|
||||
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Debug;
|
||||
@@ -503,14 +468,10 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
FRAMEWORK_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-ftknhsqfpsthfogvisxisgpbbhsj/Build/Products/Debug-iphoneos",
|
||||
"$(USER_LIBRARY_DIR)/Developer/Xcode/DerivedData/CoreStoreDemo-bnajhooxbfnxepepdtkvkfplrqyq/Build/Products/Debug-iphoneos",
|
||||
);
|
||||
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
};
|
||||
name = Release;
|
||||
@@ -559,13 +520,14 @@
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
};
|
||||
B56964DD1B2321E30075EE4A /* MigrationDemo.xcdatamodeld */ = {
|
||||
B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */ = {
|
||||
isa = XCVersionGroup;
|
||||
children = (
|
||||
B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */,
|
||||
B56964DE1B2321E30075EE4A /* MigrationDemo.xcdatamodel */,
|
||||
B5EE25881B36E2750000406B /* MigrationDemoV3.xcdatamodel */,
|
||||
B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */,
|
||||
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */,
|
||||
);
|
||||
currentVersion = B56964E01B2326F30075EE4A /* MigrationDemoV2.xcdatamodel */;
|
||||
currentVersion = B5EE25881B36E2750000406B /* MigrationDemoV3.xcdatamodel */;
|
||||
path = MigrationDemo.xcdatamodeld;
|
||||
sourceTree = "<group>";
|
||||
versionGroupType = wrapper.xcdatamodel;
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>IDESourceControlProjectIdentifier</key>
|
||||
<string>B6855E48-4B19-4321-B1C7-CB2706E12777</string>
|
||||
<key>IDESourceControlProjectName</key>
|
||||
<string>project</string>
|
||||
<string>CoreStoreDemo</string>
|
||||
<key>IDESourceControlProjectOriginsDictionary</key>
|
||||
<dict>
|
||||
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
|
||||
@@ -16,13 +16,13 @@
|
||||
<string>github.com:JohnEstropia/GCDKit.git</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectPath</key>
|
||||
<string>CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace</string>
|
||||
<string>CoreStoreDemo/CoreStoreDemo.xcodeproj</string>
|
||||
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
|
||||
<dict>
|
||||
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
|
||||
<string>../../..</string>
|
||||
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
|
||||
<string>../../..Libraries/GCDKit/</string>
|
||||
<string>../../..Libraries/GCDKit</string>
|
||||
</dict>
|
||||
<key>IDESourceControlProjectURL</key>
|
||||
<string>github.com:JohnEstropia/CoreStore.git</string>
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key>
|
||||
<true/>
|
||||
<key>SnapshotAutomaticallyBeforeSignificantChanges</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,39 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Bucket
|
||||
type = "1"
|
||||
version = "2.0">
|
||||
<Breakpoints>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "../CoreStore/Migrating/DataStack+Migration.swift"
|
||||
timestampString = "457102815.005196"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "80"
|
||||
endingLineNumber = "80"
|
||||
landmarkName = "needsMigrationForSQLiteStore(fileURL:configuration:)"
|
||||
landmarkType = "5">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
<BreakpointProxy
|
||||
BreakpointExtensionID = "Xcode.Breakpoint.FileBreakpoint">
|
||||
<BreakpointContent
|
||||
shouldBeEnabled = "Yes"
|
||||
ignoreCount = "0"
|
||||
continueAfterRunningActions = "No"
|
||||
filePath = "../CoreStore/Migrating/DataStack+Migration.swift"
|
||||
timestampString = "457102823.242858"
|
||||
startingColumnNumber = "9223372036854775807"
|
||||
endingColumnNumber = "9223372036854775807"
|
||||
startingLineNumber = "165"
|
||||
endingLineNumber = "165"
|
||||
landmarkName = "upgradeSQLiteStoreIfNeeded(fileURL:configuration:completion:)"
|
||||
landmarkType = "5">
|
||||
</BreakpointContent>
|
||||
</BreakpointProxy>
|
||||
</Breakpoints>
|
||||
</Bucket>
|
||||
@@ -1,11 +1,40 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8152.3" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8124.4"/>
|
||||
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
|
||||
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<customFonts key="customFonts">
|
||||
<mutableArray key="AppleSDGothicNeo.ttc">
|
||||
<string>AppleSDGothicNeo-Regular</string>
|
||||
</mutableArray>
|
||||
<mutableArray key="HelveticaNeue.ttc">
|
||||
<string>HelveticaNeue-Bold</string>
|
||||
<string>HelveticaNeue-Bold</string>
|
||||
<string>HelveticaNeue</string>
|
||||
<string>HelveticaNeue</string>
|
||||
</mutableArray>
|
||||
<mutableArray key="HelveticaNeueLights.ttc">
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Thin</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
</mutableArray>
|
||||
</customFonts>
|
||||
<scenes>
|
||||
<!--SNS Accounts-->
|
||||
<scene sceneID="3If-81-mNf">
|
||||
@@ -14,7 +43,8 @@
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="S3A-lm-AuA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<view key="tableHeaderView" contentMode="scaleToFill" id="yud-WH-MPa">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="150"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
@@ -52,17 +82,21 @@
|
||||
</view>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="8b8-lM-Krq" detailTextLabel="hR1-Zb-BOk" style="IBUITableViewCellStyleValue1" id="dMt-nx-EO5">
|
||||
<rect key="frame" x="0.0" y="236" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="dMt-nx-EO5" id="gdK-VV-zNb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8b8-lM-Krq">
|
||||
<rect key="frame" x="15" y="13" width="28.5" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hR1-Zb-BOk">
|
||||
<rect key="frame" x="533" y="13" width="52" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
|
||||
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="calibratedRGB"/>
|
||||
@@ -88,6 +122,155 @@
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3694" y="650"/>
|
||||
</scene>
|
||||
<!--Evolution-->
|
||||
<scene sceneID="iwU-Hv-zNr">
|
||||
<objects>
|
||||
<viewController automaticallyAdjustsScrollViewInsets="NO" id="iVv-Vc-nCL" customClass="MigrationsDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<layoutGuides>
|
||||
<viewControllerLayoutGuide type="top" id="Un6-jI-efh"/>
|
||||
<viewControllerLayoutGuide type="bottom" id="t9A-zf-Iew"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="75P-2m-6cr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="WUc-3Y-Quw">
|
||||
<rect key="frame" x="0.0" y="324" width="600" height="276"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="OrganismTableViewCell" id="WVb-th-o8c" customClass="OrganismTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="22" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="WVb-th-o8c" id="JBq-Ml-a9p">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OQf-Bd-Zze">
|
||||
<rect key="frame" x="520" y="8" width="72" height="27.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<inset key="contentEdgeInsets" minX="7" minY="0.0" maxX="7" maxY="0.0"/>
|
||||
<state key="normal" title="mutate!"/>
|
||||
<connections>
|
||||
<action selector="mutateButtonTouchUpInside:" destination="WVb-th-o8c" eventType="touchUpInside" id="REw-UX-rJ0"/>
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VZk-6K-4ut">
|
||||
<rect key="frame" x="15" y="8" width="495" height="27.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="17"/>
|
||||
<color key="textColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="OQf-Bd-Zze" firstAttribute="top" secondItem="JBq-Ml-a9p" secondAttribute="topMargin" id="39z-i5-EeS"/>
|
||||
<constraint firstItem="VZk-6K-4ut" firstAttribute="top" secondItem="JBq-Ml-a9p" secondAttribute="topMargin" id="WO1-gf-MIu"/>
|
||||
<constraint firstItem="VZk-6K-4ut" firstAttribute="leading" secondItem="JBq-Ml-a9p" secondAttribute="leadingMargin" constant="7" id="X0N-Il-F26"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="OQf-Bd-Zze" secondAttribute="bottom" id="YwC-dw-rGI"/>
|
||||
<constraint firstItem="OQf-Bd-Zze" firstAttribute="leading" secondItem="VZk-6K-4ut" secondAttribute="trailing" constant="10" id="mo1-NZ-iPa"/>
|
||||
<constraint firstAttribute="bottomMargin" secondItem="VZk-6K-4ut" secondAttribute="bottom" id="ucK-FJ-7v4"/>
|
||||
<constraint firstItem="OQf-Bd-Zze" firstAttribute="trailing" secondItem="JBq-Ml-a9p" secondAttribute="trailingMargin" id="v3e-PY-3wW"/>
|
||||
</constraints>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<outlet property="dnaLabel" destination="VZk-6K-4ut" id="CSU-Ig-qMu"/>
|
||||
<outlet property="mutateButton" destination="OQf-Bd-Zze" id="5gd-Jx-tMT"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</prototypes>
|
||||
<connections>
|
||||
<outlet property="dataSource" destination="iVv-Vc-nCL" id="yp2-X4-fhE"/>
|
||||
<outlet property="delegate" destination="iVv-Vc-nCL" id="V5b-s8-XOl"/>
|
||||
</connections>
|
||||
</tableView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XKA-Ub-c2X">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="260"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="i7U-bW-juB" customClass="UIButton">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="260"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Organism" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zxy-nY-P44">
|
||||
<rect key="frame" x="20" y="90.5" width="560" height="26.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="22"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="attributes" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6Ac-xl-ldZ">
|
||||
<rect key="frame" x="20" y="131.5" width="560" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="17"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="rAZ-eJ-sxy">
|
||||
<rect key="frame" x="20" y="20" width="560" height="29"/>
|
||||
<segments>
|
||||
<segment title="First"/>
|
||||
<segment title="Second"/>
|
||||
<segment title=""/>
|
||||
</segments>
|
||||
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<connections>
|
||||
<action selector="segmentedControlValueChanged:" destination="iVv-Vc-nCL" eventType="valueChanged" id="RwG-kW-RPg"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<progressView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="869-wx-Odb">
|
||||
<rect key="frame" x="20" y="68" width="560" height="2"/>
|
||||
<color key="progressTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="trackTintColor" white="1" alpha="0.20000000000000001" colorSpace="calibratedWhite"/>
|
||||
</progressView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="zxy-nY-P44" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="1u1-Tq-hRn"/>
|
||||
<constraint firstItem="6Ac-xl-ldZ" firstAttribute="top" secondItem="zxy-nY-P44" secondAttribute="bottom" constant="14.5" id="39B-9l-O3g"/>
|
||||
<constraint firstAttribute="trailing" secondItem="zxy-nY-P44" secondAttribute="trailing" constant="20" id="3d5-Zk-rB1"/>
|
||||
<constraint firstAttribute="trailing" secondItem="6Ac-xl-ldZ" secondAttribute="trailing" constant="20" id="EJq-uA-8dQ"/>
|
||||
<constraint firstItem="rAZ-eJ-sxy" firstAttribute="top" secondItem="i7U-bW-juB" secondAttribute="top" constant="20" id="Ey7-mP-RNA"/>
|
||||
<constraint firstItem="zxy-nY-P44" firstAttribute="top" secondItem="869-wx-Odb" secondAttribute="bottom" constant="20.5" id="GU3-JM-b99"/>
|
||||
<constraint firstItem="rAZ-eJ-sxy" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="KKJ-gG-pFL"/>
|
||||
<constraint firstAttribute="trailing" secondItem="869-wx-Odb" secondAttribute="trailing" constant="20" id="Lni-gD-7h0"/>
|
||||
<constraint firstItem="869-wx-Odb" firstAttribute="top" secondItem="rAZ-eJ-sxy" secondAttribute="bottom" constant="20" id="QBz-cP-SVZ"/>
|
||||
<constraint firstAttribute="trailing" secondItem="rAZ-eJ-sxy" secondAttribute="trailing" constant="20" id="WoD-cr-aj9"/>
|
||||
<constraint firstAttribute="height" constant="260" id="XTa-ql-yEW"/>
|
||||
<constraint firstItem="6Ac-xl-ldZ" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="jkv-ow-f0q"/>
|
||||
<constraint firstItem="869-wx-Odb" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="qgE-y5-uh8"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="260" id="90R-Mf-iAB"/>
|
||||
<constraint firstItem="i7U-bW-juB" firstAttribute="leading" secondItem="XKA-Ub-c2X" secondAttribute="leading" id="e9h-f4-c37"/>
|
||||
<constraint firstItem="i7U-bW-juB" firstAttribute="top" secondItem="XKA-Ub-c2X" secondAttribute="top" id="vJV-dl-RLx"/>
|
||||
<constraint firstAttribute="trailing" secondItem="i7U-bW-juB" secondAttribute="trailing" id="wYt-hg-yKM"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="XKA-Ub-c2X" secondAttribute="trailing" id="U0P-51-KT9"/>
|
||||
<constraint firstAttribute="trailing" secondItem="WUc-3Y-Quw" secondAttribute="trailing" id="i7I-7B-97K"/>
|
||||
<constraint firstItem="WUc-3Y-Quw" firstAttribute="leading" secondItem="75P-2m-6cr" secondAttribute="leading" id="i8b-GU-yFg"/>
|
||||
<constraint firstItem="XKA-Ub-c2X" firstAttribute="top" secondItem="Un6-jI-efh" secondAttribute="bottom" id="lU2-wP-RQb"/>
|
||||
<constraint firstItem="XKA-Ub-c2X" firstAttribute="leading" secondItem="75P-2m-6cr" secondAttribute="leading" id="oa7-qv-9am"/>
|
||||
<constraint firstItem="t9A-zf-Iew" firstAttribute="top" secondItem="WUc-3Y-Quw" secondAttribute="bottom" id="tts-uy-w5J"/>
|
||||
<constraint firstItem="WUc-3Y-Quw" firstAttribute="top" secondItem="XKA-Ub-c2X" secondAttribute="bottom" id="vjy-iN-Xi7"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<navigationItem key="navigationItem" title="Evolution" id="uc7-DN-PYG"/>
|
||||
<connections>
|
||||
<outlet property="headerContainer" destination="i7U-bW-juB" id="hpf-7a-yBd"/>
|
||||
<outlet property="organismLabel" destination="6Ac-xl-ldZ" id="pgF-ej-ovy"/>
|
||||
<outlet property="progressView" destination="869-wx-Odb" id="um3-UM-uOP"/>
|
||||
<outlet property="segmentedControl" destination="rAZ-eJ-sxy" id="K3Q-X2-Jrm"/>
|
||||
<outlet property="tableView" destination="WUc-3Y-Quw" id="3Ki-v0-lTb"/>
|
||||
<outlet property="titleLabel" destination="zxy-nY-P44" id="9JO-hP-yVp"/>
|
||||
</connections>
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="LzB-gZ-6fG" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3694" y="4113"/>
|
||||
</scene>
|
||||
<!--CoreStore Demos-->
|
||||
<scene sceneID="0Be-vc-h1W">
|
||||
<objects>
|
||||
@@ -100,17 +283,21 @@
|
||||
<tableViewSection id="wIP-Hn-YfF">
|
||||
<cells>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Q3n-Df-v1t" detailTextLabel="Hbn-cf-Y7m" style="IBUITableViewCellStyleSubtitle" id="AXm-KE-45G">
|
||||
<rect key="frame" x="0.0" y="99" width="600" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="AXm-KE-45G" id="9te-Wx-hkf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Accounts" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Q3n-Df-v1t">
|
||||
<rect key="frame" x="15" y="6" width="82" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901960784313" green="0.24313725490196078" blue="0.31372549019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<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"/>
|
||||
<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"/>
|
||||
@@ -123,17 +310,21 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="vpt-cT-gMo" detailTextLabel="ou9-TZ-8bf" style="IBUITableViewCellStyleSubtitle" id="fsb-zw-8Ii">
|
||||
<rect key="frame" x="0.0" y="149" width="600" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="fsb-zw-8Ii" id="Upm-AO-Fw3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Colors" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="vpt-cT-gMo">
|
||||
<rect key="frame" x="15" y="6" width="56" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observing list changes and single object changes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ou9-TZ-8bf">
|
||||
<rect key="frame" x="15" y="30" width="261.5" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
@@ -146,17 +337,21 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="UbU-Kd-yrY" detailTextLabel="uP1-Jc-o9v" style="IBUITableViewCellStyleSubtitle" id="ekW-PJ-mbo">
|
||||
<rect key="frame" x="0.0" y="199" width="600" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ekW-PJ-mbo" id="CYq-mg-PVS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Placemarks" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="UbU-Kd-yrY">
|
||||
<rect key="frame" x="15" y="6" width="100.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Making changes with transactions" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="uP1-Jc-o9v">
|
||||
<rect key="frame" x="15" y="30" width="179.5" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
@@ -169,17 +364,21 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="C8Y-0y-lEG" detailTextLabel="jZw-qE-0ws" style="IBUITableViewCellStyleSubtitle" id="ph1-8z-C1m">
|
||||
<rect key="frame" x="0.0" y="249" width="600" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ph1-8z-C1m" id="nNz-rd-ksg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Time Zones" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="C8Y-0y-lEG">
|
||||
<rect key="frame" x="15" y="6" width="101.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws">
|
||||
<rect key="frame" x="15" y="30" width="169" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
@@ -192,17 +391,21 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="ZfY-Aq-Ykq" detailTextLabel="QzD-9b-k1j" style="IBUITableViewCellStyleSubtitle" id="wyK-rk-3tI">
|
||||
<rect key="frame" x="0.0" y="299" width="600" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="wyK-rk-3tI" id="fLd-KK-QcW">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZfY-Aq-Ykq">
|
||||
<rect key="frame" x="15" y="6" width="61" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j">
|
||||
<rect key="frame" x="15" y="30" width="159.5" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
@@ -214,6 +417,33 @@
|
||||
<segue destination="5yy-0N-QDU" kind="show" id="5D7-Xp-1HT"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="hSG-mG-YBw" detailTextLabel="X9P-TQ-LYh" style="IBUITableViewCellStyleSubtitle" id="xTM-Cf-0if">
|
||||
<rect key="frame" x="0.0" y="349" width="600" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="xTM-Cf-0if" id="DfO-BW-krd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Evolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hSG-mG-YBw">
|
||||
<rect key="frame" x="15" y="6" width="78.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Migrating and de-migrating stores" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="X9P-TQ-LYh">
|
||||
<rect key="frame" x="15" y="30" width="179.5" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
</tableViewCellContentView>
|
||||
<connections>
|
||||
<segue destination="iVv-Vc-nCL" kind="show" id="HbO-rU-Qfj"/>
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
</cells>
|
||||
</tableViewSection>
|
||||
</sections>
|
||||
@@ -237,18 +467,18 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="aI4-O3-OCi"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="w8K-eN-RvU">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="268"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
|
||||
<rect key="frame" x="16" y="70" width="568" height="36"/>
|
||||
<rect key="frame" x="20" y="69.5" width="552" height="36.5"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="TIX-qi-B34"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfe-Yq-3Xa">
|
||||
<rect key="frame" x="16" y="116" width="568" height="18"/>
|
||||
<rect key="frame" x="20" y="116" width="552" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="4h9-ha-EzR"/>
|
||||
</constraints>
|
||||
@@ -257,43 +487,43 @@
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hue" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sgg-Md-Nf3">
|
||||
<rect key="frame" x="16" y="154" width="74" height="18"/>
|
||||
<rect key="frame" x="20" y="154" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Saturation" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rry-vh-bRK">
|
||||
<rect key="frame" x="16" y="192" width="74" height="18"/>
|
||||
<rect key="frame" x="20" y="192" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Brightness" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vTa-ly-eyO">
|
||||
<rect key="frame" x="16" y="230" width="74" height="18"/>
|
||||
<rect key="frame" x="20" y="230" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="359" translatesAutoresizingMaskIntoConstraints="NO" id="YQ6-fq-3Wb">
|
||||
<rect key="frame" x="98" y="148" width="488" height="31"/>
|
||||
<rect key="frame" x="102" y="148" width="472" height="31"/>
|
||||
<connections>
|
||||
<action selector="hueSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="9Hy-3h-llE"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="xXz-78-tAd">
|
||||
<rect key="frame" x="98" y="186" width="488" height="31"/>
|
||||
<rect key="frame" x="102" y="186" width="472" height="31"/>
|
||||
<connections>
|
||||
<action selector="saturationSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="qtU-ua-ZTc"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="hpy-2d-eOP">
|
||||
<rect key="frame" x="98" y="224" width="488" height="31"/>
|
||||
<rect key="frame" x="102" y="224" width="472" height="31"/>
|
||||
<connections>
|
||||
<action selector="brightnessSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="F09-EP-2iD"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p4O-tf-dgt">
|
||||
<rect key="frame" x="16" y="49" width="568" height="21"/>
|
||||
<rect key="frame" x="20" y="49" width="552" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -361,17 +591,17 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="LNL-mj-D7l"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="6x3-vn-Egt">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="268"/>
|
||||
<rect key="frame" x="4" y="64" width="592" height="268"/>
|
||||
<connections>
|
||||
<segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp">
|
||||
<rect key="frame" x="0.0" y="332" width="600" height="268"/>
|
||||
<rect key="frame" x="4" y="332" width="592" height="268"/>
|
||||
<connections>
|
||||
<segue destination="sll-yo-mBc" kind="embed" id="AAl-HS-dq2"/>
|
||||
</connections>
|
||||
@@ -425,14 +655,16 @@
|
||||
<!--List Observer-->
|
||||
<scene sceneID="gkX-bd-Rel">
|
||||
<objects>
|
||||
<tableViewController id="3AE-ED-0oj" customClass="ObjectListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController id="3AE-ED-0oj" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="DAz-BE-6Ca">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="268"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="G3X-70-BCD" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="66" width="592" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="G3X-70-BCD" id="aT8-nz-i5l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="559" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uQX-PI-UWF">
|
||||
@@ -482,15 +714,17 @@
|
||||
<!--List Observer-->
|
||||
<scene sceneID="iDB-TD-It9">
|
||||
<objects>
|
||||
<tableViewController id="lCE-i6-UCT" customClass="ObjectListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableViewController id="lCE-i6-UCT" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="Zba-8M-Zd7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="268"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="zSO-3e-OVq" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="66" width="592" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="zSO-3e-OVq" id="cHA-by-n4b">
|
||||
<rect key="frame" x="0.0" y="0.0" width="559" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5uq-Yi-XwH">
|
||||
@@ -571,7 +805,7 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="RZg-hi-T8O"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="k4s-iL-Krh">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="536"/>
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="536"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
|
||||
@@ -724,12 +958,14 @@
|
||||
</view>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="6db-P0-6Ms" style="IBUITableViewCellStyleDefault" id="vUr-WV-qur">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="44"/>
|
||||
<rect key="frame" x="0.0" y="102" width="600" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="vUr-WV-qur" id="Vr0-hE-cn9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6db-P0-6Ms">
|
||||
<rect key="frame" x="15" y="0.0" width="550" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="18"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
@@ -776,17 +1012,21 @@
|
||||
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="RgX-yK-1L2" detailTextLabel="QZ4-A2-x4h" style="IBUITableViewCellStyleSubtitle" id="uBt-Iy-nWP">
|
||||
<rect key="frame" x="0.0" y="100" width="600" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="uBt-Iy-nWP" id="6SD-ur-9zp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="59.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RgX-yK-1L2">
|
||||
<rect key="frame" x="15" y="11" width="48.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QZ4-A2-x4h">
|
||||
<rect key="frame" x="15" y="35" width="31" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
@@ -818,17 +1058,21 @@
|
||||
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="Syt-QJ-KXg" detailTextLabel="yHS-dP-IKS" style="IBUITableViewCellStyleSubtitle" id="q7Q-aF-Ftl">
|
||||
<rect key="frame" x="0.0" y="100" width="600" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="q7Q-aF-Ftl" id="fc3-eg-yes">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="59.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Syt-QJ-KXg">
|
||||
<rect key="frame" x="15" y="11" width="48.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="yHS-dP-IKS">
|
||||
<rect key="frame" x="15" y="35" width="31" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<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"/>
|
||||
@@ -854,6 +1098,6 @@
|
||||
<image name="second" width="30" height="30"/>
|
||||
</resources>
|
||||
<inferredMetricsTieBreakers>
|
||||
<segue reference="hyN-De-zte"/>
|
||||
<segue reference="fIB-GS-Ppk"/>
|
||||
</inferredMetricsTieBreakers>
|
||||
</document>
|
||||
|
||||
@@ -15,17 +15,17 @@ private struct Static {
|
||||
static let timeZonesStack: DataStack = {
|
||||
|
||||
let dataStack = DataStack()
|
||||
dataStack.addSQLiteStoreAndWait(
|
||||
"TimeZoneDemo.sqlite",
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "TimeZoneDemo.sqlite",
|
||||
configuration: "FetchingAndQueryingDemo",
|
||||
resetStoreOnMigrationFailure: true
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
|
||||
dataStack.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
transaction.deleteAll(From(TimeZone))
|
||||
|
||||
for name in NSTimeZone.knownTimeZoneNames() as! [String] {
|
||||
for name in NSTimeZone.knownTimeZoneNames() {
|
||||
|
||||
let rawTimeZone = NSTimeZone(name: name)!
|
||||
let cachedTimeZone = transaction.create(Into(TimeZone))
|
||||
@@ -112,7 +112,7 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
|
||||
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell") as! UITableViewCell
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell")!
|
||||
|
||||
switch self.segmentedControl?.selectedSegmentIndex {
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class FetchingResultsViewController: UITableViewController {
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath) as! UITableViewCell
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath)
|
||||
|
||||
let timeZone = self.timeZones[indexPath.row]
|
||||
cell.textLabel?.text = timeZone.name
|
||||
|
||||
@@ -62,7 +62,7 @@ class QueryingResultsViewController: UITableViewController {
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath) as! UITableViewCell
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath)
|
||||
|
||||
let value = self.values[indexPath.row]
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>com.johnestropia.CoreStoreDemo</string>
|
||||
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
@@ -15,7 +15,7 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<string>1.0.1</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
//
|
||||
// ObjectListObserverDemoViewController.swift
|
||||
// ListObserverDemoViewController.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/02.
|
||||
@@ -12,26 +12,61 @@ import CoreStore
|
||||
|
||||
private struct Static {
|
||||
|
||||
static let palettes: ManagedObjectListController<Palette> = {
|
||||
enum Filter: String {
|
||||
|
||||
CoreStore.addSQLiteStoreAndWait(
|
||||
"ColorsDemo.sqlite",
|
||||
case All = "All Colors"
|
||||
case Light = "Light Colors"
|
||||
case Dark = "Dark Colors"
|
||||
|
||||
func next() -> Filter {
|
||||
|
||||
switch self {
|
||||
|
||||
case All: return .Light
|
||||
case Light: return .Dark
|
||||
case Dark: return .All
|
||||
}
|
||||
}
|
||||
|
||||
func whereClause() -> Where {
|
||||
|
||||
switch self {
|
||||
|
||||
case .All: return Where(true)
|
||||
case .Light: return Where("brightness >= 0.9")
|
||||
case .Dark: return Where("brightness <= 0.4")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static var filter = Filter.All {
|
||||
|
||||
didSet {
|
||||
|
||||
self.palettes.refetch(self.filter.whereClause())
|
||||
}
|
||||
}
|
||||
|
||||
static let palettes: ListMonitor<Palette> = {
|
||||
|
||||
try! CoreStore.addSQLiteStoreAndWait(
|
||||
fileName: "ColorsDemo.sqlite",
|
||||
configuration: "ObservingDemo",
|
||||
resetStoreOnMigrationFailure: true
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
|
||||
return CoreStore.observeSectionedList(
|
||||
return CoreStore.monitorSectionedList(
|
||||
From(Palette),
|
||||
SectionedBy("colorName"),
|
||||
SectionBy("colorName"),
|
||||
OrderBy(.Ascending("hue"))
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectListObserverDemoViewController
|
||||
// MARK: - ListObserverDemoViewController
|
||||
|
||||
class ObjectListObserverDemoViewController: UITableViewController, ManagedObjectListSectionObserver {
|
||||
class ListObserverDemoViewController: UITableViewController, ListSectionObserver {
|
||||
|
||||
// MARK: NSObject
|
||||
|
||||
@@ -47,7 +82,8 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
self.navigationItem.leftBarButtonItems = [
|
||||
let navigationItem = self.navigationItem
|
||||
navigationItem.leftBarButtonItems = [
|
||||
self.editButtonItem(),
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .Trash,
|
||||
@@ -55,13 +91,26 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
|
||||
action: "resetBarButtonItemTouched:"
|
||||
)
|
||||
]
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
barButtonSystemItem: .Add,
|
||||
|
||||
let filterBarButton = UIBarButtonItem(
|
||||
title: Static.filter.rawValue,
|
||||
style: .Plain,
|
||||
target: self,
|
||||
action: "addBarButtonItemTouched:"
|
||||
action: "filterBarButtonItemTouched:"
|
||||
)
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .Add,
|
||||
target: self,
|
||||
action: "addBarButtonItemTouched:"
|
||||
),
|
||||
filterBarButton
|
||||
]
|
||||
self.filterBarButton = filterBarButton
|
||||
|
||||
Static.palettes.addObserver(self)
|
||||
|
||||
self.setTableEnabled(!Static.palettes.isPendingRefetch)
|
||||
}
|
||||
|
||||
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
|
||||
@@ -138,32 +187,44 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
|
||||
}
|
||||
|
||||
|
||||
// MARK: ManagedObjectListChangeObserver
|
||||
// MARK: ListObserver
|
||||
|
||||
func managedObjectListWillChange(listController: ManagedObjectListController<Palette>) {
|
||||
func listMonitorWillChange(monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.tableView.beginUpdates()
|
||||
}
|
||||
|
||||
func managedObjectListDidChange(listController: ManagedObjectListController<Palette>) {
|
||||
func listMonitorDidChange(monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
|
||||
func listMonitorWillRefetch(monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.setTableEnabled(false)
|
||||
}
|
||||
|
||||
// MARK: ManagedObjectListObjectObserver
|
||||
func listMonitorDidRefetch(monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.filterBarButton?.title = Static.filter.rawValue
|
||||
self.tableView.reloadData()
|
||||
self.setTableEnabled(true)
|
||||
}
|
||||
|
||||
func managedObjectList(listController: ManagedObjectListController<Palette>, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
// MARK: ListObjectObserver
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
|
||||
}
|
||||
|
||||
func managedObjectList(listController: ManagedObjectListController<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) {
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
|
||||
}
|
||||
|
||||
func managedObjectList(listController: ManagedObjectListController<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) {
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? PaletteTableViewCell {
|
||||
|
||||
@@ -173,21 +234,21 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
|
||||
}
|
||||
}
|
||||
|
||||
func managedObjectList(listController: ManagedObjectListController<Palette>, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
|
||||
|
||||
self.tableView.deleteRowsAtIndexPaths([fromIndexPath], withRowAnimation: .Automatic)
|
||||
self.tableView.insertRowsAtIndexPaths([toIndexPath], withRowAnimation: .Automatic)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ManagedObjectListSectionObserver
|
||||
// MARK: ListSectionObserver
|
||||
|
||||
func managedObjectList(listController: ManagedObjectListController<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
|
||||
}
|
||||
|
||||
func managedObjectList(listController: ManagedObjectListController<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
|
||||
}
|
||||
@@ -195,7 +256,9 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@IBAction dynamic func resetBarButtonItemTouched(sender: AnyObject?) {
|
||||
private var filterBarButton: UIBarButtonItem?
|
||||
|
||||
@IBAction private dynamic func resetBarButtonItemTouched(sender: AnyObject?) {
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
@@ -204,7 +267,12 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction dynamic func addBarButtonItemTouched(sender: AnyObject?) {
|
||||
@IBAction private dynamic func filterBarButtonItemTouched(sender: AnyObject?) {
|
||||
|
||||
Static.filter = Static.filter.next()
|
||||
}
|
||||
|
||||
@IBAction private dynamic func addBarButtonItemTouched(sender: AnyObject?) {
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
@@ -214,5 +282,23 @@ class ObjectListObserverDemoViewController: UITableViewController, ManagedObject
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
|
||||
private func setTableEnabled(enabled: Bool) {
|
||||
|
||||
UIView.animateWithDuration(
|
||||
0.2,
|
||||
delay: 0,
|
||||
options: .BeginFromCurrentState,
|
||||
animations: { () -> Void in
|
||||
|
||||
if let tableView = self.tableView {
|
||||
|
||||
tableView.alpha = enabled ? 1.0 : 0.5
|
||||
tableView.userInteractionEnabled = enabled
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,23 +12,28 @@ import CoreStore
|
||||
|
||||
// MARK: - ObjectObserverDemoViewController
|
||||
|
||||
class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver {
|
||||
class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
|
||||
var palette: Palette? {
|
||||
|
||||
get {
|
||||
|
||||
return self.objectController?.object
|
||||
return self.monitor?.object
|
||||
}
|
||||
set {
|
||||
|
||||
guard self.monitor?.object != newValue else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if let palette = newValue {
|
||||
|
||||
self.objectController = CoreStore.observeObject(palette)
|
||||
self.monitor = CoreStore.monitorObject(palette)
|
||||
}
|
||||
else {
|
||||
|
||||
self.objectController = nil
|
||||
self.monitor = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -37,17 +42,17 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
|
||||
deinit {
|
||||
|
||||
self.objectController?.removeObserver(self)
|
||||
self.monitor?.removeObserver(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
required init(coder aDecoder: NSCoder) {
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
||||
if let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue"))) {
|
||||
|
||||
self.objectController = CoreStore.observeObject(palette)
|
||||
self.monitor = CoreStore.monitorObject(palette)
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -60,7 +65,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
}
|
||||
|
||||
let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))!
|
||||
self.objectController = CoreStore.observeObject(palette)
|
||||
self.monitor = CoreStore.monitorObject(palette)
|
||||
}
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
@@ -69,28 +74,23 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
self.objectController?.addObserver(self)
|
||||
self.monitor?.addObserver(self)
|
||||
|
||||
if let palette = self.objectController?.object {
|
||||
if let palette = self.monitor?.object {
|
||||
|
||||
self.reloadPaletteInfo(palette, changedKeys: nil)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: ManagedObjectObserver
|
||||
// MARK: ObjectObserver
|
||||
|
||||
func managedObjectWillUpdate(objectController: ManagedObjectController<Palette>, object: Palette) {
|
||||
|
||||
// none
|
||||
}
|
||||
|
||||
func managedObjectWasUpdated(objectController: ManagedObjectController<Palette>, object: Palette, changedPersistentKeys: Set<KeyPath>) {
|
||||
func objectMonitor(monitor: ObjectMonitor<Palette>, didUpdateObject object: Palette, changedPersistentKeys: Set<KeyPath>) {
|
||||
|
||||
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
|
||||
}
|
||||
|
||||
func managedObjectWasDeleted(objectController: ManagedObjectController<Palette>, object: Palette) {
|
||||
func objectMonitor(monitor: ObjectMonitor<Palette>, didDeleteObject object: Palette) {
|
||||
|
||||
self.navigationItem.rightBarButtonItem?.enabled = false
|
||||
|
||||
@@ -108,7 +108,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
|
||||
// MARK: Private
|
||||
|
||||
var objectController: ManagedObjectController<Palette>?
|
||||
var monitor: ObjectMonitor<Palette>?
|
||||
|
||||
@IBOutlet weak var colorNameLabel: UILabel?
|
||||
@IBOutlet weak var colorView: UIView?
|
||||
@@ -123,7 +123,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
let hue = self.hueSlider?.value ?? 0
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
if let palette = transaction.edit(self?.objectController?.object) {
|
||||
if let palette = transaction.edit(self?.monitor?.object) {
|
||||
|
||||
palette.hue = Int32(hue)
|
||||
transaction.commit()
|
||||
@@ -136,7 +136,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
let saturation = self.saturationSlider?.value ?? 0
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
if let palette = transaction.edit(self?.objectController?.object) {
|
||||
if let palette = transaction.edit(self?.monitor?.object) {
|
||||
|
||||
palette.saturation = saturation
|
||||
transaction.commit()
|
||||
@@ -149,7 +149,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
let brightness = self.brightnessSlider?.value ?? 0
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
if let palette = transaction.edit(self?.objectController?.object) {
|
||||
if let palette = transaction.edit(self?.monitor?.object) {
|
||||
|
||||
palette.brightness = brightness
|
||||
transaction.commit()
|
||||
@@ -161,7 +161,7 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
transaction.delete(self?.objectController?.object)
|
||||
transaction.delete(self?.monitor?.object)
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
@@ -176,10 +176,6 @@ class ObjectObserverDemoViewController: UIViewController, ManagedObjectObserver
|
||||
|
||||
self.hsbLabel?.text = palette.colorText
|
||||
|
||||
let hue = palette.hue
|
||||
let saturation = palette.saturation
|
||||
let brightness = palette.brightness
|
||||
|
||||
if changedKeys == nil || changedKeys?.contains("hue") == true {
|
||||
|
||||
self.hueSlider?.value = Float(palette.hue)
|
||||
|
||||
@@ -21,7 +21,7 @@ class ObserversViewController: UIViewController {
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Observers Demo",
|
||||
message: "This demo shows how to observe changes to a list of objects. The top and bottom view controllers both observe a single shared \"ManagedObjectListController\" instance.\n\nTap on a row to see how to observe changes made to a single object using a \"ManagedObjectController\".",
|
||||
message: "This demo shows how to observe changes to a list of objects. The top and bottom view controllers both observe a single shared \"ListMonitor\" instance.\n\nTap on a row to see how to observe changes made to a single object using a \"ObjectMonitor\".",
|
||||
preferredStyle: .Alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
|
||||
@@ -30,7 +30,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
self.dataStack.addSQLiteStoreAndWait("emptyStore.sqlite")
|
||||
try! self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite")
|
||||
CoreStore.logger = self
|
||||
}
|
||||
|
||||
@@ -50,7 +50,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
// MARK: CoreStoreLogger
|
||||
|
||||
func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
@@ -62,15 +62,15 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
case .Warning: levelString = "Warning"
|
||||
case .Fatal: levelString = "Fatal"
|
||||
}
|
||||
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,10 +83,15 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
|
||||
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
@noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
Swift.fatalError("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)")
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@@ -100,11 +105,15 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
case .Some(0):
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
transaction.create(Into(UserAccount))
|
||||
transaction.create(Into(Palette))
|
||||
}
|
||||
|
||||
case .Some(1):
|
||||
self.dataStack.addSQLiteStoreAndWait("emptyStore.sqlite", configuration: "invalidStore")
|
||||
do {
|
||||
|
||||
try self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite", configuration: "invalidStore")
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
case .Some(2):
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
@@ -0,0 +1,388 @@
|
||||
//
|
||||
// MigrationsDemoViewController.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/21.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - MigrationsDemoViewController
|
||||
|
||||
class MigrationsDemoViewController: UIViewController {
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
if let segmentedControl = self.segmentedControl {
|
||||
|
||||
for (index, model) in self.models.enumerate() {
|
||||
|
||||
segmentedControl.setTitle(
|
||||
model.label,
|
||||
forSegmentAtIndex: index
|
||||
)
|
||||
}
|
||||
}
|
||||
self.setDataStack(nil, model: nil, scrollToSelection: false)
|
||||
}
|
||||
|
||||
override func viewDidAppear(animated: Bool) {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Migrations Demo",
|
||||
message: "This demo shows how to run incremental migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
|
||||
preferredStyle: .Alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
self.presentViewController(alert, animated: true, completion: nil)
|
||||
|
||||
|
||||
let modelMetadata = withExtendedLifetime(DataStack(modelName: "MigrationDemo")) {
|
||||
(dataStack: DataStack) -> ModelMetadata in
|
||||
|
||||
let models = self.models
|
||||
do {
|
||||
|
||||
let migrations = try dataStack.requiredMigrationsForSQLiteStore(
|
||||
fileName: "MigrationDemo.sqlite"
|
||||
)
|
||||
|
||||
let storeVersion = migrations.first?.sourceVersion ?? dataStack.modelVersion
|
||||
for model in models {
|
||||
|
||||
if model.version == storeVersion {
|
||||
|
||||
return model
|
||||
}
|
||||
}
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
return models.first!
|
||||
}
|
||||
|
||||
self.selectModelVersion(modelMetadata)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private typealias ModelMetadata = (label: String, version: String, entityType: AnyClass, migrationChain: MigrationChain)
|
||||
|
||||
private let models: [ModelMetadata] = [
|
||||
(
|
||||
label: "Model V1",
|
||||
version: "MigrationDemo",
|
||||
entityType: OrganismV1.self,
|
||||
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
|
||||
),
|
||||
(
|
||||
label: "Model V2",
|
||||
version: "MigrationDemoV2",
|
||||
entityType: OrganismV2.self,
|
||||
migrationChain: [
|
||||
"MigrationDemo": "MigrationDemoV2",
|
||||
"MigrationDemoV3": "MigrationDemoV2"
|
||||
]
|
||||
),
|
||||
(
|
||||
label: "Model V3",
|
||||
version: "MigrationDemoV3",
|
||||
entityType: OrganismV3.self,
|
||||
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
|
||||
)
|
||||
]
|
||||
|
||||
private var _listMonitor: ListMonitor<NSManagedObject>?
|
||||
private var listMonitor: ListMonitor<NSManagedObject>? {
|
||||
|
||||
return self._listMonitor
|
||||
}
|
||||
|
||||
private var _dataStack: DataStack?
|
||||
private var dataStack: DataStack? {
|
||||
|
||||
return self._dataStack
|
||||
}
|
||||
|
||||
private var _lastSelectedIndexPath: NSIndexPath?
|
||||
private var lastSelectedIndexPath: NSIndexPath? {
|
||||
|
||||
return self._lastSelectedIndexPath
|
||||
}
|
||||
|
||||
private func setSelectedIndexPath(indexPath: NSIndexPath, scrollToSelection: Bool) {
|
||||
|
||||
self._lastSelectedIndexPath = indexPath
|
||||
self.updateDisplay(reloadData: false, scrollToSelection: scrollToSelection, animated: true)
|
||||
}
|
||||
|
||||
@IBOutlet private dynamic weak var headerContainer: UIView?
|
||||
@IBOutlet private dynamic weak var titleLabel: UILabel?
|
||||
@IBOutlet private dynamic weak var organismLabel: UILabel?
|
||||
@IBOutlet private dynamic weak var segmentedControl: UISegmentedControl?
|
||||
@IBOutlet private dynamic weak var progressView: UIProgressView?
|
||||
@IBOutlet private dynamic weak var tableView: UITableView?
|
||||
|
||||
@IBAction private dynamic func segmentedControlValueChanged(sender: AnyObject?) {
|
||||
|
||||
guard let index = self.segmentedControl?.selectedSegmentIndex else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.selectModelVersion(self.models[index])
|
||||
}
|
||||
|
||||
private func selectModelVersion(model: ModelMetadata) {
|
||||
|
||||
if self.dataStack?.modelVersion == model.version {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.setDataStack(nil, model: nil, scrollToSelection: false) // explicitly trigger NSPersistentStore cleanup by deallocating the stack
|
||||
|
||||
let dataStack = DataStack(
|
||||
modelName: "MigrationDemo",
|
||||
migrationChain: model.migrationChain
|
||||
)
|
||||
|
||||
self.setEnabled(false)
|
||||
let progress = try! dataStack.addSQLiteStore(
|
||||
fileName: "MigrationDemo.sqlite",
|
||||
completion: { [weak self] (result) -> Void in
|
||||
|
||||
guard let strongSelf = self else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
guard case .Success = result else {
|
||||
|
||||
strongSelf.setEnabled(true)
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.setDataStack(dataStack, model: model, scrollToSelection: true)
|
||||
|
||||
let count = dataStack.queryValue(From(model.entityType), Select<Int>(.Count("dna")))
|
||||
if count > 0 {
|
||||
|
||||
strongSelf.setEnabled(true)
|
||||
}
|
||||
else {
|
||||
|
||||
dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
for i: Int64 in 1 ..< 10000 {
|
||||
|
||||
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
|
||||
organism.dna = i
|
||||
organism.mutate()
|
||||
}
|
||||
|
||||
transaction.commit { result -> Void in
|
||||
|
||||
self?.setEnabled(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
if let progress = progress {
|
||||
|
||||
progress.setProgressHandler { [weak self] (progress) -> Void in
|
||||
|
||||
self?.reloadTableHeaderWithProgress(progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func setEnabled(enabled: Bool) {
|
||||
|
||||
UIView.animateWithDuration(
|
||||
0.2,
|
||||
delay: 0,
|
||||
options: .BeginFromCurrentState,
|
||||
animations: { () -> Void in
|
||||
|
||||
let navigationItem = self.navigationItem
|
||||
navigationItem.leftBarButtonItem?.enabled = enabled
|
||||
navigationItem.rightBarButtonItem?.enabled = enabled
|
||||
navigationItem.hidesBackButton = !enabled
|
||||
|
||||
self.segmentedControl?.enabled = enabled
|
||||
|
||||
if let tableView = self.tableView {
|
||||
|
||||
tableView.alpha = enabled ? 1.0 : 0.5
|
||||
tableView.userInteractionEnabled = enabled
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func setDataStack(dataStack: DataStack?, model: ModelMetadata?, scrollToSelection: Bool) {
|
||||
|
||||
if let dataStack = dataStack, let model = model {
|
||||
|
||||
self.segmentedControl?.selectedSegmentIndex = self.models.map { $0.version }.indexOf(model.version)!
|
||||
|
||||
self._dataStack = dataStack
|
||||
let listMonitor = dataStack.monitorList(From(model.entityType), OrderBy(.Descending("dna")))
|
||||
listMonitor.addObserver(self)
|
||||
self._listMonitor = listMonitor
|
||||
|
||||
if self.lastSelectedIndexPath == nil {
|
||||
|
||||
if listMonitor.numberOfObjectsInSection(0) > 0 {
|
||||
|
||||
self.setSelectedIndexPath(NSIndexPath(forRow: 0, inSection: 0), scrollToSelection: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
self.segmentedControl?.selectedSegmentIndex = UISegmentedControlNoSegment
|
||||
self._dataStack = nil
|
||||
self._listMonitor = nil
|
||||
}
|
||||
|
||||
self.updateDisplay(reloadData: true, scrollToSelection: scrollToSelection, animated: false)
|
||||
}
|
||||
|
||||
private func reloadTableHeaderWithProgress(progress: NSProgress) {
|
||||
|
||||
self.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
|
||||
self.titleLabel?.text = "Migrating: \(progress.localizedDescription)"
|
||||
self.organismLabel?.text = "Incremental step \(progress.localizedAdditionalDescription)"
|
||||
}
|
||||
|
||||
private func updateDisplay(reloadData reloadData: Bool, scrollToSelection: Bool, animated: Bool) {
|
||||
|
||||
var lines = [String]()
|
||||
var organismType = ""
|
||||
if let indexPath = self.lastSelectedIndexPath, let organism = self.listMonitor?[indexPath] {
|
||||
|
||||
for property in organism.entity.properties {
|
||||
|
||||
let value: AnyObject = organism.valueForKey(property.name) ?? NSNull()
|
||||
lines.append("\(property.name): \(value)")
|
||||
}
|
||||
organismType = organism.entity.managedObjectClassName
|
||||
}
|
||||
|
||||
self.titleLabel?.text = organismType
|
||||
self.organismLabel?.text = lines.joinWithSeparator("\n")
|
||||
self.progressView?.progress = 0
|
||||
|
||||
self.headerContainer?.setNeedsLayout()
|
||||
|
||||
guard let tableView = self.tableView else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
if reloadData {
|
||||
|
||||
tableView.reloadData()
|
||||
}
|
||||
|
||||
tableView.layoutIfNeeded()
|
||||
|
||||
if let indexPath = self.lastSelectedIndexPath where indexPath.row < tableView.numberOfRowsInSection(0) {
|
||||
|
||||
tableView.selectRowAtIndexPath(indexPath,
|
||||
animated: scrollToSelection && animated,
|
||||
scrollPosition: scrollToSelection ? .Middle : .None
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MigrationsDemoViewController: ListObserver
|
||||
|
||||
extension MigrationsDemoViewController: ListObserver {
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
func listMonitorWillChange(monitor: ListMonitor<NSManagedObject>) { }
|
||||
|
||||
func listMonitorDidChange(monitor: ListMonitor<NSManagedObject>) {
|
||||
|
||||
if self.lastSelectedIndexPath == nil && self.listMonitor?.numberOfObjectsInSection(0) > 0 {
|
||||
|
||||
self.tableView?.reloadData()
|
||||
self.setSelectedIndexPath(NSIndexPath(forRow: 0, inSection: 0), scrollToSelection: false)
|
||||
}
|
||||
else {
|
||||
|
||||
self.updateDisplay(reloadData: true, scrollToSelection: true, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MigrationsDemoViewController: UITableViewDataSource, UITableViewDelegate
|
||||
|
||||
extension MigrationsDemoViewController: UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
@objc dynamic func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.listMonitor?.numberOfObjectsInSection(0) ?? 0
|
||||
}
|
||||
|
||||
@objc dynamic func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("OrganismTableViewCell", forIndexPath: indexPath) as! OrganismTableViewCell
|
||||
|
||||
let dna = (self.listMonitor?[indexPath] as? OrganismProtocol)?.dna.description ?? ""
|
||||
cell.dnaLabel?.text = "DNA: \(dna)"
|
||||
cell.mutateButtonHandler = { [weak self] _ -> Void in
|
||||
|
||||
guard let strongSelf = self,
|
||||
let dataStack = strongSelf.dataStack,
|
||||
let organism = strongSelf.listMonitor?[indexPath] else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.setSelectedIndexPath(indexPath, scrollToSelection: false)
|
||||
strongSelf.setEnabled(false)
|
||||
dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let organism = transaction.edit(organism) as! OrganismProtocol
|
||||
organism.mutate()
|
||||
|
||||
transaction.commit { _ -> Void in
|
||||
|
||||
self?.setEnabled(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
@objc dynamic func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="8166.2" systemVersion="14E46" minimumToolsVersion="Xcode 7.0">
|
||||
<elements/>
|
||||
</model>
|
||||
@@ -0,0 +1,16 @@
|
||||
//
|
||||
// OrganismProtocol.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/27.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol OrganismProtocol: class {
|
||||
|
||||
var dna: Int64 { get set }
|
||||
|
||||
func mutate()
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
//
|
||||
// OrganismTableViewCell.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/07/12.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class OrganismTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak dynamic var dnaLabel: UILabel?
|
||||
@IBOutlet weak dynamic var mutateButton: UIButton?
|
||||
|
||||
var mutateButtonHandler: (() -> Void)?
|
||||
|
||||
@IBAction dynamic func mutateButtonTouchUpInside(sender: UIButton?) {
|
||||
|
||||
self.mutateButtonHandler?()
|
||||
}
|
||||
}
|
||||
25
CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV1.swift
Normal file
25
CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV1.swift
Normal file
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// OrganismV1.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/21.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
class OrganismV1: NSManagedObject, OrganismProtocol {
|
||||
|
||||
@NSManaged var dna: Int64
|
||||
@NSManaged var hasHead: Bool
|
||||
@NSManaged var hasTail: Bool
|
||||
|
||||
// MARK: OrganismProtocol
|
||||
|
||||
func mutate() {
|
||||
|
||||
self.hasHead = arc4random_uniform(2) == 1
|
||||
self.hasTail = arc4random_uniform(2) == 1
|
||||
}
|
||||
}
|
||||
27
CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2.swift
Normal file
27
CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV2.swift
Normal file
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// OrganismV2.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/21.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
class OrganismV2: NSManagedObject, OrganismProtocol {
|
||||
|
||||
@NSManaged var dna: Int64
|
||||
@NSManaged var hasHead: Bool
|
||||
@NSManaged var hasTail: Bool
|
||||
@NSManaged var numberOfFlippers: Int32
|
||||
|
||||
// MARK: OrganismProtocol
|
||||
|
||||
func mutate() {
|
||||
|
||||
self.hasHead = arc4random_uniform(2) == 1
|
||||
self.hasTail = arc4random_uniform(2) == 1
|
||||
self.numberOfFlippers = Int32(arc4random_uniform(9) / 2 * 2)
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// OrganismV2ToV3MigrationPolicy.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/27.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import CoreData
|
||||
|
||||
class OrganismV2ToV3MigrationPolicy: NSEntityMigrationPolicy {
|
||||
|
||||
override func createDestinationInstancesForSourceInstance(sInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
|
||||
try super.createDestinationInstancesForSourceInstance(sInstance, entityMapping: mapping, manager: manager)
|
||||
|
||||
for dInstance in manager.destinationInstancesForEntityMappingNamed(mapping.name, sourceInstances: [sInstance]) {
|
||||
|
||||
dInstance.setValue(false, forKey: "hasVertebrae")
|
||||
dInstance.setValue(sInstance.valueForKey("numberOfFlippers"), forKey: "numberOfLimbs")
|
||||
}
|
||||
}
|
||||
}
|
||||
29
CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV3.swift
Normal file
29
CoreStoreDemo/CoreStoreDemo/MIgrations Demo/OrganismV3.swift
Normal file
@@ -0,0 +1,29 @@
|
||||
//
|
||||
// OrganismV3.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/27.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
class OrganismV3: NSManagedObject, OrganismProtocol {
|
||||
|
||||
@NSManaged var dna: Int64
|
||||
@NSManaged var hasHead: Bool
|
||||
@NSManaged var hasTail: Bool
|
||||
@NSManaged var numberOfLimbs: Int32
|
||||
@NSManaged var hasVertebrae: Bool
|
||||
|
||||
// MARK: OrganismProtocol
|
||||
|
||||
func mutate() {
|
||||
|
||||
self.hasHead = arc4random_uniform(2) == 1
|
||||
self.hasTail = arc4random_uniform(2) == 1
|
||||
self.numberOfLimbs = Int32(arc4random_uniform(9) / 2 * 2)
|
||||
self.hasVertebrae = arc4random_uniform(2) == 1
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user