Compare commits

...

146 Commits
1.0.1 ... 1.4.4

Author SHA1 Message Date
John Estropia
0c6246475a update README 2016-01-19 12:24:14 +09:00
John Estropia
087480a3a8 update cartfile 2016-01-19 12:22:14 +09:00
John Estropia
83a04e669e updated .travis.yml 2016-01-19 11:55:22 +09:00
John Estropia
d05522bb20 tidy up, set default directory to Caches folder on tvOS 2016-01-19 11:38:11 +09:00
Cihat Gündüz
9081b36cca Add tvOS target + Configure target + Add shared scheme for tvOS 2016-01-17 13:30:45 +01:00
Cihat Gündüz
9322371224 Use tvOS_support feature branch of GCDKit (Carthage + git submodules) 2016-01-17 13:11:58 +01:00
John Estropia
26ab6aacd7 exposed utility for extracting the parent transaction for objects created from UnsafeDataTransactions 2016-01-14 17:54:58 +09:00
John Estropia
3e601c1328 tidy up (WIP: queue check for NSManagedObjectContext property updates) 2016-01-08 20:44:42 +09:00
John Estropia
762b877879 Merge pull request #35 from JohnEstropia/modify-gitignore
Update .gitignore: Add .DS_Store
2016-01-08 18:41:29 +09:00
Hiroshi Kimura
084bdc431f Update .gitignore: Add .DS_Store 2016-01-08 18:40:16 +09:00
John Estropia
c63bc389b2 updated travis.yml 2016-01-06 19:29:58 +09:00
John Estropia
71c3abc4f3 Let ListMonitor expose methods for Section Indexes (fixes #32) 2016-01-06 19:16:46 +09:00
John Estropia
863d4d1d5a Merge branch 'master' into develop
# Conflicts:
#	README.md
2016-01-06 18:45:45 +09:00
John Estropia
06d177e8bd Update README.md 2016-01-04 19:38:42 +09:00
John Estropia
c2bbd537cf Update README.md 2016-01-04 19:38:11 +09:00
John Estropia
761d349b97 Rename CartFile to Cartfile. Sorry about that 2016-01-04 12:38:05 +09:00
John Rommel Estropia
6c28594e41 fixed shouldUpdateFromImportSource() not called from importUniqueObject() (fixes #31) 2015-12-29 08:25:06 +08:00
John Estropia
c229af19a2 added GCDKit to Carthage installation guide 2015-12-18 19:28:02 +09:00
John Estropia
8b8a7c7b08 fix macOSX development target 2015-12-16 14:01:56 +09:00
John Estropia
eb828d8e42 update deployment version 2015-12-15 21:20:56 +09:00
John Estropia
88a24540c6 remove IPHONEOS_DEPLOYMENT_TARGET flag 2015-12-15 20:51:48 +09:00
John Estropia
74ded8fb7d update .travis.yml 2015-12-15 19:58:59 +09:00
John Estropia
969f4cefb4 use workspaces for better carthage support 2015-12-15 19:56:07 +09:00
John Estropia
1d2947ad26 added Carthage dependency 2015-12-08 18:34:10 +09:00
John Estropia
49274c3c94 fix swift project settings 2015-12-08 16:35:50 +09:00
John Rommel Estropia
c0fbb2655b version bump 2015-12-06 20:32:06 +09:00
John Rommel Estropia
cefd6f6cbc fix watchOS travis configuration 2015-12-05 20:28:24 +09:00
John Rommel Estropia
2653f7b977 fix watchOS travis configuration 2015-12-05 20:13:52 +09:00
John Rommel Estropia
c1163283f5 fix watchOS travis configuration 2015-12-05 20:03:21 +09:00
John Rommel Estropia
529347b6a2 fix watchOS travis configuration 2015-12-05 19:48:40 +09:00
John Rommel Estropia
eba7e99374 fixed yml 2015-12-05 19:35:23 +09:00
John Rommel Estropia
3c4350bd5d enable testability 2015-12-05 19:30:04 +09:00
John Rommel Estropia
88b13189e5 fix yml 2015-12-05 19:23:12 +09:00
John Rommel Estropia
03da3544f6 change GCDKit submodule path to public url to make sir Travis happy 2015-12-05 19:20:27 +09:00
John Rommel Estropia
648b59e1ee fix yml 2015-12-05 19:14:35 +09:00
John Rommel Estropia
ab3c8ca812 tidy up 2015-12-05 19:01:07 +09:00
John Rommel Estropia
900a31c541 added build status to readme 2015-12-05 18:55:20 +09:00
John Rommel Estropia
656a107767 test travis ci 2015-12-05 18:50:54 +09:00
John Rommel Estropia
4ce3d5de3c undo interface 2015-12-05 18:21:21 +09:00
John Rommel Estropia
dec9757dc2 Merge branch 'develop' of github.com:JohnEstropia/CoreStore into develop 2015-12-05 18:19:33 +09:00
John Estropia
578e4966fc added a convenience initializer for clients that only support NSFetchedResultsController (i.e. Objective-C) 2015-11-24 19:12:04 +09:00
John Estropia
b741626574 updated GCDKit 2015-11-24 17:50:23 +09:00
John Estropia
f79a77ab78 remove unneeded imports in demo app 2015-11-24 17:02:23 +09:00
John Estropia
1f2a70fd42 minor fixes 2015-11-24 16:06:48 +09:00
John Estropia
718d2c9b7d Added ability to initialize ListMonitors asynchronously. This is a deadlock-preventive measure for apps that heavily recreates ListMonitors while updates and saves are running in the background (because NSFetchedResultController's performFetch() locks the whole NSManagedObjectContext chain up until the NSPersistentStoreCoordinator) 2015-11-24 14:49:43 +09:00
John Estropia
c5ff02335e debug flag 2015-11-24 12:32:10 +09:00
John Estropia
66b57faa44 Merge branch 'master' into develop 2015-11-24 11:34:35 +09:00
John Estropia
eef5a3d80b allow CoreStore installation both through frameworks or through direct linking 2015-11-24 11:33:52 +09:00
John Estropia
bc0757cf06 README: Added link to poll for new features 2015-11-18 19:46:17 +09:00
John Rommel Estropia
0c0a2a382c Deprecated rollback() on async and sync transactions. Added undo utilities to unsafe transactions. 2015-11-14 20:00:40 +09:00
John Rommel Estropia
05b4a7092a updated podspec for OSX 2015-10-30 01:46:47 +09:00
John Rommel Estropia
6aed070e7c version bump for OSX support 2015-10-30 01:36:15 +09:00
John Estropia
fcb1d7cbbc OSX support!!!11 2015-10-29 17:00:10 +09:00
John Estropia
d074aad111 Merge branch 'master' into develop 2015-10-26 14:42:31 +09:00
John Estropia
b7685dc747 allow Sequences of NSManagedObject subclasses as argument to delete() method 2015-10-26 14:41:58 +09:00
John Rommel Estropia
a185bc96c0 updated README 2015-10-24 11:41:13 +09:00
John Rommel Estropia
91aef44803 fix pod dependency 2015-10-23 00:49:34 +09:00
John Rommel Estropia
dcfb09eda7 attempt sight improvement for the FRC bug workaround #20 2015-10-23 00:40:38 +09:00
John Estropia
9ca83d9c5d watchOS support (fixes #19) 2015-10-20 21:01:10 +09:00
John Estropia
b00eaf2d0b fix warnings on watchOS 2015-10-20 20:54:09 +09:00
John Estropia
dc8bdf3bad watch OS support 2015-10-20 20:38:58 +09:00
John Estropia
4792c4462e fix section index titles when refetching a listMonitor 2015-10-15 18:29:08 +09:00
John Estropia
524757a7cf just a safety measure for the rare case that the FRC delegate gets released earlier than the FRC 2015-10-13 10:47:48 +09:00
John Rommel Estropia
0b6298a802 tidy up 2015-10-07 00:57:48 +09:00
John Estropia
ae77558ae8 Merge pull request #16 from mac-cain13/patch-1
Fix FRCdelegate issue where invalid type is passed
2015-10-07 00:44:15 +09:00
Mathijs Kadijk
d33aa9c5cf Fix FRCdelegate issue where invalid type is passed
See also https://forums.developer.apple.com/thread/12184#31850
2015-10-06 15:30:22 +02:00
John Estropia
d89319d324 Workaround for Xcode 7.0.1+iOS 9 FRC bug 2015-09-30 11:41:27 +09:00
John Estropia
cf9af6eef5 Merge branch 'develop' 2015-09-28 20:46:14 +09:00
John Estropia
b9ec66f425 fix entityNameMapping bug 2015-09-28 20:44:19 +09:00
John Estropia
2a8df0596d Update README.md 2015-09-24 23:47:06 +09:00
John Rommel Estropia
83e6a41d88 Merge branch 'master' into develop 2015-09-22 11:40:28 +09:00
John Rommel Estropia
c6fe494fe1 updated podspec dependency 2015-09-21 23:30:41 +09:00
John Rommel Estropia
7771e047e2 updated GCDKit version to remove warnings 2015-09-21 23:23:22 +09:00
John Rommel Estropia
5b0439835b Deprecated DetachedDataTransaction in favor of UnsafeDataTransaction. beginDetached() methods are also deprecated; use beginUnsafe() instead. 2015-09-21 15:08:46 +09:00
John Rommel Estropia
622c5aa652 Tighter generics implementations. You can now pass any SequenceType's for methods that previously only accepts Array's. 2015-09-19 19:45:01 +09:00
John Rommel Estropia
114b7ce605 Tighter generics implementations. You can now pass any SequenceType's for methods that previously only accepts Array's. 2015-09-19 18:20:52 +09:00
John Rommel Estropia
7451fbe026 added README sections about ImportableObject and ImportableUniqueObject, as well as the new fetchExisting() method usage 2015-09-19 16:23:59 +09:00
John Rommel Estropia
ee2398fcdf updated readme 2015-09-18 07:14:41 +09:00
John Rommel Estropia
8d26501040 WIP: Readme 2015-09-14 07:44:23 +09:00
John Estropia
0709fe95cf use transaction queue for refetching instead of a global queue 2015-09-11 15:06:36 +09:00
John Estropia
656a99fe12 added thread safety checks to ListMonitor to prevent deadlocks after calling refetch() 2015-09-11 13:59:28 +09:00
John Estropia
726e0eabe9 dispatch_group seems more semantically fitting than semaphore 2015-09-11 13:50:29 +09:00
John Estropia
032764b9b7 tidy up 2015-09-10 16:59:55 +09:00
John Estropia
5461bb0736 workaround an NSFetchedResultsController bug in Xcode 7 targeted on iOS 8 devices where errant index paths cause crashes 2015-09-10 16:57:35 +09:00
John Estropia
4c6bc4f494 completed appledocs for importing methods 2015-09-10 13:28:53 +09:00
John Rommel Estropia
63a43a6487 WIP: documentation 2015-09-10 07:21:53 +09:00
John Estropia
6a006d5d7c added a flag to inspect the current state of a refetch 2015-09-08 18:12:52 +09:00
John Estropia
399517e357 ListMonitor is now refetch-able. Implement the listMonitorWillRefetch and listMonitorDidRefetch methods of ListObserver and call the refetch(...) method of ListMonitor 2015-09-08 17:21:15 +09:00
John Estropia
f47adc12b3 fix typealias name clash when observing both ListMonitor and ObjectMonitor 2015-09-02 13:44:37 +09:00
John Estropia
0de1733efe oops 2015-09-01 21:05:59 +09:00
John Estropia
e627cf8161 test improvements to ListMonitor 2015-09-01 20:08:45 +09:00
John Rommel Estropia
22943d2c76 added warn_unused_result annotations where they seem reasonable 2015-08-29 22:29:03 +09:00
John Estropia
56ea14d53c fix disconnected observers when an observer registers itself to multiple ListMonitors 2015-08-28 12:09:55 +09:00
John Rommel Estropia
21a524d725 tidy up 2015-08-28 08:09:06 +09:00
John Estropia
d5e769be6c turns out inout arguments in closures are malloc nightmares. this finally fixes it 2015-08-27 22:24:40 +09:00
John Estropia
ea6b6f2c37 bandaid solution for over-deallocated sequences bug in swift (for now) 2015-08-27 19:06:52 +09:00
John Estropia
c3ef8a4172 bug fix for dictionaries getting deallocated earlier 2015-08-27 17:28:40 +09:00
John Rommel Estropia
16aabe1f3b optimize fetching objects with NSManagedObjectIDs 2015-08-26 23:59:18 +09:00
John Estropia
10e0cf8d2c tidy up 2015-08-25 20:33:36 +09:00
John Estropia
3a0f53321a improved behavior for #12 (as of Xcode 7 beta 6) 2015-08-25 14:35:22 +09:00
John Estropia
0da43d5132 Merge branch 'develop' of https://github.com/JohnEstropia/CoreStore into develop 2015-08-25 14:27:32 +09:00
John Estropia
69f902ef20 XCode7 beta 6 updates 2015-08-25 14:24:18 +09:00
John Estropia
d04b4ca085 added utility for ListMonitor to return the index/indexPath of a specified object 2015-08-24 20:40:19 +09:00
John Estropia
8ed6a78609 CoreStore adapter method 2015-08-24 17:36:40 +09:00
John Estropia
0c9e6afe0e added utility to inspect NSEntityDescription for a given NSManagedObject type 2015-08-24 17:33:27 +09:00
John Rommel Estropia
ff4629908a Merge branch 'develop' of github.com:JohnEstropia/CoreStore into develop 2015-08-23 14:37:34 +09:00
John Rommel Estropia
d3ffe7a8fc add default implementation for souldUpdateFromImportSource 2015-08-23 14:37:21 +09:00
John Rommel Estropia
3919ada428 Merge branch 'master' into develop 2015-08-23 14:29:44 +09:00
John Rommel Estropia
682b13a8d3 version bump 2015-08-23 14:25:18 +09:00
John Rommel Estropia
2f935de04a temporarily fix an Xcode 7 bug (still present as of beta 5) (temporarily fixes #12) 2015-08-23 14:21:12 +09:00
John Rommel Estropia
006d5e1402 rewrote ImportableObject protocol methods 2015-08-23 14:15:48 +09:00
John Rommel Estropia
093c1d410f temporarily fix an Xcode 7 bug (still present as of beta 5) (temporarily fixes #12) 2015-08-23 14:02:27 +09:00
John Estropia
7555ff3ad0 allow preprocessing dictionary mapping before importing objects 2015-08-21 19:48:47 +09:00
John Estropia
4c16a961ba added default implementation to didInsertFromImportSource 2015-08-21 18:28:02 +09:00
John Estropia
1121d44d7b pass transaction to ImportableObject methods 2015-08-21 18:19:21 +09:00
John Rommel Estropia
ba4fb5e5cb version bump 2015-08-21 08:03:14 +09:00
John Rommel Estropia
d860da2507 Merge branch 'develop' 2015-08-21 08:03:01 +09:00
John Estropia
2bcf8008c5 let transaction fetch existing objects from external contexts 2015-08-20 17:20:38 +09:00
John Estropia
71477c0839 allow equality comparison on ListMonitor and ObjectMonitor to help distinguish senders when observing multiple monitors 2015-08-20 14:25:14 +09:00
John Estropia
3348aa0bef added utility to check if objects exist in a ListMonitor 2015-08-20 13:51:14 +09:00
John Estropia
2ed61fdb17 added utilities to ListMonitor to extract all objects in specified sections 2015-08-20 12:15:20 +09:00
John Estropia
9cfad8a17a oops, GroupBy should not be needed for monitorList() 2015-08-19 21:00:27 +09:00
John Estropia
3bf34f33dc added utilities for ListMonitor to optionally extract objects with potentially invalid indexes/indexPaths 2015-08-19 20:56:50 +09:00
John Estropia
90bfaeaae8 removed unused key tuple 2015-08-19 18:30:39 +09:00
John Estropia
a29a4b38fe added utilities to get existing NSManagedObject instances using object IDs 2015-08-19 15:53:20 +09:00
John Estropia
5a96ef13f6 added utility to get NSManagedObjectID from an object URI 2015-08-19 15:07:15 +09:00
John Estropia
b92ee76907 added utility to refresh an object 2015-08-19 12:19:14 +09:00
John Estropia
67ccae4ef6 added missing parameters 2015-08-19 11:21:39 +09:00
John Estropia
62b11309f3 expose DataStack vars to CoreStore 2015-08-18 21:49:40 +09:00
John Estropia
8c6a7df731 provide a way to enumerate entities managed by the DataStack 2015-08-18 21:40:33 +09:00
John Rommel Estropia
b475afe79f updated Readme 2015-08-17 23:51:08 +09:00
John Rommel Estropia
a263851266 import array of ImportableObjects 2015-08-17 23:50:03 +09:00
John Estropia
cbc3eb8887 woops, develop branch should be Swift 2.0, not 1.2 2015-08-11 15:37:44 +08:00
John Rommel Estropia
2fb3263aa1 refactored autoreleasepool calls 2015-08-09 18:27:21 +09:00
John Rommel Estropia
64aa97264e added utilities for importing data 2015-08-09 05:06:42 +09:00
John Rommel Estropia
8066bf2a5a fixed assertion failures when fetching from detached data transactions 2015-08-09 05:05:26 +09:00
John Rommel Estropia
283104af3f thanks to protocol extensions, you can now omit ListObserver and ObjectObserver methods you don't need to implement 2015-08-09 05:04:47 +09:00
John Rommel Estropia
83c724f584 updated default logger to still run assertions even on optimized (-O) builds 2015-08-08 22:12:08 +09:00
John Rommel Estropia
59ad525786 fixed for Xcode 7 beta 5 2015-08-08 10:24:40 +09:00
John Rommel Estropia
b85d7521e1 version bump 2015-07-29 22:24:11 +09:00
John Rommel Estropia
4dbc4558e3 display explanation dialog for migration demo 2015-07-29 22:21:22 +09:00
John Rommel Estropia
2a56c097f2 Merge branch 'master' into develop 2015-07-29 21:32:17 +09:00
John Estropia
d4b95aed64 Merge pull request #10 from bddckr/patch-1
Fix building with Carthage
2015-07-28 12:44:43 +09:00
Christopher - Marcel Böddecker
0d2650c54b Fix building with Carthage 2015-07-27 19:26:19 +02:00
John Rommel Estropia
6a3885edda aesthetic 2015-07-26 21:21:19 +09:00
John Rommel Estropia
1c6085ad82 expose DetachedDataTransaction's context and allow creating children detached transactions (https://github.com/JohnEstropia/CoreStore/issues/9) 2015-07-26 21:20:48 +09:00
72 changed files with 4452 additions and 1068 deletions

3
.gitignore vendored
View File

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

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

41
.travis.yml Normal file
View File

@@ -0,0 +1,41 @@
language: objective-c
osx_image: xcode7.2
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.2,name=iPhone 6s" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.3,name=iPhone 5S" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.2,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 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.1,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator2.1 RUN_TESTS="NO" POD_LINT="NO"
- DESTINATION="OS=9.1,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator9.1 RUN_TESTS="YES" POD_LINT="NO"
before_install:
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- 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.2" -destination "OS=9.2,name=iPhone 6s" -configuration Debug ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator9.2" -destination "OS=9.2,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 Normal file
View File

@@ -0,0 +1 @@
github "JohnEstropia/GCDKit" == 1.1.7

1
Cartfile.resolved Normal file
View File

@@ -0,0 +1 @@
github "JohnEstropia/GCDKit" "1.1.7"

1
Carthage/Checkouts/GCDKit vendored Submodule

View File

@@ -1,16 +1,22 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "1.0.1"
s.version = "1.4.4"
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.tvos.deployment_target = "9.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.1.1"
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-D USE_FRAMEWORKS' }
s.dependency "GCDKit", "1.1.7"
end

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -16,21 +16,21 @@
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>
@@ -48,7 +48,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
BuildableName = "CoreStoreTests.xctest"
BlueprintName = "CoreStoreTests"
BlueprintName = "CoreStoreTests iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</TestableReference>
@@ -58,7 +58,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore"
BlueprintName = "CoreStore iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
@@ -80,7 +80,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore"
BlueprintName = "CoreStore iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
@@ -98,7 +98,7 @@
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore"
BlueprintName = "CoreStore iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>

View File

@@ -0,0 +1,99 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore tvOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "82BA18911C4BBCBA00A0916E"
BuildableName = "CoreStoreTests.xctest"
BlueprintName = "CoreStoreTests tvOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore tvOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore tvOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore tvOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -0,0 +1,80 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
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">
<Testables>
</Testables>
<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 = "B563216E1BD65082006C9394"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore watchOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B563216E1BD65082006C9394"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore watchOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

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

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

@@ -0,0 +1 @@
github "JohnEstropia/GCDKit" == 1.1.7

View File

@@ -1,5 +1,5 @@
//
// DetachedDataTransaction.swift
// NSManagedObject+Convenience.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
@@ -24,38 +24,27 @@
//
import Foundation
import GCDKit
import CoreData
// MARK: - DetachedDataTransaction
// MARK: - NSFetchedResultsController
/**
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 {
public extension NSFetchedResultsController {
// MARK: Public
/**
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.
- 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 convenience init<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
let context = dataStack.mainContext
from?.applyToFetchRequest(fetchRequest, context: context)
for clause in fetchClauses {
self.result = result
completion(result: result)
clause.applyToFetchRequest(fetchRequest)
}
}
// MARK: Internal
internal override var bypassesQueueing: Bool {
return true
self.init(
fetchRequest: fetchRequest,
managedObjectContext: context,
sectionNameKeyPath: sectionBy?.sectionKeyPath,
cacheName: nil
)
}
}

View File

@@ -37,6 +37,7 @@ public extension NSManagedObject {
- 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)
@@ -58,4 +59,20 @@ public extension NSManagedObject {
self.setPrimitiveValue(value, forKey: KVCKey)
self.didChangeValueForKey(KVCKey)
}
/**
Re-faults the object to use the latest values from the persistent store
*/
public func refreshAsFault() {
self.managedObjectContext?.refreshObject(self, mergeChanges: false)
}
/**
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
*/
public func refreshAndMerge() {
self.managedObjectContext?.refreshObject(self, mergeChanges: true)
}
}

View File

@@ -24,7 +24,9 @@
//
import Foundation
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - NSProgress

View File

@@ -23,6 +23,7 @@
// SOFTWARE.
//
#import <Foundation/Foundation.h>
#import <CoreData/CoreData.h>
FOUNDATION_EXPORT double CoreStoreVersionNumber;

View File

@@ -24,7 +24,9 @@
//
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - CoreStore

View File

@@ -33,6 +33,68 @@ 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.
@@ -40,10 +102,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -57,10 +120,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -74,10 +138,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -91,10 +156,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -108,10 +174,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -125,10 +192,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -142,10 +210,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -159,10 +228,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -176,10 +246,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -193,10 +264,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
@@ -213,7 +285,7 @@ public extension BaseDataTransaction {
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
self.isRunningInAllowedQueue(),
"Attempted to delete from a \(typeName(self)) outside its designated queue."
)
@@ -230,7 +302,7 @@ public extension BaseDataTransaction {
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
self.isRunningInAllowedQueue(),
"Attempted to delete from a \(typeName(self)) outside its designated queue."
)
@@ -247,10 +319,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to query from a \(typeName(self)) outside its designated queue."
)
@@ -267,10 +340,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to query from a \(typeName(self)) outside its designated queue."
)
@@ -287,10 +361,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to query from a \(typeName(self)) outside its designated queue."
)
@@ -307,10 +382,11 @@ public extension BaseDataTransaction {
- 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(),
self.isRunningInAllowedQueue(),
"Attempted to query from a \(typeName(self)) outside its designated queue."
)

View File

@@ -24,6 +24,7 @@
//
import Foundation
import CoreData
public func +(left: OrderBy, right: OrderBy) -> OrderBy {

View File

@@ -406,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
}
}
@@ -426,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
}
}
@@ -446,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
}
}
@@ -482,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
}
}

View File

@@ -24,6 +24,7 @@
//
import Foundation
import CoreData
public func &&(left: Where, right: Where) -> Where {
@@ -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
- parameter format: the format string for the predicate
- parameter 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

View File

@@ -24,6 +24,8 @@
//
import Foundation
import CoreData
// MARK: - CoreStore
@@ -31,6 +33,54 @@ 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.
@@ -38,6 +88,7 @@ public extension CoreStore {
- 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)
@@ -50,6 +101,7 @@ public extension CoreStore {
- 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)
@@ -62,6 +114,7 @@ public extension CoreStore {
- 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)
@@ -74,6 +127,7 @@ public extension CoreStore {
- 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)
@@ -86,6 +140,7 @@ public extension CoreStore {
- 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)
@@ -98,6 +153,7 @@ public extension CoreStore {
- 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)
@@ -110,6 +166,7 @@ public extension CoreStore {
- 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)
@@ -122,6 +179,7 @@ public extension CoreStore {
- 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)
@@ -134,6 +192,7 @@ public extension CoreStore {
- 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)
@@ -146,6 +205,7 @@ public extension CoreStore {
- 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)
@@ -161,6 +221,7 @@ public extension CoreStore {
- 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)
@@ -176,6 +237,7 @@ public extension CoreStore {
- 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)
@@ -191,6 +253,7 @@ public extension CoreStore {
- 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)
@@ -206,6 +269,7 @@ public extension CoreStore {
- 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)

View File

@@ -25,7 +25,9 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - DataStack
@@ -34,6 +36,68 @@ 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.
@@ -41,6 +105,7 @@ public extension DataStack {
- 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(
@@ -58,6 +123,7 @@ public extension DataStack {
- 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(
@@ -75,6 +141,7 @@ public extension DataStack {
- 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(
@@ -92,6 +159,7 @@ public extension DataStack {
- 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(
@@ -109,6 +177,7 @@ public extension DataStack {
- 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(
@@ -126,6 +195,7 @@ public extension DataStack {
- 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(
@@ -143,6 +213,7 @@ public extension DataStack {
- 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(
@@ -160,6 +231,7 @@ public extension DataStack {
- 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(
@@ -177,6 +249,7 @@ public extension DataStack {
- 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(
@@ -194,6 +267,7 @@ public extension DataStack {
- 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(
@@ -214,6 +288,7 @@ public extension DataStack {
- 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(
@@ -234,6 +309,7 @@ public extension DataStack {
- 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(
@@ -254,6 +330,7 @@ public extension DataStack {
- 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(
@@ -274,6 +351,7 @@ public extension DataStack {
- 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(

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

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

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

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>1.0.1</string>
<string>1.4.4</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

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

View File

@@ -0,0 +1,81 @@
//
// Functions.swift
// CoreStore
//
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal func autoreleasepool<T>(@noescape closure: () -> T) -> T {
var closureValue: T!
ObjectiveC.autoreleasepool {
closureValue = closure()
}
return closureValue
}
internal func autoreleasepool<T>(@noescape closure: () throws -> T) throws -> T {
var closureValue: T!
var closureError: ErrorType?
ObjectiveC.autoreleasepool {
do {
closureValue = try closure()
}
catch {
closureError = error
}
}
if let closureError = closureError {
throw closureError
}
return closureValue
}
internal func autoreleasepool(@noescape closure: () throws -> Void) throws {
var closureError: ErrorType?
ObjectiveC.autoreleasepool {
do {
try closure()
}
catch {
closureError = error
}
}
if let closureError = closureError {
throw closureError
}
}

View File

@@ -37,11 +37,12 @@ internal final class MigrationManager: NSMigrationManager, NSProgressReporting {
super.didChangeValueForKey(key)
if key == "migrationProgress" {
guard key == "migrationProgress" else {
let progress = self.progress
progress.completedUnitCount = Int64(Float(progress.totalUnitCount) * self.migrationProgress)
return
}
let progress = self.progress
progress.completedUnitCount = Int64(Float(progress.totalUnitCount) * self.migrationProgress)
}

View File

@@ -17,22 +17,10 @@ internal extension NSFileManager {
internal func removeSQLiteStoreAtURL(fileURL: NSURL) {
do {
try self.removeItemAtURL(fileURL)
}
catch _ { }
_ = try? self.removeItemAtURL(fileURL)
do {
try self.removeItemAtPath(fileURL.path!.stringByAppendingString("-shm"))
}
catch _ { }
do {
try self.removeItemAtPath(fileURL.path!.stringByAppendingString("-wal"))
}
catch _ { }
let filePath = fileURL.path!
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-shm"))
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-wal"))
}
}

View File

@@ -1,122 +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 dynamic class func createInContext(context: NSManagedObjectContext) -> Self {
return self.init(
entity: context.entityDescriptionForEntityType(self)!,
insertIntoManagedObjectContext: context
)
}
internal dynamic 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? {
do {
let existingObject = try context.existingObjectWithID(objectID)
return (existingObject as! T)
}
catch {
CoreStore.handleError(
error as NSError,
"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 objectIDError: NSError?
let didSucceed = withExtendedLifetime(self.managedObjectContext) { (context: NSManagedObjectContext?) -> Bool in
do {
try context?.obtainPermanentIDsForObjects([self])
return true
}
catch {
objectIDError = error as NSError
return false
}
}
if didSucceed != true {
CoreStore.handleError(
objectIDError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed to obtain permanent ID for object."
)
return nil
}
}
do {
let existingObject = try context.existingObjectWithID(objectID)
return (existingObject as! T)
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load existing \(typeName(self)) in context."
)
return nil
}
}
}

View File

@@ -25,7 +25,9 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - NSManagedObjectContext
@@ -61,14 +63,14 @@ internal extension NSManagedObjectContext {
internal func entityDescriptionForEntityClass(entity: AnyClass) -> NSEntityDescription? {
if let entityName = self.parentStack?.entityNameForEntityClass(entity) {
return NSEntityDescription.entityForName(
entityName,
inManagedObjectContext: self
)
guard let entityName = self.parentStack?.entityNameForEntityClass(entity) else {
return nil
}
return nil
return NSEntityDescription.entityForName(
entityName,
inManagedObjectContext: self
)
}
internal func setupForCoreStoreWithContextName(contextName: String) {
@@ -83,7 +85,7 @@ 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
}
@@ -91,7 +93,6 @@ internal extension NSManagedObjectContext {
do {
try context.obtainPermanentIDsForObjects(Array(insertedObjects))
return
}
catch {

View File

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

View File

@@ -46,7 +46,7 @@ internal extension NSManagedObjectContext {
}
set {
if self.parentContext != nil {
guard self.parentContext == nil else {
return
}
@@ -84,6 +84,11 @@ internal extension NSManagedObjectContext {
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)
}
}

View File

@@ -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)
@@ -71,7 +82,7 @@ internal extension NSManagedObjectContext {
self.performBlockAndWait { [unowned self] () -> Void in
if !self.hasChanges {
guard self.hasChanges else {
return
}
@@ -111,18 +122,15 @@ internal extension NSManagedObjectContext {
return result
}
internal func saveAsynchronouslyWithCompletion(completion: ((result: SaveResult) -> Void)?) {
internal func saveAsynchronouslyWithCompletion(completion: ((result: SaveResult) -> Void) = { _ in }) {
self.performBlock { () -> Void in
if !self.hasChanges {
guard self.hasChanges else {
if let completion = completion {
GCDQueue.Main.async {
GCDQueue.Main.async {
completion(result: SaveResult(hasChanges: false))
}
completion(result: SaveResult(hasChanges: false))
}
return
}
@@ -138,12 +146,9 @@ internal extension NSManagedObjectContext {
saveError,
"Failed to save \(typeName(NSManagedObjectContext))."
)
if let completion = completion {
GCDQueue.Main.async {
GCDQueue.Main.async {
completion(result: SaveResult(saveError))
}
completion(result: SaveResult(saveError))
}
return
}
@@ -152,7 +157,7 @@ internal extension NSManagedObjectContext {
parentContext.saveAsynchronouslyWithCompletion(completion)
}
else if let completion = completion {
else {
GCDQueue.Main.async {

View File

@@ -65,10 +65,13 @@ internal extension NSManagedObjectModel {
}
else if let resolvedVersion = modelVersions.first ?? modelVersionHints.first {
CoreStore.log(
.Warning,
message: "The MigrationChain leaf versions do not include any of the model file's embedded versions. Resolving to version \"\(resolvedVersion)\"."
)
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 {
@@ -150,6 +153,15 @@ internal extension NSManagedObjectModel {
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]
@@ -183,14 +195,15 @@ internal extension NSManagedObjectModel {
@nonobjc internal subscript(metadata: [String: AnyObject]) -> NSManagedObjectModel? {
if let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : NSData] {
guard let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : NSData] else {
for modelVersion in self.modelVersions ?? [] {
return nil
}
for modelVersion in self.modelVersions ?? [] {
if let versionModel = self[modelVersion] where modelHashes == versionModel.entityVersionHashesByName {
if let versionModel = self[modelVersion] where modelHashes == versionModel.entityVersionHashesByName {
return versionModel
}
return versionModel
}
}
return nil

View File

@@ -62,21 +62,26 @@ public final class DefaultLogger: CoreStoreLogger {
icon = ""
levelString = "Fatal"
}
Swift.print("\(icon) [CoreStore: \(levelString)] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
Swift.print("\(icon) [CoreStore: \(levelString)] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
#endif
}
public func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
#if DEBUG
Swift.print("⚠️ [CoreStore: Error] \(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n")
Swift.print("⚠️ [CoreStore: Error] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n")
#endif
}
public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
#if DEBUG
Swift.assert(condition, "❗ [CoreStore: Assertion Failure] \(message)", file: fileName, line: numericCast(lineNumber))
if condition() {
return
}
Swift.print("❗ [CoreStore: Assertion Failure] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
Swift.fatalError()
#endif
}
}

View File

@@ -25,7 +25,9 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - CoreStore
@@ -35,18 +37,20 @@ 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 fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
completion: completion
)
}
@@ -54,18 +58,20 @@ public extension CoreStore {
/**
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 fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
completion: completion
)
}
@@ -73,7 +79,7 @@ public extension CoreStore {
/**
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 fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
@@ -92,7 +98,7 @@ public extension CoreStore {
/**
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 fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
@@ -111,11 +117,12 @@ public extension CoreStore {
/**
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 fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public static func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForSQLiteStore(
@@ -133,6 +140,7 @@ public extension CoreStore {
- 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(

View File

@@ -25,7 +25,9 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - DataStack
@@ -76,7 +78,7 @@ public extension DataStack {
/**
Asynchronously adds to the stack an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
@@ -86,7 +88,7 @@ public extension DataStack {
public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.addSQLiteStore(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
@@ -100,7 +102,7 @@ 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`.)
- 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 fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
@@ -117,40 +119,37 @@ public extension DataStack {
let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) {
if store.type == NSSQLiteStoreType
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
guard store.type == NSSQLiteStoreType
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
return nil
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
}
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError(
error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
)
throw error
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
return nil
}
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
}
catch _ { }
_ = try? fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
do {
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL
URL: fileURL,
options: self.optionsForSQLiteStore()
)
return self.upgradeSQLiteStoreIfNeeded(
@@ -234,7 +233,7 @@ public extension DataStack {
/**
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 fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
@@ -243,7 +242,7 @@ public extension DataStack {
public func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.upgradeSQLiteStoreIfNeeded(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
@@ -256,7 +255,7 @@ public extension DataStack {
/**
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 fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
@@ -269,7 +268,8 @@ public extension DataStack {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL
URL: fileURL,
options: self.optionsForSQLiteStore()
)
}
catch {
@@ -293,15 +293,16 @@ public extension DataStack {
/**
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 fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try requiredMigrationsForSQLiteStore(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
@@ -318,6 +319,7 @@ public extension DataStack {
- 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]
@@ -325,7 +327,8 @@ public extension DataStack {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL
URL: fileURL,
options: self.optionsForSQLiteStore()
)
}
catch {
@@ -436,7 +439,7 @@ public extension DataStack {
let migrationOperation = NSBlockOperation()
migrationOperation.qualityOfService = .Utility
operations.map { migrationOperation.addDependency($0) }
operations.forEach { migrationOperation.addDependency($0) }
migrationOperation.addExecutionBlock { () -> Void in
GCDQueue.Main.async {

View File

@@ -166,12 +166,10 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D
var valid = true
for version in elements {
if let lastVersion = lastVersion {
if let _ = versionTree.updateValue(version, forKey: lastVersion) {
if let lastVersion = lastVersion,
let _ = versionTree.updateValue(version, forKey: lastVersion) {
valid = false
}
}
lastVersion = version
}
@@ -203,11 +201,11 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D
internal func nextVersionFrom(version: String) -> String? {
if let nextVersion = self.versionTree[version] where nextVersion != version {
guard let nextVersion = self.versionTree[version] where nextVersion != version else {
return nextVersion
return nil
}
return nil
return nextVersion
}
@@ -237,9 +235,9 @@ extension MigrationChain: CustomDebugStringConvertible {
steps.append(nextVersion)
version = nextVersion
}
paths.append("".join(steps))
paths.append(steps.joinWithSeparator(""))
}
return "[" + "], [".join(paths) + "]"
return "[" + paths.joinWithSeparator("], [") + "]"
}
}

View File

@@ -24,6 +24,7 @@
//
import Foundation
import CoreData
/**
The `NSError` error domain for `CoreStore`.

View File

@@ -29,6 +29,7 @@ import CoreData
// MARK: - CoreStore
@available(OSX, unavailable)
public extension CoreStore {
// MARK: Public
@@ -39,6 +40,7 @@ public extension CoreStore {
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
@warn_unused_result
public static func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
return self.defaultStack.monitorObject(object)
@@ -50,8 +52,9 @@ public extension CoreStore {
- 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 monitorList<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: FetchClause...) -> ListMonitor<T> {
*/
@warn_unused_result
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: FetchClause...) -> ListMonitor<T> {
return self.defaultStack.monitorList(from, queryClauses)
}
@@ -63,11 +66,36 @@ public extension CoreStore {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorList<T: NSManagedObject>(from: From<T>, _ groupBy: GroupBy? = nil, _ queryClauses: [FetchClause]) -> ListMonitor<T> {
@warn_unused_result
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: [FetchClause]) -> ListMonitor<T> {
return self.defaultStack.monitorList(from, queryClauses)
}
/**
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...) {
self.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
/**
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]) {
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.
@@ -76,6 +104,7 @@ public extension CoreStore {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@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)
@@ -89,8 +118,35 @@ public extension CoreStore {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@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)
}
}

View File

@@ -25,11 +25,14 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - DataStack
@available(OSX, unavailable)
public extension DataStack {
// MARK: Public
@@ -40,6 +43,7 @@ public extension DataStack {
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
@warn_unused_result
public func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
CoreStore.assert(
@@ -59,7 +63,8 @@ public extension DataStack {
- 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.monitorList(from, fetchClauses)
@@ -71,7 +76,8 @@ public extension DataStack {
- 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(
@@ -91,6 +97,45 @@ public extension DataStack {
)
}
/**
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...) {
self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
/**
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(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
_ = ListMonitor(
dataStack: self,
from: from,
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.
@@ -98,7 +143,8 @@ public extension DataStack {
- 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)
@@ -111,7 +157,8 @@ public extension DataStack {
- 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(
@@ -130,4 +177,45 @@ public extension DataStack {
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
)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -38,26 +38,68 @@ Implement the `ListObserver` protocol to observe changes to a list of `NSManaged
)
monitor.addObserver(self)
*/
@available(OSX, unavailable)
public protocol ListObserver: class {
/**
The `NSManagedObject` type for the observed list
*/
typealias EntityType: NSManagedObject
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<EntityType>)
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<EntityType>)
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>) { }
}
@@ -72,6 +114,7 @@ Implement the `ListObjectObserver` protocol to observe detailed changes to a lis
)
monitor.addObserver(self)
*/
@available(OSX, unavailable)
public protocol ListObjectObserver: ListObserver {
/**
@@ -81,7 +124,7 @@ public protocol ListObjectObserver: ListObserver {
- parameter object: the entity type for the inserted object
- parameter indexPath: the new `NSIndexPath` for the inserted object
*/
func listMonitor(monitor: ListMonitor<EntityType>, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath)
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: NSIndexPath)
/**
Notifies that an object was deleted from the specified `NSIndexPath` in the list
@@ -90,7 +133,7 @@ public protocol ListObjectObserver: ListObserver {
- parameter object: the entity type for the deleted object
- parameter indexPath: the `NSIndexPath` for the deleted object
*/
func listMonitor(monitor: ListMonitor<EntityType>, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath)
func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: NSIndexPath)
/**
Notifies that an object at the specified `NSIndexPath` was updated
@@ -99,7 +142,7 @@ public protocol ListObjectObserver: ListObserver {
- parameter object: the entity type for the updated object
- parameter indexPath: the `NSIndexPath` for the updated object
*/
func listMonitor(monitor: ListMonitor<EntityType>, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath)
func listMonitor(monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: NSIndexPath)
/**
Notifies that an object's index changed
@@ -109,7 +152,34 @@ public protocol ListObjectObserver: ListObserver {
- parameter fromIndexPath: the previous `NSIndexPath` for the moved object
- parameter toIndexPath: the new `NSIndexPath` for the moved object
*/
func listMonitor(monitor: ListMonitor<EntityType>, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
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) { }
}
@@ -125,6 +195,7 @@ Implement the `ListSectionObserver` protocol to observe changes to a list's sect
)
monitor.addObserver(self)
*/
@available(OSX, unavailable)
public protocol ListSectionObserver: ListObjectObserver {
/**
@@ -133,8 +204,8 @@ public protocol ListSectionObserver: ListObjectObserver {
- 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<EntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
/**
Notifies that a section was inserted at the specified index
@@ -142,6 +213,23 @@ public protocol ListSectionObserver: ListObjectObserver {
- 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<EntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
*/
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) { }
}

View File

@@ -25,21 +25,9 @@
import Foundation
import CoreData
import GCDKit
private let ObjectMonitorWillChangeObjectNotification = "ObjectMonitorWillChangeObjectNotification"
private let ObjectMonitorDidDeleteObjectNotification = "ObjectMonitorDidDeleteObjectNotification"
private let ObjectMonitorDidUpdateObjectNotification = "ObjectMonitorDidUpdateObjectNotification"
private let UserInfoKeyObject = "UserInfoKeyObject"
private struct NotificationKey {
static var willChangeObject: Void?
static var didDeleteObject: Void?
static var didUpdateObject: Void?
}
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - ObjectMonitor
@@ -54,6 +42,7 @@ The created `ObjectMonitor` instance needs to be held on (retained) for as long
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
@@ -85,7 +74,7 @@ public final class ObjectMonitor<T: NSManagedObject> {
- parameter observer: an `ObjectObserver` to send change notifications to
*/
public func addObserver<U: ObjectObserver where U.EntityType == T>(observer: U) {
public func addObserver<U: ObjectObserver where U.ObjectEntityType == T>(observer: U) {
CoreStore.assert(
NSThread.isMainThread(),
@@ -95,56 +84,60 @@ public final class ObjectMonitor<T: NSManagedObject> {
self.removeObserver(observer)
self.registerChangeNotification(
&NotificationKey.willChangeObject,
&self.willChangeObjectKey,
name: ObjectMonitorWillChangeObjectNotification,
toObserver: observer,
callback: { [weak observer] (monitor) -> Void in
if let object = monitor.object, let observer = observer {
guard let object = monitor.object, let observer = observer else {
observer.objectMonitor(monitor, willUpdateObject: object)
return
}
observer.objectMonitor(monitor, willUpdateObject: object)
}
)
self.registerObjectNotification(
&NotificationKey.didDeleteObject,
&self.didDeleteObjectKey,
name: ObjectMonitorDidDeleteObjectNotification,
toObserver: observer,
callback: { [weak observer] (monitor, object) -> Void in
if let observer = observer {
guard let observer = observer else {
observer.objectMonitor(monitor, didDeleteObject: object)
return
}
observer.objectMonitor(monitor, didDeleteObject: object)
}
)
self.registerObjectNotification(
&NotificationKey.didUpdateObject,
&self.didUpdateObjectKey,
name: ObjectMonitorDidUpdateObjectNotification,
toObserver: observer,
callback: { [weak self, weak observer] (monitor, object) -> Void in
if let strongSelf = self, let observer = observer {
guard let strongSelf = self, let observer = observer else {
let previousCommitedAttributes = strongSelf.lastCommittedAttributes
let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [String: NSObject]
let changedKeys = currentCommitedAttributes.keys.reduce(Set<String>()) { (var changedKeys, key) -> Set<String> in
if previousCommitedAttributes[key] != currentCommitedAttributes[key] {
changedKeys.insert(key)
}
return changedKeys
}
strongSelf.lastCommittedAttributes = currentCommitedAttributes
observer.objectMonitor(
monitor,
didUpdateObject: object,
changedPersistentKeys: changedKeys
)
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
)
}
)
}
@@ -156,7 +149,7 @@ public final class ObjectMonitor<T: NSManagedObject> {
- parameter observer: an `ObjectObserver` to unregister notifications to
*/
public func removeObserver<U: ObjectObserver where U.EntityType == T>(observer: U) {
public func removeObserver<U: ObjectObserver where U.ObjectEntityType == T>(observer: U) {
CoreStore.assert(
NSThread.isMainThread(),
@@ -164,9 +157,9 @@ public final class ObjectMonitor<T: NSManagedObject> {
)
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)
setAssociatedRetainedObject(nilValue, forKey: &self.willChangeObjectKey, inObject: observer)
setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteObjectKey, inObject: observer)
setAssociatedRetainedObject(nilValue, forKey: &self.didUpdateObjectKey, inObject: observer)
}
@@ -174,28 +167,22 @@ public final class ObjectMonitor<T: NSManagedObject> {
internal init(dataStack: DataStack, object: T) {
let context = dataStack.mainContext
let fetchRequest = NSFetchRequest()
fetchRequest.entity = object.entity
fetchRequest.fetchLimit = 1
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectResultType
fetchRequest.sortDescriptors = []
let originalObjectID = object.objectID
Where("SELF", isEqualTo: originalObjectID).applyToFetchRequest(fetchRequest)
fetchRequest.includesPendingChanges = false
fetchRequest.shouldRefreshRefetchedObjects = true
let fetchedResultsController = NSFetchedResultsController(
dataStack: dataStack,
fetchRequest: fetchRequest,
managedObjectContext: context,
sectionNameKeyPath: nil,
cacheName: nil
fetchClauses: [Where("SELF", isEqualTo: object.objectID)]
)
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
self.originalObjectID = originalObjectID
self.fetchedResultsController = fetchedResultsController
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
self.parentStack = dataStack
@@ -207,15 +194,23 @@ public final class ObjectMonitor<T: NSManagedObject> {
self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [String: NSObject]) ?? [:]
}
deinit {
self.fetchedResultsControllerDelegate.fetchedResultsController = nil
}
// MARK: Private
private let originalObjectID: NSManagedObjectID
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(
@@ -224,10 +219,11 @@ public final class ObjectMonitor<T: NSManagedObject> {
object: self,
closure: { [weak self] (note) -> Void in
if let strongSelf = self {
guard let strongSelf = self else {
callback(monitor: strongSelf)
return
}
callback(monitor: strongSelf)
}
),
forKey: notificationKey,
@@ -243,15 +239,13 @@ public final class ObjectMonitor<T: NSManagedObject> {
object: self,
closure: { [weak self] (note) -> Void in
if let strongSelf = self,
guard let strongSelf = self,
let userInfo = note.userInfo,
let object = userInfo[UserInfoKeyObject] as? T {
let object = userInfo[UserInfoKeyObject] as? T else {
callback(
monitor: strongSelf,
object: object
)
return
}
callback(monitor: strongSelf, object: object)
}
),
forKey: notificationKey,
@@ -261,13 +255,36 @@ public final class ObjectMonitor<T: NSManagedObject> {
}
// 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
private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
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 {
@@ -290,57 +307,18 @@ extension ObjectMonitor: FetchedResultsControllerHandler {
}
}
private func controllerWillChangeContent(controller: NSFetchedResultsController) {
internal func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { }
internal func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? {
NSNotificationCenter.defaultCenter().postNotificationName(
ObjectMonitorWillChangeObjectNotification,
object: self
)
return sectionName
}
}
// MARK: - FetchedResultsControllerHandler
private let ObjectMonitorWillChangeObjectNotification = "ObjectMonitorWillChangeObjectNotification"
private let ObjectMonitorDidDeleteObjectNotification = "ObjectMonitorDidDeleteObjectNotification"
private let ObjectMonitorDidUpdateObjectNotification = "ObjectMonitorDidUpdateObjectNotification"
private protocol FetchedResultsControllerHandler: class {
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
func controllerWillChangeContent(controller: NSFetchedResultsController)
}
private let UserInfoKeyObject = "UserInfoKeyObject"
// MARK: - FetchedResultsControllerDelegate
private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: NSFetchedResultsControllerDelegate
@objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
self.handler?.controllerWillChangeContent(controller)
}
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: NSManagedObject, 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
}
}

View File

@@ -35,12 +35,13 @@ Implement the `ObjectObserver` protocol to observe changes to a single `NSManage
let monitor = CoreStore.monitorObject(object)
monitor.addObserver(self)
*/
@available(OSX, unavailable)
public protocol ObjectObserver: class {
/**
The `NSManagedObject` type for the observed object
*/
typealias EntityType: NSManagedObject
typealias ObjectEntityType: NSManagedObject
/**
Handles processing just before a change to the observed `object` occurs
@@ -48,7 +49,7 @@ public protocol ObjectObserver: class {
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
- parameter object: the `NSManagedObject` instance being observed
*/
func objectMonitor(monitor: ObjectMonitor<EntityType>, willUpdateObject object: EntityType)
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType)
/**
Handles processing right after a change to the observed `object` occurs
@@ -57,7 +58,7 @@ public protocol ObjectObserver: class {
- 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<EntityType>, didUpdateObject object: EntityType, changedPersistentKeys: Set<KeyPath>)
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set<KeyPath>)
/**
Handles processing right after `object` is deleted
@@ -65,5 +66,27 @@ public protocol ObjectObserver: class {
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
- parameter object: the `NSManagedObject` instance being observed
*/
func objectMonitor(monitor: ObjectMonitor<EntityType>, didDeleteObject object: EntityType)
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) { }
}

View File

@@ -38,6 +38,7 @@ The `SectionBy` clause indicates the key path to use to group the `ListMonitor`
OrderBy(.Ascending("lastName"))
)
*/
@available(OSX, unavailable)
public struct SectionBy {
// MARK: Public

View File

@@ -25,7 +25,9 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - AsynchronousDataTransaction
@@ -54,14 +56,15 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
)
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()
group.wait()
}
/**
@@ -112,6 +115,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- 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(
@@ -129,6 +133,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- 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(
@@ -168,7 +173,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
"Attempted to delete an entities from an already committed \(typeName(self))."
)
super.delete([object1, object2] + objects)
super.delete(([object1, object2] + objects).flatMap { $0 })
}
/**
@@ -176,7 +181,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- 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,
@@ -187,16 +192,21 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
}
/**
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.transactionQueue.isCurrentExecutionContext(),
"Attempted to rollback a \(typeName(self)) outside its designated queue."
)
super.rollback()
self.context.reset()
}
@@ -206,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() {

View File

@@ -25,7 +25,9 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - BaseDataTransaction
@@ -54,7 +56,7 @@ public /*abstract*/ class BaseDataTransaction {
public func create<T: NSManagedObject>(into: Into<T>) -> T {
CoreStore.assert(
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
self.isRunningInAllowedQueue(),
"Attempted to create an entity of type \(typeName(T)) outside its designated queue."
)
@@ -104,14 +106,18 @@ public /*abstract*/ class BaseDataTransaction {
- parameter object: the `NSManagedObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
@warn_unused_result
public func edit<T: NSManagedObject>(object: T?) -> T? {
CoreStore.assert(
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
self.isRunningInAllowedQueue(),
"Attempted to update an entity of type \(typeName(object)) outside its designated queue."
)
return object?.inContext(self.context)
guard let object = object else {
return nil
}
return self.context.fetchExisting(object)
}
/**
@@ -121,10 +127,11 @@ public /*abstract*/ class BaseDataTransaction {
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
@warn_unused_result
public func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
CoreStore.assert(
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
self.isRunningInAllowedQueue(),
"Attempted to update an entity of type \(typeName(T)) outside its designated queue."
)
CoreStore.assert(
@@ -132,8 +139,7 @@ public /*abstract*/ class BaseDataTransaction {
|| (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 (into.entityClass as! NSManagedObject.Type).inContext(self.context, withObjectID: objectID) as? T
return self.fetchExisting(objectID) as? T
}
/**
@@ -144,11 +150,14 @@ public /*abstract*/ class BaseDataTransaction {
public func delete(object: NSManagedObject?) {
CoreStore.assert(
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
self.isRunningInAllowedQueue(),
"Attempted to delete an entity outside its designated queue."
)
object?.inContext(self.context)?.deleteFromContext()
guard let object = object else {
return
}
self.context.fetchExisting(object)?.deleteFromContext()
}
/**
@@ -160,7 +169,7 @@ public /*abstract*/ class BaseDataTransaction {
*/
public func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
self.delete([object1, object2] + objects)
self.delete(([object1, object2] + objects).flatMap { $0 })
}
/**
@@ -168,33 +177,15 @@ public /*abstract*/ class BaseDataTransaction {
- 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.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
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.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
"Attempted to rollback a \(typeName(self)) outside its designated queue."
)
self.context.reset()
objects.forEach { context.fetchExisting($0)?.deleteFromContext() }
}
@@ -203,26 +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 var bypassesQueueing: Bool {
internal func isRunningInAllowedQueue() -> Bool {
return false
return self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext()
}
}

View File

@@ -43,23 +43,32 @@ public extension CoreStore {
}
/**
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
*/
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()
}
}

View File

@@ -25,7 +25,9 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - DataStack
@@ -48,11 +50,11 @@ public extension DataStack {
}
/**
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
*/
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? {
return SynchronousDataTransaction(
@@ -62,14 +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 {
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()
}
}

View File

@@ -36,7 +36,7 @@ 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"))
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
This helps the `NSManagedObjectContext` to determine which
*/
@@ -48,7 +48,7 @@ public struct Into<T: NSManagedObject> {
Initializes an `Into` clause.
Sample Usage:
let person = transaction.create(Into<MyPersonEntity>())
let person = transaction.create(Into<MyPersonEntity>())
*/
public init(){

View File

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

View File

@@ -25,7 +25,9 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - SynchronousDataTransaction
@@ -103,6 +105,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
- 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(
@@ -120,6 +123,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
- 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(
@@ -159,7 +163,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
"Attempted to delete an entities from an already committed \(typeName(self))."
)
super.delete([object1, object2] + objects)
super.delete(([object1, object2] + objects).flatMap { $0 })
}
/**
@@ -167,7 +171,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
- 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,
@@ -179,20 +183,32 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
/**
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.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 {
@@ -210,13 +226,6 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
return self.result
}
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: SynchronousDataTransaction) -> Void) {
self.closure = closure
super.init(mainContext: mainContext, queue: queue)
}
// MARK: Private

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

View File

@@ -25,13 +25,39 @@
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`.
@@ -46,7 +72,7 @@ public extension CoreStore {
/**
Adds to the `defaultStack` an SQLite store from the given SQLite file name.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
- returns: the `NSPersistentStore` added to the stack.
@@ -63,7 +89,7 @@ public extension CoreStore {
/**
Adds to the `defaultStack` an SQLite store from the given SQLite file URL.
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory.
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- returns: the `NSPersistentStore` added to the stack.

View File

@@ -25,14 +25,22 @@
import Foundation
import CoreData
import GCDKit
#if USE_FRAMEWORKS
import GCDKit
#endif
internal let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first!
#if os(tvOS)
internal let deviceDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
#else
internal let deviceDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
#endif
internal let defaultDirectory = NSFileManager.defaultManager().URLsForDirectory(deviceDirectorySearchPath, inDomains: .UserDomainMask).first!
internal let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
internal let defaultSQLiteStoreURL = applicationSupportDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite")
internal let defaultSQLiteStoreURL = defaultDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite")
// MARK: - DataStack
@@ -81,6 +89,33 @@ public final class DataStack {
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.
@@ -127,7 +162,7 @@ public final class DataStack {
/**
Adds to the stack an SQLite store from the given SQLite file name.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
- returns: the `NSPersistentStore` added to the stack.
@@ -135,7 +170,7 @@ public final class DataStack {
public func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
return try self.addSQLiteStoreAndWait(
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
@@ -147,7 +182,7 @@ public final class DataStack {
/**
Adds to the stack an SQLite store from the given SQLite file URL.
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- returns: the `NSPersistentStore` added to the stack.
@@ -162,34 +197,31 @@ public final class DataStack {
let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) {
if store.type == NSSQLiteStoreType
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
guard store.type == NSSQLiteStoreType
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
return 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
}
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError(
error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
)
throw error
return store
}
let fileManager = NSFileManager.defaultManager()
do {
try fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
}
catch _ { }
_ = try? fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
var store: NSPersistentStore?
var storeError: NSError?
let options = self.optionsForSQLiteStore()
coordinator.performBlockAndWait {
do {
@@ -198,7 +230,7 @@ public final class DataStack {
NSSQLiteStoreType,
configuration: configuration,
URL: fileURL,
options: [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
options: options
)
}
catch {
@@ -259,7 +291,7 @@ public final class DataStack {
internal let mainContext: NSManagedObjectContext
internal let model: NSManagedObjectModel
internal let migrationChain: MigrationChain
internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue")
internal let childTransactionQueue: GCDQueue = .createSerial("com.coreStore.dataStack.childTransactionQueue")
internal let migrationQueue: NSOperationQueue = {
let migrationQueue = NSOperationQueue()
@@ -270,6 +302,11 @@ public final class DataStack {
return migrationQueue
}()
internal func optionsForSQLiteStore() -> [String: AnyObject] {
return [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
}
internal func entityNameForEntityClass(entityClass: AnyClass) -> String? {
return self.model.entityNameForClass(entityClass)
@@ -350,11 +387,7 @@ public final class DataStack {
for store in self.coordinator.persistentStores {
do {
try self.coordinator.removePersistentStore(store)
}
catch _ { }
_ = try? self.coordinator.removePersistentStore(store)
}
}
}

View File

@@ -35,10 +35,10 @@
B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; };
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; };
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */; };
B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; };
B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; };
B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
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 */; };
@@ -47,37 +47,6 @@
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;
@@ -85,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;
@@ -124,8 +93,8 @@
B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingResultsViewController.swift; sourceTree = "<group>"; };
B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = "<group>"; };
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreStore.xcodeproj; path = ../CoreStore.xcodeproj; sourceTree = "<group>"; };
B5D9C9181B20AB1900E64F0E /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B5BDC9211C202429008147CD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; 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>"; };
@@ -142,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;
};
@@ -175,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;
@@ -275,15 +244,6 @@
path = "Migrations Demo";
sourceTree = "<group>";
};
B583A9151AF5F4F3001F76AF /* Products */ = {
isa = PBXGroup;
children = (
B583A91B1AF5F4F4001F76AF /* CoreStore.framework */,
B583A91D1AF5F4F4001F76AF /* CoreStoreTests.xctest */,
);
name = Products;
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -299,8 +259,6 @@
buildRules = (
);
dependencies = (
B583A91F1AF5F512001F76AF /* PBXTargetDependency */,
B583A9231AF5F542001F76AF /* PBXTargetDependency */,
);
name = CoreStoreDemo;
productName = CoreStoreDemo;
@@ -333,12 +291,6 @@
mainGroup = B54AAD401AF4D26E00848AE0;
productRefGroup = B54AAD4A1AF4D26E00848AE0 /* Products */;
projectDirPath = "";
projectReferences = (
{
ProductGroup = B583A9151AF5F4F3001F76AF /* Products */;
ProjectRef = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
},
);
projectRoot = "";
targets = (
B54AAD481AF4D26E00848AE0 /* CoreStoreDemo */,
@@ -346,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;
@@ -415,19 +350,6 @@
};
/* End PBXSourcesBuildPhase section */
/* Begin PBXTargetDependency section */
B583A91F1AF5F512001F76AF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = CoreStore;
targetProxy = B583A91E1AF5F512001F76AF /* PBXContainerItemProxy */;
};
B583A9231AF5F542001F76AF /* PBXTargetDependency */ = {
isa = PBXTargetDependency;
name = CoreStore;
targetProxy = B583A9221AF5F542001F76AF /* PBXContainerItemProxy */;
};
/* End PBXTargetDependency section */
/* Begin PBXVariantGroup section */
B54AAD571AF4D26E00848AE0 /* Main.storyboard */ = {
isa = PBXVariantGroup;

View File

@@ -12,6 +12,41 @@ import CoreStore
private struct Static {
enum Filter: String {
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(
@@ -47,7 +82,8 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
super.viewDidLoad()
self.navigationItem.leftBarButtonItems = [
let navigationItem = self.navigationItem
navigationItem.leftBarButtonItems = [
self.editButtonItem(),
UIBarButtonItem(
barButtonSystemItem: .Trash,
@@ -55,13 +91,26 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
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?) {
@@ -150,6 +199,18 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
self.tableView.endUpdates()
}
func listMonitorWillRefetch(monitor: ListMonitor<Palette>) {
self.setTableEnabled(false)
}
func listMonitorDidRefetch(monitor: ListMonitor<Palette>) {
self.filterBarButton?.title = Static.filter.rawValue
self.tableView.reloadData()
self.setTableEnabled(true)
}
// MARK: ListObjectObserver
@@ -195,6 +256,8 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
// MARK: Private
private var filterBarButton: UIBarButtonItem?
@IBAction private dynamic func resetBarButtonItemTouched(sender: AnyObject?) {
CoreStore.beginAsynchronous { (transaction) -> Void in
@@ -204,6 +267,11 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
}
}
@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 ListObserverDemoViewController: UITableViewController, ListSectionObserver
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
)
}
}

View File

@@ -22,6 +22,11 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
}
set {
guard self.monitor?.object != newValue else {
return
}
if let palette = newValue {
self.monitor = CoreStore.monitorObject(palette)
@@ -80,11 +85,6 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
// MARK: ObjectObserver
func objectMonitor(monitor: ObjectMonitor<Palette>, willUpdateObject object: Palette) {
// none
}
func objectMonitor(monitor: ObjectMonitor<Palette>, didUpdateObject object: Palette, changedPersistentKeys: Set<KeyPath>) {
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)

View File

@@ -62,7 +62,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
case .Warning: levelString = "Warning"
case .Fatal: levelString = "Fatal"
}
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
}
}
@@ -70,7 +70,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
GCDQueue.Main.async { [weak self] in
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
}
}
@@ -83,13 +83,13 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
GCDQueue.Main.async { [weak self] in
self?.textView?.insertText("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
}
}
@noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
Swift.fatalError("\(fileName.stringValue.lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)")
Swift.fatalError("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)")
}

View File

@@ -37,6 +37,15 @@ class MigrationsDemoViewController: UIViewController {
super.viewDidAppear(animated)
let alert = UIAlertController(
title: "Migrations Demo",
message: "This demo shows how to run incremental migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
preferredStyle: .Alert
)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
let modelMetadata = withExtendedLifetime(DataStack(modelName: "MigrationDemo")) {
(dataStack: DataStack) -> ModelMetadata in
@@ -276,7 +285,7 @@ class MigrationsDemoViewController: UIViewController {
}
self.titleLabel?.text = organismType
self.organismLabel?.text = "\n".join(lines)
self.organismLabel?.text = lines.joinWithSeparator("\n")
self.progressView?.progress = 0
self.headerContainer?.setNeedsLayout()

View File

@@ -78,7 +78,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
let alert = UIAlertController(
title: "Transactions Demo",
message: "This demo shows how to use the 3 types of transactions to save updates: synchronous, asynchronous, and detached.\n\nTap and hold on the map to change the pin location.",
message: "This demo shows how to use the 3 types of transactions to save updates: synchronous, asynchronous, and unsafe.\n\nTap and hold on the map to change the pin location.",
preferredStyle: .Alert
)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
@@ -159,13 +159,14 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
if let mapView = self.mapView, let gesture = sender as? UILongPressGestureRecognizer where gesture.state == .Began {
let coordinate = mapView.convertPoint(
gesture.locationInView(mapView),
toCoordinateFromView: mapView
)
CoreStore.beginAsynchronous { (transaction) -> Void in
let place = transaction.edit(Static.placeController.object)
place?.coordinate = mapView.convertPoint(
gesture.locationInView(mapView),
toCoordinateFromView: mapView
)
place?.coordinate = coordinate
transaction.commit { (_) -> Void in }
}
}
@@ -183,7 +184,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
func geocodePlace(place: Place) {
let transaction = CoreStore.beginDetached()
let transaction = CoreStore.beginUnsafe()
self.geocoder?.cancelGeocode()

View File

@@ -23,7 +23,6 @@
// SOFTWARE.
//
import UIKit
import XCTest
@testable
@@ -103,7 +102,7 @@ class CoreStoreTests: XCTestCase {
XCTFail(error.description)
}
let detachedTransaction = CoreStore.beginDetached()
let unsafeTransaction = CoreStore.beginUnsafe()
let createExpectation = self.expectationWithDescription("Entity creation")
CoreStore.beginAsynchronous { (transaction) -> Void in
@@ -182,7 +181,7 @@ class CoreStoreTests: XCTestCase {
)
XCTAssertNil(objs4test, "objs4test == nil")
let objs5test = detachedTransaction.fetchCount(From(TestEntity2))
let objs5test = unsafeTransaction.fetchCount(From(TestEntity2))
XCTAssertTrue(objs5test == 3, "objs5test == 3")
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
@@ -289,15 +288,16 @@ class CoreStoreTests: XCTestCase {
XCTAssertNotNil(objs2, "objs2 != nil")
XCTAssertTrue(objs2?.count == 1, "objs2?.count == 1")
let detachedExpectation = self.expectationWithDescription("Query creation")
let unsafeExpectation = self.expectationWithDescription("Query creation")
let obj5 = detachedTransaction.create(Into<TestEntity1>("Config1"))
let obj5 = unsafeTransaction.create(Into<TestEntity1>("Config1"))
obj5.testEntityID = 5
obj5.testString = "hihihi"
obj5.testNumber = 70
obj5.testDate = NSDate()
XCTAssert(unsafeTransaction === obj5.unsafeDataTransaction, "unsafeTransaction === obj5.unsafeDataTransaction")
detachedTransaction.commit { (result) -> Void in
unsafeTransaction.commit { (result) -> Void in
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
switch result {
@@ -310,6 +310,7 @@ class CoreStoreTests: XCTestCase {
let obj5Copy1 = transaction.edit(obj5)
XCTAssertTrue(obj5.objectID == obj5Copy1?.objectID, "obj5.objectID == obj5Copy1?.objectID")
XCTAssertFalse(obj5 == obj5Copy1, "obj5 == obj5Copy1")
XCTAssertNil(obj5Copy1?.unsafeDataTransaction)
let obj5Copy2 = transaction.edit(Into(TestEntity1), obj5.objectID)
XCTAssertTrue(obj5.objectID == obj5Copy2?.objectID, "obj5.objectID == obj5Copy2?.objectID")
@@ -322,13 +323,14 @@ class CoreStoreTests: XCTestCase {
)
XCTAssertTrue(count == 1, "count == 1 (actual: \(count))")
let obj6 = detachedTransaction.create(Into<TestEntity1>())
let obj6 = unsafeTransaction.create(Into<TestEntity1>())
obj6.testEntityID = 6
obj6.testString = "huehuehue"
obj6.testNumber = 130
obj6.testDate = NSDate()
XCTAssert(unsafeTransaction === obj6.unsafeDataTransaction, "unsafeTransaction === obj6.unsafeDataTransaction")
detachedTransaction.commit { (result) -> Void in
unsafeTransaction.commit { (result) -> Void in
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
switch result {
@@ -358,7 +360,7 @@ class CoreStoreTests: XCTestCase {
)
XCTAssertTrue(count2 == 0, "count == 0 (actual: \(count2))")
detachedExpectation.fulfill()
unsafeExpectation.fulfill()
case .Failure(let error):
XCTFail(error.description)
@@ -377,8 +379,9 @@ class CoreStoreTests: XCTestCase {
do {
try NSFileManager.defaultManager().removeItemAtURL(
NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first!
let fileManager = NSFileManager.defaultManager()
try fileManager.removeItemAtURL(
fileManager.URLsForDirectory(deviceDirectorySearchPath, inDomains: .UserDomainMask).first!
)
}
catch _ { }

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14D136" minimumToolsVersion="Xcode 4.3" macOSVersion="Automatic" iOSVersion="Automatic">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Xcode 4.3">
<entity name="TestEntity1AAA" representedClassName="CoreStoreTests.TestEntity1" syncable="YES">
<attribute name="testDate" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="testEntityID" attributeType="Integer 64" syncable="YES"/>

Submodule Libraries/GCDKit deleted from 154d4ccc64

252
README.md
View File

@@ -1,11 +1,12 @@
# CoreStore
[![Build Status](https://img.shields.io/travis/JohnEstropia/CoreStore/master.svg)](https://travis-ci.org/JohnEstropia/CoreStore)
[![Version](https://img.shields.io/cocoapods/v/CoreStore.svg?style=flat)](http://cocoadocs.org/docsets/CoreStore)
[![Platform](https://img.shields.io/cocoapods/p/CoreStore.svg?style=flat)](http://cocoadocs.org/docsets/CoreStore)
[![License](https://img.shields.io/cocoapods/l/CoreStore.svg?style=flat)](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE)
[![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage)
Unleashing the real power of Core Data with the elegance and safety of Swift
(Swift 2.0, iOS 8+)
* Swift 2.1 (XCode 7.1), iOS 8+/OSX 10.10+ (or try out the [iOS 7 branch](https://github.com/JohnEstropia/CoreStore/tree/ios7_support_alpha))
[Click here for a wiki version of this README](https://github.com/JohnEstropia/CoreStore/wiki)
@@ -14,7 +15,7 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
## What CoreStore does better:
- Heavily supports multiple persistent stores per data stack, just the way *.xcdatamodeld* files are designed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need.
- **New in 1.0.0:** Incremental Migrations! Just tell the data stack the sequence of model versions and CoreStore will automatically use incremental migrations if needed on stores added to that stack.
- Incremental Migrations! Just tell the data stack the sequence of model versions and CoreStore will automatically use incremental migrations if needed on stores added to that stack.
- Ability to plug-in your own logging framework
- Gets around a limitation with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you are free to name them independently.
- Provides type-safe, easy to configure observers to replace `NSFetchedResultsController` and KVO
@@ -22,8 +23,9 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
- Makes it hard to fall into common concurrency mistakes. All `NSManagedObjectContext` tasks are encapsulated into safer, higher-level abstractions without sacrificing flexibility and customizability.
- Exposes clean and convenient API designed around Swifts code elegance and type safety.
- Documentation! No magic here; all public classes, functions, properties, etc. have detailed Apple Docs. This README also introduces a lot of concepts and explains a lot of CoreStore's behavior.
- **New in 1.3.0:** Efficient importing utilities!
**CoreStore's goal is not to expose shorter, magical syntax, but to provide an API that focuses on readability, consistency, and safety.**
**[Or vote for the next feature!](http://goo.gl/RIiHMP)**
@@ -40,10 +42,12 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
- [Transaction types](#transaction-types)
- [Asynchronous transactions](#asynchronous-transactions)
- [Synchronous transactions](#synchronous-transactions)
- [Detached transactions](#detached-transactions)
- [Unsafe transactions](#unsafe-transactions)
- [Creating objects](#creating-objects)
- [Updating objects](#updating-objects)
- [Deleting objects](#deleting-objects)
- [Passing objects safely](#passing-objects-safely)
- [Importing data](#importing-data)
- [Fetching and querying](#fetching-and-querying)
- [`From` clause](#from-clause)
- [Fetching](#fetching)
@@ -54,7 +58,7 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
- [`Select<T>` clause](#selectt-clause)
- [`GroupBy` clause](#groupby-clause)
- [Logging and error handling](#logging-and-error-handling)
- [Observing changes and notifications](#observing-changes-and-notifications)
- [Observing changes and notifications](#observing-changes-and-notifications) (unavailable on OSX)
- [Observe a single object](#observe-a-single-object)
- [Observe a list of objects](#observe-a-list-of-objects)
- [Roadmap](#roadmap)
@@ -143,7 +147,7 @@ If you are already familiar with the inner workings of CoreData, here is a mappi
| --- | --- |
| `NSManagedObjectModel` / `NSPersistentStoreCoordinator`<br />(.xcdatamodeld file) | `DataStack` |
| `NSPersistentStore`<br />("Configuration"s in the .xcdatamodeld file) | `DataStack` configuration<br />(multiple sqlite / in-memory stores per stack) |
| `NSManagedObjectContext` | `BaseDataTransaction` subclasses<br />(`SynchronousDataTransaction`, `AsynchronousDataTransaction`, `DetachedDataTransaction`) |
| `NSManagedObjectContext` | `BaseDataTransaction` subclasses<br />(`SynchronousDataTransaction`, `AsynchronousDataTransaction`, `UnsafeDataTransaction`) |
Popular libraries [RestKit](https://github.com/RestKit/RestKit) and [MagicalRecord](https://github.com/magicalpanda/MagicalRecord) set up their `NSManagedObjectContext`s this way:
@@ -170,7 +174,7 @@ catch {
This one-liner does the following:
- Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack`
- Sets up the stack's `NSPersistentStoreCoordinator`, the root saving `NSManagedObjectContext`, and the read-only main `NSManagedObjectContext`
- Adds an SQLite store in the *"Application Support"* directory with the file name *"[App bundle name].sqlite"*
- Adds an SQLite store in the *"Application Support"* directory (or the *"Caches"* directory on tvOS) with the file name *"[App bundle name].sqlite"*
- Creates and returns the `NSPersistentStore` instance on success, or an `NSError` on failure
For most cases, this configuration is usable as it is. But for more hardcore settings, refer to this extensive example:
@@ -252,7 +256,7 @@ class MyViewController: UIViewController {
## Migrations
So far we have only seen `addSQLiteStoreAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method will block, even if a migration occurs. If migrations are expected, the asynchronous variant `addSQLiteStore(... completion:)` method is recommended:
So far we have only seen `addSQLiteStoreAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method blocks so it should not do long tasks such as store migrations (in fact CoreStore won't even attempt to, and any model mismatch will be reported as an error). If migrations are expected, the asynchronous variant `addSQLiteStore(... completion:)` method should be used instead:
```swift
do {
let progress: NSProgress = try dataStack.addSQLiteStore(
@@ -373,7 +377,7 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
```
The `commit()` method saves the changes to the persistent store. If `commit()` is not called when the transaction block completes, all changes within the transaction is discarded.
The examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at your disposal: *asynchronous*, *synchronous*, and *detached*.
The examples above use `beginAsynchronous(...)`, but there are actually 3 types of transactions at your disposal: *asynchronous*, *synchronous*, and *unsafe*.
### Transaction types
@@ -399,10 +403,10 @@ CoreStore.beginSynchronous { (transaction) -> Void in
Since `beginSynchronous(...)` technically blocks two queues (the caller's queue and the transaction's background queue), it is considered less safe as it's more prone to deadlock. Take special care that the closure does not block on any other external queues.
#### Detached transactions
#### Unsafe transactions
are special in that they do not enclose updates within a closure:
```swift
let transaction = CoreStore.beginDetached()
let transaction = CoreStore.beginUnsafe()
// make changes
downloadJSONWithCompletion({ (json) -> Void in
@@ -417,7 +421,7 @@ downloadAnotherJSONWithCompletion({ (json) -> Void in
```
This allows for non-contiguous updates. Do note that this flexibility comes with a price: you are now responsible for managing concurrency for the transaction. As uncle Ben said, "with great power comes great race conditions."
As the above example also shows, only detached transactions are allowed to call `commit()` multiple times; doing so with synchronous and asynchronous transactions will trigger an assert.
As the above example also shows, only unsafe transactions are allowed to call `commit()` multiple times; doing so with synchronous and asynchronous transactions will trigger an assert.
You've seen how to create transactions, but we have yet to see how to make *creates*, *updates*, and *deletes*. The 3 types of transactions above are all subclasses of `BaseDataTransaction`, which implements the methods shown below.
@@ -475,7 +479,7 @@ let jane: MyPersonEntity = // ...
CoreStore.beginAsynchronous { (transaction) -> Void in
// WRONG: jane.age = jane.age + 1
// RIGHT:
let jane = transaction.edit(jane) // using the same variable name protects us from misusing the non-transaction instance
let jane = transaction.edit(jane)! // using the same variable name protects us from misusing the non-transaction instance
jane.age = jane.age + 1
transaction.commit()
}
@@ -488,9 +492,9 @@ let john: MyPersonEntity = // ...
CoreStore.beginAsynchronous { (transaction) -> Void in
// WRONG: jane.friends = [john]
// RIGHT:
let jane = transaction.edit(jane)
let john = transaction.edit(john)
jane.friends = [john]
let jane = transaction.edit(jane)!
let john = transaction.edit(john)!
jane.friends = NSSet(array: [john])
transaction.commit()
}
```
@@ -528,6 +532,215 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
}
```
### Passing objects safely
Always remember that the `DataStack` and individual transactions manage different `NSManagedObjectContext`s so you cannot just use objects between them. That's why transactions have an `edit(...)` method:
```swift
let jane: MyPersonEntity = // ...
CoreStore.beginAsynchronous { (transaction) -> Void in
let jane = transaction.edit(jane)!
jane.age = jane.age + 1
transaction.commit()
}
```
But `CoreStore`, `DataStack` and `BaseDataTransaction` have a very flexible `fetchExisting(...)` method that you can pass instances back and forth with:
```swift
let jane: MyPersonEntity = // ...
CoreStore.beginAsynchronous { (transaction) -> Void in
let jane = transaction.fetchExisting(jane)! // instance for transaction
jane.age = jane.age + 1
transaction.commit {
let jane = CoreStore.fetchExisting(jane)! // instance for DataStack
print(jane.age)
}
}
```
`fetchExisting(...)` also works with multiple `NSManagedObject`s or with `NSManagedObjectID`s:
```swift
var peopleIDs: [NSManagedObjectID] = // ...
CoreStore.beginAsynchronous { (transaction) -> Void in
let jane = transaction.fetchOne(
From(MyPersonEntity),
Where("name", isEqualTo: "Jane Smith")
)
jane.friends = NSSet(array: transactin.fetchExisting(peopleIDs)!)
// ...
}
```
## Importing data
Some times, if not most of the time, the data that we save to Core Data comes from external sources such as web servers or external files. Say you have a JSON dictionary, you may be extracting values as such:
```swift
let json: [String: AnyObject] = // ...
person.name = json["name"] as? NSString
person.age = json["age"] as? NSNumber
// ...
```
If you have many attributes, you don't want to keep repeating this mapping everytime you want to import data. CoreStore lets you write the data mapping code just once, and all you have to do is call `importObject(...)` or `importUniqueObject(...)` through `BaseDataTransaction` subclasses:
```swift
CoreStore.beginAsynchronous { (transaction) -> Void in
let json: [String: AnyObject] = // ...
try! transaction.importObject(
Into(MyPersonEntity),
source: json
)
transaction.commit()
}
```
To support data import for an entity, implement either `ImportableObject` or `ImportableUniqueObject` on the `NSManagedObject` subclass:
- `ImportableObject`: Use this protocol if the object have no inherent uniqueness and new objects should always be added when calling `importObject(...)`.
- `ImportableUniqueObject`: Use this protocol to specify a unique ID for an object that will be used to distinguish whether a new object should be created or if an existing object should be updated when calling `importUniqueObject(...)`.
Both protocols require implementers to specify an `ImportSource` which can be set to any type that the object can extract data from:
```swift
typealias ImportSource = NSDictionary
```
```swift
typealias ImportSource = [String: AnyObject]
```
```swift
typealias ImportSource = NSData
```
You can even use external types from popular 3rd-party JSON libraries ([SwiftyJSON](https://github.com/SwiftyJSON/SwiftyJSON)'s `JSON` type is a personal favorite), or just simple tuples or primitives.
#### `ImportableObject`
`ImportableObject` is a very simple protocol:
```swift
public protocol ImportableObject: class {
typealias ImportSource
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
}
```
First, set `ImportSource` to the expected type of the data source:
```swift
typealias ImportSource = [String: AnyObject]
```
This lets us call `importObject(_:source:)` with any `[String: AnyObject]` type as the argument to `source`:
```swift
CoreStore.beginAsynchronous { (transaction) -> Void in
let json: [String: AnyObject] = // ...
try! transaction.importObject(
Into(MyPersonEntity),
source: json
)
// ...
}
```
The actual extraction and assignment of values should be implemented in the `didInsertFromImportSource(...)` method of the `ImportableObject` protocol:
```swift
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
self.name = source["name"] as? NSString
self.age = source["age"] as? NSNumber
// ...
}
```
Transactions also let you import multiple objects at once using the `importObjects(_:sourceArray:)` method:
```swift
CoreStore.beginAsynchronous { (transaction) -> Void in
let jsonArray: [[String: AnyObject]] = // ...
try! transaction.importObjects(
Into(MyPersonEntity),
sourceArray: jsonArray
)
// ...
}
```
Doing so tells the transaction to iterate through the array of import sources and calls `shouldInsertFromImportSource(...)` on the `ImportableObject` to determine which instances should be created. You can do validations and return `false` from `shouldInsertFromImportSource(...)` if you want to skip importing from a source and continue on with the other sources in the array.
If on the other hand, your validation in one of the sources failed in such a manner that all other sources should also be cancelled, you can `throw` from within `didInsertFromImportSource(...)`:
```swift
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
self.name = source["name"] as? NSString
self.age = source["age"] as? NSNumber
// ...
if self.name == nil {
throw Errors.InvalidNameError
}
}
```
Doing so can let you abandon an invalid transaction immediately:
```swift
CoreStore.beginAsynchronous { (transaction) -> Void in
let jsonArray: [[String: AnyObject]] = // ...
do {
try transaction.importObjects(
Into(MyPersonEntity),
sourceArray: jsonArray
)
}
catch {
return // Woops, don't save
}
transaction.commit {
// ...
}
}
```
#### `ImportableUniqueObject`
Typically, we don't just keep creating objects every time we import data. Usually we also need to update already existing objects. Implementing the `ImportableUniqueObject` protocol lets you specify a "unique ID" that transactions can use to search existing objects before creating new ones:
```swift
public protocol ImportableUniqueObject: ImportableObject {
typealias ImportSource
typealias UniqueIDType: NSObject
static var uniqueIDKeyPath: String { get }
var uniqueIDValue: UniqueIDType { get set }
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
static func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> UniqueIDType?
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
func updateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
}
```
Notice that it has the same insert methods as `ImportableObject`, with additional methods for updates and for specifying the unique ID:
```swift
class var uniqueIDKeyPath: String {
return "personID"
}
var uniqueIDValue: NSNumber {
get { return self.personID }
set { self.personID = newValue }
}
class func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> NSNumber? {
return source["id"] as? NSNumber
}
```
For `ImportableUniqueObject`, the extraction and assignment of values should be implemented from the `updateFromImportSource(...)` method. The `didInsertFromImportSource(...)` by default calls `updateFromImportSource(...)`, but you can separate the implementation for inserts and updates if needed.
You can then call create/update an object by calling a transaction's `importUniqueObject(...)` method:
```swift
CoreStore.beginAsynchronous { (transaction) -> Void in
let json: [String: AnyObject] = // ...
try! transaction.importUniqueObject(
Into(MyPersonEntity),
source: json
)
// ...
}
```
or multiple objects at once with the `importUniqueObjects(...)` method:
```swift
CoreStore.beginAsynchronous { (transaction) -> Void in
let jsonArray: [[String: AnyObject]] = // ...
try! transaction.importObjects(
Into(MyPersonEntity),
sourceArray: jsonArray
)
// ...
}
```
As with `ImportableObject`, you can control wether to skip importing an object by implementing
`shouldInsertFromImportSource(...)` and `shouldUpdateFromImportSource(...)`, or to cancel all objects by `throw`ing an error from the `uniqueIDFromImportSource(...)`, `didInsertFromImportSource(...)` or `updateFromImportSource(...)` methods.
## Fetching and Querying
Before we dive in, be aware that CoreStore distinguishes between *fetching* and *querying*:
- A *fetch* executes searches from a specific *transaction* or *data stack*. This means fetches can include pending objects (i.e. before a transaction calls on `commit()`.) Use fetches when:
@@ -812,7 +1025,7 @@ Doing so channels all logging calls to your logger.
Note that to keep the call stack information intact, all calls to these methods are **NOT** thread-managed. Therefore you have to make sure that your logger is thread-safe or you may otherwise have to dispatch your logging implementation to a serial queue.
## Observing changes and notifications
## Observing changes and notifications (unavailable on OSX)
CoreStore provides type-safe wrappers for observing managed objects:
- `ObjectMonitor`: use to monitor changes to a single `NSManagedObject` instance (instead of Key-Value Observing)
@@ -956,7 +1169,7 @@ let person2 = self.monitor[1, 2]
# Installation
- Requires:
- iOS 8 SDK and above
- Swift 1.2
- Swift 2.0 (XCode 7 beta 6)
- Dependencies:
- [GCDKit](https://github.com/JohnEstropia/GCDKit)
@@ -968,7 +1181,8 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
### Install with Carthage
```
github "JohnEstropia/CoreStore" >= 0.2.0
github "JohnEstropia/CoreStore" >= 1.3.0
github "JohnEstropia/GCDKit" >= 1.1.5
```
### Install as Git Submodule