Compare commits

...

230 Commits

Author SHA1 Message Date
John Estropia
cff2bb1740 version bump 2016-10-27 19:16:17 +09:00
John Estropia
b955495012 Turn off whole-module-optimization to prevent Swift compiler bugs (#113) 2016-10-27 18:30:35 +09:00
John Estropia
ffea06ee7e try again 2016-09-30 19:16:14 +09:00
John Estropia
a92d6cac02 try again 2016-09-30 19:08:17 +09:00
John Estropia
de5d660257 try again 2016-09-30 17:53:15 +09:00
John Estropia
55b2e6eecd try again 2016-09-30 17:36:48 +09:00
John Estropia
b64c776335 try again 2016-09-30 17:28:02 +09:00
John Estropia
432af667e8 try again 2016-09-30 17:08:04 +09:00
John Estropia
cf60a4bc2e updated travis.yml 2016-09-30 17:00:11 +09:00
John Estropia
c620859899 run demo project on CI 2016-09-30 13:58:11 +09:00
John Estropia
65ac069a0b skip observer testing on macOS 2016-09-30 13:32:31 +09:00
John Estropia
862ef27374 version bump, cleanup, unit test 2016-09-30 13:28:19 +09:00
John Estropia
243b6a76d5 Merge pull request #104 from colinmorelli/feature/sync-transaction-async-merge
Fixes #98 (deadlock when merge happens while main queue is querying)
2016-09-30 12:38:28 +09:00
Colin Morelli
8be20370d5 Fixes #98 (deadlock when merge happens while main queue is querying) 2016-09-29 14:47:51 -04:00
John Estropia
ed8c7b35e8 Reduce leaking (a little) on the workaround for NSFetchRequest.affectedStores ARC bug 2016-09-27 17:31:08 +09:00
John Rommel Estropia
4d2ebe4ea8 workaround #100 (NSFetchRequest.affectedStores ARC bug) 2016-09-27 02:39:28 +09:00
John Rommel Estropia
f18d62f643 travis yml update 2016-09-17 14:27:36 +09:00
John Rommel Estropia
af141d4a31 travis yml update 2016-09-17 14:26:33 +09:00
John Rommel Estropia
2da659a967 travis yml update 2016-09-17 14:14:29 +09:00
John Rommel Estropia
e5f162c5e1 minor readme edits 2016-09-17 13:59:04 +09:00
John Rommel Estropia
effa231719 fix travis 2016-09-17 13:37:00 +09:00
John Rommel Estropia
6cef8f4b4f updated travis yml 2016-09-17 13:23:56 +09:00
John Rommel Estropia
aa6bceaaf3 working for Swift 2.3! 2016-09-17 12:22:25 +09:00
John Rommel Estropia
0dbd05b172 version bump 2016-09-11 14:30:32 +09:00
John Rommel Estropia
243c4044ab fix bridging producing base abstract class instead of subclass concrete class 2016-09-11 14:30:25 +09:00
John Rommel Estropia
df835114cb ignore errors when deleting wal files 2016-09-10 22:57:45 +09:00
John Rommel Estropia
f99d3cc21a fix RecreateStoreOnModelMismatch option not working when an existing xcdatamodel gets updated without adding a new version 2016-09-10 22:51:33 +09:00
John Rommel Estropia
4a34012d58 version bump 2016-09-08 00:40:58 +09:00
John Estropia
45690a29c6 Merge pull request #92 from ThibaultVlacich/develop
Add DEBUG flag to the Debug config
2016-09-07 19:01:19 +09:00
Thibault Vlacich
0d4d036a86 Add DEBUG flag to the Debug config 2016-09-07 10:43:43 +02:00
John Estropia
ed0fdc76fe update podspec 2016-09-06 11:19:38 +09:00
John Estropia
58f4907575 Prevent retain cycles in NSManagedObjectContext (fixes #87) 2016-09-06 11:13:16 +09:00
John Rommel Estropia
1950224863 version bump 2016-07-22 00:33:15 +09:00
John Rommel Estropia
f0cd288657 allow querying on relationship attributes (fixes #83) 2016-07-22 00:29:48 +09:00
John Estropia
3344e42d7c version bump 2016-07-21 11:51:57 +09:00
John Estropia
e4b6c06401 fixed internal errors getting thrown as .Unknown instead of .InternalError (fixes #84) 2016-07-21 11:51:49 +09:00
John Estropia
7fc3ad2890 Tidy up, version bump 2016-07-14 19:59:15 +09:00
John Estropia
556f6d13b3 Merge pull request #81 from jannon/mark-app-extension-safe
Mark app extension safe
2016-07-14 19:41:55 +09:00
Jannon Frank
a088fe3817 mark iOS and tvOS safe for app extensions 2016-07-12 18:39:23 -07:00
Jannon Frank
c4391a8d0c Merge remote-tracking branch 'JohnEstropia/master' into upstream 2016-07-12 18:36:53 -07:00
John Rommel Estropia
f486ace437 make cocoapods happy 2016-07-12 00:16:02 +09:00
John Estropia
edb2aa2463 Update README.md 2016-07-11 13:40:26 +09:00
John Estropia
7b48d825a3 Update README.md 2016-07-11 13:38:21 +09:00
John Rommel Estropia
921b85d91b documentation updates 2016-07-11 02:04:18 +09:00
John Estropia
4f46cbded9 Update README.md 2016-07-11 02:01:24 +09:00
John Estropia
a8af91b2f8 Update README.md 2016-07-11 00:55:01 +09:00
John Estropia
8a8b3fa0b1 Update README.md 2016-07-10 23:01:53 +09:00
John Estropia
4196ed8085 Update README.md 2016-07-10 15:49:08 +09:00
John Estropia
0458de5d65 Update README.md 2016-07-10 15:37:52 +09:00
John Estropia
90ae17e904 Update README.md
WIP
2016-07-10 15:31:59 +09:00
John Estropia
3c0d4d648d Update README.md
WIP
2016-07-09 18:44:26 +09:00
John Estropia
b60bcf8da6 Update README.md 2016-07-09 17:21:47 +09:00
John Rommel Estropia
f9014e65e0 objc keypath utilities 2016-07-01 01:06:16 +09:00
John Rommel Estropia
82887b1dd2 CSInto unit tests 2016-06-25 13:43:43 +09:00
John Estropia
11d428c05c import magic for objective c utils 2016-06-20 20:28:25 +09:00
John Estropia
0b48bb3347 renamed CSFromCreate() to CSFromClass() 2016-06-20 19:36:57 +09:00
John Estropia
aa5cd51da6 Merge branch 'develop' into corestore2_develop 2016-06-20 19:18:42 +09:00
John Estropia
e50dd9881d allow accessing a ListMonitor as a flat array when using API's without section info 2016-06-20 19:18:31 +09:00
John Rommel Estropia
3ccbce5c29 WIP: utilities for clauses 2016-06-20 08:09:11 +09:00
John Rommel Estropia
e5a199489c WIP: objc utilities 2016-06-19 02:40:25 +09:00
John Rommel Estropia
3d5c4f8121 removed unit test check for undefined behavior 2016-06-17 02:44:37 +09:00
John Rommel Estropia
2dc09289bf fixed platform dependencies 2016-06-17 01:24:19 +09:00
John Rommel Estropia
76a2bc1da2 importing unit tests 2016-06-12 20:34:13 +09:00
John Rommel Estropia
8e5c7ec9b2 querying unit tests 2016-06-11 22:57:00 +09:00
John Rommel Estropia
101ab69861 added testing for logger behaviors 2016-06-11 21:13:06 +09:00
John Rommel Estropia
ea2a65cf57 Merge branch 'corestore2_develop' of github.com:JohnEstropia/CoreStore into corestore2_develop 2016-06-11 19:10:12 +09:00
John Rommel Estropia
f59b1b6320 WIP: query tests 2016-06-11 19:09:56 +09:00
John Estropia
141e977f9c remove sort assertion when using ObjectMonitor 2016-06-09 18:56:32 +09:00
John Estropia
788cce0df6 Merge branch 'corestore2_develop' of https://github.com/JohnEstropia/CoreStore into corestore2_develop
# Conflicts:
#	Sources/Observing/ListMonitor.swift
2016-06-09 18:23:59 +09:00
John Estropia
4304d8687e Merge branch 'develop' into corestore2_develop 2016-06-09 18:20:58 +09:00
John Estropia
d25f751089 oops 2016-06-09 18:20:49 +09:00
John Estropia
6df78a0595 Merge branch 'develop' into corestore2_develop
# Conflicts:
#	Sources/Observing/ListMonitor.swift
2016-06-09 18:12:18 +09:00
John Estropia
c0ed2f23ce ignore negative indexes when using ListMonitor's safeIndex APIs 2016-06-09 18:06:08 +09:00
John Estropia
dcf562fb26 ignore negative indexes when using ListMonitor's safeIndex API 2016-06-09 17:57:15 +09:00
John Rommel Estropia
b9353238e8 transaction fetching unit tests 2016-06-07 00:14:22 +09:00
John Rommel Estropia
b98805e489 WIP: Fetching unit tests 2016-06-06 00:12:00 +09:00
John Rommel Estropia
02a89accc8 Merge branch 'develop' into corestore2_develop
# Conflicts:
#	CoreStore.podspec
#	Sources/Info.plist
#	Sources/Internal/CoreStoreFetchRequest.swift
2016-06-05 17:20:59 +09:00
John Rommel Estropia
2e4c9a7a25 fix From clauses not setting the NSFetchRequest's affectedStores property properly 2016-06-05 17:19:20 +09:00
John Rommel Estropia
fcca6b205e WIP: Unit tests 2016-06-04 14:29:10 +09:00
John Rommel Estropia
b199f38b0c Merge branch 'master' into corestore2_develop
# Conflicts:
#	CoreStore.podspec
#	Sources/Info.plist
2016-05-28 22:40:01 +09:00
John Rommel Estropia
0c8731c610 improved flushing behavior 2016-05-28 22:34:14 +09:00
John Rommel Estropia
ee4eb178ed Merge branch 'master' into corestore2_develop
# Conflicts:
#	CoreStore.podspec
#	Sources/Convenience/NSFetchedResultsController+Convenience.swift
#	Sources/Info.plist
2016-05-28 11:46:31 +09:00
John Rommel Estropia
f617f2cc53 Simpler API for creating CoreStore-managed NSFetchedResultsControllers, useful for external SDK's that require them 2016-05-28 01:47:48 +09:00
John Rommel Estropia
f396b01043 fixed Cartfile dependency 2016-05-26 01:28:26 +09:00
John Rommel Estropia
7942cf411e WIP: clauses unit tests 2016-05-26 01:03:01 +09:00
John Rommel Estropia
02be72b0b7 Merge branch 'master' into corestore2_develop
# Conflicts:
#	CoreStore.podspec
#	Sources/Info.plist
#	Sources/Transactions/UnsafeDataTransaction.swift
2016-05-25 21:41:44 +09:00
John Rommel Estropia
e38093342d version bump, updated dependendencies 2016-05-24 00:42:35 +09:00
John Rommel Estropia
c15afcb381 Allow flushing UnsafeDataTransactions to notify its ListMonitors and ObjectMonitors without saving changes to the DataStack (fixes #71) 2016-05-24 00:40:13 +09:00
John Rommel Estropia
a97001cdbb fixed scheme 2016-05-24 00:32:54 +09:00
John Rommel Estropia
000a357808 WIP: test cases 2016-05-23 23:40:00 +09:00
John Estropia
c8ca16a982 Merge pull request #69 from bretsko/master
fixed Readme
2016-05-17 14:05:11 +09:00
Oleksandr Bretsko
9e0493a20d fixed Readme
replaced importObjects with importUniqueObjects
2016-05-17 08:43:40 +04:00
John Estropia
77236d66d5 relax NSManagedObject return types so callers don't need to cast every time 2016-05-11 21:55:51 +09:00
John Estropia
8b95d337a3 relax NSManagedObject return types so callers don't need to cast every time 2016-05-10 21:49:50 +09:00
John Rommel Estropia
57b66cff34 implementations for desctiption and debugDescription complete for all CoreStore types 2016-05-08 20:30:28 +09:00
John Rommel Estropia
9dae291f62 WIP: logging clauses 2016-05-08 00:28:36 +09:00
John Rommel Estropia
0c2a3ac4e9 fix merge 2016-05-07 18:13:44 +08:00
John Rommel Estropia
a6291e116c Merge branch 'master' into corestore2_develop
# Conflicts:
#	CoreStore.podspec
#	Sources/Info.plist
#	Sources/Internal/NSManagedObjectContext+Transaction.swift
2016-05-07 17:47:35 +08:00
John Rommel Estropia
c990f7764a version bump 2016-05-07 12:19:30 +08:00
John Rommel Estropia
26ae6293ca merge changes to main context synchronously or asynchronously depending on the caller intention (fixes #65) 2016-05-07 12:18:54 +08:00
John Rommel Estropia
b9a2f96d6e renamed refreshAllObjectsAsFaults to refreshAndMergeAllObjects, which is more correct 2016-05-07 12:04:59 +08:00
John Rommel Estropia
ae5f737baf WIP: logging clauses 2016-05-07 12:01:31 +08:00
John Rommel Estropia
4eecd80710 WIP: pretty logging 2016-05-06 09:19:24 +08:00
John Rommel Estropia
0073d038e0 WIP: logging utilities 2016-05-05 09:44:14 +08:00
John Rommel Estropia
099dcfab68 Merge branch 'develop' into corestore2_develop
# Conflicts:
#	CoreStore.podspec
#	Sources/Info.plist
2016-05-02 18:22:43 +08:00
John Rommel Estropia
74fe81bfa7 Fix problem when using carthage where the iOS7 framework overwrites the iOS 8 framework 2016-05-02 12:02:31 +08:00
John Rommel Estropia
0bbb4118a1 documentation for iCloud methods 2016-04-30 01:20:35 +09:00
John Rommel Estropia
3fe9e4ee1d WIP: ICloudStore prototype 2016-04-30 00:26:06 +09:00
John Rommel Estropia
bd19326447 folder structure 2016-04-03 19:55:10 +09:00
John Rommel Estropia
dccc958ef1 comment documentations 2016-04-03 19:47:25 +09:00
John Rommel Estropia
ab3095f078 WIP: documentation 2016-04-03 13:37:00 +09:00
John Rommel Estropia
1a86510045 Group Swift files 2016-04-03 12:20:13 +09:00
John Rommel Estropia
aa32f5e158 support value querying in Objective C 2016-04-02 21:39:05 +09:00
John Rommel Estropia
b95aaf151d Merge branch 'corestore2_develop' into corestore2_develop_objc 2016-04-01 07:47:03 +09:00
John Rommel Estropia
114bc71841 Merge branch 'develop' into corestore2_develop
# Conflicts:
#	CoreStore.podspec
#	Sources/Info.plist
2016-04-01 07:46:42 +09:00
John Rommel Estropia
dff552b95f version bump 2016-04-01 07:42:00 +09:00
John Rommel Estropia
88cac73b20 fix assertion error 2016-04-01 07:41:23 +09:00
John Rommel Estropia
83d0412cb9 Merge branch 'corestore2_develop' into corestore2_develop_objc
# Conflicts:
#	CoreStore.xcodeproj/project.pbxproj
2016-04-01 07:20:42 +09:00
John Rommel Estropia
26e4118355 Merge branch 'develop' into corestore2_develop
# Conflicts:
#	CoreStore.podspec
#	CoreStore.xcodeproj/project.pbxproj
#	CoreStore/Setting Up/DataStack.swift
#	Sources/Info.plist
2016-04-01 07:18:53 +09:00
John Rommel Estropia
0c564add46 separate CoreStoreFetchRequest file 2016-04-01 07:04:02 +09:00
John Rommel Estropia
784a315fb9 version bump 2016-04-01 01:32:32 +09:00
John Rommel Estropia
44cfbebedb prevent deadlock on when DataStack gets deallocated 2016-04-01 01:28:39 +09:00
John Rommel Estropia
0b24072259 Workaround CoreData's bug when using NSFetchRequest's affectedStores property 2016-04-01 01:22:16 +09:00
John Rommel Estropia
28b7ba01dc prevent deadlock on when DataStack gets deallocated 2016-03-31 00:04:17 +09:00
John Rommel Estropia
b529735269 Merge branch 'corestore2_develop' into corestore2_develop_objc
# Conflicts:
#	Sources/Observing/ListMonitor.swift
2016-03-30 21:41:44 +09:00
John Rommel Estropia
d23121d8ed Merge branch 'develop' into corestore2_develop
# Conflicts:
#	Sources/Observing/ListMonitor.swift
2016-03-30 21:40:21 +09:00
John Rommel Estropia
633ab0a249 cache fetchClauses in property so closures do not retain them indeterminately 2016-03-30 21:16:10 +09:00
John Rommel Estropia
410feda5cd tidy up 2016-03-30 20:47:13 +09:00
John Rommel Estropia
06e952af8a clear FRC delegate on deinit 2016-03-30 20:28:10 +09:00
John Estropia
a4d3d0c0ed fix mistype 2016-03-30 13:05:29 +09:00
John Estropia
8b7af86526 WIP: Objective-C bridge (90% done!) 2016-03-30 11:12:17 +09:00
John Estropia
48a8694720 attempt to fix closure deallocation bug (#58) 2016-03-30 05:22:05 +09:00
John Rommel Estropia
09d844f5df WIP: CSSetupResult 2016-03-30 01:55:06 +09:00
John Estropia
e25e198bf8 WIP: Objective-C migrations 2016-03-29 14:46:31 +09:00
John Estropia
e99d19d2ac ListMonitor and ObjectMonitor objective C bridge 2016-03-29 14:15:57 +09:00
John Estropia
9d7960e674 typed arrays 2016-03-28 19:41:11 +09:00
John Estropia
e9ac8629a1 tidy up 2016-03-28 19:35:09 +09:00
John Estropia
b0b0df2861 allow public access to bridgeToObjectiveC and bridgeToSwift properties 2016-03-28 19:07:35 +09:00
John Estropia
eda398d758 WIP: CSListMonitor 2016-03-28 17:19:51 +09:00
John Rommel Estropia
40c320e1ca add missing files in scheme 2016-03-28 00:43:14 +09:00
John Rommel Estropia
8508dd4f79 delete iCloud folder 2016-03-27 23:13:01 +09:00
John Rommel Estropia
d3e0f576cf tidy up 2016-03-27 23:12:21 +09:00
John Rommel Estropia
1d5cf5a4cc Merge branch 'corestore2_develop' into corestore2_develop_objc 2016-03-27 23:03:37 +09:00
John Rommel Estropia
9fc0390b45 Merge branch 'develop' into corestore2_develop 2016-03-27 23:03:21 +09:00
John Rommel Estropia
b8ea7ecf01 WIP: objective-C fetching 2016-03-27 23:02:24 +09:00
John Estropia
11ac362dcc Update README.md 2016-03-27 20:32:14 +09:00
John Estropia
10c20e0d62 Update README.md 2016-03-27 10:28:35 +09:00
John Estropia
789028bc58 WIP: CSImportableUniqueObject 2016-03-25 19:59:31 +09:00
John Estropia
a168ca577a Merge branch 'corestore2_develop' into corestore2_develop_objc 2016-03-25 19:28:47 +09:00
John Estropia
781f3988d2 Merge branch 'develop' into corestore2_develop
# Conflicts:
#	CoreStoreDemo/CoreStoreDemo/Loggers Demo/CustomLoggerViewController.swift
#	CoreStoreDemo/CoreStoreDemo/Transactions Demo/TransactionsDemoViewController.swift
2016-03-25 19:28:37 +09:00
John Estropia
b914a4b5ed updated Readme 2016-03-25 18:20:24 +09:00
John Estropia
7de13131a4 small demo fix 2016-03-25 18:18:17 +09:00
John Estropia
0c1af09a8d Make demo app compile again (sorry) (fixes #57) 2016-03-25 18:08:03 +09:00
John Estropia
1ff635d8b5 WIP: CSImportableObject 2016-03-25 17:57:26 +09:00
John Rommel Estropia
707445a169 WIP: SaveResult bridge 2016-03-25 07:57:09 +09:00
John Estropia
90369cf994 WIP: simpler bridging 2016-03-24 21:22:58 +09:00
John Estropia
09708e587c Merge branch 'corestore2_develop' into corestore2_develop_objc
# Conflicts:
#	CoreStore.xcodeproj/project.pbxproj
2016-03-24 15:16:13 +09:00
John Estropia
36e6e4a8b9 Merge branch 'develop' into corestore2_develop
# Conflicts:
#	Cartfile
#	Cartfile.resolved
#	CoreStore.podspec
#	CoreStore.xcodeproj/project.pbxproj
#	CoreStore/Migrating/DataStack+Migration.swift
#	CoreStore/Setting Up/DataStack.swift
#	README.md
#	Sources/Info.plist
2016-03-24 15:09:36 +09:00
John Rommel Estropia
9f1a351311 add missing scheme 2016-03-23 23:20:24 +09:00
John Rommel Estropia
6e202aa7ca set missing build setting 2016-03-23 22:31:16 +09:00
John Rommel Estropia
783b933294 add missing file 2016-03-23 22:26:39 +09:00
John Rommel Estropia
ca49ea3a81 iOS 7 full support 2016-03-23 22:21:05 +09:00
John Rommel Estropia
ed8891a6d5 Merge branch 'corestore2_develop' into corestore2_develop_objc
# Conflicts:
#	Sources/Migrating/MigrationChain.swift
2016-03-23 21:24:30 +09:00
John Rommel Estropia
dd6c6abaf0 Merge branch 'develop' into corestore2_develop
# Conflicts:
#	CoreStore.podspec
#	CoreStore/CartFile
#	README.md
#	Sources/Info.plist
#	Sources/Internal/NSManagedObjectModel+Setup.swift
#	Sources/Logging/CoreStore+Logging.swift
2016-03-23 21:23:08 +09:00
John Estropia
928585029d XCode 7.3 full support 2016-03-23 12:39:11 +09:00
John Estropia
dc6d22b6ff updated travis XCode version 2016-03-23 12:14:34 +09:00
John Estropia
5ca0f657cb XCode 7.3 fixes 2016-03-23 12:07:31 +09:00
John Estropia
2c65ac1834 WIP: transactions in ObjC 2016-03-23 11:32:35 +09:00
John Rommel Estropia
24008d62b2 WIP: error handling 2016-03-22 22:51:42 +09:00
John Rommel Estropia
e8a9cc9d67 WIP: error handling 2016-03-22 07:23:11 +09:00
John Estropia
1507ac63f9 WIP: objective C storages 2016-03-18 20:32:23 +09:00
John Rommel Estropia
f2df8f7171 WIP: objective C interface 2016-03-18 02:39:18 +09:00
John Rommel Estropia
3ddfd3cccc WIP: objc setup 2016-03-17 07:51:22 +09:00
John Estropia
21f57518c8 WIP: Objective-C bridge 2016-03-16 21:23:41 +09:00
John Rommel Estropia
d9422f7f2e Changed error-handling method to rely on new enum CoreStoreError instead of NSErrors 2016-03-16 07:56:19 +09:00
John Estropia
245ec25ad8 Update README.md 2016-03-15 16:24:51 +09:00
John Rommel Estropia
0ce89b8460 WIP: documentation 2016-03-15 07:57:02 +09:00
John Rommel Estropia
42a889a28e WIP: documentation 2016-03-14 07:57:49 +09:00
John Rommel Estropia
456977bf12 renamed default directories 2016-03-13 11:21:01 +09:00
John Rommel Estropia
ea8412ab93 removed Nimble 2016-03-13 11:10:09 +09:00
John Rommel Estropia
603dffffb0 WIP: documentation and unit tests 2016-03-12 20:13:38 +09:00
John Rommel Estropia
8a1144b1be Merge branch 'develop' into corestore2_develop
# Conflicts:
#	README.md
2016-03-12 10:37:33 +09:00
John Rommel Estropia
c323a28c19 fix warnings in demo app 2016-03-12 09:27:59 +09:00
John Rommel Estropia
4ff7b2d6d9 WIP: documentation 2016-03-12 00:16:54 +09:00
John Estropia
64ad7b3bfd Update README.md 2016-03-11 20:59:52 +09:00
John Estropia
df441c5d04 Update README.md 2016-03-11 20:58:33 +09:00
John Estropia
fa947faa57 make demo app for migrations initialize smoother 2016-03-10 20:28:19 +09:00
John Estropia
6822a4579e WIP: clean up persistent store setup 2016-03-10 20:19:52 +09:00
John Estropia
a89dd76906 WIP: tidy up 2016-03-09 19:42:17 +09:00
John Estropia
c85ef16ad0 WIP: tidy up 2016-03-09 18:49:00 +09:00
John Estropia
67f1bd9a63 updated travis.yml 2016-03-09 17:51:45 +09:00
John Estropia
39540628df updated travis.yml 2016-03-09 17:48:54 +09:00
John Estropia
c86ca06bd4 Swift Package Manager support 2016-03-09 17:47:46 +09:00
John Estropia
40c27b9fcb Merge branch 'master' into corestore2_develop 2016-03-08 15:54:24 +09:00
John Rommel Estropia
2f8c100cb6 WIP: StorageInterface 2016-03-08 07:55:15 +09:00
John Rommel Estropia
34495d7163 WIP: prevent error when adding non-existent file 2016-03-07 07:35:55 +09:00
John Rommel Estropia
75a4ebb49b WIP: StorageInterface methods 2016-03-07 07:23:44 +09:00
John Rommel Estropia
3c514830d9 WIP: SQLiteStore implementation 2016-03-04 07:51:35 +09:00
John Estropia
b283fbf5ab WIP: StorageInterface 2016-03-03 20:03:13 +09:00
John Estropia
c1e087b8c1 goodbye strongSelf 2016-03-03 12:51:56 +09:00
John Rommel Estropia
ad1ebb3501 WIP: StorageInterface protocol 2016-03-03 07:50:43 +09:00
John Rommel Estropia
99189d160f WIP: Storage protocol 2016-03-02 08:02:33 +09:00
John Estropia
3c2fb28644 added Packaged.swift 2016-03-01 19:42:28 +09:00
John Rommel Estropia
f71ad4c577 WIP: Storage protocols 2016-03-01 08:01:40 +09:00
John Rommel Estropia
d05fcc5103 updated demo app for deprecated methods 2016-02-29 22:26:34 +09:00
John Rommel Estropia
e9329fc93c better quality image 2016-02-28 01:21:00 +09:00
John Rommel Estropia
1ea773f0ec added CoreStore logo 2016-02-27 16:01:48 +09:00
John Rommel Estropia
c68c2bdc0a Merge branch 'develop' into corestore2_develop
Conflicts:
	CoreStore.podspec
	CoreStore/Info.plist
2016-02-26 01:51:35 +09:00
John Estropia
e66a13632a Merge branch 'develop' 2016-02-25 15:52:59 +09:00
John Estropia
4c78a309bc deprecated NSFetchedResultsController initializer in favor of static factory method 2016-02-25 15:52:49 +09:00
John Estropia
9055daa58c Merge branch 'develop' 2016-02-25 15:11:40 +09:00
John Estropia
3905423038 fix compile error 2016-02-25 15:05:27 +09:00
John Estropia
15353268e2 Allow unsafe transactions to save synchronously 2016-02-25 15:04:05 +09:00
John Rommel Estropia
91dd4b6cb3 tidy up 2016-02-25 07:14:15 +09:00
John Rommel Estropia
0800b706d6 revert 2016-02-24 22:04:14 +09:00
John Rommel Estropia
2071ce722e WIP: iCloud support 2016-02-24 22:03:14 +09:00
John Rommel Estropia
df866718cf tidy up 2016-02-24 22:02:26 +09:00
John Rommel Estropia
f19a0d29eb added obsolete annotations to previously deprecated methods 2016-02-24 21:52:35 +09:00
John Estropia
8f09f90294 Changed default directories to comply with Apple's guidelines (TODO: update README) 2016-02-24 18:57:03 +09:00
John Estropia
7bddcaa4a2 swift 2.2 (Xcode 7.3 beta 4) updates 2016-02-24 16:54:39 +09:00
John Estropia
c12331f403 Merge pull request #53 from mpurbo/patch-1
added missing closing bracket on sample code.
2016-02-22 22:26:38 +09:00
Mamad Purbo
a55a8d389c added missing closing bracket on sample code. 2016-02-22 11:50:28 +09:00
John Rommel Estropia
6e88b14237 fixed readme build status 2016-02-20 12:30:02 +09:00
John Rommel Estropia
53700a267f version bump 2016-02-20 12:28:39 +09:00
John Rommel Estropia
6c95b010e9 utility to update an existing ImportableObject from its ImportSource (fixes #52) 2016-02-20 12:26:23 +09:00
John Rommel Estropia
1af675e8b7 tidy up 2016-02-20 12:05:01 +09:00
John Rommel Estropia
b85f16cf68 Merge branch 'master' into develop 2016-02-20 01:25:40 +09:00
John Estropia
37bf4179c8 renamed "Incremental Migrations" to "Progressive Migrations" to match the common term used by the community 2016-02-17 15:34:33 +09:00
John Estropia
240dda6ad6 Merge pull request #50 from ReadmeCritic/master
Correct the spelling of Xcode in README
2016-02-17 10:03:47 +09:00
ReadmeCritic
ffe73d0f66 Correct the spelling of Xcode in README 2016-02-16 11:47:36 -08:00
194 changed files with 28871 additions and 4369 deletions

2
.gitignore vendored
View File

@@ -5,3 +5,5 @@ CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata
Carthage/Build
CoreStore.xcworkspace/xcuserdata
.DS_Store
DerivedData
*.orig

View File

@@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode7.2
osx_image: xcode8
sudo: false
git:
submodules: false
@@ -10,19 +10,21 @@ env:
- 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"
- DESTINATION="OS=10.0,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="YES" RUN_DEMO="YES"
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" RUN_DEMO="YES"
# iOS 8 testing currently broken on Xcode 8
# - DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" RUN_DEMO="YES"
# - DESTINATION="OS=8.3,name=iPhone 5s" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" RUN_DEMO="YES"
# - DESTINATION="OS=8.2,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" RUN_DEMO="YES"
# - DESTINATION="OS=8.1,name=iPhone 4s" SCHEME="CoreStore iOS" SDK=iphonesimulator10.0 RUN_TESTS="YES" POD_LINT="NO" RUN_DEMO="YES"
- DESTINATION="arch=x86_64" SCHEME="CoreStore OSX" SDK=macosx10.12 RUN_TESTS="YES" POD_LINT="NO" RUN_DEMO="NO"
- DESTINATION="OS=3.0,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator3.0 RUN_TESTS="NO" POD_LINT="NO" RUN_DEMO="NO"
- DESTINATION="OS=2.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator3.0 RUN_TESTS="NO" POD_LINT="NO" RUN_DEMO="NO"
- DESTINATION="OS=9.2,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator10.0 RUN_TESTS="YES" POD_LINT="NO" RUN_DEMO="NO"
before_install:
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.11/Carthage.pkg"
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.18/Carthage.pkg"
- sudo installer -pkg "Carthage.pkg" -target /
- rm "Carthage.pkg"
before_script:
@@ -35,8 +37,9 @@ script:
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 [ $RUN_DEMO == "YES" ]; then
xcodebuild -workspace CoreStore.xcworkspace -scheme "CoreStoreDemo" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO clean build | xcpretty -c;
fi
- if [ $POD_LINT == "YES" ]; then
pod lib lint --quick;
fi

View File

@@ -1 +1 @@
github "JohnEstropia/GCDKit" == 1.1.7
github "JohnEstropia/GCDKit" == 1.3.0

View File

@@ -1 +1 @@
github "JohnEstropia/GCDKit" "1.1.7"
github "JohnEstropia/GCDKit" "1.3.0"

BIN
CoreStore.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "1.5.0"
s.version = "2.1.3"
s.license = "MIT"
s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift"
s.homepage = "https://github.com/JohnEstropia/CoreStore"
@@ -12,11 +12,13 @@ Pod::Spec.new do |s|
s.watchos.deployment_target = "2.0"
s.tvos.deployment_target = "9.0"
s.source_files = "CoreStore", "CoreStore/**/*.{swift}"
s.osx.exclude_files = "CoreStore/Observing/*.{swift}", "CoreStore/Internal/FetchedResultsControllerDelegate.swift", "CoreStore/Internal/CoreStoreFetchedResultsController.swift", "CoreStore/Convenience Helpers/NSFetchedResultsController+Convenience.swift"
s.source_files = "Sources", "Sources/**/*.{swift,h,m}"
s.public_header_files = "Sources/**/*.h"
s.frameworks = "Foundation", "CoreData"
s.requires_arc = true
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-D USE_FRAMEWORKS' }
s.dependency "GCDKit", "1.1.7"
end
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D USE_FRAMEWORKS -D DEBUG',
'OTHER_SWIFT_FLAGS[config=Release]' => '-D USE_FRAMEWORKS',
'GCC_PREPROCESSOR_DEFINITIONS' => 'USE_FRAMEWORKS=1' }
s.dependency "GCDKit", "1.3.0"
end

BIN
CoreStore.sketch Normal file

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -10,10 +10,10 @@
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "F347F55F-7F5C-4476-9148-6E902F06E4AD",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStoreLibraries\/GCDKit",
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore"
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
"DVTSourceControlWorkspaceBlueprintVersion" : 203,
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStore.xcodeproj",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0710"
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0720"
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -5,11 +5,13 @@
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0,
"95438028B10BBB846574013D29F154A00556A9D1" : 0,
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "EBFDEFFE-8BA0-441A-862A-1DE28AA5CD21",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStore\/Carthage\/Checkouts\/GCDKit\/",
"95438028B10BBB846574013D29F154A00556A9D1" : "CoreStore\/Carthage\/Checkouts\/Nimble\/",
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
@@ -25,6 +27,11 @@
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/JohnEstropia\/GCDKit.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Quick\/Nimble.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "95438028B10BBB846574013D29F154A00556A9D1"
}
]
}

View File

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

View File

@@ -1,61 +0,0 @@
//
// NSManagedObject+Convenience.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - NSFetchedResultsController
public extension NSFetchedResultsController {
/**
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful to partially support Objective-C classes by passing an `NSFetchedResultsController` instance instead of a `ListMonitor`.
*/
public func createForStack<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return CoreStoreFetchedResultsController<T>(
context: dataStack.mainContext,
fetchRequest: fetchRequest,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
// MARK: Internal
internal func createFromContext<T: NSManagedObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return CoreStoreFetchedResultsController<T>(
context: context,
fetchRequest: fetchRequest,
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
}

View File

@@ -1,262 +0,0 @@
//
// From.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - From
/**
A `From` clause specifies the source entity and source persistent store for fetch and query methods. A common usage is to just indicate the entity:
```
let person = transaction.fetchOne(From(MyPersonEntity))
```
For cases where multiple `NSPersistentStore`s contain the same entity, the source configuration's name needs to be specified as well:
```
let person = transaction.fetchOne(From<MyPersonEntity>("Configuration1"))
```
*/
public struct From<T: NSManagedObject> {
/**
Initializes a `From` clause.
Sample Usage:
```
let people = transaction.fetchAll(From<MyPersonEntity>())
```
*/
public init(){
self.init(entityClass: T.self)
}
/**
Initializes a `From` clause with the specified entity type.
Sample Usage:
```
let people = transaction.fetchAll(From<MyPersonEntity>())
```
- parameter entity: the `NSManagedObject` type to be created
*/
public init(_ entity: T.Type) {
self.init(entityClass: entity)
}
/**
Initializes a `From` clause with the specified entity class.
Sample Usage:
```
let people = transaction.fetchAll(From<MyPersonEntity>())
```
- parameter entityClass: the `NSManagedObject` class type to be created
*/
public init(_ entityClass: AnyClass) {
self.init(entityClass: entityClass)
}
/**
Initializes a `From` clause with the specified configurations.
Sample Usage:
```
let people = transaction.fetchAll(From<MyPersonEntity>(nil, "Configuration1"))
```
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
*/
public init(_ configuration: String?, otherConfigurations: String?...) {
self.init(entityClass: T.self, configurations: [configuration] + otherConfigurations)
}
/**
Initializes a `From` clause with the specified configurations.
Sample Usage:
```
let people = transaction.fetchAll(From<MyPersonEntity>(["Configuration1", "Configuration2"]))
```
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ configurations: [String?]) {
self.init(entityClass: T.self, configurations: configurations)
}
public init(_ entity: T.Type, _ configuration: String?, _ otherConfigurations: String?...) {
self.init(entityClass: entity, configurations: [configuration] + otherConfigurations)
}
public init(_ entity: T.Type, _ configurations: [String?]) {
self.init(entityClass: entity, configurations: configurations)
}
public init(_ entityClass: AnyClass, _ configuration: String?, _ otherConfigurations: String?...) {
self.init(entityClass: entityClass, configurations: [configuration] + otherConfigurations)
}
public init(_ entityClass: AnyClass, _ configurations: [String?]) {
self.init(entityClass: entityClass, configurations: configurations)
}
public init(_ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
self.init(entityClass: T.self, storeURLs: [storeURL] + otherStoreURLs)
}
public init(_ storeURLs: [NSURL]) {
self.init(entityClass: T.self, storeURLs: storeURLs)
}
public init(_ entity: T.Type, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
self.init(entityClass: entity, storeURLs: [storeURL] + otherStoreURLs)
}
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
self.init(entityClass: entity, storeURLs: storeURLs)
}
public init(_ entityClass: AnyClass, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
self.init(entityClass: entityClass, storeURLs: [storeURL] + otherStoreURLs)
}
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
self.init(entityClass: entityClass, storeURLs: storeURLs)
}
public init(_ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
self.init(entityClass: T.self, persistentStores: [persistentStore] + otherPersistentStores)
}
public init(_ persistentStores: [NSPersistentStore]) {
self.init(entityClass: T.self, persistentStores: persistentStores)
}
public init(_ entity: T.Type, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
self.init(entityClass: entity, persistentStores: [persistentStore] + otherPersistentStores)
}
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
self.init(entityClass: entity, persistentStores: persistentStores)
}
public init(_ entityClass: AnyClass, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
self.init(entityClass: entityClass, persistentStores: [persistentStore] + otherPersistentStores)
}
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
self.init(entityClass: entityClass, persistentStores: persistentStores)
}
// MARK: Internal
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext, applyAffectedStores: Bool = true) {
fetchRequest.entity = context.entityDescriptionForEntityClass(self.entityClass)
if applyAffectedStores {
self.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
}
}
internal func applyAffectedStoresForFetchedRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) -> Bool {
let stores = self.findPersistentStores(context: context)
fetchRequest.affectedStores = stores
return stores?.isEmpty == false
}
// MARK: Private
private let entityClass: AnyClass
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
private init(entityClass: AnyClass) {
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)
}
}
private init(entityClass: AnyClass, configurations: [String?]) {
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return configurationsSet.contains($0.configurationName)
}
}
}
private init(entityClass: AnyClass, storeURLs: [NSURL]) {
let storeURLsSet = Set(storeURLs)
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return $0.URL != nil && storeURLsSet.contains($0.URL!)
}
}
}
private init(entityClass: AnyClass, persistentStores: [NSPersistentStore]) {
let persistentStores = Set(persistentStores)
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return persistentStores.contains($0)
}
}
}
}

View File

@@ -1,68 +0,0 @@
//
// NSObject+CoreStore.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal func getAssociatedObjectForKey<T: AnyObject>(key: UnsafePointer<Void>, inObject object: AnyObject) -> T? {
switch objc_getAssociatedObject(object, key) {
case let associatedObject as T:
return associatedObject
case let associatedObject as WeakObject:
return associatedObject.object as? T
default:
return nil
}
}
internal func setAssociatedRetainedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
internal func setAssociatedCopiedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
internal func setAssociatedAssignedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_ASSIGN)
}
internal func setAssociatedWeakObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
if let associatedObject = associatedObject {
objc_setAssociatedObject(object, key, WeakObject(associatedObject), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
else {
objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

View File

@@ -1,26 +0,0 @@
//
// NSFileManager+Setup.swift
// CoreStore
//
// Created by John Rommel Estropia on 2015/07/19.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
//
import Foundation
// MARK: - NSFileManager
internal extension NSFileManager {
// MARK: Internal
internal func removeSQLiteStoreAtURL(fileURL: NSURL) {
_ = try? self.removeItemAtURL(fileURL)
let filePath = fileURL.path!
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-shm"))
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-wal"))
}
}

View File

@@ -1,105 +0,0 @@
//
// CoreStoreLogger.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - LogLevel
/**
The `LogLevel` indicates the severity of a log message.
*/
public enum LogLevel {
case Trace
case Notice
case Warning
case Fatal
}
// MARK: - CoreStoreLogger
/**
Custom loggers should implement the `CoreStoreLogger` protocol and pass its instance to `CoreStore.logger`. Calls to `log(...)`, `handleError(...)`, and `assert(...)` are not tied to a specific queue/thread, so it is the implementer's job to handle thread-safety.
*/
public protocol CoreStoreLogger {
/**
Handles log messages sent by the `CoreStore` framework.
:level: the severity of the log message
:message: the log message
:fileName: the source file name
:lineNumber: the source line number
:functionName: the source function name
*/
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
/**
Handles errors sent by the `CoreStore` framework.
:error: the error
:message: the error message
:fileName: the source file name
:lineNumber: the source line number
:functionName: the source function name
*/
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
/**
Handles assertions made throughout the `CoreStore` framework.
:condition: the assertion condition
:message: the assertion message
:fileName: the source file name
:lineNumber: the source line number
:functionName: the source function name
*/
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
}
// MARK: - Utilities
internal func typeName<T>(value: T) -> String {
return "'\(String(reflecting: value.dynamicType))'"
}
internal func typeName<T>(value: T.Type) -> String {
return "'\(value)'"
}
internal func typeName(value: AnyClass) -> String {
return "'\(value)'"
}
internal func typeName(name: String?) -> String {
return "<\(name ?? "unknown")>"
}

View File

@@ -1,152 +0,0 @@
//
// CoreStore+Migration.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - CoreStore
public extension CoreStore {
/**
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
completion: completion
)
}
/**
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
completion: completion
)
}
/**
Using the `defaultStack`, migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Using the `defaultStack`, migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Using the `defaultStack`, checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public static func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForSQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
/**
Using the `defaultStack`, checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
- parameter fileURL: the local file URL for the SQLite persistent store.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public static func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForSQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
}

View File

@@ -1,635 +0,0 @@
//
// DataStack+Migration.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - DataStack
public extension DataStack {
/**
Asynchronously adds an in-memory store to the stack.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
*/
public func addInMemoryStore(configuration configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
self.coordinator.performBlock {
do {
let store = try self.coordinator.addPersistentStoreWithType(
NSInMemoryStoreType,
configuration: configuration,
URL: nil,
options: nil
)
self.updateMetadataForPersistentStore(store)
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
}
catch {
let storeError = error as NSError
CoreStore.handleError(
storeError,
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
)
GCDQueue.Main.async {
completion(PersistentStoreResult(storeError))
}
}
}
}
/**
Asynchronously adds to the stack an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.addSQLiteStore(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
completion: completion
)
}
/**
Asynchronously adds to the stack an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
CoreStore.assert(
fileURL.fileURL,
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
)
let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) {
guard store.type == NSSQLiteStoreType
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError(
error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
)
throw error
}
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
return nil
}
let fileManager = NSFileManager.defaultManager()
_ = try? fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
do {
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL,
options: self.optionsForSQLiteStore()
)
return self.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
metadata: metadata,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: { (result) -> Void in
if case .Failure(let error) = result {
if resetStoreOnModelMismatch && error.isCoreDataMigrationError {
fileManager.removeSQLiteStoreAtURL(fileURL)
do {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
}
catch {
completion(PersistentStoreResult(error as NSError))
}
return
}
completion(PersistentStoreResult(error))
return
}
do {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
completion(PersistentStoreResult(store))
}
catch {
completion(PersistentStoreResult(error as NSError))
}
}
)
}
catch let error as NSError
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
return nil
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
}
}
/**
Migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.upgradeSQLiteStoreIfNeeded(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
let metadata: [String: AnyObject]
do {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL,
options: self.optionsForSQLiteStore()
)
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
}
return self.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
metadata: metadata,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try requiredMigrationsForSQLiteStore(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
/**
Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
- parameter fileURL: the local file URL for the SQLite persistent store.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
let metadata: [String : AnyObject]
do {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL,
options: self.optionsForSQLiteStore()
)
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
}
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else {
let error = NSError(coreStoreErrorCode: .MappingModelNotFound)
CoreStore.handleError(
error,
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(self.modelVersion)\"."
)
throw error
}
return migrationSteps.map { $0.migrationType }
}
// MARK: Private
private func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL, metadata: [String: AnyObject], configuration: String?, mappingModelBundles: [NSBundle]?, completion: (MigrationResult) -> Void) -> NSProgress? {
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else {
CoreStore.handleError(
NSError(coreStoreErrorCode: .MappingModelNotFound),
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(model)\"."
)
GCDQueue.Main.async {
completion(MigrationResult(.MappingModelNotFound))
}
return nil
}
let numberOfMigrations: Int64 = Int64(migrationSteps.count)
if numberOfMigrations == 0 {
GCDQueue.Main.async {
completion(MigrationResult([]))
return
}
return nil
}
let migrationTypes = migrationSteps.map { $0.migrationType }
var migrationResult: MigrationResult?
var operations = [NSOperation]()
var cancelled = false
let progress = NSProgress(parent: nil, userInfo: nil)
progress.totalUnitCount = numberOfMigrations
// todo nsprogress crashing sometimes
for (sourceModel, destinationModel, mappingModel, _) in migrationSteps {
progress.becomeCurrentWithPendingUnitCount(1)
let childProgress = NSProgress(parent: progress, userInfo: nil)
childProgress.totalUnitCount = 100
operations.append(
NSBlockOperation { [weak self] in
guard let strongSelf = self where !cancelled else {
return
}
autoreleasepool {
do {
try strongSelf.startMigrationForSQLiteStore(
fileURL: fileURL,
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
progress: childProgress
)
}
catch {
migrationResult = MigrationResult(error as NSError)
cancelled = true
}
}
GCDQueue.Main.async {
withExtendedLifetime(childProgress) { (_: NSProgress) -> Void in }
return
}
}
)
progress.resignCurrent()
}
let migrationOperation = NSBlockOperation()
migrationOperation.qualityOfService = .Utility
operations.forEach { migrationOperation.addDependency($0) }
migrationOperation.addExecutionBlock { () -> Void in
GCDQueue.Main.async {
progress.setProgressHandler(nil)
completion(migrationResult ?? MigrationResult(migrationTypes))
return
}
}
operations.append(migrationOperation)
self.migrationQueue.addOperations(operations, waitUntilFinished: false)
return progress
}
private func computeMigrationFromStoreMetadata(metadata: [String: AnyObject], configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? {
let model = self.model
if model.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) {
return []
}
guard let initialModel = model[metadata],
var currentVersion = initialModel.currentModelVersion else {
return nil
}
let migrationChain: MigrationChain = self.migrationChain.empty
? [currentVersion: model.currentModelVersion!]
: self.migrationChain
var migrationSteps = [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]()
while let nextVersion = migrationChain.nextVersionFrom(currentVersion),
let sourceModel = model[currentVersion],
let destinationModel = model[nextVersion] where sourceModel != model {
if let mappingModel = NSMappingModel(
fromBundles: mappingModelBundles,
forSourceModel: sourceModel,
destinationModel: destinationModel) {
migrationSteps.append(
(
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
migrationType: .Heavyweight(
sourceVersion: currentVersion,
destinationVersion: nextVersion
)
)
)
}
else {
do {
let mappingModel = try NSMappingModel.inferredMappingModelForSourceModel(
sourceModel,
destinationModel: destinationModel
)
migrationSteps.append(
(
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
migrationType: .Lightweight(
sourceVersion: currentVersion,
destinationVersion: nextVersion
)
)
)
}
catch {
return nil
}
}
currentVersion = nextVersion
}
if migrationSteps.last?.destinationModel == model {
return migrationSteps
}
return nil
}
private func startMigrationForSQLiteStore(fileURL fileURL: NSURL, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, progress: NSProgress) throws {
autoreleasepool {
let journalUpdatingCoordinator = NSPersistentStoreCoordinator(managedObjectModel: sourceModel)
let store = try! journalUpdatingCoordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: nil,
URL: fileURL,
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
)
try! journalUpdatingCoordinator.removePersistentStore(store)
}
let migrationManager = MigrationManager(
sourceModel: sourceModel,
destinationModel: destinationModel,
progress: progress
)
let temporaryDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString)
let fileManager = NSFileManager.defaultManager()
try! fileManager.createDirectoryAtURL(
temporaryDirectoryURL,
withIntermediateDirectories: true,
attributes: nil
)
let temporaryFileURL = temporaryDirectoryURL.URLByAppendingPathComponent(fileURL.lastPathComponent!, isDirectory: false)
do {
try migrationManager.migrateStoreFromURL(
fileURL,
type: NSSQLiteStoreType,
options: nil,
withMappingModel: mappingModel,
toDestinationURL: temporaryFileURL,
destinationType: NSSQLiteStoreType,
destinationOptions: nil
)
}
catch {
do {
try fileManager.removeItemAtURL(temporaryDirectoryURL)
}
catch _ { }
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
CoreStore.handleError(
error as NSError,
"Failed to migrate from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
)
throw error
}
do {
try fileManager.replaceItemAtURL(
fileURL,
withItemAtURL: temporaryFileURL,
backupItemName: nil,
options: [],
resultingItemURL: nil
)
progress.completedUnitCount = progress.totalUnitCount
do {
try fileManager.removeItemAtPath(fileURL.path! + "-shm")
}
catch _ { }
}
catch {
do {
try fileManager.removeItemAtURL(temporaryDirectoryURL)
}
catch _ { }
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
CoreStore.handleError(
error as NSError,
"Failed to save store after migrating from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
)
throw error
}
}
}

View File

@@ -1,102 +0,0 @@
//
// NSError+CoreStore.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStoreError
/**
The `NSError` error domain for `CoreStore`.
*/
public let CoreStoreErrorDomain = "com.corestore.error"
/**
The `NSError` error codes for `CoreStoreErrorDomain`.
*/
public enum CoreStoreErrorCode: Int {
/**
A failure occured because of an unknown error.
*/
case UnknownError
/**
The `NSPersistentStore` could note be initialized because another store existed at the specified `NSURL`.
*/
case DifferentPersistentStoreExistsAtURL
/**
The `NSPersistentStore` specified could not be found.
*/
case PersistentStoreNotFound
/**
An `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case MappingModelNotFound
}
// MARK: - NSError
public extension NSError {
/**
If the error's domain is equal to `CoreStoreErrorDomain`, returns the associated `CoreStoreErrorCode`. For other domains, returns `nil`.
*/
public var coreStoreErrorCode: CoreStoreErrorCode? {
return (self.domain == CoreStoreErrorDomain
? CoreStoreErrorCode(rawValue: self.code)
: nil)
}
// MARK: Internal
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode) {
self.init(coreStoreErrorCode: coreStoreErrorCode, userInfo: nil)
}
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(
domain: CoreStoreErrorDomain,
code: coreStoreErrorCode.rawValue,
userInfo: userInfo)
}
internal var isCoreDataMigrationError: Bool {
let code = self.code
return (code == NSPersistentStoreIncompatibleVersionHashError
|| code == NSMigrationMissingSourceModelError
|| code == NSMigrationError)
&& self.domain == NSCocoaErrorDomain
}
}

View File

@@ -1,105 +0,0 @@
//
// CoreStore+Setup.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - CoreStore
public extension CoreStore {
/**
Returns the `defaultStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
*/
public static var modelVersion: String {
return self.defaultStack.modelVersion
}
/**
Returns the entity name-to-class type mapping from the `defaultStack`'s model.
*/
public static var entityTypesByName: [String: NSManagedObject.Type] {
return self.defaultStack.entityTypesByName
}
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `defaultStack`'s model.
*/
public static func entityDescriptionForType(type: NSManagedObject.Type) -> NSEntityDescription? {
return self.defaultStack.entityDescriptionForType(type)
}
/**
Adds an in-memory store to the `defaultStack`.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
- returns: the `NSPersistentStore` added to the stack.
*/
public static func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
return try self.defaultStack.addInMemoryStoreAndWait(configuration: configuration)
}
/**
Adds to the `defaultStack` an SQLite store from the given SQLite file name.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
- returns: the `NSPersistentStore` added to the stack.
*/
public static func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
return try self.defaultStack.addSQLiteStoreAndWait(
fileName: fileName,
configuration: configuration,
resetStoreOnModelMismatch: resetStoreOnModelMismatch
)
}
/**
Adds to the `defaultStack` an SQLite store from the given SQLite file URL.
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- returns: the `NSPersistentStore` added to the stack.
*/
public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
return try self.defaultStack.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: resetStoreOnModelMismatch
)
}
}

View File

@@ -1,397 +0,0 @@
//
// DataStack.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
#if os(tvOS)
internal let deviceDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
#else
internal let deviceDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
#endif
internal let defaultDirectory = NSFileManager.defaultManager().URLsForDirectory(deviceDirectorySearchPath, inDomains: .UserDomainMask).first!
internal let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
internal let defaultSQLiteStoreURL = defaultDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite")
// MARK: - DataStack
/**
The `DataStack` encapsulates the data model for the Core Data stack. Each `DataStack` can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own `NSPersistentStoreCoordinator`, a root `NSManagedObjectContext` for disk saves, and a shared `NSManagedObjectContext` designed as a read-only model interface for `NSManagedObjects`.
*/
public final class DataStack {
/**
Initializes a `DataStack` from an `NSManagedObjectModel`.
- parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name will be used.
- parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used.
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for incremental migration. If not specified, will default to a non-migrating data stack.
*/
public required init(modelName: String = applicationName, bundle: NSBundle = NSBundle.mainBundle(), migrationChain: MigrationChain = nil) {
CoreStore.assert(
migrationChain.valid,
"Invalid migration chain passed to the \(typeName(DataStack)). Check that the model versions' order is correct and that no repetitions or ambiguities exist."
)
let model = NSManagedObjectModel.fromBundle(
bundle,
modelName: modelName,
modelVersionHints: migrationChain.leafVersions
)
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
self.model = model
self.migrationChain = migrationChain
self.rootSavingContext.parentStack = self
}
/**
Returns the `DataStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
*/
public var modelVersion: String {
return self.model.currentModelVersion!
}
/**
Returns the entity name-to-class type mapping from the `DataStack`'s model.
*/
public var entityTypesByName: [String: NSManagedObject.Type] {
return self.model.entityTypesMapping()
}
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass.
*/
public func entityDescriptionForType(type: NSManagedObject.Type) -> NSEntityDescription? {
return NSEntityDescription.entityForName(
self.model.entityNameForClass(type),
inManagedObjectContext: self.mainContext
)
}
/**
Returns the `NSManagedObjectID` for the specified object URI if it exists in the persistent store.
*/
public func objectIDForURIRepresentation(url: NSURL) -> NSManagedObjectID? {
return self.coordinator.managedObjectIDForURIRepresentation(url)
}
/**
Adds an in-memory store to the stack.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
- returns: the `NSPersistentStore` added to the stack.
*/
public func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
let coordinator = self.coordinator;
var store: NSPersistentStore?
var storeError: NSError?
coordinator.performBlockAndWait {
do {
store = try coordinator.addPersistentStoreWithType(
NSInMemoryStoreType,
configuration: configuration,
URL: nil,
options: nil
)
}
catch {
storeError = error as NSError
}
}
if let store = store {
self.updateMetadataForPersistentStore(store)
return store
}
let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
CoreStore.handleError(
error,
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
)
throw error
}
/**
Adds to the stack an SQLite store from the given SQLite file name.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
- returns: the `NSPersistentStore` added to the stack.
*/
public func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
return try self.addSQLiteStoreAndWait(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
resetStoreOnModelMismatch: resetStoreOnModelMismatch
)
}
/**
Adds to the stack an SQLite store from the given SQLite file URL.
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- returns: the `NSPersistentStore` added to the stack.
*/
public func addSQLiteStoreAndWait(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
CoreStore.assert(
fileURL.fileURL,
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
)
let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) {
guard store.type == NSSQLiteStoreType
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError(
error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
)
throw error
}
return store
}
let fileManager = NSFileManager.defaultManager()
_ = try? fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
var store: NSPersistentStore?
var storeError: NSError?
let options = self.optionsForSQLiteStore()
coordinator.performBlockAndWait {
do {
store = try coordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: configuration,
URL: fileURL,
options: options
)
}
catch {
storeError = error as NSError
}
}
if let store = store {
self.updateMetadataForPersistentStore(store)
return store
}
if let error = storeError
where (resetStoreOnModelMismatch && error.isCoreDataMigrationError) {
fileManager.removeSQLiteStoreAtURL(fileURL)
var store: NSPersistentStore?
coordinator.performBlockAndWait {
do {
store = try coordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: configuration,
URL: fileURL,
options: [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
)
}
catch {
storeError = error as NSError
}
}
if let store = store {
self.updateMetadataForPersistentStore(store)
return store
}
}
let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
CoreStore.handleError(
error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\"."
)
throw error
}
// MARK: Internal
internal let coordinator: NSPersistentStoreCoordinator
internal let rootSavingContext: NSManagedObjectContext
internal let mainContext: NSManagedObjectContext
internal let model: NSManagedObjectModel
internal let migrationChain: MigrationChain
internal let childTransactionQueue: GCDQueue = .createSerial("com.coreStore.dataStack.childTransactionQueue")
internal let migrationQueue: NSOperationQueue = {
let migrationQueue = NSOperationQueue()
migrationQueue.maxConcurrentOperationCount = 1
migrationQueue.name = "com.coreStore.migrationOperationQueue"
migrationQueue.qualityOfService = .Utility
migrationQueue.underlyingQueue = dispatch_queue_create("com.coreStore.migrationQueue", DISPATCH_QUEUE_SERIAL)
return migrationQueue
}()
internal func optionsForSQLiteStore() -> [String: AnyObject] {
return [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
}
internal func entityNameForEntityClass(entityClass: AnyClass) -> String? {
return self.model.entityNameForClass(entityClass)
}
internal func persistentStoresForEntityClass(entityClass: AnyClass) -> [NSPersistentStore]? {
var returnValue: [NSPersistentStore]? = nil
self.storeMetadataUpdateQueue.barrierSync {
returnValue = self.entityConfigurationsMapping[NSStringFromClass(entityClass)]?.map {
return self.configurationStoreMapping[$0]!
} ?? []
}
return returnValue
}
internal func persistentStoreForEntityClass(entityClass: AnyClass, configuration: String?, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
var returnValue: (store: NSPersistentStore?, isAmbiguous: Bool) = (store: nil, isAmbiguous: false)
self.storeMetadataUpdateQueue.barrierSync {
let configurationsForEntity = self.entityConfigurationsMapping[NSStringFromClass(entityClass)] ?? []
if let configuration = configuration {
if configurationsForEntity.contains(configuration) {
returnValue = (store: self.configurationStoreMapping[configuration], isAmbiguous: false)
return
}
else if !inferStoreIfPossible {
return
}
}
switch configurationsForEntity.count {
case 0:
return
case 1 where inferStoreIfPossible:
returnValue = (store: self.configurationStoreMapping[configurationsForEntity.first!], isAmbiguous: false)
default:
returnValue = (store: nil, isAmbiguous: true)
}
}
return returnValue
}
internal func updateMetadataForPersistentStore(persistentStore: NSPersistentStore) {
self.storeMetadataUpdateQueue.barrierAsync {
let configurationName = persistentStore.configurationName
self.configurationStoreMapping[configurationName] = persistentStore
for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) ?? []) {
let managedObjectClassName = entityDescription.managedObjectClassName
CoreStore.assert(
NSClassFromString(managedObjectClassName) != nil,
"The class \(typeName(managedObjectClassName)) for the entity \(typeName(entityDescription.name)) does not exist. Check if the subclass type and module name are properly configured."
)
if self.entityConfigurationsMapping[managedObjectClassName] == nil {
self.entityConfigurationsMapping[managedObjectClassName] = []
}
self.entityConfigurationsMapping[managedObjectClassName]?.insert(configurationName)
}
}
}
// MARK: Private
private let storeMetadataUpdateQueue = GCDQueue.createConcurrent("com.coreStore.persistentStoreBarrierQueue")
private var configurationStoreMapping = [String: NSPersistentStore]()
private var entityConfigurationsMapping = [String: Set<String>]()
deinit {
for store in self.coordinator.persistentStores {
_ = try? self.coordinator.removePersistentStore(store)
}
}
}

View File

@@ -1,106 +0,0 @@
//
// PersistentStoreResult.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - PersistentStoreResult
/**
The `PersistentStoreResult` indicates the result of an asynchronous initialization of a persistent store.
The `PersistentStoreResult` can be treated as a boolean:
```
try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
if result {
// succeeded
}
else {
// failed
}
})
```
or as an `enum`, where the resulting associated object can also be inspected:
```
try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
switch result {
case .Success(let persistentStore):
// persistentStore is the related NSPersistentStore instance
case .Failure(let error):
// error is the NSError instance for the failure
}
})
```
*/
public enum PersistentStoreResult {
/**
`PersistentStoreResult.Success` indicates that the persistent store process succeeded. The associated object for this `enum` value is the related `NSPersistentStore` instance.
*/
case Success(NSPersistentStore)
/**
`PersistentStoreResult.Failure` indicates that the persistent store process failed. The associated object for this value is the related `NSError` instance.
*/
case Failure(NSError)
// MARK: Internal
internal init(_ store: NSPersistentStore) {
self = .Success(store)
}
internal init(_ error: NSError) {
self = .Failure(error)
}
internal init(_ errorCode: CoreStoreErrorCode) {
self.init(errorCode, userInfo: nil)
}
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
}
}
// MARK: - PersistentStoreResult: BooleanType
extension PersistentStoreResult: BooleanType {
public var boolValue: Bool {
switch self {
case .Success: return true
case .Failure: return false
}
}
}

View File

@@ -277,6 +277,7 @@
TargetAttributes = {
B54AAD481AF4D26E00848AE0 = {
CreatedOnToolsVersion = 6.3;
LastSwiftMigration = 0800;
};
};
};
@@ -459,8 +460,9 @@
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 2.3;
};
name = Debug;
};
@@ -471,8 +473,9 @@
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 2.3;
};
name = Release;
};

View File

@@ -0,0 +1,91 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -21,6 +21,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
application.statusBarStyle = .LightContent
return true
}
}

View File

@@ -1,7 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10112" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10083"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
@@ -11,14 +12,17 @@
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="140" width="441" height="43"/>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="CoreStoreIcon" translatesAutoresizingMaskIntoConstraints="NO" id="q8C-V6-gXH">
<rect key="frame" x="155" y="83" width="170" height="170"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="273" width="440" height="57.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -26,16 +30,21 @@
</subviews>
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
<constraint firstItem="q8C-V6-gXH" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" multiplier="0.7" id="QW6-8Y-w15"/>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
<constraint firstItem="q8C-V6-gXH" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="fRb-1V-9iD"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="top" secondItem="q8C-V6-gXH" secondAttribute="bottom" constant="20" id="s63-MP-ush"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
<resources>
<image name="CoreStoreIcon" width="170" height="170"/>
</resources>
</document>

View File

@@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<<<<<<< Updated upstream
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
=======
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
>>>>>>> Stashed changes
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<<<<<<< Updated upstream
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
=======
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439.5" width="441" height="20.5"/>
>>>>>>> Stashed changes
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="133" width="441" height="57.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
</document>

View File

@@ -1,8 +1,8 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="8152.3" systemVersion="14E46" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10112" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8124.4"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10083"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
@@ -297,7 +297,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Setting up multiple persistent store configurations" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hbn-cf-Y7m">
<rect key="frame" x="15" y="30" width="264" height="13.5"/>
<rect key="frame" x="15" y="30" width="263.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -324,7 +324,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observing list changes and single object changes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ou9-TZ-8bf">
<rect key="frame" x="15" y="30" width="261.5" height="13.5"/>
<rect key="frame" x="15" y="30" width="260.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -351,7 +351,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Making changes with transactions" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="uP1-Jc-o9v">
<rect key="frame" x="15" y="30" width="179.5" height="13.5"/>
<rect key="frame" x="15" y="30" width="179" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -378,7 +378,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws">
<rect key="frame" x="15" y="30" width="169" height="13.5"/>
<rect key="frame" x="15" y="30" width="168.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -405,7 +405,7 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j">
<rect key="frame" x="15" y="30" width="159.5" height="13.5"/>
<rect key="frame" x="15" y="30" width="159" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>

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="Automatic" macOSVersion="Automatic" iOSVersion="Automatic">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10169.1" systemVersion="15D21" minimumToolsVersion="Automatic">
<entity name="Palette" representedClassName="CoreStoreDemo.Palette">
<attribute name="brightness" optional="YES" attributeType="Float" defaultValueString="0.0" syncable="YES"/>
<attribute name="colorName" optional="YES" transient="YES" attributeType="String" syncable="YES"/>

View File

@@ -15,10 +15,12 @@ private struct Static {
static let timeZonesStack: DataStack = {
let dataStack = DataStack()
try! dataStack.addSQLiteStoreAndWait(
fileName: "TimeZoneDemo.sqlite",
configuration: "FetchingAndQueryingDemo",
resetStoreOnModelMismatch: true
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "TimeZoneDemo.sqlite",
configuration: "FetchingAndQueryingDemo",
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
dataStack.beginSynchronous { (transaction) -> Void in
@@ -37,7 +39,7 @@ private struct Static {
cachedTimeZone.daylightSavingTimeOffset = rawTimeZone.daylightSavingTimeOffset
}
transaction.commit()
transaction.commitAndWait()
}
return dataStack
@@ -97,12 +99,13 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch self.segmentedControl?.selectedSegmentIndex {
case .Some(Section.Fetching.rawValue):
case Section.Fetching.rawValue?:
return self.fetchingItems.count
case .Some(Section.Querying.rawValue):
case Section.Querying.rawValue?:
return self.queryingItems.count
default:
@@ -116,10 +119,10 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
switch self.segmentedControl?.selectedSegmentIndex {
case .Some(Section.Fetching.rawValue):
case Section.Fetching.rawValue?:
cell.textLabel?.text = self.fetchingItems[indexPath.row].title
case .Some(Section.Querying.rawValue):
case Section.Querying.rawValue?:
cell.textLabel?.text = self.queryingItems[indexPath.row].title
default:
@@ -138,10 +141,10 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
switch self.segmentedControl?.selectedSegmentIndex {
case .Some(Section.Fetching.rawValue):
case Section.Fetching.rawValue?:
self.performSegueWithIdentifier("FetchingResultsViewController", sender: indexPath)
case .Some(Section.Querying.rawValue):
case Section.Querying.rawValue?:
self.performSegueWithIdentifier("QueryingResultsViewController", sender: indexPath)
default:

View File

@@ -16,19 +16,19 @@ class QueryingResultsViewController: UITableViewController {
switch value {
case .Some(let array as [AnyObject]):
case (let array as [AnyObject])?:
self.values = array.map { (item: AnyObject) -> (title: String, detail: String) in
(
title: item.description,
detail: _stdlib_getDemangledTypeName(item)
detail: String(reflecting: item.dynamicType)
)
}
case .Some(let item):
case let item?:
self.values = [
(
title: item.description,
detail: _stdlib_getDemangledTypeName(item)
detail: String(reflecting: item.dynamicType)
)
]

View File

@@ -21,13 +21,33 @@
"scale" : "3x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@2x.png",
"scale" : "2x"
},
{
"idiom" : "iphone",
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@3x-1.png",
"scale" : "3x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "car",
"filename" : "Icon-60@3x.png",
"scale" : "3x"
}
],

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -0,0 +1,6 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -0,0 +1,21 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "CoreStoreIcon@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -4,6 +4,8 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>CoreStore</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>

View File

@@ -49,10 +49,12 @@ private struct Static {
static let palettes: ListMonitor<Palette> = {
try! CoreStore.addSQLiteStoreAndWait(
fileName: "ColorsDemo.sqlite",
configuration: "ObservingDemo",
resetStoreOnModelMismatch: true
try! CoreStore.addStorageAndWait(
SQLiteStore(
fileName: "ColorsDemo.sqlite",
configuration: "ObservingDemo",
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
return CoreStore.monitorSectionedList(
@@ -88,7 +90,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
UIBarButtonItem(
barButtonSystemItem: .Trash,
target: self,
action: "resetBarButtonItemTouched:"
action: #selector(self.resetBarButtonItemTouched(_:))
)
]
@@ -96,13 +98,13 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
title: Static.filter.rawValue,
style: .Plain,
target: self,
action: "filterBarButtonItemTouched:"
action: #selector(self.filterBarButtonItemTouched(_:))
)
navigationItem.rightBarButtonItems = [
UIBarButtonItem(
barButtonSystemItem: .Add,
target: self,
action: "addBarButtonItemTouched:"
action: #selector(self.addBarButtonItemTouched(_:))
),
filterBarButton
]
@@ -119,7 +121,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
switch (segue.identifier, segue.destinationViewController, sender) {
case (.Some("ObjectObserverDemoViewController"), let destinationViewController as ObjectObserverDemoViewController, let palette as Palette):
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as Palette):
destinationViewController.palette = palette
default:

View File

@@ -61,7 +61,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
let palette = transaction.create(Into(Palette))
palette.setInitialValues()
transaction.commit()
transaction.commitAndWait()
}
let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))!

View File

@@ -30,7 +30,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
super.viewDidLoad()
try! self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite")
try! self.dataStack.addStorageAndWait(SQLiteStore(fileName: "emptyStore.sqlite"))
CoreStore.logger = self
}
@@ -66,7 +66,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
}
}
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func log(error error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
GCDQueue.Main.async { [weak self] in
@@ -74,24 +74,20 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
}
}
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
if condition() {
return
}
let messageString = message()
GCDQueue.Main.async { [weak self] in
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(messageString)\n\n")
}
}
@noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
Swift.fatalError("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)")
}
// MARK: Private
@@ -102,20 +98,21 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
switch self.segmentedControl?.selectedSegmentIndex {
case .Some(0):
case 0?:
self.dataStack.beginAsynchronous { (transaction) -> Void in
transaction.create(Into(Palette))
}
case .Some(1):
do {
try self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite", configuration: "invalidStore")
}
catch _ { }
case 1?:
_ = try? dataStack.addStorageAndWait(
SQLiteStore(
fileName: "emptyStore.sqlite",
configuration: "invalidStore"
)
)
case .Some(2):
case 2?:
self.dataStack.beginAsynchronous { (transaction) -> Void in
transaction.commit()

View File

@@ -39,7 +39,7 @@ class MigrationsDemoViewController: UIViewController {
let alert = UIAlertController(
title: "Migrations Demo",
message: "This demo shows how to run incremental migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
message: "This demo shows how to run progressive migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
preferredStyle: .Alert
)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
@@ -50,22 +50,21 @@ class MigrationsDemoViewController: UIViewController {
(dataStack: DataStack) -> ModelMetadata in
let models = self.models
do {
let migrations = try! dataStack.requiredMigrationsForStorage(
SQLiteStore(fileName: "MigrationDemo.sqlite")
)
guard let storeVersion = migrations.first?.sourceVersion else {
return models.first!
}
for model in models {
let migrations = try dataStack.requiredMigrationsForSQLiteStore(
fileName: "MigrationDemo.sqlite"
)
let storeVersion = migrations.first?.sourceVersion ?? dataStack.modelVersion
for model in models {
if model.version == storeVersion {
if model.version == storeVersion {
return model
}
return model
}
}
catch _ { }
return models.first!
}
@@ -158,40 +157,50 @@ class MigrationsDemoViewController: UIViewController {
)
self.setEnabled(false)
let progress = try! dataStack.addSQLiteStore(
fileName: "MigrationDemo.sqlite",
let progress = dataStack.addStorage(
SQLiteStore(fileName: "MigrationDemo.sqlite"),
completion: { [weak self] (result) -> Void in
guard let strongSelf = self else {
guard let `self` = self else {
return
}
guard case .Success = result else {
strongSelf.setEnabled(true)
self.setEnabled(true)
return
}
strongSelf.setDataStack(dataStack, model: model, scrollToSelection: true)
self.setDataStack(dataStack, model: model, scrollToSelection: true)
let count = dataStack.queryValue(From(model.entityType), Select<Int>(.Count("dna")))
let count = dataStack.queryValue(
From(model.entityType),
Select<Int>(.Count("dna"))
)
if count > 0 {
strongSelf.setEnabled(true)
self.setEnabled(true)
}
else {
dataStack.beginAsynchronous { (transaction) -> Void in
for i: Int64 in 0 ..< 20 {
for i: Int64 in 1 ..< 10000 {
dataStack.beginAsynchronous { (transaction) -> Void in
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
organism.dna = i
organism.mutate()
for j: Int64 in 0 ..< 500 {
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
organism.dna = (i * 500) + j + 1
organism.mutate()
}
transaction.commit()
}
}
dataStack.beginAsynchronous { [weak self] (transaction) -> Void in
transaction.commit { result -> Void in
transaction.commit { _ in
self?.setEnabled(true)
}
@@ -256,8 +265,8 @@ class MigrationsDemoViewController: UIViewController {
else {
self.segmentedControl?.selectedSegmentIndex = UISegmentedControlNoSegment
self._dataStack = nil
self._listMonitor = nil
self._dataStack = nil
}
self.updateDisplay(reloadData: true, scrollToSelection: scrollToSelection, animated: false)
@@ -267,7 +276,7 @@ class MigrationsDemoViewController: UIViewController {
self.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
self.titleLabel?.text = "Migrating: \(progress.localizedDescription)"
self.organismLabel?.text = "Incremental step \(progress.localizedAdditionalDescription)"
self.organismLabel?.text = "Progressive step \(progress.localizedAdditionalDescription)"
}
private func updateDisplay(reloadData reloadData: Bool, scrollToSelection: Bool, animated: Bool) {
@@ -355,16 +364,16 @@ extension MigrationsDemoViewController: UITableViewDataSource, UITableViewDelega
cell.dnaLabel?.text = "DNA: \(dna)"
cell.mutateButtonHandler = { [weak self] _ -> Void in
guard let strongSelf = self,
let dataStack = strongSelf.dataStack,
let organism = strongSelf.listMonitor?[indexPath] else {
guard let `self` = self,
let dataStack = self.dataStack,
let organism = self.listMonitor?[indexPath] else {
return
}
strongSelf.setSelectedIndexPath(indexPath, scrollToSelection: false)
strongSelf.setEnabled(false)
dataStack.beginAsynchronous { (transaction) -> Void in
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
self.setEnabled(false)
dataStack.beginAsynchronous { [weak self] (transaction) -> Void in
let organism = transaction.edit(organism) as! OrganismProtocol
organism.mutate()

View File

@@ -18,15 +18,19 @@ private struct Static {
static let facebookStack: DataStack = {
let dataStack = DataStack(modelName: "StackSetupDemo")
try! dataStack.addSQLiteStoreAndWait(
fileName: "AccountsDemo_FB_Male.sqlite",
configuration: maleConfiguration,
resetStoreOnModelMismatch: true
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_FB_Male.sqlite",
configuration: maleConfiguration,
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
try! dataStack.addSQLiteStoreAndWait(
fileName: "AccountsDemo_FB_Female.sqlite",
configuration: femaleConfiguration,
resetStoreOnModelMismatch: true
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_FB_Female.sqlite",
configuration: femaleConfiguration,
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
dataStack.beginSynchronous { (transaction) -> Void in
@@ -43,7 +47,7 @@ private struct Static {
account2.name = "Jane Doe HCD"
account2.friends = 314
transaction.commit()
transaction.commitAndWait()
}
return dataStack
@@ -52,15 +56,19 @@ private struct Static {
static let twitterStack: DataStack = {
let dataStack = DataStack(modelName: "StackSetupDemo")
try! dataStack.addSQLiteStoreAndWait(
fileName: "AccountsDemo_TW_Male.sqlite",
configuration: maleConfiguration,
resetStoreOnModelMismatch: true
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_TW_Male.sqlite",
configuration: maleConfiguration,
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
try! dataStack.addSQLiteStoreAndWait(
fileName: "AccountsDemo_TW_Female.sqlite",
configuration: femaleConfiguration,
resetStoreOnModelMismatch: true
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_TW_Female.sqlite",
configuration: femaleConfiguration,
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
dataStack.beginSynchronous { (transaction) -> Void in
@@ -77,7 +85,7 @@ private struct Static {
account2.name = "#janedoe_hcd"
account2.friends = 100
transaction.commit()
transaction.commitAndWait()
}
return dataStack

View File

@@ -18,10 +18,12 @@ private struct Static {
static let placeController: ObjectMonitor<Place> = {
try! CoreStore.addSQLiteStoreAndWait(
fileName: "PlaceDemo.sqlite",
configuration: "TransactionsDemo",
resetStoreOnModelMismatch: true
try! CoreStore.addStorageAndWait(
SQLiteStore(
fileName: "PlaceDemo.sqlite",
configuration: "TransactionsDemo",
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
var place = CoreStore.fetchOne(From(Place))
@@ -32,7 +34,7 @@ private struct Static {
let place = transaction.create(Into(Place))
place.setInitialValues()
transaction.commit()
transaction.commitAndWait()
}
place = CoreStore.fetchOne(From(Place))
}
@@ -60,7 +62,10 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
super.viewDidLoad()
let longPressGesture = UILongPressGestureRecognizer(target: self, action: "longPressGestureRecognized:")
let longPressGesture = UILongPressGestureRecognizer(
target: self,
action: #selector(self.longPressGestureRecognized(_:))
)
self.mapView?.addGestureRecognizer(longPressGesture)
Static.placeController.addObserver(self)
@@ -68,7 +73,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
self.navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .Refresh,
target: self,
action: "refreshButtonTapped:"
action: #selector(self.refreshButtonTapped(_:))
)
}
@@ -178,7 +183,7 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
let place = transaction.edit(Static.placeController.object)
place?.setInitialValues()
transaction.commit()
transaction.commitAndWait()
}
}

Binary file not shown.

View File

@@ -0,0 +1,201 @@
//
// BaseTestCase.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
// MARK: - BaseTestCase
class BaseTestCase: XCTestCase {
// MARK: Internal
@nonobjc
func prepareStack<T>(configurations configurations: [String?] = [nil], @noescape _ closure: (dataStack: DataStack) -> T) -> T {
let stack = DataStack(
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType)
)
do {
try configurations.forEach {
try stack.addStorageAndWait(
SQLiteStore(
fileURL: SQLiteStore.defaultRootDirectory
.URLByAppendingPathComponent(NSUUID().UUIDString)!
.URLByAppendingPathComponent("\(self.dynamicType)_\(($0 ?? "-null-")).sqlite")!,
configuration: $0,
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
}
}
catch let error as NSError {
XCTFail(error.coreStoreDumpString)
}
return closure(dataStack: stack)
}
@nonobjc
func expectLogger<T>(expectations: [TestLogger.Expectation], @noescape closure: () -> T) -> T {
CoreStore.logger = TestLogger(self.prepareLoggerExpectations(expectations))
defer {
self.checkExpectationsImmediately()
CoreStore.logger = TestLogger([:])
}
return closure()
}
@nonobjc
func expectLogger(expectations: [TestLogger.Expectation: XCTestExpectation]) {
CoreStore.logger = TestLogger(expectations)
}
@nonobjc
func prepareLoggerExpectations(expectations: [TestLogger.Expectation]) -> [TestLogger.Expectation: XCTestExpectation] {
var testExpectations: [TestLogger.Expectation: XCTestExpectation] = [:]
for expectation in expectations {
testExpectations[expectation] = self.expectationWithDescription("Logger Expectation: \(expectation)")
}
return testExpectations
}
@nonobjc
func checkExpectationsImmediately() {
self.waitForExpectationsWithTimeout(0, handler: nil)
}
@nonobjc
func waitAndCheckExpectations() {
self.waitForExpectationsWithTimeout(10, handler: nil)
}
// MARK: XCTestCase
override func setUp() {
super.setUp()
self.deleteStores()
CoreStore.logger = TestLogger([:])
}
override func tearDown() {
CoreStore.logger = DefaultLogger()
self.deleteStores()
super.tearDown()
}
// MARK: Private
private func deleteStores() {
_ = try? NSFileManager.defaultManager().removeItemAtURL(SQLiteStore.defaultRootDirectory)
}
}
// MARK: - TestLogger
class TestLogger: CoreStoreLogger {
enum Expectation {
case LogWarning
case LogFatal
case LogError
case AssertionFailure
case FatalError
}
init(_ expectations: [Expectation: XCTestExpectation]) {
self.expectations = expectations
}
// MARK: CoreStoreLogger
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
switch level {
case .Warning: self.fulfill(.LogWarning)
case .Fatal: self.fulfill(.LogFatal)
default: break
}
}
func log(error error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
self.fulfill(.LogError)
}
func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
if condition() {
return
}
self.fulfill(.AssertionFailure)
}
func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
self.fulfill(.FatalError)
}
// MARK: Private
private var expectations: [Expectation: XCTestExpectation]
private func fulfill(expectation: Expectation) {
if let instance = self.expectations[expectation] {
instance.fulfill()
self.expectations[expectation] = nil
}
else {
XCTFail("Unexpected Logger Action: \(expectation)")
}
}
}

View File

@@ -0,0 +1,76 @@
//
// BaseTestDataTestCase.swift
// CoreStore
//
// Created by John Rommel Estropia on 2016/06/11.
// Copyright © 2016 John Rommel Estropia. All rights reserved.
//
import Foundation
@testable
import CoreStore
// MARK: - BaseTestDataTestCase
class BaseTestDataTestCase: BaseTestCase {
@nonobjc
let dateFormatter: NSDateFormatter = {
let formatter = NSDateFormatter()
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
formatter.timeZone = NSTimeZone(name: "UTC")
formatter.calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
return formatter
}()
@nonobjc
func prepareTestDataForStack(stack: DataStack, configurations: [String?] = [nil]) {
stack.beginSynchronous { (transaction) in
for (configurationIndex, configuration) in configurations.enumerate() {
let configurationOrdinal = configurationIndex + 1
if configuration == nil || configuration == "Config1" {
for idIndex in 1 ... 5 {
let object = transaction.create(Into<TestEntity1>(configuration))
object.testEntityID = NSNumber(integer: (configurationOrdinal * 100) + idIndex)
object.testNumber = idIndex
object.testDate = self.dateFormatter.dateFromString("2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = (idIndex % 2) == 1
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity1:\(idIndex)"
object.testString = string
object.testData = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
}
}
if configuration == nil || configuration == "Config2" {
for idIndex in 1 ... 5 {
let object = transaction.create(Into<TestEntity2>(configuration))
object.testEntityID = NSNumber(integer: (configurationOrdinal * 200) + idIndex)
object.testNumber = idIndex
object.testDate = self.dateFormatter.dateFromString("2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = (idIndex % 2) == 1
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity2:\(idIndex)"
object.testString = string
object.testData = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
}
}
}
transaction.commitAndWait()
}
}
}

View File

@@ -0,0 +1,30 @@
//
// BridgingTests.h
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#import <XCTest/XCTest.h>
@interface BridgingTests : XCTestCase
@end

View File

@@ -0,0 +1,237 @@
//
// BridgingTests.m
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#import "BridgingTests.h"
#import <CoreStore/CoreStore.h>
#import <CoreStore/CoreStore-Swift.h>
#import "CoreStoreTests-Swift.h"
@import CoreData;
// MARK: - BridgingTests
@implementation BridgingTests
- (void)test_ThatFlags_HaveCorrectValues {
XCTAssertEqual(CSLocalStorageOptionsNone, 0);
XCTAssertEqual(CSLocalStorageOptionsRecreateStoreOnModelMismatch, 1);
XCTAssertEqual(CSLocalStorageOptionsPreventProgressiveMigration, 2);
XCTAssertEqual(CSLocalStorageOptionsAllowSynchronousLightweightMigration, 4);
}
- (void)test_ThatKeyPaths_AreCorrect {
XCTAssertEqualObjects(CSKeyPath(TestEntity1, testNumber), @"testNumber");
XCTAssertEqualObjects(CSKeyPath(TestEntity1, testString), @"testString");
XCTAssertEqualObjects(CSKeyPathOperator(count, TestEntity1, testString), @"@count.testString");
XCTAssertEqualObjects(CSKeyPathOperator(max, TestEntity1, testNumber), @"@max.testNumber");
}
- (void)test_ThatFromClauses_BridgeCorrectly {
{
CSFrom *from = CSFromClass([TestEntity1 class]);
XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]);
XCTAssertNil(from.configurations);
}
{
CSFrom *from = CSFromClass([TestEntity1 class], [NSNull null]);
XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]);
NSArray *configurations = @[[NSNull null]];
XCTAssertEqualObjects(from.configurations, configurations);
}
{
CSFrom *from = CSFromClass([TestEntity1 class], @"Config1");
XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]);
NSArray *configurations = @[@"Config1"];
XCTAssertEqualObjects(from.configurations, configurations);
}
{
CSFrom *from = CSFromClass([TestEntity1 class], @[[NSNull null], @"Config2"]);
XCTAssertEqualObjects(from.entityClass, [TestEntity1 class]);
NSArray *configurations = @[[NSNull null], @"Config2"];
XCTAssertEqualObjects(from.configurations, configurations);
}
}
- (void)test_ThatWhereClauses_BridgeCorrectly {
{
CSWhere *where = CSWhereFormat(@"%K == %@", @"key", @"value");
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"key", @"value"];
XCTAssertEqualObjects(where.predicate, predicate);
}
{
CSWhere *where = CSWhereValue(YES);
NSPredicate *predicate = [NSPredicate predicateWithValue:YES];
XCTAssertEqualObjects(where.predicate, predicate);
}
{
CSWhere *where = CSWherePredicate([NSPredicate predicateWithFormat:@"%K == %@", @"key", @"value"]);
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"%K == %@", @"key", @"value"];
XCTAssertEqualObjects(where.predicate, predicate);
}
}
- (void)test_ThatOrderByClauses_BridgeCorrectly {
{
CSOrderBy *orderBy = CSOrderByKey(CSSortAscending(@"key"));
XCTAssertEqualObjects(orderBy.sortDescriptors, @[[NSSortDescriptor sortDescriptorWithKey:@"key" ascending:YES]]);
}
{
CSOrderBy *orderBy = CSOrderByKey(CSSortDescending(@"key"));
XCTAssertEqualObjects(orderBy.sortDescriptors, @[[NSSortDescriptor sortDescriptorWithKey:@"key" ascending:NO]]);
}
{
CSOrderBy *orderBy = CSOrderByKeys(CSSortAscending(@"key1"), CSSortDescending(@"key2"), nil);
NSArray *sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"key1" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:@"key2" ascending:NO]];
XCTAssertEqualObjects(orderBy.sortDescriptors, sortDescriptors);
}
}
- (void)test_ThatGroupByClauses_BridgeCorrectly {
{
CSGroupBy *groupBy = CSGroupByKeyPath(@"key");
XCTAssertEqualObjects(groupBy.keyPaths, @[@"key"]);
}
{
CSGroupBy *groupBy = CSGroupByKeyPaths(@[@"key1", @"key2"]);
NSArray *keyPaths = @[@"key1", @"key2"];
XCTAssertEqualObjects(groupBy.keyPaths, keyPaths);
}
}
- (void)test_ThatTweakClauses_BridgeCorrectly {
CSTweak *tweak = CSTweakRequest(^(NSFetchRequest * _Nonnull fetchRequest) {
fetchRequest.fetchLimit = 100;
});
NSFetchRequest *request = [NSFetchRequest new];
tweak.block(request);
XCTAssertEqual(request.fetchLimit, 100);
}
- (void)test_ThatIntoClauses_BridgeCorrectly {
{
CSInto *into = CSIntoClass([TestEntity1 class]);
XCTAssertEqualObjects(into.entityClass, [TestEntity1 class]);
}
{
CSInto *into = CSIntoClass([TestEntity1 class], [NSNull null]);
XCTAssertEqualObjects(into.entityClass, [TestEntity1 class]);
XCTAssertNil(into.configuration);
}
{
CSInto *into = CSIntoClass([TestEntity1 class], @"Config1");
XCTAssertEqualObjects(into.entityClass, [TestEntity1 class]);
XCTAssertEqualObjects(into.configuration, @"Config1");
}
}
- (void)test_ThatDataStacks_BridgeCorrectly {
CSDataStack *dataStack = [[CSDataStack alloc]
initWithModelName:@"Model"
bundle:[NSBundle bundleForClass:[self class]]
versionChain:nil];
XCTAssertNotNil(dataStack);
[CSCoreStore setDefaultStack:dataStack];
XCTAssertTrue([dataStack isEqual:[CSCoreStore defaultStack]]);
}
- (void)test_ThatStorages_BridgeCorrectly {
NSError *memoryError;
CSInMemoryStore *memoryStorage = [CSCoreStore
addInMemoryStorageAndWait:[CSInMemoryStore new]
error:&memoryError];
XCTAssertNotNil(memoryStorage);
XCTAssertEqualObjects([[memoryStorage class] storeType], [CSInMemoryStore storeType]);
XCTAssertEqualObjects([[memoryStorage class] storeType], NSInMemoryStoreType);
XCTAssertNil(memoryStorage.configuration);
XCTAssertNil(memoryStorage.storeOptions);
XCTAssertNil(memoryError);
NSError *sqliteError;
CSSQLiteStore *sqliteStorage = [CSCoreStore
addSQLiteStorageAndWait:[CSSQLiteStore new]
error:&sqliteError];
XCTAssertNotNil(sqliteStorage);
XCTAssertEqualObjects([[sqliteStorage class] storeType], [CSSQLiteStore storeType]);
XCTAssertEqualObjects([[sqliteStorage class] storeType], NSSQLiteStoreType);
XCTAssertNil(sqliteStorage.configuration);
XCTAssertEqualObjects(sqliteStorage.storeOptions, @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" } });
XCTAssertNil(sqliteError);
}
- (void)test_ThatTransactions_BridgeCorrectly {
[CSCoreStore
setDefaultStack:[[CSDataStack alloc]
initWithModelName:@"Model"
bundle:[NSBundle bundleForClass:[self class]]
versionChain:nil]];
[CSCoreStore
addInMemoryStorageAndWait:[CSInMemoryStore new]
error:nil];
{
CSUnsafeDataTransaction *transaction = [CSCoreStore beginUnsafe];
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSUnsafeDataTransaction class]]);
}
{
XCTestExpectation *expectation = [self expectationWithDescription:@"sync"];
[CSCoreStore beginSynchronous:^(CSSynchronousDataTransaction * _Nonnull transaction) {
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSSynchronousDataTransaction class]]);
[expectation fulfill];
}];
}
{
XCTestExpectation *expectation = [self expectationWithDescription:@"async"];
[CSCoreStore beginAsynchronous:^(CSAsynchronousDataTransaction * _Nonnull transaction) {
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSAsynchronousDataTransaction class]]);
[expectation fulfill];
}];
}
[self waitForExpectationsWithTimeout:10 handler:nil];
}
@end

View File

@@ -0,0 +1,5 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//
#import "BridgingTests.h"

View File

@@ -1,389 +0,0 @@
//
// CoreStoreTests.swift
// CoreStoreTests
//
// Copyright © 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
class CoreStoreTests: XCTestCase {
override func setUp() {
super.setUp()
self.deleteStores()
}
override func tearDown() {
self.deleteStores()
super.tearDown()
}
func testMigrationChains() {
let emptyChain: MigrationChain = nil
XCTAssertTrue(emptyChain.valid, "emptyChain.valid")
XCTAssertTrue(emptyChain.empty, "emptyChain.empty")
let normalChain: MigrationChain = "version1"
XCTAssertTrue(normalChain.valid, "normalChain.valid")
XCTAssertTrue(normalChain.empty, "normalChain.empty")
let linearChain: MigrationChain = ["version1", "version2", "version3", "version4"]
XCTAssertTrue(linearChain.valid, "linearChain.valid")
XCTAssertFalse(linearChain.empty, "linearChain.empty")
let treeChain: MigrationChain = [
"version1": "version4",
"version2": "version3",
"version3": "version4"
]
XCTAssertTrue(treeChain.valid, "treeChain.valid")
XCTAssertFalse(treeChain.empty, "treeChain.empty")
// The cases below will trigger assertion failures internally
// let linearLoopChain: MigrationChain = ["version1", "version2", "version1", "version3", "version4"]
// XCTAssertFalse(linearLoopChain.valid, "linearLoopChain.valid")
//
// let treeAmbiguousChain: MigrationChain = [
// "version1": "version4",
// "version2": "version3",
// "version1": "version2",
// "version3": "version4"
// ]
// XCTAssertFalse(treeAmbiguousChain.valid, "treeAmbiguousChain.valid")
}
func testExample() {
let stack = DataStack(modelName: "Model", bundle: NSBundle(forClass: self.dynamicType))
CoreStore.defaultStack = stack
XCTAssert(CoreStore.defaultStack === stack, "CoreStore.defaultStack === stack")
do {
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore1.sqlite", configuration: "Config1", resetStoreOnModelMismatch: true)
}
catch let error as NSError {
XCTFail(error.description)
}
do {
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore2.sqlite", configuration: "Config2", resetStoreOnModelMismatch: true)
}
catch let error as NSError {
XCTFail(error.description)
}
let unsafeTransaction = CoreStore.beginUnsafe()
let createExpectation = self.expectationWithDescription("Entity creation")
CoreStore.beginAsynchronous { (transaction) -> Void in
let obj1 = transaction.create(Into(TestEntity1))
obj1.testEntityID = 1
obj1.testString = "lololol"
obj1.testNumber = 42
obj1.testDate = NSDate()
let count = transaction.queryValue(
From<TestEntity1>(),
Select<Int>(.Count("testNumber"))
)
XCTAssertTrue(count == 0, "count == 0 (actual: \(count))") // counts only objects in store
let obj2 = transaction.create(Into<TestEntity2>())
obj2.testEntityID = 2
obj2.testString = "hahaha"
obj2.testNumber = 100
obj2.testDate = NSDate()
let obj3 = transaction.create(Into<TestEntity2>("Config2"))
obj3.testEntityID = 3
obj3.testString = "hahaha"
obj3.testNumber = 90
obj3.testDate = NSDate()
let obj4 = transaction.create(Into(TestEntity2.self, "Config2"))
obj4.testEntityID = 5
obj4.testString = "hohoho"
obj4.testNumber = 80
obj4.testDate = NSDate()
transaction.beginSynchronous { (transaction) -> Void in
let obj4 = transaction.create(Into<TestEntity2>())
obj4.testEntityID = 4
obj4.testString = "hehehehe"
obj4.testNumber = 80
obj4.testDate = NSDate()
let objs4test = transaction.fetchOne(
From<TestEntity2>("Config2"),
Where("testEntityID", isEqualTo: 4),
Tweak { (fetchRequest) -> Void in
fetchRequest.includesPendingChanges = true
}
)
XCTAssertNotNil(objs4test, "objs4test != nil")
let objs5test = transaction.fetchOne(
From(TestEntity2),
Where("testEntityID", isEqualTo: 4),
Tweak { (fetchRequest) -> Void in
fetchRequest.includesPendingChanges = false
}
)
XCTAssertNil(objs5test, "objs5test == nil")
// Dont commit1
}
transaction.commit { (result) -> Void in
let objs4test = CoreStore.fetchOne(
From(TestEntity2),
Where("testEntityID", isEqualTo: 4),
Tweak { (fetchRequest) -> Void in
fetchRequest.includesPendingChanges = false
}
)
XCTAssertNil(objs4test, "objs4test == nil")
let objs5test = unsafeTransaction.fetchCount(From(TestEntity2))
XCTAssertTrue(objs5test == 3, "objs5test == 3")
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges, "hasChanges == true")
createExpectation.fulfill()
case .Failure(let error):
XCTFail(error.description)
}
}
}
let queryExpectation = self.expectationWithDescription("Query creation")
CoreStore.beginAsynchronous { (transaction) -> Void in
let obj1 = transaction.fetchOne(From(TestEntity1))
XCTAssertNotNil(obj1, "obj1 != nil")
var orderBy = OrderBy(.Ascending("testEntityID"))
orderBy += OrderBy(.Descending("testString"))
let objs2 = transaction.fetchAll(
From(TestEntity2),
Where("testNumber", isEqualTo: 100) || Where("%K == %@", "testNumber", 90),
orderBy,
Tweak { (fetchRequest) -> Void in
fetchRequest.includesPendingChanges = true
}
)
XCTAssertNotNil(objs2, "objs2 != nil")
XCTAssertTrue(objs2?.count == 2, "objs2?.count == 2")
transaction.commit { (result) -> Void in
let counts = CoreStore.queryAttributes(
From(TestEntity2),
Select("testString", .Count("testString", As: "count")),
GroupBy("testString")
)
print(counts)
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
switch result {
case .Success(let hasChanges):
XCTAssertFalse(hasChanges, "hasChanges == false")
queryExpectation.fulfill()
case .Failure(let error):
XCTFail(error.description)
}
}
}
self.waitForExpectationsWithTimeout(100, handler: nil)
let max1 = CoreStore.queryValue(
From(TestEntity2),
Select<Int>(.Maximum("testNumber"))
)
XCTAssertTrue(max1 == 100, "max == 100 (actual: \(max1))")
let max2 = CoreStore.queryValue(
From(TestEntity2),
Select<NSNumber>(.Maximum("testNumber")),
Where("%K > %@", "testEntityID", 2)
)
XCTAssertTrue(max2 == 90, "max == 90 (actual: \(max2))")
CoreStore.beginSynchronous { (transaction) -> Void in
let numberOfDeletedObjects1 = transaction.deleteAll(From(TestEntity1))
XCTAssertTrue(numberOfDeletedObjects1 == 1, "numberOfDeletedObjects1 == 1 (actual: \(numberOfDeletedObjects1))")
let numberOfDeletedObjects2 = transaction.deleteAll(
From(TestEntity2),
Where("%K > %@", "testEntityID", 2)
)
XCTAssertTrue(numberOfDeletedObjects2 == 2, "numberOfDeletedObjects2 == 2 (actual: \(numberOfDeletedObjects2))")
transaction.commit()
}
CoreStore.beginSynchronous({ (transaction) -> Void in
if let obj = CoreStore.fetchOne(From(TestEntity2)) {
let oldID = obj.testEntityID
obj.testEntityID = 0
obj.testEntityID = oldID
}
transaction.commit()
})
let objs1 = CoreStore.fetchAll(From(TestEntity1))
XCTAssertNotNil(objs1, "objs1 != nil")
XCTAssertTrue(objs1?.count == 0, "objs1?.count == 0")
let objs2 = CoreStore.fetchAll(From(TestEntity2))
XCTAssertNotNil(objs2, "objs2 != nil")
XCTAssertTrue(objs2?.count == 1, "objs2?.count == 1")
let unsafeExpectation = self.expectationWithDescription("Query creation")
let obj5 = unsafeTransaction.create(Into<TestEntity1>("Config1"))
obj5.testEntityID = 5
obj5.testString = "hihihi"
obj5.testNumber = 70
obj5.testDate = NSDate()
XCTAssert(unsafeTransaction === obj5.unsafeDataTransaction, "unsafeTransaction === obj5.unsafeDataTransaction")
unsafeTransaction.commit { (result) -> Void in
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges, "hasChanges == true")
CoreStore.beginSynchronous { (transaction) -> Void in
let obj5Copy1 = transaction.edit(obj5)
XCTAssertTrue(obj5.objectID == obj5Copy1?.objectID, "obj5.objectID == obj5Copy1?.objectID")
XCTAssertFalse(obj5 == obj5Copy1, "obj5 == obj5Copy1")
XCTAssertNil(obj5Copy1?.unsafeDataTransaction)
let obj5Copy2 = transaction.edit(Into(TestEntity1), obj5.objectID)
XCTAssertTrue(obj5.objectID == obj5Copy2?.objectID, "obj5.objectID == obj5Copy2?.objectID")
XCTAssertFalse(obj5 == obj5Copy2, "obj5 == obj5Copy2")
}
let count: Int? = CoreStore.queryValue(
From(TestEntity1),
Select(.Count("testNumber"))
)
XCTAssertTrue(count == 1, "count == 1 (actual: \(count))")
let obj6 = unsafeTransaction.create(Into<TestEntity1>())
obj6.testEntityID = 6
obj6.testString = "huehuehue"
obj6.testNumber = 130
obj6.testDate = NSDate()
XCTAssert(unsafeTransaction === obj6.unsafeDataTransaction, "unsafeTransaction === obj6.unsafeDataTransaction")
unsafeTransaction.commit { (result) -> Void in
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges, "hasChanges == true")
let count = CoreStore.queryValue(
From(TestEntity1),
Select<Int>(.Count("testNumber"))
)
XCTAssertTrue(count == 2, "count == 2 (actual: \(count))")
CoreStore.beginSynchronous { (transaction) -> Void in
let obj6 = transaction.edit(obj6)
let obj5 = transaction.edit(obj5)
transaction.delete(obj5, obj6)
transaction.commit()
}
let count2 = CoreStore.queryValue(
From(TestEntity1),
Select<Int>(.Count("testNumber"))
)
XCTAssertTrue(count2 == 0, "count == 0 (actual: \(count2))")
unsafeExpectation.fulfill()
case .Failure(let error):
XCTFail(error.description)
}
}
case .Failure(let error):
XCTFail(error.description)
}
}
self.waitForExpectationsWithTimeout(100, handler: nil)
}
private func deleteStores() {
do {
let fileManager = NSFileManager.defaultManager()
try fileManager.removeItemAtURL(
fileManager.URLsForDirectory(deviceDirectorySearchPath, inDomains: .UserDomainMask).first!
)
}
catch _ { }
}
}

View File

@@ -0,0 +1,169 @@
//
// ErrorTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
// MARK: - ErrorTests
final class ErrorTests: XCTestCase {
@objc
dynamic func test_ThatUnknownErrors_BridgeCorrectly() {
let error = CoreStoreError.Unknown
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.UnknownError.rawValue)
let userInfo: NSDictionary = [:]
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.UnknownError.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.UnknownError.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
@objc
dynamic func test_ThatDifferentStorageExistsAtURLErrors_BridgeCorrectly() {
let dummyURL = NSURL(string: "file:///test1/test2.sqlite")!
let error = CoreStoreError.DifferentStorageExistsAtURL(existingPersistentStoreURL: dummyURL)
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue)
let userInfo: NSDictionary = [
"existingPersistentStoreURL": dummyURL
]
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
@objc
dynamic func test_ThatMappingModelNotFoundErrors_BridgeCorrectly() {
let dummyURL = NSURL(string: "file:///test1/test2.sqlite")!
let model = NSManagedObjectModel.fromBundle(NSBundle(forClass: self.dynamicType), modelName: "Model")
let version = "1.0.0"
let error = CoreStoreError.MappingModelNotFound(localStoreURL: dummyURL, targetModel: model, targetModelVersion: version)
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.MappingModelNotFound.rawValue)
let userInfo: NSDictionary = [
"localStoreURL": dummyURL,
"targetModel": model,
"targetModelVersion": version
]
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.MappingModelNotFound.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.MappingModelNotFound.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
@objc
dynamic func test_ThatProgressiveMigrationRequiredErrors_BridgeCorrectly() {
let dummyURL = NSURL(string: "file:///test1/test2.sqlite")!
let error = CoreStoreError.ProgressiveMigrationRequired(localStoreURL: dummyURL)
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue)
let userInfo: NSDictionary = [
"localStoreURL": dummyURL
]
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
@objc
dynamic func test_ThatInternalErrorErrors_BridgeCorrectly() {
let internalError = NSError(
domain: "com.dummy",
code: 123,
userInfo: [
"key1": "value1",
"key2": 2,
"key3": NSDate()
]
)
let error = CoreStoreError(internalError)
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.InternalError.rawValue)
let userInfo: NSDictionary = [
"NSError": internalError
]
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.InternalError.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.InternalError.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,429 @@
//
// FromTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
//MARK: - FromTests
final class FromTests: BaseTestCase {
@objc
dynamic func test_ThatFromClauses_ConfigureCorrectly() {
do {
let from = From()
XCTAssert(from.entityClass === NSManagedObject.self)
XCTAssertNil(from.configurations)
}
do {
let from = From<TestEntity1>()
XCTAssert(from.entityClass === TestEntity1.self)
XCTAssertNil(from.configurations)
}
do {
let from = From<TestEntity1>("Config1")
XCTAssert(from.entityClass === TestEntity1.self)
XCTAssertEqual(from.configurations?.count, 1)
XCTAssertEqual(from.configurations?[0], "Config1")
}
do {
let from = From<TestEntity1>(nil, "Config1")
XCTAssert(from.entityClass === TestEntity1.self)
XCTAssertEqual(from.configurations?.count, 2)
XCTAssertEqual(from.configurations?[0], nil)
XCTAssertEqual(from.configurations?[1], "Config1")
}
}
@objc
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultConfigurations() {
self.prepareStack { (dataStack) in
do {
let from = From<TestEntity1>()
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
}
do {
let from = From<TestEntity1>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
}
}
@objc
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForSingleConfigurations() {
self.prepareStack(configurations: ["Config1"]) { (dataStack) in
do {
let from = From<TestEntity1>()
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>()
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
}
}
@objc
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultAndCustomConfigurations() {
self.prepareStack(configurations: [nil, "Config1"]) { (dataStack) in
do {
let from = From<TestEntity1>()
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(Set(affectedConfigurations), ["PF_DEFAULT_CONFIGURATION_NAME", "Config1"] as Set)
}
do {
let from = From<TestEntity1>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>()
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
}
do {
let from = From<TestEntity2>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
}
}
@objc
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForMultipleConfigurations() {
self.prepareStack(configurations: ["Config1", "Config2"]) { (dataStack) in
do {
let from = From<TestEntity1>()
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>()
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config2"])
}
do {
let from = From<TestEntity2>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config2"])
}
}
}
}

View File

@@ -0,0 +1,81 @@
//
// GroupByTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
//MARK: - GroupByTests
final class GroupByTests: BaseTestCase {
@objc
dynamic func test_ThatGroupByClauses_ConfigureCorrectly() {
do {
let groupBy = GroupBy()
XCTAssertEqual(groupBy, GroupBy([] as [String]))
XCTAssertNotEqual(groupBy, GroupBy("key"))
XCTAssertTrue(groupBy.keyPaths.isEmpty)
}
do {
let groupBy = GroupBy("key1")
XCTAssertEqual(groupBy, GroupBy("key1"))
XCTAssertEqual(groupBy, GroupBy(["key1"]))
XCTAssertNotEqual(groupBy, GroupBy("key2"))
XCTAssertEqual(groupBy.keyPaths, ["key1"])
}
do {
let groupBy = GroupBy("key1", "key2")
XCTAssertEqual(groupBy, GroupBy("key1", "key2"))
XCTAssertEqual(groupBy, GroupBy(["key1", "key2"]))
XCTAssertNotEqual(groupBy, GroupBy("key2", "key1"))
XCTAssertEqual(groupBy.keyPaths, ["key1", "key2"])
}
}
@objc
dynamic func test_ThatGroupByClauses_ApplyToFetchRequestsCorrectly() {
self.prepareStack { (dataStack) in
let groupBy = GroupBy("testString")
let request = CoreStoreFetchRequest()
_ = From(TestEntity1).applyToFetchRequest(request, context: dataStack.mainContext)
groupBy.applyToFetchRequest(request)
XCTAssertNotNil(request.propertiesToGroupBy)
let attributes = (request.propertiesToGroupBy ?? []) as! [NSAttributeDescription]
XCTAssertEqual(attributes.map { $0.name }, groupBy.keyPaths)
}
}
}

View File

@@ -0,0 +1,982 @@
//
// ImportTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
// MARK: - ImportTests
class ImportTests: BaseTestDataTestCase {
@objc
dynamic func test_ThatImportObject_CanSkipImport() {
self.prepareStack { (stack) in
stack.beginSynchronous { (transaction) in
do {
let object = try transaction.importObject(
Into(TestEntity1),
source: [
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 1),
"testDecimal": NSDecimalNumber(string: "1"),
"testString": "nil:TestEntity1:1",
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!,
"skip_insert": ""
]
)
XCTAssertNil(object)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 0)
}
catch {
XCTFail()
}
}
}
}
@objc
dynamic func test_ThatImportObject_CanThrowError() {
self.prepareStack { (stack) in
stack.beginSynchronous { (transaction) in
let errorExpectation = self.expectationWithDescription("error")
do {
let _ = try transaction.importObject(
Into(TestEntity1),
source: [
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 1),
"testDecimal": NSDecimalNumber(string: "1"),
"testString": "nil:TestEntity1:1",
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!,
"throw_on_insert": ""
]
)
XCTFail()
}
catch _ as TestInsertError {
errorExpectation.fulfill()
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
let object = transaction.fetchOne(From(TestEntity1))
XCTAssertNotNil(object)
XCTAssertNil(object?.testEntityID)
XCTAssertNil(object?.testBoolean)
XCTAssertNil(object?.testNumber)
XCTAssertNil(object?.testDecimal)
XCTAssertNil(object?.testString)
XCTAssertNil(object?.testData)
XCTAssertNil(object?.testDate)
}
catch {
XCTFail()
}
self.checkExpectationsImmediately()
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportObject_CanImportCorrectly() {
self.prepareStack { (stack) in
stack.beginSynchronous { (transaction) in
do {
let object = try transaction.importObject(
Into(TestEntity1),
source: [
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 1),
"testDecimal": NSDecimalNumber(string: "1"),
"testString": "nil:TestEntity1:1",
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!
]
)
XCTAssertNotNil(object)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
XCTAssertNil(object?.testEntityID)
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 1))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!)
try transaction.importObject(
object!,
source: [
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 2),
"testDecimal": NSDecimalNumber(string: "2"),
"testString": "nil:TestEntity1:2",
"testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!
]
)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
XCTAssertNil(object?.testEntityID)
XCTAssertEqual(object?.testBoolean, NSNumber(bool: false))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 2))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "2"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:2")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!)
}
catch {
XCTFail()
}
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportObjects_CanSkipImport() {
self.prepareStack { (stack) in
stack.beginSynchronous { (transaction) in
do {
let sourceArray: [TestEntity1.ImportSource] = [
[
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 1),
"testDecimal": NSDecimalNumber(string: "1"),
"testString": "nil:TestEntity1:1",
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!,
"skip_insert": ""
],
[
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 2),
"testDecimal": NSDecimalNumber(string: "2"),
"testString": "nil:TestEntity1:2",
"testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!
]
]
let objects = try transaction.importObjects(
Into(TestEntity1),
sourceArray: sourceArray
)
XCTAssertEqual(objects.count, 1)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
let object = objects[0]
let dictionary = sourceArray[1]
XCTAssertNil(object.testEntityID)
XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber)
XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber)
XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber)
XCTAssertEqual(object.testString, dictionary["testString"] as? String)
XCTAssertEqual(object.testData, dictionary["testData"] as? NSData)
XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate)
}
catch {
XCTFail()
}
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportObjects_CanThrowError() {
self.prepareStack { (stack) in
stack.beginSynchronous { (transaction) in
let errorExpectation = self.expectationWithDescription("error")
do {
let sourceArray: [TestEntity1.ImportSource] = [
[
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 1),
"testDecimal": NSDecimalNumber(string: "1"),
"testString": "nil:TestEntity1:1",
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!,
"throw_on_insert": ""
],
[
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 2),
"testDecimal": NSDecimalNumber(string: "2"),
"testString": "nil:TestEntity1:2",
"testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!
]
]
let _ = try transaction.importObjects(
Into(TestEntity1),
sourceArray: sourceArray
)
XCTFail()
}
catch _ as TestInsertError {
errorExpectation.fulfill()
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 1)
let object = transaction.fetchOne(From(TestEntity1))
XCTAssertNotNil(object)
XCTAssertNil(object?.testEntityID)
XCTAssertNil(object?.testBoolean)
XCTAssertNil(object?.testNumber)
XCTAssertNil(object?.testDecimal)
XCTAssertNil(object?.testString)
XCTAssertNil(object?.testData)
XCTAssertNil(object?.testDate)
}
catch {
XCTFail()
}
self.checkExpectationsImmediately()
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportObjects_CanImportCorrectly() {
self.prepareStack { (stack) in
stack.beginSynchronous { (transaction) in
do {
let sourceArray: [TestEntity1.ImportSource] = [
[
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 1),
"testDecimal": NSDecimalNumber(string: "1"),
"testString": "nil:TestEntity1:1",
"testData": ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!
],
[
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 2),
"testDecimal": NSDecimalNumber(string: "2"),
"testString": "nil:TestEntity1:2",
"testData": ("nil:TestEntity1:2" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-02T00:00:00Z")!
]
]
let objects = try transaction.importObjects(
Into(TestEntity1),
sourceArray: sourceArray
)
XCTAssertEqual(objects.count, sourceArray.count)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 2)
for i in 0 ..< sourceArray.count {
let object = objects[i]
let dictionary = sourceArray[i]
XCTAssertNil(object.testEntityID)
XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber)
XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber)
XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber)
XCTAssertEqual(object.testString, dictionary["testString"] as? String)
XCTAssertEqual(object.testData, dictionary["testData"] as? NSData)
XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate)
}
}
catch {
XCTFail()
}
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportUniqueObject_CanSkipImport() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
stack.beginSynchronous { (transaction) in
do {
let object = try transaction.importUniqueObject(
Into(TestEntity1),
source: [
"testEntityID": NSNumber(integer: 106),
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
"skip_insert": ""
]
)
XCTAssertNil(object)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5)
}
catch {
XCTFail()
}
do {
let object = try transaction.importUniqueObject(
Into(TestEntity1),
source: [
"testEntityID": NSNumber(integer: 105),
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
"skip_update": ""
]
)
XCTAssertNil(object)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5)
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
XCTAssertNotNil(existingObjects)
XCTAssertEqual(existingObjects?.count, 1)
let existingObject = existingObjects?[0]
XCTAssertEqual(existingObject?.testEntityID, NSNumber(integer: 105))
XCTAssertEqual(existingObject?.testBoolean, NSNumber(bool: true))
XCTAssertEqual(existingObject?.testNumber, NSNumber(integer: 5))
XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5"))
XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5")
XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(existingObject?.testDate, self.dateFormatter.dateFromString("2000-01-05T00:00:00Z")!)
}
catch {
XCTFail()
}
}
}
}
@objc
dynamic func test_ThatImportUniqueObject_CanThrowError() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
stack.beginSynchronous { (transaction) in
do {
let errorExpectation = self.expectationWithDescription("error")
do {
let _ = try transaction.importUniqueObject(
Into(TestEntity1),
source: [
"testEntityID": NSNumber(integer: 106),
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
"throw_on_insert": ""
]
)
XCTFail()
}
catch _ as TestInsertError {
errorExpectation.fulfill()
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
let object = transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 106))
XCTAssertNotNil(object)
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106))
XCTAssertNil(object?.testBoolean)
XCTAssertNil(object?.testNumber)
XCTAssertNil(object?.testDecimal)
XCTAssertNil(object?.testString)
XCTAssertNil(object?.testData)
XCTAssertNil(object?.testDate)
}
catch {
XCTFail()
}
self.checkExpectationsImmediately()
}
do {
let errorExpectation = self.expectationWithDescription("error")
do {
let _ = try transaction.importUniqueObject(
Into(TestEntity1),
source: [
"testEntityID": NSNumber(integer: 105),
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
"throw_on_update": ""
]
)
XCTFail()
}
catch _ as TestUpdateError {
errorExpectation.fulfill()
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
XCTAssertNotNil(existingObjects)
XCTAssertEqual(existingObjects?.count, 1)
let existingObject = existingObjects?[0]
XCTAssertNotNil(existingObject)
XCTAssertEqual(existingObject?.testEntityID, NSNumber(integer: 105))
XCTAssertEqual(existingObject?.testBoolean, NSNumber(bool: true))
XCTAssertEqual(existingObject?.testNumber, NSNumber(integer: 5))
XCTAssertEqual(existingObject?.testDecimal, NSDecimalNumber(string: "5"))
XCTAssertEqual(existingObject?.testString, "nil:TestEntity1:5")
XCTAssertEqual(existingObject?.testData, ("nil:TestEntity1:5" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(existingObject?.testDate, self.dateFormatter.dateFromString("2000-01-05T00:00:00Z")!)
}
catch {
XCTFail()
}
self.checkExpectationsImmediately()
}
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportUniqueObject_CanImportCorrectly() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
stack.beginSynchronous { (transaction) in
do {
let object = try transaction.importUniqueObject(
Into(TestEntity1),
source: [
"testEntityID": NSNumber(integer: 106),
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!
]
)
XCTAssertNotNil(object)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106))
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 6))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "6"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:6")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!)
}
catch {
XCTFail()
}
do {
let object = try transaction.importUniqueObject(
Into(TestEntity1),
source: [
"testEntityID": NSNumber(integer: 106),
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 7),
"testDecimal": NSDecimalNumber(string: "7"),
"testString": "nil:TestEntity1:7",
"testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!,
]
)
XCTAssertNotNil(object)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106))
XCTAssertEqual(object?.testBoolean, NSNumber(bool: false))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 7))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "7"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:7")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!)
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 106))
XCTAssertNotNil(existingObjects)
XCTAssertEqual(existingObjects?.count, 1)
let existingObject = existingObjects?[0]
XCTAssertEqual(existingObject, object)
}
catch {
XCTFail()
}
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportUniqueObjects_CanSkipImport() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
stack.beginSynchronous { (transaction) in
do {
let sourceArray: [TestEntity1.ImportSource] = [
[
"testEntityID": NSNumber(integer: 106),
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
"skip_insert": ""
],
[
"testEntityID": NSNumber(integer: 107),
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 7),
"testDecimal": NSDecimalNumber(string: "7"),
"testString": "nil:TestEntity1:7",
"testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!
]
]
let objects = try transaction.importUniqueObjects(
Into(TestEntity1),
sourceArray: sourceArray
)
XCTAssertEqual(objects.count, 1)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
let object = objects[0]
let dictionary = sourceArray[1]
XCTAssertEqual(object.testEntityID, dictionary["testEntityID"] as? NSNumber)
XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber)
XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber)
XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber)
XCTAssertEqual(object.testString, dictionary["testString"] as? String)
XCTAssertEqual(object.testData, dictionary["testData"] as? NSData)
XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate)
}
catch {
XCTFail()
}
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportUniqueObjects_CanThrowError() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
stack.beginSynchronous { (transaction) in
let errorExpectation = self.expectationWithDescription("error")
do {
let sourceArray: [TestEntity1.ImportSource] = [
[
"testEntityID": NSNumber(integer: 106),
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
"throw_on_id": ""
],
[
"testEntityID": NSNumber(integer: 107),
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 7),
"testDecimal": NSDecimalNumber(string: "7"),
"testString": "nil:TestEntity1:7",
"testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!
]
]
let _ = try transaction.importUniqueObjects(
Into(TestEntity1),
sourceArray: sourceArray
)
XCTFail()
}
catch _ as TestIDError {
errorExpectation.fulfill()
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5)
XCTAssertNil(transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 106)))
XCTAssertNil(transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 107)))
}
catch {
XCTFail()
}
self.checkExpectationsImmediately()
transaction.context.reset()
}
stack.beginSynchronous { (transaction) in
let errorExpectation = self.expectationWithDescription("error")
do {
let sourceArray: [TestEntity1.ImportSource] = [
[
"testEntityID": NSNumber(integer: 106),
"testBoolean": NSNumber(bool: true),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
"throw_on_insert": ""
],
[
"testEntityID": NSNumber(integer: 107),
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 7),
"testDecimal": NSDecimalNumber(string: "7"),
"testString": "nil:TestEntity1:7",
"testData": ("nil:TestEntity1:7" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-07T00:00:00Z")!
]
]
let _ = try transaction.importUniqueObjects(
Into(TestEntity1),
sourceArray: sourceArray
)
XCTFail()
}
catch _ as TestInsertError {
errorExpectation.fulfill()
let object = transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 106))
XCTAssertNotNil(object)
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 106))
XCTAssertNil(object?.testBoolean)
XCTAssertNil(object?.testNumber)
XCTAssertNil(object?.testDecimal)
XCTAssertNil(object?.testString)
XCTAssertNil(object?.testData)
XCTAssertNil(object?.testDate)
}
catch {
XCTFail()
}
self.checkExpectationsImmediately()
transaction.context.reset()
}
stack.beginSynchronous { (transaction) in
let errorExpectation = self.expectationWithDescription("error")
do {
let sourceArray: [TestEntity1.ImportSource] = [
[
"testEntityID": NSNumber(integer: 105),
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!,
"throw_on_update": ""
]
]
let _ = try transaction.importUniqueObjects(
Into(TestEntity1),
sourceArray: sourceArray
)
XCTFail()
}
catch _ as TestUpdateError {
errorExpectation.fulfill()
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 5)
let object = transaction.fetchOne(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
XCTAssertNotNil(object)
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 105))
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 5))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "5"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:5")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:5" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-05T00:00:00Z")!)
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
XCTAssertNotNil(existingObjects)
XCTAssertEqual(existingObjects?.count, 1)
let existingObject = existingObjects?[0]
XCTAssertEqual(existingObject, object)
}
catch {
XCTFail()
}
self.checkExpectationsImmediately()
transaction.context.reset()
}
}
}
@objc
dynamic func test_ThatImportUniqueObjects_CanImportCorrectly() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
stack.beginSynchronous { (transaction) in
do {
let sourceArray: [TestEntity1.ImportSource] = [
[
"testEntityID": NSNumber(integer: 105),
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 15),
"testDecimal": NSDecimalNumber(string: "15"),
"testString": "nil:TestEntity1:15",
"testData": ("nil:TestEntity1:15" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-15T00:00:00Z")!
],
[
"testEntityID": NSNumber(integer: 106),
"testBoolean": NSNumber(bool: false),
"testNumber": NSNumber(integer: 6),
"testDecimal": NSDecimalNumber(string: "6"),
"testString": "nil:TestEntity1:6",
"testData": ("nil:TestEntity1:6" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!,
"testDate": self.dateFormatter.dateFromString("2000-01-06T00:00:00Z")!
]
]
let objects = try transaction.importUniqueObjects(
Into(TestEntity1),
sourceArray: sourceArray
)
XCTAssertEqual(objects.count, sourceArray.count)
XCTAssertEqual(transaction.fetchCount(From(TestEntity1)), 6)
for i in 0 ..< sourceArray.count {
let object = objects[i]
let dictionary = sourceArray[i]
XCTAssertEqual(object.testEntityID, dictionary["testEntityID"] as? NSNumber)
XCTAssertEqual(object.testBoolean, dictionary["testBoolean"] as? NSNumber)
XCTAssertEqual(object.testNumber, dictionary["testNumber"] as? NSNumber)
XCTAssertEqual(object.testDecimal, dictionary["testDecimal"] as? NSDecimalNumber)
XCTAssertEqual(object.testString, dictionary["testString"] as? String)
XCTAssertEqual(object.testData, dictionary["testData"] as? NSData)
XCTAssertEqual(object.testDate, dictionary["testDate"] as? NSDate)
}
let existingObjects = transaction.fetchAll(From(TestEntity1), Where("testEntityID", isEqualTo: 105))
XCTAssertNotNil(existingObjects)
XCTAssertEqual(existingObjects?.count, 1)
let existingObject = existingObjects?[0]
XCTAssertEqual(existingObject, objects[0])
}
catch {
XCTFail()
}
transaction.context.reset()
}
}
}
}
// MARK: - TestInsertError
private struct TestInsertError: ErrorType {}
// MARK: - TestUpdateError
private struct TestUpdateError: ErrorType {}
// MARK: - TestIDError
private struct TestIDError: ErrorType {}
// MARK: - TestEntity1
extension TestEntity1: ImportableUniqueObject {
// MARK: ImportableObject
typealias ImportSource = [String: AnyObject]
static func shouldInsertFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) -> Bool {
return source["skip_insert"] == nil
}
func didInsertFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) throws {
if let _ = source["throw_on_insert"] {
throw TestInsertError()
}
self.testBoolean = source["testBoolean"] as? NSNumber
self.testNumber = source["testNumber"] as? NSNumber
self.testDecimal = source["testDecimal"] as? NSDecimalNumber
self.testString = source["testString"] as? String
self.testData = source["testData"] as? NSData
self.testDate = source["testDate"] as? NSDate
self.testNil = nil
}
// MARK: ImportableUniqueObject
typealias UniqueIDType = NSNumber
static var uniqueIDKeyPath: String {
return "testEntityID"
}
var uniqueIDValue: NSNumber {
get {
guard let ID = self.testEntityID else {
XCTFail()
return 0
}
return ID
}
set {
self.testEntityID = newValue
}
}
static func shouldUpdateFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) -> Bool {
return source["skip_update"] == nil
}
static func uniqueIDFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) throws -> NSNumber? {
if let _ = source["throw_on_id"] {
throw TestIDError()
}
return source["testEntityID"] as? NSNumber
}
func updateFromImportSource(source: [String: AnyObject], inTransaction transaction: BaseDataTransaction) throws {
if let _ = source["throw_on_update"] {
throw TestUpdateError()
}
self.testBoolean = source["testBoolean"] as? NSNumber
self.testNumber = source["testNumber"] as? NSNumber
self.testDecimal = source["testDecimal"] as? NSDecimalNumber
self.testString = source["testString"] as? String
self.testData = source["testData"] as? NSData
self.testDate = source["testDate"] as? NSDate
self.testNil = nil
}
}

View File

@@ -0,0 +1,204 @@
//
// IntoTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
//MARK: - IntoTests
final class IntoTests: XCTestCase {
@objc
dynamic func test_ThatIntoClauseConstants_AreCorrect() {
XCTAssertEqual(Into<NSManagedObject>.defaultConfigurationName, "PF_DEFAULT_CONFIGURATION_NAME")
}
@objc
dynamic func test_ThatIntoClauses_ConfigureCorrectly() {
do {
let into = Into()
XCTAssert(into.entityClass === NSManagedObject.self)
XCTAssertNil(into.configuration)
XCTAssertTrue(into.inferStoreIfPossible)
}
do {
let into = Into<TestEntity1>()
XCTAssert(into.entityClass === TestEntity1.self)
XCTAssertNil(into.configuration)
XCTAssertTrue(into.inferStoreIfPossible)
}
do {
let into = Into(TestEntity1)
XCTAssert(into.entityClass === TestEntity1.self)
XCTAssertNil(into.configuration)
XCTAssertTrue(into.inferStoreIfPossible)
}
do {
let into = Into(TestEntity1.self as AnyClass)
XCTAssert(into.entityClass === TestEntity1.self)
XCTAssertNil(into.configuration)
XCTAssertTrue(into.inferStoreIfPossible)
}
do {
let into = Into<TestEntity1>("Config1")
XCTAssert(into.entityClass === TestEntity1.self)
XCTAssertEqual(into.configuration, "Config1")
XCTAssertFalse(into.inferStoreIfPossible)
}
do {
let into = Into(TestEntity1.self, "Config1")
XCTAssert(into.entityClass === TestEntity1.self)
XCTAssertEqual(into.configuration, "Config1")
XCTAssertFalse(into.inferStoreIfPossible)
}
do {
let into = Into(TestEntity1.self as AnyClass, "Config1")
XCTAssert(into.entityClass === TestEntity1.self)
XCTAssertEqual(into.configuration, "Config1")
XCTAssertFalse(into.inferStoreIfPossible)
}
}
@objc
dynamic func test_ThatIntoClauses_AreEquatable() {
do {
let into = Into()
XCTAssertEqual(into, Into())
XCTAssertEqual(into, Into<NSManagedObject>())
XCTAssertEqual(into, Into(NSManagedObject.self as AnyClass))
XCTAssertFalse(into == Into<TestEntity1>())
XCTAssertNotEqual(into, Into<NSManagedObject>("Config1"))
}
do {
let into = Into<TestEntity1>()
XCTAssertEqual(into, Into(TestEntity1))
XCTAssertEqual(into, Into(TestEntity1.self as AnyClass))
XCTAssertFalse(into == Into<TestEntity2>())
XCTAssertNotEqual(into, Into<TestEntity1>("Config1"))
}
do {
let into = Into(TestEntity1)
XCTAssertEqual(into, Into<TestEntity1>())
XCTAssertEqual(into, Into(TestEntity1.self as AnyClass))
XCTAssertFalse(into == Into<TestEntity2>())
XCTAssertNotEqual(into, Into<TestEntity1>("Config1"))
}
do {
let into = Into(TestEntity1.self as AnyClass)
XCTAssert(into == Into<TestEntity1>())
XCTAssertEqual(into, Into(TestEntity1))
XCTAssertFalse(into == Into<TestEntity2>())
XCTAssertFalse(into == Into<TestEntity1>("Config1"))
}
do {
let into = Into<TestEntity1>("Config1")
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
XCTAssertEqual(into, Into(TestEntity1.self as AnyClass, "Config1"))
XCTAssertFalse(into == Into<TestEntity2>("Config1"))
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
}
do {
let into = Into(TestEntity1.self, "Config1")
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
XCTAssertEqual(into, Into<TestEntity1>("Config1"))
XCTAssertFalse(into == Into<TestEntity2>("Config1"))
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
}
do {
let into = Into(TestEntity1.self as AnyClass, "Config1")
XCTAssert(into == Into<TestEntity1>("Config1"))
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
XCTAssertFalse(into == Into<TestEntity2>("Config1"))
XCTAssertFalse(into == Into<TestEntity1>("Config2"))
}
}
@objc
dynamic func test_ThatIntoClauses_BridgeCorrectly() {
do {
let into = Into()
let objcInto = into.bridgeToObjectiveC
XCTAssertEqual(into, objcInto.bridgeToSwift)
}
do {
let into = Into<TestEntity1>()
let objcInto = into.bridgeToObjectiveC
XCTAssertTrue(into == objcInto.bridgeToSwift)
}
do {
let into = Into(TestEntity1.self as AnyClass)
let objcInto = into.bridgeToObjectiveC
XCTAssertEqual(into, objcInto.bridgeToSwift)
}
do {
let into = Into(TestEntity1.self as AnyClass)
let objcInto = into.bridgeToObjectiveC
XCTAssertEqual(into, objcInto.bridgeToSwift)
}
do {
let into = Into<TestEntity1>("Config1")
let objcInto = into.bridgeToObjectiveC
XCTAssertTrue(into == objcInto.bridgeToSwift)
}
do {
let into = Into(TestEntity1.self, "Config1")
let objcInto = into.bridgeToObjectiveC
XCTAssertTrue(into == objcInto.bridgeToSwift)
}
do {
let into = Into(TestEntity1.self as AnyClass, "Config1")
let objcInto = into.bridgeToObjectiveC
XCTAssertTrue(into == objcInto.bridgeToSwift)
}
}
}

View File

@@ -0,0 +1,682 @@
//
// ListObserverTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - ListObserverTests
class ListObserverTests: BaseTestDataTestCase {
@objc
dynamic func test_ThatListObservers_CanReceiveInsertNotifications() {
self.prepareStack { (stack) in
let observer = TestListObserver()
let monitor = stack.monitorSectionedList(
From(TestEntity1),
SectionBy("testBoolean"),
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
)
monitor.addObserver(observer)
XCTAssertFalse(monitor.hasSections())
XCTAssertFalse(monitor.hasObjects())
XCTAssertTrue(monitor.objectsInAllSections().isEmpty)
var events = 0
let willChangeExpectation = self.expectationForNotification(
"listMonitorWillChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
}
return events == 0
}
)
let didInsertSectionExpectation = self.expectationForNotification(
"listMonitor:didInsertSection:toSectionIndex:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 1)
XCTAssertEqual(
(note.userInfo ?? [:]),
[
"sectionInfo": monitor.sectionInfoAtIndex(0),
"sectionIndex": 0
] as NSDictionary
)
defer {
events += 1
}
return events == 1
}
)
let didInsertObjectExpectation = self.expectationForNotification(
"listMonitor:didInsertObject:toIndexPath:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 2)
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["indexPath", "object"]
)
let indexPath = userInfo?["indexPath"] as? NSIndexPath
XCTAssertEqual(indexPath?.section, 0)
XCTAssertEqual(indexPath?.row, 0)
let object = userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 1))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!)
defer {
events += 1
}
return events == 2
}
)
let didChangeExpectation = self.expectationForNotification(
"listMonitorDidChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
}
return events == 3
}
)
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
let object = transaction.create(Into(TestEntity1))
object.testBoolean = NSNumber(bool: true)
object.testNumber = NSNumber(integer: 1)
object.testDecimal = NSDecimalNumber(string: "1")
object.testString = "nil:TestEntity1:1"
object.testData = ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
object.testDate = self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
@objc
dynamic func test_ThatListObservers_CanReceiveUpdateNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
let observer = TestListObserver()
let monitor = stack.monitorSectionedList(
From(TestEntity1),
SectionBy("testBoolean"),
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
)
monitor.addObserver(observer)
XCTAssertTrue(monitor.hasSections())
XCTAssertEqual(monitor.numberOfSections(), 2)
XCTAssertTrue(monitor.hasObjects())
XCTAssertTrue(monitor.hasObjectsInSection(0))
XCTAssertEqual(monitor.numberOfObjectsInSection(0), 2)
XCTAssertEqual(monitor.numberOfObjectsInSection(1), 3)
var events = 0
let willChangeExpectation = self.expectationForNotification(
"listMonitorWillChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
}
return events == 0
}
)
for _ in 1 ... 2 {
let didUpdateObjectExpectation = self.expectationForNotification(
"listMonitor:didUpdateObject:atIndexPath:",
object: observer,
handler: { (note) -> Bool in
XCTAssert(events == 1 || events == 2)
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["indexPath", "object"]
)
let indexPath = userInfo?["indexPath"] as? NSIndexPath
let object = userInfo?["object"] as? TestEntity1
switch object?.testEntityID {
case NSNumber(integer: 101)?:
XCTAssertEqual(indexPath?.section, 1)
XCTAssertEqual(indexPath?.row, 0)
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 11))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:11")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-11T00:00:00Z")!)
case NSNumber(integer: 102)?:
XCTAssertEqual(indexPath?.section, 0)
XCTAssertEqual(indexPath?.row, 0)
XCTAssertEqual(object?.testBoolean, NSNumber(bool: false))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 22))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:22")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-22T00:00:00Z")!)
default:
XCTFail()
}
defer {
events += 1
}
return events == 1 || events == 2
}
)
}
let didChangeExpectation = self.expectationForNotification(
"listMonitorDidChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 3)
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
}
return events == 3
}
)
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
if let object = transaction.fetchOne(
From(TestEntity1),
Where("testEntityID", isEqualTo: 101)) {
object.testNumber = NSNumber(integer: 11)
object.testDecimal = NSDecimalNumber(string: "11")
object.testString = "nil:TestEntity1:11"
object.testData = ("nil:TestEntity1:11" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
object.testDate = self.dateFormatter.dateFromString("2000-01-11T00:00:00Z")!
}
else {
XCTFail()
}
if let object = transaction.fetchOne(
From(TestEntity1),
Where("testEntityID", isEqualTo: 102)) {
object.testNumber = NSNumber(integer: 22)
object.testDecimal = NSDecimalNumber(string: "22")
object.testString = "nil:TestEntity1:22"
object.testData = ("nil:TestEntity1:22" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
object.testDate = self.dateFormatter.dateFromString("2000-01-22T00:00:00Z")!
}
else {
XCTFail()
}
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
@objc
dynamic func test_ThatListObservers_CanReceiveMoveNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
let observer = TestListObserver()
let monitor = stack.monitorSectionedList(
From(TestEntity1),
SectionBy("testBoolean"),
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
)
monitor.addObserver(observer)
var events = 0
let willChangeExpectation = self.expectationForNotification(
"listMonitorWillChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
}
return events == 0
}
)
let didMoveObjectExpectation = self.expectationForNotification(
"listMonitor:didMoveObject:fromIndexPath:toIndexPath:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 1)
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["fromIndexPath", "toIndexPath", "object"]
)
let fromIndexPath = userInfo?["fromIndexPath"] as? NSIndexPath
XCTAssertEqual(fromIndexPath?.section, 0)
XCTAssertEqual(fromIndexPath?.row, 0)
let toIndexPath = userInfo?["toIndexPath"] as? NSIndexPath
XCTAssertEqual(toIndexPath?.section, 1)
XCTAssertEqual(toIndexPath?.row, 1)
let object = userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 102))
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
defer {
events += 1
}
return events == 1
}
)
let didChangeExpectation = self.expectationForNotification(
"listMonitorDidChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 2)
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
}
return events == 2
}
)
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
if let object = transaction.fetchOne(
From(TestEntity1),
Where("testEntityID", isEqualTo: 102)) {
object.testBoolean = NSNumber(bool: true)
}
else {
XCTFail()
}
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
@objc
dynamic func test_ThatListObservers_CanReceiveDeleteNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
let observer = TestListObserver()
let monitor = stack.monitorSectionedList(
From(TestEntity1),
SectionBy("testBoolean"),
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
)
monitor.addObserver(observer)
var events = 0
let willChangeExpectation = self.expectationForNotification(
"listMonitorWillChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
}
return events == 0
}
)
for _ in 1 ... 2 {
let didUpdateObjectExpectation = self.expectationForNotification(
"listMonitor:didDeleteObject:fromIndexPath:",
object: observer,
handler: { (note) -> Bool in
XCTAssert(events == 1 || events == 2)
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["indexPath", "object"]
)
let indexPath = userInfo?["indexPath"] as? NSIndexPath
XCTAssertEqual(indexPath?.section, 0)
XCTAssert(indexPath?.row == 0 || indexPath?.row == 1)
let object = userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.deleted, true)
defer {
events += 1
}
return events == 1 || events == 2
}
)
}
let didDeleteSectionExpectation = self.expectationForNotification(
"listMonitor:didDeleteSection:fromSectionIndex:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 3)
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["sectionInfo", "sectionIndex"]
)
let sectionInfo = userInfo?["sectionInfo"]
XCTAssertNotNil(sectionInfo)
XCTAssertEqual(sectionInfo?.name, "0")
let sectionIndex = userInfo?["sectionIndex"]
XCTAssertEqual(sectionIndex as? NSNumber, NSNumber(integer: 0))
defer {
events += 1
}
return events == 3
}
)
let didChangeExpectation = self.expectationForNotification(
"listMonitorDidChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 4)
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
}
return events == 4
}
)
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
transaction.deleteAll(
From(TestEntity1),
Where("testBoolean", isEqualTo: false)
)
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
}
// MARK: TestListObserver
class TestListObserver: ListSectionObserver {
// MARK: ListObserver
typealias ListEntityType = TestEntity1
func listMonitorWillChange(monitor: ListMonitor<TestEntity1>) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitorWillChange:",
object: self,
userInfo: [:]
)
}
func listMonitorDidChange(monitor: ListMonitor<TestEntity1>) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitorDidChange:",
object: self,
userInfo: [:]
)
}
func listMonitorWillRefetch(monitor: ListMonitor<TestEntity1>) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitorWillRefetch:",
object: self,
userInfo: [:]
)
}
func listMonitorDidRefetch(monitor: ListMonitor<TestEntity1>) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitorDidRefetch:",
object: self,
userInfo: [:]
)
}
// MARK: ListObjectObserver
func listMonitor(monitor: ListMonitor<TestEntity1>, didInsertObject object: TestEntity1, toIndexPath indexPath: NSIndexPath) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didInsertObject:toIndexPath:",
object: self,
userInfo: [
"object": object,
"indexPath": indexPath
]
)
}
func listMonitor(monitor: ListMonitor<TestEntity1>, didDeleteObject object: TestEntity1, fromIndexPath indexPath: NSIndexPath) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didDeleteObject:fromIndexPath:",
object: self,
userInfo: [
"object": object,
"indexPath": indexPath
]
)
}
func listMonitor(monitor: ListMonitor<TestEntity1>, didUpdateObject object: TestEntity1, atIndexPath indexPath: NSIndexPath) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didUpdateObject:atIndexPath:",
object: self,
userInfo: [
"object": object,
"indexPath": indexPath
]
)
}
func listMonitor(monitor: ListMonitor<TestEntity1>, didMoveObject object: TestEntity1, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didMoveObject:fromIndexPath:toIndexPath:",
object: self,
userInfo: [
"object": object,
"fromIndexPath": fromIndexPath,
"toIndexPath": toIndexPath
]
)
}
// MARK: ListSectionObserver
func listMonitor(monitor: ListMonitor<TestEntity1>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didInsertSection:toSectionIndex:",
object: self,
userInfo: [
"sectionInfo": sectionInfo,
"sectionIndex": sectionIndex
]
)
}
func listMonitor(monitor: ListMonitor<TestEntity1>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didDeleteSection:fromSectionIndex:",
object: self,
userInfo: [
"sectionInfo": sectionInfo,
"sectionIndex": sectionIndex
]
)
}
}
#endif

View File

@@ -0,0 +1,147 @@
//
// MigrationChainTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
// MARK: - MigrationChainTests
final class MigrationChainTests: XCTestCase {
@objc
dynamic func test_ThatNilMigrationChains_HaveNoVersions() {
let chain: MigrationChain = nil
XCTAssertTrue(chain.valid)
XCTAssertTrue(chain.empty)
XCTAssertFalse(chain.contains("version1"))
XCTAssertNil(chain.nextVersionFrom("version1"))
}
@objc
dynamic func test_ThatStringMigrationChains_HaveOneVersion() {
let chain: MigrationChain = "version1"
XCTAssertTrue(chain.valid)
XCTAssertTrue(chain.empty)
XCTAssertTrue(chain.contains("version1"))
XCTAssertFalse(chain.contains("version2"))
XCTAssertNil(chain.nextVersionFrom("version1"))
XCTAssertNil(chain.nextVersionFrom("version2"))
}
@objc
dynamic func test_ThatArrayMigrationChains_HaveLinearVersions() {
let chain: MigrationChain = ["version1", "version2", "version3", "version4"]
XCTAssertTrue(chain.valid)
XCTAssertFalse(chain.empty)
XCTAssertTrue(chain.contains("version1"))
XCTAssertTrue(chain.contains("version2"))
XCTAssertTrue(chain.contains("version3"))
XCTAssertTrue(chain.contains("version4"))
XCTAssertFalse(chain.contains("version5"))
XCTAssertEqual(chain.nextVersionFrom("version1"), "version2")
XCTAssertEqual(chain.nextVersionFrom("version2"), "version3")
XCTAssertEqual(chain.nextVersionFrom("version3"), "version4")
XCTAssertNil(chain.nextVersionFrom("version4"))
XCTAssertNil(chain.nextVersionFrom("version5"))
}
@objc
dynamic func test_ThatDictionaryMigrationChains_HaveTreeVersions() {
let chain: MigrationChain = [
"version1": "version4",
"version2": "version3",
"version3": "version4"
]
XCTAssertTrue(chain.valid)
XCTAssertFalse(chain.empty)
XCTAssertTrue(chain.contains("version1"))
XCTAssertTrue(chain.contains("version2"))
XCTAssertTrue(chain.contains("version3"))
XCTAssertTrue(chain.contains("version4"))
XCTAssertFalse(chain.contains("version5"))
XCTAssertEqual(chain.nextVersionFrom("version1"), "version4")
XCTAssertEqual(chain.nextVersionFrom("version2"), "version3")
XCTAssertEqual(chain.nextVersionFrom("version3"), "version4")
XCTAssertNil(chain.nextVersionFrom("version4"))
XCTAssertNil(chain.nextVersionFrom("version5"))
// The cases below will trigger assertion failures internally
// let linearLoopChain: MigrationChain = ["version1", "version2", "version1", "version3", "version4"]
// XCTAssertFalse(linearLoopChain.valid, "linearLoopChain.valid")
//
// let treeAmbiguousChain: MigrationChain = [
// "version1": "version4",
// "version2": "version3",
// "version1": "version2",
// "version3": "version4"
// ]
// XCTAssertFalse(treeAmbiguousChain.valid, "treeAmbiguousChain.valid")
}
@objc
dynamic func test_ThatMigrationChains_AreEquatable() {
do {
let chain1: MigrationChain = nil
let chain2: MigrationChain = []
let chain3: MigrationChain = [:]
XCTAssertEqual(chain1, chain2)
XCTAssertEqual(chain2, chain3)
XCTAssertEqual(chain3, chain1)
}
do {
let chain1: MigrationChain = "version1"
let chain2: MigrationChain = ["version1"]
XCTAssertEqual(chain1, chain2)
}
do {
let chain1: MigrationChain = ["version1", "version2", "version3", "version4"]
let chain2: MigrationChain = [
"version1": "version2",
"version2": "version3",
"version3": "version4"
]
XCTAssertEqual(chain1, chain2)
}
}
}

View File

@@ -1,15 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Xcode 4.3">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10174" systemVersion="15F34" minimumToolsVersion="Xcode 4.3">
<entity name="TestEntity1AAA" representedClassName="CoreStoreTests.TestEntity1" syncable="YES">
<attribute name="testBoolean" optional="YES" attributeType="Boolean" syncable="YES"/>
<attribute name="testData" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="testDate" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="testEntityID" attributeType="Integer 64" syncable="YES"/>
<attribute name="testNumber" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="testDecimal" optional="YES" attributeType="Decimal" syncable="YES"/>
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="testNil" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="testNumber" optional="YES" attributeType="Integer 32" syncable="YES"/>
<attribute name="testString" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<entity name="TestEntity2" representedClassName="CoreStoreTests.TestEntity2" syncable="YES">
<attribute name="testBoolean" optional="YES" attributeType="Boolean" syncable="YES"/>
<attribute name="testData" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="testDate" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="testEntityID" attributeType="Integer 64" syncable="YES"/>
<attribute name="testNumber" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
<attribute name="testDecimal" optional="YES" attributeType="Decimal" syncable="YES"/>
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="testNil" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="testNumber" optional="YES" attributeType="Integer 32" syncable="YES"/>
<attribute name="testString" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<configuration name="Config1">
@@ -19,7 +27,7 @@
<memberEntity name="TestEntity2"/>
</configuration>
<elements>
<element name="TestEntity1AAA" positionX="-63" positionY="-18" width="128" height="105"/>
<element name="TestEntity2" positionX="-63" positionY="9" width="128" height="105"/>
<element name="TestEntity1AAA" positionX="-63" positionY="-18" width="128" height="165"/>
<element name="TestEntity2" positionX="-63" positionY="9" width="128" height="165"/>
</elements>
</model>

View File

@@ -0,0 +1,246 @@
//
// ObjectObserverTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - ObjectObserverTests
class ObjectObserverTests: BaseTestDataTestCase {
@objc
dynamic func test_ThatObjectObservers_CanReceiveUpdateNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
guard let object = stack.fetchOne(
From(TestEntity1),
Where("testEntityID", isEqualTo: 101)) else {
XCTFail()
return
}
let observer = TestObjectObserver()
let monitor = stack.monitorObject(object)
monitor.addObserver(observer)
XCTAssertEqual(monitor.object, object)
XCTAssertFalse(monitor.isObjectDeleted)
var events = 0
let willUpdateExpectation = self.expectationForNotification(
"objectMonitor:willUpdateObject:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual(
(note.userInfo ?? [:]),
["object": object] as NSDictionary
)
defer {
events += 1
}
return events == 0
}
)
let didUpdateExpectation = self.expectationForNotification(
"objectMonitor:didUpdateObject:changedPersistentKeys:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 1)
XCTAssertEqual(
(note.userInfo ?? [:]),
[
"object": object,
"changedPersistentKeys": Set(
[
"testNumber",
"testString"
]
)
] as NSDictionary
)
let object = note.userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.testNumber, NSNumber(integer: 10))
XCTAssertEqual(object?.testString, "nil:TestEntity1:10")
defer {
events += 1
}
return events == 1
}
)
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
guard let object = transaction.edit(object) else {
XCTFail()
return
}
object.testNumber = NSNumber(integer: 10)
object.testString = "nil:TestEntity1:10"
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
@objc
dynamic func test_ThatObjectObservers_CanReceiveDeleteNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
guard let object = stack.fetchOne(
From(TestEntity1),
Where("testEntityID", isEqualTo: 101)) else {
XCTFail()
return
}
let observer = TestObjectObserver()
let monitor = stack.monitorObject(object)
monitor.addObserver(observer)
XCTAssertEqual(monitor.object, object)
XCTAssertFalse(monitor.isObjectDeleted)
var events = 0
let didDeleteExpectation = self.expectationForNotification(
"objectMonitor:didDeleteObject:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual(
(note.userInfo ?? [:]),
["object": object] as NSDictionary
)
defer {
events += 1
}
return events == 0
}
)
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
guard let object = transaction.edit(object) else {
XCTFail()
return
}
transaction.delete(object)
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
XCTAssertTrue(monitor.isObjectDeleted)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
}
// MARK: TestObjectObserver
class TestObjectObserver: ObjectObserver {
typealias ObjectEntityType = TestEntity1
func objectMonitor(monitor: ObjectMonitor<TestEntity1>, willUpdateObject object: TestEntity1) {
NSNotificationCenter.defaultCenter().postNotificationName(
"objectMonitor:willUpdateObject:",
object: self,
userInfo: [
"object": object
]
)
}
func objectMonitor(monitor: ObjectMonitor<TestEntity1>, didUpdateObject object: TestEntity1, changedPersistentKeys: Set<KeyPath>) {
NSNotificationCenter.defaultCenter().postNotificationName(
"objectMonitor:didUpdateObject:changedPersistentKeys:",
object: self,
userInfo: [
"object": object,
"changedPersistentKeys": changedPersistentKeys
]
)
}
func objectMonitor(monitor: ObjectMonitor<TestEntity1>, didDeleteObject object: TestEntity1) {
NSNotificationCenter.defaultCenter().postNotificationName(
"objectMonitor:didDeleteObject:",
object: self,
userInfo: [
"object": object
]
)
}
}
#endif

View File

@@ -0,0 +1,187 @@
//
// OrderByTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
//MARK: - OrderByTests
final class OrderByTests: XCTestCase {
@objc
dynamic func test_ThatOrderByClauses_ConfigureCorrectly() {
do {
let orderBy = OrderBy()
XCTAssertEqual(orderBy, OrderBy([] as [NSSortDescriptor]))
XCTAssertNotEqual(orderBy, OrderBy(NSSortDescriptor(key: "key", ascending: false)))
XCTAssertTrue(orderBy.sortDescriptors.isEmpty)
}
do {
let sortDescriptor = NSSortDescriptor(key: "key1", ascending: true)
let orderBy = OrderBy(sortDescriptor)
XCTAssertEqual(orderBy, OrderBy(sortDescriptor))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.Descending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(NSSortDescriptor(key: "key1", ascending: false)))
XCTAssertEqual(orderBy, OrderBy([sortDescriptor]))
XCTAssertEqual(orderBy.sortDescriptors, [sortDescriptor])
}
do {
let sortDescriptors = [
NSSortDescriptor(key: "key1", ascending: true),
NSSortDescriptor(key: "key2", ascending: false)
]
let orderBy = OrderBy(sortDescriptors)
XCTAssertEqual(orderBy, OrderBy(sortDescriptors))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
XCTAssertNotEqual(
orderBy,
OrderBy(
[
NSSortDescriptor(key: "key1", ascending: false),
NSSortDescriptor(key: "key2", ascending: false)
]
)
)
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key3")))
XCTAssertEqual(orderBy.sortDescriptors, sortDescriptors)
}
do {
let orderBy = OrderBy(.Ascending("key1"))
let sortDescriptor = NSSortDescriptor(key: "key1", ascending: true)
XCTAssertEqual(orderBy, OrderBy(sortDescriptor))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(.Descending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key2")))
XCTAssertEqual(orderBy, OrderBy([sortDescriptor]))
XCTAssertEqual(orderBy.sortDescriptors, [sortDescriptor])
}
do {
let orderBy = OrderBy(.Ascending("key1"), .Descending("key2"))
let sortDescriptors = [
NSSortDescriptor(key: "key1", ascending: true),
NSSortDescriptor(key: "key2", ascending: false)
]
XCTAssertEqual(orderBy, OrderBy(sortDescriptors))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
XCTAssertNotEqual(
orderBy,
OrderBy(
[
NSSortDescriptor(key: "key1", ascending: false),
NSSortDescriptor(key: "key2", ascending: false)
]
)
)
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key3")))
XCTAssertEqual(orderBy.sortDescriptors, sortDescriptors)
}
do {
let sortKeys: [SortKey] = [.Ascending("key1"), .Descending("key2")]
let orderBy = OrderBy(sortKeys)
let sortDescriptors = [
NSSortDescriptor(key: "key1", ascending: true),
NSSortDescriptor(key: "key2", ascending: false)
]
XCTAssertEqual(orderBy, OrderBy(sortDescriptors))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
XCTAssertNotEqual(
orderBy,
OrderBy(
[
NSSortDescriptor(key: "key1", ascending: false),
NSSortDescriptor(key: "key2", ascending: false)
]
)
)
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key3")))
XCTAssertEqual(orderBy.sortDescriptors, sortDescriptors)
}
}
@objc
dynamic func test_ThatOrderByClauseOperations_ComputeCorrectly() {
let orderBy1 = OrderBy(.Ascending("key1"))
let orderBy2 = OrderBy(.Descending("key2"))
let orderBy3 = OrderBy(.Ascending("key3"))
do {
let plusOrderBy = orderBy1 + orderBy2 + orderBy3
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2"), .Ascending("key3")))
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1")) + OrderBy(.Descending("key2"), .Ascending("key3")))
XCTAssertNotEqual(plusOrderBy, orderBy1 + orderBy3 + orderBy2)
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy1 + orderBy3)
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy3 + orderBy1)
XCTAssertNotEqual(plusOrderBy, orderBy3 + orderBy1 + orderBy2)
XCTAssertNotEqual(plusOrderBy, orderBy3 + orderBy2 + orderBy1)
XCTAssertEqual(plusOrderBy.sortDescriptors, orderBy1.sortDescriptors + orderBy2.sortDescriptors + orderBy3.sortDescriptors)
}
do {
var plusOrderBy = orderBy1
plusOrderBy += orderBy2
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1")) + OrderBy(.Descending("key2")))
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy1)
XCTAssertEqual(plusOrderBy.sortDescriptors, orderBy1.sortDescriptors + orderBy2.sortDescriptors)
plusOrderBy += orderBy3
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2"), .Ascending("key3")))
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2")) + OrderBy(.Ascending("key3")))
XCTAssertNotEqual(plusOrderBy, orderBy1 + orderBy3 + orderBy2)
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy1 + orderBy3)
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy3 + orderBy1)
XCTAssertNotEqual(plusOrderBy, orderBy3 + orderBy1 + orderBy2)
XCTAssertNotEqual(plusOrderBy, orderBy3 + orderBy2 + orderBy1)
XCTAssertEqual(plusOrderBy.sortDescriptors, orderBy1.sortDescriptors + orderBy2.sortDescriptors + orderBy3.sortDescriptors)
}
}
@objc
dynamic func test_ThatOrderByClauses_ApplyToFetchRequestsCorrectly() {
let orderBy = OrderBy(.Ascending("key"))
let request = CoreStoreFetchRequest()
orderBy.applyToFetchRequest(request)
XCTAssertNotNil(request.sortDescriptors)
XCTAssertEqual(request.sortDescriptors ?? [], orderBy.sortDescriptors)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,57 @@
//
// SectionByTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
#if os(iOS) || os(watchOS) || os(tvOS)
//MARK: - SectionByTests
final class SectionByTests: XCTestCase {
@objc
dynamic func test_ThatSectionByClauses_ConfigureCorrectly() {
do {
let sectionBy = SectionBy("key")
XCTAssertEqual(sectionBy.sectionKeyPath, "key")
XCTAssertEqual(sectionBy.sectionIndexTransformer(sectionName: "key"), "key")
}
do {
let sectionBy = SectionBy("key") { $0.flatMap { "\($0):suffix" } }
XCTAssertEqual(sectionBy.sectionKeyPath, "key")
XCTAssertEqual(sectionBy.sectionIndexTransformer(sectionName: "key"), "key:suffix")
XCTAssertNil(sectionBy.sectionIndexTransformer(sectionName: nil))
}
}
}
#endif

View File

@@ -0,0 +1,420 @@
//
// SelectTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
//MARK: - SelectTests
final class SelectTests: XCTestCase {
@objc
dynamic func test_ThatAttributeSelectTerms_ConfigureCorrectly() {
do {
let term: SelectTerm = "attribute"
XCTAssertEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Attribute(let key):
XCTAssertEqual(key, "attribute")
default:
XCTFail()
}
}
do {
let term = SelectTerm.Attribute("attribute")
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Attribute(let key):
XCTAssertEqual(key, "attribute")
default:
XCTFail()
}
}
}
@objc
dynamic func test_ThatAverageSelectTerms_ConfigureCorrectly() {
do {
let term = SelectTerm.Average("attribute")
XCTAssertEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "average:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "average(attribute)")
XCTAssertTrue(nativeType == .DecimalAttributeType)
default:
XCTFail()
}
}
do {
let term = SelectTerm.Average("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Average("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "average:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .DecimalAttributeType)
default:
XCTFail()
}
}
}
@objc
dynamic func test_ThatCountSelectTerms_ConfigureCorrectly() {
do {
let term = SelectTerm.Count("attribute")
XCTAssertEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "count:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "count(attribute)")
XCTAssertTrue(nativeType == .Integer64AttributeType)
default:
XCTFail()
}
}
do {
let term = SelectTerm.Count("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Count("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "count:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .Integer64AttributeType)
default:
XCTFail()
}
}
}
@objc
dynamic func test_ThatMaximumSelectTerms_ConfigureCorrectly() {
do {
let term = SelectTerm.Maximum("attribute")
XCTAssertEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "max:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "max(attribute)")
XCTAssertTrue(nativeType == .UndefinedAttributeType)
default:
XCTFail()
}
}
do {
let term = SelectTerm.Maximum("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Maximum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "max:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .UndefinedAttributeType)
default:
XCTFail()
}
}
}
@objc
dynamic func test_ThatMinimumSelectTerms_ConfigureCorrectly() {
do {
let term = SelectTerm.Minimum("attribute")
XCTAssertEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "min:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "min(attribute)")
XCTAssertTrue(nativeType == .UndefinedAttributeType)
default:
XCTFail()
}
}
do {
let term = SelectTerm.Minimum("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Minimum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "min:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .UndefinedAttributeType)
default:
XCTFail()
}
}
}
@objc
dynamic func test_ThatSumSelectTerms_ConfigureCorrectly() {
do {
let term = SelectTerm.Sum("attribute")
XCTAssertEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "sum:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "sum(attribute)")
XCTAssertTrue(nativeType == .DecimalAttributeType)
default:
XCTFail()
}
}
do {
let term = SelectTerm.Sum("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Sum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "sum:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .DecimalAttributeType)
default:
XCTFail()
}
}
}
@objc
dynamic func test_ThatObjectIDSelectTerms_ConfigureCorrectly() {
do {
let term = SelectTerm.ObjectID()
XCTAssertEqual(term, SelectTerm.ObjectID())
XCTAssertNotEqual(term, SelectTerm.ObjectID(As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
switch term {
case ._Identity(let alias, let nativeType):
XCTAssertEqual(alias, "objectID")
XCTAssertTrue(nativeType == .ObjectIDAttributeType)
default:
XCTFail()
}
}
do {
let term = SelectTerm.ObjectID(As: "alias")
XCTAssertEqual(term, SelectTerm.ObjectID(As: "alias"))
XCTAssertNotEqual(term, SelectTerm.ObjectID(As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
switch term {
case ._Identity(let alias, let nativeType):
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .ObjectIDAttributeType)
default:
XCTFail()
}
}
}
@objc
dynamic func test_ThatSelectClauses_ConfigureCorrectly() {
let term1 = SelectTerm.Attribute("attribute1")
let term2 = SelectTerm.Attribute("attribute2")
let term3 = SelectTerm.Attribute("attribute3")
do {
let select = Select<Int>(term1, term2, term3)
XCTAssertEqual(select.selectTerms, [term1, term2, term3])
XCTAssertNotEqual(select.selectTerms, [term1, term3, term2])
XCTAssertNotEqual(select.selectTerms, [term2, term1, term3])
XCTAssertNotEqual(select.selectTerms, [term2, term3, term1])
XCTAssertNotEqual(select.selectTerms, [term3, term1, term2])
XCTAssertNotEqual(select.selectTerms, [term3, term2, term1])
}
do {
let select = Select<Int>([term1, term2, term3])
XCTAssertEqual(select.selectTerms, [term1, term2, term3])
XCTAssertNotEqual(select.selectTerms, [term1, term3, term2])
XCTAssertNotEqual(select.selectTerms, [term2, term1, term3])
XCTAssertNotEqual(select.selectTerms, [term2, term3, term1])
XCTAssertNotEqual(select.selectTerms, [term3, term1, term2])
XCTAssertNotEqual(select.selectTerms, [term3, term2, term1])
}
}
}

View File

@@ -0,0 +1,253 @@
//
// SetupTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
@testable
import CoreStore
// MARK: - SetupTests
class SetupTests: BaseTestCase {
@objc
dynamic func test_ThatDataStacks_ConfigureCorrectly() {
do {
let model = NSManagedObjectModel.mergedModelFromBundles([NSBundle(forClass: self.dynamicType)])!
let stack = DataStack(model: model, migrationChain: nil)
XCTAssertEqual(stack.coordinator.managedObjectModel, model)
XCTAssertEqual(stack.rootSavingContext.persistentStoreCoordinator, stack.coordinator)
XCTAssertNil(stack.rootSavingContext.parentContext)
XCTAssertEqual(stack.mainContext.parentContext, stack.rootSavingContext)
XCTAssertEqual(stack.model, model)
XCTAssertTrue(stack.migrationChain.valid)
XCTAssertTrue(stack.migrationChain.empty)
XCTAssertTrue(stack.migrationChain.rootVersions.isEmpty)
XCTAssertTrue(stack.migrationChain.leafVersions.isEmpty)
CoreStore.defaultStack = stack
XCTAssertEqual(CoreStore.defaultStack, stack)
}
do {
let migrationChain: MigrationChain = ["version1", "version2", "version3"]
let stack = self.expectLogger([.LogWarning]) {
DataStack(
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType),
migrationChain: migrationChain
)
}
XCTAssertEqual(stack.modelVersion, "Model")
XCTAssertEqual(stack.migrationChain, migrationChain)
CoreStore.defaultStack = stack
XCTAssertEqual(CoreStore.defaultStack, stack)
}
}
@objc
dynamic func test_ThatInMemoryStores_SetupCorrectly() {
let stack = DataStack(
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType)
)
do {
let inMemoryStore = InMemoryStore()
do {
try stack.addStorageAndWait(inMemoryStore)
}
catch let error as NSError {
XCTFail(error.description)
}
let persistentStore = stack.persistentStoreForStorage(inMemoryStore)
XCTAssertNotNil(persistentStore)
}
do {
let inMemoryStore = InMemoryStore(
configuration: "Config1"
)
do {
try stack.addStorageAndWait(inMemoryStore)
}
catch let error as NSError {
XCTFail(error.description)
}
let persistentStore = stack.persistentStoreForStorage(inMemoryStore)
XCTAssertNotNil(persistentStore)
}
do {
let inMemoryStore = InMemoryStore(
configuration: "Config2"
)
do {
try stack.addStorageAndWait(inMemoryStore)
}
catch let error as NSError {
XCTFail(error.description)
}
let persistentStore = stack.persistentStoreForStorage(inMemoryStore)
XCTAssertNotNil(persistentStore)
}
}
@objc
dynamic func test_ThatSQLiteStores_SetupCorrectly() {
let stack = DataStack(
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType)
)
do {
let sqliteStore = SQLiteStore()
do {
try stack.addStorageAndWait(sqliteStore)
}
catch let error as NSError {
XCTFail(error.description)
}
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
XCTAssertNotNil(persistentStore)
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
}
do {
let sqliteStore = SQLiteStore(
fileName: "ConfigStore1.sqlite",
configuration: "Config1",
localStorageOptions: .RecreateStoreOnModelMismatch
)
do {
try stack.addStorageAndWait(sqliteStore)
}
catch let error as NSError {
XCTFail(error.description)
}
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
XCTAssertNotNil(persistentStore)
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
}
do {
let sqliteStore = SQLiteStore(
fileName: "ConfigStore2.sqlite",
configuration: "Config2",
localStorageOptions: .RecreateStoreOnModelMismatch
)
do {
try stack.addStorageAndWait(sqliteStore)
}
catch let error as NSError {
XCTFail(error.description)
}
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
XCTAssertNotNil(persistentStore)
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
}
}
@objc
dynamic func test_ThatLegacySQLiteStores_SetupCorrectly() {
let stack = DataStack(
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType)
)
do {
let sqliteStore = SQLiteStore()
do {
try stack.addStorageAndWait(sqliteStore)
}
catch let error as NSError {
XCTFail(error.description)
}
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
XCTAssertNotNil(persistentStore)
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
}
do {
let sqliteStore = SQLiteStore(
fileName: "ConfigStore1.sqlite",
configuration: "Config1",
localStorageOptions: .RecreateStoreOnModelMismatch
)
do {
try stack.addStorageAndWait(sqliteStore)
}
catch let error as NSError {
XCTFail(error.description)
}
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
XCTAssertNotNil(persistentStore)
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
}
do {
let sqliteStore = SQLiteStore(
fileName: "ConfigStore2.sqlite",
configuration: "Config2",
localStorageOptions: .RecreateStoreOnModelMismatch
)
do {
try stack.addStorageAndWait(sqliteStore)
}
catch let error as NSError {
XCTFail(error.description)
}
let persistentStore = stack.persistentStoreForStorage(sqliteStore)
XCTAssertNotNil(persistentStore)
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
}
}
}

View File

@@ -0,0 +1,217 @@
//
// StorageInterfaceTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
//MARK: - StorageInterfaceTests
final class StorageInterfaceTests: XCTestCase {
@objc
dynamic func test_ThatDefaultInMemoryStores_ConfigureCorrectly() {
let store = InMemoryStore()
XCTAssertEqual(store.dynamicType.storeType, NSInMemoryStoreType)
XCTAssertNil(store.configuration)
XCTAssertNil(store.storeOptions)
}
@objc
dynamic func test_ThatCustomInMemoryStores_ConfigureCorrectly() {
let store = InMemoryStore(configuration: "config1")
XCTAssertEqual(store.dynamicType.storeType, NSInMemoryStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertNil(store.storeOptions)
}
@objc
dynamic func test_ThatSQLiteStoreDefaultDirectories_AreCorrect() {
#if os(tvOS)
let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
#else
let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
#endif
let defaultSystemDirectory = NSFileManager
.defaultManager()
.URLsForDirectory(systemDirectorySearchPath, inDomains: .UserDomainMask).first!
let defaultRootDirectory = defaultSystemDirectory.URLByAppendingPathComponent(
NSBundle.mainBundle().bundleIdentifier ?? "com.CoreStore.DataStack",
isDirectory: true)!
let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
let defaultFileURL = defaultRootDirectory
.URLByAppendingPathComponent(applicationName, isDirectory: false)!
.URLByAppendingPathExtension("sqlite")!
XCTAssertEqual(SQLiteStore.defaultRootDirectory, defaultRootDirectory)
XCTAssertEqual(SQLiteStore.defaultFileURL, defaultFileURL)
}
@objc
dynamic func test_ThatDefaultSQLiteStores_ConfigureCorrectly() {
let store = SQLiteStore()
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertNil(store.configuration)
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL, SQLiteStore.defaultFileURL)
XCTAssertEqual(store.mappingModelBundles, NSBundle.allBundles())
XCTAssertEqual(store.localStorageOptions, [.None])
}
@objc
dynamic func test_ThatFileURLSQLiteStores_ConfigureCorrectly() {
let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
.URLByAppendingPathComponent(NSUUID().UUIDString, isDirectory: false)!
.URLByAppendingPathExtension("db")!
let bundles = [NSBundle(forClass: self.dynamicType)]
let store = SQLiteStore(
fileURL: fileURL,
configuration: "config1",
mappingModelBundles: bundles,
localStorageOptions: .RecreateStoreOnModelMismatch
)
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL, fileURL)
XCTAssertEqual(store.mappingModelBundles, bundles)
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
}
@objc
dynamic func test_ThatFileNameSQLiteStores_ConfigureCorrectly() {
let fileName = NSUUID().UUIDString + ".db"
let bundles = [NSBundle(forClass: self.dynamicType)]
let store = SQLiteStore(
fileName: fileName,
configuration: "config1",
mappingModelBundles: bundles,
localStorageOptions: .RecreateStoreOnModelMismatch
)
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL.URLByDeletingLastPathComponent, SQLiteStore.defaultRootDirectory)
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
XCTAssertEqual(store.mappingModelBundles, bundles)
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
}
@objc
dynamic func test_ThatLegacySQLiteStoreDefaultDirectories_AreCorrect() {
#if os(tvOS)
let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
#else
let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
#endif
let legacyDefaultRootDirectory = NSFileManager.defaultManager().URLsForDirectory(
systemDirectorySearchPath,
inDomains: .UserDomainMask
).first!
let legacyDefaultFileURL = legacyDefaultRootDirectory
.URLByAppendingPathComponent(DataStack.applicationName, isDirectory: false)!
.URLByAppendingPathExtension("sqlite")!
XCTAssertEqual(LegacySQLiteStore.defaultRootDirectory, legacyDefaultRootDirectory)
XCTAssertEqual(LegacySQLiteStore.defaultFileURL, legacyDefaultFileURL)
}
@objc
dynamic func test_ThatDefaultLegacySQLiteStores_ConfigureCorrectly() {
let store = LegacySQLiteStore()
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertNil(store.configuration)
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL, LegacySQLiteStore.defaultFileURL)
XCTAssertEqual(store.mappingModelBundles, NSBundle.allBundles())
XCTAssertEqual(store.localStorageOptions, [.None])
}
@objc
dynamic func test_ThatFileURLLegacySQLiteStores_ConfigureCorrectly() {
let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
.URLByAppendingPathComponent(NSUUID().UUIDString, isDirectory: false)!
.URLByAppendingPathExtension("db")!
let bundles = [NSBundle(forClass: self.dynamicType)]
let store = LegacySQLiteStore(
fileURL: fileURL,
configuration: "config1",
mappingModelBundles: bundles,
localStorageOptions: .RecreateStoreOnModelMismatch
)
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL, fileURL)
XCTAssertEqual(store.mappingModelBundles, bundles)
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
}
@objc
dynamic func test_ThatFileNameLegacySQLiteStores_ConfigureCorrectly() {
let fileName = NSUUID().UUIDString + ".db"
let bundles = [NSBundle(forClass: self.dynamicType)]
let store = LegacySQLiteStore(
fileName: fileName,
configuration: "config1",
mappingModelBundles: bundles,
localStorageOptions: .RecreateStoreOnModelMismatch
)
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL.URLByDeletingLastPathComponent, LegacySQLiteStore.defaultRootDirectory)
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
XCTAssertEqual(store.mappingModelBundles, bundles)
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
}
}

View File

@@ -32,4 +32,8 @@ class TestEntity1: NSManagedObject {
@NSManaged var testString: String?
@NSManaged var testNumber: NSNumber?
@NSManaged var testDate: NSDate?
@NSManaged var testBoolean: NSNumber?
@NSManaged var testDecimal: NSDecimalNumber?
@NSManaged var testData: NSData?
@NSManaged var testNil: String?
}

View File

@@ -32,6 +32,8 @@ class TestEntity2: NSManagedObject {
@NSManaged var testString: String?
@NSManaged var testNumber: NSNumber?
@NSManaged var testDate: NSDate?
var testProperty: NSNumber?
@NSManaged var testBoolean: NSNumber?
@NSManaged var testDecimal: NSDecimalNumber?
@NSManaged var testData: NSData?
@NSManaged var testNil: String?
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,53 @@
//
// TweakTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
//MARK: - TweakTests
final class TweakTests: XCTestCase {
@objc
dynamic func test_ThatTweakClauses_ApplyToFetchRequestsCorrectly() {
let predicate = NSPredicate(format: "%K == %@", "key", "value")
let tweak = Tweak {
$0.fetchOffset = 100
$0.fetchLimit = 200
$0.predicate = predicate
}
let request = CoreStoreFetchRequest()
tweak.applyToFetchRequest(request)
XCTAssertEqual(request.fetchOffset, 100)
XCTAssertEqual(request.fetchLimit, 200)
XCTAssertNotNil(request.predicate)
XCTAssertEqual(request.predicate, predicate)
}
}

View File

@@ -0,0 +1,150 @@
//
// WhereTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
//MARK: - WhereTests
final class WhereTests: XCTestCase {
@objc
dynamic func test_ThatWhereClauses_ConfigureCorrectly() {
do {
let whereClause = Where()
XCTAssertEqual(whereClause, Where(true))
XCTAssertNotEqual(whereClause, Where(false))
XCTAssertEqual(whereClause.predicate, NSPredicate(value: true))
}
do {
let whereClause = Where(true)
XCTAssertEqual(whereClause, Where())
XCTAssertNotEqual(whereClause, Where(false))
XCTAssertEqual(whereClause.predicate, NSPredicate(value: true))
}
do {
let predicate = NSPredicate(format: "%K == %@", "key", "value")
let whereClause = Where(predicate)
XCTAssertEqual(whereClause, Where(predicate))
XCTAssertEqual(whereClause.predicate, predicate)
}
do {
let whereClause = Where("%K == %@", "key", "value")
let predicate = NSPredicate(format: "%K == %@", "key", "value")
XCTAssertEqual(whereClause, Where(predicate))
XCTAssertEqual(whereClause.predicate, predicate)
}
do {
let whereClause = Where("%K == %@", argumentArray: ["key", "value"])
let predicate = NSPredicate(format: "%K == %@", "key", "value")
XCTAssertEqual(whereClause, Where(predicate))
XCTAssertEqual(whereClause.predicate, predicate)
}
do {
let whereClause = Where("key", isEqualTo: "value")
let predicate = NSPredicate(format: "%K == %@", "key", "value")
XCTAssertEqual(whereClause, Where(predicate))
XCTAssertEqual(whereClause.predicate, predicate)
}
do {
let whereClause = Where("key", isMemberOf: ["value1", "value2", "value3"])
let predicate = NSPredicate(format: "%K IN %@", "key", ["value1", "value2", "value3"])
XCTAssertEqual(whereClause, Where(predicate))
XCTAssertEqual(whereClause.predicate, predicate)
}
}
@objc
dynamic func test_ThatWhereClauseOperations_ComputeCorrectly() {
let whereClause1 = Where("key1", isEqualTo: "value1")
let whereClause2 = Where("key2", isEqualTo: "value2")
let whereClause3 = Where("key3", isEqualTo: "value3")
do {
let notWhere = !whereClause1
let notPredicate = NSCompoundPredicate(
type: .NotPredicateType,
subpredicates: [whereClause1.predicate]
)
XCTAssertEqual(notWhere.predicate, notPredicate)
XCTAssertEqual(notWhere, !whereClause1)
}
do {
let andWhere = whereClause1 && whereClause2 && whereClause3
let andPredicate = NSCompoundPredicate(
type: .AndPredicateType,
subpredicates: [
NSCompoundPredicate(
type: .AndPredicateType,
subpredicates: [whereClause1.predicate, whereClause2.predicate]
),
whereClause3.predicate
]
)
XCTAssertEqual(andWhere.predicate, andPredicate)
XCTAssertEqual(andWhere, whereClause1 && whereClause2 && whereClause3)
}
do {
let orWhere = whereClause1 || whereClause2 || whereClause3
let orPredicate = NSCompoundPredicate(
type: .OrPredicateType,
subpredicates: [
NSCompoundPredicate(
type: .OrPredicateType,
subpredicates: [whereClause1.predicate, whereClause2.predicate]
),
whereClause3.predicate
]
)
XCTAssertEqual(orWhere.predicate, orPredicate)
XCTAssertEqual(orWhere, whereClause1 || whereClause2 || whereClause3)
}
}
@objc
dynamic func test_ThatWhereClauses_ApplyToFetchRequestsCorrectly() {
let whereClause = Where("key", isEqualTo: "value")
let request = CoreStoreFetchRequest()
whereClause.applyToFetchRequest(request)
XCTAssertNotNil(request.predicate)
XCTAssertEqual(request.predicate, whereClause.predicate)
}
}

51
Package.swift Normal file
View File

@@ -0,0 +1,51 @@
//
// Package.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import PackageDescription
let targets: [Target]
#if os(iOS)
targets = [Target(name: "CoreStore iOS")]
#elseif os(OSX)
targets = [Target(name: "CoreStore OSX")]
#elseif os(watchOS)
targets = [Target(name: "CoreStore watchOS")]
#elseif os(tvOS)
targets = [Target(name: "CoreStore tvOS")]
#else
targets = []
#endif
let package = Package(
name: "CoreStore",
targets: targets,
dependencies: [
.Package(
url: "https://github.com/JohnEstropia/GCDKit.git",
"1.2.6"
)
],
exclude: ["Carthage", "CoreStoreDemo", "Sources/libA/images"]
)

600
README.md
View File

@@ -1,33 +1,57 @@
# CoreStore
[![Build Status](https://img.shields.io/travis/JohnEstropia/CoreStore/develop.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)
<p align="center">
<img alt="CoreStore" src="https://cloud.githubusercontent.com/assets/3029684/13373932/84daee2a-ddb8-11e5-99db-fdf415620102.png" width=614 />
<br />
<br />
Unleashing the real power of Core Data with the elegance and safety of Swift
* 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))
<br />
<br />
<a href="https://travis-ci.org/JohnEstropia/CoreStore"><img alt="Build Status" src="https://img.shields.io/travis/JohnEstropia/CoreStore/master.svg?style=flat" /></a>
<a href="http://cocoadocs.org/docsets/CoreStore"><img alt="Platform" src="https://img.shields.io/cocoapods/p/CoreStore.svg?style=flat" /></a>
<a href="https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE"><img alt="License" src="https://img.shields.io/cocoapods/l/CoreStore.svg?style=flat" /></a>
<br /><br />Dependency managers<br />
<a href="https://cocoapods.org/pods/CoreStore"><img alt="Cocoapods compatible" src="https://img.shields.io/cocoapods/v/CoreStore.svg?style=flat&label=Cocoapods" /></a>
<a href="https://github.com/Carthage/Carthage"><img alt="Carthage compatible" src="https://img.shields.io/badge/Carthage-compatible-16a085.svg?style=flat" /></a>
<a href="https://swiftpkgs.ng.bluemix.net/package/JohnEstropia/CoreStore"><img alt="Swift Package Manager compatible" src="https://img.shields.io/badge/Swift_Package_Manager-compatible-orange.svg?style=flat" /></a>
<br /><br />Contact<br />
<a href="http://swift-corestore-slack.herokuapp.com/"><img alt="Join us on Slack!" src="http://swift-corestore-slack.herokuapp.com/badge.svg" /></a>
<a href="https://twitter.com/JohnEstropia"><img alt="Reach me on Twitter!" src="https://img.shields.io/badge/twitter-%40JohnEstropia-3498db.svg" /></a>
<br />
</p>
[Click here for a wiki version of this README](https://github.com/JohnEstropia/CoreStore/wiki)
* iOS 7+ / macOS 10.10+ / watchOS 2.0+ / tvOS 9.0+
- for Swift 2.2 (Xcode 7.3 and iOS 7): Use version [2.0.6](https://github.com/JohnEstropia/CoreStore/releases/tag/2.0.6) or the [master_ios_7_to_9](https://github.com/JohnEstropia/CoreStore/tree/master_ios_7_to_9) branch
- for Swift 2.3 (Xcode 8): Use version [2.1.0](https://github.com/JohnEstropia/CoreStore/releases/tag/2.1.0) or the [master](https://github.com/JohnEstropia/CoreStore/tree/master) branch
- for Swift 3 (Xcode 8): Use the [swift3_develop](https://github.com/JohnEstropia/CoreStore/tree/swift3_develop) branch
* **New in CoreStore 2.0:** Objective-C support! All CoreStore types now have their corresponding Objective-C "bridging classes". Perfect for projects transitioning from Objective-C to Swift!
Upgrading from CoreStore 1.x to 2.x? Check out the [new features](#new-in-corestore-20) and make sure to read the [Migration guide](#upgrading-from-1xx-to-2xx).
## Why use CoreStore?
I was [MagicalRecord](https://github.com/magicalpanda/MagicalRecord)'s heavy user back then, but I took the promising opportunity to create CoreStore when Swift came around. Part of the inspiration is to address the trend of developers [avoiding](http://inessential.com/2010/02/26/on_switching_away_from_core_data) [Core Data](http://bsktapp.com/blog/why-is-realm-great-and-why-are-we-not-using-it/) [for](https://www.quora.com/Why-would-you-use-Realm-over-Core-Data) [perplexing](http://sebastiandobrincu.com/blog/5-reasons-why-you-should-choose-realm-over-coredata) [reasons](https://medium.com/the-way-north/ditching-core-data-865c1bb5564c#.a5h8ou6ri).
## What CoreStore does better:
CoreStore was (and is) heavily shaped by real-world needs of developing data-dependent apps. It enforces safe and convenient Core Data usage while letting you take advantage of the industry's encouraged best practices. And with Core Data and Swift continuously being improved by Apple, CoreStore will just get better and better!
- **Heavily supports multiple persistent stores per data stack**, just the way *.xcdatamodeld* files are designed to. CoreStore will also manage one data stack by default, but you can create and manage as many as you need.
- **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 entities and their class names independently**.
- Provides type-safe, easy to configure **observers to replace `NSFetchedResultsController` and KVO**
- Exposes **API not just for fetching, but also for querying aggregates and property values**
- Makes it hard to fall into common concurrency mistakes. All `NSManagedObjectContext` tasks are encapsulated into **safer, higher-level abstractions** without sacrificing flexibility and customizability.
- Exposes clean and convenient API designed around **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.
- **Efficient importing utilities!**
**[Vote for the next feature!](http://goo.gl/RIiHMP)**
### Features
- **Heavy support for multiple persistent stores per data stack.** CoreStore lets you manage separate stores in a single `DataStack`, just the way *.xcdatamodeld* configurations are designed to. CoreStore will also manage one stack by default, but you can create and manage as many as you need. *(See [Setting up](#setting-up))*
- **Progressive migrations.** No need to think how to migrate from all previous model versions to your latest model. Just tell the `DataStack` the sequence of version strings (`MigrationChain`s) and CoreStore will automatically use progressive migrations when needed. *(See [Migrations](#migrations))*
- **Plug-in your own logging framework.** Although a default logger is built-in, all logging, asserting, and error reporting can be funneled to `CoreStoreLogger` protocol implementations. *(See [Logging and error reporting](#logging-and-error-reporting))*
- **Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign different names for the entities and their class names.
- **Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, `ListMonitor`s and `ObjectMonitor`s can have multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
- **Clean fetching and querying API.** Fetching objects is easy, but querying for raw aggregates (min, max, etc.) and raw property values is now just as convenient. *(See [Fetching and querying](#fetching-and-querying))*
- **Safer concurrency architecture.** CoreStore makes it hard to fall into common concurrency mistakes. The main `NSManagedObjectContext` is strictly read-only, while all updates are done through serial *transactions*. *(See [Saving and processing transactions](#saving-and-processing-transactions))*
- **Efficient importing utilities.** Map your entities once with their corresponding import source (JSON for example), and importing from *transactions* becomes elegant. Uniquing is also done with an efficient find-and-replace algorithm. *(See [Importing data](#importing-data))*
- **Tight design around Swifts code elegance and type safety.** CoreStore fully utilizes Swift's community-driven language features.
- **Full Documentation.** No magic here; all public classes, functions, properties, etc. have detailed *Apple Docs*. This *README* also introduces a lot of concepts and explains a lot of CoreStore's behavior.
### New in CoreStore 2.0
- **Informative (and pretty) logs.** All CoreStore and Core Data-related types now have very informative and pretty print outputs! *(See [Logging and error reporting](#logging-and-error-reporting))*
- **Objective-C support!** Is your project transitioning from Objective-C to Swift but still can't quite fully convert some huge classes to Swift yet? CoreStore 2.0 is the answer to the ever-increasing Swift adoption. While still written in pure Swift, all CoreStore types now have their corresponding Objective-C-visible "bridging classes". *(See [Objective-C support](#objective-c-support))*
- **iCloud storage (beta) support.** CoreStore now allows creation of iCloud persistent stores, as well as observing of iCloud-related events through the `ICloudStoreObserver`. *(See [iCloud storage](#icloud-storages))*
- **More extensive Unit Tests.** Extending CoreStore is now safer without having to worry about breaking old behavior.
*Have ideas that may benefit other Core Data users? [Feature Request](https://github.com/JohnEstropia/CoreStore/issues)s are welcome!*
## Contents
@@ -35,8 +59,11 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
- [Architecture](#architecture)
- CoreStore Tutorials (All of these have demos in the **CoreStoreDemo** app project!)
- [Setting up](#setting-up)
- [In-memory store](#in-memory-store)
- [Local store](#local-store)
- [iCloud store](#icloud-store)
- [Migrations](#migrations)
- [Incremental migrations](#incremental-migrations)
- [Progressive migrations](#progressive-migrations)
- [Forecasting migrations](#forecasting-migrations)
- [Saving and processing transactions](#saving-and-processing-transactions)
- [Transaction types](#transaction-types)
@@ -57,20 +84,24 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
- [Querying](#querying)
- [`Select<T>` clause](#selectt-clause)
- [`GroupBy` clause](#groupby-clause)
- [Logging and error handling](#logging-and-error-handling)
- [Observing changes and notifications](#observing-changes-and-notifications) (unavailable on OSX)
- [Logging and error reporting](#logging-and-error-reporting)
- [Observing changes and notifications](#observing-changes-and-notifications) (unavailable on macOS)
- [Observe a single object](#observe-a-single-object)
- [Observe a list of objects](#observe-a-list-of-objects)
- [Objective-C support](#objective-c-support)
- [Roadmap](#roadmap)
- [Installation](#installation)
- [Changesets](#changesets)
- [Upgrading from v0.2.0 to 1.0.0](#upgrading-from-v020-to-100)
- [Upgrading from 1.x.x to 2.x.x](#upgrading-from-1xx-to-2xx)
- [Contact](#contact)
- [Who uses CoreStore?](#who-uses-corestore)
- [License](#license)
## TL;DR (a.k.a. sample codes)
Setting-up with incremental migration support:
Setting-up with progressive migration support:
```swift
CoreStore.defaultStack = DataStack(
modelName: "MyStore",
@@ -80,17 +111,12 @@ CoreStore.defaultStack = DataStack(
Adding a store:
```swift
do {
try CoreStore.addSQLiteStore(
fileName: "MyStore.sqlite",
completion: { (result) -> Void in
// ...
}
)
}
catch {
// ...
}
CoreStore.addStorage(
SQLiteStore(fileName: "MyStore.sqlite"),
completion: { (result) -> Void in
// ...
}
)
```
Starting transactions:
@@ -109,10 +135,12 @@ CoreStore.beginAsynchronous { (transaction) -> Void in
}
```
Fetching objects:
Fetching objects (simple):
```swift
let people = CoreStore.fetchAll(From(MyPersonEntity))
```
Fetching objects (complex):
```swift
let people = CoreStore.fetchAll(
From(MyPersonEntity),
@@ -132,7 +160,7 @@ let maxAge = CoreStore.queryValue(
)
```
But really, there's a reason I wrote this huge README. Read up on the details!
But really, there's a reason I wrote this huge *README*. Read up on the details!
Check out the **CoreStoreDemo** app project for sample codes as well!
@@ -146,16 +174,16 @@ If you are already familiar with the inner workings of CoreData, here is a mappi
| *Core Data* | *CoreStore* |
| --- | --- |
| `NSManagedObjectModel` / `NSPersistentStoreCoordinator`<br />(.xcdatamodeld file) | `DataStack` |
| `NSPersistentStore`<br />("Configuration"s in the .xcdatamodeld file) | `DataStack` configuration<br />(multiple sqlite / in-memory stores per stack) |
| `NSPersistentStore`<br />("Configuration"s in the .xcdatamodeld file) | `StorageInterface` implementations<br />(`InMemoryStore`, `SQLiteStore`, `ICloudStore`) |
| `NSManagedObjectContext` | `BaseDataTransaction` subclasses<br />(`SynchronousDataTransaction`, `AsynchronousDataTransaction`, `UnsafeDataTransaction`) |
Popular libraries [RestKit](https://github.com/RestKit/RestKit) and [MagicalRecord](https://github.com/magicalpanda/MagicalRecord) set up their `NSManagedObjectContext`s this way:
A lot of Core Data wrapper libraries set up their `NSManagedObjectContext`s this way:
<img src="https://cloud.githubusercontent.com/assets/3029684/6734049/40579660-ce99-11e4-9d38-829877386afb.png" alt="nested contexts" height=271 />
<img src="https://cloud.githubusercontent.com/assets/3029684/16707160/984ef25c-4600-11e6-869f-8db7d2c63668.png" alt="nested contexts" height=380 />
Nesting context saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But as <a href="http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/">Florian Kugler's investigation</a> found out, merging contexts is still by far faster than saving nested contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context, and only allows changes to be made within *transactions* on the child context:
Nesting saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But <a href="http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/">in reality</a>, merging contexts is still by far faster than saving contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context, and only allows changes to be made within *transactions* on the child context:
<img src="https://cloud.githubusercontent.com/assets/3029684/6734050/4078b642-ce99-11e4-95ea-c0c1d24fbe80.png" alt="nested contexts and merge hybrid" height=212 />
<img src="https://cloud.githubusercontent.com/assets/3029684/16707161/9adeb962-4600-11e6-8bc8-4ec85764dba4.png" alt="nested contexts and merge hybrid" height=292 />
This allows for a butter-smooth main thread, while still taking advantage of safe nested contexts.
@@ -164,53 +192,35 @@ This allows for a butter-smooth main thread, while still taking advantage of saf
## Setting up
The simplest way to initialize CoreStore is to add a default store to the default stack:
```swift
do {
try CoreStore.addSQLiteStoreAndWait()
}
catch {
// ...
}
try CoreStore.addStorageAndWait()
```
This one-liner does the following:
- Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack`
- Sets up the stack's `NSPersistentStoreCoordinator`, the root saving `NSManagedObjectContext`, and the read-only main `NSManagedObjectContext`
- Adds an SQLite store in the *"Application Support"* directory (or the *"Caches"* directory on tvOS) with the file name *"[App bundle name].sqlite"*
- Adds an `SQLiteStore` in the *"Application Support/<bundle id>"* directory (or the *"Caches/<bundle id>"* directory on tvOS) with the file name *"[App bundle name].sqlite"*
- Creates and returns the `NSPersistentStore` instance on success, or an `NSError` on failure
For most cases, this configuration is usable as it is. But for more hardcore settings, refer to this extensive example:
For most cases, this configuration is enough as it is. But for more hardcore settings, refer to this extensive example:
```swift
let dataStack = DataStack(
modelName: "MyModel", // loads from the "MyModel.xcdatamodeld" file
migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"] // model versions for incremental migrations
migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"] // model versions for progressive migrations
)
do {
// creates an in-memory store with entities from the "Config1" configuration in the .xcdatamodeld file
let persistentStore = try dataStack.addInMemoryStoreAndWait(configuration: "Config1") // persistentStore is an NSPersistentStore instance
print("Successfully created an in-memory store: \(persistentStore)"
}
catch {
print("Failed creating an in-memory store with error: \(error as NSError)"
}
do {
try dataStack.addSQLiteStore(
let migrationProgress = dataStack.addStorage(
SQLiteStore(
fileURL: sqliteFileURL, // set the target file URL for the sqlite file
configuration: "Config2", // use entities from the "Config2" configuration in the .xcdatamodeld file
resetStoreOnModelMismatch: true,
completion: { (result) -> Void in
switch result {
case .Success(let persistentStore):
print("Successfully added sqlite store: \(persistentStore)"
case .Failure(let error):
print("Failed adding sqlite store with error: \(error)"
}
localStorageOptions: .RecreateStoreOnModelMismatch // if migration paths cannot be resolved, recreate the sqlite file
),
completion: { (result) -> Void in
switch result {
case .Success(let storage):
print("Successfully added sqlite store: \(storage)"
case .Failure(let error):
print("Failed adding sqlite store with error: \(error)"
}
)
}
catch {
print("Failed adding sqlite store with error: \(error as NSError)"
}
}
)
CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on
```
@@ -221,11 +231,11 @@ CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier
In our sample code above, note that you don't need to do the `CoreStore.defaultStack = dataStack` line. You can just as well hold a reference to the `DataStack` like below and call all its instance methods directly:
```swift
class MyViewController: UIViewController {
let dataStack = DataStack(modelName: "MyModel")
let dataStack = DataStack(modelName: "MyModel") // keep reference to the stack
override func viewDidLoad() {
super.viewDidLoad()
do {
try self.dataStack.addSQLiteStoreAndWait()
try self.dataStack.addStorageAndWait(SQLiteStore)
}
catch { // ...
}
@@ -239,10 +249,11 @@ class MyViewController: UIViewController {
The difference is when you set the stack as the `CoreStore.defaultStack`, you can call the stack's methods directly from `CoreStore` itself:
```swift
class MyViewController: UIViewController {
// elsewhere: CoreStore.defaultStack = DataStack(modelName: "MyModel")
override func viewDidLoad() {
super.viewDidLoad()
do {
try CoreStore.addSQLiteStoreAndWait()
try CoreStore.addStorageAndWait(SQLiteStore)
}
catch { // ...
}
@@ -254,44 +265,162 @@ class MyViewController: UIViewController {
}
```
Notice that in our previous examples, `addStorageAndWait(_:)` and `addStorage(_:completion:)` both accept either `InMemoryStore`, `SQLiteStore`, or `ICloudStore`. These implement the `StorageInterface` protocol.
## Migrations
So far we have only seen `addSQLiteStoreAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests, this method blocks so it should not do long tasks such as store migrations (in fact CoreStore won't even attempt to, and any model mismatch will be reported as an error). If migrations are expected, the asynchronous variant `addSQLiteStore(... completion:)` method should be used instead:
### In-memory store
The most basic `StorageInterface` concrete type is the `InMemoryStore`, which just stores objects in memory. Since `InMemoryStore`s always start with a fresh empty data, they do not need any migration information.
```swift
do {
let progress: NSProgress? = try dataStack.addSQLiteStore(
fileName: "MyStore.sqlite",
configuration: "Config2",
completion: { (result) -> Void in
switch result {
case .Success(let persistentStore):
print("Successfully added sqlite store: \(persistentStore)"
case .Failure(let error):
print("Failed adding sqlite store with error: \(error)"
}
}
try CoreStore.addStorageAndWait(
InMemoryStore(
configuration: "Config2" // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
)
}
catch {
print("Failed adding sqlite store with error: \(error as NSError)"
)
```
`InMemoryStore`s also implement the `DefaultInitializableStore` sugar protocol which tells CoreStore that this store can initialize without any arguments (`init()`). This lets us provide just the type instead of an instance:
```swift
try CoreStore.addStorageAndWait(InMemoryStore)
```
### Local Store
The most common `StorageInterface` you will probably use is the `SQLiteStore`, which saves data in a local SQLite file.
```swift
let migrationProgress = CoreStore.addStorage(
SQLiteStore(
fileName: "MyStore.sqlite",
configuration: "Config2", // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
mappingModelBundles: [NSBundle.mainBundle()], // optional. The bundles that contain required .xcmappingmodel files, if any
localStorageOptions: .RecreateStoreOnModelMismatch // optional. Provides settings that tells the DataStack how to setup the persistent store
),
completion: { /* ... */ }
)
```
Refer to the *SQLiteStore.swift* source documentation for detailed explanations for each of the default values.
CoreStore can decide the default values for these properties, so `SQLiteStore`s also implement the `DefaultInitializableStore` sugar protocol which lets us write:
```swift
try CoreStore.addStorageAndWait(SQLiteStore)
```
or
```swift
let migrationProgress = CoreStore.addStorage(SQLiteStore.self, completion: { /* ... */ })
```
The file-related properties above are actually requirements of another protocol that `SQLiteStore` implements, the `LocalStorage` protocol:
```swift
public protocol LocalStorage: StorageInterface {
var fileURL: NSURL { get }
var mappingModelBundles: [NSBundle] { get }
var localStorageOptions: LocalStorageOptions { get }
func storeOptionsForOptions(options: LocalStorageOptions) -> [String: AnyObject]?
func eraseStorageAndWait(soureModel soureModel: NSManagedObjectModel?) throws
}
```
The `completion` block reports a `PersistentStoreResult` that indicates success or failure.
If you have custom `NSIncrementalStore` or `NSAtomicStore` subclasses, you can implement this protocol and use it similarly to `SQLiteStore`.
`addSQLiteStore(...)` throws an error if the store at the specified URL conflicts with an existing store in the `DataStack`, or if an existing sqlite file could not be read. If an error is thrown, the `completion` block will not be executed.
### iCloud Store
> The iCloud Store is currently in beta. Please use with caution. If you have any concerns please do send me a message on [Twitter](https://twitter.com/JohnEstropia) or on the [CoreStore Slack Team](http://swift-corestore-slack.herokuapp.com/)
Notice that this method also returns an optional `NSProgress`. If `nil`, no migrations are needed, thus progress reporting is unnecessary as well. If not `nil`, you can use this to track migration progress by using standard KVO on the "fractionCompleted" key, or by using a closure-based utility exposed in *NSProgress+Convenience.swift*:
As a counterpart to `LocalStorage`, the `CloudStorage` protocol abstracts stores managed in the cloud. CoreStore currently provides the concrete class `ICloudStore`. Unlike `InMemoryStore` and `SQLiteStore` though, the `ICloudStore`'s initializer may return `nil` if the iCloud container could not be located or if iCloud is not available on the device:
```swift
progress?.setProgressHandler { [weak self] (progress) -> Void in
guard let storage = ICloudStore(
ubiquitousContentName: "MyAppCloudData", // the name of the store in iCloud
ubiquitousContentTransactionLogsSubdirectory: "logs/config1", // optional. Subdirectory path for the transaction logs
ubiquitousContainerID: "iCloud.com.mycompany.myapp.containername", // optional. The container if your app has multiple ubiquity container identifiers in its entitlements
ubiquitousPeerToken: "9614d658014f4151a95d8048fb717cf0", // optional. A per-application salt to allow multiple apps on the same device to share a Core Data store integrated with iCloud
configuration: "Config1", // optional. Use entities from the "Config1" configuration in the .xcdatamodeld file
cloudStorageOptions: .RecreateLocalStoreOnModelMismatch // optional. Provides settings that tells the DataStack how to setup the persistent store
) else {
// The iCloud container could not be located or if iCloud is not available on the device.
// Handle appropriately
return
}
CoreStore.addStorage(,
storage,
completion: { result in
switch result {
case .Success(let storage): // ...
case .Failure(let error): // ...
}
}
)
```
If your app is using iCloud stores, you may want to be notified of particular iCloud events. The `ICloudStoreObserver` functions are all optional, so you may implement only the ones your app is interested in:
```swift
public protocol ICloudStoreObserver: class {
func iCloudStoreWillFinishUbiquitousStoreInitialImport(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreDidFinishUbiquitousStoreInitialImport(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreWillAddAccount(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreDidAddAccount(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreWillRemoveAccount(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreDidRemoveAccount(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreWillRemoveContent(storage storage: ICloudStore, dataStack: DataStack)
func iCloudStoreDidRemoveContent(storage storage: ICloudStore, dataStack: DataStack)
}
```
To register your `ICloudStoreObserver`, call `addObserver(_:)` on the `ICloudStore` instance:
```swift
guard let storage = ICloudStore(/* ... */) else {
return
}
storage.addObserver(self) // assuming self implements ICloudStoreObserver
CoreStore.addStorage(,
storage,
completion: { result in
switch result {
case .Success(let storage): // ... You may also call storage.addObserver(_:) here
case .Failure(let error): // ...
}
}
)
```
The `ICloudStore` only keeps weak references of the registered observers. You may call `removeObserver(_:)` for precise deregistration, but `ICloudStore` automatically removes deallocated observers.
## Migrations
We have seen `addStorageAndWait(...)` used to initialize our persistent store. As the method name's "AndWait" suffix suggests though, this method blocks so it should not do long tasks such as store migrations. In fact CoreStore will only attempt a synchronous **lightweight** migration if you explicitly provide the `.AllowSynchronousLightweightMigration` option:
```swift
try dataStack.addStorageAndWait(
SQLiteStore(
fileURL: sqliteFileURL,
localStorageOptions: .AllowSynchronousLightweightMigration
)
}
```
if you do so, any model mismatch will be thrown as an error.
In general though, if migrations are expected the asynchronous variant `addStorage(_:completion:)` method is recommended instead:
```swift
let migrationProgress: NSProgress? = try dataStack.addStorage(
SQLiteStore(
fileName: "MyStore.sqlite",
configuration: "Config2"
),
completion: { (result) -> Void in
switch result {
case .Success(let storage):
print("Successfully added sqlite store: \(storage)")
case .Failure(let error):
print("Failed adding sqlite store with error: \(error)")
}
}
)
```
The `completion` block reports a `SetupResult` that indicates success or failure.
Notice that this method also returns an optional `NSProgress`. If `nil`, no migrations are needed, thus progress reporting is unnecessary as well. If not `nil`, you can use this to track migration progress by using standard KVO on the `"fractionCompleted"` key, or by using a closure-based utility exposed in *NSProgress+Convenience.swift*:
```swift
migrationProgress?.setProgressHandler { [weak self] (progress) -> Void in
self?.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
self?.percentLabel?.text = progress.localizedDescription // "50% completed"
self?.stepLabel?.text = progress.localizedAdditionalDescription // "0 of 2"
}
```
This closure is executed on the main thread so UIKit calls can be done safely.
This closure is executed on the main thread so UIKit and AppKit calls can be done safely.
### Incremental migrations
### Progressive migrations
By default, CoreStore uses Core Data's default automatic migration mechanism. In other words, CoreStore will try to migrate the existing persistent store to the *.xcdatamodeld* file's current model version. If no mapping model is found from the store's version to the data model's version, CoreStore gives up and reports an error.
The `DataStack` lets you specify hints on how to break a migration into several sub-migrations using a `MigrationChain`. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants:
@@ -301,7 +430,7 @@ let dataStack = DataStack(migrationChain:
```
The most common usage is to pass in the *.xcdatamodeld* version names in increasing order as above.
For more complex migration paths, you can also pass in a version tree that maps the key-values to the source-destination versions:
For more complex, non-linear migration paths, you can also pass in a version tree that maps the key-values to the source-destination versions:
```swift
let dataStack = DataStack(migrationChain: [
"MyAppModel": "MyAppModelV3",
@@ -314,7 +443,7 @@ This allows for different migration paths depending on the starting version. The
- MyAppModelV2-MyAppModelV4
- MyAppModelV3-MyAppModelV4
Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to disable incremental migrations and revert to the default migration behavior (i.e. use the .xcdatamodel's current version as the final version):
Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to disable progressive migrations and revert to the default migration behavior (i.e. use the .xcdatamodel's current version as the final version):
```swift
let dataStack = DataStack(migrationChain: nil)
```
@@ -329,28 +458,28 @@ One important thing to remember is that **if a `MigrationChain` is specified, th
### Forecasting migrations
Sometimes migrations are huge and you may want prior information so your app could display a loading screen, or to display a confirmation dialog to the user. For this, CoreStore provides a `requiredMigrationsForSQLiteStore(...)` method you can use to inspect a persistent store before you actually call `addSQLiteStore(...)`:
Sometimes migrations are huge and you may want prior information so your app could display a loading screen, or to display a confirmation dialog to the user. For this, CoreStore provides a `requiredMigrationsForStorage(_:)` method you can use to inspect a persistent store before you actually call `addStorageAndWait(_:)` or `addStorage(_:completion:)`:
```swift
do {
let migrationTypes: [MigrationType] = CoreStore.requiredMigrationsForSQLiteStore(fileName: "MyStore.sqlite")
let storage = SQLiteStorage(fileName: "MyStore.sqlite")
let migrationTypes: [MigrationType] = try CoreStore.requiredMigrationsForStorage(storage)
if migrationTypes.count > 1
|| (migrationTypes.filter { $0.isHeavyweightMigration }.count) > 0 {
// ... Show special waiting screen
// ... will migrate more than once. Show special waiting screen
}
else if migrationTypes.count > 0 {
// ... Show simple activity indicator
// ... will migrate just once. Show simple activity indicator
}
else {
// ... Do nothing
}
CoreStore.addSQLiteStore(/* ... */)
CoreStore.addStorage(storage, completion: { /* ... */ })
}
catch {
// ...
// ... either inspection of the store failed, or if no mapping model was found/inferred
}
```
`requiredMigrationsForSQLiteStore(...)` returns an array of `MigrationType`s, where each item in the array may be either of the following values:
`requiredMigrationsForStorage(_:)` returns an array of `MigrationType`s, where each item in the array may be either of the following values:
```swift
case Lightweight(sourceVersion: String, destinationVersion: String)
case Heavyweight(sourceVersion: String, destinationVersion: String)
@@ -730,7 +859,7 @@ or multiple objects at once with the `importUniqueObjects(...)` method:
```swift
CoreStore.beginAsynchronous { (transaction) -> Void in
let jsonArray: [[String: AnyObject]] = // ...
try! transaction.importObjects(
try! transaction.importUniqueObjects(
Into(MyPersonEntity),
sourceArray: jsonArray
)
@@ -1000,24 +1129,17 @@ this returns dictionaries that shows the count for each `"age"`:
]
```
## Logging and error handling
## Logging and error reporting
One unfortunate thing when using some third-party libraries is that they usually pollute the console with their own logging mechanisms. CoreStore provides its own default logging class, but you can plug-in your own favorite logger by implementing the `CoreStoreLogger` protocol.
```swift
final class MyLogger: CoreStoreLogger {
func log(#level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
// pass to your logger
}
func handleError(#error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
// pass to your logger
}
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
// pass to your logger
}
public protocol CoreStoreLogger {
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
func log(error error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
}
```
Then pass an instance of this class to `CoreStore`:
Implement this protocol with your custom class then pass the instance to `CoreStore.logger`:
```swift
CoreStore.logger = MyLogger()
```
@@ -1025,7 +1147,23 @@ Doing so channels all logging calls to your logger.
Note that to keep the call stack information intact, all calls to these methods are **NOT** thread-managed. Therefore you have to make sure that your logger is thread-safe or you may otherwise have to dispatch your logging implementation to a serial queue.
## Observing changes and notifications (unavailable on OSX)
Take special care when implementing `CoreStoreLogger`'s `assert(...)` and `abort(...)` functions:
- `assert(...)`: The behavior between `DEBUG` and release builds, or `-O` and `-Onone`, are all left to the implementers' responsibility. CoreStore calls `CoreStoreLogger.assert(...)` only for invalid but usually recoverable errors (for example, early validation failures that may cause an error thrown and handled somewhere else)
- `abort(...)`: This method is *the* last-chance for your app to *synchronously* log a fatal error within CoreStore. The app will be terminated right after this function is called (CoreStore calls `fatalError()` internally)
Starting CoreStore 2.0, all CoreStore types now have very useful (and pretty formatted!) `print(...)` outputs.
A couple of examples, `ListMonitor`:
<img width="369" alt="screen shot 2016-07-10 at 22 56 44" src="https://cloud.githubusercontent.com/assets/3029684/16713994/ae06e702-46f1-11e6-83a8-dee48b480bab.png" />
`CoreStoreError.MappingModelNotFoundError`:
<img width="506" alt="MappingModelNotFoundError" src="https://cloud.githubusercontent.com/assets/3029684/16713962/e021f548-46f0-11e6-8100-f9b5ea6b4a08.png" />
These are all implemented with `CustomDebugStringConvertible.debugDescription`, so they work with lldb's `po` command as well.
## Observing changes and notifications (unavailable on macOS)
CoreStore provides type-safe wrappers for observing managed objects:
- `ObjectMonitor`: use to monitor changes to a single `NSManagedObject` instance (instead of Key-Value Observing)
@@ -1078,23 +1216,20 @@ Including `ListObserver`, there are 3 observer protocols you can implement depen
- `ListObserver`: lets you handle these callback methods:
```swift
func listMonitorWillChange(monitor: ListMonitor<MyPersonEntity>)
func listMonitorDidChange(monitor: ListMonitor<MyPersonEntity>)
func listMonitorWillRefetch(monitor: ListMonitor<MyPersonEntity>)
func listMonitorDidRefetch(monitor: ListMonitor<MyPersonEntity>)
```
- `ListObjectObserver`: in addition to `ListObserver` methods, also lets you handle object inserts, updates, and deletes:
```swift
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath)
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath)
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath)
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didMoveObject object: MyPersonEntity, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
```
- `ListSectionObserver`: in addition to `ListObjectObserver` methods, also lets you handle section inserts and deletes:
```swift
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
func listMonitor(monitor: ListMonitor<MyPersonEntity>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
```
@@ -1160,18 +1295,144 @@ let person2 = self.monitor[1, 2]
// person1 and person2 are the same object
```
## Objective-C support
CoreStore 2.0 was a big move to address the large number of apps starting to convert from Objective-C to Swift. The basic problem is this: The cost of converting all code base to Swift is very big, so most apps are forced to do undergo a *transitional* ObjC-Swift hybrid phase. This used to mean that these apps could not use the Swifty-est libraries out there yet, or that they may have to write their own bridging methods just to make new code usable in their old Objective-C code.
With 2.0, all CoreStore types are still written in pure Swift, but they now have Objective-C "bridging classes" that are visible to Objective-C code. To show a couple of usage examples:
<table>
<tr><th>Swift</th><th>Objective-C</th></tr>
<tr>
<td><pre lang=swift>
try CoreStore.addStorageAndWait(SQLiteStore)
</pre></td>
<td><pre lang=objc>
NSError *error
[CSCoreStore addSQLiteStorageAndWait:[CSSQLiteStore new] error:&error]
</pre></td>
</tr>
<tr>
<td><pre lang=swift>
CoreStore.beginAsynchronous { (transaction) in
// ...
transaction.commit { (result) in
switch result {
case .Success(let hasChanges): print(hasChanges)
case .Failure(let error): print(error)
}
}
}
</pre></td>
<td><pre lang=objc>
[CSCoreStore beginAsynchronous:^(CSAsynchronousDataTransaction *transaction) {
// ...
[transaction commitWithCompletion:^(CSSaveResult *result) {
if (result.isSuccess) {
NSLog(@"hasChanges: %d", result.hasChanges);
}
else if (result.isFailure) {
NSLog(@"error: %@", result.error);
}
}];
}];
</pre></td>
</tr>
</table>
All of these `CS`-prefixed bridging classes have very similar usage to the existing CoreStore APIs, and ironically *none of them are written in Objective-C*. The secret is all in *CoreStoreBridge.swift*, where we see the signature of these bridging classes, the `CoreStoreObjectiveCType` protocol:
```swift
public protocol CoreStoreObjectiveCType: class, AnyObject {
associatedtype SwiftType
var bridgeToSwift: SwiftType { get }
init(_ swiftValue: SwiftType)
}
```
Notice that these bridging classes all hold a reference to their corresponding `SwiftType`.
Conversely, CoreStore original types implement the `CoreStoreSwiftType` protocol:
```swift
public protocol CoreStoreSwiftType {
associatedtype ObjectiveCType
var bridgeToObjectiveC: ObjectiveCType { get }
}
```
These two protocols let CoreStore types free to bridge instances between Objective-C and Swift.
This is very different to the common approach where apps and libraries write Objective-C APIs just to support both Objective-C and Swift. The advantage with CoreStore's approach is that your Swift codebase can already use the purely-Swift API without further changes in the future, but your "hybrid" codebase can still bridge instances back and forth from Objective-C to Swift.
For example, you may have a new, modern Swift class that holds a `ListMonitor`:
```swift
class MyViewController: UIViewController {
let monitor = CoreStore.monitorList(From(MyEntity), ...)
// ...
}
```
Now let's say you have a legacy Objective-C class that previously uses `NSFetchedResultsController`. It's easy to switch from `NSFetchedResultsController` to `CSListMonitor`, but converting the rest of this huge class is impractical. You end up with
```objc
@interface MYOldViewController: UIViewController
@property (nonatomic, readonly, strong) CSListMonitor* monitor;
- (instancetype)initWithMonitor:(CSListMonitor *)monitor;
@end
```
When you need to instantiate this class from Swift, you just call `bridgeToObjectiveC`:
```swift
class MyViewController: UIViewController {
let monitor = CoreStore.monitorList(From(MyEntity), ...)
func showOldController() {
let controller = MYOldViewController(monitor: self.monitor.bridgeToObjectiveC)
self.presentViewController(controller, animated: true, completion: nil)
}
}
```
Note that the `CSListMonitor` holds the exact same `ListMonitor` instance, which means that no copies and no extra fetching occur.
### Objective-C syntax sugars
Objective-C tends to be verbose, so some method calls are long and unreadable. For example, fetching looks like this:
```objc
NSArray<MYPerson *> *objects =
[CSCoreStore
fetchAllFrom:[[CSFrom alloc] initWithEntityClass:[MYPerson class]]
fetchClauses:@[[[CSWhere alloc] initWithFormat:@"%K == %@", @"isHidden", @NO],
[[CSOrderBy alloc] initWithSortDescriptors:@[[NSSortDescriptor sortDescriptorWithKey:@"lastName" ascending:YES],
[NSSortDescriptor sortDescriptorWithKey:@"firstName" ascending:YES]]]]];
```
Although it works, it looks terrible. For this, CoreStore provides *CoreStoreBridge.h* where these Objective-C calls are wrapped in readable, convenient macros and global functions. The call above becomes
```objc
NSArray<MYPerson *> *objects =
[CSCoreStore
fetchAllFrom:CSFromClass([MYPerson class])
fetchClauses:@[CSWhereFormat(@"%K == %@", @"isHidden", @NO),
CSOrderByKeys(CSSortAscending(@"lastName"),
CSSortAscending(@"firstName"), nil)]];
```
That's much shorter now. But we can still do better. Notice that we have strings being used as key paths. The `CSKeyPath(...)` macro gives us compile-time checking so keys that don't exist in a class will generate errors. Our key-safe code now looks like this:
```objc
NSArray<MYPerson *> *objects =
[CSCoreStore
fetchAllFrom:CSFromClass([MYPerson class])
fetchClauses:@[CSWhereFormat(@"%K == %@", CSKeyPath(MYPerson, isHidden), @NO),
CSOrderByKeys(CSSortAscending(CSKeyPath(MYPerson, lastName)),
CSSortAscending(CSKeyPath(MYPerson, firstName)), nil)]];
```
To use these syntax sugars, include *CoreStoreBridge.h* in your Objective-C source files.
# Roadmap
- Support iCloud stores
- CoreSpotlight auto-indexing (experimental)
- Built-in "singleton objects" support
- Built-in "readonly" stores
- CoreSpotlight auto-indexing (experimenting, still some roadblocks ahead)
- Synching
# Installation
- Requires:
- iOS 8 SDK and above
- Swift 2.0 (XCode 7 beta 6)
- Swift 2.3 (Xcode 8)
- Dependencies:
- [GCDKit](https://github.com/JohnEstropia/GCDKit)
- Other notes:
- The `com.apple.CoreData.ConcurrencyDebug` debug argument should be turned off for the app. CoreStore already guarantees safety for you by making the main context read-only, and by only executing transactions serially.
### Install with CocoaPods
```
@@ -1182,8 +1443,8 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
### Install with Carthage
In your `Cartfile`, add
```
github "JohnEstropia/CoreStore" >= 1.4.4
github "JohnEstropia/GCDKit" >= 1.1.7
github "JohnEstropia/CoreStore" >= 2.1.0
github "JohnEstropia/GCDKit" >= 1.3.0
```
and run
```
@@ -1203,27 +1464,48 @@ Drag and drop **CoreStore.xcodeproj** to your project.
Add all *.swift* files to your project.
### Objective-C support
To use the Objective-C syntax sugars, import *CoreStoreBridge.h* in your *.m* source files.
# Changesets
### Upgrading from v0.2.0 to 1.0.0
- Renamed some classes/protocols to shorter, more relevant, easier to remember names:
- `ManagedObjectController` to `ObjectMonitor`
- `ManagedObjectObserver` to `ObjectObserver`
- `ManagedObjectListController` to `ListMonitor`
- `ManagedObjectListChangeObserver` to `ListObserver`
- `ManagedObjectListObjectObserver` to `ListObjectObserver`
- `ManagedObjectListSectionObserver` to `ListSectionObserver`
- `SectionedBy` to `SectionBy` (match tense with `OrderBy` and `GroupBy`)
The protocols above had their methods renamed as well, to retain the natural language semantics.
- Several methods now `throw` errors insted of returning a result `enum`.
- New migration utilities! (README still pending) Check out *DataStack+Migration.swift* and *CoreStore+Migration.swift* for the new methods, as well as *DataStack.swift* for its new initializer.
### Upgrading from 1.x.x to 2.x.x
**Obsoleted**
- `AsynchronousDataTransaction.rollback()` was removed. Undo and rollback functionality are now only allowed on `UnsafeDataTransaction`s
- `DetachedDataTransaction` was renamed to `UnsafeDataTransaction`
- `beginDetached()` was renamed to `beginUnsafe()`
- `PersistentStoreResult` was removed in favor of `SetupResult<T>`
- `SynchronousDataTransaction.commit()` was renamed to `SynchronousDataTransaction.commitAndWait()`
- `From` initializers that accepted `NSURL`s and `NSPersistentStore` were removed.
**Deprecated**
The following methods are still available, but will be removed in a future update.
- `add*Store(...)` method variants. It is strongly recommended to convert to the new API. Refer to [Local store](#local-store)) usage then use `LegacySQLiteStore` instead of `SQLiteStore` to maintain the old default file names and directory values
- `addInMemoryStoreAndWait(...)``addStorageAndWait(InMemoryStore(...))`
- `addSQLiteStoreAndWait(...)``addStorageAndWait(LegacySQLiteStore(...))`
- `addInMemoryStore(...)``addStorage(InMemoryStore(...), ...)`
- `addSQLiteStore(...)``addStorage(LegacySQLiteStore(...), ...)`
- `requiredMigrationsForSQLiteStore(...)``requiredMigrationsForStorage(...)`
- `upgradeSQLiteStoreIfNeeded(...)``upgradeStorageIfNeeded(...)`
- The `resetStoreOnModelMismatch: Bool` argument for the methods above are now provided to the `LegacySQLiteStore` and `SQLiteStore` initializers as a `LocalStorageOptions` option set
- `NSError` used to have a `coreStoreErrorCode` property that returns `CoreStoreErrorCode` enum, but all CoreStore errors are now guaranteed to be `CoreStoreError` enum type in swift, and `CSError` type on Objective-C.
- `CoreStoreLogger.handleError(...)` was deprecated in favor of `CoreStoreLogger.log(error:...)`. `CoreStoreLogger` may also implement `CoreStoreLogger.abort(...)`, which is called just before CoreStore executes `fatalError()` due to critical runtime errors.
# Contributions
While CoreStore's design is pretty solid and the unit test and demo app work well, CoreStore is pretty much still in its early stage. With more exposure to production code usage and criticisms from the developer community, CoreStore hopes to mature as well.
Please feel free to report any issues, suggestions, or criticisms!
日本語で連絡していただいても構いません!
# Contact
Questions? Suggestions?
## License
CoreStore is released under an MIT license. See the LICENSE file for more information
Reach me on Twitter [@JohnEstropia](https://twitter.com/JohnEstropia)
or join our Slack team at [swift-corestore.slack.com](http://swift-corestore-slack.herokuapp.com/)
日本語の対応も可能なので是非!
# Who uses CoreStore?
I'd love to hear about apps using CoreStore. Send me a message and I'll feature your apps here!
# License
CoreStore is released under an MIT license. See the [LICENSE](https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE) file for more information

View File

@@ -0,0 +1,223 @@
//
// NSManagedObject+Convenience.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - NSFetchedResultsController
public extension NSFetchedResultsController {
/**
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter dataStack: the `DataStack` to observe objects from
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: an `NSFetchedResultsController` that observes a `DataStack`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
return self.createFromContext(
dataStack.mainContext,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter dataStack: the `DataStack` to observe objects from
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: an `NSFetchedResultsController` that observes a `DataStack`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return self.createFromContext(
dataStack.mainContext,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter dataStack: the `DataStack` to observe objects from
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: an `NSFetchedResultsController` that observes a `DataStack`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
return self.createFromContext(
dataStack.mainContext,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: nil,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter dataStack: the `DataStack` to observe objects from
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: an `NSFetchedResultsController` that observes a `DataStack`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return self.createFromContext(
dataStack.mainContext,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: nil,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
return self.createFromContext(
transaction.context,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return self.createFromContext(
transaction.context,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
return self.createFromContext(
transaction.context,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: nil,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
@nonobjc
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return self.createFromContext(
transaction.context,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: nil,
fetchClauses: fetchClauses
)
}
// MARK: Internal
@nonobjc
internal static func createFromContext<T: NSManagedObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return CoreStoreFetchedResultsController(
context: context,
fetchRequest: fetchRequest,
from: from,
sectionBy: sectionBy,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
CoreStore.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(cs_typeName(NSFetchedResultsController)) requires a sort information. Specify from a \(cs_typeName(OrderBy)) clause or any custom \(cs_typeName(FetchClause)) that provides a sort descriptor."
)
}
)
}
}
#endif

View File

@@ -37,6 +37,7 @@ public extension NSManagedObject {
- parameter KVCKey: the KVC key
- returns: the primitive value for the KVC key
*/
@nonobjc
@warn_unused_result
public func accessValueForKVCKey(KVCKey: KeyPath) -> AnyObject? {
@@ -53,6 +54,7 @@ public extension NSManagedObject {
- parameter value: the value to set the KVC key with
- parameter KVCKey: the KVC key
*/
@nonobjc
public func setValue(value: AnyObject?, forKVCKey KVCKey: KeyPath) {
self.willChangeValueForKey(KVCKey)
@@ -63,6 +65,7 @@ public extension NSManagedObject {
/**
Re-faults the object to use the latest values from the persistent store
*/
@nonobjc
public func refreshAsFault() {
self.managedObjectContext?.refreshObject(self, mergeChanges: false)
@@ -71,6 +74,7 @@ public extension NSManagedObject {
/**
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
*/
@nonobjc
public func refreshAndMerge() {
self.managedObjectContext?.refreshObject(self, mergeChanges: true)

View File

@@ -35,8 +35,10 @@ public extension NSProgress {
/**
Sets a closure that the `NSProgress` calls whenever its `fractionCompleted` changes. You can use this instead of setting up KVO.
- parameter closure: the closure to execute on progress change
*/
@nonobjc
public func setProgressHandler(closure: ((progress: NSProgress) -> Void)?) {
self.progressObserver.progressHandler = closure
@@ -50,18 +52,19 @@ public extension NSProgress {
static var progressObserver: Void?
}
@nonobjc
private var progressObserver: ProgressObserver {
get {
let object: ProgressObserver? = getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
let object: ProgressObserver? = cs_getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
if let observer = object {
return observer
}
let observer = ProgressObserver(self)
setAssociatedRetainedObject(
cs_setAssociatedRetainedObject(
observer,
forKey: &PropertyKeys.progressObserver,
inObject: self
@@ -73,7 +76,10 @@ public extension NSProgress {
}
@objc private final class ProgressObserver: NSObject {
// MARK: - ProgressObserver
@objc
private final class ProgressObserver: NSObject {
private unowned let progress: NSProgress
private var progressHandler: ((progress: NSProgress) -> Void)? {

View File

@@ -28,3 +28,5 @@
FOUNDATION_EXPORT double CoreStoreVersionNumber;
FOUNDATION_EXPORT const unsigned char CoreStoreVersionString[];
#import "CoreStoreBridge.h"

View File

@@ -38,8 +38,8 @@ public enum CoreStore {
/**
The default `DataStack` instance to be used. If `defaultStack` is not set before the first time accessed, a default-configured `DataStack` will be created.
Changing the `defaultStack` is thread safe, but it is recommended to setup `DataStacks` on a common queue (e.g. the main queue).
- SeeAlso: `DataStack`
- Note: Changing the `defaultStack` is thread safe, but it is recommended to setup `DataStacks` on a common queue (e.g. the main queue).
*/
public static var defaultStack: DataStack {

View File

@@ -0,0 +1,227 @@
//
// CoreStoreError.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStoreError
/**
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
*/
public enum CoreStoreError: ErrorType, Hashable {
/**
A failure occured because of an unknown error.
*/
case Unknown
/**
The `NSPersistentStore` could not be initialized because another store existed at the specified `NSURL`.
*/
case DifferentStorageExistsAtURL(existingPersistentStoreURL: NSURL)
/**
An `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case MappingModelNotFound(localStoreURL: NSURL, targetModel: NSManagedObjectModel, targetModelVersion: String)
/**
Progressive migrations are disabled for a store, but an `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case ProgressiveMigrationRequired(localStoreURL: NSURL)
/**
An internal SDK call failed with the specified `NSError`.
*/
case InternalError(NSError: NSError)
// MARK: ErrorType
public var _domain: String {
return CoreStoreErrorDomain
}
public var _code: Int {
switch self {
case .Unknown:
return CoreStoreErrorCode.UnknownError.rawValue
case .DifferentStorageExistsAtURL:
return CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue
case .MappingModelNotFound:
return CoreStoreErrorCode.MappingModelNotFound.rawValue
case .ProgressiveMigrationRequired:
return CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue
case .InternalError:
return CoreStoreErrorCode.InternalError.rawValue
}
}
// MARK: Hashable
public var hashValue: Int {
let code = self._code
switch self {
case .Unknown:
return code.hashValue
case .DifferentStorageExistsAtURL(let existingPersistentStoreURL):
return code.hashValue ^ existingPersistentStoreURL.hashValue
case .MappingModelNotFound(let localStoreURL, let targetModel, let targetModelVersion):
return code.hashValue ^ localStoreURL.hashValue ^ targetModel.hashValue ^ targetModelVersion.hashValue
case .ProgressiveMigrationRequired(let localStoreURL):
return code.hashValue ^ localStoreURL.hashValue
case .InternalError(let NSError):
return code.hashValue ^ NSError.hashValue
}
}
// MARK: Internal
internal init(_ error: ErrorType?) {
self = error.flatMap { $0.bridgeToSwift } ?? .Unknown
}
}
// MARK: - CoreStoreError: Equatable
@warn_unused_result
public func == (lhs: CoreStoreError, rhs: CoreStoreError) -> Bool {
switch (lhs, rhs) {
case (.Unknown, .Unknown):
return true
case (.DifferentStorageExistsAtURL(let url1), .DifferentStorageExistsAtURL(let url2)):
return url1 == url2
case (.MappingModelNotFound(let url1, let model1, let version1), .MappingModelNotFound(let url2, let model2, let version2)):
return url1 == url2 && model1 == model2 && version1 == version2
case (.ProgressiveMigrationRequired(let url1), .ProgressiveMigrationRequired(let url2)):
return url1 == url2
case (.InternalError(let NSError1), .InternalError(let NSError2)):
return NSError1 == NSError2
default:
return false
}
}
// MARK: - CoreStoreErrorDomain
/**
The `NSError` error domain string for `CSError`.
*/
@nonobjc
public let CoreStoreErrorDomain = "com.corestore.error"
// MARK: - CoreStoreErrorCode
/**
The `NSError` error codes for `CoreStoreErrorDomain`.
*/
public enum CoreStoreErrorCode: Int {
/**
A failure occured because of an unknown error.
*/
case UnknownError
/**
The `NSPersistentStore` could note be initialized because another store existed at the specified `NSURL`.
*/
case DifferentPersistentStoreExistsAtURL
/**
An `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case MappingModelNotFound
/**
Progressive migrations are disabled for a store, but an `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case ProgressiveMigrationRequired
/**
An internal SDK call failed with the specified "NSError" userInfo key.
*/
case InternalError
}
// MARK: - NSError
public extension NSError {
// MARK: Internal
internal var isCoreDataMigrationError: Bool {
let code = self.code
return (code == NSPersistentStoreIncompatibleVersionHashError
|| code == NSMigrationMissingSourceModelError
|| code == NSMigrationError)
&& self.domain == NSCocoaErrorDomain
}
// MARK: Deprecated
/**
Deprecated. Use `CoreStoreError` enum values instead.
If the error's domain is equal to `CoreStoreErrorDomain`, returns the associated `CoreStoreErrorCode`. For other domains, returns `nil`.
*/
@available(*, deprecated=2.0.0, message="Use CoreStoreError enum values instead.")
public var coreStoreErrorCode: CoreStoreErrorCode? {
return (self.domain == CoreStoreErrorDomain
? CoreStoreErrorCode(rawValue: self.code)
: nil)
}
}

View File

@@ -103,12 +103,7 @@ public extension BaseDataTransaction {
@warn_unused_result
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchOne(from, fetchClauses)
return self.fetchOne(from, fetchClauses)
}
/**
@@ -123,9 +118,8 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchOne(from, fetchClauses)
}
@@ -139,12 +133,7 @@ public extension BaseDataTransaction {
@warn_unused_result
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchAll(from, fetchClauses)
return self.fetchAll(from, fetchClauses)
}
/**
@@ -159,9 +148,8 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchAll(from, fetchClauses)
}
@@ -175,12 +163,7 @@ public extension BaseDataTransaction {
@warn_unused_result
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchCount(from, fetchClauses)
return self.fetchCount(from, fetchClauses)
}
/**
@@ -195,7 +178,7 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchCount(from, fetchClauses)
@@ -211,12 +194,7 @@ public extension BaseDataTransaction {
@warn_unused_result
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchObjectID(from, fetchClauses)
return self.fetchObjectID(from, fetchClauses)
}
/**
@@ -231,9 +209,8 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchObjectID(from, fetchClauses)
}
@@ -247,12 +224,7 @@ public extension BaseDataTransaction {
@warn_unused_result
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchObjectIDs(from, fetchClauses)
return self.fetchObjectIDs(from, fetchClauses)
}
/**
@@ -267,9 +239,8 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchObjectIDs(from, fetchClauses)
}
@@ -284,7 +255,7 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete from a \(typeName(self)) outside its designated queue."
"Attempted to delete from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.deleteAll(from, deleteClauses)
@@ -301,7 +272,7 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete from a \(typeName(self)) outside its designated queue."
"Attempted to delete from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.deleteAll(from, deleteClauses)
@@ -322,7 +293,7 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to query from a \(typeName(self)) outside its designated queue."
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.queryValue(from, selectClause, queryClauses)
@@ -343,7 +314,7 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to query from a \(typeName(self)) outside its designated queue."
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.queryValue(from, selectClause, queryClauses)
@@ -364,7 +335,7 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to query from a \(typeName(self)) outside its designated queue."
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.queryAttributes(from, selectClause, queryClauses)
@@ -385,7 +356,7 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to query from a \(typeName(self)) outside its designated queue."
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.queryAttributes(from, selectClause, queryClauses)

View File

@@ -0,0 +1,373 @@
//
// From.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - From
/**
A `From` clause specifies the source entity and source persistent store for fetch and query methods. A common usage is to just indicate the entity:
```
let person = transaction.fetchOne(From(MyPersonEntity))
```
For cases where multiple `NSPersistentStore`s contain the same entity, the source configuration's name needs to be specified as well:
```
let person = transaction.fetchOne(From<MyPersonEntity>("Configuration1"))
```
*/
public struct From<T: NSManagedObject> {
/**
The associated `NSManagedObject` entity class
*/
public let entityClass: AnyClass
/**
The `NSPersistentStore` configuration names to associate objects from.
May contain `String`s to pertain to named configurations, or `nil` to pertain to the default configuration
*/
public let configurations: [String?]?
/**
Initializes a `From` clause.
```
let people = transaction.fetchAll(From<MyPersonEntity>())
```
*/
public init(){
self.init(entityClass: T.self, configurations: nil)
}
/**
Initializes a `From` clause with the specified entity type.
```
let people = transaction.fetchAll(From<MyPersonEntity>())
```
- parameter entity: the associated `NSManagedObject` type
*/
public init(_ entity: T.Type) {
self.init(entityClass: entity, configurations: nil)
}
/**
Initializes a `From` clause with the specified entity class.
```
let people = transaction.fetchAll(From<MyPersonEntity>())
```
- parameter entityClass: the associated `NSManagedObject` entity class
*/
public init(_ entityClass: AnyClass) {
CoreStore.assert(
entityClass is T.Type,
"Attempted to create generic type \(cs_typeName(From<T>)) with entity class \(cs_typeName(entityClass))"
)
self.init(entityClass: entityClass, configurations: nil)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From<MyPersonEntity>(nil, "Configuration1"))
```
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
*/
public init(_ configuration: String?, _ otherConfigurations: String?...) {
self.init(entityClass: T.self, configurations: [configuration] + otherConfigurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From<MyPersonEntity>(["Configuration1", "Configuration2"]))
```
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ configurations: [String?]) {
self.init(entityClass: T.self, configurations: configurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From(MyPersonEntity.self, nil, "Configuration1"))
```
- parameter entity: the associated `NSManagedObject` type
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
*/
public init(_ entity: T.Type, _ configuration: String?, _ otherConfigurations: String?...) {
self.init(entityClass: entity, configurations: [configuration] + otherConfigurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From(MyPersonEntity.self, ["Configuration1", "Configuration1"]))
```
- parameter entity: the associated `NSManagedObject` type
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ entity: T.Type, _ configurations: [String?]) {
self.init(entityClass: entity, configurations: configurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From(MyPersonEntity.self, nil, "Configuration1"))
```
- parameter entity: the associated `NSManagedObject` entity class
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
*/
public init(_ entityClass: AnyClass, _ configuration: String?, _ otherConfigurations: String?...) {
CoreStore.assert(
entityClass is T.Type,
"Attempted to create generic type \(cs_typeName(From<T>)) with entity class \(cs_typeName(entityClass))"
)
self.init(entityClass: entityClass, configurations: [configuration] + otherConfigurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From(MyPersonEntity.self, ["Configuration1", "Configuration1"]))
```
- parameter entity: the associated `NSManagedObject` entity class
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ entityClass: AnyClass, _ configurations: [String?]) {
CoreStore.assert(
entityClass is T.Type,
"Attempted to create generic type \(cs_typeName(From<T>)) with entity class \(cs_typeName(entityClass))"
)
self.init(entityClass: entityClass, configurations: configurations)
}
// MARK: Internal
@warn_unused_result
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext, applyAffectedStores: Bool = true) -> Bool {
fetchRequest.entity = context.entityDescriptionForEntityClass(self.entityClass)
guard applyAffectedStores else {
return true
}
if self.applyAffectedStoresForFetchedRequest(fetchRequest, context: context) {
return true
}
CoreStore.log(
.Warning,
message: "Attempted to perform a fetch but could not find any persistent store for the entity \(cs_typeName(fetchRequest.entityName))"
)
return false
}
internal func applyAffectedStoresForFetchedRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) -> Bool {
let stores = self.findPersistentStores(context: context)
fetchRequest.affectedStores = stores
return stores?.isEmpty == false
}
internal func upcast() -> From<NSManagedObject> {
return From<NSManagedObject>(
entityClass: self.entityClass,
configurations: self.configurations,
findPersistentStores: self.findPersistentStores
)
}
// MARK: Private
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
private init(entityClass: AnyClass, configurations: [String?]?) {
self.entityClass = entityClass
self.configurations = configurations
if let configurations = configurations {
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return configurationsSet.contains($0.configurationName)
}
}
}
else {
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)
}
}
}
private init(entityClass: AnyClass, configurations: [String?]?, findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?) {
self.entityClass = entityClass
self.configurations = configurations
self.findPersistentStores = findPersistentStores
}
// MARK: Obsolete
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ storeURLs: [NSURL]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entity: T.Type, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entityClass: AnyClass, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ persistentStores: [NSPersistentStore]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entity: T.Type, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entityClass: AnyClass, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
}

View File

@@ -32,17 +32,12 @@ import CoreData
/**
The `GroupBy` clause specifies that the result of a query be grouped accoording to the specified key path.
*/
public struct GroupBy: QueryClause {
public struct GroupBy: QueryClause, Hashable {
/**
Initializes a `GroupBy` clause with a list of key path strings
- parameter keyPaths: a list of key path strings to group results with
The list of key path strings to group results with
*/
public init(_ keyPaths: [KeyPath]) {
self.keyPaths = keyPaths
}
public let keyPaths: [KeyPath]
/**
Initializes a `GroupBy` clause with an empty list of key path strings
@@ -63,7 +58,15 @@ public struct GroupBy: QueryClause {
self.init([keyPath] + keyPaths)
}
public let keyPaths: [KeyPath]
/**
Initializes a `GroupBy` clause with a list of key path strings
- parameter keyPaths: a list of key path strings to group results with
*/
public init(_ keyPaths: [KeyPath]) {
self.keyPaths = keyPaths
}
// MARK: QueryClause
@@ -74,10 +77,27 @@ public struct GroupBy: QueryClause {
CoreStore.log(
.Warning,
message: "An existing \"propertiesToGroupBy\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
message: "An existing \"propertiesToGroupBy\" for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
)
}
fetchRequest.propertiesToGroupBy = self.keyPaths
}
// MARK: Hashable
public var hashValue: Int {
return (self.keyPaths as NSArray).hashValue
}
}
// MARK: - GroupBy: Equatable
@warn_unused_result
public func == (lhs: GroupBy, rhs: GroupBy) -> Bool {
return lhs.keyPaths == rhs.keyPaths
}

View File

@@ -67,17 +67,12 @@ public enum SortKey {
/**
The `OrderBy` clause specifies the sort order for results for a fetch or a query.
*/
public struct OrderBy: FetchClause, QueryClause, DeleteClause {
public struct OrderBy: FetchClause, QueryClause, DeleteClause, Hashable {
/**
Initializes a `OrderBy` clause with a list of sort descriptors
- parameter sortDescriptors: a series of `NSSortDescriptor`s
The list of sort descriptors
*/
public init(_ sortDescriptors: [NSSortDescriptor]) {
self.sortDescriptors = sortDescriptors
}
public let sortDescriptors: [NSSortDescriptor]
/**
Initializes a `OrderBy` clause with an empty list of sort descriptors
@@ -97,6 +92,16 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
self.init([sortDescriptor])
}
/**
Initializes a `OrderBy` clause with a list of sort descriptors
- parameter sortDescriptors: a series of `NSSortDescriptor`s
*/
public init(_ sortDescriptors: [NSSortDescriptor]) {
self.sortDescriptors = sortDescriptors
}
/**
Initializes a `OrderBy` clause with a series of `SortKey`s
@@ -130,8 +135,6 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
self.init([sortKey] + sortKeys)
}
public let sortDescriptors: [NSSortDescriptor]
// MARK: FetchClause, QueryClause, DeleteClause
@@ -141,10 +144,27 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause {
CoreStore.log(
.Warning,
message: "Existing sortDescriptors for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
message: "Existing sortDescriptors for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
)
}
fetchRequest.sortDescriptors = self.sortDescriptors
}
// MARK: Hashable
public var hashValue: Int {
return (self.sortDescriptors as NSArray).hashValue
}
}
// MARK: - OrderBy: Equatable
@warn_unused_result
public func == (lhs: OrderBy, rhs: OrderBy) -> Bool {
return lhs.sortDescriptors == rhs.sortDescriptors
}

View File

@@ -32,7 +32,7 @@ import CoreData
/**
The `SelectResultType` protocol is implemented by return types supported by the `Select` clause.
*/
public protocol SelectResultType { }
public protocol SelectResultType {}
// MARK: - SelectValueResultType
@@ -42,6 +42,8 @@ public protocol SelectResultType { }
*/
public protocol SelectValueResultType: SelectResultType {
static var attributeType: NSAttributeType { get }
static func fromResultObject(result: AnyObject) -> Self?
}
@@ -62,7 +64,7 @@ public protocol SelectAttributesResultType: SelectResultType {
/**
The `SelectTerm` is passed to the `Select` clause to indicate the attributes/aggregate keys to be queried.
*/
public enum SelectTerm: StringLiteralConvertible {
public enum SelectTerm: StringLiteralConvertible, Hashable {
/**
Provides a `SelectTerm` to a `Select` clause for querying an entity attribute. A shorter way to do the same is to assign from the string keypath directly:
@@ -81,6 +83,7 @@ public enum SelectTerm: StringLiteralConvertible {
Where("employeeID", isEqualTo: 1111)
)
```
- parameter keyPath: the attribute name
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
*/
@@ -97,6 +100,7 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Average("age"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
@@ -105,8 +109,8 @@ public enum SelectTerm: StringLiteralConvertible {
return ._Aggregate(
function: "average:",
keyPath,
As: alias ?? "average(\(keyPath))",
keyPath: keyPath,
alias: alias ?? "average(\(keyPath))",
nativeType: .DecimalAttributeType
)
}
@@ -119,6 +123,7 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Count("employeeID"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for a count query
@@ -127,8 +132,8 @@ public enum SelectTerm: StringLiteralConvertible {
return ._Aggregate(
function: "count:",
keyPath,
As: alias ?? "count(\(keyPath))",
keyPath: keyPath,
alias: alias ?? "count(\(keyPath))",
nativeType: .Integer64AttributeType
)
}
@@ -141,6 +146,7 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Maximum("age"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
@@ -149,8 +155,8 @@ public enum SelectTerm: StringLiteralConvertible {
return ._Aggregate(
function: "max:",
keyPath,
As: alias ?? "max(\(keyPath))",
keyPath: keyPath,
alias: alias ?? "max(\(keyPath))",
nativeType: .UndefinedAttributeType
)
}
@@ -163,6 +169,7 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Minimum("age"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
@@ -171,8 +178,8 @@ public enum SelectTerm: StringLiteralConvertible {
return ._Aggregate(
function: "min:",
keyPath,
As: alias ?? "min(\(keyPath))",
keyPath: keyPath,
alias: alias ?? "min(\(keyPath))",
nativeType: .UndefinedAttributeType
)
}
@@ -185,6 +192,7 @@ public enum SelectTerm: StringLiteralConvertible {
Select<Int>(.Sum("age"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
@@ -193,12 +201,34 @@ public enum SelectTerm: StringLiteralConvertible {
return ._Aggregate(
function: "sum:",
keyPath,
As: alias ?? "sum(\(keyPath))",
keyPath: keyPath,
alias: alias ?? "sum(\(keyPath))",
nativeType: .DecimalAttributeType
)
}
/**
Provides a `SelectTerm` to a `Select` clause for querying the `NSManagedObjectID`.
```
let objectID = CoreStore.queryValue(
From(MyPersonEntity),
Select<NSManagedObjectID>(),
Where("employeeID", isEqualTo: 1111)
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "objecID" is used
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
*/
public static func ObjectID(As alias: KeyPath? = nil) -> SelectTerm {
return ._Identity(
alias: alias ?? "objectID",
nativeType: .ObjectIDAttributeType
)
}
// MARK: StringLiteralConvertible
@@ -218,10 +248,55 @@ public enum SelectTerm: StringLiteralConvertible {
}
// MARK: Hashable
public var hashValue: Int {
switch self {
case ._Attribute(let keyPath):
return 0 ^ keyPath.hashValue
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
return 1 ^ function.hashValue ^ keyPath.hashValue ^ alias.hashValue ^ nativeType.hashValue
case ._Identity(let alias, let nativeType):
return 3 ^ alias.hashValue ^ nativeType.hashValue
}
}
// MARK: Internal
case _Attribute(KeyPath)
case _Aggregate(function: String, KeyPath, As: String, nativeType: NSAttributeType)
case _Aggregate(function: String, keyPath: KeyPath, alias: String, nativeType: NSAttributeType)
case _Identity(alias: String, nativeType: NSAttributeType)
}
// MARK: - SelectTerm: Equatable
@warn_unused_result
public func == (lhs: SelectTerm, rhs: SelectTerm) -> Bool {
switch (lhs, rhs) {
case (._Attribute(let keyPath1), ._Attribute(let keyPath2)):
return keyPath1 == keyPath2
case (._Aggregate(let function1, let keyPath1, let alias1, let nativeType1),
._Aggregate(let function2, let keyPath2, let alias2, let nativeType2)):
return function1 == function2
&& keyPath1 == keyPath2
&& alias1 == alias2
&& nativeType1 == nativeType2
case (._Identity(let alias1, let nativeType1), ._Identity(let alias2, let nativeType2)):
return alias1 == alias2 && nativeType1 == nativeType2
default:
return false
}
}
@@ -267,7 +342,7 @@ public enum SelectTerm: StringLiteralConvertible {
- parameter sortDescriptors: a series of `NSSortDescriptor`s
*/
public struct Select<T: SelectResultType> {
public struct Select<T: SelectResultType>: Hashable {
/**
The `SelectResultType` type for the query's return value
@@ -285,93 +360,45 @@ public struct Select<T: SelectResultType> {
self.selectTerms = [selectTerm] + selectTerms
}
/**
Initializes a `Select` clause with a list of `SelectTerm`s
- parameter selectTerms: a series of `SelectTerm`s
*/
public init(_ selectTerms: [SelectTerm]) {
self.selectTerms = selectTerms
}
// MARK: Hashable
public var hashValue: Int {
return self.selectTerms.map { $0.hashValue }.reduce(0, combine: ^)
}
// MARK: Internal
internal func applyToFetchRequest(fetchRequest: NSFetchRequest) {
internal let selectTerms: [SelectTerm]
}
public extension Select where T: NSManagedObjectID {
public init() {
if fetchRequest.propertiesToFetch != nil {
CoreStore.log(
.Warning,
message: "An existing \"propertiesToFetch\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
)
}
fetchRequest.includesPendingChanges = false
fetchRequest.resultType = .DictionaryResultType
let entityDescription = fetchRequest.entity!
let propertiesByName = entityDescription.propertiesByName
let attributesByName = entityDescription.attributesByName
var propertiesToFetch = [AnyObject]()
for term in self.selectTerms {
switch term {
case ._Attribute(let keyPath):
if let propertyDescription = propertiesByName[keyPath] {
propertiesToFetch.append(propertyDescription)
}
else {
CoreStore.log(
.Warning,
message: "The property \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause."
)
}
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
if let attributeDescription = attributesByName[keyPath] {
let expressionDescription = NSExpressionDescription()
expressionDescription.name = alias
if nativeType == .UndefinedAttributeType {
expressionDescription.expressionResultType = attributeDescription.attributeType
}
else {
expressionDescription.expressionResultType = nativeType
}
expressionDescription.expression = NSExpression(
forFunction: function,
arguments: [NSExpression(forKeyPath: keyPath)]
)
propertiesToFetch.append(expressionDescription)
}
else {
CoreStore.log(
.Warning,
message: "The attribute \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause."
)
}
}
}
fetchRequest.propertiesToFetch = propertiesToFetch
self.init(.ObjectID())
}
}
// MARK: - Select: Equatable
@warn_unused_result
public func == <T: SelectResultType, U: SelectResultType>(lhs: Select<T>, rhs: Select<U>) -> Bool {
internal func keyPathForFirstSelectTerm() -> KeyPath {
switch self.selectTerms.first! {
case ._Attribute(let keyPath):
return keyPath
case ._Aggregate(_, _, let alias, _):
return alias
}
}
// MARK: Private
private let selectTerms: [SelectTerm]
return lhs.selectTerms == rhs.selectTerms
}
@@ -385,8 +412,19 @@ extension Bool: SelectValueResultType {
}
public static func fromResultObject(result: AnyObject) -> Bool? {
return (result as? NSNumber)?.boolValue
switch result {
case let decimal as NSDecimalNumber:
// iOS: NSDecimalNumber(string: "0.5").boolValue // true
// OSX: NSDecimalNumber(string: "0.5").boolValue // false
return NSNumber(double: decimal.doubleValue).boolValue
case let number as NSNumber:
return number.boolValue
default:
return nil
}
}
}
@@ -666,3 +704,119 @@ extension NSDictionary: SelectAttributesResultType {
return result as! [[NSString: AnyObject]]
}
}
// MARK: - Internal
internal extension CollectionType where Generator.Element == SelectTerm {
internal func applyToFetchRequest<T>(fetchRequest: NSFetchRequest, owner: T) {
fetchRequest.includesPendingChanges = false
fetchRequest.resultType = .DictionaryResultType
func attributeDescriptionForKeyPath(keyPath: String, inEntity entity: NSEntityDescription) -> NSAttributeDescription? {
let components = keyPath.componentsSeparatedByString(".")
switch components.count {
case 0:
return nil
case 1:
return entity.attributesByName[components[0]]
default:
guard let relationship = entity.relationshipsByName[components[0]] else {
return nil
}
return attributeDescriptionForKeyPath(
components.dropFirst().joinWithSeparator("."),
inEntity: relationship.entity
)
}
}
var propertiesToFetch = [AnyObject]()
for term in self {
switch term {
case ._Attribute(let keyPath):
let entityDescription = fetchRequest.entity!
if let attributeDescription = attributeDescriptionForKeyPath(keyPath, inEntity: entityDescription) {
propertiesToFetch.append(attributeDescription)
}
else {
CoreStore.log(
.Warning,
message: "The key path \"\(keyPath)\" could not be resolved in entity \(cs_typeName(entityDescription.managedObjectClassName)) as an attribute and will be ignored by \(cs_typeName(owner)) query clause."
)
}
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
let entityDescription = fetchRequest.entity!
if let attributeDescription = attributeDescriptionForKeyPath(keyPath, inEntity: entityDescription) {
let expressionDescription = NSExpressionDescription()
expressionDescription.name = alias
if nativeType == .UndefinedAttributeType {
expressionDescription.expressionResultType = attributeDescription.attributeType
}
else {
expressionDescription.expressionResultType = nativeType
}
expressionDescription.expression = NSExpression(
forFunction: function,
arguments: [NSExpression(forKeyPath: keyPath)]
)
propertiesToFetch.append(expressionDescription)
}
else {
CoreStore.log(
.Warning,
message: "The key path \"\(keyPath)\" could not be resolved in entity \(cs_typeName(entityDescription.managedObjectClassName)) as an attribute and will be ignored by \(cs_typeName(owner)) query clause."
)
}
case ._Identity(let alias, let nativeType):
let expressionDescription = NSExpressionDescription()
expressionDescription.name = alias
if nativeType == .UndefinedAttributeType {
expressionDescription.expressionResultType = .ObjectIDAttributeType
}
else {
expressionDescription.expressionResultType = nativeType
}
expressionDescription.expression = NSExpression.expressionForEvaluatedObject()
propertiesToFetch.append(expressionDescription)
}
}
fetchRequest.propertiesToFetch = propertiesToFetch
}
internal func keyPathForFirstSelectTerm() -> KeyPath {
switch self.first! {
case ._Attribute(let keyPath):
return keyPath
case ._Aggregate(_, _, let alias, _):
return alias
case ._Identity(let alias, _):
return alias
}
}
}

View File

@@ -44,14 +44,20 @@ import CoreData
*/
public struct Tweak: FetchClause, QueryClause, DeleteClause {
/**
The block to customize the `NSFetchRequest`
*/
public let closure: (fetchRequest: NSFetchRequest) -> Void
/**
Initializes a `Tweak` clause with a closure where the `NSFetchRequest` may be configured.
- parameter customization: a list of key path strings to group results with
- Important: `Tweak`'s closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions. Also, some utilities (such as `ListMonitor`s) may keep `FetchClause`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter closure: the block to customize the `NSFetchRequest`
*/
public init(_ customization: (fetchRequest: NSFetchRequest) -> Void) {
public init(_ closure: (fetchRequest: NSFetchRequest) -> Void) {
self.customization = customization
self.closure = closure
}
@@ -59,11 +65,6 @@ public struct Tweak: FetchClause, QueryClause, DeleteClause {
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
self.customization(fetchRequest: fetchRequest)
self.closure(fetchRequest: fetchRequest)
}
// MARK: Private
private let customization: (fetchRequest: NSFetchRequest) -> Void
}

View File

@@ -48,17 +48,12 @@ public prefix func !(clause: Where) -> Where {
/**
The `Where` clause specifies the conditions for a fetch or a query.
*/
public struct Where: FetchClause, QueryClause, DeleteClause {
public struct Where: FetchClause, QueryClause, DeleteClause, Hashable {
/**
Initializes a `Where` clause with an `NSPredicate`
- parameter predicate: the `NSPredicate` for the fetch or query
The `NSPredicate` for the fetch or query
*/
public init(_ predicate: NSPredicate) {
self.predicate = predicate
}
public let predicate: NSPredicate
/**
Initializes a `Where` clause with a predicate that always evaluates to `true`
@@ -119,7 +114,7 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
- parameter keyPath: the keyPath to compare with
- parameter list: the array to check membership of
*/
public init(_ keyPath: KeyPath, isMemberOf list: NSArray) {
public init(_ keyPath: KeyPath, isMemberOf list: [NSObject]) {
self.init(NSPredicate(format: "\(keyPath) IN %@", list))
}
@@ -135,7 +130,15 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
self.init(NSPredicate(format: "\(keyPath) IN %@", Array(list) as NSArray))
}
public let predicate: NSPredicate
/**
Initializes a `Where` clause with an `NSPredicate`
- parameter predicate: the `NSPredicate` for the fetch or query
*/
public init(_ predicate: NSPredicate) {
self.predicate = predicate
}
// MARK: FetchClause, QueryClause, DeleteClause
@@ -146,10 +149,27 @@ public struct Where: FetchClause, QueryClause, DeleteClause {
CoreStore.log(
.Warning,
message: "An existing predicate for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
message: "An existing predicate for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
)
}
fetchRequest.predicate = self.predicate
}
// MARK: Hashable
public var hashValue: Int {
return self.predicate.hashValue
}
}
// MARK: - Where: Equatable
@warn_unused_result
public func == (lhs: Where, rhs: Where) -> Bool {
return lhs.predicate == rhs.predicate
}

View File

@@ -108,9 +108,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchOne(from, fetchClauses)
}
@@ -126,9 +125,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchOne(from, fetchClauses)
}
@@ -144,9 +142,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchAll(from, fetchClauses)
}
@@ -162,9 +159,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchAll(from, fetchClauses)
}
@@ -180,9 +176,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchCount(from, fetchClauses)
}
@@ -198,9 +193,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchCount(from, fetchClauses)
}
@@ -216,9 +210,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchObjectID(from, fetchClauses)
}
@@ -234,9 +227,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchObjectID(from, fetchClauses)
}
@@ -252,9 +244,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchObjectIDs(from, fetchClauses)
}
@@ -270,9 +261,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
"Attempted to fetch from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.fetchObjectIDs(from, fetchClauses)
}
@@ -291,9 +281,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to query from a \(typeName(self)) outside the main thread."
"Attempted to query from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.queryValue(from, selectClause, queryClauses)
}
@@ -312,9 +301,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to query from a \(typeName(self)) outside the main thread."
"Attempted to query from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.queryValue(from, selectClause, queryClauses)
}
@@ -333,9 +321,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to query from a \(typeName(self)) outside the main thread."
"Attempted to query from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
}
@@ -354,9 +341,8 @@ public extension DataStack {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to query from a \(typeName(self)) outside the main thread."
"Attempted to query from a \(cs_typeName(self)) outside the main thread."
)
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
}
}

View File

@@ -36,6 +36,7 @@ public extension BaseDataTransaction {
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- throws: an `ErrorType` thrown from any of the `ImportableObject` methods
- returns: the created `ImportableObject` instance, or `nil` if the import was ignored
*/
public func importObject<T where T: NSManagedObject, T: ImportableObject>(
@@ -44,10 +45,10 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
)
return try autoreleasepool {
return try cs_autoreleasepool {
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
@@ -60,11 +61,39 @@ public extension BaseDataTransaction {
}
}
/**
Updates an existing `ImportableObject` by importing values from the specified import source.
- parameter object: the `NSManagedObject` to update
- parameter source: the object to import values from
- throws: an `ErrorType` thrown from any of the `ImportableObject` methods
*/
public func importObject<T where T: NSManagedObject, T: ImportableObject>(
object: T,
source: T.ImportSource) throws {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(cs_typeName(object)) outside the transaction's designated queue."
)
try cs_autoreleasepool {
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
return
}
try object.didInsertFromImportSource(source, inTransaction: self)
}
}
/**
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
- throws: an `ErrorType` thrown from any of the `ImportableObject` methods
- returns: the array of created `ImportableObject` instances
*/
public func importObjects<T, S: SequenceType where T: NSManagedObject, T: ImportableObject, S.Generator.Element == T.ImportSource>(
@@ -73,10 +102,10 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
)
return try autoreleasepool {
return try cs_autoreleasepool {
return try sourceArray.flatMap { (source) -> T? in
@@ -85,7 +114,7 @@ public extension BaseDataTransaction {
return nil
}
return try autoreleasepool {
return try cs_autoreleasepool {
let object = self.create(into)
try object.didInsertFromImportSource(source, inTransaction: self)
@@ -100,6 +129,7 @@ public extension BaseDataTransaction {
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- throws: an `ErrorType` thrown from any of the `ImportableUniqueObject` methods
- returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored
*/
public func importUniqueObject<T where T: NSManagedObject, T: ImportableUniqueObject>(
@@ -108,10 +138,10 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
)
return try autoreleasepool {
return try cs_autoreleasepool {
let uniqueIDKeyPath = T.uniqueIDKeyPath
guard let uniqueIDValue = try T.uniqueIDFromImportSource(source, inTransaction: self) else {
@@ -146,10 +176,12 @@ public extension BaseDataTransaction {
/**
Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources.
- Warning: While the array returned from `importUniqueObjects(...)` correctly maps to the order of `sourceArray`, the order of objects called with `ImportableUniqueObject` methods is arbitrary. Do not make assumptions that any particular object will be imported ahead or after another object.
- parameter into: an `Into` clause specifying the entity type
- parameter sourceArray: the array of objects to import values from
- parameter preProcess: a closure that lets the caller tweak the internal `UniqueIDType`-to-`ImportSource` mapping to be used for importing. Callers can remove from/add to/update `mapping` and return the updated array from the closure.
- throws: an `ErrorType` thrown from any of the `ImportableUniqueObject` methods
- returns: the array of created/updated `ImportableUniqueObject` instances
*/
public func importUniqueObjects<T, S: SequenceType where T: NSManagedObject, T: ImportableUniqueObject, S.Generator.Element == T.ImportSource>(
@@ -159,13 +191,13 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
)
return try autoreleasepool {
return try cs_autoreleasepool {
var mapping = Dictionary<T.UniqueIDType, T.ImportSource>()
let sortedIDs = try autoreleasepool {
let sortedIDs = try cs_autoreleasepool {
return try sourceArray.flatMap { (source) -> T.UniqueIDType? in
@@ -179,12 +211,12 @@ public extension BaseDataTransaction {
}
}
mapping = try autoreleasepool { try preProcess(mapping: mapping) }
mapping = try cs_autoreleasepool { try preProcess(mapping: mapping) }
var objects = Dictionary<T.UniqueIDType, T>()
for object in self.fetchAll(From(T), Where(T.uniqueIDKeyPath, isMemberOf: mapping.keys)) ?? [] {
for object in self.fetchAll(From(T), Where(T.uniqueIDKeyPath, isMemberOf: sortedIDs)) ?? [] {
try autoreleasepool {
try cs_autoreleasepool {
let uniqueIDValue = object.uniqueIDValue
@@ -201,7 +233,7 @@ public extension BaseDataTransaction {
for (uniqueIDValue, source) in mapping {
try autoreleasepool {
try cs_autoreleasepool {
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {

View File

@@ -53,7 +53,7 @@ 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
associatedtype ImportSource
/**
Return `true` if an object should be created from `source`. Return `false` to ignore and skip `source`. The default implementation returns `true`.

Some files were not shown because too many files have changed in this diff Show More