Compare commits

..

63 Commits

Author SHA1 Message Date
John Rommel Estropia
85706a3c57 version bump 2017-09-22 23:43:19 +09:00
John Rommel Estropia
c5ae4606b9 move cocoapods.yml 2017-09-22 23:41:07 +09:00
John Rommel Estropia
fa682215c5 version bump 2017-09-22 23:26:15 +09:00
John Estropia
dc73cd6dd9 added a yml file for pod try (#200) 2017-09-22 15:48:02 +09:00
John Rommel Estropia
f436b26e8e allow compound keypaths in Select terms 2017-09-08 01:28:24 +09:00
John Estropia
3ebc44b546 optimize redundant casting in printCoreStoreSchema() 2017-08-31 18:28:23 +09:00
John Estropia
68f1027ba7 fix code misuse in readme 2017-08-31 17:55:10 +09:00
John Estropia
211e69023e version bump 2017-08-23 12:59:36 +09:00
John Rommel Estropia
2912dcf010 iOS 11 fixed the NSFetchRequest.affectedStores bug 2017-08-23 12:55:02 +09:00
John Rommel Estropia
3e00a3da06 fix demo app errors 2017-08-17 09:24:33 +09:00
John Rommel Estropia
86be046c9f updated podspec 2017-08-16 20:43:10 +09:00
John Rommel Estropia
0f10bc3349 version bump 2017-08-16 20:26:22 +09:00
John Rommel Estropia
9685f0aef2 #191 2017-08-16 20:21:26 +09:00
John Rommel Estropia
3bd459bb1a updated docs 2017-08-16 20:09:09 +09:00
John Rommel Estropia
28b43f33fa Value.Required now requires an "initial:" parameter. For previous CoreStoreObject users, use the appropriate empty value for your existing properties (0 for numeric types, false for Bool, "" for String) 2017-08-08 18:36:25 +09:00
John Estropia
a366bcf1a3 Merge branch 'develop' of github.com:JohnEstropia/CoreStore into develop 2017-07-11 15:47:20 +09:00
John Rommel Estropia
c6e68ac24f reset transactions' context on deinit to break reference cycles in unsafed many-to-many relationships 2017-07-05 23:32:45 +09:00
John Estropia
a11915db12 minor cleanup 2017-07-04 12:21:46 +09:00
John Rommel Estropia
961f39a806 add internal utilities to force checkpoint operations on SQLite 2017-07-01 17:05:10 +09:00
John Rommel Estropia
3096cb784c add internal utilities to force checkpoint operations on SQLite 2017-07-01 16:45:11 +09:00
John Rommel Estropia
809aa4ff96 allow querying file size on SQLiteStore 2017-07-01 11:50:23 +09:00
John Estropia
8d926d25ec Merge pull request #177 from blender/feature/optional-where-chaining
Add optional && and || operators to Where clause
2017-06-28 13:32:16 +09:00
Tommaso Piazza
790454f514 Add optional && and || operators to Where clause 2017-06-27 13:58:16 +02:00
John Rommel Estropia
f72efc80b2 added utilities for combining Where arrays and OrderBy arrays 2017-06-24 17:42:56 +09:00
John Estropia
e8eb309d82 Added source docs on usage of custom getters and setters for CoreStoreObject properties (Value.Required, etc) 2017-06-20 20:32:27 +09:00
John Rommel Estropia
d0c3203e63 force reset contexts after autocommit 2017-06-17 21:50:21 +09:00
John Rommel Estropia
f5b3901caa fix tests and demo app 2017-06-17 02:13:54 +09:00
John Rommel Estropia
1a99fea820 complete PartialObject utilities 2017-06-17 01:55:36 +09:00
John Rommel Estropia
195b60615b added missing utilities 2017-06-16 01:42:09 +09:00
John Rommel Estropia
2c394965b8 remove direct access to CoreStoreObject.primitiveValues and replace PartialObject<O> 2017-06-16 01:02:23 +09:00
John Rommel Estropia
746d697691 WIP: new PartialObject to act as faster KVC wrappers when implementing custom getters and setters for CoreStoreObject 2017-06-15 08:27:08 +09:00
John Estropia
5689158b43 version bump 2017-06-14 21:50:58 +09:00
John Estropia
6fcdf3d011 fix logger demo 2017-06-14 17:43:34 +09:00
John Estropia
e26573c18e converted the Demo app's observer demo to use CoreStoreObject instead of NSManagedObject 2017-06-14 17:39:57 +09:00
John Estropia
801cf8d9f0 Fixed ListMonitor bug for CoreStoreObjects where ListObservers don't get update notifications 2017-06-14 17:37:46 +09:00
John Rommel Estropia
8c437e19b8 version bump 2017-06-10 23:19:02 +09:00
John Rommel Estropia
9cd3b6c879 code cleanup 2017-06-10 22:13:32 +09:00
John Rommel Estropia
fe135acbec Improve CoreStoreObjects KVO behavior 2017-06-10 21:02:36 +09:00
John Estropia
997c5bdcfa uniquify subclass names across model versions 2017-06-09 12:47:22 +09:00
John Rommel Estropia
129f975d96 version bump 2017-06-08 01:17:39 +09:00
John Rommel Estropia
a2e463e58c Merge branch 'prototype/customMigrationTest' of github.com:JohnEstropia/CoreStore into prototype/customMigrationTest 2017-06-08 01:07:00 +09:00
John Rommel Estropia
5fd50f0e15 fix migration for CoreStoreObject relationships 2017-06-08 01:06:51 +09:00
John Estropia
0a81736b7a Merge branch 'develop' into prototype/customMigrationTest 2017-06-07 12:31:42 +09:00
John Estropia
f9b6dd0c6a fix bug when using ObjectMonitor with CoreStoreObjects 2017-06-06 17:40:29 +09:00
John Rommel Estropia
0354401b56 WIP: bugfix for CustomSchemaMappingProvider relationship migration bug 2017-06-06 08:39:59 +09:00
John Rommel Estropia
0304067beb fix ToManyOrdered and ToManyUnordered enumeration crash 2017-06-06 00:49:16 +09:00
John Rommel Estropia
ddd83da434 add Where comparison operators for optional values 2017-06-05 23:09:19 +09:00
John Estropia
fc7df671de Fixes https://bugs.swift.org/browse/SR-4981 2017-06-05 12:45:35 +09:00
John Rommel Estropia
5fde9030c7 use stronger namespace for CoreStoreObject's internal managed object type 2017-06-05 08:03:37 +09:00
John Rommel Estropia
d7b07b3f00 Added alternative way to set keyPathsForValuesAffectingValue(forKey:) for CoreStoreObjects 2017-06-05 01:30:26 +09:00
John Rommel Estropia
ddd1ffb29f added ~= operation to create Where clauses for value arrays 2017-06-03 08:51:52 +09:00
John Rommel Estropia
98094000bb Merge branch 'master' into develop 2017-06-02 23:10:31 +09:00
John Estropia
d5026ef996 Merge branch 'temp/develop' into develop 2017-06-02 19:35:01 +09:00
John Estropia
2cd913b9dd added more utilities for CoreStoreObject meta 2017-06-02 19:34:54 +09:00
John Estropia
ad9520abbc Update README.md 2017-06-02 13:24:15 +09:00
John Estropia
c0fc57d10c Update README.md 2017-06-02 11:34:04 +09:00
John Estropia
0cf4d303e4 Added README sample on how to version CoreStoreObjects 2017-06-02 11:32:48 +09:00
John Rommel Estropia
6de397958a fix demo app warnings 2017-06-02 02:06:47 +09:00
John Rommel Estropia
55292a84dc made mapping modell providers public 2017-06-02 02:03:41 +09:00
John Estropia
981b560d53 test travis ci 2017-05-30 11:14:57 +09:00
John Estropia
c4c4dd55cd test travis build 2017-05-30 11:06:30 +09:00
John Rommel Estropia
1ddbe20c86 Updated README, fixed demo app, and bumped to 4.0.1 2017-05-28 11:37:40 +09:00
John Rommel Estropia
da9e8c1550 disallow "empty" default values on some ImportableAttributeTypes 2017-05-28 10:50:25 +09:00
60 changed files with 2783 additions and 1182 deletions

2
.cocoapods.yml Normal file
View File

@@ -0,0 +1,2 @@
try:
project: 'CoreStore.xcworkspace'

View File

@@ -9,7 +9,7 @@ env:
global:
- LC_CTYPE=en_US.UTF-8
- LANG=en_US.UTF-8
matrix:
matrix:
- DESTINATION="OS=10.3,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=10.1,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
@@ -25,7 +25,7 @@ matrix:
before_install:
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.20.1/Carthage.pkg"
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.23.0/Carthage.pkg"
- sudo installer -pkg "Carthage.pkg" -target /
- rm "Carthage.pkg"
before_script:

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "4.0.0"
s.version = "4.1.3"
s.license = "MIT"
s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift"
s.homepage = "https://github.com/JohnEstropia/CoreStore"

View File

@@ -238,6 +238,14 @@
B538BA781D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; };
B538BA791D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; };
B538BA7A1D15B3E30003A766 /* CoreStoreBridge.m in Sources */ = {isa = PBXBuildFile; fileRef = B538BA701D15B3E30003A766 /* CoreStoreBridge.m */; };
B53B275F1EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; };
B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; };
B53B27611EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; };
B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */; };
B53CA9A21EF1EF1600E0F440 /* PartialObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */; };
B53CA9A31EF1EF1600E0F440 /* PartialObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */; };
B53CA9A41EF1EF1600E0F440 /* PartialObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */; };
B53CA9A51EF1EF1600E0F440 /* PartialObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */; };
B53FB9FE1CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; };
B53FBA001CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; };
B53FBA011CAB2D2F00F0D40A /* CSMigrationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */; };
@@ -441,6 +449,18 @@
B580857A1CDF808C004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
B580857B1CDF808D004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
B580857C1CDF808F004C2EEB /* SetupTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58085741CDF7F00004C2EEB /* SetupTests.swift */; };
B5831B701F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
B5831B711F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
B5831B721F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
B5831B731F34AC3400A9F647 /* AttributeProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */; };
B5831B751F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */; };
B5831B761F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */; };
B5831B771F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */; };
B5831B781F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */; };
B5831B7A1F34ACBA00A9F647 /* Transformable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B791F34ACBA00A9F647 /* Transformable.swift */; };
B5831B7B1F34ACBA00A9F647 /* Transformable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B791F34ACBA00A9F647 /* Transformable.swift */; };
B5831B7C1F34ACBA00A9F647 /* Transformable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B791F34ACBA00A9F647 /* Transformable.swift */; };
B5831B7D1F34ACBA00A9F647 /* Transformable.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5831B791F34ACBA00A9F647 /* Transformable.swift */; };
B58B22F51C93C1BA00521925 /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2F03A53019C5C6DA005002A5 /* CoreStore.framework */; };
B58D0C631EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; };
B58D0C641EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */; };
@@ -752,6 +772,8 @@
B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+Logging.swift"; sourceTree = "<group>"; };
B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DispatchQueue+CoreStore.swift"; sourceTree = "<group>"; };
B538BA701D15B3E30003A766 /* CoreStoreBridge.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = CoreStoreBridge.m; sourceTree = "<group>"; };
B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreManagedObject.swift; sourceTree = "<group>"; };
B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PartialObject.swift; sourceTree = "<group>"; };
B53FB9FD1CAB2D2F00F0D40A /* CSMigrationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSMigrationResult.swift; sourceTree = "<group>"; };
B53FBA031CAB300C00F0D40A /* CSMigrationType.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CSMigrationType.swift; sourceTree = "<group>"; };
B53FBA0A1CAB5E6500F0D40A /* CSCoreStore+Migrating.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CSCoreStore+Migrating.swift"; sourceTree = "<group>"; };
@@ -806,6 +828,9 @@
B57D27BD1D0BBE8200539C58 /* BaseTestDataTestCase.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BaseTestDataTestCase.swift; sourceTree = "<group>"; };
B57D27C11D0BC20100539C58 /* QueryTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryTests.swift; sourceTree = "<group>"; };
B58085741CDF7F00004C2EEB /* SetupTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SetupTests.swift; sourceTree = "<group>"; };
B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributeProtocol.swift; sourceTree = "<group>"; };
B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RelationshipProtocol.swift; sourceTree = "<group>"; };
B5831B791F34ACBA00A9F647 /* Transformable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Transformable.swift; sourceTree = "<group>"; };
B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject+DynamicModel.swift"; sourceTree = "<group>"; };
B596BBAD1DD59FDB001DCDD9 /* ConvenienceTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConvenienceTests.swift; sourceTree = "<group>"; };
B596BBB51DD5BC67001DCDD9 /* FetchableSource.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchableSource.swift; sourceTree = "<group>"; };
@@ -823,6 +848,7 @@
B5C976E21C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UnsafeDataTransaction+Observing.swift"; sourceTree = "<group>"; };
B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreFetchedResultsController.swift; sourceTree = "<group>"; };
B5D1E22B19FA9FBC003B2874 /* CoreStoreError.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreError.swift; sourceTree = "<group>"; };
B5D2D5A91F7558CB00A4DE67 /* .cocoapods.yml */ = {isa = PBXFileReference; lastKnownFileType = text; path = .cocoapods.yml; sourceTree = SOURCE_ROOT; };
B5D339B31E925C2B00C880DE /* DynamicModelTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicModelTests.swift; sourceTree = "<group>"; };
B5D339D71E9489AB00C880DE /* CoreStoreObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CoreStoreObject.swift; sourceTree = "<group>"; };
B5D339DC1E9489C700C880DE /* DynamicObject.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DynamicObject.swift; sourceTree = "<group>"; };
@@ -1019,6 +1045,7 @@
B5E84ED91AFF82360064E85B /* LICENSE */,
B5D9C8F61B160ED200E64F0E /* CoreStore.podspec */,
B5BDC91A1C202269008147CD /* Cartfile */,
B5D2D5A91F7558CB00A4DE67 /* .cocoapods.yml */,
B5BDC9271C2024F2008147CD /* .travis.yml */,
B5AD60CD1C90141E00F2B2E8 /* Package.swift */,
);
@@ -1224,17 +1251,27 @@
isa = PBXGroup;
children = (
B5D339D71E9489AB00C880DE /* CoreStoreObject.swift */,
B53CA9A11EF1EF1600E0F440 /* PartialObject.swift */,
B5831B6E1F3355C300A9F647 /* Properties */,
B52F74391E9B8724005F3DAC /* Dynamic Schema */,
B5D339DC1E9489C700C880DE /* DynamicObject.swift */,
B52F742E1E9B50D0005F3DAC /* SchemaHistory.swift */,
B5D339E61E9493A500C880DE /* Entity.swift */,
B5D33A001E96012400C880DE /* Relationship.swift */,
B5D339E11E948C3600C880DE /* Value.swift */,
B5A991EB1E9DC2CE0091A2E3 /* VersionLock.swift */,
);
name = "Dynamic Models";
sourceTree = "<group>";
};
B5831B6E1F3355C300A9F647 /* Properties */ = {
isa = PBXGroup;
children = (
B5D33A001E96012400C880DE /* Relationship.swift */,
B5D339E11E948C3600C880DE /* Value.swift */,
B5831B791F34ACBA00A9F647 /* Transformable.swift */,
);
name = Properties;
sourceTree = "<group>";
};
B5A5F26B1CAFF8D0004AB9AF /* Swift */ = {
isa = PBXGroup;
children = (
@@ -1423,18 +1460,21 @@
B5E84F291AFF849C0064E85B /* Internal */ = {
isa = PBXGroup;
children = (
B5831B6F1F34AC3400A9F647 /* AttributeProtocol.swift */,
B5831B741F34AC7A00A9F647 /* RelationshipProtocol.swift */,
B5C976E61C6E3A5900B1AF90 /* CoreStoreFetchedResultsController.swift */,
B526613F1CADD585007B85D9 /* CoreStoreFetchRequest+CoreStore.swift */,
B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */,
B56923C31EB823B4007C4DC9 /* NSEntityDescription+Migration.swift */,
B53B275E1EE3B92E00E9B352 /* CoreStoreManagedObject.swift */,
B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */,
B51260921E9B28F100402229 /* EntityIdentifier.swift */,
B54A6A541BA15F2A007870FD /* FetchedResultsControllerDelegate.swift */,
B5E834BA1B7691F3001D3D50 /* Functions.swift */,
B5FAD6AB1B51285300714891 /* MigrationManager.swift */,
B5E84F2B1AFF849C0064E85B /* NotificationObserver.swift */,
B533C4DA1D7D4BFA001383CB /* DispatchQueue+CoreStore.swift */,
B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */,
B51260881E9B252B00402229 /* NSEntityDescription+DynamicModel.swift */,
B56923C31EB823B4007C4DC9 /* NSEntityDescription+Migration.swift */,
B58D0C621EAA0C7E003EDD87 /* NSManagedObject+DynamicModel.swift */,
B52FD3A91E3B3EF10001D919 /* NSManagedObject+Logging.swift */,
B5E84F2C1AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift */,
B5E84F351AFF85470064E85B /* NSManagedObjectContext+Querying.swift */,
B5E84F321AFF85470064E85B /* NSManagedObjectContext+Setup.swift */,
@@ -1802,6 +1842,7 @@
B5ECDC1D1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */,
B5C976E31C6C9F6A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
B53FBA121CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
B5831B751F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
B5E1B5A81CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */,
B5D339F11E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
B56007161B4018AB00A9A8F9 /* MigrationChain.swift in Sources */,
@@ -1820,6 +1861,7 @@
B5FAD6AE1B518DCB00714891 /* CoreStore+Migration.swift in Sources */,
B5E84EE71AFF84610064E85B /* CoreStore+Logging.swift in Sources */,
B546F9731C9C553300D5AC55 /* SetupResult.swift in Sources */,
B53CA9A21EF1EF1600E0F440 /* PartialObject.swift in Sources */,
B56007111B3F6BD500A9A8F9 /* Into.swift in Sources */,
B5E84F111AFF847B0064E85B /* Select.swift in Sources */,
B51260931E9B28F100402229 /* EntityIdentifier.swift in Sources */,
@@ -1875,6 +1917,7 @@
B5E2222A1CA51B6E00BA2E95 /* CSUnsafeDataTransaction.swift in Sources */,
B5E84F391AFF85470064E85B /* NSManagedObjectContext+Querying.swift in Sources */,
B56923E81EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B53B275F1EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
B5D33A011E96012400C880DE /* Relationship.swift in Sources */,
B5E84EE81AFF84610064E85B /* CoreStoreLogger.swift in Sources */,
B56923C91EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */,
@@ -1892,6 +1935,7 @@
B59FA0AE1CCBAC95007C9BCA /* ICloudStore.swift in Sources */,
B5E84EF81AFF846E0064E85B /* CoreStore+Transaction.swift in Sources */,
B5E84F301AFF849C0064E85B /* NSManagedObjectContext+CoreStore.swift in Sources */,
B5831B7A1F34ACBA00A9F647 /* Transformable.swift in Sources */,
B546F9691C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
B53FBA1E1CAB63FA00F0D40A /* NSFetchedResultsController+ObjectiveC.swift in Sources */,
B549F65E1E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */,
@@ -1907,6 +1951,7 @@
B5E84EE61AFF84610064E85B /* DefaultLogger.swift in Sources */,
B53FBA041CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
B5E84EF41AFF846E0064E85B /* AsynchronousDataTransaction.swift in Sources */,
B5831B701F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
B5DBE2CD1C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
B546F95D1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */,
B5D339E71E9493A500C880DE /* Entity.swift in Sources */,
@@ -1986,6 +2031,7 @@
B5ECDC1F1CA81A2100C7F112 /* CSDataStack+Querying.swift in Sources */,
B5C976E41C6C9F9A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
B53FBA141CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
B5831B761F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
B5E1B5AA1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */,
B5D339F21E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
B5D3F6461C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */,
@@ -2004,6 +2050,7 @@
82BA18A31C4BBD2200A0916E /* DataStack.swift in Sources */,
82BA18C81C4BBD5900A0916E /* MigrationChain.swift in Sources */,
B546F9741C9C553300D5AC55 /* SetupResult.swift in Sources */,
B53CA9A31EF1EF1600E0F440 /* PartialObject.swift in Sources */,
82BA18B11C4BBD3100A0916E /* SaveResult.swift in Sources */,
82BA18DD1C4BBE1400A0916E /* NSFetchedResultsController+Convenience.swift in Sources */,
B51260941E9B28F100402229 /* EntityIdentifier.swift in Sources */,
@@ -2059,6 +2106,7 @@
82BA18A71C4BBD2900A0916E /* CoreStore+Logging.swift in Sources */,
82BA18D81C4BBD7100A0916E /* WeakObject.swift in Sources */,
B56923E91EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B53B27601EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
B5D33A021E96012400C880DE /* Relationship.swift in Sources */,
B559CD4B1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */,
B56923CA1EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */,
@@ -2076,6 +2124,7 @@
82BA18D31C4BBD7100A0916E /* NSManagedObjectContext+CoreStore.swift in Sources */,
82BA18AD1C4BBD3100A0916E /* UnsafeDataTransaction.swift in Sources */,
B546F96A1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
B5831B7B1F34ACBA00A9F647 /* Transformable.swift in Sources */,
B53FBA201CAB63FA00F0D40A /* NSFetchedResultsController+ObjectiveC.swift in Sources */,
82BA18A81C4BBD2900A0916E /* CoreStoreLogger.swift in Sources */,
B549F65F1E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */,
@@ -2091,6 +2140,7 @@
B53FBA061CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
82BA18BE1C4BBD4A00A0916E /* Tweak.swift in Sources */,
B5DBE2CE1C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
B5831B711F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
B546F95E1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */,
B5ECDC0D1CA8161B00C7F112 /* CSGroupBy.swift in Sources */,
B5D339E81E9493A500C880DE /* Entity.swift in Sources */,
@@ -2170,6 +2220,7 @@
B5D3F6481C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */,
B5220E1C1D130801009BC71E /* FetchedResultsControllerDelegate.swift in Sources */,
B52DD19E1BE1F92C00949AFE /* AsynchronousDataTransaction.swift in Sources */,
B5831B781F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */,
B5D339F41E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
B5220E241D13085E009BC71E /* NSFetchedResultsController+Convenience.swift in Sources */,
@@ -2188,6 +2239,7 @@
B52DD1961BE1F92500949AFE /* DataStack.swift in Sources */,
B5ECDBFD1CA804FD00C7F112 /* NSManagedObjectContext+ObjectiveC.swift in Sources */,
B52DD1BD1BE1F94300949AFE /* NSManagedObject+Convenience.swift in Sources */,
B53CA9A51EF1EF1600E0F440 /* PartialObject.swift in Sources */,
B52DD1AD1BE1F93900949AFE /* Where.swift in Sources */,
B53FBA1C1CAB63E200F0D40A /* NSManagedObject+ObjectiveC.swift in Sources */,
B51260961E9B28F100402229 /* EntityIdentifier.swift in Sources */,
@@ -2243,6 +2295,7 @@
B52DD1A71BE1F93200949AFE /* BaseDataTransaction+Querying.swift in Sources */,
B546F96C1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
B56923EB1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B53B27621EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
B5D33A041E96012400C880DE /* Relationship.swift in Sources */,
B52DD1C61BE1F94600949AFE /* NSManagedObjectContext+CoreStore.swift in Sources */,
B56923CC1EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */,
@@ -2260,6 +2313,7 @@
B52DD1A01BE1F92C00949AFE /* UnsafeDataTransaction.swift in Sources */,
B5ECDC331CA81CDC00C7F112 /* CSCoreStore+Transaction.swift in Sources */,
B52DD1BB1BE1F94000949AFE /* MigrationType.swift in Sources */,
B5831B7D1F34ACBA00A9F647 /* Transformable.swift in Sources */,
B52DD1C91BE1F94600949AFE /* NSManagedObjectContext+Transaction.swift in Sources */,
B5220E151D130663009BC71E /* CoreStore+Observing.swift in Sources */,
B549F6611E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */,
@@ -2275,6 +2329,7 @@
B52DD1B91BE1F94000949AFE /* CoreStore+Migration.swift in Sources */,
B5519A5C1CA2008C002BEF78 /* CSBaseDataTransaction.swift in Sources */,
B5DBE2D51C991B3E00B5CEFA /* CSDataStack.swift in Sources */,
B5831B731F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
B5AEFAB81C9962AE00AD137F /* CoreStoreBridge.swift in Sources */,
B598514B1C90289F00C99590 /* NSPersistentStoreCoordinator+Setup.swift in Sources */,
B5D339EA1E9493A500C880DE /* Entity.swift in Sources */,
@@ -2354,6 +2409,7 @@
B5C976E51C6C9F9B00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */,
B53FBA151CAB63CB00F0D40A /* Progress+ObjectiveC.swift in Sources */,
B5E1B5AB1CAA49E2007FD580 /* CSDataStack+Migrating.swift in Sources */,
B5831B771F34AC7A00A9F647 /* RelationshipProtocol.swift in Sources */,
B5D3F6471C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */,
B5D339F31E94AF5800C880DE /* CoreStoreStrings.swift in Sources */,
B5E1B5A01CAA2568007FD580 /* CSDataStack+Observing.swift in Sources */,
@@ -2372,6 +2428,7 @@
B56321A81BD65219006C9394 /* NSManagedObject+Convenience.swift in Sources */,
B546F9751C9C553300D5AC55 /* SetupResult.swift in Sources */,
B56321981BD65216006C9394 /* Where.swift in Sources */,
B53CA9A41EF1EF1600E0F440 /* PartialObject.swift in Sources */,
B5202CFD1C046E8400DED140 /* NSFetchedResultsController+Convenience.swift in Sources */,
B5FE4DA91C84FB4400FA6A91 /* InMemoryStore.swift in Sources */,
B51260951E9B28F100402229 /* EntityIdentifier.swift in Sources */,
@@ -2427,6 +2484,7 @@
B563219F1BD65216006C9394 /* ObjectMonitor.swift in Sources */,
B56321B61BD6521C006C9394 /* WeakObject.swift in Sources */,
B56923EA1EB827F5007C4DC9 /* InferredSchemaMappingProvider.swift in Sources */,
B53B27611EE3B92E00E9B352 /* CoreStoreManagedObject.swift in Sources */,
B5D33A031E96012400C880DE /* Relationship.swift in Sources */,
B559CD4C1CAA8C6D00E4D58B /* CSStorageInterface.swift in Sources */,
B56923CB1EB82410007C4DC9 /* NSManagedObjectModel+Migration.swift in Sources */,
@@ -2444,6 +2502,7 @@
B56321B11BD6521C006C9394 /* NSManagedObjectContext+CoreStore.swift in Sources */,
B563218D1BD65216006C9394 /* CoreStore+Transaction.swift in Sources */,
B546F96B1C9AF26D00D5AC55 /* CSInMemoryStore.swift in Sources */,
B5831B7C1F34ACBA00A9F647 /* Transformable.swift in Sources */,
B53FBA211CAB63FA00F0D40A /* NSFetchedResultsController+ObjectiveC.swift in Sources */,
B563218B1BD65216006C9394 /* UnsafeDataTransaction.swift in Sources */,
B549F6601E569C7400FBAB2D /* QueryableAttributeType.swift in Sources */,
@@ -2459,6 +2518,7 @@
B53FBA071CAB300C00F0D40A /* CSMigrationType.swift in Sources */,
B56321841BD65216006C9394 /* DefaultLogger.swift in Sources */,
B5DBE2CF1C9914A900B5CEFA /* CSCoreStore.swift in Sources */,
B5831B721F34AC3400A9F647 /* AttributeProtocol.swift in Sources */,
B546F95F1C9A12B800D5AC55 /* CSSQliteStore.swift in Sources */,
B5ECDC0E1CA8161B00C7F112 /* CSGroupBy.swift in Sources */,
B5D339E91E9493A500C880DE /* Entity.swift in Sources */,

View File

@@ -8,7 +8,6 @@
import UIKit
import CoreStore
// MARK: - AppDelegate

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12113" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Ni8-QF-XHB">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12121" systemVersion="16G29" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Ni8-QF-XHB">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12078"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12089"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
@@ -459,8 +459,8 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
<rect key="frame" x="20" y="69.5" width="552" height="36.5"/>
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
<rect key="frame" x="16" y="89.5" width="343" height="50"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="TIX-qi-B34"/>
@@ -511,8 +511,8 @@
<action selector="brightnessSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="F09-EP-2iD"/>
</connections>
</slider>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p4O-tf-dgt">
<rect key="frame" x="20" y="49" width="552" height="20.5"/>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p4O-tf-dgt">
<rect key="frame" x="16" y="69" width="343" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -797,8 +797,8 @@
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
<rect key="frame" x="0.0" y="0.0" width="375" height="536"/>
<mapView verifyAmbiguity="ignoreSizes" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
<rect key="frame" x="0.0" y="0.0" width="375" height="558"/>
<connections>
<outlet property="delegate" destination="jPl-fH-NlD" id="Sjn-YC-haS"/>
</connections>
@@ -806,7 +806,7 @@
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="RZg-hi-T8O" firstAttribute="top" secondItem="V2U-0R-Ts0" secondAttribute="bottom" id="GcS-Jz-Wcm"/>
<constraint firstItem="RZg-hi-T8O" firstAttribute="top" secondItem="V2U-0R-Ts0" secondAttribute="bottom" id="N9r-9J-68d"/>
<constraint firstItem="V2U-0R-Ts0" firstAttribute="top" secondItem="k4s-iL-Krh" secondAttribute="top" id="S5Z-Da-V6J"/>
<constraint firstAttribute="trailing" secondItem="V2U-0R-Ts0" secondAttribute="trailing" id="YPc-RK-5ib"/>
<constraint firstItem="V2U-0R-Ts0" firstAttribute="leading" secondItem="k4s-iL-Krh" secondAttribute="leading" id="hk5-Rz-FyU"/>
@@ -820,7 +820,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="YnG-TD-zxQ" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3694" y="2020"/>
<point key="canvasLocation" x="3693.5" y="2019.5"/>
</scene>
<!--Logger-->
<scene sceneID="n7W-0g-bbY">
@@ -927,7 +927,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bordered" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="YVj-dA-fyV">
<rect key="frame" x="20" y="26" width="560" height="29"/>
<rect key="frame" x="20" y="26" width="335" height="29"/>
<segments>
<segment title="Fetch"/>
<segment title="Query"/>

View File

@@ -1,36 +1,25 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10169.1" systemVersion="15D21" minimumToolsVersion="Automatic">
<entity name="Palette" representedClassName="CoreStoreDemo.Palette">
<attribute name="brightness" optional="YES" attributeType="Float" defaultValueString="0.0" syncable="YES"/>
<attribute name="colorName" optional="YES" transient="YES" attributeType="String" syncable="YES"/>
<attribute name="hue" optional="YES" attributeType="Integer 32" defaultValueString="0.0" syncable="YES"/>
<attribute name="saturation" optional="YES" attributeType="Float" defaultValueString="0.0" syncable="YES"/>
<userInfo/>
</entity>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="12141" systemVersion="16F73" minimumToolsVersion="Xcode 7.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<entity name="Place" representedClassName="CoreStoreDemo.Place" syncable="YES">
<attribute name="latitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
<attribute name="longitude" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
<attribute name="latitude" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="longitude" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="subtitle" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="title" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<entity name="TimeZone" representedClassName="CoreStoreDemo.TimeZone" syncable="YES">
<attribute name="abbreviation" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="daylightSavingTimeOffset" optional="YES" attributeType="Double" defaultValueString="0.0" syncable="YES"/>
<attribute name="hasDaylightSavingTime" optional="YES" attributeType="Boolean" syncable="YES"/>
<attribute name="daylightSavingTimeOffset" optional="YES" attributeType="Double" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="hasDaylightSavingTime" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="name" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="secondsFromGMT" optional="YES" attributeType="Integer 32" defaultValueString="0.0" syncable="YES"/>
<attribute name="secondsFromGMT" optional="YES" attributeType="Integer 32" defaultValueString="0.0" usesScalarValueType="NO" syncable="YES"/>
</entity>
<configuration name="FetchingAndQueryingDemo">
<memberEntity name="TimeZone"/>
</configuration>
<configuration name="ObservingDemo">
<memberEntity name="Palette"/>
</configuration>
<configuration name="TransactionsDemo">
<memberEntity name="Place"/>
</configuration>
<elements>
<element name="Palette" positionX="261" positionY="189" width="128" height="105"/>
<element name="Place" positionX="261" positionY="225" width="128" height="105"/>
<element name="TimeZone" positionX="297" positionY="270" width="128" height="120"/>
</elements>

View File

@@ -17,11 +17,11 @@
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0.1</string>
<string>4.0.3</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<string>4</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UILaunchStoryboardName</key>

View File

@@ -10,7 +10,7 @@ import UIKit
import CoreStore
private struct Static {
struct ColorsDemo {
enum Filter: String {
@@ -33,8 +33,8 @@ private struct Static {
switch self {
case .all: return Where(true)
case .light: return Where("%K >= %@", #keyPath(Palette.brightness), 0.9)
case .dark: return Where("%K <= %@", #keyPath(Palette.brightness), 0.4)
case .light: return Palette.where({ $0.brightness >= 0.9 })
case .dark: return Palette.where({ $0.brightness <= 0.4 })
}
}
}
@@ -45,25 +45,38 @@ private struct Static {
self.palettes.refetch(
self.filter.whereClause(),
OrderBy(.ascending(#keyPath(Palette.hue)))
Palette.orderBy(ascending: { $0.hue })
)
}
}
static let stack: DataStack = {
return DataStack(
CoreStoreSchema(
modelVersion: "ColorsDemo",
entities: [
Entity<Palette>("Palette"),
],
versionLock: [
"Palette": [0x8c25aa53c7c90a28, 0xa243a34d25f1a3a7, 0x56565b6935b6055a, 0x4f988bb257bf274f]
]
)
)
}()
static let palettes: ListMonitor<Palette> = {
try! CoreStore.addStorageAndWait(
try! ColorsDemo.stack.addStorageAndWait(
SQLiteStore(
fileName: "ColorsDemo.sqlite",
configuration: "ObservingDemo",
localStorageOptions: .recreateStoreOnModelMismatch
)
)
return CoreStore.monitorSectionedList(
return ColorsDemo.stack.monitorSectionedList(
From<Palette>(),
SectionBy(#keyPath(Palette.colorName)),
OrderBy(.ascending(#keyPath(Palette.hue)))
SectionBy(Palette.keyPath({ $0.colorName })),
Palette.orderBy(ascending: { $0.hue })
)
}()
}
@@ -77,7 +90,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
deinit {
Static.palettes.removeObserver(self)
ColorsDemo.palettes.removeObserver(self)
}
@@ -98,7 +111,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
]
let filterBarButton = UIBarButtonItem(
title: Static.filter.rawValue,
title: ColorsDemo.filter.rawValue,
style: .plain,
target: self,
action: #selector(self.filterBarButtonItemTouched(_:))
@@ -113,9 +126,9 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
]
self.filterBarButton = filterBarButton
Static.palettes.addObserver(self)
ColorsDemo.palettes.addObserver(self)
self.setTable(enabled: !Static.palettes.isPendingRefetch)
self.setTable(enabled: !ColorsDemo.palettes.isPendingRefetch)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
@@ -137,19 +150,19 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
override func numberOfSections(in tableView: UITableView) -> Int {
return Static.palettes.numberOfSections()
return ColorsDemo.palettes.numberOfSections()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return Static.palettes.numberOfObjectsInSection(section)
return ColorsDemo.palettes.numberOfObjectsInSection(section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
let palette = Static.palettes[indexPath]
let palette = ColorsDemo.palettes[indexPath]
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
@@ -165,7 +178,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
self.performSegue(
withIdentifier: "ObjectObserverDemoViewController",
sender: Static.palettes[indexPath]
sender: ColorsDemo.palettes[indexPath]
)
}
@@ -174,8 +187,8 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
switch editingStyle {
case .delete:
let palette = Static.palettes[indexPath]
CoreStore.perform(
let palette = ColorsDemo.palettes[indexPath]
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
transaction.delete(palette)
@@ -190,7 +203,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return Static.palettes.sectionInfoAtIndex(section).name
return ColorsDemo.palettes.sectionInfoAtIndex(section).name
}
@@ -213,7 +226,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
func listMonitorDidRefetch(_ monitor: ListMonitor<Palette>) {
self.filterBarButton?.title = Static.filter.rawValue
self.filterBarButton?.title = ColorsDemo.filter.rawValue
self.tableView.reloadData()
self.setTable(enabled: true)
}
@@ -235,7 +248,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
if let cell = self.tableView.cellForRow(at: indexPath) as? PaletteTableViewCell {
let palette = Static.palettes[indexPath]
let palette = ColorsDemo.palettes[indexPath]
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
}
@@ -268,7 +281,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
@IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) {
CoreStore.perform(
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
transaction.deleteAll(From<Palette>())
@@ -279,16 +292,16 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
@IBAction private dynamic func filterBarButtonItemTouched(_ sender: AnyObject?) {
Static.filter = Static.filter.next()
ColorsDemo.filter = ColorsDemo.filter.next()
}
@IBAction private dynamic func addBarButtonItemTouched(_ sender: AnyObject?) {
CoreStore.perform(
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
let palette = transaction.create(Into<Palette>())
palette.setInitialValues()
palette.setInitialValues(in: transaction)
},
completion: { _ in }
)

View File

@@ -29,7 +29,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
if let palette = newValue {
self.monitor = CoreStore.monitorObject(palette)
self.monitor = ColorsDemo.stack.monitorObject(palette)
}
else {
@@ -50,22 +50,22 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
required init?(coder aDecoder: NSCoder) {
if let palette = CoreStore.fetchOne(From<Palette>(), OrderBy(.ascending(#keyPath(Palette.hue)))) {
if let palette = ColorsDemo.stack.fetchOne(From<Palette>(), Palette.orderBy(ascending: { $0.hue })) {
self.monitor = CoreStore.monitorObject(palette)
self.monitor = ColorsDemo.stack.monitorObject(palette)
}
else {
_ = try? CoreStore.perform(
_ = try? ColorsDemo.stack.perform(
synchronous: { (transaction) in
let palette = transaction.create(Into(Palette.self))
palette.setInitialValues()
let palette = transaction.create(Into<Palette>())
palette.setInitialValues(in: transaction)
}
)
let palette = CoreStore.fetchOne(From<Palette>(), OrderBy(.ascending(#keyPath(Palette.hue))))!
self.monitor = CoreStore.monitorObject(palette)
let palette = ColorsDemo.stack.fetchOne(From<Palette>(), Palette.orderBy(ascending: { $0.hue }))!
self.monitor = ColorsDemo.stack.monitorObject(palette)
}
super.init(coder: aDecoder)
@@ -121,12 +121,12 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
@IBAction dynamic func hueSliderValueDidChange(_ sender: AnyObject?) {
let hue = self.hueSlider?.value ?? 0
CoreStore.perform(
ColorsDemo.stack.perform(
asynchronous: { [weak self] (transaction) in
if let palette = transaction.edit(self?.monitor?.object) {
palette.hue = Int32(hue)
palette.hue .= Int(hue)
}
},
completion: { _ in }
@@ -136,12 +136,12 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
@IBAction dynamic func saturationSliderValueDidChange(_ sender: AnyObject?) {
let saturation = self.saturationSlider?.value ?? 0
CoreStore.perform(
ColorsDemo.stack.perform(
asynchronous: { [weak self] (transaction) in
if let palette = transaction.edit(self?.monitor?.object) {
palette.saturation = saturation
palette.saturation .= saturation
}
},
completion: { _ in }
@@ -151,12 +151,12 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
@IBAction dynamic func brightnessSliderValueDidChange(_ sender: AnyObject?) {
let brightness = self.brightnessSlider?.value ?? 0
CoreStore.perform(
ColorsDemo.stack.perform(
asynchronous: { [weak self] (transaction) in
if let palette = transaction.edit(self?.monitor?.object) {
palette.brightness = brightness
palette.brightness .= brightness
}
},
completion: { _ in }
@@ -165,7 +165,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
@IBAction dynamic func deleteBarButtonTapped(_ sender: AnyObject?) {
CoreStore.perform(
ColorsDemo.stack.perform(
asynchronous: { [weak self] (transaction) in
transaction.delete(self?.monitor?.object)
@@ -176,7 +176,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
func reloadPaletteInfo(_ palette: Palette, changedKeys: Set<String>?) {
self.colorNameLabel?.text = palette.colorName
self.colorNameLabel?.text = palette.colorName.value
let color = palette.color
self.colorNameLabel?.textColor = color
@@ -184,17 +184,17 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
self.hsbLabel?.text = palette.colorText
if changedKeys == nil || changedKeys?.contains(#keyPath(Palette.hue)) == true {
if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.hue }) == true {
self.hueSlider?.value = Float(palette.hue)
self.hueSlider?.value = Float(palette.hue.value)
}
if changedKeys == nil || changedKeys?.contains(#keyPath(Palette.saturation)) == true {
if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.saturation }) == true {
self.saturationSlider?.value = palette.saturation
self.saturationSlider?.value = palette.saturation.value
}
if changedKeys == nil || changedKeys?.contains(#keyPath(Palette.brightness)) == true {
if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.brightness }) == true {
self.brightnessSlider?.value = palette.brightness
self.brightnessSlider?.value = palette.brightness.value
}
}
}

View File

@@ -14,64 +14,65 @@ import CoreStore
// MARK: - Palette
class Palette: NSManagedObject {
@NSManaged var hue: Int32
@NSManaged var saturation: Float
@NSManaged var brightness: Float
final class Palette: CoreStoreObject {
@objc dynamic var colorName: String {
let hue = Value.Required<Int>("hue", initial: 0)
let saturation = Value.Required<Float>("saturation", initial: 0)
let brightness = Value.Required<Float>("brightness", initial: 0)
let colorName = Value.Optional<String>(
"colorName",
isTransient: true,
customGetter: Palette.getColorName
)
private static func getColorName(_ partialObject: PartialObject<Palette>) -> String? {
get {
if let colorName = partialObject.primitiveValue(for: { $0.colorName }) {
let KVCKey = #keyPath(Palette.colorName)
if let colorName = self.getValue(forKvcKey: KVCKey) as? String {
return colorName
}
let colorName: String
switch self.hue % 360 {
case 0 ..< 20: colorName = "Lower Reds"
case 20 ..< 57: colorName = "Oranges and Browns"
case 57 ..< 90: colorName = "Yellow-Greens"
case 90 ..< 159: colorName = "Greens"
case 159 ..< 197: colorName = "Blue-Greens"
case 197 ..< 241: colorName = "Blues"
case 241 ..< 297: colorName = "Violets"
case 297 ..< 331: colorName = "Magentas"
default: colorName = "Upper Reds"
}
self.setPrimitiveValue(colorName, forKey: KVCKey)
return colorName
}
set {
let colorName: String
switch partialObject.value(for: { $0.hue }) % 360 {
self.setValue(newValue.cs_toImportableNativeType(), forKvcKey: #keyPath(Palette.colorName))
case 0 ..< 20: colorName = "Lower Reds"
case 20 ..< 57: colorName = "Oranges and Browns"
case 57 ..< 90: colorName = "Yellow-Greens"
case 90 ..< 159: colorName = "Greens"
case 159 ..< 197: colorName = "Blue-Greens"
case 197 ..< 241: colorName = "Blues"
case 241 ..< 297: colorName = "Violets"
case 297 ..< 331: colorName = "Magentas"
default: colorName = "Upper Reds"
}
partialObject.setPrimitiveValue(colorName, for: { $0.colorName })
return colorName
}
}
extension Palette {
var color: UIColor {
return UIColor(
hue: CGFloat(self.hue) / 360.0,
saturation: CGFloat(self.saturation),
brightness: CGFloat(self.brightness),
hue: CGFloat(self.hue.value) / 360.0,
saturation: CGFloat(self.saturation.value),
brightness: CGFloat(self.brightness.value),
alpha: 1.0
)
}
var colorText: String {
return "H: \(self.hue)˚, S: \(round(self.saturation * 100.0))%, B: \(round(self.brightness * 100.0))%"
return "H: \(self.hue.value)˚, S: \(round(self.saturation.value * 100.0))%, B: \(round(self.brightness.value * 100.0))%"
}
func setInitialValues() {
func setInitialValues(in transaction: BaseDataTransaction) {
self.hue = Int32(arc4random_uniform(360))
self.saturation = 1.0
self.brightness = Float(arc4random_uniform(70) + 30) / 100.0
self.hue .= Int(arc4random_uniform(360))
self.saturation .= Float(1.0)
self.brightness .= Float(arc4random_uniform(70) + 30) / 100.0
}
}

View File

@@ -113,7 +113,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
case 2?:
DispatchQueue.global(qos: .background).async {
_ = self.dataStack.fetchOne(From<Palette>())
_ = self.dataStack.fetchOne(From<Place>())
}
default:

View File

@@ -46,7 +46,7 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
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
@@ -91,6 +91,11 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
}
}
func listMonitorDidRefetch(_ monitor: ListMonitor<NSManagedObject>) {
self.listMonitorDidChange(monitor)
}
// MARK: UITableViewDataSource
@objc dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
@@ -148,7 +153,10 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
label: "Model V1",
entityType: OrganismV1.self,
schemaHistory: SchemaHistory(
modelName: "MigrationDemo",
XcodeDataModelSchema.from(
modelName: "MigrationDemo",
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
),
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
)
),
@@ -156,7 +164,13 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
label: "Model V2",
entityType: OrganismV2.self,
schemaHistory: SchemaHistory(
modelName: "MigrationDemo",
XcodeDataModelSchema.from(
modelName: "MigrationDemo",
migrationChain: [
"MigrationDemo": "MigrationDemoV2",
"MigrationDemoV3": "MigrationDemoV2"
]
),
migrationChain: [
"MigrationDemo": "MigrationDemoV2",
"MigrationDemoV3": "MigrationDemoV2"
@@ -167,7 +181,10 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
label: "Model V3",
entityType: OrganismV3.self,
schemaHistory: SchemaHistory(
modelName: "MigrationDemo",
XcodeDataModelSchema.from(
modelName: "MigrationDemo",
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
),
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
)
)

View File

@@ -17,7 +17,7 @@ private struct Static {
static let facebookStack: DataStack = {
let dataStack = DataStack(modelName: "StackSetupDemo")
let dataStack = DataStack(xcodeModelName: "StackSetupDemo")
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_FB_Male.sqlite",
@@ -55,7 +55,7 @@ private struct Static {
static let twitterStack: DataStack = {
let dataStack = DataStack(modelName: "StackSetupDemo")
let dataStack = DataStack(xcodeModelName: "StackSetupDemo")
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_TW_Male.sqlite",

View File

@@ -37,7 +37,7 @@ import CoreStore
class Animal: CoreStoreObject {
let species = Value.Required<String>("species", default: "Swift")
let species = Value.Required<String>("species", initial: "Swift")
let master = Relationship.ToOne<Person>("master")
let color = Transformable.Optional<Color>("color")
}
@@ -45,21 +45,66 @@ class Animal: CoreStoreObject {
class Dog: Animal {
let nickname = Value.Optional<String>("nickname")
let age = Value.Required<Int>("age", default: 1)
let age = Value.Required<Int>("age", initial: 1)
let friends = Relationship.ToManyOrdered<Dog>("friends")
let friendedBy = Relationship.ToManyUnordered<Dog>("friendedBy", inverse: { $0.friends })
}
class Person: CoreStoreObject {
let title = Value.Required<String>("title", default: "Mr.")
let title = Value.Required<String>(
"title",
initial: "Mr.",
customSetter: Person.setTitle
)
let name = Value.Required<String>(
"name",
customGetter: { (`self`, getValue) in
return "\(self.title.value) \(getValue())"
}
initial: "",
customSetter: Person.setName
)
let displayName = Value.Optional<String>(
"displayName",
isTransient: true,
customGetter: Person.getDisplayName(_:),
affectedByKeyPaths: Person.keyPathsAffectingDisplayName()
)
let pets = Relationship.ToManyUnordered<Animal>("pets", inverse: { $0.master })
private static func setTitle(_ partialObject: PartialObject<Person>, _ newValue: String) {
partialObject.setPrimitiveValue(newValue, for: { $0.title })
partialObject.setPrimitiveValue(nil, for: { $0.displayName })
}
private static func setName(_ partialObject: PartialObject<Person>, _ newValue: String) {
partialObject.setPrimitiveValue(newValue, for: { $0.name })
partialObject.setPrimitiveValue(nil, for: { $0.displayName })
}
static func getDisplayName(_ partialObject: PartialObject<Person>) -> String? {
if let displayName = partialObject.primitiveValue(for: { $0.displayName }) {
return displayName
}
let title = partialObject.value(for: { $0.title })
let name = partialObject.value(for: { $0.name })
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
return displayName
}
static func keyPathsAffectingDisplayName() -> Set<String> {
return [
self.keyPath({ $0.title }),
self.keyPath({ $0.name })
]
}
}
@@ -124,11 +169,25 @@ class DynamicModelTests: BaseTestDataTestCase {
let person = transaction.create(Into<Person>())
XCTAssertTrue(person.pets.value.isEmpty)
XCTAssertEqual(
object_getClass(person.rawObject!).keyPathsForValuesAffectingValue(forKey: "displayName"),
["title", "name"]
)
person.name .= "Joe"
XCTAssertEqual(person.rawObject!.value(forKey: "name") as! String?, "Joe")
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "Mr. Joe")
person.rawObject!.setValue("AAAA", forKey: "displayName")
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "AAAA")
person.name .= "John"
XCTAssertEqual(person.name.value, "Mr. John") // Custom getter
XCTAssertEqual(person.name.value, "John")
XCTAssertEqual(person.displayName.value, "Mr. John") // Custom getter
person.title .= "Sir"
XCTAssertEqual(person.name.value, "Sir John")
XCTAssertEqual(person.displayName.value, "Sir John")
person.pets.value.insert(dog)
XCTAssertEqual(person.pets.count, 1)

View File

@@ -256,6 +256,21 @@ final class WhereTests: XCTestCase {
XCTAssertEqual(andWhere.predicate, andPredicate)
XCTAssertEqual(andWhere, whereClause1 && whereClause2 && whereClause3)
}
do {
let andWhere = whereClause1 && whereClause2 && whereClause3
let noneWhere: Where? = nil
let someWhere: Where? = Where("key4", isEqualTo: "value4")
let finalNoneWhere = andWhere && noneWhere
let finalSomeWhere = andWhere && someWhere
let unwrappedFinalSomeWhere = andWhere && someWhere!
XCTAssertEqual(andWhere.predicate, finalNoneWhere.predicate)
XCTAssertEqual(finalSomeWhere.predicate, unwrappedFinalSomeWhere.predicate)
}
do {
let orWhere = whereClause1 || whereClause2 || whereClause3
@@ -272,6 +287,21 @@ final class WhereTests: XCTestCase {
XCTAssertEqual(orWhere.predicate, orPredicate)
XCTAssertEqual(orWhere, whereClause1 || whereClause2 || whereClause3)
}
do {
let orWhere = whereClause1 || whereClause2 || whereClause3
let noneWhere: Where? = nil
let someWhere: Where? = Where("key4", isEqualTo: "value4")
let finalNoneWhere = orWhere && noneWhere
let finalSomeWhere = orWhere && someWhere
let unwrappedFinalSomeWhere = orWhere && someWhere!
XCTAssertEqual(orWhere.predicate, finalNoneWhere.predicate)
XCTAssertEqual(finalSomeWhere.predicate, unwrappedFinalSomeWhere.predicate)
}
}
@objc

View File

@@ -19,9 +19,12 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
</p>
* **Swift 3.1:** iOS 8+ / macOS 10.10+ / watchOS 2.0+ / tvOS 9.0+
* Beta support: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/prototype/Swift_3_2), [Swift 4.0](https://github.com/JohnEstropia/CoreStore/tree/prototype/Swift_4_0)
Upgrading from CoreStore 3.x to 4.x? Check out the [new features](#features) and make sure to read the [Migration guide](#upgrading-from-3xx-to-4xx).
CoreStore is now part of the [Swift Source Compatibility projects](https://swift.org/source-compatibility/#current-list-of-projects).
## Why use CoreStore?
CoreStore is the answer to the [challenges](http://inessential.com/2010/02/26/on_switching_away_from_core_data) [of](http://bsktapp.com/blog/why-is-realm-great-and-why-are-we-not-using-it/) [using](https://www.quora.com/Why-would-you-use-Realm-over-Core-Data) [Core](http://sebastiandobrincu.com/blog/5-reasons-why-you-should-choose-realm-over-coredata) [Data](https://medium.com/the-way-north/ditching-core-data-865c1bb5564c#.a5h8ou6ri).
@@ -88,6 +91,7 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
- [Observe a list of objects](#observe-a-list-of-objects)
- [Objective-C support](#objective-c-support)
- [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects)
- [`VersionLock`s](#versionlocks)
- [Roadmap](#roadmap)
- [Installation](#installation)
- [Changesets](#changesets)
@@ -457,6 +461,55 @@ CoreStore.defaultStack = DataStack(
)
```
**`CoreStoreSchema`-based model versions with progressive migration**
```swift
typealias Animal = V2.Animal
typealias Dog = V2.Dog
typealias Person = V2.Person
enum V2 {
class Animal: CoreStoreObject {
// ...
}
class Dog: Animal {
// ...
}
class Person: CoreStoreObject {
// ...
}
}
enum V1 {
class Animal: CoreStoreObject {
// ...
}
class Dog: Animal {
// ...
}
class Person: CoreStoreObject {
// ...
}
}
CoreStore.defaultStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<V1.Animal>("Animal", isAbstract: true),
Entity<V1.Dog>("Dog"),
Entity<V1.Person>("Person")
]
),
CoreStoreSchema(
modelVersion: "V2",
entities: [
Entity<V2.Animal>("Animal", isAbstract: true),
Entity<V2.Dog>("Dog"),
Entity<V2.Person>("Person")
]
),
migrationChain: ["V1", "V2"]
)
```
### Starting migrations
We have seen `addStorageAndWait(...)` used to initialize our persistent store. As the method name's *~AndWait* suffix suggests though, this method blocks so it should not do long tasks such as store migrations. In fact CoreStore will only attempt a synchronous **lightweight** migration if you explicitly provide the `.allowSynchronousLightweightMigration` option:
@@ -1669,7 +1722,7 @@ These special properties' values can be accessed or mutated using `.value`:
```swift
CoreStore.perform(
asynchronous: { (transaction) in
let dog: Dog = CoreStore.fetchOne(From<Dog>())!
let dog: Dog = transaction.fetchOne(From<Dog>())!
// ...
let nickname = dog.nickname.value // String?
let species = dog.species.value // String
@@ -1696,6 +1749,39 @@ let puppies = CoreStore.fetchAll(
All CoreStore APIs that are usable with `NSManagedObject`s are also available for `CoreStoreObject`s. These include `ListMonitor`s, `ImportableObject`s, fetching, etc.
### `VersionLock`s
While it is convenient to be able to declare entities only in code, it is worrying that we might accidentally change the `CoreStoreObject`'s properties and break our users' model version history. For this, the `CoreStoreSchema` allows us to "lock" our properties to a particular configuration. Any changes to that `VersionLock` will raise an assertion failure during the `CoreStoreSchema` initialization, so you can then look for the commit which changed the `VersionLock` hash.
To use `VersionLock`s, create the `CoreStoreSchema`, run the app, and look for this particular log message that is automatically printed to the console:
<img width="700" alt="VersionLock" src="https://cloud.githubusercontent.com/assets/3029684/26525632/757f1bd0-4398-11e7-9795-4132a2df0538.png" />
Copy this dictionary value and use it as the `versionLock:` argument of the `CoreStoreSchema` initializer:
```swift
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<Animal>("Animal", isAbstract: true),
Entity<Dog>("Dog"),
Entity<Person>("Person"),
],
versionLock: [
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
"Dog": [0xe3f0afeb109b283a, 0x29998d292938eb61, 0x6aab788333cfc2a3, 0x492ff1d295910ea7],
"Person": [0x66d8bbfd8b21561f, 0xcecec69ecae3570f, 0xc4b73d71256214ef, 0x89b99bfe3e013e8b]
]
)
```
You can also get this hash after the `DataStack` has been fully set up by printing to the console:
```swift
print(CoreStore.defaultStack.modelSchema.printCoreStoreSchema())
```
Once the version lock is set, any changes in the properties or to the model will trigger an assertion failure similar to this:
<img width="700" alt="VersionLock failure" src="https://cloud.githubusercontent.com/assets/3029684/26525666/92f46f0c-4399-11e7-9395-4379f6f20876.png" />
# Installation
- Requires:

View File

@@ -210,6 +210,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
group.leave()
}
group.wait()
self.context.reset()
}

View File

@@ -0,0 +1,47 @@
//
// AttributeProtocol.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - AttributeProtocol
internal protocol AttributeProtocol: class {
static var attributeType: NSAttributeType { get }
var keyPath: KeyPath { get }
var isOptional: Bool { get }
var isIndexed: Bool { get }
var isTransient: Bool { get }
var versionHashModifier: () -> String? { get }
var renamingIdentifier: () -> String? { get }
var defaultValue: () -> Any? { get }
var affectedByKeyPaths: () -> Set<String> { get }
weak var parentObject: CoreStoreObject? { get set }
var getter: CoreStoreManagedObject.CustomGetter? { get }
var setter: CoreStoreManagedObject.CustomSetter? { get }
}

View File

@@ -438,6 +438,11 @@ public /*abstract*/ class BaseDataTransaction {
return self.bypassesQueueing || self.transactionQueue.cs_isCurrentExecutionContext()
}
deinit {
self.context.reset()
}
// MARK: Deprecated

View File

@@ -35,7 +35,7 @@ import CoreData
- SeeAlso: `AsynchronousDataTransaction`
*/
@objc
public final class CSAsynchronousDataTransaction: CSBaseDataTransaction {
public final class CSAsynchronousDataTransaction: CSBaseDataTransaction, CoreStoreObjectiveCType {
/**
Saves the transaction changes. This method should not be used after the `-commitWithCompletion:` method was already called once.
@@ -139,9 +139,9 @@ public final class CSAsynchronousDataTransaction: CSBaseDataTransaction {
public typealias SwiftType = AsynchronousDataTransaction
public override var bridgeToSwift: AsynchronousDataTransaction {
public var bridgeToSwift: AsynchronousDataTransaction {
return super.bridgeToSwift as! AsynchronousDataTransaction
return super.swiftTransaction as! AsynchronousDataTransaction
}
public required init(_ swiftValue: AsynchronousDataTransaction) {
@@ -149,9 +149,9 @@ public final class CSAsynchronousDataTransaction: CSBaseDataTransaction {
super.init(swiftValue as BaseDataTransaction)
}
public required init(_ swiftValue: BaseDataTransaction) {
public required override init(_ swiftValue: BaseDataTransaction) {
super.init(swiftValue as! AsynchronousDataTransaction)
super.init(swiftValue)
}

View File

@@ -40,7 +40,7 @@ public extension CSBaseDataTransaction {
@objc
public func fetchExistingObject(_ object: NSManagedObject) -> Any? {
return self.bridgeToSwift.context.fetchExisting(object) as NSManagedObject?
return self.swiftTransaction.context.fetchExisting(object) as NSManagedObject?
}
/**
@@ -52,7 +52,7 @@ public extension CSBaseDataTransaction {
@objc
public func fetchExistingObjectWithID(_ objectID: NSManagedObjectID) -> Any? {
return self.bridgeToSwift.context.fetchExisting(objectID) as NSManagedObject?
return self.swiftTransaction.context.fetchExisting(objectID) as NSManagedObject?
}
/**
@@ -64,7 +64,7 @@ public extension CSBaseDataTransaction {
@objc
public func fetchExistingObjects(_ objects: [NSManagedObject]) -> [Any] {
return self.bridgeToSwift.context.fetchExisting(objects) as [NSManagedObject]
return self.swiftTransaction.context.fetchExisting(objects) as [NSManagedObject]
}
/**
@@ -76,7 +76,7 @@ public extension CSBaseDataTransaction {
@objc
public func fetchExistingObjectsWithIDs(_ objectIDs: [NSManagedObjectID]) -> [Any] {
return self.bridgeToSwift.context.fetchExisting(objectIDs) as [NSManagedObject]
return self.swiftTransaction.context.fetchExisting(objectIDs) as [NSManagedObject]
}
/**
@@ -90,10 +90,10 @@ public extension CSBaseDataTransaction {
public func fetchOneFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> Any? {
CoreStore.assert(
self.bridgeToSwift.isRunningInAllowedQueue(),
self.swiftTransaction.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.bridgeToSwift.context.fetchOne(from, fetchClauses)
return self.swiftTransaction.context.fetchOne(from, fetchClauses)
}
/**
@@ -107,10 +107,10 @@ public extension CSBaseDataTransaction {
public func fetchAllFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> [Any]? {
CoreStore.assert(
self.bridgeToSwift.isRunningInAllowedQueue(),
self.swiftTransaction.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.bridgeToSwift.context.fetchAll(from, fetchClauses)
return self.swiftTransaction.context.fetchAll(from, fetchClauses)
}
/**
@@ -124,10 +124,10 @@ public extension CSBaseDataTransaction {
public func fetchCountFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> NSNumber? {
CoreStore.assert(
self.bridgeToSwift.isRunningInAllowedQueue(),
self.swiftTransaction.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.bridgeToSwift.context
return self.swiftTransaction.context
.fetchCount(from, fetchClauses)
.flatMap { NSNumber(value: $0) }
}
@@ -143,10 +143,10 @@ public extension CSBaseDataTransaction {
public func fetchObjectIDFrom(_ from: CSFrom, fetchClauses: [CSFetchClause]) -> NSManagedObjectID? {
CoreStore.assert(
self.bridgeToSwift.isRunningInAllowedQueue(),
self.swiftTransaction.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.bridgeToSwift.context.fetchObjectID(from, fetchClauses)
return self.swiftTransaction.context.fetchObjectID(from, fetchClauses)
}
/**
@@ -163,10 +163,10 @@ public extension CSBaseDataTransaction {
public func queryValueFrom(_ from: CSFrom, selectClause: CSSelect, queryClauses: [CSQueryClause]) -> Any? {
CoreStore.assert(
self.bridgeToSwift.isRunningInAllowedQueue(),
self.swiftTransaction.isRunningInAllowedQueue(),
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.bridgeToSwift.context.queryValue(from, selectClause, queryClauses)
return self.swiftTransaction.context.queryValue(from, selectClause, queryClauses)
}
/**
@@ -183,9 +183,9 @@ public extension CSBaseDataTransaction {
public func queryAttributesFrom(_ from: CSFrom, selectClause: CSSelect, queryClauses: [CSQueryClause]) -> [[String: Any]]? {
CoreStore.assert(
self.bridgeToSwift.isRunningInAllowedQueue(),
self.swiftTransaction.isRunningInAllowedQueue(),
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.bridgeToSwift.context.queryAttributes(from, selectClause, queryClauses)
return self.swiftTransaction.context.queryAttributes(from, selectClause, queryClauses)
}
}

View File

@@ -35,7 +35,7 @@ import CoreData
- SeeAlso: `BaseDataTransaction`
*/
@objc
public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
public class CSBaseDataTransaction: NSObject {
// MARK: Object management
@@ -45,7 +45,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public var hasChanges: Bool {
return self.bridgeToSwift.hasChanges
return self.swiftTransaction.hasChanges
}
/**
@@ -57,7 +57,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func createInto(_ into: CSInto) -> Any {
return self.bridgeToSwift.create(into.bridgeToSwift)
return self.swiftTransaction.create(into.bridgeToSwift)
}
/**
@@ -69,7 +69,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func editObject(_ object: NSManagedObject?) -> Any? {
return self.bridgeToSwift.edit(object)
return self.swiftTransaction.edit(object)
}
/**
@@ -82,7 +82,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func editInto(_ into: CSInto, objectID: NSManagedObjectID) -> Any? {
return self.bridgeToSwift.edit(into.bridgeToSwift, objectID)
return self.swiftTransaction.edit(into.bridgeToSwift, objectID)
}
/**
@@ -93,7 +93,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func deleteObject(_ object: NSManagedObject?) {
self.bridgeToSwift.delete(object)
self.swiftTransaction.delete(object)
}
/**
@@ -104,7 +104,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func deleteObjects(_ objects: [NSManagedObject]) {
self.bridgeToSwift.delete(objects)
self.swiftTransaction.delete(objects)
}
/**
@@ -113,7 +113,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func refreshAndMergeAllObjects() {
self.bridgeToSwift.refreshAndMergeAllObjects()
self.swiftTransaction.refreshAndMergeAllObjects()
}
@@ -128,7 +128,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func insertedObjectsOfType(_ entity: NSManagedObject.Type) -> Set<NSManagedObject> {
return self.bridgeToSwift.insertedObjects(entity)
return self.swiftTransaction.insertedObjects(entity)
}
/**
@@ -139,7 +139,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func insertedObjectIDs() -> Set<NSManagedObjectID> {
return self.bridgeToSwift.insertedObjectIDs()
return self.swiftTransaction.insertedObjectIDs()
}
/**
@@ -151,7 +151,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func insertedObjectIDsOfType(_ entity: NSManagedObject.Type) -> Set<NSManagedObjectID> {
return self.bridgeToSwift.insertedObjectIDs(entity)
return self.swiftTransaction.insertedObjectIDs(entity)
}
/**
@@ -163,7 +163,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func updatedObjectsOfType(_ entity: NSManagedObject.Type) -> Set<NSManagedObject> {
return self.bridgeToSwift.updatedObjects(entity)
return self.swiftTransaction.updatedObjects(entity)
}
/**
@@ -174,7 +174,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func updatedObjectIDs() -> Set<NSManagedObjectID> {
return self.bridgeToSwift.updatedObjectIDs()
return self.swiftTransaction.updatedObjectIDs()
}
/**
@@ -186,7 +186,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func updatedObjectIDsOfType(_ entity: NSManagedObject.Type) -> Set<NSManagedObjectID> {
return self.bridgeToSwift.updatedObjectIDs(entity)
return self.swiftTransaction.updatedObjectIDs(entity)
}
/**
@@ -198,7 +198,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func deletedObjectsOfType(_ entity: NSManagedObject.Type) -> Set<NSManagedObject> {
return self.bridgeToSwift.deletedObjects(entity)
return self.swiftTransaction.deletedObjects(entity)
}
/**
@@ -209,7 +209,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func deletedObjectIDs() -> Set<NSManagedObjectID> {
return self.bridgeToSwift.deletedObjectIDs()
return self.swiftTransaction.deletedObjectIDs()
}
/**
@@ -221,7 +221,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func deletedObjectIDsOfType(_ entity: NSManagedObject.Type) -> Set<NSManagedObjectID> {
return self.bridgeToSwift.deletedObjectIDs(entity)
return self.swiftTransaction.deletedObjectIDs(entity)
}
@@ -229,7 +229,7 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
public override var hash: Int {
return ObjectIdentifier(self.bridgeToSwift).hashValue
return ObjectIdentifier(self.swiftTransaction).hashValue
}
public override func isEqual(_ object: Any?) -> Bool {
@@ -238,28 +238,20 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
return false
}
return self.bridgeToSwift === object.bridgeToSwift
return self.swiftTransaction === object.swiftTransaction
}
// MARK: CoreStoreObjectiveCType
// MARK: Internal
public required init(_ swiftValue: BaseDataTransaction) {
internal let swiftTransaction: BaseDataTransaction
internal init(_ swiftValue: BaseDataTransaction) {
self.swiftTransaction = swiftValue
super.init()
}
public var bridgeToSwift: BaseDataTransaction {
return self.swiftTransaction
}
// MARK: Private
private let swiftTransaction: BaseDataTransaction
// MARK: Deprecated
@@ -267,20 +259,20 @@ public class CSBaseDataTransaction: NSObject, CoreStoreObjectiveCType {
@objc
public func insertedObjects() -> Set<NSManagedObject> {
return self.bridgeToSwift.insertedObjects()
return self.swiftTransaction.insertedObjects()
}
@available(*, deprecated, message: "Use -[updatedObjectsOfType:] and pass the specific entity class")
@objc
public func updatedObjects() -> Set<NSManagedObject> {
return self.bridgeToSwift.updatedObjects()
return self.swiftTransaction.updatedObjects()
}
@available(*, deprecated, message: "Use -[deletedObjectsOfType:] and pass the specific entity class")
@objc
public func deletedObjects() -> Set<NSManagedObject> {
return self.bridgeToSwift.deletedObjects()
return self.swiftTransaction.deletedObjects()
}
}

View File

@@ -35,7 +35,7 @@ import CoreData
- SeeAlso: `SynchronousDataTransaction`
*/
@objc
public final class CSSynchronousDataTransaction: CSBaseDataTransaction {
public final class CSSynchronousDataTransaction: CSBaseDataTransaction, CoreStoreObjectiveCType {
/**
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `-commitAndWaitWithError:` method was already called once.
@@ -129,9 +129,9 @@ public final class CSSynchronousDataTransaction: CSBaseDataTransaction {
public typealias SwiftType = SynchronousDataTransaction
public override var bridgeToSwift: SynchronousDataTransaction {
public var bridgeToSwift: SynchronousDataTransaction {
return super.bridgeToSwift as! SynchronousDataTransaction
return super.swiftTransaction as! SynchronousDataTransaction
}
public required init(_ swiftValue: SynchronousDataTransaction) {
@@ -139,9 +139,9 @@ public final class CSSynchronousDataTransaction: CSBaseDataTransaction {
super.init(swiftValue as BaseDataTransaction)
}
public required init(_ swiftValue: BaseDataTransaction) {
public required override init(_ swiftValue: BaseDataTransaction) {
super.init(swiftValue as! SynchronousDataTransaction)
super.init(swiftValue)
}

View File

@@ -35,7 +35,7 @@ import CoreData
- SeeAlso: `UnsafeDataTransaction`
*/
@objc
public final class CSUnsafeDataTransaction: CSBaseDataTransaction {
public final class CSUnsafeDataTransaction: CSBaseDataTransaction, CoreStoreObjectiveCType {
/**
Saves the transaction changes asynchronously. For a `CSUnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
@@ -189,9 +189,9 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction {
public typealias SwiftType = UnsafeDataTransaction
public override var bridgeToSwift: UnsafeDataTransaction {
public var bridgeToSwift: UnsafeDataTransaction {
return super.bridgeToSwift as! UnsafeDataTransaction
return super.swiftTransaction as! UnsafeDataTransaction
}
public required init(_ swiftValue: UnsafeDataTransaction) {
@@ -199,9 +199,9 @@ public final class CSUnsafeDataTransaction: CSBaseDataTransaction {
super.init(swiftValue as BaseDataTransaction)
}
public required init(_ swiftValue: BaseDataTransaction) {
public required override init(_ swiftValue: BaseDataTransaction) {
super.init(swiftValue as! UnsafeDataTransaction)
super.init(swiftValue)
}

View File

@@ -868,7 +868,8 @@ extension SQLiteStore: CustomDebugStringConvertible, CoreStoreDebugStringConvert
("storeOptions", self.storeOptions as Any),
("fileURL", self.fileURL),
("migrationMappingProviders", self.migrationMappingProviders),
("localStorageOptions", self.localStorageOptions)
("localStorageOptions", self.localStorageOptions),
("fileSize", self.fileSize() as Any)
)
}
}

View File

@@ -54,7 +54,7 @@ public extension CoreStore {
}
/**
Using the `defaultStack`, performs a transaction synchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be rethrown from `perform(synchronous:)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
Using the `defaultStack`, performs a transaction synchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be thrown from `perform(synchronous:)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the synchronous non-escaping closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`.

View File

@@ -235,15 +235,16 @@ CSWhere *_Nonnull CSWherePredicate(NSPredicate *_Nonnull predicate) CORESTORE_RE
- (void)setAffectedStores:(NSArray<NSPersistentStore *> *_Nullable)affectedStores {
// Bugfix for NSFetchRequest messing up memory management for `affectedStores`
// http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified
if (NSFoundationVersionNumber < NSFoundationVersionNumber10_0) {
NSProcessInfo *processInfo = [NSProcessInfo processInfo];
if ([processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 11, 0, 0 }]
|| ![processInfo isOperatingSystemAtLeastVersion:(NSOperatingSystemVersion){ 10, 0, 0 }]) {
self.safeAffectedStores = affectedStores;
[super setAffectedStores:affectedStores];
return;
}
// Bugfix for NSFetchRequest messing up memory management for `affectedStores`
// http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified
if (self.releaseArray != NULL) {
CFRelease(self.releaseArray);

View File

@@ -35,7 +35,7 @@ internal final class CoreStoreFetchedResultsController: NSFetchedResultsControll
// MARK: Internal
@nonobjc
internal convenience init<T: DynamicObject>(dataStack: DataStack, fetchRequest: NSFetchRequest<NSManagedObject>, from: From<T>?, sectionBy: SectionBy? = nil, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void) {
internal convenience init<T: DynamicObject>(dataStack: DataStack, fetchRequest: NSFetchRequest<NSManagedObject>, from: From<T>, sectionBy: SectionBy? = nil, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void) {
self.init(
context: dataStack.mainContext,
@@ -47,33 +47,18 @@ internal final class CoreStoreFetchedResultsController: NSFetchedResultsControll
}
@nonobjc
internal init<T: DynamicObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest<NSManagedObject>, from: From<T>?, sectionBy: SectionBy? = nil, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void) {
internal init<T: DynamicObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest<NSManagedObject>, from: From<T>, sectionBy: SectionBy? = nil, applyFetchClauses: @escaping (_ fetchRequest: NSFetchRequest<NSManagedObject>) -> Void) {
_ = from?.applyToFetchRequest(
_ = from.applyToFetchRequest(
fetchRequest,
context: context,
applyAffectedStores: false
)
applyFetchClauses(fetchRequest)
if let from = from {
self.reapplyAffectedStores = { fetchRequest, context in
self.reapplyAffectedStores = { fetchRequest, context in
return from.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
}
}
else {
guard let from = (fetchRequest.entity.flatMap { $0.managedObjectClassName }).flatMap(NSClassFromString).flatMap({ From<T>($0 as! T.Type) }) else {
CoreStore.abort("Attempted to create a \(CoreStoreFetchedResultsController.self) without a \(cs_typeName(From<T>.self)) clause or an \(cs_typeName(NSEntityDescription.self)).")
}
self.reapplyAffectedStores = { fetchRequest, context in
return from.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
}
return from.applyAffectedStoresForFetchedRequest(fetchRequest, context: context)
}
super.init(

View File

@@ -0,0 +1,34 @@
//
// CoreStoreManagedObject.swift
// CoreStore
//
// Created by John Rommel Estropia on 2017/06/04.
// Copyright © 2017 John Rommel Estropia. All rights reserved.
//
import CoreData
// MARK: - CoreStoreManagedObject
@objc internal class CoreStoreManagedObject: NSManagedObject {
internal typealias CustomGetter = @convention(block) (_ rawObject: Any) -> Any?
internal typealias CustomSetter = @convention(block) (_ rawObject: Any, _ newValue: Any?) -> Void
internal typealias CustomGetterSetter = (getter: CustomGetter?, setter: CustomSetter?)
@nonobjc @inline(__always)
internal static func cs_subclassName(for entity: DynamicEntity, in modelVersion: ModelVersion) -> String {
return "_\(NSStringFromClass(CoreStoreManagedObject.self))__\(modelVersion)__\(NSStringFromClass(entity.type))__\(entity.entityName)"
}
}
// MARK: - Private
private enum Static {
static let queue = DispatchQueue.concurrent("com.coreStore.coreStoreManagerObjectBarrierQueue")
static var cache: [ObjectIdentifier: [KeyPath: Set<KeyPath>]] = [:]
}

View File

@@ -53,6 +53,39 @@ public extension DynamicObject where Self: CoreStoreObject {
return attribute(self.meta).keyPath
}
/**
Extracts the keyPath string from a `CoreStoreObject.Relationship` property.
```
let keyPath: String = Person.keyPath { $0.pets }
```
*/
public static func keyPath<O: CoreStoreObject, D: CoreStoreObject>(_ relationship: (Self) -> RelationshipContainer<O>.ToOne<D>) -> String {
return relationship(self.meta).keyPath
}
/**
Extracts the keyPath string from a `CoreStoreObject.Relationship` property.
```
let keyPath: String = Person.keyPath { $0.pets }
```
*/
public static func keyPath<O: CoreStoreObject, D: CoreStoreObject>(_ relationship: (Self) -> RelationshipContainer<O>.ToManyOrdered<D>) -> String {
return relationship(self.meta).keyPath
}
/**
Extracts the keyPath string from a `CoreStoreObject.Relationship` property.
```
let keyPath: String = Person.keyPath { $0.pets }
```
*/
public static func keyPath<O: CoreStoreObject, D: CoreStoreObject>(_ relationship: (Self) -> RelationshipContainer<O>.ToManyUnordered<D>) -> String {
return relationship(self.meta).keyPath
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
@@ -67,10 +100,10 @@ public extension DynamicObject where Self: CoreStoreObject {
/**
Creates an `OrderBy` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchAll(From<Person>(), Person.ascending { $0.age })
let person = CoreStore.fetchAll(From<Person>(), Person.orderBy(ascending: { $0.age }))
```
*/
public static func ascending<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
public static func orderBy<O: CoreStoreObject, V: ImportableAttributeType>(ascending attribute: (Self) -> ValueContainer<O>.Required<V>) -> OrderBy {
return OrderBy(.ascending(attribute(self.meta).keyPath))
}
@@ -78,9 +111,46 @@ public extension DynamicObject where Self: CoreStoreObject {
/**
Creates an `OrderBy` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchAll(From<Person>(), Person.descending { $0.age })
let person = CoreStore.fetchAll(From<Person>(), Person.orderBy(ascending: { $0.age }))
```
*/
public static func orderBy<O: CoreStoreObject, V: ImportableAttributeType>(ascending attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.ascending(attribute(self.meta).keyPath))
}
/**
Creates an `OrderBy` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchAll(From<Person>(), Person.orderBy(descending: { $0.age }))
```
*/
public static func orderBy<O: CoreStoreObject, V: ImportableAttributeType>(descending attribute: (Self) -> ValueContainer<O>.Required<V>) -> OrderBy {
return OrderBy(.descending(attribute(self.meta).keyPath))
}
/**
Creates an `OrderBy` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchAll(From<Person>(), Person.orderBy(descending: { $0.age }))
```
*/
public static func orderBy<O: CoreStoreObject, V: ImportableAttributeType>(descending attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.descending(attribute(self.meta).keyPath))
}
// MARK: Deprecated
@available(*, deprecated, renamed: "orderBy(ascending:)")
public static func ascending<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.ascending(attribute(self.meta).keyPath))
}
@available(*, deprecated, renamed: "orderBy(descending:)")
public static func descending<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.descending(attribute(self.meta).keyPath))
@@ -163,6 +233,18 @@ public extension ValueContainer.Required {
return Where("%K >= %@", attribute.keyPath, value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname })
```
*/
@inline(__always)
public static func ~= <S: Sequence>(_ sequence: S, _ attribute: ValueContainer<O>.Required<V>) -> Where where S.Iterator.Element == V {
return Where(attribute.keyPath, isMemberOf: sequence)
}
}
@@ -193,4 +275,146 @@ public extension ValueContainer.Optional {
return !Where(attribute.keyPath, isEqualTo: value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age < 20 })
```
*/
@inline(__always)
public static func < (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
if let value = value {
return Where("%K < %@", attribute.keyPath, value)
}
else {
return Where("%K < nil", attribute.keyPath)
}
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age > 20 })
```
*/
@inline(__always)
public static func > (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
if let value = value {
return Where("%K > %@", attribute.keyPath, value)
}
else {
return Where("%K > nil", attribute.keyPath)
}
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age <= 20 })
```
*/
@inline(__always)
public static func <= (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
if let value = value {
return Where("%K <= %@", attribute.keyPath, value)
}
else {
return Where("%K <= nil", attribute.keyPath)
}
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age >= 20 })
```
*/
@inline(__always)
public static func >= (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
if let value = value {
return Where("%K >= %@", attribute.keyPath, value)
}
else {
return Where("%K >= nil", attribute.keyPath)
}
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname })
```
*/
@inline(__always)
public static func ~= <S: Sequence>(_ sequence: S, _ attribute: ValueContainer<O>.Optional<V>) -> Where where S.Iterator.Element == V {
return Where(attribute.keyPath, isMemberOf: sequence)
}
}
// MARK: - RelationshipContainer.ToOne
public extension RelationshipContainer.ToOne {
/**
Creates a `Where` clause from a `CoreStoreObject.Relationship` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { $0.master == me })
```
*/
@inline(__always)
public static func == (_ relationship: RelationshipContainer<O>.ToOne<D>, _ object: D?) -> Where {
return Where(relationship.keyPath, isEqualTo: object)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Relationship` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { $0.master != me })
```
*/
@inline(__always)
public static func != (_ relationship: RelationshipContainer<O>.ToOne<D>, _ object: D?) -> Where {
return !Where(relationship.keyPath, isEqualTo: object)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Relationship` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { $0.master ~= me })
```
*/
@inline(__always)
public static func ~= (_ relationship: RelationshipContainer<O>.ToOne<D>, _ object: D?) -> Where {
return Where(relationship.keyPath, isEqualTo: object)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Relationship` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { [john, joe, bob] ~= $0.master })
```
*/
@inline(__always)
public static func ~= <S: Sequence>(_ sequence: S, _ relationship: RelationshipContainer<O>.ToOne<D>) -> Where where S.Iterator.Element == D {
return Where(relationship.keyPath, isMemberOf: sequence)
}
}

View File

@@ -68,8 +68,8 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
public required init(rawObject: NSManagedObject) {
self.isMeta = false
self.rawObject = rawObject
self.initializeAttributes(Mirror(reflecting: self), { [unowned self] in self })
self.rawObject = (rawObject as! CoreStoreManagedObject)
self.initializeAttributes(Mirror(reflecting: self), self)
}
/**
@@ -110,13 +110,13 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
// MARK: Internal
internal let rawObject: NSManagedObject?
internal let rawObject: CoreStoreManagedObject?
internal let isMeta: Bool
// MARK: Private
private func initializeAttributes(_ mirror: Mirror, _ parentObject: @escaping () -> CoreStoreObject) {
private func initializeAttributes(_ mirror: Mirror, _ parentObject: CoreStoreObject) {
_ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, parentObject) })
for child in mirror.children {
@@ -135,3 +135,23 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
}
}
}
// MARK: - DynamicObject where Self: CoreStoreObject
public extension DynamicObject where Self: CoreStoreObject {
public func partialObject() -> PartialObject<Self> {
CoreStore.assert(
!self.isMeta,
"Attempted to create a \(cs_typeName(PartialObject<Self>.self)) from a meta object. Meta objects are only used for querying keyPaths and infering types."
)
return PartialObject<Self>(self.rawObject!)
}
internal static var meta: Self {
return self.init(asMeta: ())
}
}

View File

@@ -200,35 +200,44 @@ public final class CoreStoreSchema: DynamicSchema {
public func rawModel() -> NSManagedObjectModel {
if let cachedRawModel = self.cachedRawModel {
return CoreStoreSchema.barrierQueue.sync(flags: .barrier) {
return cachedRawModel
}
let rawModel = NSManagedObjectModel()
var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
for entity in self.allEntities {
let entityDescription = self.entityDescription(
for: entity,
initializer: CoreStoreSchema.firstPassCreateEntityDescription
if let cachedRawModel = self.cachedRawModel {
return cachedRawModel
}
let rawModel = NSManagedObjectModel()
var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
var allCustomGettersSetters: [DynamicEntity: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]] = [:]
for entity in self.allEntities {
let (entityDescription, customGetterSetterByKeyPaths) = self.entityDescription(
for: entity,
initializer: CoreStoreSchema.firstPassCreateEntityDescription(from:in:)
)
entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription)
allCustomGettersSetters[entity] = customGetterSetterByKeyPaths
}
CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity)
CoreStoreSchema.thirdPassConnectInheritanceTree(for: entityDescriptionsByEntity)
CoreStoreSchema.fourthPassSynthesizeManagedObjectClasses(
for: entityDescriptionsByEntity,
allCustomGettersSetters: allCustomGettersSetters
)
entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription)
}
CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity)
CoreStoreSchema.thirdPassConnectInheritanceTree(for: entityDescriptionsByEntity)
rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! })
for (configuration, entities) in self.entitiesByConfiguration {
rawModel.setEntities(
entities
.map({ entityDescriptionsByEntity[$0]! })
.sorted(by: { $0.name! < $1.name! }),
forConfigurationName: configuration
)
rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! })
for (configuration, entities) in self.entitiesByConfiguration {
rawModel.setEntities(
entities
.map({ entityDescriptionsByEntity[$0]! })
.sorted(by: { $0.name! < $1.name! }),
forConfigurationName: configuration
)
}
self.cachedRawModel = rawModel
return rawModel
}
self.cachedRawModel = rawModel
return rawModel
}
@@ -244,28 +253,33 @@ public final class CoreStoreSchema: DynamicSchema {
private let allEntities: Set<DynamicEntity>
private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
private var customGettersSettersByEntity: [DynamicEntity: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]] = [:]
private weak var cachedRawModel: NSManagedObjectModel?
private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity) -> NSEntityDescription) -> NSEntityDescription {
private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity, ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter])) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]) {
if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] {
return cachedEntityDescription
return (cachedEntityDescription, self.customGettersSettersByEntity[entity] ?? [:])
}
let entityDescription = withoutActuallyEscaping(initializer, do: { $0(entity) })
let modelVersion = self.modelVersion
let (entityDescription, customGetterSetterByKeyPaths) = withoutActuallyEscaping(initializer, do: { $0(entity, modelVersion) })
self.entityDescriptionsByEntity[entity] = entityDescription
return entityDescription
self.customGettersSettersByEntity[entity] = customGetterSetterByKeyPaths
return (entityDescription, customGetterSetterByKeyPaths)
}
private static func firstPassCreateEntityDescription(from entity: DynamicEntity) -> NSEntityDescription {
private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]) {
let entityDescription = NSEntityDescription()
entityDescription.coreStoreEntity = entity
entityDescription.name = entity.entityName
entityDescription.isAbstract = entity.isAbstract
entityDescription.versionHashModifier = entity.versionHashModifier
entityDescription.managedObjectClassName = NSStringFromClass(NSManagedObject.self)
entityDescription.managedObjectClassName = CoreStoreManagedObject.cs_subclassName(for: entity, in: modelVersion)
var keyPathsByAffectedKeyPaths: [KeyPath: Set<KeyPath>] = [:]
var customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter] = [:]
func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] {
var propertyDescriptions: [NSPropertyDescription] = []
@@ -279,11 +293,13 @@ public final class CoreStoreSchema: DynamicSchema {
description.attributeType = type(of: attribute).attributeType
description.isOptional = attribute.isOptional
description.isIndexed = attribute.isIndexed
description.defaultValue = attribute.defaultValue
description.defaultValue = attribute.defaultValue()
description.isTransient = attribute.isTransient
description.versionHashModifier = attribute.versionHashModifier
description.renamingIdentifier = attribute.renamingIdentifier
description.versionHashModifier = attribute.versionHashModifier()
description.renamingIdentifier = attribute.renamingIdentifier()
propertyDescriptions.append(description)
keyPathsByAffectedKeyPaths[attribute.keyPath] = attribute.affectedByKeyPaths()
customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter)
case let relationship as RelationshipProtocol:
let description = NSRelationshipDescription()
@@ -292,9 +308,10 @@ public final class CoreStoreSchema: DynamicSchema {
description.maxCount = relationship.maxCount
description.isOrdered = relationship.isOrdered
description.deleteRule = relationship.deleteRule
description.versionHashModifier = relationship.versionHashModifier
description.renamingIdentifier = relationship.renamingIdentifier
description.versionHashModifier = relationship.versionHashModifier()
description.renamingIdentifier = relationship.renamingIdentifier()
propertyDescriptions.append(description)
keyPathsByAffectedKeyPaths[relationship.keyPath] = relationship.affectedByKeyPaths()
default:
continue
@@ -302,9 +319,9 @@ public final class CoreStoreSchema: DynamicSchema {
}
return propertyDescriptions
}
entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type)
return entityDescription
entityDescription.keyPathsByAffectedKeyPaths = keyPathsByAffectedKeyPaths
return (entityDescription, customGetterSetterByKeyPaths)
}
private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) {
@@ -425,4 +442,112 @@ public final class CoreStoreSchema: DynamicSchema {
)
}
}
private static func fourthPassSynthesizeManagedObjectClasses(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription], allCustomGettersSetters: [DynamicEntity: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]]) {
func createManagedObjectSubclass(for entityDescription: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]?) {
let superEntity = entityDescription.superentity
let className = entityDescription.managedObjectClassName!
guard case nil = NSClassFromString(className) as! CoreStoreManagedObject.Type? else {
return
}
if let superEntity = superEntity {
createManagedObjectSubclass(
for: superEntity,
customGetterSetterByKeyPaths: superEntity.coreStoreEntity.flatMap({ allCustomGettersSetters[$0] })
)
}
let superClass = cs_lazy { () -> CoreStoreManagedObject.Type in
if let superClassName = superEntity?.managedObjectClassName,
let superClass = NSClassFromString(superClassName) {
return superClass as! CoreStoreManagedObject.Type
}
return CoreStoreManagedObject.self
}
let managedObjectClass: AnyClass = className.withCString {
return objc_allocateClassPair(superClass, $0, 0)!
}
defer {
objc_registerClassPair(managedObjectClass)
}
func capitalize(_ string: String) -> String {
return string.replacingCharacters(
in: Range(uncheckedBounds: (string.startIndex, string.index(after: string.startIndex))),
with: String(string[string.startIndex]).uppercased()
)
}
for (attributeName, customGetterSetters) in (customGetterSetterByKeyPaths ?? [:])
where customGetterSetters.getter != nil || customGetterSetters.setter != nil {
if let getter = customGetterSetters.getter {
let getterName = "\(attributeName)"
guard class_addMethod(
managedObjectClass,
NSSelectorFromString(getterName),
imp_implementationWithBlock(getter),
"@@:") else {
CoreStore.abort("Could not dynamically add getter method \"\(getterName)\" to class \(cs_typeName(managedObjectClass))")
}
}
if let setter = customGetterSetters.setter {
let setterName = "set\(capitalize(attributeName)):"
guard class_addMethod(
managedObjectClass,
NSSelectorFromString(setterName),
imp_implementationWithBlock(setter),
"v@:@") else {
CoreStore.abort("Could not dynamically add setter method \"\(setterName)\" to class \(cs_typeName(managedObjectClass))")
}
}
}
let newSelector = NSSelectorFromString("cs_keyPathsForValuesAffectingValueForKey:")
let keyPathsByAffectedKeyPaths = entityDescription.keyPathsByAffectedKeyPaths
let keyPathsForValuesAffectingValue: @convention(block) (Any, String) -> Set<String> = { (instance, keyPath) in
if let keyPaths = keyPathsByAffectedKeyPaths[keyPath] {
return keyPaths
}
return []
}
let origSelector = #selector(NSManagedObject.keyPathsForValuesAffectingValue(forKey:))
let metaClass: AnyClass = object_getClass(managedObjectClass)!
let origMethod = class_getClassMethod(managedObjectClass, origSelector)
let origImp = method_getImplementation(origMethod)
let newImp = imp_implementationWithBlock(keyPathsForValuesAffectingValue)
if class_addMethod(metaClass, origSelector, newImp, method_getTypeEncoding(origMethod)) {
class_replaceMethod(metaClass, newSelector, origImp, method_getTypeEncoding(origMethod))
}
else {
let newMethod = class_getClassMethod(managedObjectClass, newSelector)
method_exchangeImplementations(origMethod, newMethod)
}
}
for (dynamicEntity, entityDescription) in entityDescriptionsByEntity {
createManagedObjectSubclass(
for: entityDescription,
customGetterSetterByKeyPaths: allCustomGettersSetters[dynamicEntity]
)
}
}
}

View File

@@ -32,7 +32,7 @@ import Foundation
/**
A `SchemaMappingProvider` that accepts custom mappings for some entities. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible.
*/
open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
/**
The source model version for the mapping.
@@ -352,7 +352,7 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
)
func expression(forSource sourceEntity: NSEntityDescription) -> NSExpression {
return NSExpression(format: "FETCH(FUNCTION($\(NSMigrationManagerKey), \"fetchRequestForSourceEntityNamed:predicateString:\" , \"\(sourceEntity.name!)\", \"\(NSPredicate(value: true))\"), $\(NSMigrationManagerKey).\(#keyPath(NSMigrationManager.sourceContext)), \(false))")
return NSExpression(format: "FETCH(FUNCTION($\(NSMigrationManagerKey), \"fetchRequestForSourceEntityNamed:predicateString:\" , \"\(sourceEntity.name!)\", \"\(NSPredicate(value: true))\"), FUNCTION($\(NSMigrationManagerKey), \"\(#selector(getter: NSMigrationManager.sourceContext))\"), \(false))")
}
let sourceEntitiesByName = sourceModel.entitiesByName
@@ -427,22 +427,25 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
let destinationAttribute = destination.attribute
let propertyMapping = NSPropertyMapping()
propertyMapping.name = destinationAttribute.name
propertyMapping.valueExpression = NSExpression(format: "$\(NSMigrationSourceObjectKey).\(sourceAttribute.name)")
propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationSourceObjectKey), \"\(#selector(NSManagedObject.value(forKey:)))\", \"\(sourceAttribute.name)\")")
attributeMappings.append(propertyMapping)
}
return attributeMappings
}
let entityMappingName = entityMapping.name!
entityMapping.relationshipMappings = autoreleasepool { () -> [NSPropertyMapping] in
let sourceRelationships = sourceEntity.cs_resolvedRelationshipRenamingIdentities()
let destinationRelationships = destinationEntity.cs_resolvedRelationshipRenamingIdentities()
var relationshipMappings: [NSPropertyMapping] = []
for (_, destination) in destinationRelationships {
for (renamingIdentifier, destination) in destinationRelationships {
let sourceRelationship = sourceRelationships[renamingIdentifier]!.relationship
let destinationRelationship = destination.relationship
let sourceRelationshipName = sourceRelationship.name
let propertyMapping = NSPropertyMapping()
propertyMapping.name = destinationRelationship.name
propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationManagerKey), \"\(#selector(NSMigrationManager.destinationInstances(forEntityMappingName:sourceInstances:)))\" , \"\(entityMappingName)\", $\(NSMigrationSourceObjectKey))[0]")
propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationManagerKey), \"destinationInstancesForSourceRelationshipNamed:sourceInstances:\", \"\(sourceRelationshipName)\", FUNCTION($\(NSMigrationSourceObjectKey), \"\(#selector(NSManagedObject.value(forKey:)))\", \"\(sourceRelationshipName)\"))")
relationshipMappings.append(propertyMapping)
}
return relationshipMappings
@@ -483,17 +486,24 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
}
userInfo[CustomEntityMigrationPolicy.UserInfoKey.sourceAttributesByDestinationKey] = sourceAttributesByDestinationKey
}
let entityMappingName = entityMapping.name!
entityMapping.relationshipMappings = autoreleasepool { () -> [NSPropertyMapping] in
let sourceRelationships = sourceEntity.cs_resolvedRelationshipRenamingIdentities()
let destinationRelationships = destinationEntity.cs_resolvedRelationshipRenamingIdentities()
let transformedRenamingIdentifiers = Set(destinationRelationships.keys)
.intersection(sourceRelationships.keys)
var relationshipMappings: [NSPropertyMapping] = []
for (_, destination) in destinationRelationships {
let destinationRelationship = destination.relationship
for renamingIdentifier in transformedRenamingIdentifiers {
let sourceRelationship = sourceRelationships[renamingIdentifier]!.relationship
let destinationRelationship = destinationRelationships[renamingIdentifier]!.relationship
let sourceRelationshipName = sourceRelationship.name
let destinationRelationshipName = destinationRelationship.name
let propertyMapping = NSPropertyMapping()
propertyMapping.name = destinationRelationship.name
propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationManagerKey), \"\(#selector(NSMigrationManager.destinationInstances(forEntityMappingName:sourceInstances:)))\" , \"\(entityMappingName)\", $\(NSMigrationSourceObjectKey))[0]")
propertyMapping.name = destinationRelationshipName
propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationManagerKey), \"destinationInstancesForSourceRelationshipNamed:sourceInstances:\", \"\(sourceRelationshipName)\", FUNCTION($\(NSMigrationSourceObjectKey), \"\(#selector(NSManagedObject.value(forKey:)))\", \"\(sourceRelationshipName)\"))")
relationshipMappings.append(propertyMapping)
}
return relationshipMappings
@@ -545,11 +555,7 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
)
if let dInstance = destinationObject?.rawObject {
manager.associate(
sourceInstance: sInstance,
withDestinationInstance: dInstance,
for: mapping
)
manager.associate(sourceInstance: sInstance, withDestinationInstance: dInstance, for: mapping)
}
}
@@ -670,77 +676,76 @@ open class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
allMappedSourceKeys[sourceEntity] = destinationEntity
allMappedDestinationKeys[destinationEntity] = sourceEntity
}
}
for renamingIdentifier in transformedRenamingIdentifiers {
for renamingIdentifier in transformedRenamingIdentifiers {
let sourceEntity = sourceRenamingIdentifiers[renamingIdentifier]!.entity
let destinationEntity = destinationRenamingIdentifiers[renamingIdentifier]!.entity
let sourceEntityName = sourceEntity.name!
let destinationEntityName = destinationEntity.name!
switch (allMappedSourceKeys[sourceEntityName], allMappedDestinationKeys[destinationEntityName]) {
let sourceEntity = sourceRenamingIdentifiers[renamingIdentifier]!.entity
let destinationEntity = destinationRenamingIdentifiers[renamingIdentifier]!.entity
let sourceEntityName = sourceEntity.name!
let destinationEntityName = destinationEntity.name!
switch (allMappedSourceKeys[sourceEntityName], allMappedDestinationKeys[destinationEntityName]) {
case (nil, nil):
if sourceEntity.versionHash == destinationEntity.versionHash {
case (nil, nil):
if sourceEntity.versionHash == destinationEntity.versionHash {
copyMappings.insert(
.copyEntity(
sourceEntity: sourceEntityName,
destinationEntity: destinationEntityName
)
copyMappings.insert(
.copyEntity(
sourceEntity: sourceEntityName,
destinationEntity: destinationEntityName
)
}
else {
transformMappings.insert(
.transformEntity(
sourceEntity: sourceEntityName,
destinationEntity: destinationEntityName,
transformer: CustomMapping.inferredTransformation
)
)
}
else {
transformMappings.insert(
.transformEntity(
sourceEntity: sourceEntityName,
destinationEntity: destinationEntityName,
transformer: CustomMapping.inferredTransformation
)
}
allMappedSourceKeys[sourceEntityName] = destinationEntityName
allMappedDestinationKeys[destinationEntityName] = sourceEntityName
case (""?, nil):
insertMappings.insert(.insertEntity(destinationEntity: destinationEntityName))
allMappedDestinationKeys[destinationEntityName] = ""
case (nil, ""?):
deleteMappings.insert(.deleteEntity(sourceEntity: sourceEntityName))
allMappedSourceKeys[sourceEntityName] = ""
default:
continue
)
}
}
for renamingIdentifier in removedRenamingIdentifiers {
allMappedSourceKeys[sourceEntityName] = destinationEntityName
allMappedDestinationKeys[destinationEntityName] = sourceEntityName
let sourceEntity = sourceRenamingIdentifiers[renamingIdentifier]!.entity
let sourceEntityName = sourceEntity.name!
switch allMappedSourceKeys[sourceEntityName] {
case nil:
deleteMappings.insert(.deleteEntity(sourceEntity: sourceEntityName))
allMappedSourceKeys[sourceEntityName] = ""
default:
continue
}
}
for renamingIdentifier in addedRenamingIdentifiers {
case (""?, nil):
insertMappings.insert(.insertEntity(destinationEntity: destinationEntityName))
allMappedDestinationKeys[destinationEntityName] = ""
let destinationEntity = destinationRenamingIdentifiers[renamingIdentifier]!.entity
let destinationEntityName = destinationEntity.name!
switch allMappedDestinationKeys[destinationEntityName] {
case nil:
insertMappings.insert(.insertEntity(destinationEntity: destinationEntityName))
allMappedDestinationKeys[destinationEntityName] = ""
default:
continue
}
case (nil, ""?):
deleteMappings.insert(.deleteEntity(sourceEntity: sourceEntityName))
allMappedSourceKeys[sourceEntityName] = ""
default:
continue
}
}
for renamingIdentifier in removedRenamingIdentifiers {
let sourceEntity = sourceRenamingIdentifiers[renamingIdentifier]!.entity
let sourceEntityName = sourceEntity.name!
switch allMappedSourceKeys[sourceEntityName] {
case nil:
deleteMappings.insert(.deleteEntity(sourceEntity: sourceEntityName))
allMappedSourceKeys[sourceEntityName] = ""
default:
continue
}
}
for renamingIdentifier in addedRenamingIdentifiers {
let destinationEntity = destinationRenamingIdentifiers[renamingIdentifier]!.entity
let destinationEntityName = destinationEntity.name!
switch allMappedDestinationKeys[destinationEntityName] {
case nil:
insertMappings.insert(.insertEntity(destinationEntity: destinationEntityName))
allMappedDestinationKeys[destinationEntityName] = ""
default:
continue
}
}
return (deleteMappings, insertMappings, copyMappings, transformMappings)

View File

@@ -707,6 +707,7 @@ public extension DataStack {
do {
try storage.cs_finalizeStorageAndWait(soureModelHint: sourceModel)
try migrationManager.migrateStore(
from: fileURL,
sourceType: type(of: storage).storeType,
@@ -716,6 +717,13 @@ public extension DataStack {
destinationType: type(of: storage).storeType,
destinationOptions: nil
)
let temporaryStorage = SQLiteStore(
fileURL: temporaryFileURL,
configuration: storage.configuration,
migrationMappingProviders: storage.migrationMappingProviders,
localStorageOptions: storage.localStorageOptions
)
try temporaryStorage.cs_finalizeStorageAndWait(soureModelHint: destinationModel)
}
catch {

View File

@@ -95,7 +95,7 @@ public extension DataStack {
}
/**
Performs a transaction synchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be rethrown from `perform(synchronous:)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
Performs a transaction synchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be thrown from `perform(synchronous:)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the synchronous non-escaping closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`.

View File

@@ -155,14 +155,3 @@ extension CoreStoreObject {
return self.rawObject!
}
}
// MARK: - Internal
internal extension DynamicObject where Self: CoreStoreObject {
internal static var meta: Self {
return self.init(asMeta: ())
}
}

View File

@@ -32,7 +32,7 @@ import Foundation
public extension DynamicSchema {
/**
Prints the `DynamicSchema` as their corresponding `CoreStoreObject` Swift declarations. This is useful for converting current `XcodeDataModelSchema`-based models into the new `CoreStoreSchema` framework. Additional adjustments may need to be done to the generated source code for "Transformable" attributes.
Prints the `DynamicSchema` as their corresponding `CoreStoreObject` Swift declarations. This is useful for converting current `XcodeDataModelSchema`-based models into the new `CoreStoreSchema` framework. Additional adjustments may need to be done to the generated source code; for example: `Transformable` concrete types need to be provided, as well as `default` values.
- Important: After transitioning to the new `CoreStoreSchema` framework, it is recommended to add the new schema as a new version that the existing versions' `XcodeDataModelSchema` can migrate to. It is discouraged to load existing SQLite files created with `XcodeDataModelSchema` directly into a `CoreStoreSchema`.
- returns: a string that represents the source code for the `DynamicSchema` as their corresponding `CoreStoreObject` Swift declarations.
@@ -96,72 +96,62 @@ public extension DynamicSchema {
case .integer16AttributeType:
valueType = Int16.self
if let defaultValue = (attribute.defaultValue as! Int16.ImportableNativeType?).flatMap(Int16.cs_fromImportableNativeType),
defaultValue != Int16.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Int16.ImportableNativeType?).flatMap(Int16.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .integer32AttributeType:
valueType = Int32.self
if let defaultValue = (attribute.defaultValue as! Int32.ImportableNativeType?).flatMap(Int32.cs_fromImportableNativeType),
defaultValue != Int32.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Int32.ImportableNativeType?).flatMap(Int32.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .integer64AttributeType:
valueType = Int64.self
if let defaultValue = (attribute.defaultValue as! Int64.ImportableNativeType?).flatMap(Int64.cs_fromImportableNativeType),
defaultValue != Int64.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Int64.ImportableNativeType?).flatMap(Int64.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .decimalAttributeType:
valueType = NSDecimalNumber.self
if let defaultValue = (attribute.defaultValue as! NSDecimalNumber.ImportableNativeType?).flatMap(NSDecimalNumber.cs_fromImportableNativeType),
defaultValue != NSDecimalNumber.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! NSDecimalNumber?) {
defaultString = ", default: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")"
defaultString = ", initial: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")"
}
case .doubleAttributeType:
valueType = Double.self
if let defaultValue = (attribute.defaultValue as! Double.ImportableNativeType?).flatMap(Double.cs_fromImportableNativeType),
defaultValue != Double.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Double.ImportableNativeType?).flatMap(Double.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .floatAttributeType:
valueType = Float.self
if let defaultValue = (attribute.defaultValue as! Float.ImportableNativeType?).flatMap(Float.cs_fromImportableNativeType),
defaultValue != Float.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Float.ImportableNativeType?).flatMap(Float.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue)"
defaultString = ", initial: \(defaultValue)"
}
case .stringAttributeType:
valueType = String.self
if let defaultValue = (attribute.defaultValue as! String.ImportableNativeType?).flatMap(String.cs_fromImportableNativeType),
defaultValue != String.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! String.ImportableNativeType?).flatMap(String.cs_fromImportableNativeType) {
// TODO: escape strings
defaultString = ", default: \"\(defaultValue)\""
defaultString = ", initial: \"\(defaultValue)\""
}
case .booleanAttributeType:
valueType = Bool.self
if let defaultValue = (attribute.defaultValue as! Bool.ImportableNativeType?).flatMap(Bool.cs_fromImportableNativeType),
defaultValue != Bool.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Bool.ImportableNativeType?).flatMap(Bool.cs_fromImportableNativeType) {
defaultString = ", default: \(defaultValue ? "true" : "false")"
defaultString = ", initial: \(defaultValue ? "true" : "false")"
}
case .dateAttributeType:
valueType = Date.self
if let defaultValue = (attribute.defaultValue as! Date.ImportableNativeType?).flatMap(Date.cs_fromImportableNativeType),
defaultValue != Date.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Date.ImportableNativeType?).flatMap(Date.cs_fromImportableNativeType) {
defaultString = ", default: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))"
defaultString = ", initial: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))"
}
case .binaryDataAttributeType:
valueType = Data.self
if let defaultValue = (attribute.defaultValue as! Data.ImportableNativeType?).flatMap(Data.cs_fromImportableNativeType),
defaultValue != Data.cs_emptyValue() {
if let defaultValue = (attribute.defaultValue as! Data.ImportableNativeType?).flatMap(Data.cs_fromImportableNativeType) {
let count = defaultValue.count
let bytes = defaultValue.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) in
@@ -169,7 +159,7 @@ public extension DynamicSchema {
return (0 ..< (count / MemoryLayout<UInt8>.size))
.map({ "\("0x\(String(pointer[$0], radix: 16, uppercase: false))")" })
}
defaultString = ", default: Data(bytes: [\(bytes.joined(separator: ", "))])"
defaultString = ", initial: Data(bytes: [\(bytes.joined(separator: ", "))])"
}
case .transformableAttributeType:
if let attributeValueClassName = attribute.attributeValueClassName {
@@ -182,11 +172,11 @@ public extension DynamicSchema {
}
if let defaultValue = attribute.defaultValue {
defaultString = ", default: /* \"\(defaultValue)\" */"
defaultString = ", initial: /* \"\(defaultValue)\" */"
}
else if !attribute.isOptional {
defaultString = ", default: /* required */"
defaultString = ", initial: /* required */"
}
default:
fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)")
@@ -194,9 +184,11 @@ public extension DynamicSchema {
let indexedString = attribute.isIndexed ? ", isIndexed: true" : ""
let transientString = attribute.isTransient ? ", isTransient: true" : ""
// TODO: escape strings
let versionHashModifierString = attribute.versionHashModifier.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
let versionHashModifierString = attribute.versionHashModifier
.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
// TODO: escape strings
let renamingIdentifierString = attribute.renamingIdentifier.flatMap({ ", renamingIdentifier: \"\($0)\"" }) ?? ""
let renamingIdentifierString = attribute.renamingIdentifier
.flatMap({ ($0 == attributeName ? "" : ", renamingIdentifier: \"\($0)\"") as String }) ?? ""
output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(indexedString)\(defaultString)\(transientString)\(versionHashModifierString)\(renamingIdentifierString))\n")
}
}
@@ -261,8 +253,10 @@ public extension DynamicSchema {
fatalError("Unsupported delete rule \((relationship.deleteRule)) for relationship \"\(relationshipQualifier)\"")
}
}
let versionHashModifierString = relationship.versionHashModifier.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
let renamingIdentifierString = relationship.renamingIdentifier.flatMap({ ", renamingIdentifier: \"\($0)\"" }) ?? ""
let versionHashModifierString = relationship.versionHashModifier
.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
let renamingIdentifierString = relationship.renamingIdentifier
.flatMap({ ($0 == relationshipName ? "" : ", renamingIdentifier: \"\($0)\"") as String }) ?? ""
output.append(" let \(relationshipName) = \(containerType)<\(relationship.destinationEntity!.name!)>(\"\(relationshipName)\"\(inverseString)\(deleteRuleString)\(minCountString)\(maxCountString)\(versionHashModifierString)\(renamingIdentifierString))\n")
}
}

View File

@@ -64,12 +64,6 @@ public protocol ImportableAttributeType: QueryableAttributeType {
*/
associatedtype ImportableNativeType: QueryableNativeType
/**
Returns the default "empty" value for this type.
*/
@inline(__always)
static func cs_emptyValue() -> Self
/**
Creates an instance of this type from its `ImportableNativeType` value.
*/
@@ -88,13 +82,9 @@ public protocol ImportableAttributeType: QueryableAttributeType {
extension Bool: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Bool {
return false
}
public typealias ImportableNativeType = NSNumber
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Bool? {
@@ -109,18 +99,13 @@ extension Bool: ImportableAttributeType {
}
}
// MARK: - CGFloat
extension CGFloat: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> CGFloat {
return 0
}
public typealias ImportableNativeType = NSNumber
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> CGFloat? {
@@ -140,13 +125,9 @@ extension CGFloat: ImportableAttributeType {
extension Data: ImportableAttributeType {
public typealias ImportableNativeType = NSData
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Data {
return Data()
}
public typealias ImportableNativeType = NSData
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Data? {
@@ -166,13 +147,9 @@ extension Data: ImportableAttributeType {
extension Date: ImportableAttributeType {
public typealias ImportableNativeType = NSDate
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Date {
return Date(timeIntervalSinceReferenceDate: 0)
}
public typealias ImportableNativeType = NSDate
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Date? {
@@ -192,13 +169,9 @@ extension Date: ImportableAttributeType {
extension Double: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Double {
return 0
}
public typealias ImportableNativeType = NSNumber
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Double? {
@@ -218,13 +191,9 @@ extension Double: ImportableAttributeType {
extension Float: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Float {
return 0
}
public typealias ImportableNativeType = NSNumber
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Float? {
@@ -244,13 +213,9 @@ extension Float: ImportableAttributeType {
extension Int: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int {
return 0
}
public typealias ImportableNativeType = NSNumber
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int? {
@@ -270,13 +235,9 @@ extension Int: ImportableAttributeType {
extension Int8: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int8 {
return 0
}
public typealias ImportableNativeType = NSNumber
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int8? {
@@ -296,13 +257,9 @@ extension Int8: ImportableAttributeType {
extension Int16: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int16 {
return 0
}
public typealias ImportableNativeType = NSNumber
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int16? {
@@ -322,13 +279,9 @@ extension Int16: ImportableAttributeType {
extension Int32: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int32 {
return 0
}
public typealias ImportableNativeType = NSNumber
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int32? {
@@ -348,13 +301,9 @@ extension Int32: ImportableAttributeType {
extension Int64: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> Int64 {
return 0
}
public typealias ImportableNativeType = NSNumber
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Int64? {
@@ -374,13 +323,9 @@ extension Int64: ImportableAttributeType {
extension NSData: ImportableAttributeType {
public typealias ImportableNativeType = NSData
// MARK: ImportableAttributeType
@nonobjc @inline(__always)
public class func cs_emptyValue() -> Self {
return self.init()
}
public typealias ImportableNativeType = NSData
@nonobjc @inline(__always)
public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? {
@@ -400,13 +345,9 @@ extension NSData: ImportableAttributeType {
extension NSDate: ImportableAttributeType {
public typealias ImportableNativeType = NSDate
// MARK: ImportableAttributeType
@nonobjc @inline(__always)
public class func cs_emptyValue() -> Self {
return self.init(timeIntervalSinceReferenceDate: 0)
}
public typealias ImportableNativeType = NSDate
@nonobjc @inline(__always)
public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? {
@@ -426,13 +367,9 @@ extension NSDate: ImportableAttributeType {
extension NSNumber: ImportableAttributeType {
public typealias ImportableNativeType = NSNumber
// MARK: ImportableAttributeType
@nonobjc @inline(__always)
public class func cs_emptyValue() -> Self {
return self.init()
}
public typealias ImportableNativeType = NSNumber
@nonobjc @inline(__always)
public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? {
@@ -452,13 +389,9 @@ extension NSNumber: ImportableAttributeType {
extension NSString: ImportableAttributeType {
public typealias ImportableNativeType = NSString
// MARK: ImportableAttributeType
@nonobjc @inline(__always)
public class func cs_emptyValue() -> Self {
return self.init()
}
public typealias ImportableNativeType = NSString
@nonobjc @inline(__always)
public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? {
@@ -478,13 +411,9 @@ extension NSString: ImportableAttributeType {
extension NSURL: ImportableAttributeType {
public typealias ImportableNativeType = NSString
// MARK: ImportableAttributeType
@nonobjc @inline(__always)
public class func cs_emptyValue() -> Self {
return self.init(string: "")!
}
public typealias ImportableNativeType = NSString
@nonobjc @inline(__always)
public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? {
@@ -504,16 +433,9 @@ extension NSURL: ImportableAttributeType {
extension NSUUID: ImportableAttributeType {
public typealias ImportableNativeType = NSString
// MARK: ImportableAttributeType
public class func cs_emptyValue() -> Self {
enum Static {
static var zero = Array<UInt8>(repeating: 0, count: 16)
}
return self.init(uuidBytes: &Static.zero)
}
public typealias ImportableNativeType = NSString
@nonobjc @inline(__always)
public class func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? {
@@ -533,13 +455,9 @@ extension NSUUID: ImportableAttributeType {
extension String: ImportableAttributeType {
public typealias ImportableNativeType = NSString
// MARK: ImportableAttributeType
@inline(__always)
public static func cs_emptyValue() -> String {
return ""
}
public typealias ImportableNativeType = NSString
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> String? {
@@ -559,16 +477,9 @@ extension String: ImportableAttributeType {
extension URL: ImportableAttributeType {
public typealias ImportableNativeType = NSString
// MARK: ImportableAttributeType
public static func cs_emptyValue() -> URL {
enum Static {
static let empty = URL(string: "")!
}
return Static.empty
}
public typealias ImportableNativeType = NSString
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> URL? {
@@ -588,20 +499,9 @@ extension URL: ImportableAttributeType {
extension UUID: ImportableAttributeType {
public typealias ImportableNativeType = NSString
// MARK: ImportableAttributeType
public static func cs_emptyValue() -> UUID {
enum Static {
static let empty: UUID = cs_lazy {
var zero = Array<UInt8>(repeating: 0, count: 16)
return NSUUID(uuidBytes: &zero) as UUID
}
}
return Static.empty
}
public typealias ImportableNativeType = NSString
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> UUID? {
@@ -623,11 +523,6 @@ extension RawRepresentable where RawValue: ImportableAttributeType {
public typealias ImportableNativeType = RawValue.ImportableNativeType
public static func cs_emptyValue() -> Self {
return self.init(rawValue: RawValue.cs_emptyValue())!
}
@inline(__always)
public static func cs_fromImportableNativeType(_ value: ImportableNativeType) -> Self? {

View File

@@ -32,7 +32,7 @@ import Foundation
/**
A `SchemaMappingProvider` that tries to infer model migration between two `DynamicSchema` versions by searching all `xcmappingmodel`s from `Bundle.allBundles` or by relying on lightweight migration if possible. Throws an error if lightweight migration is impossible for the two `DynamicSchema`. This mapping is automatically used as a fallback mapping provider, even if no mapping providers are explicitly declared in the `StorageInterface`.
*/
final class InferredSchemaMappingProvider: Hashable, SchemaMappingProvider {
public final class InferredSchemaMappingProvider: Hashable, SchemaMappingProvider {
// MARK: Equatable

View File

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

View File

@@ -86,6 +86,11 @@ public final class LegacySQLiteStore: LocalStorage {
public var localStorageOptions: LocalStorageOptions
public func cs_finalizeStorageAndWait(soureModelHint: NSManagedObjectModel) throws {
fatalError()
}
public func cs_eraseStorageAndWait(metadata: [String: Any], soureModelHint: NSManagedObjectModel?) throws {
fatalError()

View File

@@ -704,13 +704,13 @@ public final class ListMonitor<D: DynamicObject>: Hashable {
guard let `self` = self,
let userInfo = note.userInfo,
let object = userInfo[String(describing: NSManagedObject.self)] as? ObjectType else {
let rawObject = userInfo[String(describing: NSManagedObject.self)] as? NSManagedObject else {
return
}
callback(
self,
object,
ObjectType.cs_fromRaw(object: rawObject),
userInfo[String(describing: IndexPath.self)] as? IndexPath,
userInfo["\(String(describing: IndexPath.self)).New"] as? IndexPath
)

View File

@@ -54,17 +54,65 @@ internal extension NSEntityDescription {
if let newValue = newValue {
var userInfo: [AnyHashable : Any] = [
UserInfoKey.CoreStoreManagedObjectTypeName: NSStringFromClass(newValue.type),
UserInfoKey.CoreStoreManagedObjectEntityName: newValue.entityName,
UserInfoKey.CoreStoreManagedObjectIsAbstract: newValue.isAbstract
]
userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] = newValue.versionHashModifier
self.userInfo = userInfo
cs_setUserInfo { (userInfo) in
userInfo[UserInfoKey.CoreStoreManagedObjectTypeName] = NSStringFromClass(newValue.type)
userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] = newValue.entityName
userInfo[UserInfoKey.CoreStoreManagedObjectIsAbstract] = newValue.isAbstract
userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] = newValue.versionHashModifier
}
}
else {
self.userInfo = [:]
cs_setUserInfo { (userInfo) in
userInfo[UserInfoKey.CoreStoreManagedObjectTypeName] = nil
userInfo[UserInfoKey.CoreStoreManagedObjectEntityName] = nil
userInfo[UserInfoKey.CoreStoreManagedObjectIsAbstract] = nil
userInfo[UserInfoKey.CoreStoreManagedObjectVersionHashModifier] = nil
}
}
}
}
@nonobjc
internal var keyPathsByAffectedKeyPaths: [KeyPath: Set<KeyPath>] {
get {
if let userInfo = self.userInfo,
let value = userInfo[UserInfoKey.CoreStoreManagedObjectKeyPathsByAffectedKeyPaths] {
return value as! [KeyPath: Set<KeyPath>]
}
return [:]
}
set {
cs_setUserInfo { (userInfo) in
userInfo[UserInfoKey.CoreStoreManagedObjectKeyPathsByAffectedKeyPaths] = newValue
}
}
}
@nonobjc
internal var customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter] {
get {
if let userInfo = self.userInfo,
let value = userInfo[UserInfoKey.CoreStoreManagedObjectCustomGetterSetterByKeyPaths] {
return value as! [KeyPath: CoreStoreManagedObject.CustomGetterSetter]
}
return [:]
}
set {
cs_setUserInfo { (userInfo) in
userInfo[UserInfoKey.CoreStoreManagedObjectCustomGetterSetterByKeyPaths] = newValue
}
}
}
@@ -74,11 +122,22 @@ internal extension NSEntityDescription {
// MARK: - UserInfoKey
fileprivate enum UserInfoKey {
private enum UserInfoKey {
fileprivate static let CoreStoreManagedObjectTypeName = "CoreStoreManagedObjectTypeName"
fileprivate static let CoreStoreManagedObjectEntityName = "CoreStoreManagedObjectEntityName"
fileprivate static let CoreStoreManagedObjectIsAbstract = "CoreStoreManagedObjectIsAbstract"
fileprivate static let CoreStoreManagedObjectVersionHashModifier = "CoreStoreManagedObjectVersionHashModifier"
fileprivate static let CoreStoreManagedObjectKeyPathsByAffectedKeyPaths = "CoreStoreManagedObjectKeyPathsByAffectedKeyPaths"
fileprivate static let CoreStoreManagedObjectCustomGetterSetterByKeyPaths = "CoreStoreManagedObjectCustomGetterSetterByKeyPaths"
}
private func cs_setUserInfo(_ closure: (_ userInfo: inout [AnyHashable: Any]) -> Void) {
var userInfo = self.userInfo ?? [:]
closure(&userInfo)
self.userInfo = userInfo
}
}

View File

@@ -201,7 +201,7 @@ public extension UnsafeDataTransaction {
// MARK: - Private
@available(OSX 10.12, *)
fileprivate func createFRC<T: NSManagedObject>(fromContext context: NSManagedObjectContext, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController<T> {
fileprivate func createFRC<T: NSManagedObject>(fromContext context: NSManagedObjectContext, from: From<T>, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController<T> {
let controller = CoreStoreFetchedResultsController(
context: context,

View File

@@ -68,12 +68,12 @@ public extension CSUnsafeDataTransaction {
// MARK: - Private
@available(OSX 10.12, *)
fileprivate func createFRC(fromContext context: NSManagedObjectContext, from: CSFrom? = nil, sectionBy: CSSectionBy?, fetchClauses: [CSFetchClause]) -> NSFetchedResultsController<NSManagedObject> {
fileprivate func createFRC(fromContext context: NSManagedObjectContext, from: CSFrom, sectionBy: CSSectionBy?, fetchClauses: [CSFetchClause]) -> NSFetchedResultsController<NSManagedObject> {
let controller = CoreStoreFetchedResultsController(
context: context,
fetchRequest: CoreStoreFetchRequest().dynamicCast(),
from: from?.bridgeToSwift,
from: from.bridgeToSwift,
sectionBy: sectionBy?.bridgeToSwift,
applyFetchClauses: { (fetchRequest) in

View File

@@ -62,7 +62,7 @@ extension NSManagedObjectContext: FetchableSource, QueryableSource {
return object
}
return T.cs_fromRaw(object: existingRawObject)
return type(of: object).cs_fromRaw(object: existingRawObject)
}
catch {

View File

@@ -272,7 +272,7 @@ public final class ObjectMonitor<D: DynamicObject>: Equatable {
let fetchedResultsController = CoreStoreFetchedResultsController(
context: context,
fetchRequest: fetchRequest.dynamicCast(),
from: nil as From<ObjectType>?,
from: From<ObjectType>([objectID.persistentStore?.configurationName]),
applyFetchClauses: Where("SELF", isEqualTo: objectID).applyToFetchRequest
)

View File

@@ -172,3 +172,17 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause, Hashable {
return (self.sortDescriptors as NSArray).hashValue
}
}
// MARK: - Sequence where Element == OrderBy
public extension Sequence where Iterator.Element == OrderBy {
/**
Combines multiple `OrderBy` predicates together
*/
public func combined() -> OrderBy {
return OrderBy(self.flatMap({ $0.sortDescriptors }))
}
}

173
Sources/PartialObject.swift Normal file
View File

@@ -0,0 +1,173 @@
//
// PartialObject.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - PartialObject
/**
A `PartialObject` is only used when overriding getters and setters for `CoreStoreObject` properties. Custom getters and setters are implemented as a closure that "overrides" the default property getter/setter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a heavy performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.persistentValue(for:)` instead of `PartialObject<O>.value(for:)`, which would unintentionally execute the same closure again recursively.
*/
public struct PartialObject<O: CoreStoreObject> {
public func completeObject() -> O {
return O.cs_fromRaw(object: self.rawObject)
}
// MARK: Value.Required accessors/mutators
public func value<V: ImportableAttributeType>(for property: (O) -> ValueContainer<O>.Required<V>) -> V {
return V.cs_fromImportableNativeType(
self.rawObject.value(forKey: property(O.meta).keyPath)! as! V.ImportableNativeType
)!
}
public func setValue<V: ImportableAttributeType>(_ value: V, for property: (O) -> ValueContainer<O>.Required<V>) {
self.rawObject.setValue(
value.cs_toImportableNativeType(),
forKey: property(O.meta).keyPath
)
}
public func primitiveValue<V: ImportableAttributeType>(for property: (O) -> ValueContainer<O>.Required<V>) -> V {
return V.cs_fromImportableNativeType(
self.rawObject.primitiveValue(forKey: property(O.meta).keyPath)! as! V.ImportableNativeType
)!
}
public func setPrimitiveValue<V: ImportableAttributeType>(_ value: V, for property: (O) -> ValueContainer<O>.Required<V>) {
self.rawObject.setPrimitiveValue(
value.cs_toImportableNativeType(),
forKey: property(O.meta).keyPath
)
}
// MARK: Value.Optional utilities
public func value<V: ImportableAttributeType>(for property: (O) -> ValueContainer<O>.Optional<V>) -> V? {
return (self.rawObject.value(forKey: property(O.meta).keyPath) as! V.ImportableNativeType?)
.flatMap(V.cs_fromImportableNativeType)
}
public func setValue<V: ImportableAttributeType>(_ value: V?, for property: (O) -> ValueContainer<O>.Optional<V>) {
self.rawObject.setValue(
value?.cs_toImportableNativeType(),
forKey: property(O.meta).keyPath
)
}
public func primitiveValue<V: ImportableAttributeType>(for property: (O) -> ValueContainer<O>.Optional<V>) -> V? {
return (self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V.ImportableNativeType?)
.flatMap(V.cs_fromImportableNativeType)
}
public func setPrimitiveValue<V: ImportableAttributeType>(_ value: V?, for property: (O) -> ValueContainer<O>.Optional<V>) {
self.rawObject.setPrimitiveValue(
value?.cs_toImportableNativeType(),
forKey: property(O.meta).keyPath
)
}
// MARK: Transformable.Required utilities
public func value<V: NSCoding & NSCopying>(for property: (O) -> TransformableContainer<O>.Required<V>) -> V {
return self.rawObject.value(forKey: property(O.meta).keyPath)! as! V
}
public func setValue<V: NSCoding & NSCopying>(_ value: V, for property: (O) -> TransformableContainer<O>.Required<V>) {
self.rawObject.setValue(
value,
forKey: property(O.meta).keyPath
)
}
public func primitiveValue<V: NSCoding & NSCopying>(for property: (O) -> TransformableContainer<O>.Required<V>) -> V {
return self.rawObject.primitiveValue(forKey: property(O.meta).keyPath)! as! V
}
public func setPrimitiveValue<V: NSCoding & NSCopying>(_ value: V, for property: (O) -> TransformableContainer<O>.Required<V>) {
self.rawObject.setPrimitiveValue(
value,
forKey: property(O.meta).keyPath
)
}
// MARK: Transformable.Optional utilities
public func value<V: NSCoding & NSCopying>(for property: (O) -> TransformableContainer<O>.Optional<V>) -> V? {
return self.rawObject.value(forKey: property(O.meta).keyPath) as! V?
}
public func setValue<V: NSCoding & NSCopying>(_ value: V?, for property: (O) -> TransformableContainer<O>.Optional<V>) {
self.rawObject.setValue(
value,
forKey: property(O.meta).keyPath
)
}
public func primitiveValue<V: NSCoding & NSCopying>(for property: (O) -> TransformableContainer<O>.Optional<V>) -> V? {
return self.rawObject.primitiveValue(forKey: property(O.meta).keyPath) as! V?
}
public func setPrimitiveValue<V: NSCoding & NSCopying>(_ value: V?, for property: (O) -> TransformableContainer<O>.Optional<V>) {
self.rawObject.setPrimitiveValue(
value,
forKey: property(O.meta).keyPath
)
}
// MARK: Internal
internal let rawObject: NSManagedObject
internal init(_ rawObject: NSManagedObject) {
self.rawObject = rawObject
}
}

View File

@@ -92,10 +92,23 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
inverseKeyPath: { nil },
deleteRule: deleteRule,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -113,10 +126,24 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
inverseKeyPath: { inverse(D.meta).keyPath },
deleteRule: deleteRule,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -134,10 +161,24 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
inverseKeyPath: { inverse(D.meta).keyPath },
deleteRule: deleteRule,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -155,10 +196,24 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
inverseKeyPath: { inverse(D.meta).keyPath },
deleteRule: deleteRule,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -187,56 +242,66 @@ public enum RelationshipContainer<O: CoreStoreObject> {
internal let minCount: Int = 0
internal let maxCount: Int = 1
internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?)
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal var nativeValue: NSManagedObject? {
get {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { $0 as! NSManagedObject? }
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { $0 as! NSManagedObject? }
)
}
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
)
}
}
}
// MARK: Private
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?) {
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
self.keyPath = keyPath
self.deleteRule = deleteRule.nativeValue
self.inverse = (D.self, inverseKeyPath)
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.affectedByKeyPaths = affectedByKeyPaths
}
}
@@ -273,10 +338,27 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, minCount: Int = 0, maxCount: Int = 0, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
minCount: Int = 0,
maxCount: Int = 0,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, minCount: minCount, maxCount: maxCount, inverseKeyPath: { nil }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
minCount: minCount,
maxCount: maxCount,
inverseKeyPath: { nil },
deleteRule: deleteRule,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -296,10 +378,28 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, minCount: Int = 0, maxCount: Int = 0, inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
minCount: Int = 0,
maxCount: Int = 0,
inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, minCount: minCount, maxCount: maxCount, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
minCount: minCount,
maxCount: maxCount,
inverseKeyPath: { inverse(D.meta).keyPath },
deleteRule: deleteRule,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -319,10 +419,28 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, minCount: Int = 0, maxCount: Int = 0, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
minCount: Int = 0,
maxCount: Int = 0,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, minCount: minCount, maxCount: maxCount, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
minCount: minCount,
maxCount: maxCount,
inverseKeyPath: { inverse(D.meta).keyPath },
deleteRule: deleteRule,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -342,10 +460,28 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, minCount: Int = 0, maxCount: Int = 0, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>, deleteRule: DeleteRule = .nullify, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
minCount: Int = 0,
maxCount: Int = 0,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>,
deleteRule: DeleteRule = .nullify,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, minCount: minCount, maxCount: maxCount, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
minCount: minCount,
maxCount: maxCount,
inverseKeyPath: { inverse(D.meta).keyPath },
deleteRule: deleteRule,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -375,50 +511,59 @@ public enum RelationshipContainer<O: CoreStoreObject> {
internal let minCount: Int
internal let maxCount: Int
internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?)
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal var nativeValue: NSOrderedSet {
get {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { ($0 as! NSOrderedSet?) ?? [] }
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { ($0 as! NSOrderedSet?) ?? [] }
)
}
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
)
}
}
}
// MARK: Private
private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: String?, renamingIdentifier: String?) {
private init(keyPath: String, minCount: Int, maxCount: Int, inverseKeyPath: @escaping () -> String?, deleteRule: DeleteRule, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
self.keyPath = keyPath
self.deleteRule = deleteRule.nativeValue
@@ -429,6 +574,7 @@ public enum RelationshipContainer<O: CoreStoreObject> {
let range = (Swift.max(0, minCount) ... maxCount)
self.minCount = range.lowerBound
self.maxCount = range.upperBound
self.affectedByKeyPaths = affectedByKeyPaths
}
}
@@ -466,10 +612,27 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
deleteRule: DeleteRule = .nullify,
minCount: Int = 0,
maxCount: Int = 0,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, inverseKeyPath: { nil }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
inverseKeyPath: { nil },
deleteRule: deleteRule,
minCount: minCount,
maxCount: maxCount,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -489,10 +652,28 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
inverse: @escaping (D) -> RelationshipContainer<D>.ToOne<O>,
deleteRule: DeleteRule = .nullify,
minCount: Int = 0,
maxCount: Int = 0,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
inverseKeyPath: { inverse(D.meta).keyPath },
deleteRule: deleteRule,
minCount: minCount,
maxCount: maxCount,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -512,10 +693,28 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyOrdered<O>,
deleteRule: DeleteRule = .nullify,
minCount: Int = 0,
maxCount: Int = 0,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
inverseKeyPath: { inverse(D.meta).keyPath },
deleteRule: deleteRule,
minCount: minCount,
maxCount: maxCount,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -535,10 +734,28 @@ public enum RelationshipContainer<O: CoreStoreObject> {
- parameter deleteRule: defines what happens to relationship when an object is deleted. Valid values are `.nullify`, `.cascade`, and `.delete`. Defaults to `.nullify`.
- parameter versionHashModifier: used to mark or denote a relationship as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public convenience init(_ keyPath: KeyPath, inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>, deleteRule: DeleteRule = .nullify, minCount: Int = 0, maxCount: Int = 0, versionHashModifier: String? = nil, renamingIdentifier: String? = nil) {
public convenience init(
_ keyPath: KeyPath,
inverse: @escaping (D) -> RelationshipContainer<D>.ToManyUnordered<O>,
deleteRule: DeleteRule = .nullify,
minCount: Int = 0,
maxCount: Int = 0,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(keyPath: keyPath, inverseKeyPath: { inverse(D.meta).keyPath }, deleteRule: deleteRule, minCount: minCount, maxCount: maxCount, versionHashModifier: versionHashModifier, renamingIdentifier: renamingIdentifier)
self.init(
keyPath: keyPath,
inverseKeyPath: { inverse(D.meta).keyPath },
deleteRule: deleteRule,
minCount: minCount,
maxCount: maxCount,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
@@ -568,50 +785,59 @@ public enum RelationshipContainer<O: CoreStoreObject> {
internal let minCount: Int
internal let maxCount: Int
internal let inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?)
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal var nativeValue: NSSet {
get {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { ($0 as! NSSet?) ?? [] }
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { ($0 as! NSSet?) ?? [] }
)
}
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
)
}
}
}
// MARK: Private
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: String?, renamingIdentifier: String?) {
private init(keyPath: KeyPath, inverseKeyPath: @escaping () -> KeyPath?, deleteRule: DeleteRule, minCount: Int, maxCount: Int, versionHashModifier: @autoclosure @escaping () -> String?, renamingIdentifier: @autoclosure @escaping () -> String?, affectedByKeyPaths: @autoclosure @escaping () -> Set<String>) {
self.keyPath = keyPath
self.deleteRule = deleteRule.nativeValue
@@ -622,6 +848,7 @@ public enum RelationshipContainer<O: CoreStoreObject> {
let range = (Swift.max(0, minCount) ... maxCount)
self.minCount = range.lowerBound
self.maxCount = range.upperBound
self.affectedByKeyPaths = affectedByKeyPaths
}
}
@@ -658,7 +885,7 @@ extension RelationshipContainer.ToManyOrdered: RandomAccessCollection {
public func makeIterator() -> Iterator {
let iterator = self.nativeValue.makeIterator()
return AnyIterator({ D.cs_fromRaw(object: iterator.next() as! NSManagedObject) })
return AnyIterator({ iterator.next().flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) })
}
@@ -713,7 +940,7 @@ extension RelationshipContainer.ToManyUnordered: Sequence {
public func makeIterator() -> Iterator {
let iterator = self.nativeValue.makeIterator()
return AnyIterator({ D.cs_fromRaw(object: iterator.next() as! NSManagedObject) })
return AnyIterator({ iterator.next().flatMap({ D.cs_fromRaw(object: $0 as! NSManagedObject) }) })
}
}
@@ -977,20 +1204,3 @@ extension RelationshipContainer.ToManyUnordered {
return relationship.nativeValue.isEqual(relationship2.nativeValue)
}
}
// MARK: - RelationshipProtocol
internal protocol RelationshipProtocol: class {
var keyPath: KeyPath { get }
var isToMany: Bool { get }
var isOrdered: Bool { get }
var deleteRule: NSDeleteRule { get }
var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get }
var parentObject: () -> CoreStoreObject { get set }
var versionHashModifier: String? { get }
var renamingIdentifier: String? { get }
var minCount: Int { get }
var maxCount: Int { get }
}

View File

@@ -0,0 +1,45 @@
//
// RelationshipProtocol.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - RelationshipProtocol
internal protocol RelationshipProtocol: class {
var keyPath: KeyPath { get }
var isToMany: Bool { get }
var isOrdered: Bool { get }
var deleteRule: NSDeleteRule { get }
var inverse: (type: CoreStoreObject.Type, keyPath: () -> KeyPath?) { get }
var affectedByKeyPaths: () -> Set<String> { get }
weak var parentObject: CoreStoreObject? { get set }
var versionHashModifier: () -> String? { get }
var renamingIdentifier: () -> String? { get }
var minCount: Int { get }
var maxCount: Int { get }
}

View File

@@ -117,6 +117,20 @@ public final class SQLiteStore: LocalStorage {
)
}
/**
Queries the file size (in bytes) of the store, or `nil` if the file does not exist yet
*/
public func fileSize() -> UInt64? {
guard let attribute = try? FileManager.default.attributesOfItem(atPath: self.fileURL.path),
let sizeAttribute = attribute[.size],
let fileSize = sizeAttribute as? NSNumber else {
return nil
}
return fileSize.uint64Value
}
// MARK: StorageInterface
@@ -191,6 +205,22 @@ public final class SQLiteStore: LocalStorage {
return storeOptions
}
/**
Called by the `DataStack` to perform checkpoint operations on the storage. For `SQLiteStore`, this converts the database's WAL journaling mode to DELETE to force a checkpoint.
*/
public func cs_finalizeStorageAndWait(soureModelHint: NSManagedObjectModel) throws {
_ = try withExtendedLifetime(NSPersistentStoreCoordinator(managedObjectModel: soureModelHint)) { (coordinator: NSPersistentStoreCoordinator) in
try coordinator.addPersistentStore(
ofType: type(of: self).storeType,
configurationName: self.configuration,
at: fileURL,
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
)
}
}
/**
Called by the `DataStack` to perform actual deletion of the store file from disk. Do not call directly! The `sourceModel` argument is a hint for the existing store's model version. For `SQLiteStore`, this converts the database's WAL journaling mode to DELETE before deleting the file.
*/

View File

@@ -401,13 +401,14 @@ internal extension Collection where Iterator.Element == SelectTerm {
return entity.attributesByName[components[0]]
default:
guard let relationship = entity.relationshipsByName[components[0]] else {
return nil
guard let relationship = entity.relationshipsByName[components[0]],
let destinationEntity = relationship.destinationEntity else {
return nil
}
return attributeDescription(
for: components.dropFirst().joined(separator: "."),
in: relationship.entity
in: destinationEntity
)
}
}
@@ -418,18 +419,7 @@ internal extension Collection where Iterator.Element == SelectTerm {
switch term {
case ._attribute(let keyPath):
let entityDescription = fetchRequest.entity!
if let attributeDescription = attributeDescription(for: keyPath, in: entityDescription) {
propertiesToFetch.append(attributeDescription)
}
else {
CoreStore.log(
.warning,
message: "The key path \"\(keyPath)\" could not be resolved in entity \(cs_typeName(entityDescription.managedObjectClassName)) as an attribute and will be ignored by \(cs_typeName(owner)) query clause."
)
}
propertiesToFetch.append(keyPath)
case ._aggregate(let function, let keyPath, let alias, let nativeType):
let entityDescription = fetchRequest.entity!

View File

@@ -141,6 +141,11 @@ public protocol LocalStorage: StorageInterface {
*/
func dictionary(forOptions options: LocalStorageOptions) -> [AnyHashable: Any]?
/**
Called by the `DataStack` to perform checkpoint operations on the storage. (SQLite stores for example, can convert the database's WAL journaling mode to DELETE to force a checkpoint)
*/
func cs_finalizeStorageAndWait(soureModelHint: NSManagedObjectModel) throws
/**
Called by the `DataStack` to perform actual deletion of the store file from disk. **Do not call directly!** The `sourceModel` argument is a hint for the existing store's model version. Implementers can use the `sourceModel` to perform necessary store operations. (SQLite stores for example, can convert WAL journaling mode to DELETE before deleting)
*/

View File

@@ -158,6 +158,10 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
self.isCommitted = true
let result = self.context.saveSynchronously(waitForMerge: waitForMerge)
self.result = result
defer {
self.context.reset()
}
return result
}

597
Sources/Transformable.swift Normal file
View File

@@ -0,0 +1,597 @@
//
// Transformable.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - DynamicObject
public extension DynamicObject where Self: CoreStoreObject {
/**
The containing type for transformable properties. `Transformable` properties support types that conforms to `NSCoding & NSCopying`.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public typealias Transformable = TransformableContainer<Self>
}
// MARK: - TransformableContainer
/**
The containing type for transformable properties. Use the `DynamicObject.Transformable` typealias instead for shorter syntax.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
*/
public enum TransformableContainer<O: CoreStoreObject> {
// MARK: - Required
/**
The containing type for transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public final class Required<V: NSCoding & NSCopying>: AttributeProtocol {
/**
Initializes the metadata for the property.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let color = Transformable.Required<UIColor>(
"color",
initial: UIColor.clear,
isTransient: true,
customGetter: Animal.getColor(_:)
)
}
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor {
let cachedColor = partialObject.primitiveValue(for: { $0.color })
if cachedColor != UIColor.clear {
return cachedColor
}
let color: UIColor
switch partialObject.value(for: { $0.species }) {
case "Swift": color = UIColor.orange
case "Bulbasaur": color = UIColor.green
default: color = UIColor.black
}
partialObject.setPrimitiveValue(color, for: { $0.color })
return color
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
- parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified.
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.value(for:)`, which would unintentionally execute the same closure again recursively.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public init(
_ keyPath: KeyPath,
initial: @autoclosure @escaping () -> V,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.keyPath = keyPath
self.defaultValue = initial
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
self.affectedByKeyPaths = affectedByKeyPaths
}
/**
The property value.
*/
public var value: V {
get {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
if let customGetter = self.customGetter {
return customGetter(PartialObject<O>(object.rawObject!))
}
return object.rawObject!.value(forKey: self.keyPath)! as! V
}
}
set {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
if let customSetter = self.customSetter {
return customSetter(PartialObject<O>(object.rawObject!), newValue)
}
object.rawObject!.setValue(
newValue,
forKey: self.keyPath
)
}
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = false
internal let isIndexed: Bool
internal let isTransient: Bool
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in
guard let customGetter = self.customGetter else {
return nil
}
let keyPath = self.keyPath
return { (_ id: Any) -> Any? in
let rawObject = id as! CoreStoreManagedObject
rawObject.willAccessValue(forKey: keyPath)
defer {
rawObject.didAccessValue(forKey: keyPath)
}
let value = customGetter(PartialObject<O>(rawObject))
return value
}
}
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in
guard let customSetter = self.customSetter else {
return nil
}
let keyPath = self.keyPath
return { (_ id: Any, _ newValue: Any?) -> Void in
let rawObject = id as! CoreStoreManagedObject
rawObject.willChangeValue(forKey: keyPath)
defer {
rawObject.didChangeValue(forKey: keyPath)
}
customSetter(
PartialObject<O>(rawObject),
newValue as! V
)
}
}
// MARK: Private
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
// MARK: Deprecated
@available(*, deprecated: 3.1, renamed: "init(_:initial:isIndexed:isTransient:versionHashModifier:renamingIdentifier:customGetter:customSetter:affectedByKeyPaths:)")
public convenience init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
keyPath,
initial: `default`,
isIndexed: isIndexed,
isTransient: isTransient,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
customGetter: customGetter,
customSetter: customSetter,
affectedByKeyPaths: affectedByKeyPaths
)
}
}
// MARK: - Optional
/**
The containing type for optional transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public final class Optional<V: NSCoding & NSCopying>: AttributeProtocol {
/**
Initializes the metadata for the property.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let color = Transformable.Optional<UIColor>(
"color",
isTransient: true,
customGetter: Animal.getColor(_:)
)
}
private static func getColor(_ partialObject: PartialObject<Animal>) -> UIColor? {
if let cachedColor = partialObject.primitiveValue(for: { $0.color }) {
return cachedColor
}
let color: UIColor?
switch partialObject.value(for: { $0.species }) {
case "Swift": color = UIColor.orange
case "Bulbasaur": color = UIColor.green
default: return nil
}
partialObject.setPrimitiveValue(color, for: { $0.color })
return color
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter initial: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
- parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified.
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.value(for:)`, which would unintentionally execute the same closure again recursively.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public init(
_ keyPath: KeyPath,
initial: @autoclosure @escaping () -> V? = nil,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V?)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.keyPath = keyPath
self.defaultValue = initial
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
self.affectedByKeyPaths = affectedByKeyPaths
}
/**
The property value.
*/
public var value: V? {
get {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
if let customGetter = self.customGetter {
return customGetter(PartialObject<O>(object.rawObject!))
}
return object.rawObject!.value(forKey: self.keyPath) as! V?
}
}
set {
CoreStore.assert(
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
if let customSetter = self.customSetter {
return customSetter(PartialObject<O>(object.rawObject!), newValue)
}
object.rawObject!.setValue(
newValue,
forKey: self.keyPath
)
}
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = true
internal let isIndexed: Bool
internal let isTransient: Bool
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in
guard let customGetter = self.customGetter else {
return nil
}
let keyPath = self.keyPath
return { (_ id: Any) -> Any? in
let rawObject = id as! CoreStoreManagedObject
rawObject.willAccessValue(forKey: keyPath)
defer {
rawObject.didAccessValue(forKey: keyPath)
}
let value = customGetter(PartialObject<O>(rawObject))
return value
}
}
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in
guard let customSetter = self.customSetter else {
return nil
}
let keyPath = self.keyPath
return { (_ id: Any, _ newValue: Any?) -> Void in
let rawObject = id as! CoreStoreManagedObject
rawObject.willChangeValue(forKey: keyPath)
defer {
rawObject.didChangeValue(forKey: keyPath)
}
customSetter(
PartialObject<O>(rawObject),
newValue as! V?
)
}
}
// MARK: Private
private let customGetter: ((_ partialObject: PartialObject<O>) -> V?)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)?
// MARK: Deprecated
@available(*, deprecated: 3.1, renamed: "init(_:initial:isIndexed:isTransient:versionHashModifier:renamingIdentifier:customGetter:customSetter:affectedByKeyPaths:)")
public convenience init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V?,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V?)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
keyPath,
initial: `default`,
isIndexed: isIndexed,
isTransient: isTransient,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
customGetter: customGetter,
customSetter: customSetter,
affectedByKeyPaths: affectedByKeyPaths
)
}
}
}
// MARK: - Operations
infix operator .= : AssignmentPrecedence
infix operator .== : ComparisonPrecedence
extension TransformableContainer.Required {
/**
Assigns a transformable value to the property. The operation
```
animal.color .= UIColor.red
```
is equivalent to
```
animal.color.value = UIColor.red
```
*/
public static func .= (_ property: TransformableContainer<O>.Required<V>, _ newValue: V) {
property.value = newValue
}
/**
Assigns a transformable value from another property. The operation
```
animal.nickname .= anotherAnimal.species
```
is equivalent to
```
animal.nickname.value = anotherAnimal.species.value
```
*/
public static func .= <O2>(_ property: TransformableContainer<O>.Required<V>, _ property2: TransformableContainer<O2>.Required<V>) {
property.value = property2.value
}
}
extension TransformableContainer.Optional {
/**
Assigns an optional transformable value to the property. The operation
```
animal.color .= UIColor.red
```
is equivalent to
```
animal.color.value = UIColor.red
```
*/
public static func .= (_ property: TransformableContainer<O>.Optional<V>, _ newValue: V?) {
property.value = newValue
}
/**
Assigns an optional transformable value from another property. The operation
```
animal.color .= anotherAnimal.color
```
is equivalent to
```
animal.color.value = anotherAnimal.color.value
```
*/
public static func .= <O2>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Optional<V>) {
property.value = property2.value
}
/**
Assigns a transformable value from another property. The operation
```
animal.color .= anotherAnimal.color
```
is equivalent to
```
animal.color.value = anotherAnimal.color.value
```
*/
public static func .= <O2>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Required<V>) {
property.value = property2.value
}
}

View File

@@ -43,19 +43,6 @@ public extension DynamicObject where Self: CoreStoreObject {
- Important: `Value` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public typealias Value = ValueContainer<Self>
/**
The containing type for transformable properties. `Transformable` properties support types that conforms to `NSCoding & NSCopying`.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public typealias Transformable = TransformableContainer<Self>
}
@@ -92,39 +79,57 @@ public enum ValueContainer<O: CoreStoreObject> {
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
let title = Value.Required<String>("title", default: "Mr.")
let name = Value.Required<String>(
"name",
customGetter: { (`self`, getValue) in
return "\(self.title.value) \(getValue())"
}
let title = Value.Required<String>("title", initial: "Mr.")
let name = Value.Required<String>("name")
let displayName = Value.Required<String>(
"displayName",
isTransient: true,
customGetter: Person.getName(_:)
)
private static func getName(_ partialObject: PartialObject<Person>) -> String {
let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName })
if !cachedDisplayName.isEmpty {
return cachedDisplayName
}
let title = partialObject.value(for: { $0.title })
let name = partialObject.value(for: { $0.name })
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
return displayName
}
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter default: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
- parameter initial: the initial value for the property when the object is first created
- parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified.
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter.
- parameter self: the `CoreStoreObject`
- parameter getValue: the original getter for the property
- parameter customSetter: use this closure to make final transformations to the new value before assigning to the property.
- parameter setValue: the original setter for the property
- parameter finalNewValue: the transformed new value
- parameter originalNewValue: the original new value
- parameter customGetter: use this closure as an "override" for the default property getter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.primitiveValue(for:)` instead of `PartialObject<O>.value(for:)`, which would unintentionally execute the same closure again recursively.
- parameter customSetter: use this closure as an "override" for the default property setter. The closure receives a `PartialObject<O>`, which acts as a fast, type-safe KVC interface for `CoreStoreObject`. The reason a `CoreStoreObject` instance is not passed directly is because the Core Data runtime is not aware of `CoreStoreObject` properties' static typing, and so loading those info everytime KVO invokes this accessor method incurs a cumulative performance hit (especially in KVO-heavy operations such as `ListMonitor` observing.) When accessing the property value from `PartialObject<O>`, make sure to use `PartialObject<O>.setPrimitiveValue(_:for:)` instead of `PartialObject<O>.setValue(_:for:)`, which would unintentionally execute the same closure again recursively.
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public init(_ keyPath: KeyPath, `default`: V = V.cs_emptyValue(), isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void = { $1($2) }) {
public init(
_ keyPath: KeyPath,
initial: @autoclosure @escaping () -> V,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.keyPath = keyPath
self.isIndexed = isIndexed
self.isTransient = isTransient
self.defaultValue = `default`.cs_toImportableNativeType()
self.defaultValue = { initial().cs_toImportableNativeType() }
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
self.affectedByKeyPaths = affectedByKeyPaths
}
/**
@@ -134,45 +139,50 @@ public enum ValueContainer<O: CoreStoreObject> {
get {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return self.customGetter(
object,
{ () -> V in
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
if let customGetter = self.customGetter {
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { V.cs_fromImportableNativeType($0 as! V.ImportableNativeType)! }
)
return customGetter(PartialObject<O>(object.rawObject!))
}
)
return V.cs_fromImportableNativeType(
object.rawObject!.value(forKey: self.keyPath)! as! V.ImportableNativeType
)!
}
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
self.customSetter(
object,
{ (newValue: V) -> Void in
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
if let customSetter = self.customSetter {
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { $0.cs_toImportableNativeType() }
)
},
newValue
)
return customSetter(PartialObject<O>(object.rawObject!), newValue)
}
return object.rawObject!.setValue(
newValue.cs_toImportableNativeType(),
forKey: self.keyPath
)
}
}
}
@@ -189,20 +199,87 @@ public enum ValueContainer<O: CoreStoreObject> {
internal let isOptional = false
internal let isIndexed: Bool
internal let isTransient: Bool
internal let defaultValue: Any?
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal var parentObject: () -> CoreStoreObject = {
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
guard let customGetter = self.customGetter else {
return nil
}
let keyPath = self.keyPath
return { (_ id: Any) -> Any? in
let rawObject = id as! CoreStoreManagedObject
rawObject.willAccessValue(forKey: keyPath)
defer {
rawObject.didAccessValue(forKey: keyPath)
}
let value = customGetter(PartialObject<O>(rawObject))
return value.cs_toImportableNativeType()
}
}
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in
guard let customSetter = self.customSetter else {
return nil
}
let keyPath = self.keyPath
return { (_ id: Any, _ newValue: Any?) -> Void in
let rawObject = id as! CoreStoreManagedObject
rawObject.willChangeValue(forKey: keyPath)
defer {
rawObject.didChangeValue(forKey: keyPath)
}
customSetter(
PartialObject<O>(rawObject),
V.cs_fromImportableNativeType(newValue as! V.ImportableNativeType)!
)
}
}
// MARK: Private
private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V
private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void
private let customGetter: ((_ partialObject: PartialObject<O>) -> V)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)?
// MARK: Deprecated
@available(*, deprecated: 3.1, renamed: "init(_:initial:isIndexed:isTransient:versionHashModifier:renamingIdentifier:customGetter:customSetter:affectedByKeyPaths:)")
public convenience init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.init(
keyPath,
initial: `default`,
isIndexed: isIndexed,
isTransient: isTransient,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
customGetter: customGetter,
customSetter: customSetter,
affectedByKeyPaths: affectedByKeyPaths
)
}
}
@@ -225,17 +302,28 @@ public enum ValueContainer<O: CoreStoreObject> {
Initializes the metadata for the property.
```
class Person: CoreStoreObject {
let title = Value.Required<String>("title", default: "Mr.")
let name = Value.Required<String>(
"name",
customGetter: { (`self`, getValue) in
return "\(self.title.value) \(getValue())"
}
let title = Value.Optional<String>("title", initial: "Mr.")
let name = Value.Optional<String>("name")
let displayName = Value.Optional<String>(
"displayName",
isTransient: true,
customGetter: Person.getName(_:)
)
private static func getName(_ partialObject: PartialObject<Person>) -> String? {
if let cachedDisplayName = partialObject.primitiveValue(for: { $0.displayName }) {
return cachedDisplayName
}
let title = partialObject.value(for: { $0.title })
let name = partialObject.value(for: { $0.name })
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName })
return displayName
}
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter default: the initial value for the property when the object is first created. Defaults to `nil` if not specified.
- parameter initial: the initial value for the property when the object is first created. Defaults to `nil` if not specified.
- parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified.
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
@@ -247,17 +335,28 @@ public enum ValueContainer<O: CoreStoreObject> {
- parameter setValue: the original setter for the property
- parameter finalNewValue: the transformed new value
- parameter originalNewValue: the original new value
- parameter affectedByKeyPaths: a set of key paths for properties whose values affect the value of the receiver. This is similar to `NSManagedObject.keyPathsForValuesAffectingValue(forKey:)`.
*/
public init(_ keyPath: KeyPath, `default`: V? = nil, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void = { $1($2) }) {
public init(
_ keyPath: KeyPath,
initial: @autoclosure @escaping () -> V? = nil,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V?)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.keyPath = keyPath
self.isIndexed = isIndexed
self.isTransient = isTransient
self.defaultValue = `default`?.cs_toImportableNativeType()
self.defaultValue = { initial()?.cs_toImportableNativeType() }
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
self.affectedByKeyPaths = affectedByKeyPaths
}
/**
@@ -267,45 +366,49 @@ public enum ValueContainer<O: CoreStoreObject> {
get {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
return self.customGetter(
object,
{ () -> V? in
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
if let customGetter = self.customGetter {
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { ($0 as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType) }
)
return customGetter(PartialObject<O>(object.rawObject!))
}
)
return (object.rawObject!.value(forKey: self.keyPath) as! V.ImportableNativeType?)
.flatMap(V.cs_fromImportableNativeType)
}
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
self.parentObject != nil,
"Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
self.customSetter(
object,
{ (newValue: V?) -> Void in
return withExtendedLifetime(self.parentObject! as! O) { (object: O) in
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
if let customSetter = self.customSetter {
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath,
willSetValue: { $0?.cs_toImportableNativeType() }
)
},
newValue
)
return customSetter(PartialObject<O>(object.rawObject!), newValue)
}
object.rawObject!.setValue(
newValue?.cs_toImportableNativeType(),
forKey: self.keyPath
)
}
}
}
@@ -321,287 +424,87 @@ public enum ValueContainer<O: CoreStoreObject> {
internal let isOptional = true
internal let isIndexed: Bool
internal let isTransient: Bool
internal let defaultValue: Any?
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal let versionHashModifier: () -> String?
internal let renamingIdentifier: () -> String?
internal let defaultValue: () -> Any?
internal let affectedByKeyPaths: () -> Set<String>
internal weak var parentObject: CoreStoreObject?
internal var parentObject: () -> CoreStoreObject = {
internal private(set) lazy var getter: CoreStoreManagedObject.CustomGetter? = cs_lazy { [unowned self] in
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
guard let customGetter = self.customGetter else {
return nil
}
let keyPath = self.keyPath
return { (_ id: Any) -> Any? in
let rawObject = id as! CoreStoreManagedObject
rawObject.willAccessValue(forKey: keyPath)
defer {
rawObject.didAccessValue(forKey: keyPath)
}
let value = customGetter(PartialObject<O>(rawObject))
return value?.cs_toImportableNativeType()
}
}
internal private(set) lazy var setter: CoreStoreManagedObject.CustomSetter? = cs_lazy { [unowned self] in
guard let customSetter = self.customSetter else {
return nil
}
let keyPath = self.keyPath
return { (_ id: Any, _ newValue: Any?) -> Void in
let rawObject = id as! CoreStoreManagedObject
rawObject.willChangeValue(forKey: keyPath)
defer {
rawObject.didChangeValue(forKey: keyPath)
}
customSetter(
PartialObject<O>(rawObject),
(newValue as! V.ImportableNativeType?).flatMap(V.cs_fromImportableNativeType)
)
}
}
// MARK: Private
private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V?
private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void
}
}
// MARK: - TransformableContainer
/**
The containing type for transformable properties. Use the `DynamicObject.Transformable` typealias instead for shorter syntax.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
*/
public enum TransformableContainer<O: CoreStoreObject> {
// MARK: - Required
/**
The containing type for transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable.Required` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public final class Required<V: NSCoding & NSCopying>: AttributeProtocol {
private let customGetter: ((_ partialObject: PartialObject<O>) -> V?)?
private let customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)?
/**
Initializes the metadata for the property.
```
class Animal: CoreStoreObject {
let color = Transformable.Optional<UIColor>("color")
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter default: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
- parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified.
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter.
- parameter self: the `CoreStoreObject`
- parameter getValue: the original getter for the property
- parameter customSetter: use this closure to make final transformations to the new value before assigning to the property.
- parameter setValue: the original setter for the property
- parameter finalNewValue: the transformed new value
- parameter originalNewValue: the original new value
*/
public init(_ keyPath: KeyPath, `default`: V, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V) -> V = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V) -> Void, _ originalNewValue: V) -> Void = { $1($2) }) {
// MARK: Deprecated
@available(*, deprecated: 3.1, renamed: "init(_:initial:isIndexed:isTransient:versionHashModifier:renamingIdentifier:customGetter:customSetter:affectedByKeyPaths:)")
public convenience init(
_ keyPath: KeyPath,
`default`: @autoclosure @escaping () -> V?,
isIndexed: Bool = false,
isTransient: Bool = false,
versionHashModifier: @autoclosure @escaping () -> String? = nil,
renamingIdentifier: @autoclosure @escaping () -> String? = nil,
customGetter: ((_ partialObject: PartialObject<O>) -> V?)? = nil,
customSetter: ((_ partialObject: PartialObject<O>, _ newValue: V?) -> Void)? = nil,
affectedByKeyPaths: @autoclosure @escaping () -> Set<String> = []) {
self.keyPath = keyPath
self.defaultValue = `default`
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
self.init(
keyPath,
initial: `default`,
isIndexed: isIndexed,
isTransient: isTransient,
versionHashModifier: versionHashModifier,
renamingIdentifier: renamingIdentifier,
customGetter: customGetter,
customSetter: customSetter,
affectedByKeyPaths: affectedByKeyPaths
)
}
/**
The property value.
*/
public var value: V {
get {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return self.customGetter(
object,
{ () -> V in
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { $0 as! V }
)
}
)
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
self.customSetter(
object,
{ (newValue: V) -> Void in
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
)
},
newValue
)
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = false
internal let isIndexed: Bool
internal let isTransient: Bool
internal let defaultValue: Any?
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
// MARK: Private
private let customGetter: (_ `self`: O, _ getValue: () -> V) -> V
private let customSetter: (_ `self`: O, _ setValue: (V) -> Void, _ newValue: V) -> Void
}
// MARK: - Optional
/**
The containing type for optional transformable properties. Any type that conforms to `NSCoding & NSCopying` are supported.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let color = Transformable.Optional<UIColor>("color")
}
```
- Important: `Transformable.Optional` properties are required to be stored properties. Computed properties will be ignored, including `lazy` and `weak` properties.
*/
public final class Optional<V: NSCoding & NSCopying>: AttributeProtocol {
/**
Initializes the metadata for the property.
```
class Animal: CoreStoreObject {
let color = Transformable.Optional<UIColor>("color")
}
```
- parameter keyPath: the permanent attribute name for this property.
- parameter default: the initial value for the property when the object is first created. Defaults to the `ImportableAttributeType`'s empty value if not specified.
- parameter isIndexed: `true` if the property should be indexed for searching, otherwise `false`. Defaults to `false` if not specified.
- parameter isTransient: `true` if the property is transient, otherwise `false`. Defaults to `false` if not specified. The transient flag specifies whether or not a property's value is ignored when an object is saved to a persistent store. Transient properties are not saved to the persistent store, but are still managed for undo, redo, validation, and so on.
- parameter versionHashModifier: used to mark or denote a property as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where the properties are unchanged but the format or content of its data are changed.)
- parameter renamingIdentifier: used to resolve naming conflicts between models. When creating an entity mapping between entities in two managed object models, a source entity property and a destination entity property that share the same identifier indicate that a property mapping should be configured to migrate from the source to the destination. If unset, the identifier will be the property's name.
- parameter customGetter: use this closure to make final transformations to the property's value before returning from the getter.
- parameter self: the `CoreStoreObject`
- parameter getValue: the original getter for the property
- parameter customSetter: use this closure to make final transformations to the new value before assigning to the property.
- parameter setValue: the original setter for the property
- parameter finalNewValue: the transformed new value
- parameter originalNewValue: the original new value
*/
public init(_ keyPath: KeyPath, `default`: V? = nil, isIndexed: Bool = false, isTransient: Bool = false, versionHashModifier: String? = nil, renamingIdentifier: String? = nil, customGetter: @escaping (_ `self`: O, _ getValue: () -> V?) -> V? = { $1() }, customSetter: @escaping (_ `self`: O, _ setValue: (_ finalNewValue: V?) -> Void, _ originalNewValue: V?) -> Void = { $1($2) }) {
self.keyPath = keyPath
self.defaultValue = `default`
self.isIndexed = isIndexed
self.isTransient = isTransient
self.versionHashModifier = versionHashModifier
self.renamingIdentifier = renamingIdentifier
self.customGetter = customGetter
self.customSetter = customSetter
}
/**
The property value.
*/
public var value: V? {
get {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
return self.customGetter(
object,
{ () -> V? in
return object.rawObject!.getValue(
forKvcKey: self.keyPath,
didGetValue: { $0 as! V? }
)
}
)
}
set {
let object = self.parentObject() as! O
CoreStore.assert(
object.rawObject!.isRunningInAllowedQueue() == true,
"Attempted to access \(cs_typeName(O.self))'s value outside it's designated queue."
)
CoreStore.assert(
object.rawObject!.isEditableInContext() == true,
"Attempted to update a \(cs_typeName(O.self))'s value from outside a transaction."
)
self.customSetter(
object,
{ (newValue: V?) -> Void in
object.rawObject!.setValue(
newValue,
forKvcKey: self.keyPath
)
},
newValue
)
}
}
// MARK: AttributeProtocol
internal static var attributeType: NSAttributeType {
return .transformableAttributeType
}
public let keyPath: KeyPath
internal let isOptional = true
internal let isIndexed: Bool
internal let isTransient: Bool
internal let defaultValue: Any?
internal let versionHashModifier: String?
internal let renamingIdentifier: String?
internal var parentObject: () -> CoreStoreObject = {
CoreStore.abort("Attempted to access values from a \(cs_typeName(O.self)) meta object. Meta objects are only used for querying keyPaths and infering types.")
}
// MARK: Private
private let customGetter: (_ `self`: O, _ getValue: () -> V?) -> V?
private let customSetter: (_ `self`: O, _ setValue: (V?) -> Void, _ newValue: V?) -> Void
}
}
@@ -811,101 +714,3 @@ extension ValueContainer.Optional {
return property.value == property2.value
}
}
extension TransformableContainer.Required {
/**
Assigns a transformable value to the property. The operation
```
animal.color .= UIColor.red
```
is equivalent to
```
animal.color.value = UIColor.red
```
*/
public static func .= (_ property: TransformableContainer<O>.Required<V>, _ newValue: V) {
property.value = newValue
}
/**
Assigns a transformable value from another property. The operation
```
animal.nickname .= anotherAnimal.species
```
is equivalent to
```
animal.nickname.value = anotherAnimal.species.value
```
*/
public static func .= <O2: CoreStoreObject>(_ property: TransformableContainer<O>.Required<V>, _ property2: TransformableContainer<O2>.Required<V>) {
property.value = property2.value
}
}
extension TransformableContainer.Optional {
/**
Assigns an optional transformable value to the property. The operation
```
animal.color .= UIColor.red
```
is equivalent to
```
animal.color.value = UIColor.red
```
*/
public static func .= (_ property: TransformableContainer<O>.Optional<V>, _ newValue: V?) {
property.value = newValue
}
/**
Assigns an optional transformable value from another property. The operation
```
animal.color .= anotherAnimal.color
```
is equivalent to
```
animal.color.value = anotherAnimal.color.value
```
*/
public static func .= <O2: CoreStoreObject>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Optional<V>) {
property.value = property2.value
}
/**
Assigns a transformable value from another property. The operation
```
animal.color .= anotherAnimal.color
```
is equivalent to
```
animal.color.value = anotherAnimal.color.value
```
*/
public static func .= <O2: CoreStoreObject>(_ property: TransformableContainer<O>.Optional<V>, _ property2: TransformableContainer<O2>.Required<V>) {
property.value = property2.value
}
}
// MARK: - AttributeProtocol
internal protocol AttributeProtocol: class {
static var attributeType: NSAttributeType { get }
var keyPath: KeyPath { get }
var isOptional: Bool { get }
var isIndexed: Bool { get }
var isTransient: Bool { get }
var defaultValue: Any? { get }
var versionHashModifier: String? { get }
var renamingIdentifier: String? { get }
var parentObject: () -> CoreStoreObject { get set }
}

View File

@@ -42,6 +42,36 @@ public struct Where: FetchClause, QueryClause, DeleteClause, Hashable {
return Where(NSCompoundPredicate(type: .and, subpredicates: [left.predicate, right.predicate]))
}
/**
Combines two `Where` predicates together using `AND` operator.
- parameter left: the left hand side `Where` clause
- parameter right: the right hand side `Where` clause
- returns: Return `left` unchanged if `right` is nil
*/
public static func && (left: Where, right: Where?) -> Where {
if let right = right {
return left && right
}
return left
}
/**
Combines two `Where` predicates together using `AND` operator.
- parameter left: the left hand side `Where` clause
- parameter right: the right hand side `Where` clause
- returns: Returns `right` unchanged if `left` is nil
*/
public static func && (left: Where?, right: Where) -> Where {
if let left = left {
return left && right
}
return right
}
/**
Combines two `Where` predicates together using `OR` operator
*/
@@ -50,6 +80,36 @@ public struct Where: FetchClause, QueryClause, DeleteClause, Hashable {
return Where(NSCompoundPredicate(type: .or, subpredicates: [left.predicate, right.predicate]))
}
/**
Combines two `Where` predicates together using `OR` operator.
- parameter left: the left hand side `Where` clause
- parameter right: the right hand side `Where` clause
- returns: Returns `left` unchanged if `right` is nil
*/
public static func || (left: Where, right: Where?) -> Where {
if let right = right {
return left || right
}
return left
}
/**
Combines two `Where` predicates together using `OR` operator.
- parameter left: the left hand side `Where` clause
- parameter right: the right hand side `Where` clause
- returns: Return `right` unchanged if `left` is nil
*/
public static func || (left: Where?, right: Where) -> Where {
if let left = left {
return left || right
}
return right
}
/**
Inverts the predicate of a `Where` clause using `NOT` operator
*/
@@ -216,3 +276,25 @@ public struct Where: FetchClause, QueryClause, DeleteClause, Hashable {
return self.predicate.hashValue
}
}
// MARK: - Sequence where Element == Where
public extension Sequence where Iterator.Element == Where {
/**
Combines multiple `Where` predicates together using `AND` operator
*/
public func combinedByAnd() -> Where {
return Where(NSCompoundPredicate(type: .and, subpredicates: self.map({ $0.predicate })))
}
/**
Combines multiple `Where` predicates together using `OR` operator
*/
public func combinedByOr() -> Where {
return Where(NSCompoundPredicate(type: .or, subpredicates: self.map({ $0.predicate })))
}
}

View File

@@ -32,7 +32,7 @@ import Foundation
/**
A `SchemaMappingProvider` that tries to infer model migration between two `DynamicSchema` versions by loading an xcmappingmodel file from the specified `Bundle`. Throws `CoreStoreError.mappingModelNotFound` if the xcmappingmodel file cannot be found, or if the xcmappingmodel doesn't resolve the source and destination `DynamicSchema`.
*/
final class XcodeSchemaMappingProvider: Hashable, SchemaMappingProvider {
public final class XcodeSchemaMappingProvider: Hashable, SchemaMappingProvider {
/**
The source model version for the mapping.