Compare commits
474 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
18b933957e | ||
|
|
2c7039232e | ||
|
|
d3b3b5ff4a | ||
|
|
d90e8d1303 | ||
|
|
e314db8f56 | ||
|
|
48d936d068 | ||
|
|
f3816b9abf | ||
|
|
21961780d4 | ||
|
|
305e2b61a0 | ||
|
|
588fa35c84 | ||
|
|
f6614cda66 | ||
|
|
639574d8c2 | ||
|
|
27ffc1d225 | ||
|
|
ba6f0c39d5 | ||
|
|
ab2eac8f6c | ||
|
|
f460a0b30f | ||
|
|
50bc3ace06 | ||
|
|
d2ddf2002f | ||
|
|
b4117eeb02 | ||
|
|
50e50c0613 | ||
|
|
5c8a0e425b | ||
|
|
03b71caf7e | ||
|
|
7ff29d6086 | ||
|
|
8d86425875 | ||
|
|
0aa8c03424 | ||
|
|
85706a3c57 | ||
|
|
c5ae4606b9 | ||
|
|
fa682215c5 | ||
|
|
be5da632b3 | ||
|
|
dc73cd6dd9 | ||
|
|
53100b202d | ||
|
|
2a62770552 | ||
|
|
f436b26e8e | ||
|
|
0b57cff27d | ||
|
|
3ebc44b546 | ||
|
|
68f1027ba7 | ||
|
|
8bac4aa901 | ||
|
|
211e69023e | ||
|
|
2912dcf010 | ||
|
|
3e00a3da06 | ||
|
|
9922deac4d | ||
|
|
86be046c9f | ||
|
|
0f10bc3349 | ||
|
|
9685f0aef2 | ||
|
|
3bd459bb1a | ||
|
|
28b43f33fa | ||
|
|
74c64619c3 | ||
|
|
f36cb8af63 | ||
|
|
10ccadd96c | ||
|
|
5c0e78bd53 | ||
|
|
8a09688117 | ||
|
|
a366bcf1a3 | ||
|
|
c6e68ac24f | ||
|
|
a20ad87583 | ||
|
|
a11915db12 | ||
|
|
961f39a806 | ||
|
|
3096cb784c | ||
|
|
809aa4ff96 | ||
|
|
8d926d25ec | ||
|
|
790454f514 | ||
|
|
fb7e2f7f7f | ||
|
|
f72efc80b2 | ||
|
|
fd3a9b00ec | ||
|
|
5f5000218a | ||
|
|
e8eb309d82 | ||
|
|
d0c3203e63 | ||
|
|
f5b3901caa | ||
|
|
1a99fea820 | ||
|
|
195b60615b | ||
|
|
2c394965b8 | ||
|
|
746d697691 | ||
|
|
5689158b43 | ||
|
|
6fcdf3d011 | ||
|
|
e26573c18e | ||
|
|
801cf8d9f0 | ||
|
|
6a42a0054e | ||
|
|
8c437e19b8 | ||
|
|
9cd3b6c879 | ||
|
|
fe135acbec | ||
|
|
997c5bdcfa | ||
|
|
23e12c4539 | ||
|
|
f618617053 | ||
|
|
49b8b9c372 | ||
|
|
129f975d96 | ||
|
|
a2e463e58c | ||
|
|
5fd50f0e15 | ||
|
|
7f9a915d71 | ||
|
|
0a81736b7a | ||
|
|
f9b6dd0c6a | ||
|
|
0354401b56 | ||
|
|
0304067beb | ||
|
|
ddd83da434 | ||
|
|
fc7df671de | ||
|
|
5fde9030c7 | ||
|
|
d7b07b3f00 | ||
|
|
ddd1ffb29f | ||
|
|
98094000bb | ||
|
|
d5026ef996 | ||
|
|
2cd913b9dd | ||
|
|
ad9520abbc | ||
|
|
c0fc57d10c | ||
|
|
0cf4d303e4 | ||
|
|
6de397958a | ||
|
|
55292a84dc | ||
|
|
981b560d53 | ||
|
|
c4c4dd55cd | ||
|
|
1ddbe20c86 | ||
|
|
da9e8c1550 | ||
|
|
ef0937fec4 | ||
|
|
35885b40de | ||
|
|
d669569196 | ||
|
|
ae919ff3c8 | ||
|
|
1a7a4690d1 | ||
|
|
b9b96d1a35 | ||
|
|
da3a9590ac | ||
|
|
6b3d75bea1 | ||
|
|
d44721fef0 | ||
|
|
3f268e8376 | ||
|
|
303fea4ebe | ||
|
|
77173cdad0 | ||
|
|
1e24a7d739 | ||
|
|
a3b33bedb8 | ||
|
|
eaf7544c50 | ||
|
|
67863120e0 | ||
|
|
1b0e305d9a | ||
|
|
91fda01071 | ||
|
|
feb0e30735 | ||
|
|
9c25336ff6 | ||
|
|
66bef87422 | ||
|
|
dd2949ee18 | ||
|
|
e0abb9b0af | ||
|
|
42c28b064a | ||
|
|
0b097b8c85 | ||
|
|
fce5d2f301 | ||
|
|
8ff163af30 | ||
|
|
19abedfa9f | ||
|
|
48828fdca3 | ||
|
|
9d65a27557 | ||
|
|
6d04806608 | ||
|
|
54129f7362 | ||
|
|
54c81d23f5 | ||
|
|
53ab140341 | ||
|
|
9dc4331b26 | ||
|
|
e6aa72fb5f | ||
|
|
274a54451a | ||
|
|
fe70b7a27d | ||
|
|
02a660e4a6 | ||
|
|
a543a4c94a | ||
|
|
fd14a18248 | ||
|
|
b0e2655bdf | ||
|
|
b6bc7c2edf | ||
|
|
1938f0d9de | ||
|
|
94e6db669f | ||
|
|
b1972b82f1 | ||
|
|
5ffaca1375 | ||
|
|
a73306fecb | ||
|
|
9f3db61ff7 | ||
|
|
e5ef4992d3 | ||
|
|
c0ae129b22 | ||
|
|
4aa1a63f9a | ||
|
|
4fc10afe1e | ||
|
|
8b77e4e5a0 | ||
|
|
258c237100 | ||
|
|
6948db516d | ||
|
|
b5d80fd272 | ||
|
|
cdcd7d0416 | ||
|
|
a5162239d5 | ||
|
|
1ad6ac5769 | ||
|
|
ecb3d0cfa0 | ||
|
|
81355fd9c5 | ||
|
|
97d7a276fe | ||
|
|
d72d1afe8b | ||
|
|
716e069984 | ||
|
|
881ee4af0a | ||
|
|
acc0ce1c32 | ||
|
|
b97f6d6a0a | ||
|
|
818abfc1a1 | ||
|
|
f055c54a66 | ||
|
|
cb6d5b015b | ||
|
|
494965de23 | ||
|
|
fe25a9aa36 | ||
|
|
f21e4e12e0 | ||
|
|
92890d1e1d | ||
|
|
0c483e0e19 | ||
|
|
6055685c00 | ||
|
|
ee71442b08 | ||
|
|
341ec5e771 | ||
|
|
8569c3c524 | ||
|
|
3224fcf71d | ||
|
|
9ff1c9d545 | ||
|
|
c40d17a6ad | ||
|
|
9d5e04854a | ||
|
|
d2fd03c1f0 | ||
|
|
7baaee493d | ||
|
|
03973790a8 | ||
|
|
7ee027f44d | ||
|
|
19fea6953a | ||
|
|
9f11b1005d | ||
|
|
698326f89a | ||
|
|
bdf6308d8f | ||
|
|
69d96c53d6 | ||
|
|
7b961fa249 | ||
|
|
73450d0b29 | ||
|
|
6d83564a1a | ||
|
|
c0d72799b4 | ||
|
|
06a1919e91 | ||
|
|
16def2d84b | ||
|
|
e9a2c58f32 | ||
|
|
1a3e0dd4c6 | ||
|
|
518bb134f9 | ||
|
|
4d7feca848 | ||
|
|
64a80bf401 | ||
|
|
4d63fc744a | ||
|
|
9480e372f1 | ||
|
|
23df460c35 | ||
|
|
1c233b7302 | ||
|
|
f42288802c | ||
|
|
a27556f294 | ||
|
|
e330291e1b | ||
|
|
4a282150f0 | ||
|
|
5d2956d674 | ||
|
|
92756fec42 | ||
|
|
2b37daefe0 | ||
|
|
5724d4599e | ||
|
|
4a882e6108 | ||
|
|
b230ed6400 | ||
|
|
33a5c123aa | ||
|
|
73637321ce | ||
|
|
088f1717f9 | ||
|
|
cff2bb1740 | ||
|
|
d902d62172 | ||
|
|
b955495012 | ||
|
|
d2e78a70e1 | ||
|
|
970957cbc2 | ||
|
|
2f9e5db89f | ||
|
|
1789eb7daf | ||
|
|
3999654ee7 | ||
|
|
4c3bec287c | ||
|
|
2a2d9b3483 | ||
|
|
32a388e0ca | ||
|
|
1a6fbad3d4 | ||
|
|
ac55f20f5f | ||
|
|
910b5039fd | ||
|
|
ffea06ee7e | ||
|
|
a92d6cac02 | ||
|
|
de5d660257 | ||
|
|
55b2e6eecd | ||
|
|
b64c776335 | ||
|
|
432af667e8 | ||
|
|
cf60a4bc2e | ||
|
|
c620859899 | ||
|
|
65ac069a0b | ||
|
|
862ef27374 | ||
|
|
243b6a76d5 | ||
|
|
8be20370d5 | ||
|
|
a9c0feae46 | ||
|
|
2e44f86eb6 | ||
|
|
ed8c7b35e8 | ||
|
|
4d2ebe4ea8 | ||
|
|
54be9d471c | ||
|
|
f18d62f643 | ||
|
|
af141d4a31 | ||
|
|
2da659a967 | ||
|
|
e5f162c5e1 | ||
|
|
effa231719 | ||
|
|
6cef8f4b4f | ||
|
|
aa6bceaaf3 | ||
|
|
0dbd05b172 | ||
|
|
243c4044ab | ||
|
|
df835114cb | ||
|
|
f99d3cc21a | ||
|
|
a51ed1a007 | ||
|
|
e5245a0e5b | ||
|
|
0fa2a23461 | ||
|
|
3f28198552 | ||
|
|
4a34012d58 | ||
|
|
45690a29c6 | ||
|
|
0d4d036a86 | ||
|
|
82de482191 | ||
|
|
b502895d63 | ||
|
|
ed0fdc76fe | ||
|
|
58f4907575 | ||
|
|
0ba63c6e72 | ||
|
|
e9be711d4c | ||
|
|
db5b8ca702 | ||
|
|
1950224863 | ||
|
|
f0cd288657 | ||
|
|
2f39f9188b | ||
|
|
3344e42d7c | ||
|
|
e4b6c06401 | ||
|
|
872e69ddc6 | ||
|
|
f9e33101a0 | ||
|
|
0621c54868 | ||
|
|
a638620858 | ||
|
|
267c21063a | ||
|
|
7fc3ad2890 | ||
|
|
556f6d13b3 | ||
|
|
a088fe3817 | ||
|
|
c4391a8d0c | ||
|
|
f486ace437 | ||
|
|
edb2aa2463 | ||
|
|
7b48d825a3 | ||
|
|
921b85d91b | ||
|
|
4f46cbded9 | ||
|
|
a8af91b2f8 | ||
|
|
8a8b3fa0b1 | ||
|
|
4196ed8085 | ||
|
|
0458de5d65 | ||
|
|
90ae17e904 | ||
|
|
3c0d4d648d | ||
|
|
b60bcf8da6 | ||
|
|
f9014e65e0 | ||
|
|
82887b1dd2 | ||
|
|
11d428c05c | ||
|
|
0b48bb3347 | ||
|
|
aa5cd51da6 | ||
|
|
e50dd9881d | ||
|
|
3ccbce5c29 | ||
|
|
e5a199489c | ||
|
|
3d5c4f8121 | ||
|
|
2dc09289bf | ||
|
|
76a2bc1da2 | ||
|
|
8e5c7ec9b2 | ||
|
|
101ab69861 | ||
|
|
ea2a65cf57 | ||
|
|
f59b1b6320 | ||
|
|
141e977f9c | ||
|
|
788cce0df6 | ||
|
|
4304d8687e | ||
|
|
d25f751089 | ||
|
|
6df78a0595 | ||
|
|
c0ed2f23ce | ||
|
|
dcf562fb26 | ||
|
|
b9353238e8 | ||
|
|
b98805e489 | ||
|
|
02a89accc8 | ||
|
|
2e4c9a7a25 | ||
|
|
fcca6b205e | ||
|
|
b199f38b0c | ||
|
|
0c8731c610 | ||
|
|
ee4eb178ed | ||
|
|
f617f2cc53 | ||
|
|
f396b01043 | ||
|
|
7942cf411e | ||
|
|
02be72b0b7 | ||
|
|
e38093342d | ||
|
|
c15afcb381 | ||
|
|
a97001cdbb | ||
|
|
000a357808 | ||
|
|
c8ca16a982 | ||
|
|
9e0493a20d | ||
|
|
77236d66d5 | ||
|
|
8b95d337a3 | ||
|
|
57b66cff34 | ||
|
|
9dae291f62 | ||
|
|
0c2a3ac4e9 | ||
|
|
a6291e116c | ||
|
|
c990f7764a | ||
|
|
26ae6293ca | ||
|
|
b9a2f96d6e | ||
|
|
ae5f737baf | ||
|
|
4eecd80710 | ||
|
|
0073d038e0 | ||
|
|
099dcfab68 | ||
|
|
74fe81bfa7 | ||
|
|
0bbb4118a1 | ||
|
|
3fe9e4ee1d | ||
|
|
bd19326447 | ||
|
|
dccc958ef1 | ||
|
|
ab3095f078 | ||
|
|
1a86510045 | ||
|
|
aa32f5e158 | ||
|
|
b95aaf151d | ||
|
|
114bc71841 | ||
|
|
dff552b95f | ||
|
|
88cac73b20 | ||
|
|
83d0412cb9 | ||
|
|
26e4118355 | ||
|
|
0c564add46 | ||
|
|
784a315fb9 | ||
|
|
44cfbebedb | ||
|
|
0b24072259 | ||
|
|
28b7ba01dc | ||
|
|
b529735269 | ||
|
|
d23121d8ed | ||
|
|
633ab0a249 | ||
|
|
410feda5cd | ||
|
|
06e952af8a | ||
|
|
a4d3d0c0ed | ||
|
|
8b7af86526 | ||
|
|
48a8694720 | ||
|
|
09d844f5df | ||
|
|
e25e198bf8 | ||
|
|
e99d19d2ac | ||
|
|
9d7960e674 | ||
|
|
e9ac8629a1 | ||
|
|
b0b0df2861 | ||
|
|
eda398d758 | ||
|
|
40c320e1ca | ||
|
|
8508dd4f79 | ||
|
|
d3e0f576cf | ||
|
|
1d5cf5a4cc | ||
|
|
9fc0390b45 | ||
|
|
b8ea7ecf01 | ||
|
|
11ac362dcc | ||
|
|
10c20e0d62 | ||
|
|
789028bc58 | ||
|
|
a168ca577a | ||
|
|
781f3988d2 | ||
|
|
b914a4b5ed | ||
|
|
7de13131a4 | ||
|
|
0c1af09a8d | ||
|
|
1ff635d8b5 | ||
|
|
707445a169 | ||
|
|
90369cf994 | ||
|
|
09708e587c | ||
|
|
36e6e4a8b9 | ||
|
|
9f1a351311 | ||
|
|
6e202aa7ca | ||
|
|
783b933294 | ||
|
|
ca49ea3a81 | ||
|
|
ed8891a6d5 | ||
|
|
dd6c6abaf0 | ||
|
|
928585029d | ||
|
|
dc6d22b6ff | ||
|
|
5ca0f657cb | ||
|
|
2c65ac1834 | ||
|
|
24008d62b2 | ||
|
|
e8a9cc9d67 | ||
|
|
1507ac63f9 | ||
|
|
f2df8f7171 | ||
|
|
3ddfd3cccc | ||
|
|
21f57518c8 | ||
|
|
d9422f7f2e | ||
|
|
245ec25ad8 | ||
|
|
0ce89b8460 | ||
|
|
42a889a28e | ||
|
|
456977bf12 | ||
|
|
ea8412ab93 | ||
|
|
603dffffb0 | ||
|
|
8a1144b1be | ||
|
|
c323a28c19 | ||
|
|
4ff7b2d6d9 | ||
|
|
64ad7b3bfd | ||
|
|
df441c5d04 | ||
|
|
fa947faa57 | ||
|
|
6822a4579e | ||
|
|
a89dd76906 | ||
|
|
c85ef16ad0 | ||
|
|
67f1bd9a63 | ||
|
|
39540628df | ||
|
|
c86ca06bd4 | ||
|
|
40c27b9fcb | ||
|
|
2f8c100cb6 | ||
|
|
34495d7163 | ||
|
|
75a4ebb49b | ||
|
|
3c514830d9 | ||
|
|
b283fbf5ab | ||
|
|
c1e087b8c1 | ||
|
|
ad1ebb3501 | ||
|
|
99189d160f | ||
|
|
3c2fb28644 | ||
|
|
f71ad4c577 | ||
|
|
d05fcc5103 | ||
|
|
e9329fc93c | ||
|
|
1ea773f0ec | ||
|
|
c68c2bdc0a | ||
|
|
91dd4b6cb3 | ||
|
|
0800b706d6 | ||
|
|
2071ce722e | ||
|
|
df866718cf | ||
|
|
f19a0d29eb | ||
|
|
8f09f90294 | ||
|
|
7bddcaa4a2 |
2
.cocoapods.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
try:
|
||||
project: 'CoreStore.xcworkspace'
|
||||
1
.gitignore
vendored
@@ -6,3 +6,4 @@ Carthage/Build
|
||||
CoreStore.xcworkspace/xcuserdata
|
||||
.DS_Store
|
||||
DerivedData
|
||||
*.orig
|
||||
|
||||
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "Carthage/Checkouts/GCDKit"]
|
||||
path = Carthage/Checkouts/GCDKit
|
||||
url = https://github.com/JohnEstropia/GCDKit.git
|
||||
|
||||
1
.swift-version
Normal file
@@ -0,0 +1 @@
|
||||
3.2
|
||||
34
.travis.yml
@@ -1,5 +1,5 @@
|
||||
language: objective-c
|
||||
osx_image: xcode7.2
|
||||
osx_image: xcode9
|
||||
sudo: false
|
||||
git:
|
||||
submodules: false
|
||||
@@ -10,21 +10,29 @@ 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=11.0,name=iPhone 8" SCHEME="CoreStore iOS" SDK=iphonesimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=10.3,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=10.1,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=8.3,name=iPhone 5S" SCHEME="CoreStore iOS" SDK=iphonesimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=8.3,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=8.3,name=iPhone 4S" SCHEME="CoreStore iOS" SDK=iphonesimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="arch=x86_64" SCHEME="CoreStore OSX" SDK=macosx10.13 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=4.0,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator4.0 RUN_TESTS="NO" POD_LINT="NO"
|
||||
- DESTINATION="OS=3.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator4.0 RUN_TESTS="NO" POD_LINT="NO"
|
||||
- DESTINATION="OS=2.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator4.0 RUN_TESTS="NO" POD_LINT="NO"
|
||||
- DESTINATION="OS=11.0,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=10.2,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
- DESTINATION="OS=9.2,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator11.0 RUN_TESTS="YES" POD_LINT="NO"
|
||||
before_install:
|
||||
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
|
||||
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
|
||||
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.11/Carthage.pkg"
|
||||
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.26.0/Carthage.pkg"
|
||||
- sudo installer -pkg "Carthage.pkg" -target /
|
||||
- rm "Carthage.pkg"
|
||||
- npm install ios-sim -g
|
||||
- ios-sim start --devicetypeid "com.apple.CoreSimulator.SimDeviceType.iPhone-8, 11.0"
|
||||
before_script:
|
||||
- carthage update --use-submodules
|
||||
script:
|
||||
@@ -35,8 +43,8 @@ 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;
|
||||
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStoreDemo" -sdk "iphonesimulator11.0" -destination "OS=11.0,name=iPhone 8" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
|
||||
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStoreDemo" -sdk "iphonesimulator11.0" -destination "OS=11.0,name=iPhone 8" -configuration Release ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
|
||||
- if [ $POD_LINT == "YES" ]; then
|
||||
pod lib lint --quick;
|
||||
fi
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
github "JohnEstropia/GCDKit" "1.1.7"
|
||||
|
||||
1
Carthage/Checkouts/GCDKit
vendored
BIN
CoreStore.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
@@ -1,6 +1,6 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CoreStore"
|
||||
s.version = "1.5.3"
|
||||
s.version = "4.2.4"
|
||||
s.license = "MIT"
|
||||
s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift"
|
||||
s.homepage = "https://github.com/JohnEstropia/CoreStore"
|
||||
@@ -12,11 +12,9 @@ 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 DEBUG' }
|
||||
end
|
||||
|
||||
BIN
CoreStore.sketch
Normal file
@@ -10,10 +10,10 @@
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "F347F55F-7F5C-4476-9148-6E902F06E4AD",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStoreLibraries\/GCDKit",
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore"
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore\/"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 203,
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
|
||||
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStore.xcodeproj",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
|
||||
{
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0710"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -63,6 +63,11 @@
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0720"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0800"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
3
CoreStore.xcworkspace/contents.xcworkspacedata
generated
@@ -7,7 +7,4 @@
|
||||
<FileRef
|
||||
location = "group:CoreStoreDemo/CoreStoreDemo.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:Carthage/Checkouts/GCDKit/GCDKit.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
|
||||
@@ -5,11 +5,13 @@
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0,
|
||||
"95438028B10BBB846574013D29F154A00556A9D1" : 0,
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "EBFDEFFE-8BA0-441A-862A-1DE28AA5CD21",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStore\/Carthage\/Checkouts\/GCDKit\/",
|
||||
"95438028B10BBB846574013D29F154A00556A9D1" : "CoreStore\/Carthage\/Checkouts\/Nimble\/",
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore\/"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
|
||||
@@ -25,6 +27,11 @@
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/JohnEstropia\/GCDKit.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97"
|
||||
},
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Quick\/Nimble.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "95438028B10BBB846574013D29F154A00556A9D1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
github "JohnEstropia/GCDKit" == 1.1.7
|
||||
@@ -1,92 +0,0 @@
|
||||
//
|
||||
// NSManagedObject+Convenience.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - NSFetchedResultsController
|
||||
|
||||
public extension NSFetchedResultsController {
|
||||
|
||||
/**
|
||||
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful to partially support Objective-C classes by passing an `NSFetchedResultsController` instance instead of a `ListMonitor`.
|
||||
*/
|
||||
public static func createForStack<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
|
||||
|
||||
return CoreStoreFetchedResultsController<T>(
|
||||
context: dataStack.mainContext,
|
||||
fetchRequest: fetchRequest,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
@available(*, deprecated=1.5.2, message="Use NSFetchedResultsController.createForStack(_:fetchRequest:from:sectionBy:fetchClauses:) to create NSFetchedResultsControllers directly")
|
||||
public convenience init<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
|
||||
|
||||
let context = dataStack.mainContext
|
||||
from?.applyToFetchRequest(fetchRequest, context: context, applyAffectedStores: false)
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
if let from = from {
|
||||
|
||||
from.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
|
||||
}
|
||||
else {
|
||||
|
||||
guard let from = (fetchRequest.entity.flatMap { $0.managedObjectClassName }).flatMap(NSClassFromString).flatMap(From.init) else {
|
||||
|
||||
fatalError("Attempted to create an \(typeName(NSFetchedResultsController)) without a From clause or an NSEntityDescription.")
|
||||
}
|
||||
from.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
|
||||
}
|
||||
|
||||
self.init(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: context,
|
||||
sectionNameKeyPath: sectionBy?.sectionKeyPath,
|
||||
cacheName: nil
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static func createFromContext<T: NSManagedObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
|
||||
|
||||
return CoreStoreFetchedResultsController<T>(
|
||||
context: context,
|
||||
fetchRequest: fetchRequest,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,374 +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)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified configurations.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From(MyPersonEntity.self, nil, "Configuration1"))
|
||||
```
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
|
||||
*/
|
||||
public init(_ entity: T.Type, _ configuration: String?, _ otherConfigurations: String?...) {
|
||||
|
||||
self.init(entityClass: entity, configurations: [configuration] + otherConfigurations)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified configurations.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From(MyPersonEntity.self, ["Configuration1", "Configuration1"]))
|
||||
```
|
||||
- parameter entity: the associated `NSManagedObject` 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.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From(MyPersonEntity.self, nil, "Configuration1"))
|
||||
```
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ configuration: String?, _ otherConfigurations: String?...) {
|
||||
|
||||
self.init(entityClass: entityClass, configurations: [configuration] + otherConfigurations)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified configurations.
|
||||
Sample Usage:
|
||||
```
|
||||
let people = transaction.fetchAll(From(MyPersonEntity.self, ["Configuration1", "Configuration1"]))
|
||||
```
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ configurations: [String?]) {
|
||||
|
||||
self.init(entityClass: entityClass, configurations: configurations)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter storeURL: the persistent store URL to associate objects from.
|
||||
- parameter otherStoreURLs: an optional list of other persistent store URLs to associate objects from (see `storeURL` parameter)
|
||||
*/
|
||||
public init(_ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: T.self, storeURLs: [storeURL] + otherStoreURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter storeURLs: the persistent store URLs to associate objects from.
|
||||
*/
|
||||
public init(_ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: T.self, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter storeURL: the persistent store URL to associate objects from.
|
||||
- parameter otherStoreURLs: an optional list of other persistent store URLs to associate objects from (see `storeURL` parameter)
|
||||
*/
|
||||
public init(_ entity: T.Type, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: entity, storeURLs: [storeURL] + otherStoreURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter storeURLs: the persistent store URLs to associate objects from.
|
||||
*/
|
||||
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: entity, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter storeURL: the persistent store URL to associate objects from.
|
||||
- parameter otherStoreURLs: an optional list of other persistent store URLs to associate objects from (see `storeURL` parameter)
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: [storeURL] + otherStoreURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified store URLs.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter storeURLs: the persistent store URLs to associate objects from.
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter persistentStore: the `NSPersistentStore` to associate objects from.
|
||||
- parameter otherPersistentStores: an optional list of other `NSPersistentStore`s to associate objects from (see `persistentStore` parameter)
|
||||
*/
|
||||
public init(_ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: T.self, persistentStores: [persistentStore] + otherPersistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter persistentStores: the `NSPersistentStore`s to associate objects from.
|
||||
*/
|
||||
public init(_ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: T.self, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter persistentStore: the `NSPersistentStore` to associate objects from.
|
||||
- parameter otherPersistentStores: an optional list of other `NSPersistentStore`s to associate objects from (see `persistentStore` parameter)
|
||||
*/
|
||||
public init(_ entity: T.Type, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: entity, persistentStores: [persistentStore] + otherPersistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` type
|
||||
- parameter persistentStores: the `NSPersistentStore`s to associate objects from.
|
||||
*/
|
||||
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: entity, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter persistentStore: the `NSPersistentStore` to associate objects from.
|
||||
- parameter otherPersistentStores: an optional list of other `NSPersistentStore`s to associate objects from (see `persistentStore` parameter)
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: [persistentStore] + otherPersistentStores)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `From` clause with the specified `NSPersistentStore`s.
|
||||
|
||||
- parameter entity: the associated `NSManagedObject` entity class
|
||||
- parameter persistentStores: the `NSPersistentStore`s to associate objects from.
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext, applyAffectedStores: Bool = true) {
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,668 +0,0 @@
|
||||
//
|
||||
// Select.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: - SelectResultType
|
||||
|
||||
/**
|
||||
The `SelectResultType` protocol is implemented by return types supported by the `Select` clause.
|
||||
*/
|
||||
public protocol SelectResultType { }
|
||||
|
||||
|
||||
// MARK: - SelectValueResultType
|
||||
|
||||
/**
|
||||
The `SelectValueResultType` protocol is implemented by return types supported by the `queryValue(...)` methods.
|
||||
*/
|
||||
public protocol SelectValueResultType: SelectResultType {
|
||||
|
||||
static func fromResultObject(result: AnyObject) -> Self?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SelectAttributesResultType
|
||||
|
||||
/**
|
||||
The `SelectValueResultType` protocol is implemented by return types supported by the `queryAttributes(...)` methods.
|
||||
*/
|
||||
public protocol SelectAttributesResultType: SelectResultType {
|
||||
|
||||
static func fromResultObjects(result: [AnyObject]) -> [[NSString: AnyObject]]
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SelectTerm
|
||||
|
||||
/**
|
||||
The `SelectTerm` is passed to the `Select` clause to indicate the attributes/aggregate keys to be queried.
|
||||
*/
|
||||
public enum SelectTerm: StringLiteralConvertible {
|
||||
|
||||
/**
|
||||
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:
|
||||
```
|
||||
let fullName = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<String>(.Attribute("fullName")),
|
||||
Where("employeeID", isEqualTo: 1111)
|
||||
)
|
||||
```
|
||||
is equivalent to:
|
||||
```
|
||||
let fullName = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<String>("fullName"),
|
||||
Where("employeeID", isEqualTo: 1111)
|
||||
)
|
||||
```
|
||||
- parameter keyPath: the attribute name
|
||||
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
|
||||
*/
|
||||
public static func Attribute(keyPath: KeyPath) -> SelectTerm {
|
||||
|
||||
return ._Attribute(keyPath)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the average value of an attribute.
|
||||
```
|
||||
let averageAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
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
|
||||
*/
|
||||
public static func Average(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "average:",
|
||||
keyPath,
|
||||
As: alias ?? "average(\(keyPath))",
|
||||
nativeType: .DecimalAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for a count query.
|
||||
```
|
||||
let numberOfEmployees = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
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
|
||||
*/
|
||||
public static func Count(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "count:",
|
||||
keyPath,
|
||||
As: alias ?? "count(\(keyPath))",
|
||||
nativeType: .Integer64AttributeType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute.
|
||||
```
|
||||
let maximumAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
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
|
||||
*/
|
||||
public static func Maximum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "max:",
|
||||
keyPath,
|
||||
As: alias ?? "max(\(keyPath))",
|
||||
nativeType: .UndefinedAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute.
|
||||
```
|
||||
let minimumAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
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
|
||||
*/
|
||||
public static func Minimum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "min:",
|
||||
keyPath,
|
||||
As: alias ?? "min(\(keyPath))",
|
||||
nativeType: .UndefinedAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a `SelectTerm` to a `Select` clause for querying the sum value for an attribute.
|
||||
```
|
||||
let totalAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
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
|
||||
*/
|
||||
public static func Sum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
|
||||
|
||||
return ._Aggregate(
|
||||
function: "sum:",
|
||||
keyPath,
|
||||
As: alias ?? "sum(\(keyPath))",
|
||||
nativeType: .DecimalAttributeType
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: StringLiteralConvertible
|
||||
|
||||
public init(stringLiteral value: KeyPath) {
|
||||
|
||||
self = ._Attribute(value)
|
||||
}
|
||||
|
||||
public init(unicodeScalarLiteral value: KeyPath) {
|
||||
|
||||
self = ._Attribute(value)
|
||||
}
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: KeyPath) {
|
||||
|
||||
self = ._Attribute(value)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
case _Attribute(KeyPath)
|
||||
case _Aggregate(function: String, KeyPath, As: String, nativeType: NSAttributeType)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Select
|
||||
|
||||
/**
|
||||
The `Select` clause indicates the attribute / aggregate value to be queried. The generic type is a `SelectResultType`, and will be used as the return type for the query.
|
||||
|
||||
You can bind the return type by specializing the initializer:
|
||||
```
|
||||
let maximumAge = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select<Int>(.Maximum("age"))
|
||||
)
|
||||
```
|
||||
or by casting the type of the return value:
|
||||
```
|
||||
let maximumAge: Int = CoreStore.queryValue(
|
||||
From(MyPersonEntity),
|
||||
Select(.Maximum("age"))
|
||||
)
|
||||
```
|
||||
Valid return types depend on the query:
|
||||
|
||||
- for `queryValue(...)` methods:
|
||||
- `Bool`
|
||||
- `Int8`
|
||||
- `Int16`
|
||||
- `Int32`
|
||||
- `Int64`
|
||||
- `Double`
|
||||
- `Float`
|
||||
- `String`
|
||||
- `NSNumber`
|
||||
- `NSString`
|
||||
- `NSDecimalNumber`
|
||||
- `NSDate`
|
||||
- `NSData`
|
||||
- `NSManagedObjectID`
|
||||
- `NSString`
|
||||
- for `queryAttributes(...)` methods:
|
||||
- `NSDictionary`
|
||||
|
||||
- parameter sortDescriptors: a series of `NSSortDescriptor`s
|
||||
*/
|
||||
public struct Select<T: SelectResultType> {
|
||||
|
||||
/**
|
||||
The `SelectResultType` type for the query's return value
|
||||
*/
|
||||
public typealias ReturnType = T
|
||||
|
||||
/**
|
||||
Initializes a `Select` clause with a list of `SelectTerm`s
|
||||
|
||||
- parameter selectTerm: a `SelectTerm`
|
||||
- parameter selectTerms: a series of `SelectTerm`s
|
||||
*/
|
||||
public init(_ selectTerm: SelectTerm, _ selectTerms: SelectTerm...) {
|
||||
|
||||
self.selectTerms = [selectTerm] + selectTerms
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func applyToFetchRequest(fetchRequest: NSFetchRequest) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
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]
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Bool: SelectValueResultType
|
||||
|
||||
extension Bool: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .BooleanAttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Bool? {
|
||||
|
||||
return (result as? NSNumber)?.boolValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Int8: SelectValueResultType
|
||||
|
||||
extension Int8: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .Integer64AttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int8? {
|
||||
|
||||
guard let value = (result as? NSNumber)?.longLongValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return numericCast(value) as Int8
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Int16: SelectValueResultType
|
||||
|
||||
extension Int16: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .Integer64AttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int16? {
|
||||
|
||||
guard let value = (result as? NSNumber)?.longLongValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return numericCast(value) as Int16
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Int32: SelectValueResultType
|
||||
|
||||
extension Int32: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .Integer64AttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int32? {
|
||||
|
||||
guard let value = (result as? NSNumber)?.longLongValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return numericCast(value) as Int32
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Int64: SelectValueResultType
|
||||
|
||||
extension Int64: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .Integer64AttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int64? {
|
||||
|
||||
return (result as? NSNumber)?.longLongValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Int: SelectValueResultType
|
||||
|
||||
extension Int: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .Integer64AttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int? {
|
||||
|
||||
guard let value = (result as? NSNumber)?.longLongValue else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return numericCast(value) as Int
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Double : SelectValueResultType
|
||||
|
||||
extension Double: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .DoubleAttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Double? {
|
||||
|
||||
return (result as? NSNumber)?.doubleValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Float: SelectValueResultType
|
||||
|
||||
extension Float: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .FloatAttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Float? {
|
||||
|
||||
return (result as? NSNumber)?.floatValue
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - String: SelectValueResultType
|
||||
|
||||
extension String: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .StringAttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> String? {
|
||||
|
||||
return result as? NSString as? String
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSNumber: SelectValueResultType
|
||||
|
||||
extension NSNumber: SelectValueResultType {
|
||||
|
||||
public class var attributeType: NSAttributeType {
|
||||
|
||||
return .Integer64AttributeType
|
||||
}
|
||||
|
||||
public class func fromResultObject(result: AnyObject) -> Self? {
|
||||
|
||||
func forceCast<T: NSNumber>(object: AnyObject) -> T? {
|
||||
|
||||
return (object as? T)
|
||||
}
|
||||
return forceCast(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSString: SelectValueResultType
|
||||
|
||||
extension NSString: SelectValueResultType {
|
||||
|
||||
public class var attributeType: NSAttributeType {
|
||||
|
||||
return .StringAttributeType
|
||||
}
|
||||
|
||||
public class func fromResultObject(result: AnyObject) -> Self? {
|
||||
|
||||
func forceCast<T: NSString>(object: AnyObject) -> T? {
|
||||
|
||||
return (object as? T)
|
||||
}
|
||||
return forceCast(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSDecimalNumber: SelectValueResultType
|
||||
|
||||
extension NSDecimalNumber {
|
||||
|
||||
public override class var attributeType: NSAttributeType {
|
||||
|
||||
return .DecimalAttributeType
|
||||
}
|
||||
|
||||
public override class func fromResultObject(result: AnyObject) -> Self? {
|
||||
|
||||
func forceCast<T: NSDecimalNumber>(object: AnyObject) -> T? {
|
||||
|
||||
return (object as? T)
|
||||
}
|
||||
return forceCast(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSDate: SelectValueResultType
|
||||
|
||||
extension NSDate: SelectValueResultType {
|
||||
|
||||
public class var attributeType: NSAttributeType {
|
||||
|
||||
return .DateAttributeType
|
||||
}
|
||||
|
||||
public class func fromResultObject(result: AnyObject) -> Self? {
|
||||
|
||||
func forceCast<T: NSDate>(object: AnyObject) -> T? {
|
||||
|
||||
return (object as? T)
|
||||
}
|
||||
return forceCast(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSData: SelectValueResultType
|
||||
|
||||
extension NSData: SelectValueResultType {
|
||||
|
||||
public class var attributeType: NSAttributeType {
|
||||
|
||||
return .BinaryDataAttributeType
|
||||
}
|
||||
|
||||
public class func fromResultObject(result: AnyObject) -> Self? {
|
||||
|
||||
func forceCast<T: NSData>(object: AnyObject) -> T? {
|
||||
|
||||
return (object as? T)
|
||||
}
|
||||
return forceCast(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectID: SelectValueResultType
|
||||
|
||||
extension NSManagedObjectID: SelectValueResultType {
|
||||
|
||||
public class var attributeType: NSAttributeType {
|
||||
|
||||
return .ObjectIDAttributeType
|
||||
}
|
||||
|
||||
public class func fromResultObject(result: AnyObject) -> Self? {
|
||||
|
||||
func forceCast<T: NSManagedObjectID>(object: AnyObject) -> T? {
|
||||
|
||||
return (object as? T)
|
||||
}
|
||||
return forceCast(result)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectID: SelectAttributesResultType
|
||||
|
||||
extension NSDictionary: SelectAttributesResultType {
|
||||
|
||||
// MARK: SelectAttributesResultType
|
||||
|
||||
public class func fromResultObjects(result: [AnyObject]) -> [[NSString: AnyObject]] {
|
||||
|
||||
return result as! [[NSString: AnyObject]]
|
||||
}
|
||||
}
|
||||
@@ -1,155 +0,0 @@
|
||||
//
|
||||
// Where.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
|
||||
|
||||
|
||||
public func &&(left: Where, right: Where) -> Where {
|
||||
|
||||
return Where(NSCompoundPredicate(type: .AndPredicateType, subpredicates: [left.predicate, right.predicate]))
|
||||
}
|
||||
|
||||
public func ||(left: Where, right: Where) -> Where {
|
||||
|
||||
return Where(NSCompoundPredicate(type: .OrPredicateType, subpredicates: [left.predicate, right.predicate]))
|
||||
}
|
||||
|
||||
public prefix func !(clause: Where) -> Where {
|
||||
|
||||
return Where(NSCompoundPredicate(type: .NotPredicateType, subpredicates: [clause.predicate]))
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Where
|
||||
|
||||
/**
|
||||
The `Where` clause specifies the conditions for a fetch or a query.
|
||||
*/
|
||||
public struct Where: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause with an `NSPredicate`
|
||||
|
||||
- parameter predicate: the `NSPredicate` for the fetch or query
|
||||
*/
|
||||
public init(_ predicate: NSPredicate) {
|
||||
|
||||
self.predicate = predicate
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause with a predicate that always evaluates to `true`
|
||||
*/
|
||||
public init() {
|
||||
|
||||
self.init(true)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause with a predicate that always evaluates to the specified boolean value
|
||||
|
||||
- parameter value: the boolean value for the predicate
|
||||
*/
|
||||
public init(_ value: Bool) {
|
||||
|
||||
self.init(NSPredicate(value: value))
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause with a predicate using the specified string format and arguments
|
||||
|
||||
- parameter format: the format string for the predicate
|
||||
- parameter args: the arguments for `format`
|
||||
*/
|
||||
public init(_ format: String, _ args: NSObject...) {
|
||||
|
||||
self.init(NSPredicate(format: format, argumentArray: args))
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause with a predicate using the specified string format and arguments
|
||||
|
||||
- parameter format: the format string for the predicate
|
||||
- parameter argumentArray: the arguments for `format`
|
||||
*/
|
||||
public init(_ format: String, argumentArray: [NSObject]?) {
|
||||
|
||||
self.init(NSPredicate(format: format, argumentArray: argumentArray))
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares equality
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter value: the arguments for the `==` operator
|
||||
*/
|
||||
public init(_ keyPath: KeyPath, isEqualTo value: NSObject?) {
|
||||
|
||||
self.init(value == nil
|
||||
? NSPredicate(format: "\(keyPath) == nil")
|
||||
: NSPredicate(format: "\(keyPath) == %@", argumentArray: [value!]))
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares membership
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter list: the array to check membership of
|
||||
*/
|
||||
public init(_ keyPath: KeyPath, isMemberOf list: NSArray) {
|
||||
|
||||
self.init(NSPredicate(format: "\(keyPath) IN %@", list))
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `Where` clause that compares membership
|
||||
|
||||
- parameter keyPath: the keyPath to compare with
|
||||
- parameter list: the sequence to check membership of
|
||||
*/
|
||||
public init<S: SequenceType where S.Generator.Element: NSObject>(_ keyPath: KeyPath, isMemberOf list: S) {
|
||||
|
||||
self.init(NSPredicate(format: "\(keyPath) IN %@", Array(list) as NSArray))
|
||||
}
|
||||
|
||||
public let predicate: NSPredicate
|
||||
|
||||
|
||||
// MARK: FetchClause, QueryClause, DeleteClause
|
||||
|
||||
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
|
||||
|
||||
if let predicate = fetchRequest.predicate where predicate != self.predicate {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "An existing predicate for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.predicate = self.predicate
|
||||
}
|
||||
}
|
||||
@@ -1,137 +0,0 @@
|
||||
//
|
||||
// ImportableUniqueObject.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: - ImportableUniqueObject
|
||||
|
||||
/**
|
||||
`NSManagedObject` subclasses that conform to the `ImportableUniqueObject` protocol can be imported from a specified `ImportSource`. This allows transactions to either update existing objects or create new instances this way:
|
||||
```
|
||||
class MyPersonEntity: NSManagedObject, ImportableUniqueObject {
|
||||
typealias ImportSource = NSDictionary
|
||||
typealias UniqueIDType = NSString
|
||||
// ...
|
||||
}
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
let json: NSDictionary = // ...
|
||||
let person = try! transaction.importUniqueObject(
|
||||
Into(MyPersonEntity),
|
||||
source: json
|
||||
)
|
||||
// ...
|
||||
transaction.commit()
|
||||
}
|
||||
```
|
||||
*/
|
||||
public protocol ImportableUniqueObject: ImportableObject {
|
||||
|
||||
/**
|
||||
The data type for the import source. This is most commonly an `NSDictionary` or another external source such as an `NSUserDefaults`.
|
||||
*/
|
||||
typealias ImportSource
|
||||
|
||||
/**
|
||||
The data type for the entity's unique ID attribute
|
||||
*/
|
||||
typealias UniqueIDType: NSObject
|
||||
|
||||
/**
|
||||
The keyPath to the entity's unique ID attribute
|
||||
*/
|
||||
static var uniqueIDKeyPath: String { get }
|
||||
|
||||
/**
|
||||
The object's unique ID value
|
||||
*/
|
||||
var uniqueIDValue: UniqueIDType { get set }
|
||||
|
||||
/**
|
||||
Return `true` if an object should be created from `source`. Return `false` to ignore and skip `source`. The default implementation returns the value returned by the `shouldUpdateFromImportSource(:inTransaction:)` implementation.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
- returns: `true` if an object should be created from `source`. Return `false` to ignore.
|
||||
*/
|
||||
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
|
||||
|
||||
/**
|
||||
Return `true` if an object should be updated from `source`. Return `false` to ignore and skip `source`. The default implementation returns `true`.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
- returns: `true` if an object should be updated from `source`. Return `false` to ignore.
|
||||
*/
|
||||
static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
|
||||
|
||||
/**
|
||||
Return the unique ID as extracted from `source`. This method is called before `shouldInsertFromImportSource(...)` or `shouldUpdateFromImportSource(...)`. Return `nil` to skip importing from `source`. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
- returns: the unique ID as extracted from `source`, or `nil` to skip importing from `source`.
|
||||
*/
|
||||
static func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> UniqueIDType?
|
||||
|
||||
/**
|
||||
Implements the actual importing of data from `source`. This method is called just after the object is created and assigned its unique ID as returned from `uniqueIDFromImportSource(...)`. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled. The default implementation simply calls `updateFromImportSource(...)`.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
*/
|
||||
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
|
||||
|
||||
/**
|
||||
Implements the actual importing of data from `source`. This method is called just after the existing object is fetched using its unique ID. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled.
|
||||
|
||||
- parameter source: the object to import from
|
||||
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
|
||||
*/
|
||||
func updateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ImportableUniqueObject (Default Implementations)
|
||||
|
||||
public extension ImportableUniqueObject {
|
||||
|
||||
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
|
||||
|
||||
return self.shouldUpdateFromImportSource(source, inTransaction: transaction)
|
||||
}
|
||||
|
||||
static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
|
||||
|
||||
try self.updateFromImportSource(source, inTransaction: transaction)
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
//
|
||||
// NSFileManager+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/07/19.
|
||||
// Copyright © 2015 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - NSFileManager
|
||||
|
||||
internal extension NSFileManager {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func removeSQLiteStoreAtURL(fileURL: NSURL) {
|
||||
|
||||
_ = try? self.removeItemAtURL(fileURL)
|
||||
|
||||
let filePath = fileURL.path!
|
||||
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-shm"))
|
||||
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-wal"))
|
||||
}
|
||||
}
|
||||
@@ -1,422 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectContext+Querying.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: - NSManagedObjectContext
|
||||
|
||||
internal extension NSManagedObjectContext {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
if object.objectID.temporaryID {
|
||||
|
||||
do {
|
||||
|
||||
try withExtendedLifetime(self) { (context: NSManagedObjectContext) -> Void in
|
||||
|
||||
try context.obtainPermanentIDsForObjects([object])
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to obtain permanent ID for object."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.existingObjectWithID(object.objectID)
|
||||
return (existingObject as! T)
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load existing \(typeName(object)) in context."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
internal func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
return self.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
internal func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 1
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
var fetchResults: [T]?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest) as? [T]
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fetchResults?.first
|
||||
}
|
||||
|
||||
internal func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
return self.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
internal func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
var fetchResults: [T]?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest) as? [T]
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fetchResults
|
||||
}
|
||||
|
||||
internal func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
return self.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
internal func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
var count = 0
|
||||
var error: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
count = self.countForFetchRequest(fetchRequest, error: &error)
|
||||
}
|
||||
if count == NSNotFound {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
|
||||
internal func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
return self.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
internal func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 1
|
||||
fetchRequest.resultType = .ManagedObjectIDResultType
|
||||
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
var fetchResults: [NSManagedObjectID]?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest) as? [NSManagedObjectID]
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fetchResults?.first
|
||||
}
|
||||
|
||||
internal func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
return self.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
internal func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectIDResultType
|
||||
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
var fetchResults: [NSManagedObjectID]?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest) as? [NSManagedObjectID]
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if fetchResults == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
return fetchResults
|
||||
}
|
||||
|
||||
internal func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
|
||||
|
||||
return self.deleteAll(from, deleteClauses)
|
||||
}
|
||||
|
||||
internal func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchRequest.returnsObjectsAsFaults = true
|
||||
fetchRequest.includesPropertyValues = false
|
||||
|
||||
for clause in deleteClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
var numberOfDeletedObjects: Int?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
let fetchResults = try self.executeFetchRequest(fetchRequest) as? [T] ?? []
|
||||
for object in fetchResults {
|
||||
|
||||
self.deleteObject(object)
|
||||
}
|
||||
numberOfDeletedObjects = fetchResults.count
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
}
|
||||
if numberOfDeletedObjects == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
return numberOfDeletedObjects
|
||||
}
|
||||
|
||||
internal func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
|
||||
|
||||
return self.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
internal func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
|
||||
selectClause.applyToFetchRequest(fetchRequest)
|
||||
|
||||
for clause in queryClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
var fetchResults: [AnyObject]?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest)
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
if let rawResult = fetchResults.first as? NSDictionary,
|
||||
let rawObject: AnyObject = rawResult[selectClause.keyPathForFirstSelectTerm()] {
|
||||
|
||||
return Select<U>.ReturnType.fromResultObject(rawObject)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
internal func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
return self.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
internal func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: self)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
|
||||
selectClause.applyToFetchRequest(fetchRequest)
|
||||
|
||||
for clause in queryClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
var fetchResults: [AnyObject]?
|
||||
var fetchError: NSError?
|
||||
self.performBlockAndWait {
|
||||
|
||||
do {
|
||||
|
||||
fetchResults = try self.executeFetchRequest(fetchRequest)
|
||||
}
|
||||
catch {
|
||||
|
||||
fetchError = error as NSError
|
||||
}
|
||||
}
|
||||
if let fetchResults = fetchResults {
|
||||
|
||||
return Select<NSDictionary>.ReturnType.fromResultObjects(fetchResults)
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed executing fetch request."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectContext+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
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectContext
|
||||
|
||||
internal extension NSManagedObjectContext {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal weak var parentStack: DataStack? {
|
||||
|
||||
get {
|
||||
|
||||
if let parentContext = self.parentContext {
|
||||
|
||||
return parentContext.parentStack
|
||||
}
|
||||
|
||||
return getAssociatedObjectForKey(&PropertyKeys.parentStack, inObject: self)
|
||||
}
|
||||
set {
|
||||
|
||||
guard self.parentContext == nil else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
setAssociatedWeakObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.parentStack,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal class func rootSavingContextForCoordinator(coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
|
||||
context.persistentStoreCoordinator = coordinator
|
||||
context.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
|
||||
context.undoManager = nil
|
||||
context.setupForCoreStoreWithContextName("com.corestore.rootcontext")
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
internal class func mainContextForRootContext(rootContext: NSManagedObjectContext) -> NSManagedObjectContext {
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
|
||||
context.parentContext = rootContext
|
||||
context.mergePolicy = NSRollbackMergePolicy
|
||||
context.undoManager = nil
|
||||
context.setupForCoreStoreWithContextName("com.corestore.maincontext")
|
||||
context.observerForDidSaveNotification = NotificationObserver(
|
||||
notificationName: NSManagedObjectContextDidSaveNotification,
|
||||
object: rootContext,
|
||||
closure: { [weak context] (note) -> Void in
|
||||
|
||||
context?.performBlock { () -> Void in
|
||||
|
||||
let updatedObjects = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? []
|
||||
for object in updatedObjects {
|
||||
|
||||
context?.objectWithID(object.objectID).willAccessValueForKey(nil)
|
||||
}
|
||||
context?.mergeChangesFromContextDidSaveNotification(note)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var parentStack: Void?
|
||||
static var observerForDidSaveNotification: Void?
|
||||
}
|
||||
|
||||
private var observerForDidSaveNotification: NotificationObserver? {
|
||||
|
||||
get {
|
||||
|
||||
return getAssociatedObjectForKey(
|
||||
&PropertyKeys.observerForDidSaveNotification,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.observerForDidSaveNotification,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,189 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectContext+Transaction.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: - NSManagedObjectContext
|
||||
|
||||
internal extension NSManagedObjectContext {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal weak var parentTransaction: BaseDataTransaction? {
|
||||
|
||||
get {
|
||||
|
||||
return getAssociatedObjectForKey(
|
||||
&PropertyKeys.parentTransaction,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedWeakObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.parentTransaction,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal func isRunningInAllowedQueue() -> Bool {
|
||||
|
||||
guard let parentTransaction = self.parentTransaction else {
|
||||
|
||||
return false
|
||||
}
|
||||
return parentTransaction.isRunningInAllowedQueue()
|
||||
}
|
||||
|
||||
internal func temporaryContextInTransactionWithConcurrencyType(concurrencyType: NSManagedObjectContextConcurrencyType) -> NSManagedObjectContext {
|
||||
|
||||
let context = NSManagedObjectContext(concurrencyType: concurrencyType)
|
||||
context.parentContext = self
|
||||
context.parentStack = self.parentStack
|
||||
context.setupForCoreStoreWithContextName("com.corestore.temporarycontext")
|
||||
context.shouldCascadeSavesToParent = (self.parentStack?.rootSavingContext == self)
|
||||
context.retainsRegisteredObjects = true
|
||||
|
||||
return context
|
||||
}
|
||||
|
||||
internal func saveSynchronously() -> SaveResult {
|
||||
|
||||
var result = SaveResult(hasChanges: false)
|
||||
|
||||
self.performBlockAndWait { [unowned self] () -> Void in
|
||||
|
||||
guard self.hasChanges else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try self.save()
|
||||
}
|
||||
catch {
|
||||
|
||||
let saveError = error as NSError
|
||||
CoreStore.handleError(
|
||||
saveError,
|
||||
"Failed to save \(typeName(NSManagedObjectContext))."
|
||||
)
|
||||
result = SaveResult(saveError)
|
||||
return
|
||||
}
|
||||
|
||||
if let parentContext = self.parentContext where self.shouldCascadeSavesToParent {
|
||||
|
||||
switch parentContext.saveSynchronously() {
|
||||
|
||||
case .Success:
|
||||
result = SaveResult(hasChanges: true)
|
||||
|
||||
case .Failure(let error):
|
||||
result = SaveResult(error)
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
result = SaveResult(hasChanges: true)
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
internal func saveAsynchronouslyWithCompletion(completion: ((result: SaveResult) -> Void) = { _ in }) {
|
||||
|
||||
self.performBlock { () -> Void in
|
||||
|
||||
guard self.hasChanges else {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(hasChanges: false))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try self.save()
|
||||
}
|
||||
catch {
|
||||
|
||||
let saveError = error as NSError
|
||||
CoreStore.handleError(
|
||||
saveError,
|
||||
"Failed to save \(typeName(NSManagedObjectContext))."
|
||||
)
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(saveError))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if let parentContext = self.parentContext where self.shouldCascadeSavesToParent {
|
||||
|
||||
parentContext.saveAsynchronouslyWithCompletion(completion)
|
||||
}
|
||||
else {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(hasChanges: true))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal func refreshAllObjectsAsFaults() {
|
||||
|
||||
if #available(iOS 8.3, OSX 10.11, *) {
|
||||
|
||||
self.refreshAllObjects()
|
||||
}
|
||||
else {
|
||||
|
||||
self.registeredObjects.forEach { self.refreshObject($0, mergeChanges: false) }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var parentTransaction: Void?
|
||||
}
|
||||
}
|
||||
@@ -1,279 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectModel+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
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectModel
|
||||
|
||||
internal extension NSManagedObjectModel {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc internal class func fromBundle(bundle: NSBundle, modelName: String, modelVersionHints: Set<String> = []) -> NSManagedObjectModel {
|
||||
|
||||
guard let modelFilePath = bundle.pathForResource(modelName, ofType: "momd") else {
|
||||
|
||||
fatalError("Could not find \"\(modelName).momd\" from the bundle. \(bundle)")
|
||||
}
|
||||
|
||||
let modelFileURL = NSURL(fileURLWithPath: modelFilePath)
|
||||
let versionInfoPlistURL = modelFileURL.URLByAppendingPathComponent("VersionInfo.plist", isDirectory: false)
|
||||
|
||||
guard let versionInfo = NSDictionary(contentsOfURL: versionInfoPlistURL),
|
||||
let versionHashes = versionInfo["NSManagedObjectModel_VersionHashes"] as? [String: AnyObject] else {
|
||||
|
||||
fatalError("Could not load \(typeName(NSManagedObjectModel)) metadata from path \"\(versionInfoPlistURL)\".")
|
||||
}
|
||||
|
||||
let modelVersions = Set(versionHashes.keys)
|
||||
let currentModelVersion: String
|
||||
if let plistModelVersion = versionInfo["NSManagedObjectModel_CurrentVersionName"] as? String where modelVersionHints.isEmpty || modelVersionHints.contains(plistModelVersion) {
|
||||
|
||||
currentModelVersion = plistModelVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.intersect(modelVersionHints).first {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The MigrationChain leaf versions do not include the model file's current version. Resolving to version \"\(resolvedVersion)\"."
|
||||
)
|
||||
currentModelVersion = resolvedVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.first ?? modelVersionHints.first {
|
||||
|
||||
if !modelVersionHints.isEmpty {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The MigrationChain leaf versions do not include any of the model file's embedded versions. Resolving to version \"\(resolvedVersion)\"."
|
||||
)
|
||||
}
|
||||
currentModelVersion = resolvedVersion
|
||||
}
|
||||
else {
|
||||
|
||||
fatalError("No model files were found in URL \"\(modelFileURL)\".")
|
||||
}
|
||||
|
||||
var modelVersionFileURL: NSURL?
|
||||
for modelVersion in modelVersions {
|
||||
|
||||
let fileURL = modelFileURL.URLByAppendingPathComponent("\(modelVersion).mom", isDirectory: false)
|
||||
|
||||
if modelVersion == currentModelVersion {
|
||||
|
||||
modelVersionFileURL = fileURL
|
||||
continue
|
||||
}
|
||||
|
||||
precondition(
|
||||
NSManagedObjectModel(contentsOfURL: fileURL) != nil,
|
||||
"Could not find the \"\(modelVersion).mom\" version file for the model at URL \"\(modelFileURL)\"."
|
||||
)
|
||||
}
|
||||
|
||||
if let modelVersionFileURL = modelVersionFileURL,
|
||||
let rootModel = NSManagedObjectModel(contentsOfURL: modelVersionFileURL) {
|
||||
|
||||
rootModel.modelVersionFileURL = modelVersionFileURL
|
||||
rootModel.modelVersions = modelVersions
|
||||
rootModel.currentModelVersion = currentModelVersion
|
||||
return rootModel
|
||||
}
|
||||
|
||||
fatalError("Could not create an \(typeName(NSManagedObjectModel)) from the model at URL \"\(modelFileURL)\".")
|
||||
}
|
||||
|
||||
@nonobjc private(set) internal var currentModelVersion: String? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSString? = getAssociatedObjectForKey(
|
||||
&PropertyKeys.currentModelVersion,
|
||||
inObject: self
|
||||
)
|
||||
return value as? String
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedCopiedObject(
|
||||
newValue == nil ? nil : (newValue! as NSString),
|
||||
forKey: &PropertyKeys.currentModelVersion,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc private(set) internal var modelVersions: Set<String>? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSSet? = getAssociatedObjectForKey(
|
||||
&PropertyKeys.modelVersions,
|
||||
inObject: self
|
||||
)
|
||||
return value as? Set<String>
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedCopiedObject(
|
||||
newValue == nil ? nil : (newValue! as NSSet),
|
||||
forKey: &PropertyKeys.modelVersions,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc internal func entityNameForClass(entityClass: AnyClass) -> String {
|
||||
|
||||
return self.entityNameMapping[NSStringFromClass(entityClass)]!
|
||||
}
|
||||
|
||||
@nonobjc internal func entityTypesMapping() -> [String: NSManagedObject.Type] {
|
||||
|
||||
return self.entityNameMapping.reduce([:]) { (var mapping, pair) in
|
||||
|
||||
mapping[pair.1] = (NSClassFromString(pair.0)! as! NSManagedObject.Type)
|
||||
return mapping
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc internal func mergedModels() -> [NSManagedObjectModel] {
|
||||
|
||||
return self.modelVersions?.map { self[$0] }.flatMap { $0 == nil ? [] : [$0!] } ?? [self]
|
||||
}
|
||||
|
||||
@nonobjc internal subscript(modelVersion: String) -> NSManagedObjectModel? {
|
||||
|
||||
if modelVersion == self.currentModelVersion {
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
guard let modelFileURL = self.modelFileURL,
|
||||
let modelVersions = self.modelVersions
|
||||
where modelVersions.contains(modelVersion) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let versionModelFileURL = modelFileURL.URLByAppendingPathComponent("\(modelVersion).mom", isDirectory: false)
|
||||
guard let model = NSManagedObjectModel(contentsOfURL: versionModelFileURL) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
model.currentModelVersion = modelVersion
|
||||
model.modelVersionFileURL = versionModelFileURL
|
||||
model.modelVersions = modelVersions
|
||||
return model
|
||||
}
|
||||
|
||||
@nonobjc internal subscript(metadata: [String: AnyObject]) -> NSManagedObjectModel? {
|
||||
|
||||
guard let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : NSData] else {
|
||||
|
||||
return nil
|
||||
}
|
||||
for modelVersion in self.modelVersions ?? [] {
|
||||
|
||||
if let versionModel = self[modelVersion] where modelHashes == versionModel.entityVersionHashesByName {
|
||||
|
||||
return versionModel
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var modelFileURL: NSURL? {
|
||||
|
||||
get {
|
||||
|
||||
return self.modelVersionFileURL?.URLByDeletingLastPathComponent
|
||||
}
|
||||
}
|
||||
|
||||
private var modelVersionFileURL: NSURL? {
|
||||
|
||||
get {
|
||||
|
||||
let value: NSURL? = getAssociatedObjectForKey(
|
||||
&PropertyKeys.modelVersionFileURL,
|
||||
inObject: self
|
||||
)
|
||||
return value
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedCopiedObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.modelVersionFileURL,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private var entityNameMapping: [String: String] {
|
||||
|
||||
get {
|
||||
|
||||
if let mapping: NSDictionary = getAssociatedObjectForKey(&PropertyKeys.entityNameMapping, inObject: self) {
|
||||
|
||||
return mapping as! [String: String]
|
||||
}
|
||||
|
||||
let mapping = self.entities.reduce([String: String]()) {
|
||||
(var mapping, entityDescription) -> [String: String] in
|
||||
|
||||
if let entityName = entityDescription.name {
|
||||
|
||||
let className = entityDescription.managedObjectClassName
|
||||
mapping[className] = entityName
|
||||
}
|
||||
return mapping
|
||||
}
|
||||
setAssociatedCopiedObject(
|
||||
mapping as NSDictionary,
|
||||
forKey: &PropertyKeys.entityNameMapping,
|
||||
inObject: self
|
||||
)
|
||||
return mapping
|
||||
}
|
||||
}
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var entityNameMapping: Void?
|
||||
|
||||
static var modelVersionFileURL: Void?
|
||||
static var modelVersions: Void?
|
||||
static var currentModelVersion: Void?
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
//
|
||||
// CoreStoreLogger.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - LogLevel
|
||||
|
||||
/**
|
||||
The `LogLevel` indicates the severity of a log message.
|
||||
*/
|
||||
public enum LogLevel {
|
||||
|
||||
case Trace
|
||||
case Notice
|
||||
case Warning
|
||||
case Fatal
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CoreStoreLogger
|
||||
|
||||
/**
|
||||
Custom loggers should implement the `CoreStoreLogger` protocol and pass its instance to `CoreStore.logger`. Calls to `log(...)`, `handleError(...)`, and `assert(...)` are not tied to a specific queue/thread, so it is the implementer's job to handle thread-safety.
|
||||
*/
|
||||
public protocol CoreStoreLogger {
|
||||
|
||||
/**
|
||||
Handles log messages sent by the `CoreStore` framework.
|
||||
|
||||
:level: the severity of the log message
|
||||
:message: the log message
|
||||
:fileName: the source file name
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
|
||||
/**
|
||||
Handles errors sent by the `CoreStore` framework.
|
||||
|
||||
:error: the error
|
||||
:message: the error message
|
||||
:fileName: the source file name
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
|
||||
/**
|
||||
Handles assertions made throughout the `CoreStore` framework.
|
||||
|
||||
:condition: the assertion condition
|
||||
:message: the assertion message
|
||||
:fileName: the source file name
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Utilities
|
||||
|
||||
internal func typeName<T>(value: T) -> String {
|
||||
|
||||
return "'\(String(reflecting: value.dynamicType))'"
|
||||
}
|
||||
|
||||
internal func typeName<T>(value: T.Type) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(value: AnyClass) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(name: String?) -> String {
|
||||
|
||||
return "<\(name ?? "unknown")>"
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
//
|
||||
// DefaultLogger.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: - DefaultLogger
|
||||
|
||||
/**
|
||||
The `DefaultLogger` is a basic implementation of the `CoreStoreLogger` protocol.
|
||||
|
||||
- The `log(...)` method calls `print(...)` to print the level, source file name, line number, function name, and the log message.
|
||||
- The `handleError(...)` method calls `print(...)` to print the source file name, line number, function name, and the error message.
|
||||
- The `assert(...)` method calls `assert(...)` on the arguments.
|
||||
*/
|
||||
public final class DefaultLogger: CoreStoreLogger {
|
||||
|
||||
public init() { }
|
||||
|
||||
public func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
let icon: String
|
||||
let levelString: String
|
||||
switch level {
|
||||
|
||||
case .Trace:
|
||||
icon = "🔹"
|
||||
levelString = "Trace"
|
||||
|
||||
case .Notice:
|
||||
icon = "🔸"
|
||||
levelString = "Notice"
|
||||
|
||||
case .Warning:
|
||||
icon = "⚠️"
|
||||
levelString = "Warning"
|
||||
|
||||
case .Fatal:
|
||||
icon = "❗"
|
||||
levelString = "Fatal"
|
||||
}
|
||||
Swift.print("\(icon) [CoreStore: \(levelString)] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
Swift.print("⚠️ [CoreStore: Error] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
if condition() {
|
||||
|
||||
return
|
||||
}
|
||||
Swift.print("❗ [CoreStore: Assertion Failure] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
Swift.fatalError()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
//
|
||||
// CoreStore+Migration.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
/**
|
||||
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.addSQLiteStore(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.addSQLiteStore(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
|
||||
|
||||
return try self.defaultStack.requiredMigrationsForSQLiteStore(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
|
||||
|
||||
return try self.defaultStack.requiredMigrationsForSQLiteStore(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
//
|
||||
// NSError+CoreStore.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - CoreStoreError
|
||||
|
||||
/**
|
||||
The `NSError` error domain for `CoreStore`.
|
||||
*/
|
||||
public let CoreStoreErrorDomain = "com.corestore.error"
|
||||
|
||||
/**
|
||||
The `NSError` error codes for `CoreStoreErrorDomain`.
|
||||
*/
|
||||
public enum CoreStoreErrorCode: Int {
|
||||
|
||||
/**
|
||||
A failure occured because of an unknown error.
|
||||
*/
|
||||
case UnknownError
|
||||
|
||||
/**
|
||||
The `NSPersistentStore` could note be initialized because another store existed at the specified `NSURL`.
|
||||
*/
|
||||
case DifferentPersistentStoreExistsAtURL
|
||||
|
||||
/**
|
||||
The `NSPersistentStore` specified could not be found.
|
||||
*/
|
||||
case PersistentStoreNotFound
|
||||
|
||||
/**
|
||||
An `NSMappingModel` could not be found for a specific source and destination model versions.
|
||||
*/
|
||||
case MappingModelNotFound
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSError
|
||||
|
||||
public extension NSError {
|
||||
|
||||
/**
|
||||
If the error's domain is equal to `CoreStoreErrorDomain`, returns the associated `CoreStoreErrorCode`. For other domains, returns `nil`.
|
||||
*/
|
||||
public var coreStoreErrorCode: CoreStoreErrorCode? {
|
||||
|
||||
return (self.domain == CoreStoreErrorDomain
|
||||
? CoreStoreErrorCode(rawValue: self.code)
|
||||
: nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode) {
|
||||
|
||||
self.init(coreStoreErrorCode: coreStoreErrorCode, userInfo: nil)
|
||||
}
|
||||
|
||||
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(
|
||||
domain: CoreStoreErrorDomain,
|
||||
code: coreStoreErrorCode.rawValue,
|
||||
userInfo: userInfo)
|
||||
}
|
||||
|
||||
internal var isCoreDataMigrationError: Bool {
|
||||
|
||||
let code = self.code
|
||||
return (code == NSPersistentStoreIncompatibleVersionHashError
|
||||
|| code == NSMigrationMissingSourceModelError
|
||||
|| code == NSMigrationError)
|
||||
&& self.domain == NSCocoaErrorDomain
|
||||
}
|
||||
}
|
||||
@@ -1,219 +0,0 @@
|
||||
//
|
||||
// DataStack+Observing.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
|
||||
|
||||
@available(OSX, unavailable)
|
||||
public extension DataStack {
|
||||
|
||||
/**
|
||||
Creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
|
||||
|
||||
- parameter object: the `NSManagedObject` to observe changes from
|
||||
- returns: a `ObjectMonitor` that monitors changes to `object`
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return ObjectMonitor(
|
||||
dataStack: self,
|
||||
object: object
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
|
||||
|
||||
return self.monitorList(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
CoreStore.assert(
|
||||
fetchClauses.filter { $0 is OrderBy }.count > 0,
|
||||
"A ListMonitor requires an OrderBy clause."
|
||||
)
|
||||
|
||||
return ListMonitor(
|
||||
dataStack: self,
|
||||
from: from,
|
||||
sectionBy: nil,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: FetchClause...) {
|
||||
|
||||
self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
CoreStore.assert(
|
||||
fetchClauses.filter { $0 is OrderBy }.count > 0,
|
||||
"A ListMonitor requires an OrderBy clause."
|
||||
)
|
||||
|
||||
_ = ListMonitor(
|
||||
dataStack: self,
|
||||
from: from,
|
||||
sectionBy: nil,
|
||||
fetchClauses: fetchClauses,
|
||||
createAsynchronously: createAsynchronously
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
|
||||
|
||||
return self.monitorSectionedList(from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
CoreStore.assert(
|
||||
fetchClauses.filter { $0 is OrderBy }.count > 0,
|
||||
"A ListMonitor requires an OrderBy clause."
|
||||
)
|
||||
|
||||
return ListMonitor(
|
||||
dataStack: self,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) {
|
||||
|
||||
self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
|
||||
|
||||
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
*/
|
||||
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to observe objects from \(typeName(self)) outside the main thread."
|
||||
)
|
||||
CoreStore.assert(
|
||||
fetchClauses.filter { $0 is OrderBy }.count > 0,
|
||||
"A ListMonitor requires an OrderBy clause."
|
||||
)
|
||||
|
||||
_ = ListMonitor(
|
||||
dataStack: self,
|
||||
from: from,
|
||||
sectionBy: sectionBy,
|
||||
fetchClauses: fetchClauses,
|
||||
createAsynchronously: createAsynchronously
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,330 +0,0 @@
|
||||
//
|
||||
// ObjectMonitor.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: - ObjectMonitor
|
||||
|
||||
/**
|
||||
The `ObjectMonitor` monitors changes to a single `NSManagedObject` instance. Observers that implement the `ObjectObserver` protocol may then register themselves to the `ObjectMonitor`'s `addObserver(_:)` method:
|
||||
```
|
||||
let monitor = CoreStore.monitorObject(object)
|
||||
monitor.addObserver(self)
|
||||
```
|
||||
The created `ObjectMonitor` instance needs to be held on (retained) for as long as the object needs to be observed.
|
||||
|
||||
Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
|
||||
*/
|
||||
@available(OSX, unavailable)
|
||||
public final class ObjectMonitor<T: NSManagedObject> {
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted.
|
||||
*/
|
||||
public var object: T? {
|
||||
|
||||
return self.fetchedResultsController.fetchedObjects?.first as? T
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `NSManagedObject` instance being observed still exists, or `false` if the object was already deleted.
|
||||
*/
|
||||
public var isObjectDeleted: Bool {
|
||||
|
||||
return self.object?.managedObjectContext == nil
|
||||
}
|
||||
|
||||
/**
|
||||
Registers an `ObjectObserver` to be notified when changes to the receiver's `object` are made.
|
||||
|
||||
To prevent retain-cycles, `ObjectMonitor` only keeps `weak` references to its observers.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
Calling `addObserver(_:)` multiple times on the same observer is safe, as `ObjectMonitor` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
- parameter observer: an `ObjectObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ObjectObserver where U.ObjectEntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to add an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&self.willChangeObjectKey,
|
||||
name: ObjectMonitorWillChangeObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor) -> Void in
|
||||
|
||||
guard let object = monitor.object, let observer = observer else {
|
||||
|
||||
return
|
||||
}
|
||||
observer.objectMonitor(monitor, willUpdateObject: object)
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&self.didDeleteObjectKey,
|
||||
name: ObjectMonitorDidDeleteObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object) -> Void in
|
||||
|
||||
guard let observer = observer else {
|
||||
|
||||
return
|
||||
}
|
||||
observer.objectMonitor(monitor, didDeleteObject: object)
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&self.didUpdateObjectKey,
|
||||
name: ObjectMonitorDidUpdateObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak self, weak observer] (monitor, object) -> Void in
|
||||
|
||||
guard let strongSelf = self, let observer = observer else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let previousCommitedAttributes = strongSelf.lastCommittedAttributes
|
||||
let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [String: NSObject]
|
||||
|
||||
var changedKeys = Set<String>()
|
||||
for key in currentCommitedAttributes.keys {
|
||||
|
||||
if previousCommitedAttributes[key] != currentCommitedAttributes[key] {
|
||||
|
||||
changedKeys.insert(key)
|
||||
}
|
||||
}
|
||||
|
||||
strongSelf.lastCommittedAttributes = currentCommitedAttributes
|
||||
observer.objectMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
changedPersistentKeys: changedKeys
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Unregisters an `ObjectObserver` from receiving notifications for changes to the receiver's `object`.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
- parameter observer: an `ObjectObserver` to unregister notifications to
|
||||
*/
|
||||
public func removeObserver<U: ObjectObserver where U.ObjectEntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to remove an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
let nilValue: AnyObject? = nil
|
||||
setAssociatedRetainedObject(nilValue, forKey: &self.willChangeObjectKey, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteObjectKey, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &self.didUpdateObjectKey, inObject: observer)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal convenience init(dataStack: DataStack, object: T) {
|
||||
|
||||
self.init(context: dataStack.mainContext, object: object)
|
||||
}
|
||||
|
||||
internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: T) {
|
||||
|
||||
self.init(context: unsafeTransaction.context, object: object)
|
||||
}
|
||||
|
||||
private init(context: NSManagedObjectContext, object: T) {
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
fetchRequest.entity = object.entity
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchRequest.sortDescriptors = []
|
||||
fetchRequest.includesPendingChanges = false
|
||||
fetchRequest.shouldRefreshRefetchedObjects = true
|
||||
|
||||
let fetchedResultsController = CoreStoreFetchedResultsController<T>(
|
||||
context: context,
|
||||
fetchRequest: fetchRequest,
|
||||
fetchClauses: [Where("SELF", isEqualTo: object.objectID)]
|
||||
)
|
||||
|
||||
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
|
||||
|
||||
self.fetchedResultsController = fetchedResultsController
|
||||
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
|
||||
|
||||
fetchedResultsControllerDelegate.handler = self
|
||||
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
|
||||
try! fetchedResultsController.performFetchFromSpecifiedStores()
|
||||
|
||||
self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [String: NSObject]) ?? [:]
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.fetchedResultsControllerDelegate.fetchedResultsController = nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let fetchedResultsController: CoreStoreFetchedResultsController<T>
|
||||
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
|
||||
private var lastCommittedAttributes = [String: NSObject]()
|
||||
|
||||
private var willChangeObjectKey: Void?
|
||||
private var didDeleteObjectKey: Void?
|
||||
private var didUpdateObjectKey: Void?
|
||||
|
||||
private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor<T>) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
guard let strongSelf = self else {
|
||||
|
||||
return
|
||||
}
|
||||
callback(monitor: strongSelf)
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
|
||||
private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor<T>, object: T) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
guard let strongSelf = self,
|
||||
let userInfo = note.userInfo,
|
||||
let object = userInfo[UserInfoKeyObject] as? T else {
|
||||
|
||||
return
|
||||
}
|
||||
callback(monitor: strongSelf, object: object)
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectMonitor: Equatable
|
||||
|
||||
@available(OSX, unavailable)
|
||||
public func ==<T: NSManagedObject>(lhs: ObjectMonitor<T>, rhs: ObjectMonitor<T>) -> Bool {
|
||||
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
@available(OSX, unavailable)
|
||||
extension ObjectMonitor: Equatable { }
|
||||
|
||||
|
||||
// MARK: - ObjectMonitor: FetchedResultsControllerHandler
|
||||
|
||||
@available(OSX, unavailable)
|
||||
extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: FetchedResultsControllerHandler
|
||||
|
||||
internal func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorWillChangeObjectNotification,
|
||||
object: self
|
||||
)
|
||||
}
|
||||
|
||||
internal func controllerDidChangeContent(controller: NSFetchedResultsController) { }
|
||||
|
||||
internal func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
switch type {
|
||||
|
||||
case .Delete:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorDidDeleteObjectNotification,
|
||||
object: self,
|
||||
userInfo: [UserInfoKeyObject: anObject]
|
||||
)
|
||||
|
||||
case .Update:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorDidUpdateObjectNotification,
|
||||
object: self,
|
||||
userInfo: [UserInfoKeyObject: anObject]
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
internal func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) { }
|
||||
|
||||
internal func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? {
|
||||
|
||||
return sectionName
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private let ObjectMonitorWillChangeObjectNotification = "ObjectMonitorWillChangeObjectNotification"
|
||||
private let ObjectMonitorDidDeleteObjectNotification = "ObjectMonitorDidDeleteObjectNotification"
|
||||
private let ObjectMonitorDidUpdateObjectNotification = "ObjectMonitorDidUpdateObjectNotification"
|
||||
|
||||
private let UserInfoKeyObject = "UserInfoKeyObject"
|
||||
|
||||
@@ -1,256 +0,0 @@
|
||||
//
|
||||
// AsynchronousDataTransaction.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: - AsynchronousDataTransaction
|
||||
|
||||
/**
|
||||
The `AsynchronousDataTransaction` provides an interface for `NSManagedObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.beginAsynchronous(_:)`, or from `CoreStore.beginAsynchronous(_:)`.
|
||||
*/
|
||||
public final class AsynchronousDataTransaction: BaseDataTransaction {
|
||||
|
||||
/**
|
||||
Saves the transaction changes. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
|
||||
*/
|
||||
public func commit(completion: (result: SaveResult) -> Void = { _ in }) {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to commit a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to commit a \(typeName(self)) more than once."
|
||||
)
|
||||
|
||||
self.isCommitted = true
|
||||
let group = GCDGroup()
|
||||
group.enter()
|
||||
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
|
||||
|
||||
self.result = result
|
||||
completion(result: result)
|
||||
group.leave()
|
||||
}
|
||||
group.wait()
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
*/
|
||||
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to begin a child transaction from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return SynchronousDataTransaction(
|
||||
mainContext: self.context,
|
||||
queue: self.childTransactionQueue,
|
||||
closure: closure).performAndWait()
|
||||
}
|
||||
|
||||
|
||||
// MARK: BaseDataTransaction
|
||||
|
||||
/**
|
||||
Creates a new `NSManagedObject` with the specified entity type.
|
||||
|
||||
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
|
||||
- returns: a new `NSManagedObject` instance of the specified entity type.
|
||||
*/
|
||||
public override func create<T: NSManagedObject>(into: Into<T>) -> T {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.create(into)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter object: the `NSManagedObject` type to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public override func edit<T: NSManagedObject>(object: T?) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter objectID: the `NSManagedObjectID` for the object to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public override func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(into, objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter object: the `NSManagedObject` type to be deleted
|
||||
*/
|
||||
public override func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter object1: the `NSManagedObject` type to be deleted
|
||||
- parameter object2: another `NSManagedObject` type to be deleted
|
||||
- parameter objects: other `NSManagedObject`s type to be deleted
|
||||
*/
|
||||
public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(([object1, object2] + objects).flatMap { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter objects: the `NSManagedObject`s type to be deleted
|
||||
*/
|
||||
public override func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
@available(*, deprecated=1.3.4, message="Resetting the context is inherently unsafe. This method will be removed in the near future. Use `beginUnsafe()` to create transactions with `undo` support.")
|
||||
public func rollback() {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to rollback an already committed \(typeName(self))."
|
||||
)
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to rollback a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
self.context.reset()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: AsynchronousDataTransaction) -> Void) {
|
||||
|
||||
self.closure = closure
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
|
||||
}
|
||||
|
||||
internal func perform() {
|
||||
|
||||
self.transactionQueue.async {
|
||||
|
||||
self.closure(transaction: self)
|
||||
if !self.isCommitted && self.hasChanges {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal func performAndWait() -> SaveResult? {
|
||||
|
||||
self.transactionQueue.sync {
|
||||
|
||||
self.closure(transaction: self)
|
||||
|
||||
if !self.isCommitted && self.hasChanges {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
|
||||
)
|
||||
}
|
||||
}
|
||||
return self.result
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let closure: (transaction: AsynchronousDataTransaction) -> Void
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
//
|
||||
// CoreStore+Transaction.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: - CoreStore
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
*/
|
||||
public static func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) {
|
||||
|
||||
self.defaultStack.beginAsynchronous(closure)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
*/
|
||||
public static func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
|
||||
|
||||
return self.defaultStack.beginSynchronous(closure)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms. An unsafe transaction object should typically be only used from the main queue.
|
||||
|
||||
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public static func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction {
|
||||
|
||||
return self.defaultStack.beginUnsafe(supportsUndo: supportsUndo)
|
||||
}
|
||||
|
||||
/**
|
||||
Refreshes all registered objects `NSManagedObject`s in the `DataStack`.
|
||||
*/
|
||||
public static func refreshAllObjectsAsFaults() {
|
||||
|
||||
self.defaultStack.refreshAllObjectsAsFaults()
|
||||
}
|
||||
|
||||
@available(*, deprecated=1.3.1, renamed="beginUnsafe")
|
||||
@warn_unused_result
|
||||
public static func beginDetached() -> UnsafeDataTransaction {
|
||||
|
||||
return self.beginUnsafe()
|
||||
}
|
||||
}
|
||||
@@ -1,102 +0,0 @@
|
||||
//
|
||||
// DataStack+Transaction.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 {
|
||||
|
||||
/**
|
||||
Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
*/
|
||||
public func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) {
|
||||
|
||||
AsynchronousDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: self.childTransactionQueue,
|
||||
closure: closure).perform()
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made.
|
||||
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
*/
|
||||
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
|
||||
|
||||
return SynchronousDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: self.childTransactionQueue,
|
||||
closure: closure).performAndWait()
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a non-contiguous transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction {
|
||||
|
||||
return UnsafeDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: .createSerial(
|
||||
"com.coreStore.dataStack.unsafeTransactionQueue",
|
||||
targetQueue: .UserInitiated
|
||||
),
|
||||
supportsUndo: supportsUndo
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Refreshes all registered objects `NSManagedObject`s in the `DataStack`.
|
||||
*/
|
||||
public func refreshAllObjectsAsFaults() {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to refresh entities outside their designated queue."
|
||||
)
|
||||
|
||||
self.mainContext.refreshAllObjectsAsFaults()
|
||||
}
|
||||
|
||||
@available(*, deprecated=1.3.1, renamed="beginUnsafe")
|
||||
@warn_unused_result
|
||||
public func beginDetached() -> UnsafeDataTransaction {
|
||||
|
||||
return self.beginUnsafe()
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
//
|
||||
// SaveResult.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - SaveResult
|
||||
|
||||
/**
|
||||
The `SaveResult` indicates the result of a `commit(...)` for a transaction.
|
||||
The `SaveResult` can be treated as a boolean:
|
||||
```
|
||||
CoreStore.beginAsynchronous { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
if result {
|
||||
// succeeded
|
||||
}
|
||||
else {
|
||||
// failed
|
||||
}
|
||||
}
|
||||
```
|
||||
or as an `enum`, where the resulting associated object can also be inspected:
|
||||
```
|
||||
CoreStore.beginAsynchronous { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
switch result {
|
||||
case .Success(let hasChanges):
|
||||
// hasChanges indicates if there were changes or not
|
||||
case .Failure(let error):
|
||||
// error is the NSError instance for the failure
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public enum SaveResult {
|
||||
|
||||
/**
|
||||
`SaveResult.Success` indicates that the `commit()` for the transaction succeeded, either because the save succeeded or because there were no changes to save. The associated value `hasChanges` indicates if there were saved changes or not.
|
||||
*/
|
||||
case Success(hasChanges: Bool)
|
||||
|
||||
/**
|
||||
`SaveResult.Failure` indicates that the `commit()` for the transaction failed. The associated object for this value is the related `NSError` instance.
|
||||
*/
|
||||
case Failure(NSError)
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(hasChanges: Bool) {
|
||||
|
||||
self = .Success(hasChanges: hasChanges)
|
||||
}
|
||||
|
||||
internal init(_ error: NSError) {
|
||||
|
||||
self = .Failure(error)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode) {
|
||||
|
||||
self.init(errorCode, userInfo: nil)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SaveResult: BooleanType
|
||||
|
||||
extension SaveResult: BooleanType {
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .Success: return true
|
||||
case .Failure: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,242 +0,0 @@
|
||||
//
|
||||
// SynchronousDataTransaction.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: - SynchronousDataTransaction
|
||||
|
||||
/**
|
||||
The `SynchronousDataTransaction` provides an interface for `NSManagedObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.beginSynchronous(_:)`, or from `CoreStore.beginSynchronous(_:)`.
|
||||
*/
|
||||
public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
|
||||
/**
|
||||
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- returns: a `SaveResult` containing the success or failure information
|
||||
*/
|
||||
public func commitAndWait() -> SaveResult {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to commit a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to commit a \(typeName(self)) more than once."
|
||||
)
|
||||
|
||||
self.isCommitted = true
|
||||
|
||||
let result = self.context.saveSynchronously()
|
||||
self.result = result
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
*/
|
||||
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to begin a child transaction from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return SynchronousDataTransaction(
|
||||
mainContext: self.context,
|
||||
queue: self.childTransactionQueue,
|
||||
closure: closure).performAndWait()
|
||||
}
|
||||
|
||||
|
||||
// MARK: BaseDataTransaction
|
||||
|
||||
/**
|
||||
Creates a new `NSManagedObject` with the specified entity type.
|
||||
|
||||
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
|
||||
- returns: a new `NSManagedObject` instance of the specified entity type.
|
||||
*/
|
||||
public override func create<T: NSManagedObject>(into: Into<T>) -> T {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.create(into)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter object: the `NSManagedObject` type to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public override func edit<T: NSManagedObject>(object: T?) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter objectID: the `NSManagedObjectID` for the object to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public override func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to update an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.edit(into, objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter object: the `NSManagedObject` type to be deleted
|
||||
*/
|
||||
public override func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter object1: the `NSManagedObject` to be deleted
|
||||
- parameter object2: another `NSManagedObject` to be deleted
|
||||
- parameter objects: other `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(([object1, object2] + objects).flatMap { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter objects: the `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public override func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
@available(*, deprecated=1.3.4, message="Resetting the context is inherently unsafe. This method will be removed in the near future. Use `beginUnsafe()` to create transactions with `undo` support.")
|
||||
public func rollback() {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to rollback an already committed \(typeName(self))."
|
||||
)
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to rollback a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
self.context.reset()
|
||||
}
|
||||
|
||||
@available(*, deprecated=1.5.2, renamed="commitAndWait")
|
||||
public func commit() {
|
||||
|
||||
self.commitAndWait()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: SynchronousDataTransaction) -> Void) {
|
||||
|
||||
self.closure = closure
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
|
||||
}
|
||||
|
||||
internal func performAndWait() -> SaveResult? {
|
||||
|
||||
self.transactionQueue.sync {
|
||||
|
||||
self.closure(transaction: self)
|
||||
|
||||
if !self.isCommitted && self.hasChanges {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
|
||||
)
|
||||
}
|
||||
}
|
||||
return self.result
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let closure: (transaction: SynchronousDataTransaction) -> Void
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
//
|
||||
// UnsafeDataTransaction.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
|
||||
|
||||
|
||||
@available(*, deprecated=1.3.1, renamed="UnsafeDataTransaction")
|
||||
public typealias DetachedDataTransaction = UnsafeDataTransaction
|
||||
|
||||
|
||||
// MARK: - UnsafeDataTransaction
|
||||
|
||||
/**
|
||||
The `UnsafeDataTransaction` provides an interface for non-contiguous `NSManagedObject` creates, updates, and deletes. This is useful for making temporary changes, such as partially filled forms. An unsafe transaction object should typically be only used from the main queue.
|
||||
*/
|
||||
public final class UnsafeDataTransaction: BaseDataTransaction {
|
||||
|
||||
/**
|
||||
Saves the transaction changes asynchronously. For a `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
|
||||
|
||||
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
|
||||
*/
|
||||
public func commit(completion: (result: SaveResult) -> Void) {
|
||||
|
||||
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
|
||||
|
||||
self.result = result
|
||||
completion(result: result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Saves the transaction changes and waits for completion synchronously. For a `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
|
||||
|
||||
- returns: a `SaveResult` containing the success or failure information
|
||||
*/
|
||||
public func commitAndWait() -> SaveResult {
|
||||
|
||||
let result = self.context.saveSynchronously()
|
||||
self.result = result
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
Rolls back the transaction.
|
||||
*/
|
||||
public func rollback() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.supportsUndo,
|
||||
"Attempted to rollback a \(typeName(self)) with Undo support disabled."
|
||||
)
|
||||
self.context.rollback()
|
||||
}
|
||||
|
||||
/**
|
||||
Undo's the last change made to the transaction.
|
||||
*/
|
||||
public func undo() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.supportsUndo,
|
||||
"Attempted to undo a \(typeName(self)) with Undo support disabled."
|
||||
)
|
||||
self.context.undo()
|
||||
}
|
||||
|
||||
/**
|
||||
Redo's the last undone change to the transaction.
|
||||
*/
|
||||
public func redo() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.supportsUndo,
|
||||
"Attempted to redo a \(typeName(self)) with Undo support disabled."
|
||||
)
|
||||
self.context.redo()
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
|
||||
|
||||
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
|
||||
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
@warn_unused_result
|
||||
public func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction {
|
||||
|
||||
return UnsafeDataTransaction(
|
||||
mainContext: self.context,
|
||||
queue: self.transactionQueue,
|
||||
supportsUndo: supportsUndo
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObjectContext` for this unsafe transaction. Use only for cases where external frameworks need an `NSManagedObjectContext` instance to work with.
|
||||
|
||||
Note that it is the developer's responsibility to ensure the following:
|
||||
- that the `UnsafeDataTransaction` that owns this context should be strongly referenced and prevented from being deallocated during the context's lifetime
|
||||
- that all saves will be done either through the `UnsafeDataTransaction`'s `commit(...)` method, or by calling `save()` manually on the context, its parent, and all other ancestor contexts if there are any.
|
||||
*/
|
||||
public var internalContext: NSManagedObjectContext {
|
||||
|
||||
return self.context
|
||||
}
|
||||
|
||||
@available(*, deprecated=1.3.1, renamed="beginUnsafe")
|
||||
@warn_unused_result
|
||||
public func beginDetached() -> UnsafeDataTransaction {
|
||||
|
||||
return self.beginUnsafe()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, supportsUndo: Bool) {
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue, supportsUndo: supportsUndo, bypassesQueueing: true)
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
//
|
||||
// CoreStore+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
#if USE_FRAMEWORKS
|
||||
import GCDKit
|
||||
#endif
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
/**
|
||||
Returns the `defaultStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
|
||||
*/
|
||||
public static var modelVersion: String {
|
||||
|
||||
return self.defaultStack.modelVersion
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the entity name-to-class type mapping from the `defaultStack`'s model.
|
||||
*/
|
||||
public static var entityTypesByName: [String: NSManagedObject.Type] {
|
||||
|
||||
return self.defaultStack.entityTypesByName
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `defaultStack`'s model.
|
||||
*/
|
||||
public static func entityDescriptionForType(type: NSManagedObject.Type) -> NSEntityDescription? {
|
||||
|
||||
return self.defaultStack.entityDescriptionForType(type)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds an in-memory store to the `defaultStack`.
|
||||
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addInMemoryStoreAndWait(configuration: configuration)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the `defaultStack` an SQLite store from the given SQLite file name.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the `defaultStack` an SQLite store from the given SQLite file URL.
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS).
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,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 progressive migrations. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
public required init(modelName: String = applicationName, bundle: NSBundle = NSBundle.mainBundle(), migrationChain: MigrationChain = nil) {
|
||||
|
||||
CoreStore.assert(
|
||||
migrationChain.valid,
|
||||
"Invalid migration chain passed to the \(typeName(DataStack)). Check that the model versions' order is correct and that no repetitions or ambiguities exist."
|
||||
)
|
||||
|
||||
let model = NSManagedObjectModel.fromBundle(
|
||||
bundle,
|
||||
modelName: modelName,
|
||||
modelVersionHints: migrationChain.leafVersions
|
||||
)
|
||||
|
||||
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
|
||||
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
|
||||
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
|
||||
self.model = model
|
||||
self.migrationChain = migrationChain
|
||||
|
||||
self.rootSavingContext.parentStack = self
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `DataStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
|
||||
*/
|
||||
public var modelVersion: String {
|
||||
|
||||
return self.model.currentModelVersion!
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the entity name-to-class type mapping from the `DataStack`'s model.
|
||||
*/
|
||||
public var entityTypesByName: [String: NSManagedObject.Type] {
|
||||
|
||||
return self.model.entityTypesMapping()
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass.
|
||||
*/
|
||||
public func entityDescriptionForType(type: NSManagedObject.Type) -> NSEntityDescription? {
|
||||
|
||||
return NSEntityDescription.entityForName(
|
||||
self.model.entityNameForClass(type),
|
||||
inManagedObjectContext: self.mainContext
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObjectID` for the specified object URI if it exists in the persistent store.
|
||||
*/
|
||||
public func objectIDForURIRepresentation(url: NSURL) -> NSManagedObjectID? {
|
||||
|
||||
return self.coordinator.managedObjectIDForURIRepresentation(url)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds an in-memory store to the stack.
|
||||
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
|
||||
var store: NSPersistentStore?
|
||||
var storeError: NSError?
|
||||
coordinator.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,7 +12,6 @@
|
||||
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADD1AFDC71700F90881 /* Palette.swift */; };
|
||||
B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */; };
|
||||
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C111B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel */; };
|
||||
B5125C141B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C131B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel */; };
|
||||
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977D81B120B80003D50A5 /* ObserversViewController.swift */; };
|
||||
B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */; };
|
||||
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52977DE1B120F83003D50A5 /* MapKit.framework */; };
|
||||
@@ -36,8 +35,6 @@
|
||||
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; };
|
||||
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */; };
|
||||
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */; };
|
||||
B5E89ACD1C52929C003B04A9 /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9241C202429008147CD /* GCDKit.framework */; };
|
||||
B5E89ACE1C52929C003B04A9 /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9241C202429008147CD /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; };
|
||||
B5E89AD11C5292A2003B04A9 /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25841B36E23C0000406B /* OrganismV1.swift */; };
|
||||
@@ -54,7 +51,6 @@
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
B5E89ACE1C52929C003B04A9 /* GCDKit.framework in Embed Frameworks */,
|
||||
B5E89AD11C5292A2003B04A9 /* CoreStore.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
@@ -94,7 +90,6 @@
|
||||
B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = "<group>"; };
|
||||
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5BDC9211C202429008147CD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5BDC9241C202429008147CD /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OrganismTableViewCell.swift; path = "CoreStoreDemo/MIgrations Demo/OrganismTableViewCell.swift"; sourceTree = SOURCE_ROOT; };
|
||||
B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = "<group>"; };
|
||||
B5EE25841B36E23C0000406B /* OrganismV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV1.swift; sourceTree = "<group>"; };
|
||||
@@ -110,7 +105,6 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B5E89ACD1C52929C003B04A9 /* GCDKit.framework in Frameworks */,
|
||||
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */,
|
||||
B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */,
|
||||
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */,
|
||||
@@ -146,7 +140,6 @@
|
||||
children = (
|
||||
B52977E01B120F8A003D50A5 /* CoreLocation.framework */,
|
||||
B5BDC9211C202429008147CD /* CoreStore.framework */,
|
||||
B5BDC9241C202429008147CD /* GCDKit.framework */,
|
||||
B52977DE1B120F83003D50A5 /* MapKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
@@ -272,11 +265,12 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0700;
|
||||
LastUpgradeCheck = 0800;
|
||||
ORGANIZATIONNAME = "John Rommel Estropia";
|
||||
TargetAttributes = {
|
||||
B54AAD481AF4D26E00848AE0 = {
|
||||
CreatedOnToolsVersion = 6.3;
|
||||
LastSwiftMigration = 0800;
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -336,7 +330,6 @@
|
||||
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */,
|
||||
B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */,
|
||||
B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */,
|
||||
B5125C141B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel in Sources */,
|
||||
B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */,
|
||||
B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */,
|
||||
B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */,
|
||||
@@ -383,8 +376,10 @@
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
@@ -428,8 +423,10 @@
|
||||
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||
CLANG_WARN_EMPTY_BODY = YES;
|
||||
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||
CLANG_WARN_INT_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
@@ -459,8 +456,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 = 3.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -471,8 +469,10 @@
|
||||
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_OPTIMIZATION_LEVEL = "-Owholemodule";
|
||||
SWIFT_VERSION = 3.0;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -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>
|
||||
@@ -18,9 +18,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
|
||||
application.statusBarStyle = .LightContent
|
||||
application.statusBarStyle = .lightContent
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10112" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10083"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
@@ -11,14 +12,17 @@
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="CoreStoreIcon" translatesAutoresizingMaskIntoConstraints="NO" id="q8C-V6-gXH">
|
||||
<rect key="frame" x="155" y="83" width="170" height="170"/>
|
||||
</imageView>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="273" width="440" height="57.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
@@ -26,16 +30,21 @@
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstItem="q8C-V6-gXH" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" multiplier="0.7" id="QW6-8Y-w15"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="q8C-V6-gXH" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="fRb-1V-9iD"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="top" secondItem="q8C-V6-gXH" secondAttribute="bottom" constant="20" id="s63-MP-ush"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="CoreStoreIcon" width="170" height="170"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
53
CoreStoreDemo/CoreStoreDemo/Base.lproj/LaunchScreen.xib.orig
Normal file
@@ -0,0 +1,53 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<<<<<<< Updated upstream
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
|
||||
=======
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
|
||||
>>>>>>> Stashed changes
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<<<<<<< Updated upstream
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
=======
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439.5" width="441" height="20.5"/>
|
||||
>>>>>>> Stashed changes
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="133" width="441" height="57.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
@@ -1,39 +1,28 @@
|
||||
<?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">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Ni8-QF-XHB">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="8124.4"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
|
||||
<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"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<customFonts key="customFonts">
|
||||
<mutableArray key="AppleSDGothicNeo.ttc">
|
||||
<array key="AppleSDGothicNeo.ttc">
|
||||
<string>AppleSDGothicNeo-Regular</string>
|
||||
</mutableArray>
|
||||
<mutableArray key="HelveticaNeue.ttc">
|
||||
<string>HelveticaNeue-Bold</string>
|
||||
<string>HelveticaNeue-Bold</string>
|
||||
</array>
|
||||
<array key="HelveticaNeue.ttc">
|
||||
<string>HelveticaNeue</string>
|
||||
<string>HelveticaNeue</string>
|
||||
</mutableArray>
|
||||
<mutableArray key="HelveticaNeueLights.ttc">
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Bold</string>
|
||||
</array>
|
||||
<array key="HelveticaNeueLights.ttc">
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Thin</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
<string>HelveticaNeue-Light</string>
|
||||
</mutableArray>
|
||||
</array>
|
||||
</customFonts>
|
||||
<scenes>
|
||||
<!--SNS Accounts-->
|
||||
@@ -41,34 +30,34 @@
|
||||
<objects>
|
||||
<tableViewController id="AW4-lY-JNk" customClass="StackSetupDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="S3A-lm-AuA">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<view key="tableHeaderView" contentMode="scaleToFill" id="yud-WH-MPa">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="150"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="150"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="sns" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="czc-nd-es9">
|
||||
<rect key="frame" x="20" y="20" width="560" height="26.5"/>
|
||||
<rect key="frame" x="20" y="20" width="335" height="26.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="22"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="name" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1eh-91-O2N">
|
||||
<rect key="frame" x="20" y="70" width="41" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="17"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="friends" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p2y-0T-hQs">
|
||||
<rect key="frame" x="539.5" y="72" width="40.5" height="17"/>
|
||||
<rect key="frame" x="314.5" y="72" width="40.5" height="17"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="14"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" 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"/>
|
||||
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="1eh-91-O2N" firstAttribute="top" secondItem="czc-nd-es9" secondAttribute="bottom" constant="23.5" id="F6q-Jt-glI"/>
|
||||
<constraint firstItem="p2y-0T-hQs" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="1eh-91-O2N" secondAttribute="trailing" constant="20" id="GVS-6o-rmj"/>
|
||||
@@ -82,24 +71,24 @@
|
||||
</view>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="8b8-lM-Krq" detailTextLabel="hR1-Zb-BOk" style="IBUITableViewCellStyleValue1" id="dMt-nx-EO5">
|
||||
<rect key="frame" x="0.0" y="236" width="600" height="44"/>
|
||||
<rect key="frame" x="0.0" y="172" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="dMt-nx-EO5" id="gdK-VV-zNb">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8b8-lM-Krq">
|
||||
<rect key="frame" x="15" y="13" width="28.5" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hR1-Zb-BOk">
|
||||
<rect key="frame" x="533" y="13" width="52" height="19"/>
|
||||
<rect key="frame" x="308" y="13" width="52" height="19"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
|
||||
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -131,23 +120,23 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="t9A-zf-Iew"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="75P-2m-6cr">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="WUc-3Y-Quw">
|
||||
<rect key="frame" x="0.0" y="324" width="600" height="276"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<rect key="frame" x="0.0" y="324" width="375" height="343"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="OrganismTableViewCell" id="WVb-th-o8c" customClass="OrganismTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="22" width="600" height="44"/>
|
||||
<rect key="frame" x="0.0" y="22" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="WVb-th-o8c" id="JBq-Ml-a9p">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OQf-Bd-Zze">
|
||||
<rect key="frame" x="520" y="8" width="72" height="27.5"/>
|
||||
<rect key="frame" x="295" y="8" width="72" height="27.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<inset key="contentEdgeInsets" minX="7" minY="0.0" maxX="7" maxY="0.0"/>
|
||||
<state key="normal" title="mutate!"/>
|
||||
@@ -156,9 +145,9 @@
|
||||
</connections>
|
||||
</button>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VZk-6K-4ut">
|
||||
<rect key="frame" x="15" y="8" width="495" height="27.5"/>
|
||||
<rect key="frame" x="15" y="8" width="270" height="27.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="17"/>
|
||||
<color key="textColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -184,42 +173,42 @@
|
||||
</connections>
|
||||
</tableView>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XKA-Ub-c2X">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="260"/>
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="260"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="i7U-bW-juB" customClass="UIButton">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="260"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="260"/>
|
||||
<subviews>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Organism" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zxy-nY-P44">
|
||||
<rect key="frame" x="20" y="90.5" width="560" height="26.5"/>
|
||||
<rect key="frame" x="20" y="90.5" width="335" height="26.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="22"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="attributes" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6Ac-xl-ldZ">
|
||||
<rect key="frame" x="20" y="131.5" width="560" height="20.5"/>
|
||||
<rect key="frame" x="20" y="131.5" width="335" height="20.5"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="17"/>
|
||||
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="rAZ-eJ-sxy">
|
||||
<rect key="frame" x="20" y="20" width="560" height="29"/>
|
||||
<rect key="frame" x="20" y="20" width="335" height="29"/>
|
||||
<segments>
|
||||
<segment title="First"/>
|
||||
<segment title="Second"/>
|
||||
<segment title=""/>
|
||||
</segments>
|
||||
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<action selector="segmentedControlValueChanged:" destination="iVv-Vc-nCL" eventType="valueChanged" id="RwG-kW-RPg"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
<progressView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="869-wx-Odb">
|
||||
<rect key="frame" x="20" y="68" width="560" height="2"/>
|
||||
<color key="progressTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="trackTintColor" white="1" alpha="0.20000000000000001" colorSpace="calibratedWhite"/>
|
||||
<rect key="frame" x="20" y="68" width="335" height="2"/>
|
||||
<color key="progressTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="trackTintColor" red="1" green="1" blue="1" alpha="0.20000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</progressView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="zxy-nY-P44" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="1u1-Tq-hRn"/>
|
||||
<constraint firstItem="6Ac-xl-ldZ" firstAttribute="top" secondItem="zxy-nY-P44" secondAttribute="bottom" constant="14.5" id="39B-9l-O3g"/>
|
||||
@@ -237,7 +226,7 @@
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="260" id="90R-Mf-iAB"/>
|
||||
<constraint firstItem="i7U-bW-juB" firstAttribute="leading" secondItem="XKA-Ub-c2X" secondAttribute="leading" id="e9h-f4-c37"/>
|
||||
@@ -246,7 +235,7 @@
|
||||
</constraints>
|
||||
</view>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="trailing" secondItem="XKA-Ub-c2X" secondAttribute="trailing" id="U0P-51-KT9"/>
|
||||
<constraint firstAttribute="trailing" secondItem="WUc-3Y-Quw" secondAttribute="trailing" id="i7I-7B-97K"/>
|
||||
@@ -276,31 +265,31 @@
|
||||
<objects>
|
||||
<tableViewController id="t0d-B0-B7U" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="50" sectionHeaderHeight="10" sectionFooterHeight="10" id="uHB-Yr-ujV">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<sections>
|
||||
<tableViewSection id="wIP-Hn-YfF">
|
||||
<cells>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Q3n-Df-v1t" detailTextLabel="Hbn-cf-Y7m" style="IBUITableViewCellStyleSubtitle" id="AXm-KE-45G">
|
||||
<rect key="frame" x="0.0" y="99" width="600" height="50"/>
|
||||
<rect key="frame" x="0.0" y="35" width="375" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="AXm-KE-45G" id="9te-Wx-hkf">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Accounts" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Q3n-Df-v1t">
|
||||
<rect key="frame" x="15" y="6" width="82" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901960784313" green="0.24313725490196078" blue="0.31372549019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Setting up multiple persistent store configurations" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hbn-cf-Y7m">
|
||||
<rect key="frame" x="15" y="30" width="264" height="13.5"/>
|
||||
<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"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -310,24 +299,24 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="vpt-cT-gMo" detailTextLabel="ou9-TZ-8bf" style="IBUITableViewCellStyleSubtitle" id="fsb-zw-8Ii">
|
||||
<rect key="frame" x="0.0" y="149" width="600" height="50"/>
|
||||
<rect key="frame" x="0.0" y="85" width="375" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="fsb-zw-8Ii" id="Upm-AO-Fw3">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Colors" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="vpt-cT-gMo">
|
||||
<rect key="frame" x="15" y="6" width="56" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observing list changes and single object changes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ou9-TZ-8bf">
|
||||
<rect key="frame" x="15" y="30" width="261.5" height="13.5"/>
|
||||
<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"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -337,24 +326,24 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="UbU-Kd-yrY" detailTextLabel="uP1-Jc-o9v" style="IBUITableViewCellStyleSubtitle" id="ekW-PJ-mbo">
|
||||
<rect key="frame" x="0.0" y="199" width="600" height="50"/>
|
||||
<rect key="frame" x="0.0" y="135" width="375" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ekW-PJ-mbo" id="CYq-mg-PVS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Placemarks" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="UbU-Kd-yrY">
|
||||
<rect key="frame" x="15" y="6" width="100.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Making changes with transactions" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="uP1-Jc-o9v">
|
||||
<rect key="frame" x="15" y="30" width="179.5" height="13.5"/>
|
||||
<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"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -364,24 +353,24 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="C8Y-0y-lEG" detailTextLabel="jZw-qE-0ws" style="IBUITableViewCellStyleSubtitle" id="ph1-8z-C1m">
|
||||
<rect key="frame" x="0.0" y="249" width="600" height="50"/>
|
||||
<rect key="frame" x="0.0" y="185" width="375" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ph1-8z-C1m" id="nNz-rd-ksg">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Time Zones" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="C8Y-0y-lEG">
|
||||
<rect key="frame" x="15" y="6" width="101.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws">
|
||||
<rect key="frame" x="15" y="30" width="169" height="13.5"/>
|
||||
<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"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -391,24 +380,24 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="ZfY-Aq-Ykq" detailTextLabel="QzD-9b-k1j" style="IBUITableViewCellStyleSubtitle" id="wyK-rk-3tI">
|
||||
<rect key="frame" x="0.0" y="299" width="600" height="50"/>
|
||||
<rect key="frame" x="0.0" y="235" width="375" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="wyK-rk-3tI" id="fLd-KK-QcW">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZfY-Aq-Ykq">
|
||||
<rect key="frame" x="15" y="6" width="61" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j">
|
||||
<rect key="frame" x="15" y="30" width="159.5" height="13.5"/>
|
||||
<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"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -418,24 +407,24 @@
|
||||
</connections>
|
||||
</tableViewCell>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="hSG-mG-YBw" detailTextLabel="X9P-TQ-LYh" style="IBUITableViewCellStyleSubtitle" id="xTM-Cf-0if">
|
||||
<rect key="frame" x="0.0" y="349" width="600" height="50"/>
|
||||
<rect key="frame" x="0.0" y="285" width="375" height="50"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="xTM-Cf-0if" id="DfO-BW-krd">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="49.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Evolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hSG-mG-YBw">
|
||||
<rect key="frame" x="15" y="6" width="78.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Migrating and de-migrating stores" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="X9P-TQ-LYh">
|
||||
<rect key="frame" x="15" y="30" width="179.5" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -467,69 +456,69 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="aI4-O3-OCi"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="w8K-eN-RvU">
|
||||
<rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
|
||||
<rect key="frame" x="20" y="69.5" width="552" height="36.5"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<rect key="frame" x="16" y="89.5" width="343" height="50"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="TIX-qi-B34"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfe-Yq-3Xa">
|
||||
<rect key="frame" x="20" y="116" width="552" height="18"/>
|
||||
<rect key="frame" x="16" y="149.5" width="343" height="18"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="4h9-ha-EzR"/>
|
||||
</constraints>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hue" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sgg-Md-Nf3">
|
||||
<rect key="frame" x="20" y="154" width="74" height="18"/>
|
||||
<rect key="frame" x="16" y="187.5" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Saturation" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rry-vh-bRK">
|
||||
<rect key="frame" x="20" y="192" width="74" height="18"/>
|
||||
<rect key="frame" x="16" y="225.5" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Brightness" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vTa-ly-eyO">
|
||||
<rect key="frame" x="20" y="230" width="74" height="18"/>
|
||||
<rect key="frame" x="16" y="263.5" width="74" height="18"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="15"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="359" translatesAutoresizingMaskIntoConstraints="NO" id="YQ6-fq-3Wb">
|
||||
<rect key="frame" x="102" y="148" width="472" height="31"/>
|
||||
<rect key="frame" x="98" y="181.5" width="263" height="31"/>
|
||||
<connections>
|
||||
<action selector="hueSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="9Hy-3h-llE"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="xXz-78-tAd">
|
||||
<rect key="frame" x="102" y="186" width="472" height="31"/>
|
||||
<rect key="frame" x="98" y="219.5" width="263" height="31"/>
|
||||
<connections>
|
||||
<action selector="saturationSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="qtU-ua-ZTc"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="hpy-2d-eOP">
|
||||
<rect key="frame" x="102" y="224" width="472" height="31"/>
|
||||
<rect key="frame" x="98" y="257.5" width="263" height="31"/>
|
||||
<connections>
|
||||
<action selector="brightnessSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="F09-EP-2iD"/>
|
||||
</connections>
|
||||
</slider>
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p4O-tf-dgt">
|
||||
<rect key="frame" x="20" y="49" width="552" height="20.5"/>
|
||||
<rect key="frame" x="16" y="69" width="343" height="20.5"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="p4O-tf-dgt" firstAttribute="trailing" secondItem="w8K-eN-RvU" secondAttribute="trailingMargin" id="022-ll-1s1"/>
|
||||
<constraint firstItem="vTa-ly-eyO" firstAttribute="top" secondItem="rry-vh-bRK" secondAttribute="bottom" constant="20" id="1hd-Ti-CnT"/>
|
||||
@@ -591,23 +580,23 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="LNL-mj-D7l"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="6x3-vn-Egt">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf">
|
||||
<rect key="frame" x="4" y="64" width="592" height="268"/>
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="301.5"/>
|
||||
<connections>
|
||||
<segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp">
|
||||
<rect key="frame" x="4" y="332" width="592" height="268"/>
|
||||
<rect key="frame" x="0.0" y="365.5" width="375" height="301.5"/>
|
||||
<connections>
|
||||
<segue destination="sll-yo-mBc" kind="embed" id="AAl-HS-dq2"/>
|
||||
</connections>
|
||||
</containerView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="6So-f3-4Gp" firstAttribute="top" secondItem="L5f-tW-lXf" secondAttribute="bottom" id="3m8-tj-Nd4"/>
|
||||
<constraint firstAttribute="trailingMargin" secondItem="6So-f3-4Gp" secondAttribute="trailing" constant="-16" id="4L8-wZ-F59"/>
|
||||
@@ -636,11 +625,11 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="00L-5k-Eno">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="tintColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="24"/>
|
||||
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</textAttributes>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -657,19 +646,19 @@
|
||||
<objects>
|
||||
<tableViewController id="3AE-ED-0oj" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="DAz-BE-6Ca">
|
||||
<rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="G3X-70-BCD" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="66" width="592" height="44"/>
|
||||
<rect key="frame" x="0.0" y="22" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="G3X-70-BCD" id="aT8-nz-i5l">
|
||||
<rect key="frame" x="0.0" y="0.0" width="559" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uQX-PI-UWF">
|
||||
<rect key="frame" x="8" y="8" width="27" height="27"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="uQX-PI-UWF" secondAttribute="height" multiplier="1:1" id="9qA-iN-Neb"/>
|
||||
</constraints>
|
||||
@@ -677,7 +666,7 @@
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HJC-5w-lIN">
|
||||
<rect key="frame" x="45" y="8" width="34.5" height="27"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="14"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -716,20 +705,20 @@
|
||||
<objects>
|
||||
<tableViewController id="lCE-i6-UCT" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="Zba-8M-Zd7">
|
||||
<rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="zSO-3e-OVq" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
|
||||
<rect key="frame" x="0.0" y="66" width="592" height="44"/>
|
||||
<rect key="frame" x="0.0" y="22" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="zSO-3e-OVq" id="cHA-by-n4b">
|
||||
<rect key="frame" x="0.0" y="0.0" width="559" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5uq-Yi-XwH">
|
||||
<rect key="frame" x="8" y="8" width="27" height="27"/>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" secondItem="5uq-Yi-XwH" secondAttribute="height" multiplier="1:1" id="oOe-HC-VyN"/>
|
||||
</constraints>
|
||||
@@ -737,7 +726,7 @@
|
||||
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Zyu-PC-WmO">
|
||||
<rect key="frame" x="45" y="8" width="34.5" height="27"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="14"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -780,11 +769,11 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="6XA-6M-yvZ">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" red="0.74117647060000003" green="0.76470588240000004" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="tintColor" red="0.68773996829986572" green="0.71417498588562012" blue="0.73246318101882935" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.74117647060000003" green="0.76470588240000004" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.68773996829986572" green="0.71417498588562012" blue="0.73246318101882935" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</textAttributes>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -805,19 +794,19 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="RZg-hi-T8O"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="k4s-iL-Krh">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="536"/>
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="536"/>
|
||||
<mapView verifyAmbiguity="ignoreSizes" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="558"/>
|
||||
<connections>
|
||||
<outlet property="delegate" destination="jPl-fH-NlD" id="Sjn-YC-haS"/>
|
||||
</connections>
|
||||
</mapView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="RZg-hi-T8O" firstAttribute="top" secondItem="V2U-0R-Ts0" secondAttribute="bottom" id="GcS-Jz-Wcm"/>
|
||||
<constraint firstItem="RZg-hi-T8O" firstAttribute="top" secondItem="V2U-0R-Ts0" secondAttribute="bottom" id="N9r-9J-68d"/>
|
||||
<constraint firstItem="V2U-0R-Ts0" firstAttribute="top" secondItem="k4s-iL-Krh" secondAttribute="top" id="S5Z-Da-V6J"/>
|
||||
<constraint firstAttribute="trailing" secondItem="V2U-0R-Ts0" secondAttribute="trailing" id="YPc-RK-5ib"/>
|
||||
<constraint firstItem="V2U-0R-Ts0" firstAttribute="leading" secondItem="k4s-iL-Krh" secondAttribute="leading" id="hk5-Rz-FyU"/>
|
||||
@@ -831,7 +820,7 @@
|
||||
</viewController>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="YnG-TD-zxQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
|
||||
</objects>
|
||||
<point key="canvasLocation" x="3694" y="2020"/>
|
||||
<point key="canvasLocation" x="3693.5" y="2019.5"/>
|
||||
</scene>
|
||||
<!--Logger-->
|
||||
<scene sceneID="n7W-0g-bbY">
|
||||
@@ -842,30 +831,30 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="Cwn-Jd-4Lr"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="n9M-Je-Dj0">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" directionalLockEnabled="YES" alwaysBounceVertical="YES" indicatorStyle="white" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TpK-gX-CTN">
|
||||
<rect key="frame" x="0.0" y="142" width="600" height="458"/>
|
||||
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.94509803921568625" green="0.7686274509803922" blue="0.058823529411764705" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<rect key="frame" x="0.0" y="142" width="375" height="525"/>
|
||||
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.92706394195556641" green="0.72759377956390381" blue="0.064024783670902252" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<fontDescription key="fontDescription" name="AppleSDGothicNeo-Regular" family="Apple SD Gothic Neo" pointSize="15"/>
|
||||
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
|
||||
</textView>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bordered" momentary="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4iq-B4-k0p">
|
||||
<rect key="frame" x="20" y="94" width="560" height="29"/>
|
||||
<rect key="frame" x="20" y="94" width="335" height="29"/>
|
||||
<segments>
|
||||
<segment title="Log"/>
|
||||
<segment title="Error"/>
|
||||
<segment title="Assert"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="tintColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<action selector="segmentedControlValueChanged:" destination="5yy-0N-QDU" eventType="valueChanged" id="2pp-Vt-2Os"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="4iq-B4-k0p" firstAttribute="leading" secondItem="n9M-Je-Dj0" secondAttribute="leading" constant="20" id="6SM-eN-NxP"/>
|
||||
<constraint firstItem="TpK-gX-CTN" firstAttribute="leading" secondItem="n9M-Je-Dj0" secondAttribute="leading" id="AAw-IG-taz"/>
|
||||
@@ -901,11 +890,11 @@
|
||||
<navigationBar key="navigationBar" contentMode="scaleToFill" id="wJo-mp-1pS">
|
||||
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<color key="tintColor" red="0.74117647058823533" green="0.76470588235294112" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.17254901960784313" green="0.24313725490196078" blue="0.31372549019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="tintColor" red="0.68773996829986572" green="0.71417498588562012" blue="0.73246318101882935" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="barTintColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<textAttributes key="titleTextAttributes">
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.74117647060000003" green="0.76470588240000004" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.68773996829986572" green="0.71417498588562012" blue="0.73246318101882935" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
</textAttributes>
|
||||
</navigationBar>
|
||||
<nil name="viewControllers"/>
|
||||
@@ -926,30 +915,30 @@
|
||||
<viewControllerLayoutGuide type="bottom" id="DlN-cN-JXd"/>
|
||||
</layoutGuides>
|
||||
<view key="view" contentMode="scaleToFill" id="eC3-ql-d2o">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="WGY-kX-mAx">
|
||||
<rect key="frame" x="0.0" y="64" width="600" height="536"/>
|
||||
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
|
||||
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<view key="tableHeaderView" contentMode="scaleToFill" id="iaH-1W-Sbo">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="80"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<subviews>
|
||||
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bordered" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="YVj-dA-fyV">
|
||||
<rect key="frame" x="20" y="26" width="560" height="29"/>
|
||||
<rect key="frame" x="20" y="26" width="335" height="29"/>
|
||||
<segments>
|
||||
<segment title="Fetch"/>
|
||||
<segment title="Query"/>
|
||||
</segments>
|
||||
<color key="tintColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="tintColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<connections>
|
||||
<action selector="segmentedControlValueChanged:" destination="qbj-MK-nIY" eventType="valueChanged" id="Wok-dl-uq7"/>
|
||||
</connections>
|
||||
</segmentedControl>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="0.80000000000000004" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="YVj-dA-fyV" firstAttribute="leading" secondItem="iaH-1W-Sbo" secondAttribute="leading" constant="20" id="1dX-t7-dyR"/>
|
||||
<constraint firstAttribute="centerY" secondItem="YVj-dA-fyV" secondAttribute="centerY" id="HXU-Z7-jfu"/>
|
||||
@@ -958,17 +947,17 @@
|
||||
</view>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="6db-P0-6Ms" style="IBUITableViewCellStyleDefault" id="vUr-WV-qur">
|
||||
<rect key="frame" x="0.0" y="102" width="600" height="44"/>
|
||||
<rect key="frame" x="0.0" y="102" width="375" height="44"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="vUr-WV-qur" id="Vr0-hE-cn9">
|
||||
<rect key="frame" x="0.0" y="0.0" width="567" height="43.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6db-P0-6Ms">
|
||||
<rect key="frame" x="15" y="0.0" width="550" height="43.5"/>
|
||||
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="18"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -981,7 +970,7 @@
|
||||
</connections>
|
||||
</tableView>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
|
||||
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<constraints>
|
||||
<constraint firstItem="WGY-kX-mAx" firstAttribute="top" secondItem="dgu-PC-LiB" secondAttribute="bottom" id="0pS-cU-ibk"/>
|
||||
<constraint firstAttribute="trailing" secondItem="WGY-kX-mAx" secondAttribute="trailing" id="NeY-g5-CaB"/>
|
||||
@@ -1006,30 +995,30 @@
|
||||
<objects>
|
||||
<tableViewController id="tIs-pN-OgO" customClass="FetchingResultsViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="60" sectionHeaderHeight="36" sectionFooterHeight="22" id="tVl-tT-UDk">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="RgX-yK-1L2" detailTextLabel="QZ4-A2-x4h" style="IBUITableViewCellStyleSubtitle" id="uBt-Iy-nWP">
|
||||
<rect key="frame" x="0.0" y="100" width="600" height="60"/>
|
||||
<rect key="frame" x="0.0" y="36" width="375" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="uBt-Iy-nWP" id="6SD-ur-9zp">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="59.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="59.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RgX-yK-1L2">
|
||||
<rect key="frame" x="15" y="11" width="48.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QZ4-A2-x4h">
|
||||
<rect key="frame" x="15" y="35" width="31" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
@@ -1052,30 +1041,30 @@
|
||||
<objects>
|
||||
<tableViewController id="o9D-Xm-13g" customClass="QueryingResultsViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
|
||||
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="60" sectionHeaderHeight="36" sectionFooterHeight="22" id="bMh-zR-xwu">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="separatorColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<prototypes>
|
||||
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="Syt-QJ-KXg" detailTextLabel="yHS-dP-IKS" style="IBUITableViewCellStyleSubtitle" id="q7Q-aF-Ftl">
|
||||
<rect key="frame" x="0.0" y="100" width="600" height="60"/>
|
||||
<rect key="frame" x="0.0" y="36" width="375" height="60"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="q7Q-aF-Ftl" id="fc3-eg-yes">
|
||||
<rect key="frame" x="0.0" y="0.0" width="600" height="59.5"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="375" height="59.5"/>
|
||||
<autoresizingMask key="autoresizingMask"/>
|
||||
<subviews>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Syt-QJ-KXg">
|
||||
<rect key="frame" x="15" y="11" width="48.5" height="24"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="yHS-dP-IKS">
|
||||
<rect key="frame" x="15" y="35" width="31" height="13.5"/>
|
||||
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="11"/>
|
||||
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
|
||||
@@ -1,36 +1,25 @@
|
||||
<?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">
|
||||
<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"/>
|
||||
<attribute name="hue" optional="YES" attributeType="Integer 32" defaultValueString="0.0" syncable="YES"/>
|
||||
<attribute name="saturation" optional="YES" attributeType="Float" defaultValueString="0.0" syncable="YES"/>
|
||||
<userInfo/>
|
||||
</entity>
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="12141" systemVersion="16F73" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Place" representedClassName="CoreStoreDemo.Place" syncable="YES">
|
||||
<attribute name="latitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
|
||||
<attribute name="longitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
|
||||
<attribute name="latitude" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="longitude" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="TimeZone" representedClassName="CoreStoreDemo.TimeZone" syncable="YES">
|
||||
<attribute name="abbreviation" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="daylightSavingTimeOffset" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
|
||||
<attribute name="hasDaylightSavingTime" optional="YES" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="daylightSavingTimeOffset" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="hasDaylightSavingTime" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="secondsFromGMT" optional="YES" attributeType="Integer 32" defaultValueString="0.0" syncable="YES"/>
|
||||
<attribute name="secondsFromGMT" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<configuration name="FetchingAndQueryingDemo">
|
||||
<memberEntity name="TimeZone"/>
|
||||
</configuration>
|
||||
<configuration name="ObservingDemo">
|
||||
<memberEntity name="Palette"/>
|
||||
</configuration>
|
||||
<configuration name="TransactionsDemo">
|
||||
<memberEntity name="Place"/>
|
||||
</configuration>
|
||||
<elements>
|
||||
<element name="Palette" positionX="261" positionY="189" width="128" height="105"/>
|
||||
<element name="Place" positionX="261" positionY="225" width="128" height="105"/>
|
||||
<element name="TimeZone" positionX="297" positionY="270" width="128" height="120"/>
|
||||
</elements>
|
||||
|
||||
@@ -15,31 +15,31 @@ 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
|
||||
|
||||
transaction.deleteAll(From(TimeZone))
|
||||
|
||||
for name in NSTimeZone.knownTimeZoneNames() {
|
||||
_ = try? dataStack.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
let rawTimeZone = NSTimeZone(name: name)!
|
||||
let cachedTimeZone = transaction.create(Into(TimeZone))
|
||||
transaction.deleteAll(From<TimeZone>())
|
||||
|
||||
cachedTimeZone.name = rawTimeZone.name
|
||||
cachedTimeZone.abbreviation = rawTimeZone.abbreviation ?? ""
|
||||
cachedTimeZone.secondsFromGMT = Int32(rawTimeZone.secondsFromGMT)
|
||||
cachedTimeZone.hasDaylightSavingTime = rawTimeZone.daylightSavingTime
|
||||
cachedTimeZone.daylightSavingTimeOffset = rawTimeZone.daylightSavingTimeOffset
|
||||
for name in NSTimeZone.knownTimeZoneNames {
|
||||
|
||||
let rawTimeZone = NSTimeZone(name: name)!
|
||||
let cachedTimeZone = transaction.create(Into<TimeZone>())
|
||||
|
||||
cachedTimeZone.name = rawTimeZone.name
|
||||
cachedTimeZone.abbreviation = rawTimeZone.abbreviation ?? ""
|
||||
cachedTimeZone.secondsFromGMT = Int32(rawTimeZone.secondsFromGMT)
|
||||
cachedTimeZone.hasDaylightSavingTime = rawTimeZone.isDaylightSavingTime
|
||||
cachedTimeZone.daylightSavingTimeOffset = rawTimeZone.daylightSavingTimeOffset
|
||||
}
|
||||
}
|
||||
|
||||
transaction.commit()
|
||||
}
|
||||
|
||||
)
|
||||
return dataStack
|
||||
}()
|
||||
}
|
||||
@@ -51,7 +51,7 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
override func viewDidAppear(animated: Bool) {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
@@ -65,27 +65,27 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
let alert = UIAlertController(
|
||||
title: "Fetch and Query Demo",
|
||||
message: "This demo shows how to execute fetches and queries.\n\nEach menu item executes and displays a preconfigured fetch/query.",
|
||||
preferredStyle: .Alert
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
self.presentViewController(alert, animated: true, completion: nil)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
|
||||
super.prepareForSegue(segue, sender: sender)
|
||||
super.prepare(for: segue, sender: sender)
|
||||
|
||||
if let indexPath = sender as? NSIndexPath {
|
||||
if let indexPath = sender as? IndexPath {
|
||||
|
||||
switch segue.destinationViewController {
|
||||
switch segue.destination {
|
||||
|
||||
case let controller as FetchingResultsViewController:
|
||||
let item = self.fetchingItems[indexPath.row]
|
||||
controller.setTimeZones(item.fetch(), title: item.title)
|
||||
controller.set(timeZones: item.fetch(), title: item.title)
|
||||
|
||||
case let controller as QueryingResultsViewController:
|
||||
let item = self.queryingItems[indexPath.row]
|
||||
controller.setValue(item.query(), title: item.title)
|
||||
controller.set(value: item.query(), title: item.title)
|
||||
|
||||
default:
|
||||
break
|
||||
@@ -96,13 +96,14 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
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:
|
||||
@@ -110,16 +111,16 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
}
|
||||
}
|
||||
|
||||
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell")!
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell")!
|
||||
|
||||
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:
|
||||
@@ -132,17 +133,17 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
switch self.segmentedControl?.selectedSegmentIndex {
|
||||
|
||||
case .Some(Section.Fetching.rawValue):
|
||||
self.performSegueWithIdentifier("FetchingResultsViewController", sender: indexPath)
|
||||
case Section.fetching.rawValue?:
|
||||
self.performSegue(withIdentifier: "FetchingResultsViewController", sender: indexPath)
|
||||
|
||||
case .Some(Section.Querying.rawValue):
|
||||
self.performSegueWithIdentifier("QueryingResultsViewController", sender: indexPath)
|
||||
case Section.querying.rawValue?:
|
||||
self.performSegue(withIdentifier: "QueryingResultsViewController", sender: indexPath)
|
||||
|
||||
default:
|
||||
break
|
||||
@@ -154,8 +155,8 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
|
||||
private enum Section: Int {
|
||||
|
||||
case Fetching
|
||||
case Querying
|
||||
case fetching
|
||||
case querying
|
||||
}
|
||||
|
||||
private let fetchingItems = [
|
||||
@@ -164,8 +165,8 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
OrderBy(.Ascending("name"))
|
||||
From<TimeZone>(),
|
||||
OrderBy(.ascending(#keyPath(TimeZone.name)))
|
||||
)!
|
||||
}
|
||||
),
|
||||
@@ -174,9 +175,9 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
Where("%K BEGINSWITH[c] %@", "name", "Asia"),
|
||||
OrderBy(.Ascending("secondsFromGMT"))
|
||||
From<TimeZone>(),
|
||||
Where("%K BEGINSWITH[c] %@", #keyPath(TimeZone.name), "Asia"),
|
||||
OrderBy(.ascending(#keyPath(TimeZone.secondsFromGMT)))
|
||||
)!
|
||||
}
|
||||
),
|
||||
@@ -185,10 +186,10 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
Where("%K BEGINSWITH[c] %@", "name", "America")
|
||||
|| Where("%K BEGINSWITH[c] %@", "name", "Europe"),
|
||||
OrderBy(.Ascending("secondsFromGMT"))
|
||||
From<TimeZone>(),
|
||||
Where("%K BEGINSWITH[c] %@", #keyPath(TimeZone.name), "America")
|
||||
|| Where("%K BEGINSWITH[c] %@", #keyPath(TimeZone.name), "Europe"),
|
||||
OrderBy(.ascending(#keyPath(TimeZone.secondsFromGMT)))
|
||||
)!
|
||||
}
|
||||
),
|
||||
@@ -197,9 +198,9 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
!Where("%K BEGINSWITH[c] %@", "name", "America"),
|
||||
OrderBy(.Ascending("secondsFromGMT"))
|
||||
From<TimeZone>(),
|
||||
!Where("%K BEGINSWITH[c] %@", #keyPath(TimeZone.name), "America"),
|
||||
OrderBy(.ascending(#keyPath(TimeZone.secondsFromGMT)))
|
||||
)!
|
||||
}
|
||||
),
|
||||
@@ -208,9 +209,9 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
From<TimeZone>(),
|
||||
Where("hasDaylightSavingTime", isEqualTo: true),
|
||||
OrderBy(.Ascending("name"))
|
||||
OrderBy(.ascending(#keyPath(TimeZone.name)))
|
||||
)!
|
||||
}
|
||||
)
|
||||
@@ -219,60 +220,60 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
private let queryingItems = [
|
||||
(
|
||||
title: "Number of Time Zones",
|
||||
query: { () -> AnyObject in
|
||||
query: { () -> Any in
|
||||
|
||||
return Static.timeZonesStack.queryValue(
|
||||
From(TimeZone),
|
||||
Select<NSNumber>(.Count("name"))
|
||||
From<TimeZone>(),
|
||||
Select<NSNumber>(.count(#keyPath(TimeZone.name)))
|
||||
)!
|
||||
}
|
||||
),
|
||||
(
|
||||
title: "Abbreviation For Tokyo's Time Zone",
|
||||
query: { () -> AnyObject in
|
||||
query: { () -> Any in
|
||||
|
||||
return Static.timeZonesStack.queryValue(
|
||||
From(TimeZone),
|
||||
Select<String>("abbreviation"),
|
||||
Where("%K ENDSWITH[c] %@", "name", "Tokyo")
|
||||
From<TimeZone>(),
|
||||
Select<String>(#keyPath(TimeZone.abbreviation)),
|
||||
Where("%K ENDSWITH[c] %@", #keyPath(TimeZone.name), "Tokyo")
|
||||
)!
|
||||
}
|
||||
),
|
||||
(
|
||||
title: "All Abbreviations",
|
||||
query: { () -> AnyObject in
|
||||
query: { () -> Any in
|
||||
|
||||
return Static.timeZonesStack.queryAttributes(
|
||||
From(TimeZone),
|
||||
Select<NSDictionary>("name", "abbreviation"),
|
||||
OrderBy(.Ascending("name"))
|
||||
From<TimeZone>(),
|
||||
Select<NSDictionary>(#keyPath(TimeZone.name), #keyPath(TimeZone.abbreviation)),
|
||||
OrderBy(.ascending(#keyPath(TimeZone.name)))
|
||||
)!
|
||||
}
|
||||
),
|
||||
(
|
||||
title: "Number of Countries per Time Zone",
|
||||
query: { () -> AnyObject in
|
||||
query: { () -> Any in
|
||||
|
||||
return Static.timeZonesStack.queryAttributes(
|
||||
From(TimeZone),
|
||||
Select<NSDictionary>(.Count("abbreviation"), "abbreviation"),
|
||||
GroupBy("abbreviation"),
|
||||
OrderBy(.Ascending("secondsFromGMT"), .Ascending("name"))
|
||||
From<TimeZone>(),
|
||||
Select<NSDictionary>(.count(#keyPath(TimeZone.abbreviation)), #keyPath(TimeZone.abbreviation)),
|
||||
GroupBy(#keyPath(TimeZone.abbreviation)),
|
||||
OrderBy(.ascending(#keyPath(TimeZone.secondsFromGMT)), .ascending(#keyPath(TimeZone.name)))
|
||||
)!
|
||||
}
|
||||
),
|
||||
(
|
||||
title: "Number of Countries with Summer Time",
|
||||
query: { () -> AnyObject in
|
||||
query: { () -> Any in
|
||||
|
||||
return Static.timeZonesStack.queryAttributes(
|
||||
From(TimeZone),
|
||||
From<TimeZone>(),
|
||||
Select<NSDictionary>(
|
||||
.Count("hasDaylightSavingTime", As: "numberOfCountries"),
|
||||
"hasDaylightSavingTime"
|
||||
.count(#keyPath(TimeZone.hasDaylightSavingTime), as: "numberOfCountries"),
|
||||
#keyPath(TimeZone.hasDaylightSavingTime)
|
||||
),
|
||||
GroupBy("hasDaylightSavingTime"),
|
||||
OrderBy(.Descending("hasDaylightSavingTime"))
|
||||
GroupBy(#keyPath(TimeZone.hasDaylightSavingTime)),
|
||||
OrderBy(.descending(#keyPath(TimeZone.hasDaylightSavingTime)))
|
||||
)!
|
||||
}
|
||||
)
|
||||
@@ -283,7 +284,7 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
@IBOutlet dynamic weak var segmentedControl: UISegmentedControl?
|
||||
@IBOutlet dynamic weak var tableView: UITableView?
|
||||
|
||||
@IBAction dynamic func segmentedControlValueChanged(sender: AnyObject?) {
|
||||
@IBAction dynamic func segmentedControlValueChanged(_ sender: AnyObject?) {
|
||||
|
||||
self.tableView?.reloadData()
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ class FetchingResultsViewController: UITableViewController {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
func setTimeZones(timeZones: [TimeZone]?, title: String) {
|
||||
func set(timeZones: [TimeZone]?, title: String) {
|
||||
|
||||
self.timeZones += timeZones ?? []
|
||||
self.sectionTitle = title
|
||||
@@ -36,14 +36,14 @@ class FetchingResultsViewController: UITableViewController {
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.timeZones.count
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath)
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
|
||||
|
||||
let timeZone = self.timeZones[indexPath.row]
|
||||
cell.textLabel?.text = timeZone.name
|
||||
@@ -55,7 +55,7 @@ class FetchingResultsViewController: UITableViewController {
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
return self.sectionTitle
|
||||
}
|
||||
|
||||
@@ -12,23 +12,23 @@ class QueryingResultsViewController: UITableViewController {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
func setValue(value: AnyObject?, title: String) {
|
||||
func set(value: Any?, title: String) {
|
||||
|
||||
switch value {
|
||||
|
||||
case .Some(let array as [AnyObject]):
|
||||
self.values = array.map { (item: AnyObject) -> (title: String, detail: String) in
|
||||
case (let array as [Any])?:
|
||||
self.values = array.map { (item: Any) -> (title: String, detail: String) in
|
||||
(
|
||||
title: item.description,
|
||||
detail: _stdlib_getDemangledTypeName(item)
|
||||
title: String(describing: item),
|
||||
detail: String(reflecting: type(of: item))
|
||||
)
|
||||
}
|
||||
|
||||
case .Some(let item):
|
||||
case let item?:
|
||||
self.values = [
|
||||
(
|
||||
title: item.description,
|
||||
detail: _stdlib_getDemangledTypeName(item)
|
||||
title: String(describing: item),
|
||||
detail: String(reflecting: type(of: item))
|
||||
)
|
||||
]
|
||||
|
||||
@@ -55,14 +55,14 @@ class QueryingResultsViewController: UITableViewController {
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.values.count
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath)
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
|
||||
|
||||
let value = self.values[indexPath.row]
|
||||
|
||||
@@ -75,7 +75,7 @@ class QueryingResultsViewController: UITableViewController {
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
return self.sectionTitle
|
||||
}
|
||||
|
||||
@@ -21,13 +21,33 @@
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@3x-1.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "car",
|
||||
"filename" : "Icon-60@3x.png",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
|
||||
|
After Width: | Height: | Size: 7.1 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 11 KiB |
|
After Width: | Height: | Size: 4.1 KiB |
|
After Width: | Height: | Size: 9.3 KiB |
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
21
CoreStoreDemo/CoreStoreDemo/Images.xcassets/CoreStoreIcon.imageset/Contents.json
vendored
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"filename" : "CoreStoreIcon@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
BIN
CoreStoreDemo/CoreStoreDemo/Images.xcassets/CoreStoreIcon.imageset/CoreStoreIcon@2x.png
vendored
Normal file
|
After Width: | Height: | Size: 28 KiB |
@@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>CoreStore</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
@@ -15,11 +17,11 @@
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0.1</string>
|
||||
<string>4.0.3</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>4</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
|
||||
@@ -10,21 +10,21 @@ import UIKit
|
||||
import CoreStore
|
||||
|
||||
|
||||
private struct Static {
|
||||
struct ColorsDemo {
|
||||
|
||||
enum Filter: String {
|
||||
|
||||
case All = "All Colors"
|
||||
case Light = "Light Colors"
|
||||
case Dark = "Dark Colors"
|
||||
case all = "All Colors"
|
||||
case light = "Light Colors"
|
||||
case dark = "Dark Colors"
|
||||
|
||||
func next() -> Filter {
|
||||
|
||||
switch self {
|
||||
|
||||
case All: return .Light
|
||||
case Light: return .Dark
|
||||
case Dark: return .All
|
||||
case .all: return .light
|
||||
case .light: return .dark
|
||||
case .dark: return .all
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,33 +32,51 @@ private struct Static {
|
||||
|
||||
switch self {
|
||||
|
||||
case .All: return Where(true)
|
||||
case .Light: return Where("brightness >= 0.9")
|
||||
case .Dark: return Where("brightness <= 0.4")
|
||||
case .all: return Where(true)
|
||||
case .light: return Palette.where({ $0.brightness >= 0.9 })
|
||||
case .dark: return Palette.where({ $0.brightness <= 0.4 })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static var filter = Filter.All {
|
||||
static var filter = Filter.all {
|
||||
|
||||
didSet {
|
||||
|
||||
self.palettes.refetch(self.filter.whereClause())
|
||||
self.palettes.refetch(
|
||||
self.filter.whereClause(),
|
||||
Palette.orderBy(ascending: { $0.hue })
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static let stack: DataStack = {
|
||||
|
||||
return DataStack(
|
||||
CoreStoreSchema(
|
||||
modelVersion: "ColorsDemo",
|
||||
entities: [
|
||||
Entity<Palette>("Palette"),
|
||||
],
|
||||
versionLock: [
|
||||
"Palette": [0x8c25aa53c7c90a28, 0xa243a34d25f1a3a7, 0x56565b6935b6055a, 0x4f988bb257bf274f]
|
||||
]
|
||||
)
|
||||
)
|
||||
}()
|
||||
|
||||
static let palettes: ListMonitor<Palette> = {
|
||||
|
||||
try! CoreStore.addSQLiteStoreAndWait(
|
||||
fileName: "ColorsDemo.sqlite",
|
||||
configuration: "ObservingDemo",
|
||||
resetStoreOnModelMismatch: true
|
||||
try! ColorsDemo.stack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "ColorsDemo.sqlite",
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
|
||||
return CoreStore.monitorSectionedList(
|
||||
From(Palette),
|
||||
SectionBy("colorName"),
|
||||
OrderBy(.Ascending("hue"))
|
||||
return ColorsDemo.stack.monitorSectionedList(
|
||||
From<Palette>(),
|
||||
SectionBy(Palette.keyPath({ $0.colorName })),
|
||||
Palette.orderBy(ascending: { $0.hue })
|
||||
)
|
||||
}()
|
||||
}
|
||||
@@ -72,7 +90,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
|
||||
deinit {
|
||||
|
||||
Static.palettes.removeObserver(self)
|
||||
ColorsDemo.palettes.removeObserver(self)
|
||||
}
|
||||
|
||||
|
||||
@@ -84,42 +102,42 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
|
||||
let navigationItem = self.navigationItem
|
||||
navigationItem.leftBarButtonItems = [
|
||||
self.editButtonItem(),
|
||||
self.editButtonItem,
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .Trash,
|
||||
barButtonSystemItem: .trash,
|
||||
target: self,
|
||||
action: "resetBarButtonItemTouched:"
|
||||
action: #selector(self.resetBarButtonItemTouched(_:))
|
||||
)
|
||||
]
|
||||
|
||||
let filterBarButton = UIBarButtonItem(
|
||||
title: Static.filter.rawValue,
|
||||
style: .Plain,
|
||||
title: ColorsDemo.filter.rawValue,
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: "filterBarButtonItemTouched:"
|
||||
action: #selector(self.filterBarButtonItemTouched(_:))
|
||||
)
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .Add,
|
||||
barButtonSystemItem: .add,
|
||||
target: self,
|
||||
action: "addBarButtonItemTouched:"
|
||||
action: #selector(self.addBarButtonItemTouched(_:))
|
||||
),
|
||||
filterBarButton
|
||||
]
|
||||
self.filterBarButton = filterBarButton
|
||||
|
||||
Static.palettes.addObserver(self)
|
||||
ColorsDemo.palettes.addObserver(self)
|
||||
|
||||
self.setTableEnabled(!Static.palettes.isPendingRefetch)
|
||||
self.setTable(enabled: !ColorsDemo.palettes.isPendingRefetch)
|
||||
}
|
||||
|
||||
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
|
||||
super.prepareForSegue(segue, sender: sender)
|
||||
super.prepare(for: segue, sender: sender)
|
||||
|
||||
switch (segue.identifier, segue.destinationViewController, sender) {
|
||||
switch (segue.identifier, segue.destination, 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:
|
||||
@@ -130,21 +148,21 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
return Static.palettes.numberOfSections()
|
||||
return ColorsDemo.palettes.numberOfSections()
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return Static.palettes.numberOfObjectsInSection(section)
|
||||
return ColorsDemo.palettes.numberOfObjectsInSection(section)
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("PaletteTableViewCell") as! PaletteTableViewCell
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
|
||||
|
||||
let palette = Static.palettes[indexPath]
|
||||
let palette = ColorsDemo.palettes[indexPath]
|
||||
cell.colorView?.backgroundColor = palette.color
|
||||
cell.label?.text = palette.colorText
|
||||
|
||||
@@ -154,103 +172,106 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
||||
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
self.performSegueWithIdentifier(
|
||||
"ObjectObserverDemoViewController",
|
||||
sender: Static.palettes[indexPath]
|
||||
self.performSegue(
|
||||
withIdentifier: "ObjectObserverDemoViewController",
|
||||
sender: ColorsDemo.palettes[indexPath]
|
||||
)
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
|
||||
|
||||
switch editingStyle {
|
||||
|
||||
case .Delete:
|
||||
let palette = Static.palettes[indexPath]
|
||||
CoreStore.beginAsynchronous{ (transaction) -> Void in
|
||||
|
||||
transaction.delete(palette)
|
||||
transaction.commit { (result) -> Void in }
|
||||
}
|
||||
case .delete:
|
||||
let palette = ColorsDemo.palettes[indexPath]
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
transaction.delete(palette)
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
return Static.palettes.sectionInfoAtIndex(section).name
|
||||
return ColorsDemo.palettes.sectionInfoAtIndex(section).name
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
func listMonitorWillChange(monitor: ListMonitor<Palette>) {
|
||||
func listMonitorWillChange(_ monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.tableView.beginUpdates()
|
||||
}
|
||||
|
||||
func listMonitorDidChange(monitor: ListMonitor<Palette>) {
|
||||
func listMonitorDidChange(_ monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
|
||||
func listMonitorWillRefetch(monitor: ListMonitor<Palette>) {
|
||||
func listMonitorWillRefetch(_ monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.setTableEnabled(false)
|
||||
self.setTable(enabled: false)
|
||||
}
|
||||
|
||||
func listMonitorDidRefetch(monitor: ListMonitor<Palette>) {
|
||||
func listMonitorDidRefetch(_ monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.filterBarButton?.title = Static.filter.rawValue
|
||||
self.filterBarButton?.title = ColorsDemo.filter.rawValue
|
||||
self.tableView.reloadData()
|
||||
self.setTableEnabled(true)
|
||||
self.setTable(enabled: true)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObjectObserver
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) {
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: IndexPath) {
|
||||
|
||||
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
|
||||
self.tableView.insertRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) {
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: IndexPath) {
|
||||
|
||||
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
|
||||
self.tableView.deleteRows(at: [indexPath], with: .automatic)
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) {
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: IndexPath) {
|
||||
|
||||
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? PaletteTableViewCell {
|
||||
if let cell = self.tableView.cellForRow(at: indexPath) as? PaletteTableViewCell {
|
||||
|
||||
let palette = Static.palettes[indexPath]
|
||||
let palette = ColorsDemo.palettes[indexPath]
|
||||
cell.colorView?.backgroundColor = palette.color
|
||||
cell.label?.text = palette.colorText
|
||||
}
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
|
||||
|
||||
self.tableView.deleteRowsAtIndexPaths([fromIndexPath], withRowAnimation: .Automatic)
|
||||
self.tableView.insertRowsAtIndexPaths([toIndexPath], withRowAnimation: .Automatic)
|
||||
self.tableView.deleteRows(at: [fromIndexPath], with: .automatic)
|
||||
self.tableView.insertRows(at: [toIndexPath], with: .automatic)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListSectionObserver
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
|
||||
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
|
||||
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
|
||||
}
|
||||
|
||||
|
||||
@@ -258,43 +279,46 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
|
||||
private var filterBarButton: UIBarButtonItem?
|
||||
|
||||
@IBAction private dynamic func resetBarButtonItemTouched(sender: AnyObject?) {
|
||||
@IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
transaction.deleteAll(From(Palette))
|
||||
transaction.commit()
|
||||
}
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
transaction.deleteAll(From<Palette>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction private dynamic func filterBarButtonItemTouched(sender: AnyObject?) {
|
||||
@IBAction private dynamic func filterBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
Static.filter = Static.filter.next()
|
||||
ColorsDemo.filter = ColorsDemo.filter.next()
|
||||
}
|
||||
|
||||
@IBAction private dynamic func addBarButtonItemTouched(sender: AnyObject?) {
|
||||
@IBAction private dynamic func addBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let palette = transaction.create(Into(Palette))
|
||||
palette.setInitialValues()
|
||||
|
||||
transaction.commit()
|
||||
}
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let palette = transaction.create(Into<Palette>())
|
||||
palette.setInitialValues(in: transaction)
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
private func setTableEnabled(enabled: Bool) {
|
||||
private func setTable(enabled: Bool) {
|
||||
|
||||
UIView.animateWithDuration(
|
||||
0.2,
|
||||
UIView.animate(
|
||||
withDuration: 0.2,
|
||||
delay: 0,
|
||||
options: .BeginFromCurrentState,
|
||||
options: .beginFromCurrentState,
|
||||
animations: { () -> Void in
|
||||
|
||||
if let tableView = self.tableView {
|
||||
|
||||
tableView.alpha = enabled ? 1.0 : 0.5
|
||||
tableView.userInteractionEnabled = enabled
|
||||
tableView.isUserInteractionEnabled = enabled
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
|
||||
@@ -29,7 +29,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
|
||||
if let palette = newValue {
|
||||
|
||||
self.monitor = CoreStore.monitorObject(palette)
|
||||
self.monitor = ColorsDemo.stack.monitorObject(palette)
|
||||
}
|
||||
else {
|
||||
|
||||
@@ -50,22 +50,22 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
||||
if let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue"))) {
|
||||
if let palette = ColorsDemo.stack.fetchOne(From<Palette>(), Palette.orderBy(ascending: { $0.hue })) {
|
||||
|
||||
self.monitor = CoreStore.monitorObject(palette)
|
||||
self.monitor = ColorsDemo.stack.monitorObject(palette)
|
||||
}
|
||||
else {
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let palette = transaction.create(Into(Palette))
|
||||
palette.setInitialValues()
|
||||
|
||||
transaction.commit()
|
||||
}
|
||||
_ = try? ColorsDemo.stack.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
let palette = transaction.create(Into<Palette>())
|
||||
palette.setInitialValues(in: transaction)
|
||||
}
|
||||
)
|
||||
|
||||
let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))!
|
||||
self.monitor = CoreStore.monitorObject(palette)
|
||||
let palette = ColorsDemo.stack.fetchOne(From<Palette>(), Palette.orderBy(ascending: { $0.hue }))!
|
||||
self.monitor = ColorsDemo.stack.monitorObject(palette)
|
||||
}
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
@@ -85,24 +85,24 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
|
||||
// MARK: ObjectObserver
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<Palette>, didUpdateObject object: Palette, changedPersistentKeys: Set<KeyPath>) {
|
||||
func objectMonitor(_ monitor: ObjectMonitor<Palette>, didUpdateObject object: Palette, changedPersistentKeys: Set<RawKeyPath>) {
|
||||
|
||||
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
|
||||
}
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<Palette>, didDeleteObject object: Palette) {
|
||||
func objectMonitor(_ monitor: ObjectMonitor<Palette>, didDeleteObject object: Palette) {
|
||||
|
||||
self.navigationItem.rightBarButtonItem?.enabled = false
|
||||
self.navigationItem.rightBarButtonItem?.isEnabled = false
|
||||
|
||||
self.colorNameLabel?.alpha = 0.3
|
||||
self.colorView?.alpha = 0.3
|
||||
|
||||
self.hsbLabel?.text = "Deleted"
|
||||
self.hsbLabel?.textColor = UIColor.redColor()
|
||||
self.hsbLabel?.textColor = UIColor.red
|
||||
|
||||
self.hueSlider?.enabled = false
|
||||
self.saturationSlider?.enabled = false
|
||||
self.brightnessSlider?.enabled = false
|
||||
self.hueSlider?.isEnabled = false
|
||||
self.saturationSlider?.isEnabled = false
|
||||
self.brightnessSlider?.isEnabled = false
|
||||
}
|
||||
|
||||
|
||||
@@ -118,57 +118,65 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
@IBOutlet weak var saturationSlider: UISlider?
|
||||
@IBOutlet weak var brightnessSlider: UISlider?
|
||||
|
||||
@IBAction dynamic func hueSliderValueDidChange(sender: AnyObject?) {
|
||||
@IBAction dynamic func hueSliderValueDidChange(_ sender: AnyObject?) {
|
||||
|
||||
let hue = self.hueSlider?.value ?? 0
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
if let palette = transaction.edit(self?.monitor?.object) {
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { [weak self] (transaction) in
|
||||
|
||||
palette.hue = Int32(hue)
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
if let palette = transaction.edit(self?.monitor?.object) {
|
||||
|
||||
palette.hue .= Int(hue)
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction dynamic func saturationSliderValueDidChange(sender: AnyObject?) {
|
||||
@IBAction dynamic func saturationSliderValueDidChange(_ sender: AnyObject?) {
|
||||
|
||||
let saturation = self.saturationSlider?.value ?? 0
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
if let palette = transaction.edit(self?.monitor?.object) {
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { [weak self] (transaction) in
|
||||
|
||||
palette.saturation = saturation
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
if let palette = transaction.edit(self?.monitor?.object) {
|
||||
|
||||
palette.saturation .= saturation
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction dynamic func brightnessSliderValueDidChange(sender: AnyObject?) {
|
||||
@IBAction dynamic func brightnessSliderValueDidChange(_ sender: AnyObject?) {
|
||||
|
||||
let brightness = self.brightnessSlider?.value ?? 0
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
if let palette = transaction.edit(self?.monitor?.object) {
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { [weak self] (transaction) in
|
||||
|
||||
palette.brightness = brightness
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
if let palette = transaction.edit(self?.monitor?.object) {
|
||||
|
||||
palette.brightness .= brightness
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction dynamic func deleteBarButtonTapped(sender: AnyObject?) {
|
||||
@IBAction dynamic func deleteBarButtonTapped(_ sender: AnyObject?) {
|
||||
|
||||
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
|
||||
|
||||
transaction.delete(self?.monitor?.object)
|
||||
transaction.commit()
|
||||
}
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { [weak self] (transaction) in
|
||||
|
||||
transaction.delete(self?.monitor?.object)
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
func reloadPaletteInfo(palette: Palette, changedKeys: Set<String>?) {
|
||||
func reloadPaletteInfo(_ palette: Palette, changedKeys: Set<String>?) {
|
||||
|
||||
self.colorNameLabel?.text = palette.colorName
|
||||
self.colorNameLabel?.text = palette.colorName.value
|
||||
|
||||
let color = palette.color
|
||||
self.colorNameLabel?.textColor = color
|
||||
@@ -176,17 +184,17 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
|
||||
self.hsbLabel?.text = palette.colorText
|
||||
|
||||
if changedKeys == nil || changedKeys?.contains("hue") == true {
|
||||
if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.hue }) == true {
|
||||
|
||||
self.hueSlider?.value = Float(palette.hue)
|
||||
self.hueSlider?.value = Float(palette.hue.value)
|
||||
}
|
||||
if changedKeys == nil || changedKeys?.contains("saturation") == true {
|
||||
if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.saturation }) == true {
|
||||
|
||||
self.saturationSlider?.value = palette.saturation
|
||||
self.saturationSlider?.value = palette.saturation.value
|
||||
}
|
||||
if changedKeys == nil || changedKeys?.contains("brightness") == true {
|
||||
if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.brightness }) == true {
|
||||
|
||||
self.brightnessSlider?.value = palette.brightness
|
||||
self.brightnessSlider?.value = palette.brightness.value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,16 +15,16 @@ class ObserversViewController: UIViewController {
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
override func viewDidAppear(animated: Bool) {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Observers Demo",
|
||||
message: "This demo shows how to observe changes to a list of objects. The top and bottom view controllers both observe a single shared \"ListMonitor\" instance.\n\nTap on a row to see how to observe changes made to a single object using a \"ObjectMonitor\".",
|
||||
preferredStyle: .Alert
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
self.presentViewController(alert, animated: true, completion: nil)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,64 +14,65 @@ import CoreStore
|
||||
|
||||
// MARK: - Palette
|
||||
|
||||
class Palette: NSManagedObject {
|
||||
|
||||
@NSManaged var hue: Int32
|
||||
@NSManaged var saturation: Float
|
||||
@NSManaged var brightness: Float
|
||||
final class Palette: CoreStoreObject {
|
||||
|
||||
@objc dynamic var colorName: String {
|
||||
let hue = Value.Required<Int>("hue", initial: 0)
|
||||
let saturation = Value.Required<Float>("saturation", initial: 0)
|
||||
let brightness = Value.Required<Float>("brightness", initial: 0)
|
||||
|
||||
let colorName = Value.Optional<String>(
|
||||
"colorName",
|
||||
isTransient: true,
|
||||
customGetter: Palette.getColorName
|
||||
)
|
||||
|
||||
private static func getColorName(_ partialObject: PartialObject<Palette>) -> String? {
|
||||
|
||||
get {
|
||||
if let colorName = partialObject.primitiveValue(for: { $0.colorName }) {
|
||||
|
||||
let KVCKey = "colorName"
|
||||
if let colorName = self.accessValueForKVCKey(KVCKey) as? String {
|
||||
|
||||
return colorName
|
||||
}
|
||||
|
||||
let colorName: String
|
||||
switch self.hue % 360 {
|
||||
|
||||
case 0 ..< 20: colorName = "Lower Reds"
|
||||
case 20 ..< 57: colorName = "Oranges and Browns"
|
||||
case 57 ..< 90: colorName = "Yellow-Greens"
|
||||
case 90 ..< 159: colorName = "Greens"
|
||||
case 159 ..< 197: colorName = "Blue-Greens"
|
||||
case 197 ..< 241: colorName = "Blues"
|
||||
case 241 ..< 297: colorName = "Violets"
|
||||
case 297 ..< 331: colorName = "Magentas"
|
||||
default: colorName = "Upper Reds"
|
||||
}
|
||||
|
||||
self.setPrimitiveValue(colorName, forKey: KVCKey)
|
||||
return colorName
|
||||
}
|
||||
set {
|
||||
|
||||
let colorName: String
|
||||
switch partialObject.value(for: { $0.hue }) % 360 {
|
||||
|
||||
self.setValue(newValue, forKVCKey: "colorName")
|
||||
case 0 ..< 20: colorName = "Lower Reds"
|
||||
case 20 ..< 57: colorName = "Oranges and Browns"
|
||||
case 57 ..< 90: colorName = "Yellow-Greens"
|
||||
case 90 ..< 159: colorName = "Greens"
|
||||
case 159 ..< 197: colorName = "Blue-Greens"
|
||||
case 197 ..< 241: colorName = "Blues"
|
||||
case 241 ..< 297: colorName = "Violets"
|
||||
case 297 ..< 331: colorName = "Magentas"
|
||||
default: colorName = "Upper Reds"
|
||||
}
|
||||
|
||||
partialObject.setPrimitiveValue(colorName, for: { $0.colorName })
|
||||
return colorName
|
||||
}
|
||||
}
|
||||
|
||||
extension Palette {
|
||||
|
||||
var color: UIColor {
|
||||
|
||||
return UIColor(
|
||||
hue: CGFloat(self.hue) / 360.0,
|
||||
saturation: CGFloat(self.saturation),
|
||||
brightness: CGFloat(self.brightness),
|
||||
hue: CGFloat(self.hue.value) / 360.0,
|
||||
saturation: CGFloat(self.saturation.value),
|
||||
brightness: CGFloat(self.brightness.value),
|
||||
alpha: 1.0
|
||||
)
|
||||
}
|
||||
|
||||
var colorText: String {
|
||||
|
||||
return "H: \(self.hue)˚, S: \(round(self.saturation * 100.0))%, B: \(round(self.brightness * 100.0))%"
|
||||
return "H: \(self.hue.value)˚, S: \(round(self.saturation.value * 100.0))%, B: \(round(self.brightness.value * 100.0))%"
|
||||
}
|
||||
|
||||
func setInitialValues() {
|
||||
func setInitialValues(in transaction: BaseDataTransaction) {
|
||||
|
||||
self.hue = Int32(arc4random_uniform(360))
|
||||
self.saturation = 1.0
|
||||
self.brightness = Float(arc4random_uniform(70) + 30) / 100.0
|
||||
self.hue .= Int(arc4random_uniform(360))
|
||||
self.saturation .= Float(1.0)
|
||||
self.brightness .= Float(arc4random_uniform(70) + 30) / 100.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - CustomLoggerViewController
|
||||
@@ -30,96 +29,91 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
try! self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite")
|
||||
try! self.dataStack.addStorageAndWait(SQLiteStore(fileName: "emptyStore.sqlite"))
|
||||
CoreStore.logger = self
|
||||
}
|
||||
|
||||
override func viewDidAppear(animated: Bool) {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Logger Demo",
|
||||
message: "This demo shows how to plug-in any logging framework to CoreStore.\n\nThe view controller implements CoreStoreLogger and appends all logs to the text view.",
|
||||
preferredStyle: .Alert
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
self.presentViewController(alert, animated: true, completion: nil)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: CoreStoreLogger
|
||||
|
||||
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
func log(level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
|
||||
let levelString: String
|
||||
switch level {
|
||||
|
||||
case .Trace: levelString = "Trace"
|
||||
case .Notice: levelString = "Notice"
|
||||
case .Warning: levelString = "Warning"
|
||||
case .Fatal: levelString = "Fatal"
|
||||
case .trace: levelString = "Trace"
|
||||
case .notice: levelString = "Notice"
|
||||
case .warning: levelString = "Warning"
|
||||
case .fatal: levelString = "Fatal"
|
||||
}
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
|
||||
self?.textView?.insertText("\((String(describing: fileName) as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
func log(error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
|
||||
self?.textView?.insertText("\((String(describing: fileName) as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
|
||||
}
|
||||
}
|
||||
|
||||
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
func assert(_ condition: @autoclosure () -> Bool, message: @autoclosure () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
if condition() {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
GCDQueue.Main.async { [weak self] in
|
||||
let messageString = message()
|
||||
DispatchQueue.main.async { [weak self] in
|
||||
|
||||
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
|
||||
self?.textView?.insertText("\((String(describing: fileName) 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
|
||||
|
||||
@IBOutlet dynamic weak var textView: UITextView?
|
||||
@IBOutlet dynamic weak var segmentedControl: UISegmentedControl?
|
||||
|
||||
@IBAction dynamic func segmentedControlValueChanged(sender: AnyObject?) {
|
||||
@IBAction dynamic func segmentedControlValueChanged(_ sender: AnyObject?) {
|
||||
|
||||
switch self.segmentedControl?.selectedSegmentIndex {
|
||||
|
||||
case .Some(0):
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
transaction.create(Into(Palette))
|
||||
}
|
||||
case 0?:
|
||||
let request = NSFetchRequest<NSFetchRequestResult>()
|
||||
Where(true).applyToFetchRequest(request)
|
||||
Where(false).applyToFetchRequest(request)
|
||||
|
||||
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):
|
||||
self.dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
case 2?:
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
|
||||
transaction.commit()
|
||||
transaction.commit()
|
||||
_ = self.dataStack.fetchOne(From<Place>())
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -12,7 +12,7 @@ import CoreStore
|
||||
|
||||
// MARK: - MigrationsDemoViewController
|
||||
|
||||
class MigrationsDemoViewController: UIViewController {
|
||||
class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
@@ -22,50 +22,49 @@ class MigrationsDemoViewController: UIViewController {
|
||||
|
||||
if let segmentedControl = self.segmentedControl {
|
||||
|
||||
for (index, model) in self.models.enumerate() {
|
||||
for (index, model) in self.models.enumerated() {
|
||||
|
||||
segmentedControl.setTitle(
|
||||
model.label,
|
||||
forSegmentAtIndex: index
|
||||
forSegmentAt: index
|
||||
)
|
||||
}
|
||||
}
|
||||
self.setDataStack(nil, model: nil, scrollToSelection: false)
|
||||
self.set(dataStack: nil, model: nil, scrollToSelection: false)
|
||||
}
|
||||
|
||||
override func viewDidAppear(animated: Bool) {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Migrations Demo",
|
||||
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
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
self.presentViewController(alert, animated: true, completion: nil)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
|
||||
|
||||
let modelMetadata = withExtendedLifetime(DataStack(modelName: "MigrationDemo")) {
|
||||
let modelMetadata = withExtendedLifetime(DataStack(xcodeModelName: "MigrationDemo")) {
|
||||
(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.schemaHistory.currentModelVersion == storeVersion {
|
||||
|
||||
if model.version == storeVersion {
|
||||
|
||||
return model
|
||||
}
|
||||
return model
|
||||
}
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
return models.first!
|
||||
}
|
||||
@@ -73,32 +72,121 @@ class MigrationsDemoViewController: UIViewController {
|
||||
self.selectModelVersion(modelMetadata)
|
||||
}
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
func listMonitorWillChange(_ monitor: ListMonitor<NSManagedObject>) { }
|
||||
|
||||
func listMonitorDidChange(_ monitor: ListMonitor<NSManagedObject>) {
|
||||
|
||||
if self.lastSelectedIndexPath == nil,
|
||||
let numberOfObjectsInSection = self.listMonitor?.numberOfObjectsInSection(0),
|
||||
numberOfObjectsInSection > 0 {
|
||||
|
||||
self.tableView?.reloadData()
|
||||
self.setSelectedIndexPath(IndexPath(row: 0, section: 0), scrollToSelection: false)
|
||||
}
|
||||
else {
|
||||
|
||||
self.updateDisplay(reloadData: true, scrollToSelection: true, animated: true)
|
||||
}
|
||||
}
|
||||
|
||||
func listMonitorDidRefetch(_ monitor: ListMonitor<NSManagedObject>) {
|
||||
|
||||
self.listMonitorDidChange(monitor)
|
||||
}
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
@objc dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.listMonitor?.numberOfObjectsInSection(0) ?? 0
|
||||
}
|
||||
|
||||
@objc dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "OrganismTableViewCell", for: indexPath) as! OrganismTableViewCell
|
||||
|
||||
let dna = (self.listMonitor?[indexPath] as? OrganismProtocol)?.dna.description ?? ""
|
||||
cell.dnaLabel?.text = "DNA: \(dna)"
|
||||
cell.mutateButtonHandler = { [weak self] _ -> Void in
|
||||
|
||||
guard let `self` = self,
|
||||
let dataStack = self.dataStack,
|
||||
let organism = self.listMonitor?[indexPath] else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
|
||||
self.setEnabled(false)
|
||||
dataStack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let organism = transaction.edit(organism) as! OrganismProtocol
|
||||
organism.mutate()
|
||||
},
|
||||
completion: { [weak self] _ in
|
||||
|
||||
self?.setEnabled(true)
|
||||
}
|
||||
)
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
@objc dynamic func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
||||
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private typealias ModelMetadata = (label: String, version: String, entityType: AnyClass, migrationChain: MigrationChain)
|
||||
private typealias ModelMetadata = (label: String, entityType: NSManagedObject.Type, schemaHistory: SchemaHistory)
|
||||
|
||||
private let models: [ModelMetadata] = [
|
||||
(
|
||||
label: "Model V1",
|
||||
version: "MigrationDemo",
|
||||
entityType: OrganismV1.self,
|
||||
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
|
||||
schemaHistory: SchemaHistory(
|
||||
XcodeDataModelSchema.from(
|
||||
modelName: "MigrationDemo",
|
||||
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
|
||||
),
|
||||
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
|
||||
)
|
||||
),
|
||||
(
|
||||
label: "Model V2",
|
||||
version: "MigrationDemoV2",
|
||||
entityType: OrganismV2.self,
|
||||
migrationChain: [
|
||||
"MigrationDemo": "MigrationDemoV2",
|
||||
"MigrationDemoV3": "MigrationDemoV2"
|
||||
]
|
||||
schemaHistory: SchemaHistory(
|
||||
XcodeDataModelSchema.from(
|
||||
modelName: "MigrationDemo",
|
||||
migrationChain: [
|
||||
"MigrationDemo": "MigrationDemoV2",
|
||||
"MigrationDemoV3": "MigrationDemoV2"
|
||||
]
|
||||
),
|
||||
migrationChain: [
|
||||
"MigrationDemo": "MigrationDemoV2",
|
||||
"MigrationDemoV3": "MigrationDemoV2"
|
||||
]
|
||||
)
|
||||
),
|
||||
(
|
||||
label: "Model V3",
|
||||
version: "MigrationDemoV3",
|
||||
entityType: OrganismV3.self,
|
||||
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
|
||||
schemaHistory: SchemaHistory(
|
||||
XcodeDataModelSchema.from(
|
||||
modelName: "MigrationDemo",
|
||||
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
|
||||
),
|
||||
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
@@ -114,13 +202,13 @@ class MigrationsDemoViewController: UIViewController {
|
||||
return self._dataStack
|
||||
}
|
||||
|
||||
private var _lastSelectedIndexPath: NSIndexPath?
|
||||
private var lastSelectedIndexPath: NSIndexPath? {
|
||||
private var _lastSelectedIndexPath: IndexPath?
|
||||
private var lastSelectedIndexPath: IndexPath? {
|
||||
|
||||
return self._lastSelectedIndexPath
|
||||
}
|
||||
|
||||
private func setSelectedIndexPath(indexPath: NSIndexPath, scrollToSelection: Bool) {
|
||||
private func setSelectedIndexPath(_ indexPath: IndexPath, scrollToSelection: Bool) {
|
||||
|
||||
self._lastSelectedIndexPath = indexPath
|
||||
self.updateDisplay(reloadData: false, scrollToSelection: scrollToSelection, animated: true)
|
||||
@@ -133,7 +221,7 @@ class MigrationsDemoViewController: UIViewController {
|
||||
@IBOutlet private dynamic weak var progressView: UIProgressView?
|
||||
@IBOutlet private dynamic weak var tableView: UITableView?
|
||||
|
||||
@IBAction private dynamic func segmentedControlValueChanged(sender: AnyObject?) {
|
||||
@IBAction private dynamic func segmentedControlValueChanged(_ sender: AnyObject?) {
|
||||
|
||||
guard let index = self.segmentedControl?.selectedSegmentIndex else {
|
||||
|
||||
@@ -143,59 +231,92 @@ class MigrationsDemoViewController: UIViewController {
|
||||
self.selectModelVersion(self.models[index])
|
||||
}
|
||||
|
||||
private func selectModelVersion(model: ModelMetadata) {
|
||||
private func selectModelVersion(_ model: ModelMetadata) {
|
||||
|
||||
if self.dataStack?.modelVersion == model.version {
|
||||
if self.dataStack?.modelVersion == model.schemaHistory.currentModelVersion {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
self.setDataStack(nil, model: nil, scrollToSelection: false) // explicitly trigger NSPersistentStore cleanup by deallocating the stack
|
||||
self.set(dataStack: nil, model: nil, scrollToSelection: false) // explicitly trigger NSPersistentStore cleanup by deallocating the stack
|
||||
|
||||
let dataStack = DataStack(
|
||||
modelName: "MigrationDemo",
|
||||
migrationChain: model.migrationChain
|
||||
)
|
||||
let dataStack = DataStack(schemaHistory: model.schemaHistory)
|
||||
|
||||
self.setEnabled(false)
|
||||
let progress = try! dataStack.addSQLiteStore(
|
||||
fileName: "MigrationDemo.sqlite",
|
||||
let progress = dataStack.addStorage(
|
||||
SQLiteStore(
|
||||
fileName: "MigrationDemo.sqlite",
|
||||
migrationMappingProviders: [
|
||||
CustomSchemaMappingProvider(
|
||||
from: "MigrationDemoV3",
|
||||
to: "MigrationDemoV2",
|
||||
entityMappings: [
|
||||
.transformEntity(
|
||||
sourceEntity: "Organism",
|
||||
destinationEntity: "Organism",
|
||||
transformer: { (source, createDestination) in
|
||||
|
||||
let destination = createDestination()
|
||||
destination.enumerateAttributes { (attribute, sourceAttribute) in
|
||||
|
||||
if let sourceAttribute = sourceAttribute {
|
||||
|
||||
destination[attribute] = source[sourceAttribute]
|
||||
}
|
||||
}
|
||||
destination["numberOfFlippers"] = source["numberOfLimbs"]
|
||||
}
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
),
|
||||
completion: { [weak self] (result) -> Void in
|
||||
|
||||
guard let strongSelf = self else {
|
||||
guard let `self` = self else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
guard case .Success = result else {
|
||||
guard case .success = result else {
|
||||
|
||||
strongSelf.setEnabled(true)
|
||||
self.setEnabled(true)
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.setDataStack(dataStack, model: model, scrollToSelection: true)
|
||||
self.set(dataStack: 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(#keyPath(OrganismV1.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 {
|
||||
|
||||
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
|
||||
organism.dna = i
|
||||
organism.mutate()
|
||||
}
|
||||
|
||||
transaction.commit { result -> Void in
|
||||
|
||||
dataStack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
for j: Int64 in 0 ..< 500 {
|
||||
|
||||
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
|
||||
organism.dna = (i * 500) + j + 1
|
||||
organism.mutate()
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
dataStack.perform(
|
||||
asynchronous: { _ in },
|
||||
completion: { [weak self] _ in
|
||||
|
||||
self?.setEnabled(true)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
@@ -209,39 +330,45 @@ class MigrationsDemoViewController: UIViewController {
|
||||
}
|
||||
}
|
||||
|
||||
private func setEnabled(enabled: Bool) {
|
||||
private func setEnabled(_ enabled: Bool) {
|
||||
|
||||
UIView.animateWithDuration(
|
||||
0.2,
|
||||
UIView.animate(
|
||||
withDuration: 0.2,
|
||||
delay: 0,
|
||||
options: .BeginFromCurrentState,
|
||||
options: .beginFromCurrentState,
|
||||
animations: { () -> Void in
|
||||
|
||||
let navigationItem = self.navigationItem
|
||||
navigationItem.leftBarButtonItem?.enabled = enabled
|
||||
navigationItem.rightBarButtonItem?.enabled = enabled
|
||||
navigationItem.leftBarButtonItem?.isEnabled = enabled
|
||||
navigationItem.rightBarButtonItem?.isEnabled = enabled
|
||||
navigationItem.hidesBackButton = !enabled
|
||||
|
||||
self.segmentedControl?.enabled = enabled
|
||||
self.segmentedControl?.isEnabled = enabled
|
||||
|
||||
if let tableView = self.tableView {
|
||||
|
||||
tableView.alpha = enabled ? 1.0 : 0.5
|
||||
tableView.userInteractionEnabled = enabled
|
||||
tableView.isUserInteractionEnabled = enabled
|
||||
}
|
||||
},
|
||||
completion: nil
|
||||
)
|
||||
}
|
||||
|
||||
private func setDataStack(dataStack: DataStack?, model: ModelMetadata?, scrollToSelection: Bool) {
|
||||
private func set(dataStack: DataStack?, model: ModelMetadata?, scrollToSelection: Bool) {
|
||||
|
||||
if let dataStack = dataStack, let model = model {
|
||||
|
||||
self.segmentedControl?.selectedSegmentIndex = self.models.map { $0.version }.indexOf(model.version)!
|
||||
self.segmentedControl?.selectedSegmentIndex = self.models
|
||||
.index(
|
||||
where: { (_, _, schemaHistory) -> Bool in
|
||||
|
||||
schemaHistory.currentModelVersion == model.schemaHistory.currentModelVersion
|
||||
}
|
||||
)!
|
||||
|
||||
self._dataStack = dataStack
|
||||
let listMonitor = dataStack.monitorList(From(model.entityType), OrderBy(.Descending("dna")))
|
||||
let listMonitor = dataStack.monitorList(From(model.entityType), OrderBy(.descending("dna")))
|
||||
listMonitor.addObserver(self)
|
||||
self._listMonitor = listMonitor
|
||||
|
||||
@@ -249,28 +376,28 @@ class MigrationsDemoViewController: UIViewController {
|
||||
|
||||
if listMonitor.numberOfObjectsInSection(0) > 0 {
|
||||
|
||||
self.setSelectedIndexPath(NSIndexPath(forRow: 0, inSection: 0), scrollToSelection: true)
|
||||
self.setSelectedIndexPath(IndexPath(row: 0, section: 0), scrollToSelection: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
self.segmentedControl?.selectedSegmentIndex = UISegmentedControlNoSegment
|
||||
self._dataStack = nil
|
||||
self._listMonitor = nil
|
||||
self._dataStack = nil
|
||||
}
|
||||
|
||||
self.updateDisplay(reloadData: true, scrollToSelection: scrollToSelection, animated: false)
|
||||
}
|
||||
|
||||
private func reloadTableHeaderWithProgress(progress: NSProgress) {
|
||||
private func reloadTableHeaderWithProgress(_ progress: Progress) {
|
||||
|
||||
self.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
|
||||
self.titleLabel?.text = "Migrating: \(progress.localizedDescription)"
|
||||
self.organismLabel?.text = "Progressive step \(progress.localizedAdditionalDescription)"
|
||||
self.titleLabel?.text = "Migrating: \(progress.localizedDescription ?? "")"
|
||||
self.organismLabel?.text = "Progressive step \(progress.localizedAdditionalDescription ?? "")"
|
||||
}
|
||||
|
||||
private func updateDisplay(reloadData reloadData: Bool, scrollToSelection: Bool, animated: Bool) {
|
||||
private func updateDisplay(reloadData: Bool, scrollToSelection: Bool, animated: Bool) {
|
||||
|
||||
var lines = [String]()
|
||||
var organismType = ""
|
||||
@@ -278,14 +405,14 @@ class MigrationsDemoViewController: UIViewController {
|
||||
|
||||
for property in organism.entity.properties {
|
||||
|
||||
let value: AnyObject = organism.valueForKey(property.name) ?? NSNull()
|
||||
let value = organism.value(forKey: property.name) ?? NSNull()
|
||||
lines.append("\(property.name): \(value)")
|
||||
}
|
||||
organismType = organism.entity.managedObjectClassName
|
||||
}
|
||||
|
||||
self.titleLabel?.text = organismType
|
||||
self.organismLabel?.text = lines.joinWithSeparator("\n")
|
||||
self.organismLabel?.text = lines.joined(separator: "\n")
|
||||
self.progressView?.progress = 0
|
||||
|
||||
self.headerContainer?.setNeedsLayout()
|
||||
@@ -302,87 +429,13 @@ class MigrationsDemoViewController: UIViewController {
|
||||
|
||||
tableView.layoutIfNeeded()
|
||||
|
||||
if let indexPath = self.lastSelectedIndexPath where indexPath.row < tableView.numberOfRowsInSection(0) {
|
||||
if let indexPath = self.lastSelectedIndexPath,
|
||||
indexPath.row < tableView.numberOfRows(inSection: 0) {
|
||||
|
||||
tableView.selectRowAtIndexPath(indexPath,
|
||||
tableView.selectRow(at: indexPath,
|
||||
animated: scrollToSelection && animated,
|
||||
scrollPosition: scrollToSelection ? .Middle : .None
|
||||
scrollPosition: scrollToSelection ? .middle : .none
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MigrationsDemoViewController: ListObserver
|
||||
|
||||
extension MigrationsDemoViewController: ListObserver {
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
func listMonitorWillChange(monitor: ListMonitor<NSManagedObject>) { }
|
||||
|
||||
func listMonitorDidChange(monitor: ListMonitor<NSManagedObject>) {
|
||||
|
||||
if self.lastSelectedIndexPath == nil && self.listMonitor?.numberOfObjectsInSection(0) > 0 {
|
||||
|
||||
self.tableView?.reloadData()
|
||||
self.setSelectedIndexPath(NSIndexPath(forRow: 0, inSection: 0), scrollToSelection: false)
|
||||
}
|
||||
else {
|
||||
|
||||
self.updateDisplay(reloadData: true, scrollToSelection: true, animated: true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MigrationsDemoViewController: UITableViewDataSource, UITableViewDelegate
|
||||
|
||||
extension MigrationsDemoViewController: UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
@objc dynamic func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.listMonitor?.numberOfObjectsInSection(0) ?? 0
|
||||
}
|
||||
|
||||
@objc dynamic func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("OrganismTableViewCell", forIndexPath: indexPath) as! OrganismTableViewCell
|
||||
|
||||
let dna = (self.listMonitor?[indexPath] as? OrganismProtocol)?.dna.description ?? ""
|
||||
cell.dnaLabel?.text = "DNA: \(dna)"
|
||||
cell.mutateButtonHandler = { [weak self] _ -> Void in
|
||||
|
||||
guard let strongSelf = self,
|
||||
let dataStack = strongSelf.dataStack,
|
||||
let organism = strongSelf.listMonitor?[indexPath] else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
strongSelf.setSelectedIndexPath(indexPath, scrollToSelection: false)
|
||||
strongSelf.setEnabled(false)
|
||||
dataStack.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let organism = transaction.edit(organism) as! OrganismProtocol
|
||||
organism.mutate()
|
||||
|
||||
transaction.commit { _ -> Void in
|
||||
|
||||
self?.setEnabled(true)
|
||||
}
|
||||
}
|
||||
}
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
@objc dynamic func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,4 @@ protocol OrganismProtocol: class {
|
||||
var dna: Int64 { get set }
|
||||
|
||||
func mutate()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ class OrganismTableViewCell: UITableViewCell {
|
||||
|
||||
var mutateButtonHandler: (() -> Void)?
|
||||
|
||||
@IBAction dynamic func mutateButtonTouchUpInside(sender: UIButton?) {
|
||||
@IBAction dynamic func mutateButtonTouchUpInside(_ sender: UIButton?) {
|
||||
|
||||
self.mutateButtonHandler?()
|
||||
}
|
||||
|
||||
@@ -10,14 +10,20 @@ import CoreData
|
||||
|
||||
class OrganismV2ToV3MigrationPolicy: NSEntityMigrationPolicy {
|
||||
|
||||
override func createDestinationInstancesForSourceInstance(sInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
|
||||
|
||||
try super.createDestinationInstancesForSourceInstance(sInstance, entityMapping: mapping, manager: manager)
|
||||
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
|
||||
|
||||
for dInstance in manager.destinationInstancesForEntityMappingNamed(mapping.name, sourceInstances: [sInstance]) {
|
||||
for dInstance in manager.destinationInstances(forEntityMappingName: mapping.name, sourceInstances: [sInstance]) {
|
||||
|
||||
dInstance.setValue(false, forKey: "hasVertebrae")
|
||||
dInstance.setValue(sInstance.valueForKey("numberOfFlippers"), forKey: "numberOfLimbs")
|
||||
dInstance.setValue(
|
||||
false,
|
||||
forKey: #keyPath(OrganismV3.hasVertebrae)
|
||||
)
|
||||
dInstance.setValue(
|
||||
sInstance.value(forKey: #keyPath(OrganismV2.numberOfFlippers)),
|
||||
forKey: #keyPath(OrganismV3.numberOfLimbs)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" standalone="no"?>
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE database SYSTEM "file:///System/Library/DTDs/CoreData.dtd">
|
||||
|
||||
<database>
|
||||
@@ -10,7 +10,7 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSPersistenceFrameworkVersion</key>
|
||||
<integer>526</integer>
|
||||
<integer>754</integer>
|
||||
<key>NSStoreModelVersionHashes</key>
|
||||
<dict>
|
||||
<key>XDDevAttributeMapping</key>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14E46" minimumToolsVersion="Xcode 4.3">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="11198.3" systemVersion="15F34" minimumToolsVersion="Xcode 4.3" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Organism" representedClassName="CoreStoreDemo.OrganismV1" syncable="YES">
|
||||
<attribute name="dna" optional="YES" attributeType="Integer 64" syncable="YES"/>
|
||||
<attribute name="hasHead" optional="YES" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="hasTail" optional="YES" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="dna" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="hasHead" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="hasTail" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Organism" positionX="-36" positionY="9" width="128" height="90"/>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14E46" minimumToolsVersion="Xcode 4.3">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="11198.3" systemVersion="15F34" minimumToolsVersion="Xcode 4.3" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Organism" representedClassName="CoreStoreDemo.OrganismV2" syncable="YES">
|
||||
<attribute name="dna" optional="YES" attributeType="Integer 64" syncable="YES"/>
|
||||
<attribute name="hasHead" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="hasTail" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="numberOfFlippers" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="dna" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="hasHead" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="hasTail" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="numberOfFlippers" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Organism" positionX="-36" positionY="9" width="128" height="105"/>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14E46" minimumToolsVersion="Xcode 4.3">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="12141" systemVersion="16E195" minimumToolsVersion="Xcode 4.3" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
|
||||
<entity name="Organism" representedClassName="CoreStoreDemo.OrganismV3" syncable="YES">
|
||||
<attribute name="dna" optional="YES" attributeType="Integer 64" syncable="YES"/>
|
||||
<attribute name="hasHead" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="hasTail" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="hasVertebrae" attributeType="Boolean" syncable="YES"/>
|
||||
<attribute name="numberOfLimbs" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="dna" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="hasHead" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="hasTail" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="hasVertebrae" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="numberOfLimbs" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" elementID="numberOfFlippers" syncable="YES"/>
|
||||
</entity>
|
||||
<elements>
|
||||
<element name="Organism" positionX="-36" positionY="9" width="128" height="120"/>
|
||||
|
||||
@@ -17,69 +17,76 @@ private struct Static {
|
||||
|
||||
static let facebookStack: DataStack = {
|
||||
|
||||
let dataStack = DataStack(modelName: "StackSetupDemo")
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_FB_Male.sqlite",
|
||||
configuration: maleConfiguration,
|
||||
resetStoreOnModelMismatch: true
|
||||
let dataStack = DataStack(xcodeModelName: "StackSetupDemo")
|
||||
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
|
||||
|
||||
transaction.deleteAll(From(UserAccount))
|
||||
|
||||
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
|
||||
account1.accountType = "Facebook"
|
||||
account1.name = "John Smith HCD"
|
||||
account1.friends = 42
|
||||
|
||||
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
|
||||
account2.accountType = "Facebook"
|
||||
account2.name = "Jane Doe HCD"
|
||||
account2.friends = 314
|
||||
|
||||
transaction.commit()
|
||||
}
|
||||
_ = try? dataStack.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
transaction.deleteAll(From<UserAccount>())
|
||||
|
||||
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
|
||||
account1.accountType = "Facebook"
|
||||
account1.name = "John Smith HCD"
|
||||
account1.friends = 42
|
||||
|
||||
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
|
||||
account2.accountType = "Facebook"
|
||||
account2.name = "Jane Doe HCD"
|
||||
account2.friends = 314
|
||||
}
|
||||
)
|
||||
|
||||
return dataStack
|
||||
}()
|
||||
|
||||
static let twitterStack: DataStack = {
|
||||
|
||||
let dataStack = DataStack(modelName: "StackSetupDemo")
|
||||
try! dataStack.addSQLiteStoreAndWait(
|
||||
fileName: "AccountsDemo_TW_Male.sqlite",
|
||||
configuration: maleConfiguration,
|
||||
resetStoreOnModelMismatch: true
|
||||
let dataStack = DataStack(xcodeModelName: "StackSetupDemo")
|
||||
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
|
||||
|
||||
transaction.deleteAll(From(UserAccount))
|
||||
|
||||
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
|
||||
account1.accountType = "Twitter"
|
||||
account1.name = "#johnsmith_hcd"
|
||||
account1.friends = 7
|
||||
|
||||
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
|
||||
account2.accountType = "Twitter"
|
||||
account2.name = "#janedoe_hcd"
|
||||
account2.friends = 100
|
||||
|
||||
transaction.commit()
|
||||
}
|
||||
|
||||
_ = try? dataStack.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
transaction.deleteAll(From<UserAccount>())
|
||||
|
||||
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
|
||||
account1.accountType = "Twitter"
|
||||
account1.name = "#johnsmith_hcd"
|
||||
account1.friends = 7
|
||||
|
||||
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
|
||||
account2.accountType = "Twitter"
|
||||
account2.name = "#janedoe_hcd"
|
||||
account2.friends = 100
|
||||
}
|
||||
)
|
||||
return dataStack
|
||||
}()
|
||||
}
|
||||
@@ -92,53 +99,53 @@ private struct Static {
|
||||
class StackSetupDemoViewController: UITableViewController {
|
||||
|
||||
let accounts = [
|
||||
Static.facebookStack.fetchAll(From(UserAccount)) ?? [],
|
||||
Static.twitterStack.fetchAll(From(UserAccount)) ?? []
|
||||
Static.facebookStack.fetchAll(From(UserAccount.self)) ?? [],
|
||||
Static.twitterStack.fetchAll(From(UserAccount.self)) ?? []
|
||||
]
|
||||
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
override func viewWillAppear(animated: Bool) {
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
self.tableView.reloadData()
|
||||
|
||||
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
|
||||
self.tableView.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: .None)
|
||||
self.updateDetailsWithAccount(self.accounts[indexPath.section][indexPath.row])
|
||||
let indexPath = IndexPath(row: 0, section: 0)
|
||||
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
|
||||
self.updateDetails(account: self.accounts[indexPath.section][indexPath.row])
|
||||
}
|
||||
|
||||
override func viewDidAppear(animated: Bool) {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Setup Demo",
|
||||
message: "This demo shows how to initialize 2 DataStacks with 2 configurations each, for a total of 4 SQLite files, each with 1 instance of a \"UserAccount\" entity.",
|
||||
preferredStyle: .Alert
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
self.presentViewController(alert, animated: true, completion: nil)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||||
|
||||
return self.accounts.count
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return self.accounts[section].count
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell")!
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell")!
|
||||
|
||||
let account = self.accounts[indexPath.section][indexPath.row]
|
||||
cell.textLabel?.text = account.name
|
||||
@@ -150,13 +157,13 @@ class StackSetupDemoViewController: UITableViewController {
|
||||
|
||||
// MARK: UITableViewDelegate
|
||||
|
||||
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
|
||||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||||
|
||||
let account = self.accounts[indexPath.section][indexPath.row]
|
||||
self.updateDetailsWithAccount(account)
|
||||
self.updateDetails(account: account)
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
switch section {
|
||||
|
||||
@@ -180,7 +187,7 @@ class StackSetupDemoViewController: UITableViewController {
|
||||
@IBOutlet private dynamic weak var nameLabel: UILabel?
|
||||
@IBOutlet private dynamic weak var friendsLabel: UILabel?
|
||||
|
||||
private func updateDetailsWithAccount(account: UserAccount) {
|
||||
private func updateDetails(account: UserAccount) {
|
||||
|
||||
self.accountTypeLabel?.text = account.accountType
|
||||
self.nameLabel?.text = account.name
|
||||
|
||||
@@ -11,30 +11,31 @@ import CoreLocation
|
||||
import MapKit
|
||||
import AddressBookUI
|
||||
import CoreStore
|
||||
import GCDKit
|
||||
|
||||
|
||||
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))
|
||||
var place = CoreStore.fetchOne(From<Place>())
|
||||
if place == nil {
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let place = transaction.create(Into(Place))
|
||||
place.setInitialValues()
|
||||
|
||||
transaction.commit()
|
||||
}
|
||||
place = CoreStore.fetchOne(From(Place))
|
||||
_ = try? CoreStore.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
let place = transaction.create(Into<Place>())
|
||||
place.setInitialValues()
|
||||
}
|
||||
)
|
||||
place = CoreStore.fetchOne(From<Place>())
|
||||
}
|
||||
|
||||
return CoreStore.monitorObject(place!)
|
||||
@@ -60,39 +61,42 @@ 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)
|
||||
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
barButtonSystemItem: .Refresh,
|
||||
barButtonSystemItem: .refresh,
|
||||
target: self,
|
||||
action: "refreshButtonTapped:"
|
||||
action: #selector(self.refreshButtonTapped(_:))
|
||||
)
|
||||
}
|
||||
|
||||
override func viewDidAppear(animated: Bool) {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
let alert = UIAlertController(
|
||||
title: "Transactions Demo",
|
||||
message: "This demo shows how to use the 3 types of transactions to save updates: synchronous, asynchronous, and unsafe.\n\nTap and hold on the map to change the pin location.",
|
||||
preferredStyle: .Alert
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
self.presentViewController(alert, animated: true, completion: nil)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
|
||||
self.present(alert, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
override func viewWillAppear(animated: Bool) {
|
||||
override func viewWillAppear(_ animated: Bool) {
|
||||
|
||||
super.viewWillAppear(animated)
|
||||
|
||||
if let mapView = self.mapView, let place = Static.placeController.object {
|
||||
|
||||
mapView.addAnnotation(place)
|
||||
mapView.setCenterCoordinate(place.coordinate, animated: false)
|
||||
mapView.setCenter(place.coordinate, animated: false)
|
||||
mapView.selectAnnotation(place, animated: false)
|
||||
}
|
||||
}
|
||||
@@ -100,14 +104,14 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
|
||||
|
||||
// MARK: MKMapViewDelegate
|
||||
|
||||
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
|
||||
|
||||
let identifier = "MKAnnotationView"
|
||||
var annotationView: MKPinAnnotationView! = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier) as? MKPinAnnotationView
|
||||
var annotationView: MKPinAnnotationView! = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
|
||||
if annotationView == nil {
|
||||
|
||||
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
|
||||
annotationView.enabled = true
|
||||
annotationView.isEnabled = true
|
||||
annotationView.canShowCallout = true
|
||||
annotationView.animatesDrop = true
|
||||
}
|
||||
@@ -122,28 +126,28 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
|
||||
|
||||
// MARK: ObjectObserver
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<Place>, willUpdateObject object: Place) {
|
||||
func objectMonitor(_ monitor: ObjectMonitor<Place>, willUpdateObject object: Place) {
|
||||
|
||||
// none
|
||||
}
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<Place>, didUpdateObject object: Place, changedPersistentKeys: Set<KeyPath>) {
|
||||
func objectMonitor(_ monitor: ObjectMonitor<Place>, didUpdateObject object: Place, changedPersistentKeys: Set<RawKeyPath>) {
|
||||
|
||||
if let mapView = self.mapView {
|
||||
|
||||
mapView.removeAnnotations(mapView.annotations ?? [])
|
||||
mapView.removeAnnotations(mapView.annotations)
|
||||
mapView.addAnnotation(object)
|
||||
mapView.setCenterCoordinate(object.coordinate, animated: true)
|
||||
mapView.setCenter(object.coordinate, animated: true)
|
||||
mapView.selectAnnotation(object, animated: true)
|
||||
|
||||
if changedPersistentKeys.contains("latitude") || changedPersistentKeys.contains("longitude") {
|
||||
if changedPersistentKeys.contains(#keyPath(Place.latitude)) || changedPersistentKeys.contains(#keyPath(Place.longitude)) {
|
||||
|
||||
self.geocodePlace(object)
|
||||
self.geocode(place: object)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func objectMonitor(monitor: ObjectMonitor<Place>, didDeleteObject object: Place) {
|
||||
func objectMonitor(_ monitor: ObjectMonitor<Place>, didDeleteObject object: Place) {
|
||||
|
||||
// none
|
||||
}
|
||||
@@ -155,34 +159,39 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
|
||||
|
||||
@IBOutlet weak var mapView: MKMapView?
|
||||
|
||||
@IBAction dynamic func longPressGestureRecognized(sender: AnyObject?) {
|
||||
@IBAction dynamic func longPressGestureRecognized(_ sender: AnyObject?) {
|
||||
|
||||
if let mapView = self.mapView, let gesture = sender as? UILongPressGestureRecognizer where gesture.state == .Began {
|
||||
if let mapView = self.mapView,
|
||||
let gesture = sender as? UILongPressGestureRecognizer,
|
||||
gesture.state == .began {
|
||||
|
||||
let coordinate = mapView.convertPoint(
|
||||
gesture.locationInView(mapView),
|
||||
toCoordinateFromView: mapView
|
||||
let coordinate = mapView.convert(
|
||||
gesture.location(in: mapView),
|
||||
toCoordinateFrom: mapView
|
||||
)
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
CoreStore.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let place = transaction.edit(Static.placeController.object)
|
||||
place?.coordinate = coordinate
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@IBAction dynamic func refreshButtonTapped(_ sender: AnyObject?) {
|
||||
|
||||
_ = try? CoreStore.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
let place = transaction.edit(Static.placeController.object)
|
||||
place?.coordinate = coordinate
|
||||
transaction.commit { (_) -> Void in }
|
||||
place?.setInitialValues()
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction dynamic func refreshButtonTapped(sender: AnyObject?) {
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let place = transaction.edit(Static.placeController.object)
|
||||
place?.setInitialValues()
|
||||
transaction.commit()
|
||||
}
|
||||
}
|
||||
|
||||
func geocodePlace(place: Place) {
|
||||
func geocode(place: Place) {
|
||||
|
||||
let transaction = CoreStore.beginUnsafe()
|
||||
|
||||
|
||||
BIN
CoreStoreDemo/appIcons.sketch
Normal file
204
CoreStoreTests/BaseTests/BaseTestCase.swift
Normal file
@@ -0,0 +1,204 @@
|
||||
//
|
||||
// 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
|
||||
@discardableResult
|
||||
func prepareStack<T>(configurations: [ModelConfiguration] = [nil], _ closure: (_ dataStack: DataStack) -> T) -> T {
|
||||
|
||||
let stack = DataStack(
|
||||
xcodeModelName: "Model",
|
||||
bundle: Bundle(for: type(of: self))
|
||||
)
|
||||
do {
|
||||
|
||||
try configurations.forEach {
|
||||
|
||||
try stack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileURL: SQLiteStore.defaultRootDirectory
|
||||
.appendingPathComponent(UUID().uuidString)
|
||||
.appendingPathComponent("\(type(of: self))_\(($0 ?? "-null-")).sqlite"),
|
||||
configuration: $0,
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.coreStoreDumpString)
|
||||
}
|
||||
return closure(stack)
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func expectLogger<T>(_ expectations: [TestLogger.Expectation], 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.expectation(description: "Logger Expectation: \(expectation)")
|
||||
}
|
||||
return testExpectations
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func checkExpectationsImmediately() {
|
||||
|
||||
self.waitForExpectations(timeout: 0, handler: { _ in })
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func waitAndCheckExpectations() {
|
||||
|
||||
self.waitForExpectations(timeout: 10, handler: {_ in })
|
||||
}
|
||||
|
||||
// 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? FileManager.default.removeItem(at: 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
|
||||
|
||||
var enableObjectConcurrencyDebugging: Bool = true
|
||||
|
||||
func log(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: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
self.fulfill(.logError)
|
||||
}
|
||||
|
||||
func assert(_ condition: @autoclosure () -> Bool, message: @autoclosure () -> 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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
77
CoreStoreTests/BaseTests/BaseTestDataTestCase.swift
Normal file
@@ -0,0 +1,77 @@
|
||||
//
|
||||
// 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: DateFormatter = cs_lazy {
|
||||
|
||||
let formatter = DateFormatter()
|
||||
formatter.locale = Locale(identifier: "en_US_POSIX")
|
||||
formatter.timeZone = TimeZone(identifier: "UTC")
|
||||
formatter.calendar = Calendar(identifier: Calendar.Identifier.gregorian)
|
||||
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
|
||||
return formatter
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func prepareTestDataForStack(_ stack: DataStack, configurations: [ModelConfiguration] = [nil]) {
|
||||
|
||||
try! stack.perform(
|
||||
synchronous: { (transaction) in
|
||||
|
||||
for (configurationIndex, configuration) in configurations.enumerated() {
|
||||
|
||||
let configurationOrdinal = configurationIndex + 1
|
||||
if configuration == nil || configuration == "Config1" {
|
||||
|
||||
for idIndex in 1 ... 5 {
|
||||
|
||||
let object = transaction.create(Into<TestEntity1>(configuration))
|
||||
object.testEntityID = NSNumber(value: (configurationOrdinal * 100) + idIndex)
|
||||
|
||||
object.testNumber = NSNumber(value: idIndex)
|
||||
object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
|
||||
object.testBoolean = NSNumber(value: (idIndex % 2) == 1)
|
||||
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
|
||||
|
||||
let string = "\(configuration ?? "nil"):TestEntity1:\(idIndex)"
|
||||
object.testString = string
|
||||
object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue)
|
||||
}
|
||||
}
|
||||
if configuration == nil || configuration == "Config2" {
|
||||
|
||||
for idIndex in 1 ... 5 {
|
||||
|
||||
let object = transaction.create(Into<TestEntity2>(configuration))
|
||||
object.testEntityID = NSNumber(value: (configurationOrdinal * 200) + idIndex)
|
||||
|
||||
object.testNumber = NSNumber(value: idIndex)
|
||||
object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
|
||||
object.testBoolean = NSNumber(value: (idIndex % 2) == 1)
|
||||
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
|
||||
|
||||
let string = "\(configuration ?? "nil"):TestEntity2:\(idIndex)"
|
||||
object.testString = string
|
||||
object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
30
CoreStoreTests/BridgingTests.h
Normal file
@@ -0,0 +1,30 @@
|
||||
//
|
||||
// BridgingTests.h
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
@interface BridgingTests : XCTestCase
|
||||
|
||||
@end
|
||||
257
CoreStoreTests/BridgingTests.m
Normal file
@@ -0,0 +1,257 @@
|
||||
//
|
||||
// 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]
|
||||
initWithXcodeModelName:@"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]
|
||||
initWithXcodeModelName:@"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]]);
|
||||
NSError *error;
|
||||
BOOL result = [transaction commitAndWaitWithError:&error];
|
||||
XCTAssertTrue(result);
|
||||
XCTAssertNil(error);
|
||||
}
|
||||
{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"sync"];
|
||||
NSError *error;
|
||||
BOOL result = [CSCoreStore
|
||||
beginSynchronous:^(CSSynchronousDataTransaction * _Nonnull transaction) {
|
||||
|
||||
XCTAssertNotNil(transaction);
|
||||
XCTAssert([transaction isKindOfClass:[CSSynchronousDataTransaction class]]);
|
||||
NSError *error;
|
||||
XCTAssertTrue([transaction commitAndWaitWithError:&error]);
|
||||
XCTAssertNil(error);
|
||||
[expectation fulfill];
|
||||
}
|
||||
error:&error];
|
||||
XCTAssertTrue(result);
|
||||
XCTAssertNil(error);
|
||||
}
|
||||
{
|
||||
XCTestExpectation *expectation = [self expectationWithDescription:@"async"];
|
||||
[CSCoreStore beginAsynchronous:^(CSAsynchronousDataTransaction * _Nonnull transaction) {
|
||||
|
||||
XCTAssertNotNil(transaction);
|
||||
XCTAssert([transaction isKindOfClass:[CSAsynchronousDataTransaction class]]);
|
||||
[transaction
|
||||
commitWithSuccess:^{
|
||||
|
||||
[expectation fulfill];
|
||||
}
|
||||
failure:^(CSError *error){
|
||||
|
||||
XCTFail();
|
||||
}];
|
||||
}];
|
||||
}
|
||||
[self waitForExpectationsWithTimeout:10 handler:nil];
|
||||
}
|
||||
|
||||
@end
|
||||
91
CoreStoreTests/ConvenienceTests.swift
Normal file
@@ -0,0 +1,91 @@
|
||||
//
|
||||
// ConvenienceTests.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: - ConvenienceTests
|
||||
|
||||
@available(OSX 10.12, *)
|
||||
class ConvenienceTests: BaseTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDataStacks_CanCreateFetchedResultsControllers() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
let controller = stack.createFetchedResultsController(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testString)),
|
||||
Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100),
|
||||
OrderBy(.ascending(#keyPath(TestEntity1.testString))),
|
||||
Tweak { $0.fetchLimit = 10 }
|
||||
)
|
||||
XCTAssertEqual(controller.managedObjectContext, stack.mainContext)
|
||||
XCTAssertEqual(controller.fetchRequest.entity?.managedObjectClassName, NSStringFromClass(TestEntity1.self))
|
||||
XCTAssertEqual(controller.sectionNameKeyPath, #keyPath(TestEntity1.testString))
|
||||
XCTAssertEqual(
|
||||
controller.fetchRequest.sortDescriptors!,
|
||||
OrderBy(.ascending(#keyPath(TestEntity1.testString))).sortDescriptors
|
||||
)
|
||||
XCTAssertEqual(
|
||||
controller.fetchRequest.predicate,
|
||||
Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100).predicate
|
||||
)
|
||||
XCTAssertEqual(controller.fetchRequest.fetchLimit, 10)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatUnsafeDataTransactions_CanCreateFetchedResultsControllers() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
_ = withExtendedLifetime(stack.beginUnsafe()) { (transaction: UnsafeDataTransaction) in
|
||||
|
||||
let controller = transaction.createFetchedResultsController(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testString)),
|
||||
Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100),
|
||||
OrderBy(.ascending(#keyPath(TestEntity1.testString))),
|
||||
Tweak { $0.fetchLimit = 10 }
|
||||
)
|
||||
XCTAssertEqual(controller.managedObjectContext, transaction.context)
|
||||
XCTAssertEqual(controller.fetchRequest.entity?.managedObjectClassName, NSStringFromClass(TestEntity1.self))
|
||||
XCTAssertEqual(controller.sectionNameKeyPath, #keyPath(TestEntity1.testString))
|
||||
XCTAssertEqual(
|
||||
controller.fetchRequest.sortDescriptors!,
|
||||
OrderBy(.ascending(#keyPath(TestEntity1.testString))).sortDescriptors
|
||||
)
|
||||
XCTAssertEqual(
|
||||
controller.fetchRequest.predicate,
|
||||
Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100).predicate
|
||||
)
|
||||
XCTAssertEqual(controller.fetchRequest.fetchLimit, 10)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
5
CoreStoreTests/CoreStoreTests-Bridging-Header.h
Normal file
@@ -0,0 +1,5 @@
|
||||
//
|
||||
// Use this file to import your target's public headers that you would like to expose to Swift.
|
||||
//
|
||||
|
||||
#import "BridgingTests.h"
|
||||
@@ -1,389 +0,0 @@
|
||||
//
|
||||
// CoreStoreTests.swift
|
||||
// CoreStoreTests
|
||||
//
|
||||
// Copyright © 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
class CoreStoreTests: XCTestCase {
|
||||
|
||||
override func setUp() {
|
||||
|
||||
super.setUp()
|
||||
self.deleteStores()
|
||||
}
|
||||
|
||||
override func tearDown() {
|
||||
|
||||
self.deleteStores()
|
||||
super.tearDown()
|
||||
}
|
||||
|
||||
func testMigrationChains() {
|
||||
|
||||
let emptyChain: MigrationChain = nil
|
||||
XCTAssertTrue(emptyChain.valid, "emptyChain.valid")
|
||||
XCTAssertTrue(emptyChain.empty, "emptyChain.empty")
|
||||
|
||||
let normalChain: MigrationChain = "version1"
|
||||
XCTAssertTrue(normalChain.valid, "normalChain.valid")
|
||||
XCTAssertTrue(normalChain.empty, "normalChain.empty")
|
||||
|
||||
let linearChain: MigrationChain = ["version1", "version2", "version3", "version4"]
|
||||
XCTAssertTrue(linearChain.valid, "linearChain.valid")
|
||||
XCTAssertFalse(linearChain.empty, "linearChain.empty")
|
||||
|
||||
let treeChain: MigrationChain = [
|
||||
"version1": "version4",
|
||||
"version2": "version3",
|
||||
"version3": "version4"
|
||||
]
|
||||
XCTAssertTrue(treeChain.valid, "treeChain.valid")
|
||||
XCTAssertFalse(treeChain.empty, "treeChain.empty")
|
||||
|
||||
// The cases below will trigger assertion failures internally
|
||||
|
||||
// let linearLoopChain: MigrationChain = ["version1", "version2", "version1", "version3", "version4"]
|
||||
// XCTAssertFalse(linearLoopChain.valid, "linearLoopChain.valid")
|
||||
//
|
||||
// let treeAmbiguousChain: MigrationChain = [
|
||||
// "version1": "version4",
|
||||
// "version2": "version3",
|
||||
// "version1": "version2",
|
||||
// "version3": "version4"
|
||||
// ]
|
||||
// XCTAssertFalse(treeAmbiguousChain.valid, "treeAmbiguousChain.valid")
|
||||
}
|
||||
|
||||
func testExample() {
|
||||
|
||||
let stack = DataStack(modelName: "Model", bundle: NSBundle(forClass: self.dynamicType))
|
||||
CoreStore.defaultStack = stack
|
||||
XCTAssert(CoreStore.defaultStack === stack, "CoreStore.defaultStack === stack")
|
||||
|
||||
do {
|
||||
|
||||
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore1.sqlite", configuration: "Config1", resetStoreOnModelMismatch: true)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try stack.addSQLiteStoreAndWait(fileName: "ConfigStore2.sqlite", configuration: "Config2", resetStoreOnModelMismatch: true)
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.description)
|
||||
}
|
||||
|
||||
let unsafeTransaction = CoreStore.beginUnsafe()
|
||||
|
||||
let createExpectation = self.expectationWithDescription("Entity creation")
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let obj1 = transaction.create(Into(TestEntity1))
|
||||
obj1.testEntityID = 1
|
||||
obj1.testString = "lololol"
|
||||
obj1.testNumber = 42
|
||||
obj1.testDate = NSDate()
|
||||
|
||||
let count = transaction.queryValue(
|
||||
From<TestEntity1>(),
|
||||
Select<Int>(.Count("testNumber"))
|
||||
)
|
||||
XCTAssertTrue(count == 0, "count == 0 (actual: \(count))") // counts only objects in store
|
||||
|
||||
let obj2 = transaction.create(Into<TestEntity2>())
|
||||
obj2.testEntityID = 2
|
||||
obj2.testString = "hahaha"
|
||||
obj2.testNumber = 100
|
||||
obj2.testDate = NSDate()
|
||||
|
||||
let obj3 = transaction.create(Into<TestEntity2>("Config2"))
|
||||
obj3.testEntityID = 3
|
||||
obj3.testString = "hahaha"
|
||||
obj3.testNumber = 90
|
||||
obj3.testDate = NSDate()
|
||||
|
||||
let obj4 = transaction.create(Into(TestEntity2.self, "Config2"))
|
||||
obj4.testEntityID = 5
|
||||
obj4.testString = "hohoho"
|
||||
obj4.testNumber = 80
|
||||
obj4.testDate = NSDate()
|
||||
|
||||
|
||||
transaction.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let obj4 = transaction.create(Into<TestEntity2>())
|
||||
obj4.testEntityID = 4
|
||||
obj4.testString = "hehehehe"
|
||||
obj4.testNumber = 80
|
||||
obj4.testDate = NSDate()
|
||||
|
||||
let objs4test = transaction.fetchOne(
|
||||
From<TestEntity2>("Config2"),
|
||||
Where("testEntityID", isEqualTo: 4),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
|
||||
fetchRequest.includesPendingChanges = true
|
||||
}
|
||||
)
|
||||
XCTAssertNotNil(objs4test, "objs4test != nil")
|
||||
|
||||
let objs5test = transaction.fetchOne(
|
||||
From(TestEntity2),
|
||||
Where("testEntityID", isEqualTo: 4),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
|
||||
fetchRequest.includesPendingChanges = false
|
||||
}
|
||||
)
|
||||
XCTAssertNil(objs5test, "objs5test == nil")
|
||||
|
||||
// Dont commit1
|
||||
}
|
||||
|
||||
transaction.commit { (result) -> Void in
|
||||
|
||||
let objs4test = CoreStore.fetchOne(
|
||||
From(TestEntity2),
|
||||
Where("testEntityID", isEqualTo: 4),
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
|
||||
fetchRequest.includesPendingChanges = false
|
||||
}
|
||||
)
|
||||
XCTAssertNil(objs4test, "objs4test == nil")
|
||||
|
||||
let objs5test = unsafeTransaction.fetchCount(From(TestEntity2))
|
||||
XCTAssertTrue(objs5test == 3, "objs5test == 3")
|
||||
|
||||
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertTrue(hasChanges, "hasChanges == true")
|
||||
createExpectation.fulfill()
|
||||
|
||||
case .Failure(let error):
|
||||
XCTFail(error.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let queryExpectation = self.expectationWithDescription("Query creation")
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let obj1 = transaction.fetchOne(From(TestEntity1))
|
||||
XCTAssertNotNil(obj1, "obj1 != nil")
|
||||
|
||||
var orderBy = OrderBy(.Ascending("testEntityID"))
|
||||
orderBy += OrderBy(.Descending("testString"))
|
||||
let objs2 = transaction.fetchAll(
|
||||
From(TestEntity2),
|
||||
Where("testNumber", isEqualTo: 100) || Where("%K == %@", "testNumber", 90),
|
||||
orderBy,
|
||||
Tweak { (fetchRequest) -> Void in
|
||||
|
||||
fetchRequest.includesPendingChanges = true
|
||||
}
|
||||
)
|
||||
XCTAssertNotNil(objs2, "objs2 != nil")
|
||||
XCTAssertTrue(objs2?.count == 2, "objs2?.count == 2")
|
||||
|
||||
transaction.commit { (result) -> Void in
|
||||
|
||||
let counts = CoreStore.queryAttributes(
|
||||
From(TestEntity2),
|
||||
Select("testString", .Count("testString", As: "count")),
|
||||
GroupBy("testString")
|
||||
)
|
||||
print(counts)
|
||||
|
||||
XCTAssertTrue(NSThread.isMainThread(), "NSThread.isMainThread()")
|
||||
switch result {
|
||||
|
||||
case .Success(let hasChanges):
|
||||
XCTAssertFalse(hasChanges, "hasChanges == false")
|
||||
queryExpectation.fulfill()
|
||||
|
||||
case .Failure(let error):
|
||||
XCTFail(error.description)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.waitForExpectationsWithTimeout(100, handler: nil)
|
||||
|
||||
let max1 = CoreStore.queryValue(
|
||||
From(TestEntity2),
|
||||
Select<Int>(.Maximum("testNumber"))
|
||||
)
|
||||
XCTAssertTrue(max1 == 100, "max == 100 (actual: \(max1))")
|
||||
|
||||
let max2 = CoreStore.queryValue(
|
||||
From(TestEntity2),
|
||||
Select<NSNumber>(.Maximum("testNumber")),
|
||||
Where("%K > %@", "testEntityID", 2)
|
||||
)
|
||||
XCTAssertTrue(max2 == 90, "max == 90 (actual: \(max2))")
|
||||
|
||||
CoreStore.beginSynchronous { (transaction) -> Void in
|
||||
|
||||
let numberOfDeletedObjects1 = transaction.deleteAll(From(TestEntity1))
|
||||
XCTAssertTrue(numberOfDeletedObjects1 == 1, "numberOfDeletedObjects1 == 1 (actual: \(numberOfDeletedObjects1))")
|
||||
|
||||
let numberOfDeletedObjects2 = transaction.deleteAll(
|
||||
From(TestEntity2),
|
||||
Where("%K > %@", "testEntityID", 2)
|
||||
)
|
||||
XCTAssertTrue(numberOfDeletedObjects2 == 2, "numberOfDeletedObjects2 == 2 (actual: \(numberOfDeletedObjects2))")
|
||||
|
||||
transaction.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 _ { }
|
||||
}
|
||||
}
|
||||
270
CoreStoreTests/DynamicModelTests.swift
Normal file
@@ -0,0 +1,270 @@
|
||||
//
|
||||
// DynamicModelTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2017 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(OSX)
|
||||
typealias Color = NSColor
|
||||
#else
|
||||
|
||||
typealias Color = UIColor
|
||||
#endif
|
||||
|
||||
class Animal: CoreStoreObject {
|
||||
|
||||
let species = Value.Required<String>("species", initial: "Swift")
|
||||
let master = Relationship.ToOne<Person>("master")
|
||||
let color = Transformable.Optional<Color>("color")
|
||||
}
|
||||
|
||||
class Dog: Animal {
|
||||
|
||||
let nickname = Value.Optional<String>("nickname")
|
||||
let age = Value.Required<Int>("age", initial: 1)
|
||||
let friends = Relationship.ToManyOrdered<Dog>("friends")
|
||||
let friendedBy = Relationship.ToManyUnordered<Dog>("friendedBy", inverse: { $0.friends })
|
||||
}
|
||||
|
||||
class Person: CoreStoreObject {
|
||||
|
||||
let title = Value.Required<String>(
|
||||
"title",
|
||||
initial: "Mr.",
|
||||
customSetter: Person.setTitle
|
||||
)
|
||||
|
||||
let name = Value.Required<String>(
|
||||
"name",
|
||||
initial: "",
|
||||
customSetter: Person.setName
|
||||
)
|
||||
|
||||
let displayName = Value.Optional<String>(
|
||||
"displayName",
|
||||
isTransient: true,
|
||||
customGetter: Person.getDisplayName(_:),
|
||||
affectedByKeyPaths: Person.keyPathsAffectingDisplayName()
|
||||
)
|
||||
|
||||
let pets = Relationship.ToManyUnordered<Animal>("pets", inverse: { $0.master })
|
||||
|
||||
private static func setTitle(_ partialObject: PartialObject<Person>, _ newValue: String) {
|
||||
|
||||
partialObject.setPrimitiveValue(newValue, for: { $0.title })
|
||||
partialObject.setPrimitiveValue(nil, for: { $0.displayName })
|
||||
}
|
||||
|
||||
private static func setName(_ partialObject: PartialObject<Person>, _ newValue: String) {
|
||||
|
||||
partialObject.setPrimitiveValue(newValue, for: { $0.name })
|
||||
partialObject.setPrimitiveValue(nil, for: { $0.displayName })
|
||||
}
|
||||
|
||||
static func getDisplayName(_ partialObject: PartialObject<Person>) -> String? {
|
||||
|
||||
if let displayName = partialObject.primitiveValue(for: { $0.displayName }) {
|
||||
|
||||
return displayName
|
||||
}
|
||||
let title = partialObject.value(for: { $0.title })
|
||||
let name = partialObject.value(for: { $0.name })
|
||||
let displayName = "\(title) \(name)"
|
||||
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
|
||||
return displayName
|
||||
}
|
||||
|
||||
static func keyPathsAffectingDisplayName() -> Set<String> {
|
||||
|
||||
return [
|
||||
self.keyPath({ $0.title }),
|
||||
self.keyPath({ $0.name })
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - DynamicModelTests
|
||||
|
||||
class DynamicModelTests: BaseTestDataTestCase {
|
||||
|
||||
func testDynamicModels_CanBeDeclaredCorrectly() {
|
||||
|
||||
let dataStack = DataStack(
|
||||
CoreStoreSchema(
|
||||
modelVersion: "V1",
|
||||
entities: [
|
||||
Entity<Animal>("Animal"),
|
||||
Entity<Dog>("Dog"),
|
||||
Entity<Person>("Person")
|
||||
],
|
||||
versionLock: [
|
||||
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
|
||||
"Dog": [0xe3f0afeb109b283a, 0x29998d292938eb61, 0x6aab788333cfc2a3, 0x492ff1d295910ea7],
|
||||
"Person": [0x66d8bbfd8b21561f, 0xcecec69ecae3570f, 0xc4b73d71256214ef, 0x89b99bfe3e013e8b]
|
||||
]
|
||||
)
|
||||
)
|
||||
self.prepareStack(dataStack, configurations: [nil]) { (stack) in
|
||||
|
||||
let k1 = Animal.keyPath({ $0.species })
|
||||
XCTAssertEqual(k1, "species")
|
||||
|
||||
let k2 = Dog.keyPath({ $0.species })
|
||||
XCTAssertEqual(k2, "species")
|
||||
|
||||
let k3 = Dog.keyPath({ $0.nickname })
|
||||
XCTAssertEqual(k3, "nickname")
|
||||
|
||||
let updateDone = self.expectation(description: "update-done")
|
||||
let fetchDone = self.expectation(description: "fetch-done")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let animal = transaction.create(Into<Animal>())
|
||||
XCTAssertEqual(animal.species.value, "Swift")
|
||||
XCTAssertTrue(type(of: animal.species.value) == String.self)
|
||||
|
||||
animal.species .= "Sparrow"
|
||||
XCTAssertEqual(animal.species.value, "Sparrow")
|
||||
|
||||
animal.color .= .yellow
|
||||
XCTAssertEqual(animal.color.value, Color.yellow)
|
||||
|
||||
let dog = transaction.create(Into<Dog>())
|
||||
XCTAssertEqual(dog.species.value, "Swift")
|
||||
XCTAssertEqual(dog.nickname.value, nil)
|
||||
XCTAssertEqual(dog.age.value, 1)
|
||||
|
||||
dog.species .= "Dog"
|
||||
XCTAssertEqual(dog.species.value, "Dog")
|
||||
|
||||
dog.nickname .= "Spot"
|
||||
XCTAssertEqual(dog.nickname.value, "Spot")
|
||||
|
||||
let person = transaction.create(Into<Person>())
|
||||
XCTAssertTrue(person.pets.value.isEmpty)
|
||||
|
||||
XCTAssertEqual(
|
||||
object_getClass(person.rawObject!).keyPathsForValuesAffectingValue(forKey: "displayName"),
|
||||
["title", "name"]
|
||||
)
|
||||
|
||||
person.name .= "Joe"
|
||||
|
||||
XCTAssertEqual(person.rawObject!.value(forKey: "name") as! String?, "Joe")
|
||||
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "Mr. Joe")
|
||||
|
||||
person.rawObject!.setValue("AAAA", forKey: "displayName")
|
||||
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "AAAA")
|
||||
|
||||
person.name .= "John"
|
||||
XCTAssertEqual(person.name.value, "John")
|
||||
XCTAssertEqual(person.displayName.value, "Mr. John") // Custom getter
|
||||
|
||||
person.title .= "Sir"
|
||||
XCTAssertEqual(person.displayName.value, "Sir John")
|
||||
|
||||
person.pets.value.insert(dog)
|
||||
XCTAssertEqual(person.pets.count, 1)
|
||||
XCTAssertEqual(person.pets.value.first, dog)
|
||||
XCTAssertEqual(person.pets.value.first?.master.value, person)
|
||||
XCTAssertEqual(dog.master.value, person)
|
||||
XCTAssertEqual(dog.master.value?.pets.value.first, dog)
|
||||
},
|
||||
success: {
|
||||
|
||||
updateDone.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
let p1 = Animal.where({ $0.species == "Sparrow" })
|
||||
XCTAssertEqual(p1.predicate, NSPredicate(format: "%K == %@", "species", "Sparrow"))
|
||||
|
||||
let bird = transaction.fetchOne(From<Animal>(), p1)
|
||||
XCTAssertNotNil(bird)
|
||||
XCTAssertEqual(bird!.species.value, "Sparrow")
|
||||
|
||||
let p2 = Dog.where({ $0.nickname == "Spot" })
|
||||
XCTAssertEqual(p2.predicate, NSPredicate(format: "%K == %@", "nickname", "Spot"))
|
||||
|
||||
let dog = transaction.fetchOne(From<Dog>(), p2)
|
||||
XCTAssertNotNil(dog)
|
||||
XCTAssertEqual(dog!.nickname.value, "Spot")
|
||||
XCTAssertEqual(dog!.species.value, "Dog")
|
||||
|
||||
let person = transaction.fetchOne(From<Person>())
|
||||
XCTAssertNotNil(person)
|
||||
XCTAssertEqual(person!.pets.value.first, dog)
|
||||
|
||||
let p3 = Dog.where({ $0.age == 10 })
|
||||
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
|
||||
},
|
||||
success: {
|
||||
|
||||
fetchDone.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
|
||||
@nonobjc
|
||||
func prepareStack(_ dataStack: DataStack, configurations: [ModelConfiguration] = [nil], _ closure: (_ dataStack: DataStack) -> Void) {
|
||||
|
||||
do {
|
||||
|
||||
try configurations.forEach { (configuration) in
|
||||
|
||||
try dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileURL: SQLiteStore.defaultRootDirectory
|
||||
.appendingPathComponent(UUID().uuidString)
|
||||
.appendingPathComponent("\(type(of: self))_\((configuration ?? "-null-")).sqlite"),
|
||||
configuration: configuration,
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
catch let error as NSError {
|
||||
|
||||
XCTFail(error.coreStoreDumpString)
|
||||
}
|
||||
closure(dataStack)
|
||||
}
|
||||
}
|
||||
174
CoreStoreTests/ErrorTests.swift
Normal file
@@ -0,0 +1,174 @@
|
||||
//
|
||||
// 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 as NSDictionary, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.unknownError.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDifferentStorageExistsAtURLErrors_BridgeCorrectly() {
|
||||
|
||||
let dummyURL = URL(string: "file:///test1/test2.sqlite")!
|
||||
|
||||
let error = CoreStoreError.differentStorageExistsAtURL(existingPersistentStoreURL: dummyURL)
|
||||
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.differentStorageExistsAtURL.rawValue)
|
||||
|
||||
let userInfo: NSDictionary = [
|
||||
"existingPersistentStoreURL": dummyURL
|
||||
]
|
||||
let objcError = error.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError.bridgeToSwift)
|
||||
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError.code, CoreStoreErrorCode.differentStorageExistsAtURL.rawValue)
|
||||
XCTAssertEqual(objcError.userInfo as NSDictionary, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.differentStorageExistsAtURL.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatMappingModelNotFoundErrors_BridgeCorrectly() {
|
||||
|
||||
let dummyURL = URL(string: "file:///test1/test2.sqlite")!
|
||||
|
||||
let schemaHistory = SchemaHistory(
|
||||
XcodeDataModelSchema.from(
|
||||
modelName: "Model",
|
||||
bundle: Bundle(for: type(of: self))
|
||||
)
|
||||
)
|
||||
let version = "1.0.0"
|
||||
|
||||
let error = CoreStoreError.mappingModelNotFound(localStoreURL: dummyURL, targetModel: schemaHistory.rawModel, targetModelVersion: version)
|
||||
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.mappingModelNotFound.rawValue)
|
||||
|
||||
let userInfo: NSDictionary = [
|
||||
"localStoreURL": dummyURL,
|
||||
"targetModel": schemaHistory.rawModel,
|
||||
"targetModelVersion": version
|
||||
]
|
||||
let objcError = error.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError.bridgeToSwift)
|
||||
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError.code, CoreStoreErrorCode.mappingModelNotFound.rawValue)
|
||||
XCTAssertEqual(objcError.userInfo as NSDictionary, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.mappingModelNotFound.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatProgressiveMigrationRequiredErrors_BridgeCorrectly() {
|
||||
|
||||
let dummyURL = URL(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 as NSDictionary, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.progressiveMigrationRequired.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatInternalErrorErrors_BridgeCorrectly() {
|
||||
|
||||
let internalError = NSError(
|
||||
domain: "com.dummy",
|
||||
code: 123,
|
||||
userInfo: [
|
||||
"key1": "value1",
|
||||
"key2": 2,
|
||||
"key3": Date()
|
||||
]
|
||||
)
|
||||
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 as NSDictionary, userInfo)
|
||||
|
||||
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
|
||||
XCTAssertEqual(error, objcError2.bridgeToSwift)
|
||||
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
|
||||
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.internalError.rawValue)
|
||||
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
|
||||
}
|
||||
}
|
||||
3400
CoreStoreTests/FetchTests.swift
Normal file
429
CoreStoreTests/FromTests.swift
Normal file
@@ -0,0 +1,429 @@
|
||||
//
|
||||
// FromTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - FromTests
|
||||
|
||||
final class FromTests: BaseTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let from = From<NSManagedObject>()
|
||||
XCTAssert(from.entityClass === NSManagedObject.self)
|
||||
XCTAssertNil(from.configurations)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
XCTAssert(from.entityClass === TestEntity1.self)
|
||||
XCTAssertNil(from.configurations)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
XCTAssert(from.entityClass === TestEntity1.self)
|
||||
XCTAssertEqual(from.configurations?.count, 1)
|
||||
XCTAssertEqual(from.configurations?[0], "Config1")
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>(nil, "Config1")
|
||||
XCTAssert(from.entityClass === TestEntity1.self)
|
||||
XCTAssertEqual(from.configurations?.count, 2)
|
||||
XCTAssertEqual(from.configurations?[0], nil)
|
||||
XCTAssertEqual(from.configurations?[1], "Config1")
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultConfigurations() {
|
||||
|
||||
self.prepareStack { (dataStack) in
|
||||
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForSingleConfigurations() {
|
||||
|
||||
self.prepareStack(configurations: ["Config1"]) { (dataStack) in
|
||||
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForDefaultAndCustomConfigurations() {
|
||||
|
||||
self.prepareStack(configurations: [nil, "Config1"]) { (dataStack) in
|
||||
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(Set(affectedConfigurations), ["PF_DEFAULT_CONFIGURATION_NAME", "Config1"] as Set)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatFromClauses_ApplyToFetchRequestsCorrectlyForMultipleConfigurations() {
|
||||
|
||||
self.prepareStack(configurations: ["Config1", "Config2"]) { (dataStack) in
|
||||
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity1>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>()
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config2"])
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config1")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = self.expectLogger([.logWarning]) {
|
||||
|
||||
from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
}
|
||||
XCTAssertFalse(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertTrue(affectedConfigurations.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let from = From<TestEntity2>("Config2")
|
||||
|
||||
let request = CoreStoreFetchRequest()
|
||||
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
|
||||
XCTAssertTrue(storesFound)
|
||||
XCTAssertNotNil(request.entity)
|
||||
XCTAssertNotNil(request.safeAffectedStores)
|
||||
|
||||
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
|
||||
|
||||
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
|
||||
XCTAssertEqual(affectedConfigurations, ["Config2"])
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
81
CoreStoreTests/GroupByTests.swift
Normal file
@@ -0,0 +1,81 @@
|
||||
//
|
||||
// GroupByTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
//MARK: - GroupByTests
|
||||
|
||||
final class GroupByTests: BaseTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatGroupByClauses_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let groupBy = GroupBy()
|
||||
XCTAssertEqual(groupBy, GroupBy([] as [String]))
|
||||
XCTAssertNotEqual(groupBy, GroupBy("key"))
|
||||
XCTAssertTrue(groupBy.keyPaths.isEmpty)
|
||||
}
|
||||
do {
|
||||
|
||||
let groupBy = GroupBy("key1")
|
||||
XCTAssertEqual(groupBy, GroupBy("key1"))
|
||||
XCTAssertEqual(groupBy, GroupBy(["key1"]))
|
||||
XCTAssertNotEqual(groupBy, GroupBy("key2"))
|
||||
XCTAssertEqual(groupBy.keyPaths, ["key1"])
|
||||
}
|
||||
do {
|
||||
|
||||
let groupBy = GroupBy("key1", "key2")
|
||||
XCTAssertEqual(groupBy, GroupBy("key1", "key2"))
|
||||
XCTAssertEqual(groupBy, GroupBy(["key1", "key2"]))
|
||||
XCTAssertNotEqual(groupBy, GroupBy("key2", "key1"))
|
||||
XCTAssertEqual(groupBy.keyPaths, ["key1", "key2"])
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatGroupByClauses_ApplyToFetchRequestsCorrectly() {
|
||||
|
||||
self.prepareStack { (dataStack) in
|
||||
|
||||
let groupBy = GroupBy(#keyPath(TestEntity1.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)
|
||||
}
|
||||
}
|
||||
}
|
||||
1132
CoreStoreTests/ImportTests.swift
Normal file
139
CoreStoreTests/IntoTests.swift
Normal file
@@ -0,0 +1,139 @@
|
||||
//
|
||||
// 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(DataStack.defaultConfigurationName, "PF_DEFAULT_CONFIGURATION_NAME")
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatIntoClauses_ConfigureCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let into = Into<NSManagedObject>()
|
||||
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.self)
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatIntoClauses_AreEquatable() {
|
||||
|
||||
do {
|
||||
|
||||
let into = Into<NSManagedObject>()
|
||||
XCTAssertEqual(into, Into<NSManagedObject>())
|
||||
XCTAssertEqual(into, Into(NSManagedObject.self))
|
||||
XCTAssertNotEqual(into, Into<NSManagedObject>(TestEntity1.self))
|
||||
XCTAssertNotEqual(into, Into<NSManagedObject>("Config1"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into<TestEntity1>()
|
||||
XCTAssertEqual(into, Into<TestEntity1>())
|
||||
XCTAssertEqual(into, Into(TestEntity1.self))
|
||||
XCTAssertNotEqual(into, Into<TestEntity1>("Config1"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self)
|
||||
XCTAssert(into == Into<TestEntity1>())
|
||||
XCTAssertEqual(into, Into(TestEntity1.self))
|
||||
XCTAssertFalse(into == Into<TestEntity1>("Config1"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into<TestEntity1>("Config1")
|
||||
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
|
||||
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self, "Config1")
|
||||
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
|
||||
XCTAssertEqual(into, Into<TestEntity1>("Config1"))
|
||||
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
|
||||
}
|
||||
do {
|
||||
|
||||
let into = Into(TestEntity1.self, "Config1")
|
||||
XCTAssertEqual(into, Into<TestEntity1>("Config1"))
|
||||
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
|
||||
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatIntoClauses_BridgeCorrectly() {
|
||||
|
||||
do {
|
||||
|
||||
let into = Into<NSManagedObject>()
|
||||
let objcInto = into.bridgeToObjectiveC
|
||||
XCTAssertEqual(into, objcInto.bridgeToSwift)
|
||||
}
|
||||
}
|
||||
}
|
||||
676
CoreStoreTests/ListObserverTests.swift
Normal file
@@ -0,0 +1,676 @@
|
||||
//
|
||||
// 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
|
||||
|
||||
|
||||
// MARK: - ListObserverTests
|
||||
|
||||
@available(OSX 10.12, *)
|
||||
class ListObserverTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatListObservers_CanReceiveInsertNotifications() {
|
||||
|
||||
self.prepareStack { (stack) in
|
||||
|
||||
let observer = TestListObserver()
|
||||
let monitor = stack.monitorSectionedList(
|
||||
From<TestEntity1>(),
|
||||
SectionBy(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
monitor.addObserver(observer)
|
||||
|
||||
XCTAssertFalse(monitor.hasSections())
|
||||
XCTAssertFalse(monitor.hasObjects())
|
||||
XCTAssertTrue(monitor.objectsInAllSections().isEmpty)
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectation(
|
||||
forNotification: "listMonitorWillChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didInsertSectionExpectation = self.expectation(
|
||||
forNotification: "listMonitor:didInsertSection:toSectionIndex:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 1)
|
||||
XCTAssertEqual(
|
||||
((note.userInfo as NSDictionary?) ?? [:]),
|
||||
[
|
||||
"sectionInfo": monitor.sectionInfoAtIndex(0),
|
||||
"sectionIndex": 0
|
||||
] as NSDictionary
|
||||
)
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 1
|
||||
}
|
||||
)
|
||||
let didInsertObjectExpectation = self.expectation(
|
||||
forNotification: "listMonitor:didInsertObject:toIndexPath:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 2)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
["indexPath", "object"]
|
||||
)
|
||||
|
||||
let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
|
||||
XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
|
||||
|
||||
let object = userInfo?["object"] as? TestEntity1
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(value: 1))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!)
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 2
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectation(
|
||||
forNotification: "listMonitorDidChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 3
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
let object = transaction.create(Into<TestEntity1>())
|
||||
object.testBoolean = NSNumber(value: true)
|
||||
object.testNumber = NSNumber(value: 1)
|
||||
object.testDecimal = NSDecimalNumber(string: "1")
|
||||
object.testString = "nil:TestEntity1:1"
|
||||
object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!
|
||||
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
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(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.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.expectation(
|
||||
forNotification: "listMonitorWillChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
|
||||
let didUpdateObjectExpectation = self.expectation(
|
||||
forNotification: "listMonitor:didUpdateObject:atIndexPath:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssert(events == 1 || events == 2)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
["indexPath", "object"]
|
||||
)
|
||||
|
||||
let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
let object = userInfo?["object"] as? TestEntity1
|
||||
|
||||
switch object?.testEntityID {
|
||||
|
||||
case NSNumber(value: 101)?:
|
||||
XCTAssertEqual(indexPath?.index(atPosition: 0), 1)
|
||||
XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
|
||||
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(value: 11))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:11")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!)
|
||||
|
||||
case NSNumber(value: 102)?:
|
||||
XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
|
||||
XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
|
||||
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(value: false))
|
||||
XCTAssertEqual(object?.testNumber, NSNumber(value: 22))
|
||||
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22"))
|
||||
XCTAssertEqual(object?.testString, "nil:TestEntity1:22")
|
||||
XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!)
|
||||
XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!)
|
||||
|
||||
default:
|
||||
XCTFail()
|
||||
}
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 1 || events == 2
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectation(
|
||||
forNotification: "listMonitorDidChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 3)
|
||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 3
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
if let object = transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
|
||||
|
||||
object.testNumber = NSNumber(value: 11)
|
||||
object.testDecimal = NSDecimalNumber(string: "11")
|
||||
object.testString = "nil:TestEntity1:11"
|
||||
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
if let object = transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
|
||||
object.testNumber = NSNumber(value: 22)
|
||||
object.testDecimal = NSDecimalNumber(string: "22")
|
||||
object.testString = "nil:TestEntity1:22"
|
||||
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
|
||||
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
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(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
monitor.addObserver(observer)
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectation(
|
||||
forNotification: "listMonitorWillChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didMoveObjectExpectation = self.expectation(
|
||||
forNotification: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 1)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
["fromIndexPath", "toIndexPath", "object"]
|
||||
)
|
||||
|
||||
let fromIndexPath = userInfo?["fromIndexPath"] as? NSIndexPath
|
||||
XCTAssertEqual(fromIndexPath?.index(atPosition: 0), 0)
|
||||
XCTAssertEqual(fromIndexPath?.index(atPosition: 1), 0)
|
||||
|
||||
let toIndexPath = userInfo?["toIndexPath"] as? NSIndexPath
|
||||
XCTAssertEqual(toIndexPath?.index(atPosition: 0), 1)
|
||||
XCTAssertEqual(toIndexPath?.index(atPosition: 1), 1)
|
||||
|
||||
let object = userInfo?["object"] as? TestEntity1
|
||||
XCTAssertEqual(object?.testEntityID, NSNumber(value: 102))
|
||||
XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
|
||||
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 1
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectation(
|
||||
forNotification: "listMonitorDidChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 2)
|
||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 2
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
if let object = transaction.fetchOne(
|
||||
From<TestEntity1>(),
|
||||
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
|
||||
|
||||
object.testBoolean = NSNumber(value: true)
|
||||
}
|
||||
else {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
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(#keyPath(TestEntity1.testBoolean)),
|
||||
OrderBy(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
|
||||
)
|
||||
monitor.addObserver(observer)
|
||||
|
||||
var events = 0
|
||||
|
||||
let willChangeExpectation = self.expectation(
|
||||
forNotification: "listMonitorWillChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 0)
|
||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 0
|
||||
}
|
||||
)
|
||||
let didUpdateObjectExpectation = self.expectation(
|
||||
forNotification: "listMonitor:didDeleteObject:fromIndexPath:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssert(events == 1 || events == 2)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
["indexPath", "object"]
|
||||
)
|
||||
|
||||
let indexPath = userInfo?["indexPath"] as? NSIndexPath
|
||||
|
||||
XCTAssertEqual(indexPath?.section, 0)
|
||||
XCTAssert(indexPath?.index(atPosition: 1) == 0 || indexPath?.index(atPosition: 1) == 1)
|
||||
|
||||
let object = userInfo?["object"] as? TestEntity1
|
||||
XCTAssertEqual(object?.isDeleted, true)
|
||||
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 1 || events == 2
|
||||
}
|
||||
)
|
||||
let didDeleteSectionExpectation = self.expectation(
|
||||
forNotification: "listMonitor:didDeleteSection:fromSectionIndex:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 3)
|
||||
|
||||
let userInfo = note.userInfo
|
||||
XCTAssertNotNil(userInfo)
|
||||
XCTAssertEqual(
|
||||
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
|
||||
["sectionInfo", "sectionIndex"]
|
||||
)
|
||||
|
||||
let sectionInfo = userInfo?["sectionInfo"] as? NSFetchedResultsSectionInfo
|
||||
XCTAssertNotNil(sectionInfo)
|
||||
XCTAssertEqual(sectionInfo?.name, "0")
|
||||
|
||||
let sectionIndex = userInfo?["sectionIndex"]
|
||||
XCTAssertEqual(sectionIndex as? NSNumber, NSNumber(value: 0))
|
||||
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 3
|
||||
}
|
||||
)
|
||||
let didChangeExpectation = self.expectation(
|
||||
forNotification: "listMonitorDidChange:",
|
||||
object: observer,
|
||||
handler: { (note) -> Bool in
|
||||
|
||||
XCTAssertEqual(events, 4)
|
||||
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
|
||||
defer {
|
||||
|
||||
events += 1
|
||||
}
|
||||
return events == 4
|
||||
}
|
||||
)
|
||||
let saveExpectation = self.expectation(description: "save")
|
||||
stack.perform(
|
||||
asynchronous: { (transaction) -> Bool in
|
||||
|
||||
let count = transaction.deleteAll(
|
||||
From<TestEntity1>(),
|
||||
Where(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
|
||||
)
|
||||
XCTAssertEqual(count, 2)
|
||||
return transaction.hasChanges
|
||||
},
|
||||
success: { (hasChanges) in
|
||||
|
||||
XCTAssertTrue(hasChanges)
|
||||
saveExpectation.fulfill()
|
||||
},
|
||||
failure: { _ in
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
)
|
||||
self.waitAndCheckExpectations()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: TestListObserver
|
||||
|
||||
@available(OSX 10.12, *)
|
||||
class TestListObserver: ListSectionObserver {
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
typealias ListEntityType = TestEntity1
|
||||
|
||||
func listMonitorWillChange(_ monitor: ListMonitor<TestEntity1>) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "listMonitorWillChange:"),
|
||||
object: self,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitorDidChange(_ monitor: ListMonitor<TestEntity1>) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "listMonitorDidChange:"),
|
||||
object: self,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitorWillRefetch(_ monitor: ListMonitor<TestEntity1>) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "listMonitorWillRefetch:"),
|
||||
object: self,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitorDidRefetch(_ monitor: ListMonitor<TestEntity1>) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "listMonitorDidRefetch:"),
|
||||
object: self,
|
||||
userInfo: [:]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObjectObserver
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didInsertObject object: TestEntity1, toIndexPath indexPath: IndexPath) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object,
|
||||
"indexPath": indexPath
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteObject object: TestEntity1, fromIndexPath indexPath: IndexPath) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object,
|
||||
"indexPath": indexPath
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didUpdateObject object: TestEntity1, atIndexPath indexPath: IndexPath) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
|
||||
object: self,
|
||||
userInfo: [
|
||||
"object": object,
|
||||
"indexPath": indexPath
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didMoveObject object: TestEntity1, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "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) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
|
||||
object: self,
|
||||
userInfo: [
|
||||
"sectionInfo": sectionInfo,
|
||||
"sectionIndex": sectionIndex
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
|
||||
object: self,
|
||||
userInfo: [
|
||||
"sectionInfo": sectionInfo,
|
||||
"sectionIndex": sectionIndex
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
147
CoreStoreTests/MigrationChainTests.swift
Normal file
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// MigrationChainTests.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright © 2016 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
|
||||
@testable
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - MigrationChainTests
|
||||
|
||||
final class MigrationChainTests: XCTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatNilMigrationChains_HaveNoVersions() {
|
||||
|
||||
let chain: MigrationChain = nil
|
||||
XCTAssertTrue(chain.isValid)
|
||||
XCTAssertTrue(chain.isEmpty)
|
||||
|
||||
XCTAssertFalse(chain.contains("version1"))
|
||||
XCTAssertNil(chain.nextVersionFrom("version1"))
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatStringMigrationChains_HaveOneVersion() {
|
||||
|
||||
let chain: MigrationChain = "version1"
|
||||
XCTAssertTrue(chain.isValid)
|
||||
XCTAssertTrue(chain.isEmpty)
|
||||
|
||||
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.isValid)
|
||||
XCTAssertFalse(chain.isEmpty)
|
||||
|
||||
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.isValid)
|
||||
XCTAssertFalse(chain.isEmpty)
|
||||
|
||||
XCTAssertTrue(chain.contains("version1"))
|
||||
XCTAssertTrue(chain.contains("version2"))
|
||||
XCTAssertTrue(chain.contains("version3"))
|
||||
XCTAssertTrue(chain.contains("version4"))
|
||||
XCTAssertFalse(chain.contains("version5"))
|
||||
|
||||
XCTAssertEqual(chain.nextVersionFrom("version1"), "version4")
|
||||
XCTAssertEqual(chain.nextVersionFrom("version2"), "version3")
|
||||
XCTAssertEqual(chain.nextVersionFrom("version3"), "version4")
|
||||
XCTAssertNil(chain.nextVersionFrom("version4"))
|
||||
XCTAssertNil(chain.nextVersionFrom("version5"))
|
||||
|
||||
// The cases below will trigger assertion failures internally
|
||||
|
||||
// let linearLoopChain: MigrationChain = ["version1", "version2", "version1", "version3", "version4"]
|
||||
// XCTAssertFalse(linearLoopChain.valid, "linearLoopChain.valid")
|
||||
//
|
||||
// let treeAmbiguousChain: MigrationChain = [
|
||||
// "version1": "version4",
|
||||
// "version2": "version3",
|
||||
// "version1": "version2",
|
||||
// "version3": "version4"
|
||||
// ]
|
||||
// XCTAssertFalse(treeAmbiguousChain.valid, "treeAmbiguousChain.valid")
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatMigrationChains_AreEquatable() {
|
||||
|
||||
do {
|
||||
|
||||
let chain1: MigrationChain = nil
|
||||
let chain2: MigrationChain = []
|
||||
let chain3: MigrationChain = [:]
|
||||
XCTAssertEqual(chain1, chain2)
|
||||
XCTAssertEqual(chain2, chain3)
|
||||
XCTAssertEqual(chain3, chain1)
|
||||
}
|
||||
do {
|
||||
|
||||
let chain1: MigrationChain = "version1"
|
||||
let chain2: MigrationChain = ["version1"]
|
||||
XCTAssertEqual(chain1, chain2)
|
||||
}
|
||||
do {
|
||||
|
||||
let chain1: MigrationChain = ["version1", "version2", "version3", "version4"]
|
||||
let chain2: MigrationChain = [
|
||||
"version1": "version2",
|
||||
"version2": "version3",
|
||||
"version3": "version4"
|
||||
]
|
||||
XCTAssertEqual(chain1, chain2)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,15 +1,23 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
|
||||
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9057" systemVersion="15B42" minimumToolsVersion="Xcode 4.3">
|
||||
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="11759" systemVersion="16C67" minimumToolsVersion="Xcode 4.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
|
||||
<entity name="TestEntity1AAA" representedClassName="CoreStoreTests.TestEntity1" syncable="YES">
|
||||
<attribute name="testDate" optional="YES" attributeType="Date" syncable="YES"/>
|
||||
<attribute name="testEntityID" attributeType="Integer 64" syncable="YES"/>
|
||||
<attribute name="testNumber" optional="YES" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
|
||||
<attribute name="testBoolean" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="testData" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="testDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="testDecimal" optional="YES" attributeType="Decimal" syncable="YES"/>
|
||||
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="testNil" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="testNumber" optional="YES" attributeType="Integer 32" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="testString" optional="YES" attributeType="String" syncable="YES"/>
|
||||
</entity>
|
||||
<entity name="TestEntity2" representedClassName="CoreStoreTests.TestEntity2" 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="testBoolean" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="testData" optional="YES" attributeType="Binary" syncable="YES"/>
|
||||
<attribute name="testDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="testDecimal" optional="YES" attributeType="Decimal" syncable="YES"/>
|
||||
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
|
||||
<attribute name="testNil" optional="YES" attributeType="String" syncable="YES"/>
|
||||
<attribute name="testNumber" optional="YES" attributeType="Integer 32" usesScalarValueType="NO" 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>
|
||||