Compare commits
936 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e720504855 | ||
|
|
ee51c5ad9c | ||
|
|
4ac7df7364 | ||
|
|
56f873ccb3 | ||
|
|
1f90f846f3 | ||
|
|
4a97d5a8dc | ||
|
|
0eb9b6393d | ||
|
|
56d0ea46ea | ||
|
|
a7568eebdb | ||
|
|
4049e1944a | ||
|
|
73b7fcd907 | ||
|
|
74c1a97af4 | ||
|
|
97f2a53124 | ||
|
|
b6db872be0 | ||
|
|
0d9299f900 | ||
|
|
7f928dc684 | ||
|
|
231e138ab0 | ||
|
|
361dba58c6 | ||
|
|
e1b03b4a89 | ||
|
|
0df6c737c1 | ||
|
|
12c5aeaaa4 | ||
|
|
dd3fb17dd0 | ||
|
|
627a5d4355 | ||
|
|
58629bc1df | ||
|
|
f925803b93 | ||
|
|
843adf21f7 | ||
|
|
2d1b1e0592 | ||
|
|
38e9878c04 | ||
|
|
8cb8b95c2e | ||
|
|
8e4e308ccc | ||
|
|
f0f4049798 | ||
|
|
c20fe4ac17 | ||
|
|
e9c3312612 | ||
|
|
92ad895044 | ||
|
|
bcc2d9def3 | ||
|
|
43f61359da | ||
|
|
5e37ee4566 | ||
|
|
c544e0cce8 | ||
|
|
f119a3adec | ||
|
|
c951cb87a3 | ||
|
|
08147806a0 | ||
|
|
4beb11519e | ||
|
|
b7ebda4487 | ||
|
|
b4489301ac | ||
|
|
c025e5acc6 | ||
|
|
57745f36a8 | ||
|
|
eef1c99f11 | ||
|
|
9a19919392 | ||
|
|
3e2d62fe67 | ||
|
|
6f275eb63a | ||
|
|
b12dba4d15 | ||
|
|
4ee1b04523 | ||
|
|
b1decc9853 | ||
|
|
c2e4c033ef | ||
|
|
e12223df85 | ||
|
|
468922d5ed | ||
|
|
6b9a4b480b | ||
|
|
81b482e28b | ||
|
|
c112a84c0a | ||
|
|
88ab0b5e15 | ||
|
|
717cb75720 | ||
|
|
998938490c | ||
|
|
f3beca8769 | ||
|
|
4baeb6d922 | ||
|
|
98d860aff6 | ||
|
|
11a9e3991c | ||
|
|
f380d9dc25 | ||
|
|
d546ff154f | ||
|
|
f21597d332 | ||
|
|
d971c3a2ac | ||
|
|
80166a42bb | ||
|
|
3d8bdf1cf3 | ||
|
|
1ddce5aa8d | ||
|
|
7a1600fac4 | ||
|
|
e4e664d8ce | ||
|
|
145edd5a7d | ||
|
|
f5fed063ee | ||
|
|
a267395618 | ||
|
|
326b897b06 | ||
|
|
0b18366ab1 | ||
|
|
ddf599ba85 | ||
|
|
6e3e540d0a | ||
|
|
bd066f0cef | ||
|
|
9764f33086 | ||
|
|
0c19c878c5 | ||
|
|
1b8e517b5a | ||
|
|
2818a778a4 | ||
|
|
64a0264354 | ||
|
|
7932625644 | ||
|
|
4619fbbec3 | ||
|
|
6b64eb7650 | ||
|
|
f5a165d47d | ||
|
|
12c58e3955 | ||
|
|
5af0d17de4 | ||
|
|
81dfb8e3e5 | ||
|
|
d5114fc4bc | ||
|
|
693fc7fbbb | ||
|
|
b073b7e795 | ||
|
|
56d9719984 | ||
|
|
953c9723a8 | ||
|
|
c5a996d5ed | ||
|
|
53a83d823e | ||
|
|
6d75dcbc32 | ||
|
|
0345ee9c94 | ||
|
|
4016b5dc83 | ||
|
|
64b5e102aa | ||
|
|
8e3c44c072 | ||
|
|
12dc32f7e6 | ||
|
|
b4c38caf1e | ||
|
|
266b1a9913 | ||
|
|
1740d168c8 | ||
|
|
d42b397090 | ||
|
|
058cc1915b | ||
|
|
02d5bf85ae | ||
|
|
f0dac2454c | ||
|
|
d4deed0cf7 | ||
|
|
79655ffde5 | ||
|
|
cf46b45e8e | ||
|
|
ed3d21db77 | ||
|
|
f16e3593c0 | ||
|
|
67bb9340c7 | ||
|
|
5d49bd79f2 | ||
|
|
9f397b4337 | ||
|
|
7508d150d6 | ||
|
|
239db8168d | ||
|
|
30ab4386bf | ||
|
|
08053ccb15 | ||
|
|
367cfea8a7 | ||
|
|
5d3b7e3dab | ||
|
|
fe7e6e7b84 | ||
|
|
b2dba0d6fd | ||
|
|
6f290213fa | ||
|
|
0ab52d2f43 | ||
|
|
bf8a1062e0 | ||
|
|
0e254867b6 | ||
|
|
0376ac6908 | ||
|
|
b3421888a6 | ||
|
|
7b3f4ae0a4 | ||
|
|
b4e12cc922 | ||
|
|
6c282b18af | ||
|
|
ebbde8b7b6 | ||
|
|
a1407e4121 | ||
|
|
99b871b97a | ||
|
|
1cd3c4fcf4 | ||
|
|
e09ac9ee00 | ||
|
|
4b0d134acb | ||
|
|
eeec3979ee | ||
|
|
5b365c642d | ||
|
|
635201868a | ||
|
|
db4426e6b9 | ||
|
|
41902fee2e | ||
|
|
9239370793 | ||
|
|
a4d2f326a5 | ||
|
|
9f54ec26e2 | ||
|
|
9425cb56d7 | ||
|
|
1de56d2776 | ||
|
|
b3261ea930 | ||
|
|
88dd7aef72 | ||
|
|
2863605d84 | ||
|
|
94d9116299 | ||
|
|
57ddbbd515 | ||
|
|
78954b9d78 | ||
|
|
4eb06c9858 | ||
|
|
0d634c1dcc | ||
|
|
84f3740ea1 | ||
|
|
6dc48b6af7 | ||
|
|
09ce2816bf | ||
|
|
62e962eebe | ||
|
|
682472c1bd | ||
|
|
46ab70b839 | ||
|
|
ac8304f977 | ||
|
|
5777831565 | ||
|
|
42d1f41939 | ||
|
|
8c30ec3a3d | ||
|
|
237a1b648e | ||
|
|
614f1572c2 | ||
|
|
a48f16aa8c | ||
|
|
cdbadae002 | ||
|
|
4a28a39df6 | ||
|
|
5febf2542d | ||
|
|
c21ab11a41 | ||
|
|
10cd18dbf0 | ||
|
|
8409a13289 | ||
|
|
a9a73fa5c4 | ||
|
|
82cae2e11e | ||
|
|
42caee2418 | ||
|
|
1dea1d0d06 | ||
|
|
d344b9d878 | ||
|
|
95c1ce52cc | ||
|
|
cc346816d6 | ||
|
|
f14b561c33 | ||
|
|
6f655951aa | ||
|
|
ff8fbae568 | ||
|
|
4d4b02d076 | ||
|
|
06c0981ded | ||
|
|
20d581d536 | ||
|
|
45e110755d | ||
|
|
ab40532801 | ||
|
|
40f458a09c | ||
|
|
1ad233ca9d | ||
|
|
3a8e394272 | ||
|
|
1f54daa528 | ||
|
|
f25879b6fe | ||
|
|
8fa3109e91 | ||
|
|
808e8ff97a | ||
|
|
30685d4355 | ||
|
|
7152962026 | ||
|
|
91735e38d1 | ||
|
|
333a50ad9e | ||
|
|
b7a602fc67 | ||
|
|
2ff7ecef96 | ||
|
|
e3f9139304 | ||
|
|
4aa461872f | ||
|
|
3d43314076 | ||
|
|
7b1075b759 | ||
|
|
0c29e07ddb | ||
|
|
ce91cf22f9 | ||
|
|
2dd033c002 | ||
|
|
c7fcba112e | ||
|
|
c8b1c4358f | ||
|
|
5431e2e974 | ||
|
|
83e6082c56 | ||
|
|
c7bdbbde57 | ||
|
|
abf15c8aa8 | ||
|
|
d9db7e4a82 | ||
|
|
65dbc22ddd | ||
|
|
51961dd737 | ||
|
|
18a7055cdf | ||
|
|
4b9fddad6a | ||
|
|
fbe7bd7bf8 | ||
|
|
15edabdbb5 | ||
|
|
f447bcfb95 | ||
|
|
15e5e4fdf6 | ||
|
|
583c6b7249 | ||
|
|
18b933957e | ||
|
|
2c7039232e | ||
|
|
d3b3b5ff4a | ||
|
|
d90e8d1303 | ||
|
|
b55dd13dff | ||
|
|
66dd5b6f27 | ||
|
|
49c4b770eb | ||
|
|
662aaa1e75 | ||
|
|
dd4e47d7f9 | ||
|
|
9ea5073bc8 | ||
|
|
e314db8f56 | ||
|
|
48d936d068 | ||
|
|
b2ff8a15ef | ||
|
|
8a4d1cd7c6 | ||
|
|
8ce26c213d | ||
|
|
f3816b9abf | ||
|
|
21961780d4 | ||
|
|
305e2b61a0 | ||
|
|
0430f66240 | ||
|
|
65772edd00 | ||
|
|
02d7870d75 | ||
|
|
aca1709e13 | ||
|
|
b6ee0b014f | ||
|
|
e37186da73 | ||
|
|
588fa35c84 | ||
|
|
f6614cda66 | ||
|
|
639574d8c2 | ||
|
|
094703155c | ||
|
|
6dd254e713 | ||
|
|
204025721c | ||
|
|
27ffc1d225 | ||
|
|
ba6f0c39d5 | ||
|
|
ab2eac8f6c | ||
|
|
f460a0b30f | ||
|
|
50bc3ace06 | ||
|
|
d2ddf2002f | ||
|
|
b4117eeb02 | ||
|
|
106275b2dd | ||
|
|
08d9298be0 | ||
|
|
ff0c4d94fc | ||
|
|
50e50c0613 | ||
|
|
5c8a0e425b | ||
|
|
03b71caf7e | ||
|
|
7ff29d6086 | ||
|
|
8d86425875 | ||
|
|
97242d9726 | ||
|
|
780ff4e60b | ||
|
|
11743dfb5f | ||
|
|
9eaf85388c | ||
|
|
06635c9d2f | ||
|
|
1d2eef7894 | ||
|
|
85ed815ec2 | ||
|
|
096e5493a6 | ||
|
|
0aa8c03424 | ||
|
|
4ead3c34dd | ||
|
|
645034dde5 | ||
|
|
85706a3c57 | ||
|
|
c5ae4606b9 | ||
|
|
fa682215c5 | ||
|
|
e814733ae9 | ||
|
|
e2236698fa | ||
|
|
be5da632b3 | ||
|
|
dc73cd6dd9 | ||
|
|
7beb3bec75 | ||
|
|
53100b202d | ||
|
|
cc84b1f8bd | ||
|
|
474f52ed2b | ||
|
|
16225fc4c6 | ||
|
|
03bb7619da | ||
|
|
1bfb7451c3 | ||
|
|
3e082d5ed4 | ||
|
|
e45d67252c | ||
|
|
3d427c29c4 | ||
|
|
1068517b94 | ||
|
|
0d23ce1598 | ||
|
|
fd1ce20863 | ||
|
|
2c5fa63f40 | ||
|
|
78e43a37a5 | ||
|
|
fde85a9743 | ||
|
|
dbbc0adae5 | ||
|
|
2a62770552 | ||
|
|
f436b26e8e | ||
|
|
c566226747 | ||
|
|
cd405e038e | ||
|
|
92a37053b0 | ||
|
|
0b57cff27d | ||
|
|
3ebc44b546 | ||
|
|
68f1027ba7 | ||
|
|
005729be85 | ||
|
|
8bac4aa901 | ||
|
|
da170c7e51 | ||
|
|
211e69023e | ||
|
|
2912dcf010 | ||
|
|
3e00a3da06 | ||
|
|
a33e248828 | ||
|
|
75e14fbbed | ||
|
|
9922deac4d | ||
|
|
86be046c9f | ||
|
|
0f10bc3349 | ||
|
|
9685f0aef2 | ||
|
|
1e51000155 | ||
|
|
3bd459bb1a | ||
|
|
c89bc3c227 | ||
|
|
0f405a50aa | ||
|
|
f5e1643ef7 | ||
|
|
f62137fb58 | ||
|
|
37fbedd799 | ||
|
|
28b43f33fa | ||
|
|
81a72f29ea | ||
|
|
f7aaf4fb2a | ||
|
|
a52acf2ebd | ||
|
|
74c64619c3 | ||
|
|
4a5bc6450b | ||
|
|
fe69e7c6c4 | ||
|
|
ccf7c62aad | ||
|
|
f36cb8af63 | ||
|
|
10ccadd96c | ||
|
|
5c0e78bd53 | ||
|
|
8a09688117 | ||
|
|
a366bcf1a3 | ||
|
|
fcd4be9011 | ||
|
|
535eb76adc | ||
|
|
c6e68ac24f | ||
|
|
aff966aac9 | ||
|
|
32743b3aee | ||
|
|
a20ad87583 | ||
|
|
a11915db12 | ||
|
|
961f39a806 | ||
|
|
3096cb784c | ||
|
|
809aa4ff96 | ||
|
|
8d926d25ec | ||
|
|
790454f514 | ||
|
|
9a38707c58 | ||
|
|
fb7e2f7f7f | ||
|
|
f72efc80b2 | ||
|
|
fcda5399da | ||
|
|
fd3a9b00ec | ||
|
|
f56c37f9ee | ||
|
|
5f5000218a | ||
|
|
e8eb309d82 | ||
|
|
d0c3203e63 | ||
|
|
f5b3901caa | ||
|
|
1a99fea820 | ||
|
|
195b60615b | ||
|
|
2c394965b8 | ||
|
|
746d697691 | ||
|
|
5689158b43 | ||
|
|
6fcdf3d011 | ||
|
|
e26573c18e | ||
|
|
801cf8d9f0 | ||
|
|
eced8f2e93 | ||
|
|
3b735d07ec | ||
|
|
5eb5476e3a | ||
|
|
6a42a0054e | ||
|
|
8c437e19b8 | ||
|
|
9cd3b6c879 | ||
|
|
fe135acbec | ||
|
|
997c5bdcfa | ||
|
|
23e12c4539 | ||
|
|
ca9798be14 | ||
|
|
6e01a58c85 | ||
|
|
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 | ||
|
|
e66a13632a | ||
|
|
4c78a309bc | ||
|
|
9055daa58c | ||
|
|
3905423038 | ||
|
|
15353268e2 | ||
|
|
91dd4b6cb3 | ||
|
|
0800b706d6 | ||
|
|
2071ce722e | ||
|
|
df866718cf | ||
|
|
f19a0d29eb | ||
|
|
8f09f90294 | ||
|
|
7bddcaa4a2 | ||
|
|
c12331f403 | ||
|
|
a55a8d389c | ||
|
|
6e88b14237 | ||
|
|
53700a267f | ||
|
|
6c95b010e9 | ||
|
|
1af675e8b7 | ||
|
|
b85f16cf68 | ||
|
|
37bf4179c8 | ||
|
|
240dda6ad6 | ||
|
|
ffe73d0f66 | ||
|
|
43c4998e20 | ||
|
|
0c4e55f061 | ||
|
|
8c8953c507 | ||
|
|
66892f22a3 | ||
|
|
def105e73a | ||
|
|
57bf123fb2 | ||
|
|
8efd6572f0 | ||
|
|
405e861907 | ||
|
|
74c48c7486 | ||
|
|
e58b3b0131 | ||
|
|
b8fad66bfa | ||
|
|
ff1155dac3 | ||
|
|
5137649b78 | ||
|
|
7a95267a9c | ||
|
|
c07435e866 | ||
|
|
f5627f0855 | ||
|
|
d3ea655eb9 | ||
|
|
fc83180af8 | ||
|
|
0c6246475a | ||
|
|
087480a3a8 | ||
|
|
83a04e669e | ||
|
|
d05522bb20 | ||
|
|
9081b36cca | ||
|
|
9322371224 | ||
|
|
26ab6aacd7 | ||
|
|
3e601c1328 | ||
|
|
762b877879 | ||
|
|
084bdc431f | ||
|
|
c63bc389b2 | ||
|
|
71c3abc4f3 | ||
|
|
863d4d1d5a | ||
|
|
06d177e8bd | ||
|
|
c2bbd537cf | ||
|
|
761d349b97 | ||
|
|
6c28594e41 | ||
|
|
c229af19a2 | ||
|
|
8b8a7c7b08 | ||
|
|
eb828d8e42 | ||
|
|
88a24540c6 | ||
|
|
74ded8fb7d | ||
|
|
969f4cefb4 | ||
|
|
1d2947ad26 | ||
|
|
49274c3c94 | ||
|
|
c0fbb2655b | ||
|
|
cefd6f6cbc | ||
|
|
2653f7b977 | ||
|
|
c1163283f5 | ||
|
|
529347b6a2 | ||
|
|
eba7e99374 | ||
|
|
3c4350bd5d | ||
|
|
88b13189e5 | ||
|
|
03da3544f6 | ||
|
|
648b59e1ee | ||
|
|
ab3c8ca812 | ||
|
|
900a31c541 | ||
|
|
656a107767 | ||
|
|
4ce3d5de3c | ||
|
|
dec9757dc2 | ||
|
|
578e4966fc | ||
|
|
b741626574 | ||
|
|
f79a77ab78 | ||
|
|
1f2a70fd42 | ||
|
|
718d2c9b7d | ||
|
|
c5ff02335e | ||
|
|
66b57faa44 | ||
|
|
eef5a3d80b | ||
|
|
bc0757cf06 | ||
|
|
0c0a2a382c | ||
|
|
05b4a7092a | ||
|
|
6aed070e7c | ||
|
|
fcb1d7cbbc | ||
|
|
d074aad111 | ||
|
|
b7685dc747 | ||
|
|
a185bc96c0 | ||
|
|
91aef44803 | ||
|
|
dcfb09eda7 | ||
|
|
9ca83d9c5d | ||
|
|
b00eaf2d0b | ||
|
|
dc8bdf3bad | ||
|
|
4792c4462e | ||
|
|
524757a7cf | ||
|
|
0b6298a802 | ||
|
|
ae77558ae8 | ||
|
|
d33aa9c5cf | ||
|
|
d89319d324 | ||
|
|
cf9af6eef5 | ||
|
|
b9ec66f425 | ||
|
|
2a8df0596d | ||
|
|
83e6a41d88 | ||
|
|
c6fe494fe1 | ||
|
|
7771e047e2 | ||
|
|
5b0439835b | ||
|
|
622c5aa652 | ||
|
|
114b7ce605 | ||
|
|
7451fbe026 | ||
|
|
ee2398fcdf | ||
|
|
8d26501040 | ||
|
|
0709fe95cf | ||
|
|
656a99fe12 | ||
|
|
726e0eabe9 | ||
|
|
032764b9b7 | ||
|
|
5461bb0736 | ||
|
|
4c6bc4f494 | ||
|
|
63a43a6487 | ||
|
|
6a006d5d7c | ||
|
|
399517e357 | ||
|
|
f47adc12b3 | ||
|
|
0de1733efe | ||
|
|
e627cf8161 | ||
|
|
22943d2c76 | ||
|
|
56ea14d53c | ||
|
|
21a524d725 | ||
|
|
d5e769be6c | ||
|
|
ea6b6f2c37 | ||
|
|
c3ef8a4172 | ||
|
|
16aabe1f3b | ||
|
|
10e0cf8d2c | ||
|
|
3a0f53321a | ||
|
|
0da43d5132 | ||
|
|
69f902ef20 | ||
|
|
d04b4ca085 | ||
|
|
8ed6a78609 | ||
|
|
0c9e6afe0e | ||
|
|
ff4629908a | ||
|
|
d3ffe7a8fc | ||
|
|
3919ada428 | ||
|
|
682b13a8d3 | ||
|
|
2f935de04a | ||
|
|
006d5e1402 | ||
|
|
093c1d410f | ||
|
|
7555ff3ad0 | ||
|
|
4c16a961ba | ||
|
|
1121d44d7b |
2
.cocoapods.yml
Normal file
@@ -0,0 +1,2 @@
|
||||
try:
|
||||
project: 'CoreStore.xcworkspace'
|
||||
3
.github/FUNDING.yml
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
# These are supported funding model platforms
|
||||
|
||||
github: [JohnEstropia]
|
||||
10
.gitignore
vendored
@@ -2,3 +2,13 @@ CoreStoreDemo/CoreStoreDemo.xcodeproj/project.xcworkspace/xcuserdata
|
||||
CoreStore.xcodeproj/project.xcworkspace/xcuserdata
|
||||
CoreStore.xcodeproj/xcuserdata
|
||||
CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata
|
||||
Carthage/Build
|
||||
CoreStore.xcworkspace/xcuserdata
|
||||
.DS_Store
|
||||
DerivedData
|
||||
*.orig
|
||||
build
|
||||
Playground_macOS.playground/playground.xcworkspace/xcuserdata
|
||||
.swiftpm/xcode/package.xcworkspace/xcuserdata
|
||||
.swiftpm/xcode/xcuserdata
|
||||
Playground_iOS.playground/playground.xcworkspace/xcuserdata
|
||||
|
||||
3
.gitmodules
vendored
@@ -1,3 +0,0 @@
|
||||
[submodule "Libraries/GCDKit"]
|
||||
path = Libraries/GCDKit
|
||||
url = git@github.com:JohnEstropia/GCDKit.git
|
||||
|
||||
15
.jazzy.yaml
Normal file
@@ -0,0 +1,15 @@
|
||||
author: John Estropia
|
||||
author_url: https://github.com/JohnEstropia
|
||||
github_url: https://github.com/JohnEstropia/CoreStore
|
||||
module: CoreStore
|
||||
readme: README.md
|
||||
include: Sources/*
|
||||
output: docs
|
||||
theme: fullwidth
|
||||
clean: true
|
||||
skip_undocumented: true
|
||||
xcodebuild_arguments:
|
||||
- -sdk
|
||||
- iphonesimulator
|
||||
- -scheme
|
||||
- CoreStore iOS
|
||||
7
.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "self:">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,14 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>SchemeUserState</key>
|
||||
<dict>
|
||||
<key>CoreStore.xcscheme_^#shared#^_</key>
|
||||
<dict>
|
||||
<key>orderHint</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
BIN
CoreStore.png
Normal file
|
After Width: | Height: | Size: 53 KiB |
@@ -1,16 +1,22 @@
|
||||
Pod::Spec.new do |s|
|
||||
s.name = "CoreStore"
|
||||
s.version = "1.2.0"
|
||||
s.version = "7.2.0"
|
||||
s.swift_version = "5.2"
|
||||
s.license = "MIT"
|
||||
s.summary = "Simple, elegant, and smart Core Data programming with Swift"
|
||||
s.homepage = "https://github.com/JohnEstropia/CoreStore"
|
||||
s.documentation_url = "https://JohnEstropia.github.io/CoreStore"
|
||||
s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift"
|
||||
s.author = { "John Rommel Estropia" => "rommel.estropia@gmail.com" }
|
||||
s.source = { :git => "https://github.com/JohnEstropia/CoreStore.git", :tag => s.version.to_s }
|
||||
|
||||
s.ios.deployment_target = "8.0"
|
||||
s.ios.deployment_target = "10.0"
|
||||
s.osx.deployment_target = "10.12"
|
||||
s.watchos.deployment_target = "3.0"
|
||||
s.tvos.deployment_target = "10.0"
|
||||
|
||||
s.source_files = "CoreStore", "CoreStore/**/*.{swift}"
|
||||
s.frameworks = "Foundation", "UIKit", "CoreData"
|
||||
s.source_files = "Sources", "Sources/**/*.{swift,h,m}"
|
||||
s.public_header_files = "Sources/**/*.h"
|
||||
s.frameworks = "Foundation", "CoreData"
|
||||
s.requires_arc = true
|
||||
s.dependency "GCDKit", "1.1.1"
|
||||
end
|
||||
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D DEBUG', 'OTHER_LDFLAGS' => '-weak_framework Combine -weak_framework SwiftUI' }
|
||||
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" : [
|
||||
{
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B52DD1731BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore OSX"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B52DD1731BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore OSX"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B52DD17C1BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStoreTests.xctest"
|
||||
BlueprintName = "CoreStoreTests OSX"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B52DD1731BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore OSX"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B52DD1731BE1F8CC00949AFE"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore OSX"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -16,7 +16,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore"
|
||||
BlueprintName = "CoreStore iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@@ -30,7 +30,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
|
||||
BuildableName = "CoreStoreTests.xctest"
|
||||
BlueprintName = "CoreStoreTests"
|
||||
BlueprintName = "CoreStoreTests iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
@@ -41,6 +41,22 @@
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
<AdditionalOption
|
||||
key = "NSZombieEnabled"
|
||||
value = "YES"
|
||||
isEnabled = "YES">
|
||||
</AdditionalOption>
|
||||
</AdditionalOptions>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -48,22 +64,11 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
|
||||
BuildableName = "CoreStoreTests.xctest"
|
||||
BlueprintName = "CoreStoreTests"
|
||||
BlueprintName = "CoreStoreTests iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
@@ -80,12 +85,16 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore"
|
||||
BlueprintName = "CoreStore iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
<CommandLineArguments>
|
||||
<CommandLineArgument
|
||||
argument = "-com.apple.CoreData.SQLDebug 2"
|
||||
isEnabled = "NO">
|
||||
</CommandLineArgument>
|
||||
</CommandLineArguments>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
@@ -98,7 +107,7 @@
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore"
|
||||
BlueprintName = "CoreStore iOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
@@ -0,0 +1,95 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18911C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStoreTests.xctest"
|
||||
BlueprintName = "CoreStoreTests tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore tvOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B563216E1BD65082006C9394"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore watchOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B563216E1BD65082006C9394"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore watchOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B563216E1BD65082006C9394"
|
||||
BuildableName = "CoreStore.framework"
|
||||
BlueprintName = "CoreStore watchOS"
|
||||
ReferencedContainer = "container:CoreStore.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
10
CoreStore.xcworkspace/contents.xcworkspacedata
generated
Normal file
@@ -0,0 +1,10 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Workspace
|
||||
version = "1.0">
|
||||
<FileRef
|
||||
location = "group:CoreStore.xcodeproj">
|
||||
</FileRef>
|
||||
<FileRef
|
||||
location = "group:CoreStoreDemo/CoreStoreDemo.xcodeproj">
|
||||
</FileRef>
|
||||
</Workspace>
|
||||
37
CoreStore.xcworkspace/xcshareddata/CoreStore.xcscmblueprint
Normal file
@@ -0,0 +1,37 @@
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
|
||||
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0,
|
||||
"95438028B10BBB846574013D29F154A00556A9D1" : 0,
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "EBFDEFFE-8BA0-441A-862A-1DE28AA5CD21",
|
||||
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
|
||||
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStore\/Carthage\/Checkouts\/GCDKit\/",
|
||||
"95438028B10BBB846574013D29F154A00556A9D1" : "CoreStore\/Carthage\/Checkouts\/Nimble\/",
|
||||
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore\/"
|
||||
},
|
||||
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
|
||||
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
|
||||
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStore.xcworkspace",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/JohnEstropia\/CoreStore",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48"
|
||||
},
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/JohnEstropia\/GCDKit.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97"
|
||||
},
|
||||
{
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Quick\/Nimble.git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
|
||||
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "95438028B10BBB846574013D29F154A00556A9D1"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>IDEDidComputeMac32BitWarning</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict/>
|
||||
</plist>
|
||||
@@ -1,77 +0,0 @@
|
||||
//
|
||||
// NSManagedObject+Convenience.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - NSManagedObject
|
||||
|
||||
public extension NSManagedObject {
|
||||
|
||||
/**
|
||||
Provides a convenience wrapper for accessing `primitiveValueForKey(...)` with proper calls to `willAccessValueForKey(...)` and `didAccessValueForKey(...)`. This is useful when implementing accessor methods for transient attributes.
|
||||
|
||||
- parameter KVCKey: the KVC key
|
||||
- returns: the primitive value for the KVC key
|
||||
*/
|
||||
public func accessValueForKVCKey(KVCKey: KeyPath) -> AnyObject? {
|
||||
|
||||
self.willAccessValueForKey(KVCKey)
|
||||
let primitiveValue: AnyObject? = self.primitiveValueForKey(KVCKey)
|
||||
self.didAccessValueForKey(KVCKey)
|
||||
|
||||
return primitiveValue
|
||||
}
|
||||
|
||||
/**
|
||||
Provides a convenience wrapper for setting `setPrimitiveValue(...)` with proper calls to `willChangeValueForKey(...)` and `didChangeValueForKey(...)`. This is useful when implementing mutator methods for transient attributes.
|
||||
|
||||
- parameter value: the value to set the KVC key with
|
||||
- parameter KVCKey: the KVC key
|
||||
*/
|
||||
public func setValue(value: AnyObject?, forKVCKey KVCKey: KeyPath) {
|
||||
|
||||
self.willChangeValueForKey(KVCKey)
|
||||
self.setPrimitiveValue(value, forKey: KVCKey)
|
||||
self.didChangeValueForKey(KVCKey)
|
||||
}
|
||||
|
||||
/**
|
||||
Re-faults the object to use the latest values from the persistent store
|
||||
*/
|
||||
public func refreshAsFault() {
|
||||
|
||||
self.managedObjectContext?.refreshObject(self, mergeChanges: false)
|
||||
}
|
||||
|
||||
/**
|
||||
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
|
||||
*/
|
||||
public func refreshAndMerge() {
|
||||
|
||||
self.managedObjectContext?.refreshObject(self, mergeChanges: true)
|
||||
}
|
||||
}
|
||||
@@ -1,397 +0,0 @@
|
||||
//
|
||||
// BaseDataTransaction+Querying.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - DataTransaction
|
||||
|
||||
public extension BaseDataTransaction {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter object: a reference to the object created/fetched outside the transaction
|
||||
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.context.existingObjectWithID(object.objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the transaction's context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter objectID: the `NSManagedObjectID` for the object
|
||||
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.context.existingObjectWithID(objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the transaction's context from references created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter objects: an array of `NSManagedObject`s created/fetched outside the transaction
|
||||
- returns: the `NSManagedObject` array for objects that exists in the transaction
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objects: [T]) -> [T] {
|
||||
|
||||
var existingObjects = [T]()
|
||||
for object in objects {
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.context.existingObjectWithID(object.objectID) as! T
|
||||
existingObjects.append(existingObject)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
return existingObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the transaction's context from a list of `NSManagedObjectID`.
|
||||
|
||||
- parameter objectIDs: the `NSManagedObjectID` array for the objects
|
||||
- returns: the `NSManagedObject` array for objects that exists in the transaction
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objectIDs: [NSManagedObjectID]) -> [T] {
|
||||
|
||||
var existingObjects = [T]()
|
||||
for objectID in objectIDs {
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.context.existingObjectWithID(objectID) as! T
|
||||
existingObjects.append(existingObject)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
return existingObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number of `NSManagedObject`s deleted
|
||||
*/
|
||||
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.deleteAll(from, deleteClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number of `NSManagedObject`s deleted
|
||||
*/
|
||||
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.deleteAll(from, deleteClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries aggregate values or aggregates as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to query from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
return self.context.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
}
|
||||
@@ -1,201 +0,0 @@
|
||||
//
|
||||
// From.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - From
|
||||
|
||||
/**
|
||||
A `Form` clause binds the `NSManagedObject` entity type to the generics type system.
|
||||
*/
|
||||
public struct From<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public init(){
|
||||
|
||||
self.entityClass = T.self
|
||||
self.findPersistentStores = { _ in nil }
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type) {
|
||||
|
||||
self.entityClass = entity
|
||||
self.findPersistentStores = { _ in nil }
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass) {
|
||||
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { _ in nil }
|
||||
}
|
||||
|
||||
public init(_ configurations: String?...) {
|
||||
|
||||
self.init(entityClass: T.self, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ configurations: [String?]) {
|
||||
|
||||
self.init(entityClass: T.self, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ configurations: String?...) {
|
||||
|
||||
self.init(entityClass: entity, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ configurations: [String?]) {
|
||||
|
||||
self.init(entityClass: entity, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ configurations: String?...) {
|
||||
|
||||
self.init(entityClass: entityClass, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ configurations: [String?]) {
|
||||
|
||||
self.init(entityClass: entityClass, configurations: configurations)
|
||||
}
|
||||
|
||||
public init(_ storeURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: T.self, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: T.self, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ storeURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: entity, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: entity, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ storeURLs: NSURL...) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
|
||||
|
||||
self.init(entityClass: entityClass, storeURLs: storeURLs)
|
||||
}
|
||||
|
||||
public init(_ persistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: T.self, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: T.self, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ persistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: entity, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: entity, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ persistentStores: NSPersistentStore...) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
|
||||
|
||||
self.init(entityClass: entityClass, persistentStores: persistentStores)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) {
|
||||
|
||||
fetchRequest.entity = context.entityDescriptionForEntityClass(self.entityClass)
|
||||
fetchRequest.affectedStores = self.findPersistentStores(context: context)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let entityClass: AnyClass
|
||||
|
||||
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
|
||||
|
||||
private init(entityClass: AnyClass, configurations: [String?]) {
|
||||
|
||||
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return configurationsSet.contains($0.configurationName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init(entityClass: AnyClass, storeURLs: [NSURL]) {
|
||||
|
||||
let storeURLsSet = Set(storeURLs)
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return $0.URL != nil && storeURLsSet.contains($0.URL!)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private init(entityClass: AnyClass, persistentStores: [NSPersistentStore]) {
|
||||
|
||||
let persistentStores = Set(persistentStores)
|
||||
self.entityClass = entityClass
|
||||
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
|
||||
|
||||
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
|
||||
|
||||
return persistentStores.contains($0)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,85 +0,0 @@
|
||||
//
|
||||
// GroupBy.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - GroupBy
|
||||
|
||||
/**
|
||||
The `GroupBy` clause specifies that the result of a query be grouped accoording to the specified key path.
|
||||
*/
|
||||
public struct GroupBy: QueryClause {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes a `GroupBy` clause with a list of key path strings
|
||||
|
||||
- parameter keyPaths: a list of key path strings to group results with
|
||||
*/
|
||||
public init(_ keyPaths: [KeyPath]) {
|
||||
|
||||
self.keyPaths = keyPaths
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `GroupBy` clause with an empty list of key path strings
|
||||
*/
|
||||
public init() {
|
||||
|
||||
self.init([])
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `GroupBy` clause with a list of key path strings
|
||||
|
||||
- parameter keyPath: a key path string to group results with
|
||||
- parameter keyPaths: a series of key path strings to group results with
|
||||
*/
|
||||
public init(_ keyPath: KeyPath, _ keyPaths: KeyPath...) {
|
||||
|
||||
self.init([keyPath] + keyPaths)
|
||||
}
|
||||
|
||||
public let keyPaths: [KeyPath]
|
||||
|
||||
|
||||
// MARK: QueryClause
|
||||
|
||||
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
|
||||
|
||||
if fetchRequest.propertiesToGroupBy != nil {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "An existing \"propertiesToGroupBy\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.propertiesToGroupBy = self.keyPaths
|
||||
}
|
||||
}
|
||||
@@ -1,151 +0,0 @@
|
||||
//
|
||||
// OrderBy.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
public func +(left: OrderBy, right: OrderBy) -> OrderBy {
|
||||
|
||||
return OrderBy(left.sortDescriptors + right.sortDescriptors)
|
||||
}
|
||||
|
||||
public func +=(inout left: OrderBy, right: OrderBy) {
|
||||
|
||||
left = left + right
|
||||
}
|
||||
|
||||
|
||||
// MARK: - KeyPath
|
||||
|
||||
public typealias KeyPath = String
|
||||
|
||||
|
||||
// MARK: - SortKey
|
||||
|
||||
/**
|
||||
The `SortKey` is passed to the `OrderBy` clause to indicate the sort keys and their sort direction.
|
||||
*/
|
||||
public enum SortKey {
|
||||
|
||||
/**
|
||||
Indicates that the `KeyPath` should be sorted in ascending order
|
||||
*/
|
||||
case Ascending(KeyPath)
|
||||
|
||||
/**
|
||||
Indicates that the `KeyPath` should be sorted in descending order
|
||||
*/
|
||||
case Descending(KeyPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - OrderBy
|
||||
|
||||
/**
|
||||
The `OrderBy` clause specifies the sort order for results for a fetch or a query.
|
||||
*/
|
||||
public struct OrderBy: FetchClause, QueryClause, DeleteClause {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a list of sort descriptors
|
||||
|
||||
- parameter sortDescriptors: a series of `NSSortDescriptor`s
|
||||
*/
|
||||
public init(_ sortDescriptors: [NSSortDescriptor]) {
|
||||
|
||||
self.sortDescriptors = sortDescriptors
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with an empty list of sort descriptors
|
||||
*/
|
||||
public init() {
|
||||
|
||||
self.init([NSSortDescriptor]())
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a single sort descriptor
|
||||
|
||||
- parameter sortDescriptor: a `NSSortDescriptor`
|
||||
*/
|
||||
public init(_ sortDescriptor: NSSortDescriptor) {
|
||||
|
||||
self.init([sortDescriptor])
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a series of `SortKey`s
|
||||
|
||||
- parameter sortKey: a series of `SortKey`s
|
||||
*/
|
||||
public init(_ sortKey: [SortKey]) {
|
||||
|
||||
self.init(
|
||||
sortKey.map { sortKey -> NSSortDescriptor in
|
||||
|
||||
switch sortKey {
|
||||
|
||||
case .Ascending(let keyPath):
|
||||
return NSSortDescriptor(key: keyPath, ascending: true)
|
||||
|
||||
case .Descending(let keyPath):
|
||||
return NSSortDescriptor(key: keyPath, ascending: false)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `OrderBy` clause with a series of `SortKey`s
|
||||
|
||||
- parameter sortKey: a single `SortKey`
|
||||
- parameter sortKeys: a series of `SortKey`s
|
||||
*/
|
||||
public init(_ sortKey: SortKey, _ sortKeys: SortKey...) {
|
||||
|
||||
self.init([sortKey] + sortKeys)
|
||||
}
|
||||
|
||||
public let sortDescriptors: [NSSortDescriptor]
|
||||
|
||||
|
||||
// MARK: FetchClause, QueryClause, DeleteClause
|
||||
|
||||
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
|
||||
|
||||
if fetchRequest.sortDescriptors != nil {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "Existing sortDescriptors for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.sortDescriptors = self.sortDescriptors
|
||||
}
|
||||
}
|
||||
@@ -1,672 +0,0 @@
|
||||
//
|
||||
// Select.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - 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 {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
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> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
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? {
|
||||
|
||||
if let value = (result as? NSNumber)?.longLongValue {
|
||||
|
||||
return numericCast(value) as Int8
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Int16: SelectValueResultType
|
||||
|
||||
extension Int16: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .Integer64AttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int16? {
|
||||
|
||||
if let value = (result as? NSNumber)?.longLongValue {
|
||||
|
||||
return numericCast(value) as Int16
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Int32: SelectValueResultType
|
||||
|
||||
extension Int32: SelectValueResultType {
|
||||
|
||||
public static var attributeType: NSAttributeType {
|
||||
|
||||
return .Integer64AttributeType
|
||||
}
|
||||
|
||||
public static func fromResultObject(result: AnyObject) -> Int32? {
|
||||
|
||||
if let value = (result as? NSNumber)?.longLongValue {
|
||||
|
||||
return numericCast(value) as Int32
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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? {
|
||||
|
||||
if let value = (result as? NSNumber)?.longLongValue {
|
||||
|
||||
return numericCast(value) as Int
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 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,134 +0,0 @@
|
||||
//
|
||||
// Where.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
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 {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
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 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(_ keyPath: KeyPath, isEqualTo value: NSObject?) {
|
||||
|
||||
self.init(value == nil
|
||||
? NSPredicate(format: "\(keyPath) == nil")
|
||||
: NSPredicate(format: "\(keyPath) == %@", value!))
|
||||
}
|
||||
|
||||
public let predicate: NSPredicate
|
||||
|
||||
|
||||
// MARK: FetchClause, QueryClause, DeleteClause
|
||||
|
||||
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
|
||||
|
||||
if fetchRequest.predicate != nil {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "An existing predicate for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
|
||||
)
|
||||
}
|
||||
|
||||
fetchRequest.predicate = self.predicate
|
||||
}
|
||||
}
|
||||
@@ -1,257 +0,0 @@
|
||||
//
|
||||
// CoreStore+Querying.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter object: a reference to the object created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
public static func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
return self.defaultStack.fetchExisting(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter objectID: the `NSManagedObjectID` for the object
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
public static func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
return self.defaultStack.fetchExisting(objectID)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
public static func fetchExisting<T: NSManagedObject>(objects: [T]) -> [T] {
|
||||
|
||||
return self.defaultStack.fetchExisting(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
|
||||
|
||||
- parameter objectIDs: the `NSManagedObjectID` array for the objects
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
public static func fetchExisting<T: NSManagedObject>(objectIDs: [NSManagedObjectID]) -> [T] {
|
||||
|
||||
return self.defaultStack.fetchExisting(objectIDs)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
return self.defaultStack.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
|
||||
|
||||
return self.defaultStack.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
return self.defaultStack.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
|
||||
|
||||
return self.defaultStack.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
return self.defaultStack.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
|
||||
|
||||
return self.defaultStack.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
return self.defaultStack.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
|
||||
|
||||
return self.defaultStack.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
return self.defaultStack.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
|
||||
|
||||
return self.defaultStack.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
|
||||
|
||||
return self.defaultStack.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
|
||||
|
||||
return self.defaultStack.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, queries a dictionary of attribtue values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
return self.defaultStack.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
return self.defaultStack.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
}
|
||||
@@ -1,364 +0,0 @@
|
||||
//
|
||||
// DataStack+Querying.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
public extension DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter object: a reference to the object created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(object: T) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.mainContext.existingObjectWithID(object.objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
|
||||
|
||||
- parameter objectID: the `NSManagedObjectID` for the object
|
||||
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
return (try self.mainContext.existingObjectWithID(objectID) as! T)
|
||||
}
|
||||
catch _ {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
|
||||
|
||||
- parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack`
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objects: [T]) -> [T] {
|
||||
|
||||
var existingObjects = [T]()
|
||||
for object in objects {
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.mainContext.existingObjectWithID(object.objectID) as! T
|
||||
existingObjects.append(existingObject)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
return existingObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
|
||||
|
||||
- parameter objectIDs: the `NSManagedObjectID` array for the objects
|
||||
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
|
||||
*/
|
||||
public func fetchExisting<T: NSManagedObject>(objectIDs: [NSManagedObjectID]) -> [T] {
|
||||
|
||||
var existingObjects = [T]()
|
||||
for objectID in objectIDs {
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try self.mainContext.existingObjectWithID(objectID) as! T
|
||||
existingObjects.append(existingObject)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
return existingObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchOne(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchAll(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchCount(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectID(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
|
||||
*/
|
||||
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to fetch from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.fetchObjectIDs(from, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryValue(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
|
||||
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
|
||||
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
|
||||
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
|
||||
*/
|
||||
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to query from a \(typeName(self)) outside the main thread."
|
||||
)
|
||||
|
||||
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
|
||||
}
|
||||
}
|
||||
@@ -1,289 +0,0 @@
|
||||
//
|
||||
// BaseDataTransaction+Importing.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
public protocol ImportableObject: class {
|
||||
|
||||
typealias ImportSource
|
||||
|
||||
static func shouldImportFromSource(source: ImportSource) -> Bool
|
||||
|
||||
func didInsertFromImportSource(source: ImportSource) throws
|
||||
|
||||
func updateFromImportSource(source: ImportSource) throws
|
||||
}
|
||||
|
||||
public extension ImportableObject {
|
||||
|
||||
static func shouldImportFromSource(source: ImportSource) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public protocol ImportableUniqueObject: ImportableObject {
|
||||
|
||||
typealias UniqueIDType: NSObject
|
||||
|
||||
static var uniqueIDKeyPath: String { get }
|
||||
|
||||
var uniqueIDValue: UniqueIDType { get set }
|
||||
|
||||
static func uniqueIDFromImportSource(source: ImportSource) throws -> UniqueIDType
|
||||
}
|
||||
|
||||
|
||||
public extension BaseDataTransaction {
|
||||
|
||||
func importObject<T where T: NSManagedObject, T: ImportableObject>(
|
||||
into: Into<T>,
|
||||
source: T.ImportSource) throws -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
|
||||
if !T.shouldImportFromSource(source) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let object = self.create(into)
|
||||
try object.didInsertFromImportSource(source)
|
||||
return object
|
||||
}
|
||||
}
|
||||
|
||||
func importObjects<T where T: NSManagedObject, T: ImportableObject>(
|
||||
into: Into<T>,
|
||||
sourceArray: [T.ImportSource]) throws {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
for source in sourceArray {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
try object.didInsertFromImportSource(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importObjects<T where T: NSManagedObject, T: ImportableObject>(
|
||||
into: Into<T>,
|
||||
sourceArray: [T.ImportSource],
|
||||
postProcess: (sorted: [T]) -> Void) throws {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
var objects = [T]()
|
||||
for source in sourceArray {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
try object.didInsertFromImportSource(source)
|
||||
|
||||
objects.append(object)
|
||||
}
|
||||
}
|
||||
postProcess(sorted: objects)
|
||||
}
|
||||
}
|
||||
|
||||
func importUniqueObject<T where T: NSManagedObject, T: ImportableUniqueObject>(
|
||||
into: Into<T>,
|
||||
source: T.ImportSource) throws -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
return try autoreleasepool {
|
||||
|
||||
if !T.shouldImportFromSource(source) {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let uniqueIDKeyPath = T.uniqueIDKeyPath
|
||||
let uniqueIDValue = try T.uniqueIDFromImportSource(source)
|
||||
|
||||
if let object = self.fetchOne(From(T), Where(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) {
|
||||
|
||||
try object.updateFromImportSource(source)
|
||||
return object
|
||||
}
|
||||
else {
|
||||
|
||||
let object = self.create(into)
|
||||
object.uniqueIDValue = uniqueIDValue
|
||||
try object.didInsertFromImportSource(source)
|
||||
return object
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importUniqueObjects<T where T: NSManagedObject, T: ImportableUniqueObject>(
|
||||
into: Into<T>,
|
||||
sourceArray: [T.ImportSource],
|
||||
preProcess: ((mapping: [T.UniqueIDType: T.ImportSource]) throws -> Void)? = nil) throws {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
var mapping = Dictionary<T.UniqueIDType, T.ImportSource>()
|
||||
for source in sourceArray {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
if !T.shouldImportFromSource(source) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let uniqueIDValue = try T.uniqueIDFromImportSource(source)
|
||||
mapping[uniqueIDValue] = source
|
||||
}
|
||||
}
|
||||
|
||||
if let preProcess = preProcess {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
try preProcess(mapping: mapping)
|
||||
}
|
||||
}
|
||||
|
||||
for object in self.fetchAll(From(T), Where("%K IN %@", T.uniqueIDKeyPath, mapping.keys.array)) ?? [] {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let uniqueIDValue = object.uniqueIDValue
|
||||
try object.updateFromImportSource(mapping.removeValueForKey(uniqueIDValue)!)
|
||||
}
|
||||
}
|
||||
|
||||
for (uniqueIDValue, source) in mapping {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
object.uniqueIDValue = uniqueIDValue
|
||||
try object.didInsertFromImportSource(source)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func importUniqueObjects<T where T: NSManagedObject, T: ImportableUniqueObject>(
|
||||
into: Into<T>,
|
||||
sourceArray: [T.ImportSource],
|
||||
preProcess: ((mapping: [T.UniqueIDType: T.ImportSource]) throws -> Void)? = nil,
|
||||
postProcess: (sorted: [T]) -> Void) throws {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
|
||||
)
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
var sortedIDs = Array<T.UniqueIDType>()
|
||||
var mapping = Dictionary<T.UniqueIDType, T.ImportSource>()
|
||||
for source in sourceArray {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
if !T.shouldImportFromSource(source) {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
let uniqueIDValue = try T.uniqueIDFromImportSource(source)
|
||||
mapping[uniqueIDValue] = source
|
||||
sortedIDs.append(uniqueIDValue)
|
||||
}
|
||||
}
|
||||
|
||||
if let preProcess = preProcess {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
try preProcess(mapping: mapping)
|
||||
}
|
||||
}
|
||||
|
||||
var objects = Dictionary<T.UniqueIDType, T>()
|
||||
for object in self.fetchAll(From(T), Where("%K IN %@", T.uniqueIDKeyPath, mapping.keys.array)) ?? [] {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let uniqueIDValue = object.uniqueIDValue
|
||||
try object.updateFromImportSource(mapping.removeValueForKey(uniqueIDValue)!)
|
||||
objects[uniqueIDValue] = object
|
||||
}
|
||||
}
|
||||
|
||||
for (uniqueIDValue, source) in mapping {
|
||||
|
||||
try autoreleasepool {
|
||||
|
||||
let object = self.create(into)
|
||||
object.uniqueIDValue = uniqueIDValue
|
||||
try object.didInsertFromImportSource(source)
|
||||
|
||||
objects[uniqueIDValue] = object
|
||||
}
|
||||
}
|
||||
|
||||
postProcess(sorted: sortedIDs.flatMap { objects[$0] })
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
//
|
||||
// NSObject+CoreStore.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
internal func getAssociatedObjectForKey<T: AnyObject>(key: UnsafePointer<Void>, inObject object: AnyObject) -> T? {
|
||||
|
||||
switch objc_getAssociatedObject(object, key) {
|
||||
|
||||
case let associatedObject as T:
|
||||
return associatedObject
|
||||
|
||||
case let associatedObject as WeakObject:
|
||||
return associatedObject.object as? T
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
internal func setAssociatedRetainedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
|
||||
internal func setAssociatedCopiedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_COPY_NONATOMIC)
|
||||
}
|
||||
|
||||
internal func setAssociatedAssignedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_ASSIGN)
|
||||
}
|
||||
|
||||
internal func setAssociatedWeakObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
|
||||
|
||||
if let associatedObject = associatedObject {
|
||||
|
||||
objc_setAssociatedObject(object, key, WeakObject(associatedObject), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
else {
|
||||
|
||||
objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
||||
}
|
||||
}
|
||||
@@ -1,38 +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) {
|
||||
|
||||
do {
|
||||
|
||||
try self.removeItemAtURL(fileURL)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
do {
|
||||
|
||||
try self.removeItemAtPath(fileURL.path!.stringByAppendingString("-shm"))
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
do {
|
||||
|
||||
try self.removeItemAtPath(fileURL.path!.stringByAppendingString("-wal"))
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
}
|
||||
@@ -1,122 +0,0 @@
|
||||
//
|
||||
// NSManagedObject+Transaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - NSManagedObject
|
||||
|
||||
internal extension NSManagedObject {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal dynamic class func createInContext(context: NSManagedObjectContext) -> Self {
|
||||
|
||||
return self.init(
|
||||
entity: context.entityDescriptionForEntityType(self)!,
|
||||
insertIntoManagedObjectContext: context
|
||||
)
|
||||
}
|
||||
|
||||
internal dynamic class func inContext(context: NSManagedObjectContext, withObjectID objectID: NSManagedObjectID) -> Self? {
|
||||
|
||||
return self.typedObjectInContext(context, objectID: objectID)
|
||||
}
|
||||
|
||||
internal func inContext(context: NSManagedObjectContext) -> Self? {
|
||||
|
||||
return self.typedObjectInContext(context)
|
||||
}
|
||||
|
||||
internal func deleteFromContext() {
|
||||
|
||||
self.managedObjectContext?.deleteObject(self)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private class func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext, objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try context.existingObjectWithID(objectID)
|
||||
return (existingObject as! T)
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load existing \(typeName(self)) in context."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
private func typedObjectInContext<T: NSManagedObject>(context: NSManagedObjectContext) -> T? {
|
||||
|
||||
let objectID = self.objectID
|
||||
if objectID.temporaryID {
|
||||
|
||||
var objectIDError: NSError?
|
||||
let didSucceed = withExtendedLifetime(self.managedObjectContext) { (context: NSManagedObjectContext?) -> Bool in
|
||||
|
||||
do {
|
||||
|
||||
try context?.obtainPermanentIDsForObjects([self])
|
||||
return true
|
||||
}
|
||||
catch {
|
||||
|
||||
objectIDError = error as NSError
|
||||
return false
|
||||
}
|
||||
}
|
||||
if didSucceed != true {
|
||||
|
||||
CoreStore.handleError(
|
||||
objectIDError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to obtain permanent ID for object."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
let existingObject = try context.existingObjectWithID(objectID)
|
||||
return (existingObject as! T)
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load existing \(typeName(self)) in context."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectContext+CoreStore.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectContext
|
||||
|
||||
internal extension NSManagedObjectContext {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal var shouldCascadeSavesToParent: Bool {
|
||||
|
||||
get {
|
||||
|
||||
let number: NSNumber? = getAssociatedObjectForKey(
|
||||
&PropertyKeys.shouldCascadeSavesToParent,
|
||||
inObject: self
|
||||
)
|
||||
return number?.boolValue ?? false
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedCopiedObject(
|
||||
NSNumber(bool: newValue),
|
||||
forKey: &PropertyKeys.shouldCascadeSavesToParent,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal func entityDescriptionForEntityType(entity: NSManagedObject.Type) -> NSEntityDescription? {
|
||||
|
||||
return self.entityDescriptionForEntityClass(entity)
|
||||
}
|
||||
|
||||
internal func entityDescriptionForEntityClass(entity: AnyClass) -> NSEntityDescription? {
|
||||
|
||||
if let entityName = self.parentStack?.entityNameForEntityClass(entity) {
|
||||
|
||||
return NSEntityDescription.entityForName(
|
||||
entityName,
|
||||
inManagedObjectContext: self
|
||||
)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
internal func setupForCoreStoreWithContextName(contextName: String) {
|
||||
|
||||
self.name = contextName
|
||||
|
||||
self.observerForWillSaveNotification = NotificationObserver(
|
||||
notificationName: NSManagedObjectContextWillSaveNotification,
|
||||
object: self,
|
||||
closure: { (note) -> Void in
|
||||
|
||||
let context = note.object as! NSManagedObjectContext
|
||||
let insertedObjects = context.insertedObjects
|
||||
let numberOfInsertedObjects = insertedObjects.count
|
||||
if numberOfInsertedObjects <= 0 {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try context.obtainPermanentIDsForObjects(Array(insertedObjects))
|
||||
return
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to obtain permanent ID(s) for \(numberOfInsertedObjects) inserted object(s)."
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var observerForWillSaveNotification: Void?
|
||||
static var shouldCascadeSavesToParent: Void?
|
||||
}
|
||||
|
||||
private var observerForWillSaveNotification: NotificationObserver? {
|
||||
|
||||
get {
|
||||
|
||||
return getAssociatedObjectForKey(
|
||||
&PropertyKeys.observerForWillSaveNotification,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
set {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
newValue,
|
||||
forKey: &PropertyKeys.observerForWillSaveNotification,
|
||||
inObject: self
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,386 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectContext+Querying.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectContext
|
||||
|
||||
internal extension NSManagedObjectContext {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
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,122 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectContext+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - 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 {
|
||||
|
||||
if self.parentContext != nil {
|
||||
|
||||
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
|
||||
|
||||
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,172 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectContext+Transaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - 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 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
|
||||
|
||||
if !self.hasChanges {
|
||||
|
||||
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)?) {
|
||||
|
||||
self.performBlock { () -> Void in
|
||||
|
||||
if !self.hasChanges {
|
||||
|
||||
if let completion = completion {
|
||||
|
||||
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))."
|
||||
)
|
||||
if let completion = completion {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(saveError))
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if let parentContext = self.parentContext where self.shouldCascadeSavesToParent {
|
||||
|
||||
parentContext.saveAsynchronouslyWithCompletion(completion)
|
||||
}
|
||||
else if let completion = completion {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(result: SaveResult(hasChanges: true))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private struct PropertyKeys {
|
||||
|
||||
static var parentTransaction: Void?
|
||||
}
|
||||
}
|
||||
@@ -1,275 +0,0 @@
|
||||
//
|
||||
// NSManagedObjectModel+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - NSManagedObjectModel
|
||||
|
||||
internal extension NSManagedObjectModel {
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
@nonobjc internal class func fromBundle(bundle: NSBundle, modelName: String, modelVersionHints: Set<String> = []) -> NSManagedObjectModel {
|
||||
|
||||
guard let modelFilePath = bundle.pathForResource(modelName, ofType: "momd") else {
|
||||
|
||||
fatalError("Could not find \"\(modelName).momd\" from the bundle. \(bundle)")
|
||||
}
|
||||
|
||||
let modelFileURL = NSURL(fileURLWithPath: modelFilePath)
|
||||
let versionInfoPlistURL = modelFileURL.URLByAppendingPathComponent("VersionInfo.plist", isDirectory: false)
|
||||
|
||||
guard let versionInfo = NSDictionary(contentsOfURL: versionInfoPlistURL),
|
||||
let versionHashes = versionInfo["NSManagedObjectModel_VersionHashes"] as? [String: AnyObject] else {
|
||||
|
||||
fatalError("Could not load \(typeName(NSManagedObjectModel)) metadata from path \"\(versionInfoPlistURL)\".")
|
||||
}
|
||||
|
||||
let modelVersions = Set(versionHashes.keys)
|
||||
let currentModelVersion: String
|
||||
if let plistModelVersion = versionInfo["NSManagedObjectModel_CurrentVersionName"] as? String where modelVersionHints.isEmpty || modelVersionHints.contains(plistModelVersion) {
|
||||
|
||||
currentModelVersion = plistModelVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.intersect(modelVersionHints).first {
|
||||
|
||||
CoreStore.log(
|
||||
.Warning,
|
||||
message: "The MigrationChain leaf versions do not include the model file's current version. Resolving to version \"\(resolvedVersion)\"."
|
||||
)
|
||||
currentModelVersion = resolvedVersion
|
||||
}
|
||||
else if let resolvedVersion = modelVersions.first ?? modelVersionHints.first {
|
||||
|
||||
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 entityMapping() -> [String: NSManagedObject.Type] {
|
||||
|
||||
return self.entityNameMapping.reduce([:]) { (var mapping, pair) in
|
||||
|
||||
mapping[pair.0] = (NSClassFromString(pair.1)! 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? {
|
||||
|
||||
if let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : NSData] {
|
||||
|
||||
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,106 +0,0 @@
|
||||
//
|
||||
// CoreStoreLogger.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - LogLevel
|
||||
|
||||
/**
|
||||
The `LogLevel` indicates the severity of a log message.
|
||||
*/
|
||||
public enum LogLevel {
|
||||
|
||||
case Trace
|
||||
case Notice
|
||||
case Warning
|
||||
case Fatal
|
||||
}
|
||||
|
||||
|
||||
// MARK: - CoreStoreLogger
|
||||
|
||||
/**
|
||||
Custom loggers should implement the `CoreStoreLogger` protocol and pass its instance to `CoreStore.logger`. Calls to `log(...)`, `handleError(...)`, and `assert(...)` are not tied to a specific queue/thread, so it is the implementer's job to handle thread-safety.
|
||||
*/
|
||||
public protocol CoreStoreLogger {
|
||||
|
||||
/**
|
||||
Handles log messages sent by the `CoreStore` framework.
|
||||
|
||||
:level: the severity of the log message
|
||||
:message: the log message
|
||||
:fileName: the source file name
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
|
||||
/**
|
||||
Handles errors sent by the `CoreStore` framework.
|
||||
|
||||
:error: the error
|
||||
:message: the error message
|
||||
:fileName: the source file name
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
|
||||
/**
|
||||
Handles assertions made throughout the `CoreStore` framework.
|
||||
|
||||
:condition: the assertion condition
|
||||
:message: the assertion message
|
||||
:fileName: the source file name
|
||||
:lineNumber: the source line number
|
||||
:functionName: the source function name
|
||||
*/
|
||||
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - Utilities
|
||||
|
||||
internal func typeName<T>(value: T) -> String {
|
||||
|
||||
return "'\(_stdlib_getDemangledTypeName(value))'"
|
||||
}
|
||||
|
||||
internal func typeName<T>(value: T.Type) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(value: AnyClass) -> String {
|
||||
|
||||
return "'\(value)'"
|
||||
}
|
||||
|
||||
internal func typeName(name: String?) -> String {
|
||||
|
||||
let typeName = name ?? "unknown"
|
||||
return "<\(typeName)>"
|
||||
}
|
||||
@@ -1,87 +0,0 @@
|
||||
//
|
||||
// DefaultLogger.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - DefaultLogger
|
||||
|
||||
/**
|
||||
The `DefaultLogger` is a basic implementation of the `CoreStoreLogger` protocol.
|
||||
|
||||
- The `log(...)` method calls `print(...)` to print the level, source file name, line number, function name, and the log message.
|
||||
- The `handleError(...)` method calls `print(...)` to print the source file name, line number, function name, and the error message.
|
||||
- The `assert(...)` method calls `assert(...)` on the arguments.
|
||||
*/
|
||||
public final class DefaultLogger: CoreStoreLogger {
|
||||
|
||||
public init() { }
|
||||
|
||||
public func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
let icon: String
|
||||
let levelString: String
|
||||
switch level {
|
||||
|
||||
case .Trace:
|
||||
icon = "🔹"
|
||||
levelString = "Trace"
|
||||
|
||||
case .Notice:
|
||||
icon = "🔸"
|
||||
levelString = "Notice"
|
||||
|
||||
case .Warning:
|
||||
icon = "⚠️"
|
||||
levelString = "Warning"
|
||||
|
||||
case .Fatal:
|
||||
icon = "❗"
|
||||
levelString = "Fatal"
|
||||
}
|
||||
Swift.print("\(icon) [CoreStore: \(levelString)] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
Swift.print("⚠️ [CoreStore: Error] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n")
|
||||
#endif
|
||||
}
|
||||
|
||||
public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
|
||||
|
||||
#if DEBUG
|
||||
if condition() {
|
||||
|
||||
return
|
||||
}
|
||||
Swift.print("❗ [CoreStore: Assertion Failure] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
|
||||
Swift.fatalError()
|
||||
#endif
|
||||
}
|
||||
}
|
||||
@@ -1,148 +0,0 @@
|
||||
//
|
||||
// CoreStore+Migration.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
/**
|
||||
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.addSQLiteStore(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.addSQLiteStore(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public static func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
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.
|
||||
*/
|
||||
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,632 +0,0 @@
|
||||
//
|
||||
// DataStack+Migration.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
public extension DataStack {
|
||||
|
||||
/**
|
||||
Asynchronously adds an in-memory store to the stack.
|
||||
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
|
||||
*/
|
||||
public func addInMemoryStore(configuration configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
|
||||
|
||||
self.coordinator.performBlock {
|
||||
|
||||
do {
|
||||
|
||||
let store = try self.coordinator.addPersistentStoreWithType(
|
||||
NSInMemoryStoreType,
|
||||
configuration: configuration,
|
||||
URL: nil,
|
||||
options: nil
|
||||
)
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
let storeError = error as NSError
|
||||
CoreStore.handleError(
|
||||
storeError,
|
||||
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(storeError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously adds to the stack an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.addSQLiteStore(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously adds to the stack an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
CoreStore.assert(
|
||||
fileURL.fileURL,
|
||||
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
|
||||
)
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
if let store = coordinator.persistentStoreForURL(fileURL) {
|
||||
|
||||
if store.type == NSSQLiteStoreType
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
do {
|
||||
|
||||
try fileManager.createDirectoryAtURL(
|
||||
fileURL.URLByDeletingLastPathComponent!,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
do {
|
||||
|
||||
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL
|
||||
)
|
||||
|
||||
return self.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: fileURL,
|
||||
metadata: metadata,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: { (result) -> Void in
|
||||
|
||||
if case .Failure(let error) = result {
|
||||
|
||||
if resetStoreOnModelMismatch && error.isCoreDataMigrationError {
|
||||
|
||||
fileManager.removeSQLiteStoreAtURL(fileURL)
|
||||
do {
|
||||
|
||||
let store = try self.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: false
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
completion(PersistentStoreResult(error as NSError))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
completion(PersistentStoreResult(error))
|
||||
return
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
let store = try self.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: false
|
||||
)
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
catch {
|
||||
|
||||
completion(PersistentStoreResult(error as NSError))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
catch let error as NSError
|
||||
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
|
||||
|
||||
let store = try self.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: false
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
return try self.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
|
||||
*/
|
||||
public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
|
||||
|
||||
let metadata: [String: AnyObject]
|
||||
do {
|
||||
|
||||
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error as NSError,
|
||||
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
return self.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: fileURL,
|
||||
metadata: metadata,
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles,
|
||||
completion: completion
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
public func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
|
||||
|
||||
return try requiredMigrationsForSQLiteStore(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
mappingModelBundles: mappingModelBundles
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
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
|
||||
)
|
||||
}
|
||||
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.map { 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,245 +0,0 @@
|
||||
//
|
||||
// MigrationChain.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - MigrationChain
|
||||
|
||||
/**
|
||||
A `MigrationChain` indicates the sequence of model versions to be used as the order for incremental migration. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants.
|
||||
|
||||
Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to use the .xcdatamodel's current version as the final version, and to disable incremental migrations:
|
||||
|
||||
let dataStack = DataStack(migrationChain: nil)
|
||||
|
||||
This means that the mapping model will be computed from the store's version straight to the `DataStack`'s model version.
|
||||
To support incremental migrations, specify the linear order of versions:
|
||||
|
||||
let dataStack = DataStack(migrationChain:
|
||||
["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"])
|
||||
|
||||
or for more complex migration paths, a version tree that maps the key-values to the source-destination versions:
|
||||
|
||||
let dataStack = DataStack(migrationChain: [
|
||||
"MyAppModel": "MyAppModelV3",
|
||||
"MyAppModelV2": "MyAppModelV4",
|
||||
"MyAppModelV3": "MyAppModelV4"
|
||||
])
|
||||
|
||||
This allows for different migration paths depending on the starting version. The example above resolves to the following paths:
|
||||
- MyAppModel-MyAppModelV3-MyAppModelV4
|
||||
- MyAppModelV2-MyAppModelV4
|
||||
- MyAppModelV3-MyAppModelV4
|
||||
|
||||
The `MigrationChain` is validated when passed to the `DataStack` and unless it is empty, will raise an assertion if any of the following conditions are met:
|
||||
- a version appears twice in an array
|
||||
- a version appears twice as a key in a dictionary literal
|
||||
- a loop is found in any of the paths
|
||||
*/
|
||||
public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, DictionaryLiteralConvertible, ArrayLiteralConvertible {
|
||||
|
||||
// MARK: NilLiteralConvertible
|
||||
|
||||
public init(nilLiteral: ()) {
|
||||
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = []
|
||||
self.leafVersions = []
|
||||
self.valid = true
|
||||
}
|
||||
|
||||
|
||||
// MARK: StringLiteralConvertible
|
||||
|
||||
public init(stringLiteral value: String) {
|
||||
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = [value]
|
||||
self.leafVersions = [value]
|
||||
self.valid = true
|
||||
}
|
||||
|
||||
|
||||
// MARK: ExtendedGraphemeClusterLiteralConvertible
|
||||
|
||||
public init(extendedGraphemeClusterLiteral value: String) {
|
||||
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = [value]
|
||||
self.leafVersions = [value]
|
||||
self.valid = true
|
||||
}
|
||||
|
||||
|
||||
// MARK: UnicodeScalarLiteralConvertible
|
||||
|
||||
public init(unicodeScalarLiteral value: String) {
|
||||
|
||||
self.versionTree = [:]
|
||||
self.rootVersions = [value]
|
||||
self.leafVersions = [value]
|
||||
self.valid = true
|
||||
}
|
||||
|
||||
|
||||
// MARK: DictionaryLiteralConvertible
|
||||
|
||||
public init(dictionaryLiteral elements: (String, String)...) {
|
||||
|
||||
var valid = true
|
||||
let versionTree = elements.reduce([String: String]()) { (var versionTree, tuple: (String, String)) -> [String: String] in
|
||||
|
||||
if let _ = versionTree.updateValue(tuple.1, forKey: tuple.0) {
|
||||
|
||||
CoreStore.assert(false, "\(typeName(MigrationChain))'s migration chain could not be created due to ambiguous version paths.")
|
||||
|
||||
valid = false
|
||||
}
|
||||
return versionTree
|
||||
}
|
||||
let leafVersions = Set(
|
||||
elements.filter { (tuple: (String, String)) -> Bool in
|
||||
|
||||
return versionTree[tuple.1] == nil
|
||||
|
||||
}.map { $1 }
|
||||
)
|
||||
|
||||
let isVersionAmbiguous = { (start: String) -> Bool in
|
||||
|
||||
var checklist: Set<String> = [start]
|
||||
var version = start
|
||||
while let nextVersion = versionTree[version] where nextVersion != version {
|
||||
|
||||
if checklist.contains(nextVersion) {
|
||||
|
||||
CoreStore.assert(false, "\(typeName(MigrationChain))'s migration chain could not be created due to looping version paths.")
|
||||
|
||||
return true
|
||||
}
|
||||
checklist.insert(nextVersion)
|
||||
version = nextVersion
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
self.versionTree = versionTree
|
||||
self.rootVersions = Set(versionTree.keys).subtract(versionTree.values)
|
||||
self.leafVersions = leafVersions
|
||||
self.valid = valid && Set(versionTree.keys).union(versionTree.values).filter { isVersionAmbiguous($0) }.count <= 0
|
||||
}
|
||||
|
||||
|
||||
// MARK: ArrayLiteralConvertible
|
||||
|
||||
public init(arrayLiteral elements: String...) {
|
||||
|
||||
CoreStore.assert(Set(elements).count == elements.count, "\(typeName(MigrationChain))'s migration chain could not be created due to duplicate version strings.")
|
||||
|
||||
var lastVersion: String?
|
||||
var versionTree = [String: String]()
|
||||
var valid = true
|
||||
for version in elements {
|
||||
|
||||
if let lastVersion = lastVersion {
|
||||
|
||||
if let _ = versionTree.updateValue(version, forKey: lastVersion) {
|
||||
|
||||
valid = false
|
||||
}
|
||||
}
|
||||
lastVersion = version
|
||||
}
|
||||
|
||||
self.versionTree = versionTree
|
||||
self.rootVersions = Set([elements.first].flatMap { $0 == nil ? [] : [$0!] })
|
||||
self.leafVersions = Set([elements.last].flatMap { $0 == nil ? [] : [$0!] })
|
||||
self.valid = valid
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let rootVersions: Set<String>
|
||||
internal let leafVersions: Set<String>
|
||||
internal let valid: Bool
|
||||
|
||||
internal var empty: Bool {
|
||||
|
||||
return self.versionTree.count <= 0
|
||||
}
|
||||
|
||||
internal func contains(version: String) -> Bool {
|
||||
|
||||
return self.rootVersions.contains(version)
|
||||
|| self.leafVersions.contains(version)
|
||||
|| self.versionTree[version] != nil
|
||||
}
|
||||
|
||||
internal func nextVersionFrom(version: String) -> String? {
|
||||
|
||||
if let nextVersion = self.versionTree[version] where nextVersion != version {
|
||||
|
||||
return nextVersion
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let versionTree: [String: String]
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MigrationChain: CustomDebugStringConvertible
|
||||
|
||||
extension MigrationChain: CustomDebugStringConvertible {
|
||||
|
||||
public var debugDescription: String {
|
||||
|
||||
guard self.valid else {
|
||||
|
||||
return "<invalid migration chain>"
|
||||
}
|
||||
|
||||
var paths = [String]()
|
||||
for var version in self.rootVersions {
|
||||
|
||||
var steps = [version]
|
||||
while let nextVersion = self.nextVersionFrom(version) {
|
||||
|
||||
steps.append(nextVersion)
|
||||
version = nextVersion
|
||||
}
|
||||
paths.append(" → ".join(steps))
|
||||
}
|
||||
|
||||
return "[" + "], [".join(paths) + "]"
|
||||
}
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
//
|
||||
// MigrationResult.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - MigrationResult
|
||||
|
||||
/**
|
||||
The `MigrationResult` indicates the result of a migration.
|
||||
The `MigrationResult` can be treated as a boolean:
|
||||
|
||||
CoreStore.upgradeSQLiteStoreIfNeeded { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
if result {
|
||||
// succeeded
|
||||
}
|
||||
else {
|
||||
// failed
|
||||
}
|
||||
}
|
||||
|
||||
or as an `enum`, where the resulting associated object can also be inspected:
|
||||
|
||||
CoreStore.beginAsynchronous { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
switch result {
|
||||
case .Success(let hasChanges):
|
||||
// hasChanges indicates if there were changes or not
|
||||
case .Failure(let error):
|
||||
// error is the NSError instance for the failure
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public enum MigrationResult {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
`MigrationResult.Success` indicates either the migration succeeded, or there were no migrations needed. The associated value is an array of `MigrationType`s reflecting the migration steps completed.
|
||||
*/
|
||||
case Success([MigrationType])
|
||||
|
||||
/**
|
||||
`SaveResult.Failure` indicates that the migration failed. The associated object for this value is the related `NSError` instance.
|
||||
*/
|
||||
case Failure(NSError)
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(_ migrationTypes: [MigrationType]) {
|
||||
|
||||
self = .Success(migrationTypes)
|
||||
}
|
||||
|
||||
internal init(_ error: NSError) {
|
||||
|
||||
self = .Failure(error)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode) {
|
||||
|
||||
self.init(errorCode, userInfo: nil)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - MigrationResult: BooleanType
|
||||
|
||||
extension MigrationResult: BooleanType {
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
case .Success: return true
|
||||
case .Failure: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,125 +0,0 @@
|
||||
//
|
||||
// MigrationType.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - MigrationType
|
||||
|
||||
/**
|
||||
The `MigrationType` specifies the type of migration required for a store.
|
||||
*/
|
||||
public enum MigrationType: BooleanType {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Indicates that the persistent store matches the latest model version and no migration is needed
|
||||
*/
|
||||
case None(version: String)
|
||||
|
||||
/**
|
||||
Indicates that the persistent store does not match the latest model version but Core Data can infer the mapping model, so a lightweight migration is needed
|
||||
*/
|
||||
case Lightweight(sourceVersion: String, destinationVersion: String)
|
||||
|
||||
/**
|
||||
Indicates that the persistent store does not match the latest model version and Core Data could not infer a mapping model, so a custom migration is needed
|
||||
*/
|
||||
case Heavyweight(sourceVersion: String, destinationVersion: String)
|
||||
|
||||
/**
|
||||
Returns the source model version for the migration type. If no migration is required, `sourceVersion` will be equal to the `destinationVersion`.
|
||||
*/
|
||||
public var sourceVersion: String {
|
||||
|
||||
switch self {
|
||||
|
||||
case .None(let version):
|
||||
return version
|
||||
|
||||
case .Lightweight(let sourceVersion, _):
|
||||
return sourceVersion
|
||||
|
||||
case .Heavyweight(let sourceVersion, _):
|
||||
return sourceVersion
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the destination model version for the migration type. If no migration is required, `destinationVersion` will be equal to the `sourceVersion`.
|
||||
*/
|
||||
public var destinationVersion: String {
|
||||
|
||||
switch self {
|
||||
|
||||
case .None(let version):
|
||||
return version
|
||||
|
||||
case .Lightweight(_, let destinationVersion):
|
||||
return destinationVersion
|
||||
|
||||
case .Heavyweight(_, let destinationVersion):
|
||||
return destinationVersion
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `MigrationType` is a lightweight migration. Used as syntactic sugar.
|
||||
*/
|
||||
public var isLightweightMigration: Bool {
|
||||
|
||||
if case .Lightweight = self {
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `MigrationType` is a heavyweight migration. Used as syntactic sugar.
|
||||
*/
|
||||
public var isHeavyweightMigration: Bool {
|
||||
|
||||
if case .Heavyweight = self {
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
// MARK: BooleanType
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .None: return false
|
||||
case .Lightweight: return true
|
||||
case .Heavyweight: return true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,98 +0,0 @@
|
||||
//
|
||||
// NSError+CoreStore.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
/**
|
||||
The `NSError` error domain for `CoreStore`.
|
||||
*/
|
||||
public let CoreStoreErrorDomain = "com.corestore.error"
|
||||
|
||||
/**
|
||||
The `NSError` error codes for `CoreStoreErrorDomain`.
|
||||
*/
|
||||
public enum CoreStoreErrorCode: Int {
|
||||
|
||||
/**
|
||||
A failure occured because of an unknown error.
|
||||
*/
|
||||
case UnknownError
|
||||
|
||||
/**
|
||||
The `NSPersistentStore` could note be initialized because another store existed at the specified `NSURL`.
|
||||
*/
|
||||
case DifferentPersistentStoreExistsAtURL
|
||||
|
||||
/**
|
||||
The `NSPersistentStore` specified could not be found.
|
||||
*/
|
||||
case PersistentStoreNotFound
|
||||
|
||||
/**
|
||||
An `NSMappingModel` could not be found for a specific source and destination model versions.
|
||||
*/
|
||||
case MappingModelNotFound
|
||||
}
|
||||
|
||||
|
||||
// MARK: - NSError+CoreStore
|
||||
|
||||
public extension NSError {
|
||||
|
||||
/**
|
||||
If the error's domain is equal to `CoreStoreErrorDomain`, returns the associated `CoreStoreErrorCode`. For other domains, returns `nil`.
|
||||
*/
|
||||
public var coreStoreErrorCode: CoreStoreErrorCode? {
|
||||
|
||||
return (self.domain == CoreStoreErrorDomain
|
||||
? CoreStoreErrorCode(rawValue: self.code)
|
||||
: nil)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode) {
|
||||
|
||||
self.init(coreStoreErrorCode: coreStoreErrorCode, userInfo: nil)
|
||||
}
|
||||
|
||||
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(
|
||||
domain: CoreStoreErrorDomain,
|
||||
code: coreStoreErrorCode.rawValue,
|
||||
userInfo: userInfo)
|
||||
}
|
||||
|
||||
internal var isCoreDataMigrationError: Bool {
|
||||
|
||||
let code = self.code
|
||||
return (code == NSPersistentStoreIncompatibleVersionHashError
|
||||
|| code == NSMigrationMissingSourceModelError
|
||||
|| code == NSMigrationError)
|
||||
&& self.domain == NSCocoaErrorDomain
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
//
|
||||
// CoreStore+Observing.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, 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`
|
||||
*/
|
||||
public static func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
|
||||
|
||||
return self.defaultStack.monitorObject(object)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: FetchClause...) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.monitorList(from, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.monitorList(from, queryClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
public static func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses)
|
||||
}
|
||||
|
||||
/**
|
||||
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
|
||||
|
||||
- parameter from: a `From` clause indicating the entity type
|
||||
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
|
||||
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
|
||||
- returns: a `ListMonitor` instance that monitors changes to the list
|
||||
*/
|
||||
public static func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
|
||||
|
||||
return self.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses)
|
||||
}
|
||||
}
|
||||
@@ -1,133 +0,0 @@
|
||||
//
|
||||
// DataStack+Observing.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
public extension DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
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`
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
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
|
||||
*/
|
||||
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
|
||||
*/
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,944 +0,0 @@
|
||||
//
|
||||
// ListMonitor.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - ListMonitor
|
||||
|
||||
/**
|
||||
The `ListMonitor` monitors changes to a list of `NSManagedObject` instances. Observers that implement the `ListObserver` protocol may then register themselves to the `ListMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorList(
|
||||
From(MyPersonEntity),
|
||||
Where("title", isEqualTo: "Engineer"),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
monitor.addObserver(self)
|
||||
|
||||
The `ListMonitor` instance needs to be held on (retained) for as long as the list needs to be observed.
|
||||
Observers registered via `addObserver(_:)` are not retained. `ListMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
|
||||
|
||||
Lists created with `monitorList(...)` keep a single-section list of objects, where each object can be accessed by index:
|
||||
|
||||
let firstPerson: MyPersonEntity = monitor[0]
|
||||
|
||||
Accessing the list with an index above the valid range will throw an exception.
|
||||
|
||||
Creating a sectioned-list is also possible with the `monitorSectionedList(...)` method:
|
||||
|
||||
let monitor = CoreStore.monitorSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionBy("age") { "Age \($0)" },
|
||||
Where("title", isEqualTo: "Engineer"),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
monitor.addObserver(self)
|
||||
|
||||
Objects from `ListMonitor`s created this way can be accessed either by an `NSIndexPath` or a tuple:
|
||||
|
||||
let indexPath = NSIndexPath(forItem: 3, inSection: 2)
|
||||
let person1 = monitor[indexPath]
|
||||
let person2 = monitor[2, 3]
|
||||
|
||||
In the example above, both `person1` and `person2` will contain the object at section=2, index=3.
|
||||
*/
|
||||
public final class ListMonitor<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Returns the object at the given index within the first section. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`.
|
||||
|
||||
- parameter index: the index of the object. Using an index above the valid range will throw an exception.
|
||||
- returns: the `NSManagedObject` at the specified index
|
||||
*/
|
||||
public subscript(index: Int) -> T {
|
||||
|
||||
return self[0, index]
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object at the given index, or `nil` if out of bounds. This subscript indexer is typically used for `ListMonitor`s created with `monitorList(_:)`.
|
||||
|
||||
- parameter index: the index for the object. Using an index above the valid range will return `nil`.
|
||||
- returns: the `NSManagedObject` at the specified index, or `nil` if out of bounds
|
||||
*/
|
||||
public subscript(safeIndex index: Int) -> T? {
|
||||
|
||||
return self[safeSectionIndex: 0, safeItemIndex: index]
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object at the given `sectionIndex` and `itemIndex`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`.
|
||||
|
||||
- parameter sectionIndex: the section index for the object. Using a `sectionIndex` with an invalid range will throw an exception.
|
||||
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will throw an exception.
|
||||
- returns: the `NSManagedObject` at the specified section and item index
|
||||
*/
|
||||
public subscript(sectionIndex: Int, itemIndex: Int) -> T {
|
||||
|
||||
return self[NSIndexPath(forItem: itemIndex, inSection: sectionIndex)]
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object at the given section and item index, or `nil` if out of bounds. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`.
|
||||
|
||||
- parameter sectionIndex: the section index for the object. Using a `sectionIndex` with an invalid range will return `nil`.
|
||||
- parameter itemIndex: the index for the object within the section. Using an `itemIndex` with an invalid range will return `nil`.
|
||||
- returns: the `NSManagedObject` at the specified section and item index, or `nil` if out of bounds
|
||||
*/
|
||||
public subscript(safeSectionIndex sectionIndex: Int, safeItemIndex itemIndex: Int) -> T? {
|
||||
|
||||
guard let sections = self.fetchedResultsController.sections
|
||||
where sectionIndex < sections.count else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let section = sections[sectionIndex]
|
||||
guard itemIndex < section.numberOfObjects else {
|
||||
|
||||
return nil
|
||||
}
|
||||
return sections[sectionIndex].objects?[itemIndex] as? T
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object at the given `NSIndexPath`. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`.
|
||||
|
||||
- parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will throw an exception.
|
||||
- returns: the `NSManagedObject` at the specified index path
|
||||
*/
|
||||
public subscript(indexPath: NSIndexPath) -> T {
|
||||
|
||||
return self.fetchedResultsController.objectAtIndexPath(indexPath) as! T
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the object at the given `NSIndexPath`, or `nil` if out of bounds. This subscript indexer is typically used for `ListMonitor`s created with `monitorSectionedList(_:)`.
|
||||
|
||||
- parameter indexPath: the `NSIndexPath` for the object. Using an `indexPath` with an invalid range will return `nil`.
|
||||
- returns: the `NSManagedObject` at the specified index path, or `nil` if out of bounds
|
||||
*/
|
||||
public subscript(safeIndexPath indexPath: NSIndexPath) -> T? {
|
||||
|
||||
return self[safeSectionIndex: indexPath.section, safeItemIndex: indexPath.item]
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the `ListMonitor` has at least one object in any section.
|
||||
|
||||
- returns: `true` if at least one object in any section exists, `false` otherwise
|
||||
*/
|
||||
public func hasObjects() -> Bool {
|
||||
|
||||
return self.numberOfObjects() > 0
|
||||
}
|
||||
|
||||
/**
|
||||
Checks if the `ListMonitor` has at least one object the specified section.
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will return `false`.
|
||||
- returns: `true` if at least one object in the specified section exists, `false` otherwise
|
||||
*/
|
||||
public func hasObjectsInSection(section: Int) -> Bool {
|
||||
|
||||
return self.numberOfObjectsInSection(safeSectionIndex: section) > 0
|
||||
}
|
||||
|
||||
/**
|
||||
Returns all objects in all sections
|
||||
|
||||
- returns: all objects in all sections
|
||||
*/
|
||||
public func objectsInAllSections() -> [T] {
|
||||
|
||||
return (self.fetchedResultsController.fetchedObjects as? [T]) ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
Returns all objects in the specified section
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will throw an exception.
|
||||
- returns: all objects in the specified section
|
||||
*/
|
||||
public func objectsInSection(section: Int) -> [T] {
|
||||
|
||||
return (self.fetchedResultsController.sections?[section].objects as? [T]) ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
Returns all objects in the specified section, or `nil` if out of bounds.
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will return `nil`.
|
||||
- returns: all objects in the specified section
|
||||
*/
|
||||
public func objectsInSection(safeSectionIndex section: Int) -> [T]? {
|
||||
|
||||
return (self.fetchedResultsController.sections?[section].objects as? [T]) ?? []
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of sections
|
||||
|
||||
- returns: the number of sections
|
||||
*/
|
||||
public func numberOfSections() -> Int {
|
||||
|
||||
return self.fetchedResultsController.sections?.count ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of objects in all sections
|
||||
|
||||
- returns: the number of objects in all sections
|
||||
*/
|
||||
public func numberOfObjects() -> Int {
|
||||
|
||||
return self.fetchedResultsController.fetchedObjects?.count ?? 0
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of objects in the specified section
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will throw an exception.
|
||||
- returns: the number of objects in the specified section
|
||||
*/
|
||||
public func numberOfObjectsInSection(section: Int) -> Int {
|
||||
|
||||
return self.sectionInfoAtIndex(section).numberOfObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the number of objects in the specified section, or `nil` if out of bounds.
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will return `nil`.
|
||||
- returns: the number of objects in the specified section
|
||||
*/
|
||||
public func numberOfObjectsInSection(safeSectionIndex section: Int) -> Int? {
|
||||
|
||||
return self.sectionInfoAtIndex(safeSectionIndex: section)?.numberOfObjects
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSFetchedResultsSectionInfo` for the specified section
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will throw an exception.
|
||||
- returns: the `NSFetchedResultsSectionInfo` for the specified section
|
||||
*/
|
||||
public func sectionInfoAtIndex(section: Int) -> NSFetchedResultsSectionInfo {
|
||||
|
||||
return self.fetchedResultsController.sections![section]
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSFetchedResultsSectionInfo` for the specified section, or `nil` if out of bounds.
|
||||
|
||||
- parameter section: the section index. Using an index outside the valid range will return `nil`.
|
||||
- returns: the `NSFetchedResultsSectionInfo` for the specified section, or `nil` if the section index is out of bounds.
|
||||
*/
|
||||
public func sectionInfoAtIndex(safeSectionIndex section: Int) -> NSFetchedResultsSectionInfo? {
|
||||
|
||||
guard let sections = self.fetchedResultsController.sections
|
||||
where section < sections.count else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return sections[section]
|
||||
}
|
||||
|
||||
/**
|
||||
Registers a `ListObserver` to be notified when changes to the receiver's list occur.
|
||||
|
||||
To prevent retain-cycles, `ListMonitor` 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 `ListMonitor` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
- parameter observer: a `ListObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ListObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to add an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.willChangeList,
|
||||
name: ListMonitorWillChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitorWillChange(monitor)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.didChangeList,
|
||||
name: ListMonitorDidChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitorDidChange(monitor)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Registers a `ListObjectObserver` to be notified when changes to the receiver's list occur.
|
||||
|
||||
To prevent retain-cycles, `ListMonitor` 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 `ListMonitor` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
- parameter observer: a `ListObjectObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ListObjectObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to add an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.willChangeList,
|
||||
name: ListMonitorWillChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitorWillChange(monitor)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.didChangeList,
|
||||
name: ListMonitorDidChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitorDidChange(monitor)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didInsertObject,
|
||||
name: ListMonitorDidInsertObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didInsertObject: object,
|
||||
toIndexPath: newIndexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didDeleteObject,
|
||||
name: ListMonitorDidDeleteObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didDeleteObject: object,
|
||||
fromIndexPath: indexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didUpdateObject,
|
||||
name: ListMonitorDidUpdateObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
atIndexPath: indexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didMoveObject,
|
||||
name: ListMonitorDidMoveObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didMoveObject: object,
|
||||
fromIndexPath: indexPath!,
|
||||
toIndexPath: newIndexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Registers a `ListSectionObserver` to be notified when changes to the receiver's list occur.
|
||||
|
||||
To prevent retain-cycles, `ListMonitor` 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 `ListMonitor` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
- parameter observer: a `ListSectionObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ListSectionObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to add an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.willChangeList,
|
||||
name: ListMonitorWillChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitorWillChange(monitor)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.didChangeList,
|
||||
name: ListMonitorDidChangeListNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitorDidChange(monitor)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didInsertObject,
|
||||
name: ListMonitorDidInsertObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didInsertObject: object,
|
||||
toIndexPath: newIndexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didDeleteObject,
|
||||
name: ListMonitorDidDeleteObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didDeleteObject: object,
|
||||
fromIndexPath: indexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didUpdateObject,
|
||||
name: ListMonitorDidUpdateObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
atIndexPath: indexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didMoveObject,
|
||||
name: ListMonitorDidMoveObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object, indexPath, newIndexPath) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didMoveObject: object,
|
||||
fromIndexPath: indexPath!,
|
||||
toIndexPath: newIndexPath!
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
self.registerSectionNotification(
|
||||
&NotificationKey.didInsertSection,
|
||||
name: ListMonitorDidInsertSectionNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, sectionInfo, sectionIndex) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didInsertSection: sectionInfo,
|
||||
toSectionIndex: sectionIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerSectionNotification(
|
||||
&NotificationKey.didDeleteSection,
|
||||
name: ListMonitorDidDeleteSectionNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, sectionInfo, sectionIndex) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.listMonitor(
|
||||
monitor,
|
||||
didDeleteSection: sectionInfo,
|
||||
fromSectionIndex: sectionIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Unregisters a `ListObserver` from receiving notifications for changes to the receiver's list.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
- parameter observer: a `ListObserver` to unregister notifications to
|
||||
*/
|
||||
public func removeObserver<U: ListObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to remove an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
let nilValue: AnyObject? = nil
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeList, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didChangeList, inObject: observer)
|
||||
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didInsertObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didDeleteObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didUpdateObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didMoveObject, inObject: observer)
|
||||
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didInsertSection, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didDeleteSection, inObject: observer)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(dataStack: DataStack, from: From<T>, sectionBy: SectionBy?, fetchClauses: [FetchClause]) {
|
||||
|
||||
let context = dataStack.mainContext
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
from.applyToFetchRequest(fetchRequest, context: context)
|
||||
|
||||
fetchRequest.fetchLimit = 0
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
|
||||
for clause in fetchClauses {
|
||||
|
||||
clause.applyToFetchRequest(fetchRequest)
|
||||
}
|
||||
|
||||
let fetchedResultsController = NSFetchedResultsController(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: context,
|
||||
sectionNameKeyPath: sectionBy?.sectionKeyPath,
|
||||
cacheName: nil
|
||||
)
|
||||
|
||||
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
|
||||
|
||||
self.fetchedResultsController = fetchedResultsController
|
||||
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
|
||||
self.parentStack = dataStack
|
||||
|
||||
if let sectionIndexTransformer = sectionBy?.sectionIndexTransformer {
|
||||
|
||||
self.sectionIndexTransformer = sectionIndexTransformer
|
||||
}
|
||||
else {
|
||||
|
||||
self.sectionIndexTransformer = { $0 }
|
||||
}
|
||||
|
||||
fetchedResultsControllerDelegate.handler = self
|
||||
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
|
||||
try! fetchedResultsController.performFetch()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let fetchedResultsController: NSFetchedResultsController
|
||||
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
|
||||
private let sectionIndexTransformer: (sectionName: KeyPath?) -> String?
|
||||
private weak var parentStack: DataStack?
|
||||
|
||||
private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ListMonitor<T>) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
if let strongSelf = self {
|
||||
|
||||
callback(monitor: strongSelf)
|
||||
}
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
|
||||
private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ListMonitor<T>, object: T, indexPath: NSIndexPath?, newIndexPath: NSIndexPath?) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
if let strongSelf = self,
|
||||
let userInfo = note.userInfo,
|
||||
let object = userInfo[UserInfoKeyObject] as? T {
|
||||
|
||||
callback(
|
||||
monitor: strongSelf,
|
||||
object: object,
|
||||
indexPath: userInfo[UserInfoKeyIndexPath] as? NSIndexPath,
|
||||
newIndexPath: userInfo[UserInfoKeyNewIndexPath] as? NSIndexPath
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
|
||||
private func registerSectionNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ListMonitor<T>, sectionInfo: NSFetchedResultsSectionInfo, sectionIndex: Int) -> Void) {
|
||||
|
||||
setAssociatedRetainedObject(
|
||||
NotificationObserver(
|
||||
notificationName: name,
|
||||
object: self,
|
||||
closure: { [weak self] (note) -> Void in
|
||||
|
||||
if let strongSelf = self,
|
||||
let userInfo = note.userInfo,
|
||||
let sectionInfo = userInfo[UserInfoKeySectionInfo] as? NSFetchedResultsSectionInfo,
|
||||
let sectionIndex = (userInfo[UserInfoKeySectionIndex] as? NSNumber)?.integerValue {
|
||||
|
||||
callback(
|
||||
monitor: strongSelf,
|
||||
sectionInfo: sectionInfo,
|
||||
sectionIndex: sectionIndex
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListMonitor: Equatable
|
||||
|
||||
public func ==<T: NSManagedObject>(lhs: ListMonitor<T>, rhs: ListMonitor<T>) -> Bool {
|
||||
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
extension ListMonitor: Equatable { }
|
||||
|
||||
|
||||
// MARK: - ListMonitor: FetchedResultsControllerHandler
|
||||
|
||||
extension ListMonitor: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: FetchedResultsControllerHandler
|
||||
|
||||
private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
switch type {
|
||||
|
||||
case .Insert:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ListMonitorDidInsertObjectNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeyObject: anObject,
|
||||
UserInfoKeyNewIndexPath: newIndexPath!
|
||||
]
|
||||
)
|
||||
|
||||
case .Delete:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ListMonitorDidDeleteObjectNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeyObject: anObject,
|
||||
UserInfoKeyIndexPath: indexPath!
|
||||
]
|
||||
)
|
||||
|
||||
case .Update:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ListMonitorDidUpdateObjectNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeyObject: anObject,
|
||||
UserInfoKeyIndexPath: indexPath!
|
||||
]
|
||||
)
|
||||
|
||||
case .Move:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ListMonitorDidMoveObjectNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeyObject: anObject,
|
||||
UserInfoKeyIndexPath: indexPath!,
|
||||
UserInfoKeyNewIndexPath: newIndexPath!
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
|
||||
switch type {
|
||||
|
||||
case .Insert:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ListMonitorDidInsertSectionNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeySectionInfo: sectionInfo,
|
||||
UserInfoKeySectionIndex: NSNumber(integer: sectionIndex)
|
||||
]
|
||||
)
|
||||
|
||||
case .Delete:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ListMonitorDidDeleteSectionNotification,
|
||||
object: self,
|
||||
userInfo: [
|
||||
UserInfoKeySectionInfo: sectionInfo,
|
||||
UserInfoKeySectionIndex: NSNumber(integer: sectionIndex)
|
||||
]
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ListMonitorWillChangeListNotification,
|
||||
object: self
|
||||
)
|
||||
}
|
||||
|
||||
private func controllerDidChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ListMonitorDidChangeListNotification,
|
||||
object: self
|
||||
)
|
||||
}
|
||||
|
||||
private func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String? {
|
||||
|
||||
return self.sectionIndexTransformer(sectionName: sectionName)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FetchedResultsControllerHandler
|
||||
|
||||
private protocol FetchedResultsControllerHandler: class {
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType)
|
||||
|
||||
func controllerWillChangeContent(controller: NSFetchedResultsController)
|
||||
|
||||
func controllerDidChangeContent(controller: NSFetchedResultsController)
|
||||
|
||||
func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String?) -> String?
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FetchedResultsControllerDelegate
|
||||
|
||||
private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
|
||||
|
||||
// MARK: NSFetchedResultsControllerDelegate
|
||||
|
||||
@objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
self.handler?.controllerWillChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
self.handler?.controllerDidChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
|
||||
|
||||
self.handler?.controller(controller, didChangeSection: sectionInfo, atIndex: sectionIndex, forChangeType: type)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
|
||||
|
||||
return self.handler?.controller(controller, sectionIndexTitleForSectionName: sectionName)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
weak var handler: FetchedResultsControllerHandler?
|
||||
weak var fetchedResultsController: NSFetchedResultsController? {
|
||||
|
||||
didSet {
|
||||
|
||||
oldValue?.delegate = nil
|
||||
self.fetchedResultsController?.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.fetchedResultsController?.delegate = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private let ListMonitorWillChangeListNotification = "ListMonitorWillChangeListNotification"
|
||||
private let ListMonitorDidChangeListNotification = "ListMonitorDidChangeListNotification"
|
||||
|
||||
private let ListMonitorDidInsertObjectNotification = "ListMonitorDidInsertObjectNotification"
|
||||
private let ListMonitorDidDeleteObjectNotification = "ListMonitorDidDeleteObjectNotification"
|
||||
private let ListMonitorDidUpdateObjectNotification = "ListMonitorDidUpdateObjectNotification"
|
||||
private let ListMonitorDidMoveObjectNotification = "ListMonitorDidMoveObjectNotification"
|
||||
|
||||
private let ListMonitorDidInsertSectionNotification = "ListMonitorDidInsertSectionNotification"
|
||||
private let ListMonitorDidDeleteSectionNotification = "ListMonitorDidDeleteSectionNotification"
|
||||
|
||||
private let UserInfoKeyObject = "UserInfoKeyObject"
|
||||
private let UserInfoKeyIndexPath = "UserInfoKeyIndexPath"
|
||||
private let UserInfoKeyNewIndexPath = "UserInfoKeyNewIndexPath"
|
||||
|
||||
private let UserInfoKeySectionInfo = "UserInfoKeySectionInfo"
|
||||
private let UserInfoKeySectionIndex = "UserInfoKeySectionIndex"
|
||||
|
||||
private struct NotificationKey {
|
||||
|
||||
static var willChangeList: Void?
|
||||
static var didChangeList: Void?
|
||||
|
||||
static var didInsertObject: Void?
|
||||
static var didDeleteObject: Void?
|
||||
static var didUpdateObject: Void?
|
||||
static var didMoveObject: Void?
|
||||
|
||||
static var didInsertSection: Void?
|
||||
static var didDeleteSection: Void?
|
||||
}
|
||||
@@ -1,196 +0,0 @@
|
||||
//
|
||||
// ListObserver.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - ListObserver
|
||||
|
||||
/**
|
||||
Implement the `ListObserver` protocol to observe changes to a list of `NSManagedObject`s. `ListObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorList(
|
||||
From(MyPersonEntity),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
monitor.addObserver(self)
|
||||
*/
|
||||
public protocol ListObserver: class {
|
||||
|
||||
/**
|
||||
The `NSManagedObject` type for the observed list
|
||||
*/
|
||||
typealias EntityType: NSManagedObject
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed list occurs
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
*/
|
||||
func listMonitorWillChange(monitor: ListMonitor<EntityType>)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed list occurs
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the object being observed
|
||||
*/
|
||||
func listMonitorDidChange(monitor: ListMonitor<EntityType>)
|
||||
}
|
||||
|
||||
public extension ListObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitorWillChange(monitor: ListMonitor<EntityType>) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitorDidChange(monitor: ListMonitor<EntityType>) { }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListObjectObserver
|
||||
|
||||
/**
|
||||
Implement the `ListObjectObserver` protocol to observe detailed changes to a list's object. `ListObjectObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorList(
|
||||
From(MyPersonEntity),
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
monitor.addObserver(self)
|
||||
*/
|
||||
public protocol ListObjectObserver: ListObserver {
|
||||
|
||||
/**
|
||||
Notifies that an object was inserted to the specified `NSIndexPath` in the list
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the inserted object
|
||||
- parameter indexPath: the new `NSIndexPath` for the inserted object
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath)
|
||||
|
||||
/**
|
||||
Notifies that an object was deleted from the specified `NSIndexPath` in the list
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the deleted object
|
||||
- parameter indexPath: the `NSIndexPath` for the deleted object
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath)
|
||||
|
||||
/**
|
||||
Notifies that an object at the specified `NSIndexPath` was updated
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the updated object
|
||||
- parameter indexPath: the `NSIndexPath` for the updated object
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath)
|
||||
|
||||
/**
|
||||
Notifies that an object's index changed
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter object: the entity type for the moved object
|
||||
- parameter fromIndexPath: the previous `NSIndexPath` for the moved object
|
||||
- parameter toIndexPath: the new `NSIndexPath` for the moved object
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
|
||||
}
|
||||
|
||||
public extension ListObjectObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didInsertObject object: EntityType, toIndexPath indexPath: NSIndexPath) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didDeleteObject object: EntityType, fromIndexPath indexPath: NSIndexPath) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didUpdateObject object: EntityType, atIndexPath indexPath: NSIndexPath) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didMoveObject object: EntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { }
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListSectionObserver
|
||||
|
||||
/**
|
||||
Implement the `ListSectionObserver` protocol to observe changes to a list's section info. `ListSectionObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionBy("age") { "Age \($0)" },
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
monitor.addObserver(self)
|
||||
*/
|
||||
public protocol ListSectionObserver: ListObjectObserver {
|
||||
|
||||
/**
|
||||
Notifies that a section was inserted at the specified index
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section
|
||||
- parameter sectionIndex: the new section index for the new section
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
|
||||
|
||||
/**
|
||||
Notifies that a section was inserted at the specified index
|
||||
|
||||
- parameter monitor: the `ListMonitor` monitoring the list being observed
|
||||
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section
|
||||
- parameter sectionIndex: the previous section index for the deleted section
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
|
||||
}
|
||||
|
||||
public extension ListSectionObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func listMonitor(monitor: ListMonitor<EntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { }
|
||||
}
|
||||
@@ -1,356 +0,0 @@
|
||||
//
|
||||
// ObjectMonitor.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
private let ObjectMonitorWillChangeObjectNotification = "ObjectMonitorWillChangeObjectNotification"
|
||||
private let ObjectMonitorDidDeleteObjectNotification = "ObjectMonitorDidDeleteObjectNotification"
|
||||
private let ObjectMonitorDidUpdateObjectNotification = "ObjectMonitorDidUpdateObjectNotification"
|
||||
|
||||
private let UserInfoKeyObject = "UserInfoKeyObject"
|
||||
|
||||
private struct NotificationKey {
|
||||
|
||||
static var willChangeObject: Void?
|
||||
static var didDeleteObject: Void?
|
||||
static var didUpdateObject: Void?
|
||||
}
|
||||
|
||||
|
||||
// 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.
|
||||
*/
|
||||
public final class ObjectMonitor<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted.
|
||||
*/
|
||||
public var object: T? {
|
||||
|
||||
return self.fetchedResultsController.fetchedObjects?.first as? T
|
||||
}
|
||||
|
||||
/**
|
||||
Returns `true` if the `NSManagedObject` instance being observed still exists, or `false` if the object was already deleted.
|
||||
*/
|
||||
public var isObjectDeleted: Bool {
|
||||
|
||||
return self.object?.managedObjectContext == nil
|
||||
}
|
||||
|
||||
/**
|
||||
Registers an `ObjectObserver` to be notified when changes to the receiver's `object` are made.
|
||||
|
||||
To prevent retain-cycles, `ObjectMonitor` only keeps `weak` references to its observers.
|
||||
|
||||
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
|
||||
|
||||
Calling `addObserver(_:)` multiple times on the same observer is safe, as `ObjectMonitor` unregisters previous notifications to the observer before re-registering them.
|
||||
|
||||
- parameter observer: an `ObjectObserver` to send change notifications to
|
||||
*/
|
||||
public func addObserver<U: ObjectObserver where U.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to add an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
self.removeObserver(observer)
|
||||
|
||||
self.registerChangeNotification(
|
||||
&NotificationKey.willChangeObject,
|
||||
name: ObjectMonitorWillChangeObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor) -> Void in
|
||||
|
||||
if let object = monitor.object, let observer = observer {
|
||||
|
||||
observer.objectMonitor(monitor, willUpdateObject: object)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didDeleteObject,
|
||||
name: ObjectMonitorDidDeleteObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak observer] (monitor, object) -> Void in
|
||||
|
||||
if let observer = observer {
|
||||
|
||||
observer.objectMonitor(monitor, didDeleteObject: object)
|
||||
}
|
||||
}
|
||||
)
|
||||
self.registerObjectNotification(
|
||||
&NotificationKey.didUpdateObject,
|
||||
name: ObjectMonitorDidUpdateObjectNotification,
|
||||
toObserver: observer,
|
||||
callback: { [weak self, weak observer] (monitor, object) -> Void in
|
||||
|
||||
if let strongSelf = self, let observer = observer {
|
||||
|
||||
let previousCommitedAttributes = strongSelf.lastCommittedAttributes
|
||||
let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [String: NSObject]
|
||||
|
||||
let changedKeys = currentCommitedAttributes.keys.reduce(Set<String>()) { (var changedKeys, key) -> Set<String> in
|
||||
|
||||
if previousCommitedAttributes[key] != currentCommitedAttributes[key] {
|
||||
|
||||
changedKeys.insert(key)
|
||||
}
|
||||
return changedKeys
|
||||
}
|
||||
|
||||
strongSelf.lastCommittedAttributes = currentCommitedAttributes
|
||||
observer.objectMonitor(
|
||||
monitor,
|
||||
didUpdateObject: object,
|
||||
changedPersistentKeys: changedKeys
|
||||
)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
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.EntityType == T>(observer: U) {
|
||||
|
||||
CoreStore.assert(
|
||||
NSThread.isMainThread(),
|
||||
"Attempted to remove an observer of type \(typeName(observer)) outside the main thread."
|
||||
)
|
||||
|
||||
let nilValue: AnyObject? = nil
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.willChangeObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didDeleteObject, inObject: observer)
|
||||
setAssociatedRetainedObject(nilValue, forKey: &NotificationKey.didUpdateObject, inObject: observer)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(dataStack: DataStack, object: T) {
|
||||
|
||||
let context = dataStack.mainContext
|
||||
|
||||
let fetchRequest = NSFetchRequest()
|
||||
fetchRequest.entity = object.entity
|
||||
|
||||
fetchRequest.fetchLimit = 1
|
||||
fetchRequest.resultType = .ManagedObjectResultType
|
||||
fetchRequest.sortDescriptors = []
|
||||
|
||||
let originalObjectID = object.objectID
|
||||
Where("SELF", isEqualTo: originalObjectID).applyToFetchRequest(fetchRequest)
|
||||
|
||||
let fetchedResultsController = NSFetchedResultsController(
|
||||
fetchRequest: fetchRequest,
|
||||
managedObjectContext: context,
|
||||
sectionNameKeyPath: nil,
|
||||
cacheName: nil
|
||||
)
|
||||
|
||||
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
|
||||
|
||||
self.originalObjectID = originalObjectID
|
||||
self.fetchedResultsController = fetchedResultsController
|
||||
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
|
||||
self.parentStack = dataStack
|
||||
|
||||
fetchedResultsControllerDelegate.handler = self
|
||||
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
|
||||
try! fetchedResultsController.performFetch()
|
||||
|
||||
self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [String: NSObject]) ?? [:]
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let originalObjectID: NSManagedObjectID
|
||||
private let fetchedResultsController: NSFetchedResultsController
|
||||
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
|
||||
private var lastCommittedAttributes = [String: NSObject]()
|
||||
private weak var parentStack: DataStack?
|
||||
|
||||
private 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
|
||||
|
||||
if let strongSelf = self {
|
||||
|
||||
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
|
||||
|
||||
if let strongSelf = self,
|
||||
let userInfo = note.userInfo,
|
||||
let object = userInfo[UserInfoKeyObject] as? T {
|
||||
|
||||
callback(
|
||||
monitor: strongSelf,
|
||||
object: object
|
||||
)
|
||||
}
|
||||
}
|
||||
),
|
||||
forKey: notificationKey,
|
||||
inObject: observer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ObjectMonitor: Equatable
|
||||
|
||||
public func ==<T: NSManagedObject>(lhs: ObjectMonitor<T>, rhs: ObjectMonitor<T>) -> Bool {
|
||||
|
||||
return lhs === rhs
|
||||
}
|
||||
|
||||
extension ObjectMonitor: Equatable { }
|
||||
|
||||
|
||||
// MARK: - ObjectMonitor: FetchedResultsControllerHandler
|
||||
|
||||
extension ObjectMonitor: FetchedResultsControllerHandler {
|
||||
|
||||
// MARK: FetchedResultsControllerHandler
|
||||
|
||||
private func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
switch type {
|
||||
|
||||
case .Delete:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorDidDeleteObjectNotification,
|
||||
object: self,
|
||||
userInfo: [UserInfoKeyObject: anObject]
|
||||
)
|
||||
|
||||
case .Update:
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorDidUpdateObjectNotification,
|
||||
object: self,
|
||||
userInfo: [UserInfoKeyObject: anObject]
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
private func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
NSNotificationCenter.defaultCenter().postNotificationName(
|
||||
ObjectMonitorWillChangeObjectNotification,
|
||||
object: self
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FetchedResultsControllerHandler
|
||||
|
||||
private protocol FetchedResultsControllerHandler: class {
|
||||
|
||||
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
|
||||
|
||||
func controllerWillChangeContent(controller: NSFetchedResultsController)
|
||||
}
|
||||
|
||||
|
||||
// MARK: - FetchedResultsControllerDelegate
|
||||
|
||||
private final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
|
||||
|
||||
// MARK: NSFetchedResultsControllerDelegate
|
||||
|
||||
@objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
|
||||
|
||||
self.handler?.controllerWillChangeContent(controller)
|
||||
}
|
||||
|
||||
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
|
||||
|
||||
self.handler?.controller(controller, didChangeObject: anObject, atIndexPath: indexPath, forChangeType: type, newIndexPath: newIndexPath)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
weak var handler: FetchedResultsControllerHandler?
|
||||
weak var fetchedResultsController: NSFetchedResultsController? {
|
||||
|
||||
didSet {
|
||||
|
||||
oldValue?.delegate = nil
|
||||
self.fetchedResultsController?.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
|
||||
self.fetchedResultsController?.delegate = nil
|
||||
}
|
||||
}
|
||||
@@ -1,88 +0,0 @@
|
||||
//
|
||||
// ObjectObserver.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - ObjectObserver
|
||||
|
||||
/**
|
||||
Implement the `ObjectObserver` protocol to observe changes to a single `NSManagedObject` instance. `ObjectObserver`s may register themselves to a `ObjectMonitor`'s `addObserver(_:)` method:
|
||||
|
||||
let monitor = CoreStore.monitorObject(object)
|
||||
monitor.addObserver(self)
|
||||
*/
|
||||
public protocol ObjectObserver: class {
|
||||
|
||||
/**
|
||||
The `NSManagedObject` type for the observed object
|
||||
*/
|
||||
typealias EntityType: NSManagedObject
|
||||
|
||||
/**
|
||||
Handles processing just before a change to the observed `object` occurs
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `NSManagedObject` instance being observed
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, willUpdateObject object: EntityType)
|
||||
|
||||
/**
|
||||
Handles processing right after a change to the observed `object` occurs
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `NSManagedObject` instance being observed
|
||||
- parameter changedPersistentKeys: a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, didUpdateObject object: EntityType, changedPersistentKeys: Set<KeyPath>)
|
||||
|
||||
/**
|
||||
Handles processing right after `object` is deleted
|
||||
|
||||
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
|
||||
- parameter object: the `NSManagedObject` instance being observed
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, didDeleteObject object: EntityType)
|
||||
}
|
||||
|
||||
|
||||
public extension ObjectObserver {
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, willUpdateObject object: EntityType) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, didUpdateObject object: EntityType, changedPersistentKeys: Set<KeyPath>) { }
|
||||
|
||||
/**
|
||||
The default implementation does nothing.
|
||||
*/
|
||||
func objectMonitor(monitor: ObjectMonitor<EntityType>, didDeleteObject object: EntityType) { }
|
||||
}
|
||||
@@ -1,72 +0,0 @@
|
||||
//
|
||||
// SectionBy.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - SectionBy
|
||||
|
||||
/**
|
||||
The `SectionBy` clause indicates the key path to use to group the `ListMonitor` objects into sections. An optional closure can also be provided to transform the value into an appropriate section name:
|
||||
|
||||
let monitor = CoreStore.monitorSectionedList(
|
||||
From(MyPersonEntity),
|
||||
SectionBy("age") { "Age \($0)" },
|
||||
OrderBy(.Ascending("lastName"))
|
||||
)
|
||||
*/
|
||||
public struct SectionBy {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections
|
||||
|
||||
- parameter sectionKeyPath: the key path to use to group the objects into sections
|
||||
*/
|
||||
public init(_ sectionKeyPath: KeyPath) {
|
||||
|
||||
self.init(sectionKeyPath, { $0 })
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
|
||||
|
||||
- parameter sectionKeyPath: the key path to use to group the objects into sections
|
||||
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
|
||||
*/
|
||||
public init(_ sectionKeyPath: KeyPath, _ sectionIndexTransformer: (sectionName: String?) -> String?) {
|
||||
|
||||
self.sectionKeyPath = sectionKeyPath
|
||||
self.sectionIndexTransformer = sectionIndexTransformer
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let sectionKeyPath: KeyPath
|
||||
internal let sectionIndexTransformer: (sectionName: KeyPath?) -> String?
|
||||
}
|
||||
@@ -1,248 +0,0 @@
|
||||
//
|
||||
// AsynchronousDataTransaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - 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 {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
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 semaphore = GCDSemaphore(0)
|
||||
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
|
||||
|
||||
self.result = result
|
||||
completion(result: result)
|
||||
semaphore.signal()
|
||||
}
|
||||
semaphore.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`.
|
||||
*/
|
||||
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`.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter objects: the `NSManagedObject`s type to be deleted
|
||||
*/
|
||||
public override func delete(objects: [NSManagedObject?]) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
public override func rollback() {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to rollback an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.rollback()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: AsynchronousDataTransaction) -> Void) {
|
||||
|
||||
self.closure = closure
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue)
|
||||
}
|
||||
|
||||
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,228 +0,0 @@
|
||||
//
|
||||
// BaseDataTransaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - BaseDataTransaction
|
||||
|
||||
/**
|
||||
The `BaseDataTransaction` is an abstract interface for `NSManagedObject` creates, updates, and deletes. All `BaseDataTransaction` subclasses manage a private `NSManagedObjectContext` which are direct children of the `NSPersistentStoreCoordinator`'s root `NSManagedObjectContext`. This means that all updates are saved first to the persistent store, and then propagated up to the read-only `NSManagedObjectContext`.
|
||||
*/
|
||||
public /*abstract*/ class BaseDataTransaction {
|
||||
|
||||
// MARK: Object management
|
||||
|
||||
/**
|
||||
Indicates if the transaction has pending changes
|
||||
*/
|
||||
public var hasChanges: Bool {
|
||||
|
||||
return self.context.hasChanges
|
||||
}
|
||||
|
||||
/**
|
||||
Creates a new `NSManagedObject` with the specified entity type.
|
||||
|
||||
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
|
||||
- returns: a new `NSManagedObject` instance of the specified entity type.
|
||||
*/
|
||||
public func create<T: NSManagedObject>(into: Into<T>) -> T {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to create an entity of type \(typeName(T)) outside its designated queue."
|
||||
)
|
||||
|
||||
let context = self.context
|
||||
let entityClass = (into.entityClass as! NSManagedObject.Type)
|
||||
if into.inferStoreIfPossible {
|
||||
|
||||
switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: nil, inferStoreIfPossible: true) {
|
||||
|
||||
case (let persistentStore?, _):
|
||||
let object = entityClass.createInContext(context) as! T
|
||||
context.assignObject(object, toPersistentStore: persistentStore)
|
||||
return object
|
||||
|
||||
case (.None, true):
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.")
|
||||
|
||||
default:
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)), but a destination persistent store containing the entity type could not be found.")
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: into.configuration, inferStoreIfPossible: false) {
|
||||
|
||||
case (let persistentStore?, _):
|
||||
let object = entityClass.createInContext(context) as! T
|
||||
context.assignObject(object, toPersistentStore: persistentStore)
|
||||
return object
|
||||
|
||||
default:
|
||||
if let configuration = into.configuration {
|
||||
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the configuration \"\(configuration)\", which it doesn't belong to.")
|
||||
}
|
||||
else {
|
||||
|
||||
fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the default configuration, which it doesn't belong to.")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject`.
|
||||
|
||||
- parameter object: the `NSManagedObject` type to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
public func edit<T: NSManagedObject>(object: T?) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to update an entity of type \(typeName(object)) outside its designated queue."
|
||||
)
|
||||
|
||||
return object?.inContext(self.context)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
|
||||
|
||||
- parameter into: an `Into` clause specifying the entity type
|
||||
- parameter objectID: the `NSManagedObjectID` for the object to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
public func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to update an entity of type \(typeName(T)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
into.inferStoreIfPossible
|
||||
|| (into.configuration ?? Into.defaultConfigurationName) == objectID.persistentStore?.configurationName,
|
||||
"Attempted to update an entity of type \(typeName(T)) but the specified persistent store do not match the `NSManagedObjectID`."
|
||||
)
|
||||
|
||||
return (into.entityClass as! NSManagedObject.Type).inContext(self.context, withObjectID: objectID) as? T
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes a specified `NSManagedObject`.
|
||||
|
||||
- parameter object: the `NSManagedObject` to be deleted
|
||||
*/
|
||||
public func delete(object: NSManagedObject?) {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete an entity outside its designated queue."
|
||||
)
|
||||
|
||||
object?.inContext(self.context)?.deleteFromContext()
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter object1: the `NSManagedObject` to be deleted
|
||||
- parameter object2: another `NSManagedObject` to be deleted
|
||||
- parameter objects: other `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
|
||||
|
||||
self.delete([object1, object2] + objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter objects: the `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public func delete(objects: [NSManagedObject?]) {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to delete entities outside their designated queue."
|
||||
)
|
||||
|
||||
let context = self.context
|
||||
for object in objects {
|
||||
|
||||
object?.inContext(context)?.deleteFromContext()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Saving changes
|
||||
|
||||
/**
|
||||
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid.
|
||||
*/
|
||||
public func rollback() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to rollback a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
|
||||
self.context.reset()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal let context: NSManagedObjectContext
|
||||
internal let transactionQueue: GCDQueue
|
||||
internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue")
|
||||
|
||||
internal var isCommitted = false
|
||||
internal var result: SaveResult?
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue) {
|
||||
|
||||
self.transactionQueue = queue
|
||||
|
||||
let context = mainContext.temporaryContextInTransactionWithConcurrencyType(
|
||||
queue == .Main
|
||||
? .MainQueueConcurrencyType
|
||||
: .PrivateQueueConcurrencyType
|
||||
)
|
||||
self.context = context
|
||||
|
||||
context.parentTransaction = self
|
||||
}
|
||||
|
||||
internal var bypassesQueueing: Bool {
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -1,65 +0,0 @@
|
||||
//
|
||||
// CoreStore+Transaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - CoreStore
|
||||
|
||||
public extension CoreStore {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
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. A detached transaction object should typically be only used from the main queue.
|
||||
|
||||
- returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
public static func beginDetached() -> DetachedDataTransaction {
|
||||
|
||||
return self.defaultStack.beginDetached()
|
||||
}
|
||||
}
|
||||
@@ -1,79 +0,0 @@
|
||||
//
|
||||
// DataStack+Transaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
public extension DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
- returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
public func beginDetached() -> DetachedDataTransaction {
|
||||
|
||||
return DetachedDataTransaction(
|
||||
mainContext: self.rootSavingContext,
|
||||
queue: .createSerial(
|
||||
"com.coreStore.dataStack.detachedTransactionQueue",
|
||||
targetQueue: .UserInitiated
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,86 +0,0 @@
|
||||
//
|
||||
// DetachedDataTransaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - DetachedDataTransaction
|
||||
|
||||
/**
|
||||
The `DetachedDataTransaction` provides an interface for non-contiguous `NSManagedObject` creates, updates, and deletes. This is useful for making temporary changes, such as partially filled forms. A detached transaction object should typically be only used from the main queue.
|
||||
*/
|
||||
public final class DetachedDataTransaction: BaseDataTransaction {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Saves the transaction changes asynchronously. For a `DetachedDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
|
||||
|
||||
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
|
||||
*/
|
||||
public func commit(completion: (result: SaveResult) -> Void) {
|
||||
|
||||
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
|
||||
|
||||
self.result = result
|
||||
completion(result: result)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
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.
|
||||
|
||||
- returns: a `DetachedDataTransaction` instance where creates, updates, and deletes can be made.
|
||||
*/
|
||||
public func beginDetached() -> DetachedDataTransaction {
|
||||
|
||||
return DetachedDataTransaction(
|
||||
mainContext: self.context,
|
||||
queue: self.transactionQueue
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObjectContext` for this detached 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 `DetachedDataTransaction` 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 `DetachedDataTransaction`'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
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal override var bypassesQueueing: Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,145 +0,0 @@
|
||||
//
|
||||
// Into.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - Into
|
||||
|
||||
/**
|
||||
A `Into` clause contains the destination entity and destination persistent store for a `create(...)` method. A common usage is to just indicate the entity:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
|
||||
For cases where multiple `NSPersistentStore`s contain the same entity, the destination configuration's name needs to be specified as well:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
|
||||
This helps the `NSManagedObjectContext` to determine which
|
||||
*/
|
||||
public struct Into<T: NSManagedObject> {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>())
|
||||
*/
|
||||
public init(){
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
self.entityClass = T.self
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity type.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity))
|
||||
|
||||
- parameter entity: the `NSManagedObject` type to be created
|
||||
*/
|
||||
public init(_ entity: T.Type) {
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
self.entityClass = entity
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity class.
|
||||
|
||||
- parameter entityClass: the `NSManagedObject` class type to be created
|
||||
*/
|
||||
public init(_ entityClass: AnyClass) {
|
||||
|
||||
self.configuration = nil
|
||||
self.inferStoreIfPossible = true
|
||||
self.entityClass = entityClass
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
|
||||
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
self.entityClass = T.self
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity type and configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity.self, "Configuration1"))
|
||||
|
||||
- parameter entity: the `NSManagedObject` type to be created
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ entity: T.Type, _ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
self.entityClass = entity
|
||||
}
|
||||
|
||||
/**
|
||||
Initializes an `Into` clause with the specified entity class and configuration.
|
||||
Sample Usage:
|
||||
|
||||
let person = transaction.create(Into(MyPersonEntity.self, "Configuration1"))
|
||||
|
||||
- parameter entityClass: the `NSManagedObject` class type to be created
|
||||
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
|
||||
*/
|
||||
public init(_ entityClass: AnyClass, _ configuration: String?) {
|
||||
|
||||
self.configuration = configuration
|
||||
self.inferStoreIfPossible = false
|
||||
self.entityClass = entityClass
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal static var defaultConfigurationName: String {
|
||||
|
||||
return "PF_DEFAULT_CONFIGURATION_NAME"
|
||||
}
|
||||
|
||||
internal let entityClass: AnyClass
|
||||
internal let configuration: String?
|
||||
internal let inferStoreIfPossible: Bool
|
||||
}
|
||||
@@ -1,110 +0,0 @@
|
||||
//
|
||||
// SaveResult.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
// MARK: - SaveResult
|
||||
|
||||
/**
|
||||
The `SaveResult` indicates the result of a `commit(...)` for a transaction.
|
||||
The `SaveResult` can be treated as a boolean:
|
||||
|
||||
CoreStore.beginAsynchronous { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
if result {
|
||||
// succeeded
|
||||
}
|
||||
else {
|
||||
// failed
|
||||
}
|
||||
}
|
||||
|
||||
or as an `enum`, where the resulting associated object can also be inspected:
|
||||
|
||||
CoreStore.beginAsynchronous { transaction in
|
||||
// ...
|
||||
let result = transaction.commit()
|
||||
switch result {
|
||||
case .Success(let hasChanges):
|
||||
// hasChanges indicates if there were changes or not
|
||||
case .Failure(let error):
|
||||
// error is the NSError instance for the failure
|
||||
}
|
||||
}
|
||||
```
|
||||
*/
|
||||
public enum SaveResult {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
`SaveResult.Success` indicates that the `commit()` for the transaction succeeded, either because the save succeeded or because there were no changes to save. The associated value `hasChanges` indicates if there were saved changes or not.
|
||||
*/
|
||||
case Success(hasChanges: Bool)
|
||||
|
||||
/**
|
||||
`SaveResult.Failure` indicates that the `commit()` for the transaction failed. The associated object for this value is the related `NSError` instance.
|
||||
*/
|
||||
case Failure(NSError)
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(hasChanges: Bool) {
|
||||
|
||||
self = .Success(hasChanges: hasChanges)
|
||||
}
|
||||
|
||||
internal init(_ error: NSError) {
|
||||
|
||||
self = .Failure(error)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode) {
|
||||
|
||||
self.init(errorCode, userInfo: nil)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - SaveResult: BooleanType
|
||||
|
||||
extension SaveResult: BooleanType {
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
case .Success: return true
|
||||
case .Failure: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,224 +0,0 @@
|
||||
//
|
||||
// SynchronousDataTransaction.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - SynchronousDataTransaction
|
||||
|
||||
/**
|
||||
The `SynchronousDataTransaction` provides an interface for `NSManagedObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.beginSynchronous(_:)`, or from `CoreStore.beginSynchronous(_:)`.
|
||||
*/
|
||||
public final class SynchronousDataTransaction: BaseDataTransaction {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
public func commit() {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to commit a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to commit a \(typeName(self)) more than once."
|
||||
)
|
||||
|
||||
self.isCommitted = true
|
||||
self.result = self.context.saveSynchronously()
|
||||
}
|
||||
|
||||
/**
|
||||
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
|
||||
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
|
||||
*/
|
||||
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
|
||||
|
||||
CoreStore.assert(
|
||||
self.transactionQueue.isCurrentExecutionContext(),
|
||||
"Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue."
|
||||
)
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to begin a child transaction from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return SynchronousDataTransaction(
|
||||
mainContext: self.context,
|
||||
queue: self.childTransactionQueue,
|
||||
closure: closure).performAndWait()
|
||||
}
|
||||
|
||||
|
||||
// MARK: BaseDataTransaction
|
||||
|
||||
/**
|
||||
Creates a new `NSManagedObject` with the specified entity type.
|
||||
|
||||
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
|
||||
- returns: a new `NSManagedObject` instance of the specified entity type.
|
||||
*/
|
||||
public override func create<T: NSManagedObject>(into: Into<T>) -> T {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
return super.create(into)
|
||||
}
|
||||
|
||||
/**
|
||||
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
|
||||
|
||||
- parameter object: the `NSManagedObject` type to be edited
|
||||
- returns: an editable proxy for the specified `NSManagedObject`.
|
||||
*/
|
||||
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`.
|
||||
*/
|
||||
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)
|
||||
}
|
||||
|
||||
/**
|
||||
Deletes the specified `NSManagedObject`s.
|
||||
|
||||
- parameter objects: the `NSManagedObject`s to be deleted
|
||||
*/
|
||||
public override func delete(objects: [NSManagedObject?]) {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to delete an entities from an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.delete(objects)
|
||||
}
|
||||
|
||||
/**
|
||||
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once.
|
||||
*/
|
||||
public override func rollback() {
|
||||
|
||||
CoreStore.assert(
|
||||
!self.isCommitted,
|
||||
"Attempted to rollback an already committed \(typeName(self))."
|
||||
)
|
||||
|
||||
super.rollback()
|
||||
}
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, closure: (transaction: SynchronousDataTransaction) -> Void) {
|
||||
|
||||
self.closure = closure
|
||||
|
||||
super.init(mainContext: mainContext, queue: queue)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let closure: (transaction: SynchronousDataTransaction) -> Void
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
//
|
||||
// CoreStore+Setup.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2015 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
// 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 entitiesByName: [String: NSManagedObject.Type] {
|
||||
|
||||
return self.defaultStack.entitiesByName
|
||||
}
|
||||
|
||||
/**
|
||||
Adds an in-memory store to the `defaultStack`.
|
||||
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addInMemoryStoreAndWait(configuration: configuration)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the `defaultStack` an SQLite store from the given SQLite file name.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileName: fileName,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the `defaultStack` an SQLite store from the given SQLite file URL.
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.defaultStack.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,376 +0,0 @@
|
||||
//
|
||||
// DataStack.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import GCDKit
|
||||
|
||||
|
||||
internal let applicationSupportDirectory = NSFileManager.defaultManager().URLsForDirectory(.ApplicationSupportDirectory, inDomains: .UserDomainMask).first!
|
||||
|
||||
internal let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
|
||||
|
||||
internal let defaultSQLiteStoreURL = applicationSupportDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite")
|
||||
|
||||
|
||||
// MARK: - DataStack
|
||||
|
||||
/**
|
||||
The `DataStack` encapsulates the data model for the Core Data stack. Each `DataStack` can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own `NSPersistentStoreCoordinator`, a root `NSManagedObjectContext` for disk saves, and a shared `NSManagedObjectContext` designed as a read-only model interface for `NSManagedObjects`.
|
||||
*/
|
||||
public final class DataStack {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
Initializes a `DataStack` from an `NSManagedObjectModel`.
|
||||
|
||||
- parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name will be used.
|
||||
- parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used.
|
||||
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for incremental migration. If not specified, will default to a non-migrating data stack.
|
||||
*/
|
||||
public required init(modelName: String = applicationName, bundle: NSBundle = NSBundle.mainBundle(), migrationChain: MigrationChain = nil) {
|
||||
|
||||
CoreStore.assert(
|
||||
migrationChain.valid,
|
||||
"Invalid migration chain passed to the \(typeName(DataStack)). Check that the model versions' order is correct and that no repetitions or ambiguities exist."
|
||||
)
|
||||
|
||||
let model = NSManagedObjectModel.fromBundle(
|
||||
bundle,
|
||||
modelName: modelName,
|
||||
modelVersionHints: migrationChain.leafVersions
|
||||
)
|
||||
|
||||
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
|
||||
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
|
||||
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
|
||||
self.model = model
|
||||
self.migrationChain = migrationChain
|
||||
|
||||
self.rootSavingContext.parentStack = self
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `DataStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
|
||||
*/
|
||||
public var modelVersion: String {
|
||||
|
||||
return self.model.currentModelVersion!
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the entity name-to-class type mapping from the `DataStack`'s model.
|
||||
*/
|
||||
public var entitiesByName: [String: NSManagedObject.Type] {
|
||||
|
||||
return self.model.entityMapping()
|
||||
}
|
||||
|
||||
/**
|
||||
Returns the `NSManagedObjectID` for the specified object URI if it exists in the persistent store.
|
||||
*/
|
||||
public func objectIDForURIRepresentation(url: NSURL) -> NSManagedObjectID? {
|
||||
|
||||
return self.coordinator.managedObjectIDForURIRepresentation(url)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds an in-memory store to the stack.
|
||||
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
|
||||
var store: NSPersistentStore?
|
||||
var storeError: NSError?
|
||||
coordinator.performBlockAndWait {
|
||||
|
||||
do {
|
||||
|
||||
store = try coordinator.addPersistentStoreWithType(
|
||||
NSInMemoryStoreType,
|
||||
configuration: configuration,
|
||||
URL: nil,
|
||||
options: nil
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
storeError = error as NSError
|
||||
}
|
||||
}
|
||||
|
||||
if let store = store {
|
||||
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
return store
|
||||
}
|
||||
|
||||
let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the stack an SQLite store from the given SQLite file name.
|
||||
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
return try self.addSQLiteStoreAndWait(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
fileName,
|
||||
isDirectory: false
|
||||
),
|
||||
configuration: configuration,
|
||||
resetStoreOnModelMismatch: resetStoreOnModelMismatch
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
Adds to the stack an SQLite store from the given SQLite file URL.
|
||||
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
|
||||
- returns: the `NSPersistentStore` added to the stack.
|
||||
*/
|
||||
public func addSQLiteStoreAndWait(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
|
||||
|
||||
CoreStore.assert(
|
||||
fileURL.fileURL,
|
||||
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
|
||||
)
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
if let store = coordinator.persistentStoreForURL(fileURL) {
|
||||
|
||||
if store.type == NSSQLiteStoreType
|
||||
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) {
|
||||
|
||||
return store
|
||||
}
|
||||
|
||||
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
|
||||
)
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
do {
|
||||
|
||||
try fileManager.createDirectoryAtURL(
|
||||
fileURL.URLByDeletingLastPathComponent!,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
var store: NSPersistentStore?
|
||||
var storeError: NSError?
|
||||
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
|
||||
}
|
||||
|
||||
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 entityNameForEntityClass(entityClass: AnyClass) -> String? {
|
||||
|
||||
return self.model.entityNameForClass(entityClass)
|
||||
}
|
||||
|
||||
internal func persistentStoresForEntityClass(entityClass: AnyClass) -> [NSPersistentStore]? {
|
||||
|
||||
var returnValue: [NSPersistentStore]? = nil
|
||||
self.storeMetadataUpdateQueue.barrierSync {
|
||||
|
||||
returnValue = self.entityConfigurationsMapping[NSStringFromClass(entityClass)]?.map {
|
||||
|
||||
return self.configurationStoreMapping[$0]!
|
||||
} ?? []
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
|
||||
internal func persistentStoreForEntityClass(entityClass: AnyClass, configuration: String?, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
|
||||
|
||||
var returnValue: (store: NSPersistentStore?, isAmbiguous: Bool) = (store: nil, isAmbiguous: false)
|
||||
self.storeMetadataUpdateQueue.barrierSync {
|
||||
|
||||
let configurationsForEntity = self.entityConfigurationsMapping[NSStringFromClass(entityClass)] ?? []
|
||||
if let configuration = configuration {
|
||||
|
||||
if configurationsForEntity.contains(configuration) {
|
||||
|
||||
returnValue = (store: self.configurationStoreMapping[configuration], isAmbiguous: false)
|
||||
return
|
||||
}
|
||||
else if !inferStoreIfPossible {
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
switch configurationsForEntity.count {
|
||||
|
||||
case 0:
|
||||
return
|
||||
|
||||
case 1 where inferStoreIfPossible:
|
||||
returnValue = (store: self.configurationStoreMapping[configurationsForEntity.first!], isAmbiguous: false)
|
||||
|
||||
default:
|
||||
returnValue = (store: nil, isAmbiguous: true)
|
||||
}
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
|
||||
internal func updateMetadataForPersistentStore(persistentStore: NSPersistentStore) {
|
||||
|
||||
self.storeMetadataUpdateQueue.barrierAsync {
|
||||
|
||||
let configurationName = persistentStore.configurationName
|
||||
self.configurationStoreMapping[configurationName] = persistentStore
|
||||
for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) ?? []) {
|
||||
|
||||
if self.entityConfigurationsMapping[entityDescription.managedObjectClassName] == nil {
|
||||
|
||||
self.entityConfigurationsMapping[entityDescription.managedObjectClassName] = []
|
||||
}
|
||||
self.entityConfigurationsMapping[entityDescription.managedObjectClassName]?.insert(configurationName)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private let storeMetadataUpdateQueue = GCDQueue.createConcurrent("com.coreStore.persistentStoreBarrierQueue")
|
||||
private var configurationStoreMapping = [String: NSPersistentStore]()
|
||||
private var entityConfigurationsMapping = [String: Set<String>]()
|
||||
|
||||
deinit {
|
||||
|
||||
for store in self.coordinator.persistentStores {
|
||||
|
||||
do {
|
||||
|
||||
try self.coordinator.removePersistentStore(store)
|
||||
}
|
||||
catch _ { }
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,112 +0,0 @@
|
||||
//
|
||||
// PersistentStoreResult.swift
|
||||
// CoreStore
|
||||
//
|
||||
// Copyright (c) 2014 John Rommel Estropia
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
// MARK: - PersistentStoreResult
|
||||
|
||||
/**
|
||||
The `PersistentStoreResult` indicates the result of an asynchronous initialization of a persistent store.
|
||||
The `PersistentStoreResult` can be treated as a boolean:
|
||||
|
||||
try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
|
||||
if result {
|
||||
// succeeded
|
||||
}
|
||||
else {
|
||||
// failed
|
||||
}
|
||||
})
|
||||
|
||||
or as an `enum`, where the resulting associated object can also be inspected:
|
||||
|
||||
try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
|
||||
switch result {
|
||||
case .Success(let persistentStore):
|
||||
// persistentStore is the related NSPersistentStore instance
|
||||
case .Failure(let error):
|
||||
// error is the NSError instance for the failure
|
||||
}
|
||||
})
|
||||
```
|
||||
*/
|
||||
public enum PersistentStoreResult {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
/**
|
||||
`PersistentStoreResult.Success` indicates that the persistent store process succeeded. The associated object for this `enum` value is the related `NSPersistentStore` instance.
|
||||
*/
|
||||
case Success(NSPersistentStore)
|
||||
|
||||
/**
|
||||
`PersistentStoreResult.Failure` indicates that the persistent store process failed. The associated object for this value is the related `NSError` instance.
|
||||
*/
|
||||
case Failure(NSError)
|
||||
|
||||
|
||||
// MARK: Internal
|
||||
|
||||
internal init(_ store: NSPersistentStore) {
|
||||
|
||||
self = .Success(store)
|
||||
}
|
||||
|
||||
internal init(_ error: NSError) {
|
||||
|
||||
self = .Failure(error)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode) {
|
||||
|
||||
self.init(errorCode, userInfo: nil)
|
||||
}
|
||||
|
||||
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
|
||||
|
||||
self.init(NSError(
|
||||
coreStoreErrorCode: errorCode,
|
||||
userInfo: userInfo))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: - PersistentStoreResult: BooleanType
|
||||
|
||||
extension PersistentStoreResult: BooleanType {
|
||||
|
||||
// MARK: Public
|
||||
|
||||
public var boolValue: Bool {
|
||||
|
||||
switch self {
|
||||
|
||||
case .Success: return true
|
||||
case .Failure: return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,12 +7,14 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
B50132242344E24300FC238B /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50132232344E24300FC238B /* SwiftUIView.swift */; };
|
||||
B50132282344E5E900FC238B /* SwiftUIContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50132272344E5E900FC238B /* SwiftUIContainerViewController.swift */; };
|
||||
B501323523477D2300FC238B /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B501323423477D2300FC238B /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
|
||||
B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */; };
|
||||
B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */; };
|
||||
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADD1AFDC71700F90881 /* Palette.swift */; };
|
||||
B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */; };
|
||||
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C111B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel */; };
|
||||
B5125C141B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C131B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel */; };
|
||||
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977D81B120B80003D50A5 /* ObserversViewController.swift */; };
|
||||
B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */; };
|
||||
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52977DE1B120F83003D50A5 /* MapKit.framework */; };
|
||||
@@ -24,6 +26,8 @@
|
||||
B54AAD5B1AF4D26E00848AE0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */; };
|
||||
B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; };
|
||||
B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */; };
|
||||
B5635D192357F9F700B80E6B /* CollectionViewDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D182357F9F700B80E6B /* CollectionViewDemoViewController.swift */; };
|
||||
B5635D1B2357FA4B00B80E6B /* PaletteCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D1A2357FA4B00B80E6B /* PaletteCollectionViewCell.swift */; };
|
||||
B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; };
|
||||
B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; };
|
||||
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */; };
|
||||
@@ -35,11 +39,11 @@
|
||||
B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; };
|
||||
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; };
|
||||
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */; };
|
||||
B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; };
|
||||
B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B583A91B1AF5F4F4001F76AF /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; };
|
||||
B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5D9C9181B20AB1900E64F0E /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5AA37EF2357D30300FFD4B9 /* ColorsDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37EE2357D30300FFD4B9 /* ColorsDemo.swift */; };
|
||||
B5C37EE52357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C37EE42357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift */; };
|
||||
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */; };
|
||||
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; };
|
||||
B5E89AD11C5292A2003B04A9 /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
|
||||
B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25841B36E23C0000406B /* OrganismV1.swift */; };
|
||||
B5EE25871B36E2520000406B /* OrganismV2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25861B36E2520000406B /* OrganismV2.swift */; };
|
||||
B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE258B1B36E40D0000406B /* MigrationsDemoViewController.swift */; };
|
||||
@@ -47,46 +51,14 @@
|
||||
B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE259D1B3EC1B20000406B /* OrganismProtocol.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
B583A91A1AF5F4F4001F76AF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 2F03A53019C5C6DA005002A5;
|
||||
remoteInfo = CoreStore;
|
||||
};
|
||||
B583A91C1AF5F4F4001F76AF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 2F03A53B19C5C6DA005002A5;
|
||||
remoteInfo = CoreStoreTests;
|
||||
};
|
||||
B583A91E1AF5F512001F76AF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 2F03A52F19C5C6DA005002A5;
|
||||
remoteInfo = CoreStore;
|
||||
};
|
||||
B583A9221AF5F542001F76AF /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
proxyType = 1;
|
||||
remoteGlobalIDString = 2F03A52F19C5C6DA005002A5;
|
||||
remoteInfo = CoreStore;
|
||||
};
|
||||
/* End PBXContainerItemProxy section */
|
||||
|
||||
/* Begin PBXCopyFilesBuildPhase section */
|
||||
B583A9241AF5F542001F76AF /* Embed Frameworks */ = {
|
||||
B5E89ACF1C52929C003B04A9 /* Embed Frameworks */ = {
|
||||
isa = PBXCopyFilesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
dstPath = "";
|
||||
dstSubfolderSpec = 10;
|
||||
files = (
|
||||
B5D9C91A1B20AB1900E64F0E /* GCDKit.framework in Embed Frameworks */,
|
||||
B583A9211AF5F542001F76AF /* CoreStore.framework in Embed Frameworks */,
|
||||
B5E89AD11C5292A2003B04A9 /* CoreStore.framework in Embed Frameworks */,
|
||||
);
|
||||
name = "Embed Frameworks";
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -94,6 +66,9 @@
|
||||
/* End PBXCopyFilesBuildPhase section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
B50132232344E24300FC238B /* SwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIView.swift; sourceTree = "<group>"; };
|
||||
B50132272344E5E900FC238B /* SwiftUIContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIContainerViewController.swift; sourceTree = "<group>"; };
|
||||
B501323423477D2300FC238B /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
|
||||
B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListObserverDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserverDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B503FADD1AFDC71700F90881 /* Palette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palette.swift; sourceTree = "<group>"; };
|
||||
@@ -113,6 +88,8 @@
|
||||
B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
|
||||
B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
|
||||
B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV2ToV3MigrationPolicy.swift; sourceTree = "<group>"; };
|
||||
B5635D182357F9F700B80E6B /* CollectionViewDemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B5635D1A2357FA4B00B80E6B /* PaletteCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaletteCollectionViewCell.swift; sourceTree = "<group>"; };
|
||||
B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = "<group>"; };
|
||||
B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = "<group>"; };
|
||||
B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = "<group>"; };
|
||||
@@ -124,8 +101,9 @@
|
||||
B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingResultsViewController.swift; sourceTree = "<group>"; };
|
||||
B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = "<group>"; };
|
||||
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
|
||||
B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = CoreStore.xcodeproj; path = ../CoreStore.xcodeproj; sourceTree = "<group>"; };
|
||||
B5D9C9181B20AB1900E64F0E /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5AA37EE2357D30300FFD4B9 /* ColorsDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorsDemo.swift; sourceTree = "<group>"; };
|
||||
B5BDC9211C202429008147CD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
B5C37EE42357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaletteCollectionSectionHeaderView.swift; sourceTree = "<group>"; };
|
||||
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>"; };
|
||||
@@ -141,24 +119,37 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B501323523477D2300FC238B /* SwiftUI.framework in Frameworks */,
|
||||
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */,
|
||||
B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */,
|
||||
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */,
|
||||
B5D9C9191B20AB1900E64F0E /* GCDKit.framework in Frameworks */,
|
||||
B583A9201AF5F542001F76AF /* CoreStore.framework in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
/* End PBXFrameworksBuildPhase section */
|
||||
|
||||
/* Begin PBXGroup section */
|
||||
B50132222344E20B00FC238B /* SwiftUI Demo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B50132232344E24300FC238B /* SwiftUIView.swift */,
|
||||
B50132272344E5E900FC238B /* SwiftUIContainerViewController.swift */,
|
||||
);
|
||||
path = "SwiftUI Demo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B503FADA1AFDC71700F90881 /* List and Object Observers Demo */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B5AA37EE2357D30300FFD4B9 /* ColorsDemo.swift */,
|
||||
B52977D81B120B80003D50A5 /* ObserversViewController.swift */,
|
||||
B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */,
|
||||
B5635D182357F9F700B80E6B /* CollectionViewDemoViewController.swift */,
|
||||
B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */,
|
||||
B503FADD1AFDC71700F90881 /* Palette.swift */,
|
||||
B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */,
|
||||
B5635D1A2357FA4B00B80E6B /* PaletteCollectionViewCell.swift */,
|
||||
B5C37EE42357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift */,
|
||||
);
|
||||
path = "List and Object Observers Demo";
|
||||
sourceTree = "<group>";
|
||||
@@ -175,9 +166,9 @@
|
||||
B52977E21B120F90003D50A5 /* Frameworks */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */,
|
||||
B5D9C9181B20AB1900E64F0E /* GCDKit.framework */,
|
||||
B501323423477D2300FC238B /* SwiftUI.framework */,
|
||||
B52977E01B120F8A003D50A5 /* CoreLocation.framework */,
|
||||
B5BDC9211C202429008147CD /* CoreStore.framework */,
|
||||
B52977DE1B120F83003D50A5 /* MapKit.framework */,
|
||||
);
|
||||
name = Frameworks;
|
||||
@@ -204,6 +195,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B54AAD4E1AF4D26E00848AE0 /* AppDelegate.swift */,
|
||||
B50132222344E20B00FC238B /* SwiftUI Demo */,
|
||||
B566E3271B117AE700F4F0C6 /* Stack Setup Demo */,
|
||||
B503FADA1AFDC71700F90881 /* List and Object Observers Demo */,
|
||||
B52977DB1B120F2C003D50A5 /* Transactions Demo */,
|
||||
@@ -275,15 +267,6 @@
|
||||
path = "Migrations Demo";
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
B583A9151AF5F4F3001F76AF /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
B583A91B1AF5F4F4001F76AF /* CoreStore.framework */,
|
||||
B583A91D1AF5F4F4001F76AF /* CoreStoreTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
/* End PBXGroup section */
|
||||
|
||||
/* Begin PBXNativeTarget section */
|
||||
@@ -294,13 +277,11 @@
|
||||
B54AAD451AF4D26E00848AE0 /* Sources */,
|
||||
B54AAD461AF4D26E00848AE0 /* Frameworks */,
|
||||
B54AAD471AF4D26E00848AE0 /* Resources */,
|
||||
B583A9241AF5F542001F76AF /* Embed Frameworks */,
|
||||
B5E89ACF1C52929C003B04A9 /* Embed Frameworks */,
|
||||
);
|
||||
buildRules = (
|
||||
);
|
||||
dependencies = (
|
||||
B583A91F1AF5F512001F76AF /* PBXTargetDependency */,
|
||||
B583A9231AF5F542001F76AF /* PBXTargetDependency */,
|
||||
);
|
||||
name = CoreStoreDemo;
|
||||
productName = CoreStoreDemo;
|
||||
@@ -314,17 +295,19 @@
|
||||
isa = PBXProject;
|
||||
attributes = {
|
||||
LastSwiftUpdateCheck = 0700;
|
||||
LastUpgradeCheck = 0700;
|
||||
LastUpgradeCheck = 0930;
|
||||
ORGANIZATIONNAME = "John Rommel Estropia";
|
||||
TargetAttributes = {
|
||||
B54AAD481AF4D26E00848AE0 = {
|
||||
CreatedOnToolsVersion = 6.3;
|
||||
DevelopmentTeam = 2JT32EJ5BH;
|
||||
LastSwiftMigration = 0900;
|
||||
};
|
||||
};
|
||||
};
|
||||
buildConfigurationList = B54AAD441AF4D26E00848AE0 /* Build configuration list for PBXProject "CoreStoreDemo" */;
|
||||
compatibilityVersion = "Xcode 3.2";
|
||||
developmentRegion = English;
|
||||
developmentRegion = en;
|
||||
hasScannedForEncodings = 0;
|
||||
knownRegions = (
|
||||
en,
|
||||
@@ -333,12 +316,6 @@
|
||||
mainGroup = B54AAD401AF4D26E00848AE0;
|
||||
productRefGroup = B54AAD4A1AF4D26E00848AE0 /* Products */;
|
||||
projectDirPath = "";
|
||||
projectReferences = (
|
||||
{
|
||||
ProductGroup = B583A9151AF5F4F3001F76AF /* Products */;
|
||||
ProjectRef = B583A9141AF5F4F3001F76AF /* CoreStore.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
B54AAD481AF4D26E00848AE0 /* CoreStoreDemo */,
|
||||
@@ -346,23 +323,6 @@
|
||||
};
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
B583A91B1AF5F4F4001F76AF /* CoreStore.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = CoreStore.framework;
|
||||
remoteRef = B583A91A1AF5F4F4001F76AF /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
B583A91D1AF5F4F4001F76AF /* CoreStoreTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = CoreStoreTests.xctest;
|
||||
remoteRef = B583A91C1AF5F4F4001F76AF /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
/* End PBXReferenceProxy section */
|
||||
|
||||
/* Begin PBXResourcesBuildPhase section */
|
||||
B54AAD471AF4D26E00848AE0 /* Resources */ = {
|
||||
isa = PBXResourcesBuildPhase;
|
||||
@@ -382,6 +342,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */,
|
||||
B5C37EE52357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift in Sources */,
|
||||
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */,
|
||||
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */,
|
||||
B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */,
|
||||
@@ -393,6 +354,7 @@
|
||||
B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */,
|
||||
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */,
|
||||
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */,
|
||||
B5635D192357F9F700B80E6B /* CollectionViewDemoViewController.swift in Sources */,
|
||||
B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */,
|
||||
B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */,
|
||||
B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */,
|
||||
@@ -401,13 +363,16 @@
|
||||
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 */,
|
||||
B50132282344E5E900FC238B /* SwiftUIContainerViewController.swift in Sources */,
|
||||
B5635D1B2357FA4B00B80E6B /* PaletteCollectionViewCell.swift in Sources */,
|
||||
B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */,
|
||||
B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */,
|
||||
B5AA37EF2357D30300FFD4B9 /* ColorsDemo.swift in Sources */,
|
||||
B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */,
|
||||
B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */,
|
||||
B50132242344E24300FC238B /* SwiftUIView.swift in Sources */,
|
||||
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */,
|
||||
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */,
|
||||
);
|
||||
@@ -415,19 +380,6 @@
|
||||
};
|
||||
/* End PBXSourcesBuildPhase section */
|
||||
|
||||
/* Begin PBXTargetDependency section */
|
||||
B583A91F1AF5F512001F76AF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = CoreStore;
|
||||
targetProxy = B583A91E1AF5F512001F76AF /* PBXContainerItemProxy */;
|
||||
};
|
||||
B583A9231AF5F542001F76AF /* PBXTargetDependency */ = {
|
||||
isa = PBXTargetDependency;
|
||||
name = CoreStore;
|
||||
targetProxy = B583A9221AF5F542001F76AF /* PBXContainerItemProxy */;
|
||||
};
|
||||
/* End PBXTargetDependency section */
|
||||
|
||||
/* Begin PBXVariantGroup section */
|
||||
B54AAD571AF4D26E00848AE0 /* Main.storyboard */ = {
|
||||
isa = PBXVariantGroup;
|
||||
@@ -456,13 +408,23 @@
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
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_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
@@ -485,11 +447,13 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = YES;
|
||||
ONLY_ACTIVE_ARCH = YES;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 5.0;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -501,13 +465,23 @@
|
||||
CLANG_CXX_LIBRARY = "libc++";
|
||||
CLANG_ENABLE_MODULES = YES;
|
||||
CLANG_ENABLE_OBJC_ARC = YES;
|
||||
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||
CLANG_WARN_COMMA = YES;
|
||||
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||
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_NON_LITERAL_NULL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||
@@ -523,9 +497,11 @@
|
||||
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
MTL_ENABLE_DEBUG_INFO = NO;
|
||||
SDKROOT = iphoneos;
|
||||
SWIFT_SWIFT3_OBJC_INFERENCE = Off;
|
||||
SWIFT_VERSION = 5.0;
|
||||
VALIDATE_PRODUCT = YES;
|
||||
};
|
||||
name = Release;
|
||||
@@ -534,11 +510,12 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = 2JT32EJ5BH;
|
||||
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_SWIFT3_OBJC_INFERENCE = Off;
|
||||
};
|
||||
name = Debug;
|
||||
};
|
||||
@@ -546,11 +523,13 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||
DEVELOPMENT_TEAM = 2JT32EJ5BH;
|
||||
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_SWIFT3_OBJC_INFERENCE = Off;
|
||||
};
|
||||
name = Release;
|
||||
};
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "1020"
|
||||
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">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
|
||||
BuildableName = "CoreStoreDemo.app"
|
||||
BlueprintName = "CoreStoreDemo"
|
||||
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<Testables>
|
||||
</Testables>
|
||||
</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>
|
||||
</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>
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/02.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -18,9 +18,8 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
||||
|
||||
var window: UIWindow?
|
||||
|
||||
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
|
||||
|
||||
application.statusBarStyle = .LightContent
|
||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool {
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
<?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">
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14313.18" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" colorMatched="YES">
|
||||
<device id="retina4_7" orientation="portrait">
|
||||
<adaptation id="fullscreen"/>
|
||||
</device>
|
||||
<dependencies>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14283.14"/>
|
||||
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
@@ -11,31 +14,39 @@
|
||||
<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">
|
||||
<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 © 2018 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="440" 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"/>
|
||||
<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" 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"/>
|
||||
<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="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,37 +1,26 @@
|
||||
<?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="15702" systemVersion="18G1012" 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="Place" positionX="261" positionY="225" width="128" height="103"/>
|
||||
<element name="TimeZone" positionX="297" positionY="270" width="128" height="120"/>
|
||||
</elements>
|
||||
</model>
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/12.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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))
|
||||
try 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 = [
|
||||
@@ -163,117 +164,145 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
|
||||
title: "All Time Zones",
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
OrderBy(.Ascending("name"))
|
||||
)!
|
||||
return try! Static.timeZonesStack.fetchAll(
|
||||
From<TimeZone>()
|
||||
.orderBy(.ascending(\.name))
|
||||
)
|
||||
}
|
||||
),
|
||||
(
|
||||
title: "Time Zones in Asia",
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
Where("%K BEGINSWITH[c] %@", "name", "Asia"),
|
||||
OrderBy(.Ascending("secondsFromGMT"))
|
||||
)!
|
||||
return try! Static.timeZonesStack.fetchAll(
|
||||
From<TimeZone>()
|
||||
.where(
|
||||
format: "%K BEGINSWITH[c] %@",
|
||||
#keyPath(TimeZone.name),
|
||||
"Asia"
|
||||
)
|
||||
.orderBy(.ascending(\.secondsFromGMT))
|
||||
)
|
||||
}
|
||||
),
|
||||
(
|
||||
title: "Time Zones in America and Europe",
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
Where("%K BEGINSWITH[c] %@", "name", "America")
|
||||
|| Where("%K BEGINSWITH[c] %@", "name", "Europe"),
|
||||
OrderBy(.Ascending("secondsFromGMT"))
|
||||
)!
|
||||
return try! Static.timeZonesStack.fetchAll(
|
||||
From<TimeZone>()
|
||||
.where(
|
||||
format: "%K BEGINSWITH[c] %@ OR %K BEGINSWITH[c] %@",
|
||||
#keyPath(TimeZone.name),
|
||||
"America",
|
||||
#keyPath(TimeZone.name),
|
||||
"Europe"
|
||||
)
|
||||
.orderBy(.ascending(\.secondsFromGMT))
|
||||
)
|
||||
}
|
||||
),
|
||||
(
|
||||
title: "All Time Zones Except America",
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
!Where("%K BEGINSWITH[c] %@", "name", "America"),
|
||||
OrderBy(.Ascending("secondsFromGMT"))
|
||||
)!
|
||||
return try! Static.timeZonesStack.fetchAll(
|
||||
From<TimeZone>()
|
||||
.where(
|
||||
format: "%K BEGINSWITH[c] %@",
|
||||
#keyPath(TimeZone.name),
|
||||
"America"
|
||||
)
|
||||
.orderBy(.ascending(\.secondsFromGMT))
|
||||
)
|
||||
}
|
||||
),
|
||||
(
|
||||
title: "Time Zones with Summer Time",
|
||||
fetch: { () -> [TimeZone] in
|
||||
|
||||
return Static.timeZonesStack.fetchAll(
|
||||
From(TimeZone),
|
||||
Where("hasDaylightSavingTime", isEqualTo: true),
|
||||
OrderBy(.Ascending("name"))
|
||||
)!
|
||||
return try! Static.timeZonesStack.fetchAll(
|
||||
From<TimeZone>()
|
||||
.where(\.hasDaylightSavingTime == true)
|
||||
.orderBy(.ascending(\.name))
|
||||
)
|
||||
}
|
||||
)
|
||||
]
|
||||
|
||||
private let queryingItems = [
|
||||
private let queryingItems: [(title: String, query: () -> Any)] = [
|
||||
(
|
||||
title: "Number of Time Zones",
|
||||
query: { () -> AnyObject in
|
||||
query: { () -> Any in
|
||||
|
||||
return Static.timeZonesStack.queryValue(
|
||||
From(TimeZone),
|
||||
Select<NSNumber>(.Count("name"))
|
||||
return try! Static.timeZonesStack.queryValue(
|
||||
From<TimeZone>()
|
||||
.select(NSNumber.self, .count(\.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")
|
||||
return try! Static.timeZonesStack.queryValue(
|
||||
From<TimeZone>()
|
||||
.select(String.self, .attribute(\.abbreviation))
|
||||
.where(format: "%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"))
|
||||
)!
|
||||
return try! Static.timeZonesStack.queryAttributes(
|
||||
From<TimeZone>()
|
||||
.select(
|
||||
NSDictionary.self,
|
||||
.attribute(\.name),
|
||||
.attribute(\.abbreviation)
|
||||
)
|
||||
.orderBy(.ascending(\.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"))
|
||||
)!
|
||||
return try! Static.timeZonesStack.queryAttributes(
|
||||
From<TimeZone>()
|
||||
.select(
|
||||
NSDictionary.self,
|
||||
.count(\.abbreviation),
|
||||
.attribute(\.abbreviation)
|
||||
)
|
||||
.groupBy(\.abbreviation)
|
||||
.orderBy(
|
||||
.ascending(\.secondsFromGMT),
|
||||
.ascending(\.name)
|
||||
)
|
||||
)
|
||||
}
|
||||
),
|
||||
(
|
||||
title: "Number of Countries with Summer Time",
|
||||
query: { () -> AnyObject in
|
||||
query: { () -> Any in
|
||||
|
||||
return Static.timeZonesStack.queryAttributes(
|
||||
From(TimeZone),
|
||||
Select<NSDictionary>(
|
||||
.Count("hasDaylightSavingTime", As: "numberOfCountries"),
|
||||
"hasDaylightSavingTime"
|
||||
),
|
||||
GroupBy("hasDaylightSavingTime"),
|
||||
OrderBy(.Descending("hasDaylightSavingTime"))
|
||||
)!
|
||||
return try! Static.timeZonesStack.queryAttributes(
|
||||
From<TimeZone>()
|
||||
.select(
|
||||
NSDictionary.self,
|
||||
.count(\.hasDaylightSavingTime, as: "numberOfCountries"),
|
||||
.attribute(\.hasDaylightSavingTime)
|
||||
)
|
||||
.groupBy(\.hasDaylightSavingTime)
|
||||
.orderBy(
|
||||
.descending(\.hasDaylightSavingTime),
|
||||
.ascending(\.name)
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
]
|
||||
@@ -283,7 +312,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()
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/17.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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
|
||||
@@ -30,20 +30,20 @@ class FetchingResultsViewController: UITableViewController {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.estimatedRowHeight = 60
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension
|
||||
self.tableView.rowHeight = UITableView.automaticDimension
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/17.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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))
|
||||
)
|
||||
]
|
||||
|
||||
@@ -49,20 +49,20 @@ class QueryingResultsViewController: UITableViewController {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.tableView.estimatedRowHeight = 60
|
||||
self.tableView.rowHeight = UITableViewAutomaticDimension
|
||||
self.tableView.rowHeight = UITableView.automaticDimension
|
||||
}
|
||||
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/15.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "20x20",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
@@ -21,13 +31,79 @@
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "60x60",
|
||||
"idiom" : "iphone",
|
||||
"filename" : "Icon-60@3x-1.png",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "20x20",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "76x76",
|
||||
"idiom" : "ipad",
|
||||
"filename" : "Icon-76@2x.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "ipad",
|
||||
"size" : "83.5x83.5",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "1024x1024",
|
||||
"idiom" : "ios-marketing",
|
||||
"filename" : "Mask + Oval 1 + Oval 1 + Oval 1.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "car",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"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 |
|
After Width: | Height: | Size: 118 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>6.3.2</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<string>4</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
@@ -48,5 +50,7 @@
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>UIUserInterfaceStyle</key>
|
||||
<string>Light</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -0,0 +1,168 @@
|
||||
//
|
||||
// CollectionViewDemoViewController.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Estropia on 2019/10/17.
|
||||
// Copyright © 2019 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - CollectionViewDemoViewController
|
||||
|
||||
final class CollectionViewDemoViewController: UICollectionViewController {
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
let navigationItem = self.navigationItem
|
||||
navigationItem.leftBarButtonItems = [
|
||||
self.editButtonItem,
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .trash,
|
||||
target: self,
|
||||
action: #selector(self.resetBarButtonItemTouched(_:))
|
||||
)
|
||||
]
|
||||
|
||||
let filterBarButton = UIBarButtonItem(
|
||||
title: ColorsDemo.filter.rawValue,
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(self.filterBarButtonItemTouched(_:))
|
||||
)
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .add,
|
||||
target: self,
|
||||
action: #selector(self.addBarButtonItemTouched(_:))
|
||||
),
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .refresh,
|
||||
target: self,
|
||||
action: #selector(self.shuffleBarButtonItemTouched(_:))
|
||||
),
|
||||
filterBarButton
|
||||
]
|
||||
self.filterBarButton = filterBarButton
|
||||
|
||||
self.dataSource = DiffableDataSource.CollectionViewAdapter<Palette>(
|
||||
collectionView: self.collectionView,
|
||||
dataStack: ColorsDemo.stack,
|
||||
cellProvider: { (collectionView, indexPath, palette) in
|
||||
|
||||
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PaletteCollectionViewCell", for: indexPath) as! PaletteCollectionViewCell
|
||||
cell.colorView?.backgroundColor = palette.color
|
||||
cell.label?.text = palette.colorText
|
||||
return cell
|
||||
},
|
||||
supplementaryViewProvider: { (collectionView, kind, indexPath) in
|
||||
|
||||
switch kind {
|
||||
|
||||
case UICollectionView.elementKindSectionHeader:
|
||||
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PaletteCollectionSectionHeaderView", for: indexPath) as! PaletteCollectionSectionHeaderView
|
||||
view.label?.text = ColorsDemo.palettes.snapshot.sectionIDs[indexPath.section]
|
||||
return view
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
)
|
||||
ColorsDemo.palettes.addObserver(self) { [weak self] (listPublisher) in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.filterBarButton?.title = ColorsDemo.filter.rawValue
|
||||
self.dataSource?.apply(listPublisher.snapshot, animatingDifferences: true)
|
||||
}
|
||||
self.dataSource?.apply(ColorsDemo.palettes.snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||||
|
||||
super.prepare(for: segue, sender: sender)
|
||||
|
||||
switch (segue.identifier, segue.destination, sender) {
|
||||
|
||||
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as ObjectPublisher<Palette>):
|
||||
destinationViewController.setPalette(palette)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: UICollectionViewDelegate
|
||||
|
||||
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||
|
||||
collectionView.deselectItem(at: indexPath, animated: true)
|
||||
|
||||
self.performSegue(
|
||||
withIdentifier: "ObjectObserverDemoViewController",
|
||||
sender: ColorsDemo.palettes.snapshot[indexPath]
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private var filterBarButton: UIBarButtonItem?
|
||||
private var dataSource: DiffableDataSource.CollectionViewAdapter<Palette>?
|
||||
|
||||
deinit {
|
||||
|
||||
ColorsDemo.palettes.removeObserver(self)
|
||||
}
|
||||
|
||||
@IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
try transaction.deleteAll(From<Palette>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction private dynamic func filterBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
ColorsDemo.filter = ColorsDemo.filter.next()
|
||||
}
|
||||
|
||||
@IBAction private dynamic func addBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
_ = transaction.create(Into<Palette>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction private dynamic func shuffleBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
for palette in try transaction.fetchAll(From<Palette>()) {
|
||||
|
||||
palette.hue = Palette.randomHue()
|
||||
palette.colorName = nil
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
//
|
||||
// ColorsDemo.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2019/10/17.
|
||||
// Copyright © 2019 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreStore
|
||||
|
||||
|
||||
// MARK: - ColorsDemo
|
||||
|
||||
struct ColorsDemo {
|
||||
|
||||
enum Filter: String {
|
||||
|
||||
case all = "All Colors"
|
||||
case light = "Light Colors"
|
||||
case dark = "Dark Colors"
|
||||
|
||||
func next() -> Filter {
|
||||
|
||||
switch self {
|
||||
|
||||
case .all: return .light
|
||||
case .light: return .dark
|
||||
case .dark: return .all
|
||||
}
|
||||
}
|
||||
|
||||
func whereClause() -> Where<Palette> {
|
||||
|
||||
switch self {
|
||||
|
||||
case .all: return .init()
|
||||
case .light: return (\Palette.$brightness >= 0.9)
|
||||
case .dark: return (\Palette.$brightness <= 0.4)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static var filter = Filter.all {
|
||||
|
||||
didSet {
|
||||
|
||||
try! self.palettes.refetch(
|
||||
From<Palette>()
|
||||
.sectionBy(\.$colorName)
|
||||
.where(self.filter.whereClause())
|
||||
.orderBy(.ascending(\.$hue))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
static let stack: DataStack = {
|
||||
|
||||
let dataStack = DataStack(
|
||||
CoreStoreSchema(
|
||||
modelVersion: "ColorsDemo",
|
||||
entities: [
|
||||
Entity<Palette>("Palette"),
|
||||
],
|
||||
versionLock: [
|
||||
"Palette": [0x8c25aa53c7c90a28, 0xa243a34d25f1a3a7, 0x56565b6935b6055a, 0x4f988bb257bf274f]
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
try! dataStack.addStorageAndWait(
|
||||
SQLiteStore(
|
||||
fileName: "ColorsDemo.sqlite",
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
)
|
||||
)
|
||||
return dataStack
|
||||
}()
|
||||
|
||||
static let palettes: ListPublisher<Palette> = {
|
||||
|
||||
return ColorsDemo.stack.publishList(
|
||||
From<Palette>()
|
||||
.sectionBy(\.$colorName)
|
||||
.orderBy(.ascending(\.$hue))
|
||||
)
|
||||
}()
|
||||
}
|
||||
@@ -3,43 +3,41 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/02.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
|
||||
|
||||
private struct Static {
|
||||
|
||||
static let palettes: ListMonitor<Palette> = {
|
||||
|
||||
try! CoreStore.addSQLiteStoreAndWait(
|
||||
fileName: "ColorsDemo.sqlite",
|
||||
configuration: "ObservingDemo",
|
||||
resetStoreOnModelMismatch: true
|
||||
)
|
||||
|
||||
return CoreStore.monitorSectionedList(
|
||||
From(Palette),
|
||||
SectionBy("colorName"),
|
||||
OrderBy(.Ascending("hue"))
|
||||
)
|
||||
}()
|
||||
}
|
||||
|
||||
|
||||
// MARK: - ListObserverDemoViewController
|
||||
|
||||
class ListObserverDemoViewController: UITableViewController, ListSectionObserver {
|
||||
|
||||
// MARK: NSObject
|
||||
|
||||
deinit {
|
||||
|
||||
Static.palettes.removeObserver(self)
|
||||
final class ListObserverDemoViewController: UITableViewController {
|
||||
|
||||
// MARK: - EditableDataSource
|
||||
|
||||
final class EditableDataSource: DiffableDataSource.TableViewAdapter<Palette> {
|
||||
|
||||
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
|
||||
|
||||
switch editingStyle {
|
||||
|
||||
case .delete:
|
||||
let palette = ColorsDemo.palettes.snapshot[indexPath]
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
transaction.delete(palette)
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// MARK: UIViewController
|
||||
|
||||
@@ -47,31 +45,68 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
self.navigationItem.leftBarButtonItems = [
|
||||
self.editButtonItem(),
|
||||
let navigationItem = self.navigationItem
|
||||
navigationItem.leftBarButtonItems = [
|
||||
self.editButtonItem,
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .Trash,
|
||||
barButtonSystemItem: .trash,
|
||||
target: self,
|
||||
action: "resetBarButtonItemTouched:"
|
||||
action: #selector(self.resetBarButtonItemTouched(_:))
|
||||
)
|
||||
]
|
||||
self.navigationItem.rightBarButtonItem = UIBarButtonItem(
|
||||
barButtonSystemItem: .Add,
|
||||
target: self,
|
||||
action: "addBarButtonItemTouched:"
|
||||
)
|
||||
|
||||
Static.palettes.addObserver(self)
|
||||
let filterBarButton = UIBarButtonItem(
|
||||
title: ColorsDemo.filter.rawValue,
|
||||
style: .plain,
|
||||
target: self,
|
||||
action: #selector(self.filterBarButtonItemTouched(_:))
|
||||
)
|
||||
navigationItem.rightBarButtonItems = [
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .add,
|
||||
target: self,
|
||||
action: #selector(self.addBarButtonItemTouched(_:))
|
||||
),
|
||||
UIBarButtonItem(
|
||||
barButtonSystemItem: .refresh,
|
||||
target: self,
|
||||
action: #selector(self.shuffleBarButtonItemTouched(_:))
|
||||
),
|
||||
filterBarButton
|
||||
]
|
||||
self.filterBarButton = filterBarButton
|
||||
|
||||
self.dataSource = EditableDataSource(
|
||||
tableView: self.tableView,
|
||||
dataStack: ColorsDemo.stack,
|
||||
cellProvider: { (tableView, indexPath, palette) in
|
||||
|
||||
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
|
||||
cell.colorView?.backgroundColor = palette.color
|
||||
cell.label?.text = palette.colorText
|
||||
return cell
|
||||
}
|
||||
)
|
||||
ColorsDemo.palettes.addObserver(self) { [weak self] (listPublisher) in
|
||||
|
||||
guard let self = self else {
|
||||
|
||||
return
|
||||
}
|
||||
self.filterBarButton?.title = ColorsDemo.filter.rawValue
|
||||
self.dataSource?.apply(listPublisher.snapshot, animatingDifferences: true)
|
||||
}
|
||||
self.dataSource?.apply(ColorsDemo.palettes.snapshot, animatingDifferences: false)
|
||||
}
|
||||
|
||||
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):
|
||||
destinationViewController.palette = palette
|
||||
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as ObjectPublisher<Palette>):
|
||||
destinationViewController.setPalette(palette)
|
||||
|
||||
default:
|
||||
break
|
||||
@@ -79,140 +114,68 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
|
||||
}
|
||||
|
||||
|
||||
// MARK: UITableViewDataSource
|
||||
|
||||
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
|
||||
|
||||
return Static.palettes.numberOfSections()
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
return Static.palettes.numberOfObjectsInSection(section)
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
|
||||
|
||||
let cell = tableView.dequeueReusableCellWithIdentifier("PaletteTableViewCell") as! PaletteTableViewCell
|
||||
|
||||
let palette = Static.palettes[indexPath]
|
||||
cell.colorView?.backgroundColor = palette.color
|
||||
cell.label?.text = palette.colorText
|
||||
|
||||
return cell
|
||||
}
|
||||
|
||||
|
||||
// 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.snapshot[indexPath]
|
||||
)
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
switch editingStyle {
|
||||
|
||||
case .Delete:
|
||||
let palette = Static.palettes[indexPath]
|
||||
CoreStore.beginAsynchronous{ (transaction) -> Void in
|
||||
|
||||
transaction.delete(palette)
|
||||
transaction.commit { (result) -> Void in }
|
||||
}
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
return Static.palettes.sectionInfoAtIndex(section).name
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObserver
|
||||
|
||||
func listMonitorWillChange(monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.tableView.beginUpdates()
|
||||
}
|
||||
|
||||
func listMonitorDidChange(monitor: ListMonitor<Palette>) {
|
||||
|
||||
self.tableView.endUpdates()
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListObjectObserver
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) {
|
||||
|
||||
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? PaletteTableViewCell {
|
||||
|
||||
let palette = Static.palettes[indexPath]
|
||||
cell.colorView?.backgroundColor = palette.color
|
||||
cell.label?.text = palette.colorText
|
||||
}
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
|
||||
|
||||
self.tableView.deleteRowsAtIndexPaths([fromIndexPath], withRowAnimation: .Automatic)
|
||||
self.tableView.insertRowsAtIndexPaths([toIndexPath], withRowAnimation: .Automatic)
|
||||
}
|
||||
|
||||
|
||||
// MARK: ListSectionObserver
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
|
||||
}
|
||||
|
||||
func listMonitor(monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
|
||||
|
||||
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@IBAction private dynamic func resetBarButtonItemTouched(sender: AnyObject?) {
|
||||
private var filterBarButton: UIBarButtonItem?
|
||||
private var dataSource: DiffableDataSource.TableViewAdapter<Palette>?
|
||||
|
||||
deinit {
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
transaction.deleteAll(From(Palette))
|
||||
transaction.commit()
|
||||
}
|
||||
ColorsDemo.palettes.removeObserver(self)
|
||||
}
|
||||
|
||||
@IBAction private dynamic func addBarButtonItemTouched(sender: AnyObject?) {
|
||||
@IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
CoreStore.beginAsynchronous { (transaction) -> Void in
|
||||
|
||||
let palette = transaction.create(Into(Palette))
|
||||
palette.setInitialValues()
|
||||
|
||||
transaction.commit()
|
||||
}
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
try transaction.deleteAll(From<Palette>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction private dynamic func filterBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
ColorsDemo.filter = ColorsDemo.filter.next()
|
||||
}
|
||||
|
||||
@IBAction private dynamic func addBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
_ = transaction.create(Into<Palette>())
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
|
||||
@IBAction private dynamic func shuffleBarButtonItemTouched(_ sender: AnyObject?) {
|
||||
ColorsDemo.stack.perform(
|
||||
asynchronous: { (transaction) in
|
||||
|
||||
for palette in try transaction.fetchAll(From<Palette>()) {
|
||||
|
||||
palette.hue = Palette.randomHue()
|
||||
palette.colorName = nil
|
||||
}
|
||||
},
|
||||
completion: { _ in }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/06.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -14,25 +14,23 @@ import CoreStore
|
||||
|
||||
class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
|
||||
var palette: Palette? {
|
||||
func setPalette<O: ObjectRepresentation>(_ newValue: O?) where O.ObjectType == Palette {
|
||||
|
||||
get {
|
||||
guard self.monitor?.object?.objectID() != newValue?.objectID() else {
|
||||
|
||||
return self.monitor?.object
|
||||
return
|
||||
}
|
||||
set {
|
||||
if let newValue = newValue {
|
||||
|
||||
if let palette = newValue {
|
||||
|
||||
self.monitor = CoreStore.monitorObject(palette)
|
||||
}
|
||||
else {
|
||||
|
||||
self.monitor = nil
|
||||
}
|
||||
self.monitor = newValue.asReadOnly(in: ColorsDemo.stack).map(ColorsDemo.stack.monitorObject(_:))
|
||||
}
|
||||
else {
|
||||
|
||||
self.monitor = nil
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: NSObject
|
||||
|
||||
deinit {
|
||||
@@ -45,22 +43,21 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
|
||||
required init?(coder aDecoder: NSCoder) {
|
||||
|
||||
if let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue"))) {
|
||||
if let palette = try! ColorsDemo.stack.fetchOne(From<Palette>().orderBy(.ascending(\.$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
|
||||
|
||||
_ = transaction.create(Into<Palette>())
|
||||
}
|
||||
)
|
||||
|
||||
let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))!
|
||||
self.monitor = CoreStore.monitorObject(palette)
|
||||
let palette = try! ColorsDemo.stack.fetchOne(From<Palette>().orderBy(.ascending(\.$hue)))!
|
||||
self.monitor = ColorsDemo.stack.monitorObject(palette)
|
||||
}
|
||||
|
||||
super.init(coder: aDecoder)
|
||||
@@ -80,24 +77,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<KeyPathString>) {
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
||||
@@ -113,55 +110,63 @@ 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
|
||||
|
||||
@@ -171,15 +176,15 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
|
||||
|
||||
self.hsbLabel?.text = palette.colorText
|
||||
|
||||
if changedKeys == nil || changedKeys?.contains("hue") == true {
|
||||
if changedKeys == nil || changedKeys?.contains(String(keyPath: \Palette.$hue)) == true {
|
||||
|
||||
self.hueSlider?.value = Float(palette.hue)
|
||||
}
|
||||
if changedKeys == nil || changedKeys?.contains("saturation") == true {
|
||||
if changedKeys == nil || changedKeys?.contains(String(keyPath: \Palette.$saturation)) == true {
|
||||
|
||||
self.saturationSlider?.value = palette.saturation
|
||||
}
|
||||
if changedKeys == nil || changedKeys?.contains("brightness") == true {
|
||||
if changedKeys == nil || changedKeys?.contains(String(keyPath: \Palette.$brightness)) == true {
|
||||
|
||||
self.brightnessSlider?.value = palette.brightness
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/24.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -15,16 +15,45 @@ 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)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
@IBOutlet private dynamic weak var toggleTopBarButtonItem: UIBarButtonItem?
|
||||
@IBOutlet private dynamic weak var toggleBottomBarButtonItem: UIBarButtonItem?
|
||||
@IBOutlet private dynamic weak var stackView: UIStackView?
|
||||
@IBOutlet private dynamic weak var topContainerView: UIView?
|
||||
@IBOutlet private dynamic weak var bottomContainerView: UIView?
|
||||
|
||||
@IBAction private dynamic func toggleTopContainerView() {
|
||||
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
|
||||
self.topContainerView!.isHidden.toggle()
|
||||
}
|
||||
self.toggleTopBarButtonItem!.isEnabled = !self.bottomContainerView!.isHidden
|
||||
self.toggleBottomBarButtonItem!.isEnabled = !self.topContainerView!.isHidden
|
||||
}
|
||||
|
||||
@IBAction private dynamic func toggleBottomContainerView() {
|
||||
|
||||
UIView.animate(withDuration: 0.2) {
|
||||
|
||||
self.bottomContainerView!.isHidden.toggle()
|
||||
}
|
||||
self.toggleTopBarButtonItem!.isEnabled = !self.bottomContainerView!.isHidden
|
||||
self.toggleBottomBarButtonItem!.isEnabled = !self.topContainerView!.isHidden
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/05.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@@ -14,25 +14,31 @@ 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 {
|
||||
|
||||
get {
|
||||
|
||||
let KVCKey = "colorName"
|
||||
if let colorName = self.accessValueForKVCKey(KVCKey) as? String {
|
||||
|
||||
@Field.Stored(
|
||||
"hue",
|
||||
dynamicInitialValue: { Palette.randomHue() }
|
||||
)
|
||||
var hue: Int
|
||||
|
||||
@Field.Stored("saturation")
|
||||
var saturation: Float = 1
|
||||
|
||||
@Field.Stored(
|
||||
"brightness",
|
||||
dynamicInitialValue: { Palette.randomBrightness() }
|
||||
)
|
||||
var brightness: Float
|
||||
|
||||
@Field.Virtual(
|
||||
"colorName",
|
||||
customGetter: { object, field in
|
||||
if let colorName = field.primitiveValue {
|
||||
return colorName
|
||||
}
|
||||
|
||||
let colorName: String
|
||||
switch self.hue % 360 {
|
||||
|
||||
switch object.$hue.value % 360 {
|
||||
case 0 ..< 20: colorName = "Lower Reds"
|
||||
case 20 ..< 57: colorName = "Oranges and Browns"
|
||||
case 57 ..< 90: colorName = "Yellow-Greens"
|
||||
@@ -43,16 +49,25 @@ class Palette: NSManagedObject {
|
||||
case 297 ..< 331: colorName = "Magentas"
|
||||
default: colorName = "Upper Reds"
|
||||
}
|
||||
|
||||
self.setPrimitiveValue(colorName, forKey: KVCKey)
|
||||
field.primitiveValue = colorName
|
||||
return colorName
|
||||
}
|
||||
set {
|
||||
|
||||
self.setValue(newValue, forKVCKey: "colorName")
|
||||
}
|
||||
)
|
||||
var colorName: String!
|
||||
|
||||
static func randomHue() -> Int {
|
||||
|
||||
return Int.random(in: 0 ..< 360)
|
||||
}
|
||||
|
||||
static func randomBrightness() -> Float {
|
||||
|
||||
return (Float.random(in: 0 ..< 70) + 30) / 100.0
|
||||
}
|
||||
}
|
||||
|
||||
extension Palette {
|
||||
|
||||
var color: UIColor {
|
||||
|
||||
return UIColor(
|
||||
@@ -67,11 +82,4 @@ class Palette: NSManagedObject {
|
||||
|
||||
return "H: \(self.hue)˚, S: \(round(self.saturation * 100.0))%, B: \(round(self.brightness * 100.0))%"
|
||||
}
|
||||
|
||||
func setInitialValues() {
|
||||
|
||||
self.hue = Int32(arc4random_uniform(360))
|
||||
self.saturation = 1.0
|
||||
self.brightness = Float(arc4random_uniform(70) + 30) / 100.0
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
//
|
||||
// PaletteCollectionSectionHeaderView.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Estropia on 2019/10/17.
|
||||
// Copyright © 2019 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - PaletteCollectionSectionHeaderView
|
||||
|
||||
final class PaletteCollectionSectionHeaderView: UICollectionReusableView {
|
||||
|
||||
@IBOutlet weak var label: UILabel?
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
//
|
||||
// PaletteCollectionViewCell.swift
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Estropia on 2019/10/17.
|
||||
// Copyright © 2019 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
|
||||
// MARK: - PaletteCollectionViewCell
|
||||
|
||||
final class PaletteCollectionViewCell: UICollectionViewCell {
|
||||
|
||||
@IBOutlet weak var colorView: UIView?
|
||||
@IBOutlet weak var label: UILabel?
|
||||
}
|
||||
@@ -3,12 +3,15 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/05/05.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class PaletteTableViewCell: UITableViewCell {
|
||||
|
||||
// MARK: - PaletteTableViewCell
|
||||
|
||||
final class PaletteTableViewCell: UITableViewCell {
|
||||
|
||||
@IBOutlet weak var colorView: UIView?
|
||||
@IBOutlet weak var label: UILabel?
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/05.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
import CoreStore
|
||||
import GCDKit
|
||||
|
||||
|
||||
// MARK: - CustomLoggerViewController
|
||||
@@ -17,11 +16,6 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
// MARK: NSObject
|
||||
|
||||
deinit {
|
||||
|
||||
CoreStore.logger = DefaultLogger()
|
||||
}
|
||||
|
||||
let dataStack = DataStack()
|
||||
|
||||
// MARK: UIViewController
|
||||
@@ -30,96 +24,99 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
try! self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite")
|
||||
CoreStore.logger = self
|
||||
try! self.dataStack.addStorageAndWait(SQLiteStore(fileName: "emptyStore.sqlite"))
|
||||
}
|
||||
|
||||
override func viewDidAppear(animated: Bool) {
|
||||
override func viewDidAppear(_ animated: Bool) {
|
||||
|
||||
super.viewDidAppear(animated)
|
||||
|
||||
|
||||
CoreStoreDefaults.logger = self
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
override func viewDidDisappear(_ animated: Bool) {
|
||||
|
||||
super.viewDidDisappear(animated)
|
||||
|
||||
CoreStoreDefaults.logger = DefaultLogger()
|
||||
}
|
||||
|
||||
|
||||
// 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<NSManagedObject>(true).applyToFetchRequest(request)
|
||||
Where<NSManagedObject>(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()
|
||||
_ = try! self.dataStack.fetchOne(From<Place>())
|
||||
}
|
||||
|
||||
default:
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/21.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
@@ -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 incremental migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
|
||||
preferredStyle: .Alert
|
||||
message: "This demo shows how to run progressive migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
|
||||
preferredStyle: .alert
|
||||
)
|
||||
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
|
||||
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?.numberOfObjects(in: 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?.numberOfObjects(in: 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 = try! dataStack.queryValue(
|
||||
From<NSManagedObject>(model.entityType)
|
||||
.select(Int.self, .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,68 +330,78 @@ 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
|
||||
.firstIndex(
|
||||
where: { (arg) -> Bool in
|
||||
|
||||
let (_, _, schemaHistory) = arg
|
||||
return 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<NSManagedObject>(.descending(#keyPath(OrganismV1.dna)))
|
||||
)
|
||||
listMonitor.addObserver(self)
|
||||
self._listMonitor = listMonitor
|
||||
|
||||
if self.lastSelectedIndexPath == nil {
|
||||
|
||||
if listMonitor.numberOfObjectsInSection(0) > 0 {
|
||||
if listMonitor.numberOfObjects(in: 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.segmentedControl?.selectedSegmentIndex = UISegmentedControl.noSegment
|
||||
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 = "Incremental 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 +409,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 = "\n".join(lines)
|
||||
self.organismLabel?.text = lines.joined(separator: "\n")
|
||||
self.progressView?.progress = 0
|
||||
|
||||
self.headerContainer?.setNeedsLayout()
|
||||
@@ -302,87 +433,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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,14 +3,14 @@
|
||||
// CoreStoreDemo
|
||||
//
|
||||
// Created by John Rommel Estropia on 2015/06/27.
|
||||
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
|
||||
// Copyright © 2018 John Rommel Estropia. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
protocol OrganismProtocol: class {
|
||||
protocol OrganismProtocol: AnyObject {
|
||||
|
||||
var dna: Int64 { get set }
|
||||
|
||||
func mutate()
|
||||
}
|
||||
}
|
||||
|
||||