Compare commits

..

108 Commits

Author SHA1 Message Date
John Estropia bf10f4668c revert Objc method changes 2021-09-22 15:03:46 +09:00
John Estropia a1a04aaf8a tag refetch methods with source identifiers 2021-09-15 19:38:59 +09:00
John Estropia 4ddfa95140 added mechanism to track transaction sources 2021-09-15 14:45:13 +09:00
John Estropia 45215c7a18 WIP 2021-08-25 20:13:14 +09:00
John Estropia d2f1656fdd test copy-on-write for ListSnapshot 2021-08-25 09:37:20 +09:00
John Estropia 5fbb1ab09d Merge pull request #421 from xquezme/develop
Fix compound indexes for dynamic models
2021-07-20 09:26:17 +09:00
John Estropia d0cc01877e Merge pull request #433 from tackgyu/unset-library-type
Unset library type in Swift package manifests
2021-07-19 13:35:39 +09:00
tackgyu a434628249 Unset library type in Swift package manifests 2021-07-19 12:52:51 +09:00
John Estropia 9cc616f720 fix compiler error on Xcode 13 2021-06-19 15:49:45 +09:00
John Estropia 4534bc06f5 Add utility for DiffableDataSources to create an empty snapshot for custom list construction 2021-06-16 23:28:46 +09:00
John Estropia 798d30bbd9 allow ListPublisher and ObjectPublisher combine Publishers to cancel subscription from any thread 2021-06-13 14:36:22 +09:00
John Estropia 7938aa2447 Added fast index-based ListSnapshot mutators 2021-06-12 11:06:43 +09:00
John Estropia dda69389eb Fix SPM issues 2021-04-20 18:30:14 +09:00
John Estropia b20be10a19 update Readme banner image 2021-04-11 11:39:43 +09:00
John Estropia 0ee8fbabd5 version bump, update resources 2021-04-11 11:26:02 +09:00
John Estropia 1f562b25a7 Updated README 2021-04-11 11:03:17 +09:00
Pimenov Sergey 6a2394052c fix compound indexes for dynamic models 2021-04-04 20:12:40 -07:00
John Estropia 593c0510d3 Allow placeholder Views in ObjectReader when an object becomes nil 2021-03-13 11:25:27 +09:00
John Estropia 338e4ddc9f Fix sig abort error 2021-03-07 17:02:09 +09:00
John Estropia bfb1df3c40 added Publishers for addStorage functions 2021-03-07 16:08:19 +09:00
John Estropia 003bf897e2 Cleanup unnecessary #available branches 2021-03-07 12:01:08 +09:00
John Estropia 0b127956d3 Removed ObservableObject implementations of ListPublisher/ObjectPublisher in favor of LiveList/LiveObject and ".reactive" publishers 2021-03-07 10:38:53 +09:00
John Estropia 098e560fcc Merge branch 'develop' of github.com:JohnEstropia/CoreStore into develop 2021-03-07 10:17:34 +09:00
John Estropia 7dbd3777ec added sugar syntax for ForEach that removes requirement for the "id:" argument 2021-03-07 10:17:20 +09:00
John Estropia 447d8e5880 fix compile error 2021-03-03 16:59:20 +09:00
John Estropia 1f97225efa SwiftUI and Combine utilities appledocs. Bump up to 8.0 and iOS 11.0/macOS 10.13 2021-03-01 09:14:41 +09:00
John Estropia d13b0cfabb update unit tests 2021-02-21 10:59:55 +09:00
John Estropia d7b852fca4 SwiftUI utilities done (for now) 2021-02-21 10:56:27 +09:00
John Estropia f2efe175e5 deprecate misleading API for sectionIndexTransformers 2021-02-21 10:17:58 +09:00
John Estropia 8f3a6638d8 Delete long-deprecated CoreStore namespace 2021-02-21 10:16:08 +09:00
John Estropia f7471f56a4 Prototyping SwiftUI utilities 2021-02-16 09:12:36 +09:00
John Estropia edd8ba55d8 fix rare duplication of uniqueID values during import when an import candidate is added by a previous insertion block 2021-01-23 16:51:45 +09:00
John Estropia 18aac84335 fix compiler error 2021-01-02 10:36:33 +09:00
John Estropia c6be892cb0 added .where(combinedByAnd:) and .where(combinedByOr:) to help compiler with long && and || chains 2021-01-02 10:00:23 +09:00
John Estropia 2cd8101987 Implement ObjectRepresentation on ObjectMonitor, ObjectPublisher, and ObjectSnapshot for future APIs 2021-01-02 09:59:15 +09:00
John Estropia e1aed37da0 avoid using default queue qos 2020-12-26 23:45:11 +09:00
John Estropia 5de5ecee06 give opportunity for faster equating of ObjectSnapshot 2020-12-26 23:44:35 +09:00
John Estropia 9406901b28 WIP: SwiftUI utils 2020-12-26 19:21:46 +09:00
John Estropia 477f478d85 Add missing Where operators (fixes #410) 2020-12-26 14:02:20 +09:00
John Estropia 668b5ad606 Update playground samples to use Field properties instead of old syntax 2020-12-07 14:08:05 +09:00
John Estropia f985828f3b cleanup 2020-11-17 18:14:23 +09:00
John Estropia bb3bc940c2 Remove the dataStack SwiftUI environment key since it's prone to initialization issues 2020-11-11 16:56:20 +09:00
John Estropia 2d5bc77219 Merge branch 'master' into minIOS11
# Conflicts:
#	Sources/ListSnapshot.swift
2020-11-11 13:44:58 +09:00
John Estropia a40df37192 version bump 2020-11-11 13:29:02 +09:00
John Estropia 63b3d25d78 clear warnings 2020-11-11 13:18:11 +09:00
John Estropia 74721b5c12 allow ObjectSnapshot and ObjectPublisher as parameter to Where clauses 2020-10-10 16:55:35 +09:00
John Estropia f136549b46 prevent creation of ObjectSnapshot if object is already deleted 2020-10-09 20:14:04 +09:00
John Estropia 4ec2b2e311 Optimize ListSnapshot collection implementation 2020-10-05 23:12:17 +09:00
John Estropia 3d82ee18c7 reinclude LegacyDemo in workspace 2020-10-03 14:36:57 +09:00
John Estropia d6eae8c091 fix podspec 2020-09-19 15:07:27 +09:00
John Estropia 6f8d86cd8f remove xcuserdata 2020-09-19 14:59:14 +09:00
John Estropia 3b6f226389 version bump 2020-09-19 14:44:55 +09:00
John Estropia 228e4e8e1a rename CoreStoreDemo to LegacyDemo 2020-09-19 14:32:43 +09:00
John Estropia 98a42feb95 demo cleanup 2020-09-19 12:38:09 +09:00
John Estropia 2f93ee7591 Migrations demo done 2020-09-17 23:21:41 +09:00
John Estropia 11f5cb9a05 WIP: demo 2020-09-14 09:46:47 +09:00
John Estropia 2c00fc31bc WIP: migrations demo 2020-09-13 14:17:06 +09:00
John Estropia 2bbf6b34ea WIP: migrations demo 2020-09-08 09:09:36 +09:00
John Estropia 8d7f282743 added demo for classic ListMonitor 2020-08-30 20:16:01 +09:00
John Estropia 007da014f8 cleanup 2020-08-30 10:24:10 +09:00
John Estropia b26e50f777 add version lock to demo 2020-08-29 23:06:12 +09:00
John Estropia 611bc53c9a cleanup 2020-08-29 23:05:07 +09:00
John Estropia 9d36582c10 minor fix 2020-08-29 20:06:00 +09:00
John Estropia 1c735a9228 improve Pokedex demo 2020-08-29 20:02:05 +09:00
John Estropia 1db91fcec3 make demo compilable on Xcode 11 2020-08-27 09:50:12 +09:00
John Estropia 8b3b947406 pokedex demo 2020-08-20 00:39:03 +09:00
John Estropia 2c0cadf2fa WIP 2020-08-19 18:49:08 +09:00
John Estropia 5e536556da fix for SR-13069 2020-08-19 11:01:12 +09:00
John Estropia d75029f54b WIP 2020-08-19 08:32:18 +09:00
John Estropia 204c4de1f6 use randomElement in unit test 2020-08-18 17:50:00 +09:00
John Estropia 0f3455a4a4 WIP 2020-08-18 12:05:20 +09:00
John Estropia 72f36e7237 WIP 2020-08-17 17:09:41 +09:00
John Estropia d988daa025 WIP: new demo app 2020-08-17 09:06:25 +09:00
John Estropia e720504855 Update README.md 2020-06-20 17:37:12 +09:00
John Estropia ee51c5ad9c fix readme indentations 2020-06-20 16:47:52 +09:00
John Estropia 4ac7df7364 version bump 2020-06-20 13:16:14 +09:00
John Estropia 56f873ccb3 fix readme table of contents 2020-06-20 13:12:11 +09:00
John Estropia 1f90f846f3 readme update 2020-06-20 13:07:10 +09:00
John Estropia 4a97d5a8dc remove persistentstores during DataStack deinitialization to prevent warnings in Unit tests during teardown 2020-05-26 09:43:39 +09:00
John Estropia 0eb9b6393d add unit testst for Field dynamic initializers 2020-05-24 10:54:16 +09:00
John Estropia 56d0ea46ea Implement dynamic initializers for Field properties (fixes #382) 2020-05-23 12:07:16 +09:00
John Estropia a7568eebdb fix comments 2020-04-15 16:49:45 +09:00
John Estropia 4049e1944a trigger lazy initialization of ObjectPublisher observation after addObserver() (fixes #383) 2020-04-15 16:48:42 +09:00
John Estropia 73b7fcd907 Merge branch 'prototype/propertyWrapperFields' into develop 2020-03-26 02:04:33 +09:00
John Estropia 74c1a97af4 Version bump 2020-03-26 02:04:10 +09:00
John Estropia 97f2a53124 AppleDocs for Field source files 2020-03-26 01:57:32 +09:00
John Estropia b6db872be0 Merge branch 'prototype/propertyWrapperFields' of github.com:JohnEstropia/CoreStore into prototype/propertyWrapperFields 2020-03-25 19:00:56 +09:00
John Estropia 0d9299f900 WIP: docs 2020-03-25 14:21:49 +09:00
John Estropia 7f928dc684 docs 2020-03-19 14:12:09 +09:00
John Estropia 231e138ab0 performant access of relationship objectIDs for snapshots 2020-02-21 13:51:17 +09:00
John Estropia 361dba58c6 bypass thread checks depending on location of Field call 2020-02-21 11:52:11 +09:00
John Estropia e1b03b4a89 fix wrong optional configuration in Field.Virtual 2020-02-21 11:20:32 +09:00
John Estropia 0df6c737c1 fix wrong optional configuration in Field.Virtual 2020-02-21 11:17:39 +09:00
John Estropia 12c5aeaaa4 safer casting 2020-02-21 10:36:28 +09:00
John Estropia dd3fb17dd0 fix runtime issue with Fields on objects with base classes 2020-02-21 10:19:42 +09:00
John Estropia 627a5d4355 add OrderBy utilities for Field.Stored 2020-02-19 22:01:46 +09:00
John Estropia 58629bc1df add missing predicate operator overloads 2020-02-19 13:58:14 +09:00
John Estropia f925803b93 Create FUNDING.yml 2020-02-19 13:32:57 +09:00
John Estropia 843adf21f7 improved API for custom getters and setters in Field properties 2020-02-18 18:17:52 +09:00
John Estropia 2d1b1e0592 add Field.Coded dynamic lookups for ObjectPublisher and ObjectSnapshot 2020-02-17 18:30:18 +09:00
John Estropia 38e9878c04 Merge branch 'master' into prototype/propertyWrapperFields 2020-02-08 09:47:35 +09:00
John Estropia 8e4e308ccc Merge branch 'prototype/propertyWrapperFields' of github.com:JohnEstropia/CoreStore into prototype/propertyWrapperFields 2020-02-06 09:33:15 +09:00
John Estropia f0f4049798 renamed Field.Computed to Field.Virtual to distinguish from Field.Derived 2020-02-06 09:33:08 +09:00
John Estropia c20fe4ac17 add Field.Relationship dynamicMemberLookups 2020-02-05 11:03:57 +09:00
John Estropia e9c3312612 Fix default encoders for top-level values 2020-01-21 17:03:12 +09:00
John Estropia 92ad895044 Field.Relationship propertyWrapper 2020-01-20 17:13:01 +09:00
John Estropia bcc2d9def3 Field.Coded implementations for transformable attributes 2020-01-18 16:22:06 +09:00
John Estropia 43f61359da prototype new Fields as propertyWrappers (Swift 5.2 above only) 2020-01-15 18:29:58 +09:00
337 changed files with 21569 additions and 4179 deletions
+3
View File
@@ -0,0 +1,3 @@
# These are supported funding model platforms
github: [JohnEstropia]
+2
View File
@@ -12,3 +12,5 @@ Playground_macOS.playground/playground.xcworkspace/xcuserdata
.swiftpm/xcode/package.xcworkspace/xcuserdata .swiftpm/xcode/package.xcworkspace/xcuserdata
.swiftpm/xcode/xcuserdata .swiftpm/xcode/xcuserdata
Playground_iOS.playground/playground.xcworkspace/xcuserdata Playground_iOS.playground/playground.xcworkspace/xcuserdata
LegacyDemo/LegacyDemo.xcodeproj/xcuserdata
Demo/Demo.xcodeproj/xcuserdata
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

After

Width:  |  Height:  |  Size: 61 KiB

+6 -6
View File
@@ -1,7 +1,7 @@
Pod::Spec.new do |s| Pod::Spec.new do |s|
s.name = "CoreStore" s.name = "CoreStore"
s.version = "7.0.4" s.version = "8.0.1"
s.swift_version = "5.1" s.swift_version = "5.4"
s.license = "MIT" s.license = "MIT"
s.homepage = "https://github.com/JohnEstropia/CoreStore" s.homepage = "https://github.com/JohnEstropia/CoreStore"
s.documentation_url = "https://JohnEstropia.github.io/CoreStore" s.documentation_url = "https://JohnEstropia.github.io/CoreStore"
@@ -9,10 +9,10 @@ Pod::Spec.new do |s|
s.author = { "John Rommel Estropia" => "rommel.estropia@gmail.com" } s.author = { "John Rommel Estropia" => "rommel.estropia@gmail.com" }
s.source = { :git => "https://github.com/JohnEstropia/CoreStore.git", :tag => s.version.to_s } s.source = { :git => "https://github.com/JohnEstropia/CoreStore.git", :tag => s.version.to_s }
s.ios.deployment_target = "10.0" s.ios.deployment_target = "11.0"
s.osx.deployment_target = "10.12" s.osx.deployment_target = "10.13"
s.watchos.deployment_target = "3.0" s.watchos.deployment_target = "4.0"
s.tvos.deployment_target = "10.0" s.tvos.deployment_target = "11.0"
s.source_files = "Sources", "Sources/**/*.{swift,h,m}" s.source_files = "Sources", "Sources/**/*.{swift,h,m}"
s.public_header_files = "Sources/**/*.h" s.public_header_files = "Sources/**/*.h"
BIN
View File
Binary file not shown.
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1200"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1200"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1200"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1200"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
+4 -1
View File
@@ -1,10 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Workspace <Workspace
version = "1.0"> version = "1.0">
<FileRef
location = "group:Demo/Demo.xcodeproj">
</FileRef>
<FileRef <FileRef
location = "group:CoreStore.xcodeproj"> location = "group:CoreStore.xcodeproj">
</FileRef> </FileRef>
<FileRef <FileRef
location = "group:CoreStoreDemo/CoreStoreDemo.xcodeproj"> location = "group:LegacyDemo/LegacyDemo.xcodeproj">
</FileRef> </FileRef>
</Workspace> </Workspace>
@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
<false/>
<key>IDESourceControlProjectIdentifier</key>
<string>B6855E48-4B19-4321-B1C7-CB2706E12777</string>
<key>IDESourceControlProjectName</key>
<string>CoreStoreDemo</string>
<key>IDESourceControlProjectOriginsDictionary</key>
<dict>
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
<string>github.com:JohnEstropia/CoreStore.git</string>
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
<string>github.com:JohnEstropia/GCDKit.git</string>
</dict>
<key>IDESourceControlProjectPath</key>
<string>CoreStoreDemo/CoreStoreDemo.xcodeproj</string>
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
<dict>
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
<string>../../..</string>
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
<string>../../..Libraries/GCDKit</string>
</dict>
<key>IDESourceControlProjectURL</key>
<string>github.com:JohnEstropia/CoreStore.git</string>
<key>IDESourceControlProjectVersion</key>
<integer>111</integer>
<key>IDESourceControlProjectWCCIdentifier</key>
<string>4B60F1BCB491FF717C56441AE7783C74F417BE48</string>
<key>IDESourceControlProjectWCConfigurations</key>
<array>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>4B60F1BCB491FF717C56441AE7783C74F417BE48</string>
<key>IDESourceControlWCCName</key>
<string>CoreStore</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>8B2E522D57154DFA93A06982C36315ECBEA4FA97</string>
<key>IDESourceControlWCCName</key>
<string>GCDKit</string>
</dict>
</array>
</dict>
</plist>
@@ -1,30 +0,0 @@
{
"DVTSourceControlWorkspaceBlueprintPrimaryRemoteRepositoryKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48",
"DVTSourceControlWorkspaceBlueprintWorkingCopyRepositoryLocationsKey" : {
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0,
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "B6855E48-4B19-4321-B1C7-CB2706E12777",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStoreLibraries\/GCDKit",
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStoreDemo",
"DVTSourceControlWorkspaceBlueprintVersion" : 203,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStoreDemo\/CoreStoreDemo.xcodeproj",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/CoreStore.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "4B60F1BCB491FF717C56441AE7783C74F417BE48"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "github.com:JohnEstropia\/GCDKit.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97"
}
]
}
@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>IDESourceControlProjectFavoriteDictionaryKey</key>
<false/>
<key>IDESourceControlProjectIdentifier</key>
<string>7C5E31AC-5DD0-43DA-A5C6-AF73B4532D86</string>
<key>IDESourceControlProjectName</key>
<string>project</string>
<key>IDESourceControlProjectOriginsDictionary</key>
<dict>
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
<string>github.com:JohnEstropia/HardcoreData.git</string>
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
<string>github.com:JohnEstropia/GCDKit.git</string>
</dict>
<key>IDESourceControlProjectPath</key>
<string>HardcoreDataDemo/HardcoreDataDemo.xcodeproj/project.xcworkspace</string>
<key>IDESourceControlProjectRelativeInstallPathDictionary</key>
<dict>
<key>4B60F1BCB491FF717C56441AE7783C74F417BE48</key>
<string>../../..</string>
<key>8B2E522D57154DFA93A06982C36315ECBEA4FA97</key>
<string>../../..Libraries/GCDKit</string>
</dict>
<key>IDESourceControlProjectURL</key>
<string>github.com:JohnEstropia/HardcoreData.git</string>
<key>IDESourceControlProjectVersion</key>
<integer>111</integer>
<key>IDESourceControlProjectWCCIdentifier</key>
<string>4B60F1BCB491FF717C56441AE7783C74F417BE48</string>
<key>IDESourceControlProjectWCConfigurations</key>
<array>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>8B2E522D57154DFA93A06982C36315ECBEA4FA97</string>
<key>IDESourceControlWCCName</key>
<string>GCDKit</string>
</dict>
<dict>
<key>IDESourceControlRepositoryExtensionIdentifierKey</key>
<string>public.vcs.git</string>
<key>IDESourceControlWCCIdentifierKey</key>
<string>4B60F1BCB491FF717C56441AE7783C74F417BE48</string>
<key>IDESourceControlWCCName</key>
<string>HardcoreData</string>
</dict>
</array>
</dict>
</plist>
@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<<<<<<< Updated upstream
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
=======
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
>>>>>>> Stashed changes
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<<<<<<< Updated upstream
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
=======
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439.5" width="441" height="20.5"/>
>>>>>>> Stashed changes
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="133" width="441" height="57.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
</document>
@@ -1,86 +0,0 @@
//
// Palette.swift
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/05/05.
// Copyright © 2018 John Rommel Estropia. All rights reserved.
//
import Foundation
import UIKit
import CoreData
import CoreStore
// MARK: - Palette
final class Palette: CoreStoreObject {
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
)
static func randomHue() -> Int {
return Int(arc4random_uniform(360))
}
private static func getColorName(_ partialObject: PartialObject<Palette>) -> String? {
if let colorName = partialObject.primitiveValue(for: { $0.colorName }) {
return colorName
}
let colorName: String
switch partialObject.value(for: { $0.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"
}
partialObject.setPrimitiveValue(colorName, for: { $0.colorName })
return colorName
}
}
extension Palette {
var color: UIColor {
return UIColor(
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.value)˚, S: \(round(self.saturation.value * 100.0))%, B: \(round(self.brightness.value * 100.0))%"
}
}
extension Palette {
func setInitialValues(in transaction: BaseDataTransaction) {
self.hue .= Palette.randomHue()
self.saturation .= Float(1.0)
self.brightness .= Float(arc4random_uniform(70) + 30) / 100.0
}
}
Binary file not shown.
+3
View File
@@ -62,6 +62,9 @@ class BaseTestCase: XCTestCase {
XCTFail(error.coreStoreDumpString) XCTFail(error.coreStoreDumpString)
} }
self.addTeardownBlock {
stack.unsafeRemoveAllPersistentStoresAndWait()
}
} }
@nonobjc @nonobjc
+2 -10
View File
@@ -191,16 +191,8 @@
XCTAssertEqualObjects([[sqliteStorage class] storeType], [CSSQLiteStore storeType]); XCTAssertEqualObjects([[sqliteStorage class] storeType], [CSSQLiteStore storeType]);
XCTAssertEqualObjects([[sqliteStorage class] storeType], NSSQLiteStoreType); XCTAssertEqualObjects([[sqliteStorage class] storeType], NSSQLiteStoreType);
XCTAssertNil(sqliteStorage.configuration); XCTAssertNil(sqliteStorage.configuration);
NSDictionary *storeOptions; NSDictionary *storeOptions = @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" },
if (@available(iOS 11.0, macOS 10.13, tvOS 11.0, *)) { NSBinaryStoreInsecureDecodingCompatibilityOption: @YES };
storeOptions = @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" },
NSBinaryStoreInsecureDecodingCompatibilityOption: @YES };
}
else {
storeOptions = @{ NSSQLitePragmasOption: @{ @"journal_mode": @"WAL" }};
}
XCTAssertEqualObjects(sqliteStorage.storeOptions, storeOptions); XCTAssertEqualObjects(sqliteStorage.storeOptions, storeOptions);
XCTAssertNil(sqliteError); XCTAssertNil(sqliteError);
} }
+4 -2
View File
@@ -23,13 +23,15 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest
@testable @testable
import CoreStore import CoreStore
// MARK: - ConvenienceTests // MARK: - ConvenienceTests
@available(macOS 10.12, *)
class ConvenienceTests: BaseTestCase { class ConvenienceTests: BaseTestCase {
@objc @objc
@@ -64,7 +66,7 @@ class ConvenienceTests: BaseTestCase {
self.prepareStack { (stack) in self.prepareStack { (stack) in
_ = withExtendedLifetime(stack.beginUnsafe()) { (transaction: UnsafeDataTransaction) in withExtendedLifetime(stack.beginUnsafe()) { (transaction: UnsafeDataTransaction) in
let controller = transaction.createFetchedResultsController( let controller = transaction.createFetchedResultsController(
From<TestEntity1>(), From<TestEntity1>(),
+251 -156
View File
@@ -37,78 +37,149 @@ import CoreStore
#endif #endif
class Animal: CoreStoreObject { class Animal: CoreStoreObject {
let species = Value.Required<String>("species", initial: "Swift") @Field.Stored("species")
let master = Relationship.ToOne<Person>("master") var species: String = "Swift"
let color = Transformable.Optional<Color>("color")
@Field.Coded("color", coder: FieldCoders.NSCoding.self)
var color: Color? = .blue
@Field.Relationship("master")
var master: Person?
} }
class Dog: Animal { class Dog: Animal {
let nickname = Value.Optional<String>("nickname") static let commonNicknames = ["Spot", "Benjie", "Max", "Milo"]
let age = Value.Required<Int>("age", initial: 1)
let friends = Relationship.ToManyOrdered<Dog>("friends") @Field.Stored(
let friendedBy = Relationship.ToManyUnordered<Dog>("friendedBy", inverse: { $0.friends }) "nickname",
dynamicInitialValue: {
commonNicknames.randomElement()!
}
)
var nickname: String
@Field.Stored("age")
var age: Int = 1
@Field.Relationship("friends")
var friends: [Dog]
@Field.Relationship("friendedBy", inverse: \.$friends)
var friendedBy: Set<Dog>
}
struct CustomType {
var string = "customString"
}
enum Job: String, CaseIterable {
case unemployed
case engineer
case doctor
case lawyer
init?(data: Data) {
guard
let rawValue = String(data: data, encoding: .utf8),
let value = Self.init(rawValue: rawValue)
else {
return nil
}
self = value
}
func toData() -> Data {
return Data(self.rawValue.utf8)
}
} }
class Person: CoreStoreObject { class Person: CoreStoreObject {
let title = Value.Required<String>( @Field.Stored(
"title", "title",
initial: "Mr.", customSetter: { (object, field, newValue) in
customSetter: Person.setTitle field.primitiveValue = newValue
object.$displayName.primitiveValue = nil
}
) )
var title: String = "Mr."
let name = Value.Required<String>(
@Field.Stored(
"name", "name",
initial: "", customSetter: { (object, field, newValue) in
customSetter: Person.setName field.primitiveValue = newValue
object.$displayName.primitiveValue = nil
}
) )
var name: String = ""
let displayName = Value.Optional<String>(
@Field.Virtual(
"displayName", "displayName",
isTransient: true, customGetter: Person.getDisplayName(_:_:),
customGetter: Person.getDisplayName(_:),
affectedByKeyPaths: Person.keyPathsAffectingDisplayName() affectedByKeyPaths: Person.keyPathsAffectingDisplayName()
) )
var displayName: String?
let spouse = Relationship.ToOne<Person>("spouse") @Field.Virtual(
"customType",
let pets = Relationship.ToManyUnordered<Animal>("pets", inverse: { $0.master }) customGetter: { (object, field) in
private let _spouse = Relationship.ToOne<Person>("_spouseInverse", inverse: { $0.spouse }) if let value = field.primitiveValue {
return value
private static func setTitle(_ partialObject: PartialObject<Person>, _ newValue: String) { }
let value = CustomType()
partialObject.setPrimitiveValue(newValue, for: { $0.title }) field.primitiveValue = value
partialObject.setPrimitiveValue(nil, for: { $0.displayName }) return value
}
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 }) var customField: CustomType
let displayName = "\(title) \(name)"
partialObject.setPrimitiveValue(displayName, for: { $0.displayName }) @Field.Coded(
return displayName "job",
coder: (
encode: { $0.toData() },
decode: { $0.flatMap(Job.init(data:)) ?? .unemployed }
),
dynamicInitialValue: {
Job.allCases.randomElement()!
}
)
var job: Job
@Field.Relationship("spouse")
var spouse: Person?
@Field.Relationship("pets", inverse: \.$master)
var pets: Set<Animal>
@Field.Relationship("_spouseInverse", inverse: \.$spouse)
private var spouseInverse: Person?
private static func getDisplayName(_ object: ObjectProxy<Person>, _ field: ObjectProxy<Person>.FieldProxy<String?>) -> String? {
if let value = field.primitiveValue {
return value
}
let title = object.$title.value
let name = object.$name.value
let value = "\(title) \(name)"
field.primitiveValue = value
return value
} }
static func keyPathsAffectingDisplayName() -> Set<String> { private static func keyPathsAffectingDisplayName() -> Set<String> {
return [ return [
String(keyPath: \Person.title), String(keyPath: \Person.$title),
String(keyPath: \Person.name) String(keyPath: \Person.$name)
] ]
} }
} }
@@ -126,25 +197,25 @@ class DynamicModelTests: BaseTestDataTestCase {
modelVersion: "V1", modelVersion: "V1",
entities: [ entities: [
Entity<Animal>("Animal"), Entity<Animal>("Animal"),
Entity<Dog>("Dog"), Entity<Dog>("Dog", indexes: [[\Dog.$nickname, \Dog.$age]]),
Entity<Person>("Person") Entity<Person>("Person")
], ],
versionLock: [ versionLock: [
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a], "Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
"Dog": [0xe3f0afeb109b283a, 0x29998d292938eb61, 0x6aab788333cfc2a3, 0x492ff1d295910ea7], "Dog": [0xad6de93adc5565d, 0x7897e51253eba5a3, 0xd12b9ce0b13600f3, 0x5a4827cd794cd15e],
"Person": [0x2831cf046084d96d, 0xbe19b13ace54641, 0x635a082728b0f6f0, 0x3d4ef2dd4b74a87c] "Person": [0xf3e6ba6016bbedc6, 0x50dedf64f0eba490, 0xa32088a0ee83468d, 0xb72d1d0b37bd0992]
] ]
) )
) )
self.prepareStack(dataStack, configurations: [nil]) { (stack) in self.prepareStack(dataStack, configurations: [nil]) { (stack) in
let k1 = String(keyPath: \Animal.species) let k1 = String(keyPath: \Animal.$species)
XCTAssertEqual(k1, "species") XCTAssertEqual(k1, "species")
let k2 = String(keyPath: \Dog.species) let k2 = String(keyPath: \Dog.$species)
XCTAssertEqual(k2, "species") XCTAssertEqual(k2, "species")
let k3 = String(keyPath: \Dog.nickname) let k3 = String(keyPath: \Dog.$nickname)
XCTAssertEqual(k3, "nickname") XCTAssertEqual(k3, "nickname")
let updateDone = self.expectation(description: "update-done") let updateDone = self.expectation(description: "update-done")
@@ -156,27 +227,28 @@ class DynamicModelTests: BaseTestDataTestCase {
asynchronous: { (transaction) in asynchronous: { (transaction) in
let animal = transaction.create(Into<Animal>()) let animal = transaction.create(Into<Animal>())
XCTAssertEqual(animal.species.value, "Swift") XCTAssertEqual(animal.species, "Swift")
XCTAssertTrue(type(of: animal.species.value) == String.self) XCTAssertTrue(type(of: animal.species) == String.self)
XCTAssertEqual(animal.color, Color.blue)
animal.species .= "Sparrow" animal.species = "Sparrow"
XCTAssertEqual(animal.species.value, "Sparrow") XCTAssertEqual(animal.species, "Sparrow")
animal.color .= .yellow animal.color = .yellow
XCTAssertEqual(animal.color.value, Color.yellow) XCTAssertEqual(animal.color, Color.yellow)
for property in Animal.metaProperties(includeSuperclasses: true) { for property in Animal.metaProperties(includeSuperclasses: true) {
switch property.keyPath { switch property.keyPath {
case String(keyPath: \Animal.species): case String(keyPath: \Animal.$species):
XCTAssertTrue(property is ValueContainer<Animal>.Required<String>) XCTAssertTrue(property is FieldContainer<Animal>.Stored<String>)
case String(keyPath: \Animal.master): case String(keyPath: \Animal.$master):
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>) XCTAssertTrue(property is FieldContainer<Animal>.Relationship<Person?>)
case String(keyPath: \Animal.color): case String(keyPath: \Animal.$color):
XCTAssertTrue(property is TransformableContainer<Animal>.Optional<Color>) XCTAssertTrue(property is FieldContainer<Animal>.Coded<Color?>)
default: default:
XCTFail("Unknown KeyPath: \"\(property.keyPath)\"") XCTFail("Unknown KeyPath: \"\(property.keyPath)\"")
@@ -184,64 +256,51 @@ class DynamicModelTests: BaseTestDataTestCase {
} }
let dog = transaction.create(Into<Dog>()) let dog = transaction.create(Into<Dog>())
XCTAssertEqual(dog.species.value, "Swift") XCTAssertEqual(dog.species, "Swift")
XCTAssertEqual(dog.nickname.value, nil) XCTAssertEqual(dog.age, 1)
XCTAssertEqual(dog.age.value, 1) XCTAssertTrue(Dog.commonNicknames.contains(dog.nickname))
for property in Dog.metaProperties(includeSuperclasses: true) { for property in Dog.metaProperties(includeSuperclasses: true) {
switch property.keyPath { switch property.keyPath {
case String(keyPath: \Dog.species): case String(keyPath: \Dog.$species):
XCTAssertTrue(property is ValueContainer<Animal>.Required<String>) XCTAssertTrue(property is FieldContainer<Animal>.Stored<String>)
case String(keyPath: \Dog.master): case String(keyPath: \Dog.$master):
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>) XCTAssertTrue(property is FieldContainer<Animal>.Relationship<Person?>)
case String(keyPath: \Dog.color): case String(keyPath: \Dog.$color):
XCTAssertTrue(property is TransformableContainer<Animal>.Optional<Color>) XCTAssertTrue(property is FieldContainer<Animal>.Coded<Color?>)
case String(keyPath: \Dog.nickname): case String(keyPath: \Dog.$nickname):
XCTAssertTrue(property is ValueContainer<Dog>.Optional<String>) XCTAssertTrue(property is FieldContainer<Dog>.Stored<String>)
case String(keyPath: \Dog.age): case String(keyPath: \Dog.$age):
XCTAssertTrue(property is ValueContainer<Dog>.Required<Int>) XCTAssertTrue(property is FieldContainer<Dog>.Stored<Int>)
case String(keyPath: \Dog.friends): case String(keyPath: \Dog.$friends):
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyOrdered<Dog>) XCTAssertTrue(property is FieldContainer<Dog>.Relationship<[Dog]>)
case String(keyPath: \Dog.friendedBy): case String(keyPath: \Dog.$friendedBy):
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyUnordered<Dog>) XCTAssertTrue(property is FieldContainer<Dog>.Relationship<Set<Dog>>)
default: default:
XCTFail("Unknown KeyPath: \"\(property.keyPath)\"") XCTFail("Unknown KeyPath: \"\(property.keyPath)\"")
} }
} }
// #if swift(>=5.1) let didSetObserver = dog.observe(\.$species, options: [.new, .old]) { (object, change) in
//
// let dogKeyPathBuilder = Dog.keyPathBuilder()
// XCTAssertEqual(dogKeyPathBuilder.species.keyPathString, "SELF.species")
// XCTAssertEqual(dogKeyPathBuilder.master.title.keyPathString, "SELF.master.title")
// let a = dogKeyPathBuilder.master
// let b = dogKeyPathBuilder.master.spouse
// let c = dogKeyPathBuilder.master.spouse.pets
// let d = dogKeyPathBuilder.master.spouse.pets.color
// XCTAssertEqual(dogKeyPathBuilder.master.spouse.pets.color.keyPathString, "SELF.master.spouse.pets.color")
//
// #endif
let didSetObserver = dog.species.observe(options: [.new, .old]) { (object, change) in
XCTAssertEqual(object, dog) XCTAssertEqual(object, dog)
XCTAssertEqual(change.kind, .setting) XCTAssertEqual(change.kind, .setting)
XCTAssertEqual(change.newValue, "Dog") XCTAssertEqual(change.newValue, "Dog")
XCTAssertEqual(change.oldValue, "Swift") XCTAssertEqual(change.oldValue, "Swift")
XCTAssertFalse(change.isPrior) XCTAssertFalse(change.isPrior)
XCTAssertEqual(object.species.value, "Dog") XCTAssertEqual(object.species, "Dog")
didSetObserverDone.fulfill() didSetObserverDone.fulfill()
} }
let willSetObserver = dog.species.observe(options: [.new, .old, .prior]) { (object, change) in let willSetObserver = dog.observe(\.$species, options: [.new, .old, .prior]) { (object, change) in
XCTAssertEqual(object, dog) XCTAssertEqual(object, dog)
XCTAssertEqual(change.kind, .setting) XCTAssertEqual(change.kind, .setting)
@@ -250,28 +309,31 @@ class DynamicModelTests: BaseTestDataTestCase {
if change.isPrior { if change.isPrior {
XCTAssertNil(change.newValue) XCTAssertNil(change.newValue)
XCTAssertEqual(object.species.value, "Swift") XCTAssertEqual(object.species, "Swift")
willSetPriorObserverDone.fulfill() willSetPriorObserverDone.fulfill()
} }
else { else {
XCTAssertEqual(change.newValue, "Dog") XCTAssertEqual(change.newValue, "Dog")
XCTAssertEqual(object.species.value, "Dog") XCTAssertEqual(object.species, "Dog")
willSetNotPriorObserverDone.fulfill() willSetNotPriorObserverDone.fulfill()
} }
} }
dog.species .= "Dog" dog.species = "Dog"
XCTAssertEqual(dog.species.value, "Dog") XCTAssertEqual(dog.species, "Dog")
didSetObserver.invalidate() didSetObserver.invalidate()
willSetObserver.invalidate() willSetObserver.invalidate()
dog.nickname .= "Spot" dog.nickname = "Spot"
XCTAssertEqual(dog.nickname.value, "Spot") XCTAssertEqual(dog.nickname, "Spot")
let person = transaction.create(Into<Person>()) let person = transaction.create(Into<Person>())
XCTAssertTrue(person.pets.value.isEmpty) XCTAssertTrue(person.pets.isEmpty)
XCTAssertEqual(person.customField.string, "customString")
let initialJob = person.job
XCTAssertTrue(Job.allCases.contains(initialJob))
XCTAssertEqual( XCTAssertEqual(
person.rawObject! person.rawObject!
@@ -280,7 +342,7 @@ class DynamicModelTests: BaseTestDataTestCase {
["title", "name"] ["title", "name"]
) )
person.name .= "Joe" person.name = "Joe"
XCTAssertEqual(person.rawObject!.value(forKey: "name") as! String?, "Joe") XCTAssertEqual(person.rawObject!.value(forKey: "name") as! String?, "Joe")
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "Mr. Joe") XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "Mr. Joe")
@@ -288,46 +350,66 @@ class DynamicModelTests: BaseTestDataTestCase {
person.rawObject!.setValue("AAAA", forKey: "displayName") person.rawObject!.setValue("AAAA", forKey: "displayName")
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "AAAA") XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "AAAA")
person.name .= "John" person.name = "John"
XCTAssertEqual(person.name.value, "John") XCTAssertEqual(person.name, "John")
XCTAssertEqual(person.displayName.value, "Mr. John") // Custom getter XCTAssertEqual(person.displayName, "Mr. John") // Custom getter
let personSnapshot1 = person.asSnapshot(in: transaction)! let personSnapshot1 = person.asSnapshot(in: transaction)!
XCTAssertEqual(person.name.value, personSnapshot1.name) XCTAssertEqual(person.name, personSnapshot1.$name)
XCTAssertEqual(person.title.value, personSnapshot1.title) XCTAssertEqual(person.title, personSnapshot1.$title)
XCTAssertEqual(person.displayName.value, personSnapshot1.displayName) XCTAssertEqual(person.displayName, personSnapshot1.$displayName)
XCTAssertEqual(person.job, personSnapshot1.$job)
person.title .= "Sir" person.title = "Sir"
XCTAssertEqual(person.displayName.value, "Sir John") XCTAssertEqual(person.displayName, "Sir John")
XCTAssertEqual(personSnapshot1.name, "John") XCTAssertEqual(personSnapshot1.$name, "John")
XCTAssertEqual(personSnapshot1.title, "Mr.") XCTAssertEqual(personSnapshot1.$title, "Mr.")
XCTAssertEqual(personSnapshot1.displayName, "Mr. John") XCTAssertEqual(personSnapshot1.$displayName, "Mr. John")
person.customField.string = "newCustomString"
XCTAssertEqual(person.customField.string, "newCustomString")
person.job = .engineer
XCTAssertEqual(person.job, .engineer)
let personSnapshot2 = person.asSnapshot(in: transaction)! let personSnapshot2 = person.asSnapshot(in: transaction)!
XCTAssertEqual(person.name.value, personSnapshot2.name) XCTAssertEqual(person.name, personSnapshot2.$name)
XCTAssertEqual(person.title.value, personSnapshot2.title) XCTAssertEqual(person.title, personSnapshot2.$title)
XCTAssertEqual(person.displayName.value, personSnapshot2.displayName) XCTAssertEqual(person.displayName, personSnapshot2.$displayName)
XCTAssertEqual(person.job, personSnapshot2.$job)
var personSnapshot3 = personSnapshot2 var personSnapshot3 = personSnapshot2
personSnapshot3.name = "James" personSnapshot3.$name = "James"
XCTAssertEqual(personSnapshot1.name, "John") XCTAssertEqual(personSnapshot1.$name, "John")
XCTAssertEqual(personSnapshot1.displayName, "Mr. John") XCTAssertEqual(personSnapshot1.$displayName, "Mr. John")
XCTAssertEqual(personSnapshot2.name, "John") XCTAssertEqual(personSnapshot1.$job, initialJob)
XCTAssertEqual(personSnapshot2.displayName, "Sir John") XCTAssertEqual(personSnapshot2.$name, "John")
XCTAssertEqual(personSnapshot3.name, "James") XCTAssertEqual(personSnapshot2.$displayName, "Sir John")
XCTAssertEqual(personSnapshot3.displayName, "Sir John") XCTAssertEqual(personSnapshot2.$job, .engineer)
XCTAssertEqual(personSnapshot3.$name, "James")
XCTAssertEqual(personSnapshot3.$displayName, "Sir John")
XCTAssertEqual(personSnapshot3.$job, .engineer)
person.pets.value.insert(dog) person.pets.insert(dog)
XCTAssertEqual(person.pets.count, 1) XCTAssertEqual(person.pets.count, 1)
XCTAssertEqual(person.pets.value.first, dog) XCTAssertEqual(person.pets.first, dog)
XCTAssertEqual(person.pets.value.first?.master.value, person) XCTAssertEqual(person.pets.first?.master, person)
XCTAssertEqual(dog.master.value, person) XCTAssertEqual(dog.master, person)
XCTAssertEqual(dog.master.value?.pets.value.first, dog) XCTAssertEqual(dog.master?.pets.first, dog)
}, },
success: { _ in success: { _ in
let person = try! stack.fetchOne(From<Person>())
XCTAssertNotNil(person)
let personPublisher = person!.asPublisher(in: stack)
XCTAssertEqual(personPublisher.$name, "John")
XCTAssertEqual(personPublisher.$displayName, "Sir John")
XCTAssertEqual(personPublisher.$job, .engineer)
updateDone.fulfill() updateDone.fulfill()
}, },
failure: { _ in failure: { _ in
@@ -338,59 +420,67 @@ class DynamicModelTests: BaseTestDataTestCase {
stack.perform( stack.perform(
asynchronous: { (transaction) in asynchronous: { (transaction) in
let p1 = Where<Animal>({ $0.species == "Sparrow" }) let p1 = Where<Animal>({ $0.$species == "Sparrow" })
XCTAssertEqual(p1.predicate, NSPredicate(format: "%K == %@", "species", "Sparrow")) XCTAssertEqual(p1.predicate, NSPredicate(format: "%K == %@", "species", "Sparrow"))
let bird = try transaction.fetchOne(From<Animal>(), p1) let bird = try transaction.fetchOne(From<Animal>(), p1)
XCTAssertNotNil(bird) XCTAssertNotNil(bird)
XCTAssertEqual(bird!.species.value, "Sparrow") XCTAssertEqual(bird!.species, "Sparrow")
XCTAssertEqual(bird!.color, Color.yellow)
let p2 = Where<Dog>({ $0.nickname == "Spot" }) let p2 = Where<Dog>({ $0.$nickname == "Spot" })
XCTAssertEqual(p2.predicate, NSPredicate(format: "%K == %@", "nickname", "Spot")) XCTAssertEqual(p2.predicate, NSPredicate(format: "%K == %@", "nickname", "Spot"))
let dog = try transaction.fetchOne(From<Dog>().where(\.nickname == "Spot")) let dog = try transaction.fetchOne(From<Dog>().where(\.$nickname == "Spot"))
XCTAssertNotNil(dog) XCTAssertNotNil(dog)
XCTAssertEqual(dog!.nickname.value, "Spot") XCTAssertEqual(dog!.nickname, "Spot")
XCTAssertEqual(dog!.species.value, "Dog") XCTAssertEqual(dog!.species, "Dog")
let person = try transaction.fetchOne(From<Person>()) let person = try transaction.fetchOne(From<Person>())
XCTAssertNotNil(person) XCTAssertNotNil(person)
XCTAssertEqual(person!.pets.value.first, dog) XCTAssertEqual(person!.name, "John")
XCTAssertEqual(person!.title, "Sir")
XCTAssertEqual(person!.displayName, "Sir John")
XCTAssertEqual(person!.customField.string, "customString")
XCTAssertEqual(person!.job, .engineer)
XCTAssertEqual(person!.pets.first, dog)
let p3 = Where<Dog>({ $0.age == 10 }) let p3 = Where<Dog>({ $0.$age == 10 })
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10)) XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
let totalAge = try transaction.queryValue(From<Dog>().select(Int.self, .sum(\Dog.age))) let totalAge = try transaction.queryValue(
From<Dog>().select(Int.self, .sum(\.$age))
)
XCTAssertEqual(totalAge, 1) XCTAssertEqual(totalAge, 1)
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>() From<Dog>()
.where(\Animal.species == "Dog" && \Dog.age == 10) .where(\Animal.$species == "Dog" && \Dog.$age == 10)
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>() From<Dog>()
.where(\Dog.age == 10 && \Animal.species == "Dog") .where(\Dog.$age == 10 && \Animal.$species == "Dog")
.orderBy(.ascending({ $0.species })) .orderBy(.ascending({ $0.$species }))
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
Where<Dog>({ $0.age > 10 && $0.age <= 15 }) Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
Where<Dog>({ $0.species == "Dog" && $0.age == 10 }) Where<Dog>({ $0.$species == "Dog" && $0.$age == 10 })
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
Where<Dog>({ $0.age == 10 && $0.species == "Dog" }) Where<Dog>({ $0.$age == 10 && $0.$species == "Dog" })
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
Where<Dog>({ $0.age > 10 && $0.age <= 15 }) Where<Dog>({ $0.$age > 10 && $0.$age <= 15 })
) )
_ = try transaction.fetchAll( _ = try transaction.fetchAll(
From<Dog>(), From<Dog>(),
(\Dog.age > 10 && \Dog.age <= 15) (\Dog.$age > 10 && \Dog.$age <= 15)
) )
}, },
success: { _ in success: { _ in
@@ -402,15 +492,20 @@ class DynamicModelTests: BaseTestDataTestCase {
XCTFail() XCTFail()
} }
) )
self.waitAndCheckExpectations() }
self.waitForExpectations(timeout: 10, handler: { _ in })
self.addTeardownBlock {
dataStack.unsafeRemoveAllPersistentStoresAndWait()
} }
} }
@objc @objc
dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() { dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() {
XCTAssertEqual(String(keyPath: \Animal.species), "species") XCTAssertEqual(String(keyPath: \Animal.$species), "species")
XCTAssertEqual(String(keyPath: \Dog.species), "species") XCTAssertEqual(String(keyPath: \Dog.$species), "species")
} }
@nonobjc @nonobjc
+2 -2
View File
@@ -132,8 +132,8 @@ final class FetchTests: BaseTestDataTestCase {
} }
) )
} }
self.waitAndCheckExpectations()
} }
self.waitAndCheckExpectations()
} }
@objc @objc
@@ -267,8 +267,8 @@ final class FetchTests: BaseTestDataTestCase {
} }
) )
} }
self.waitAndCheckExpectations()
} }
self.waitAndCheckExpectations()
} }
@objc @objc
+1
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
+1
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
+1
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
+15 -16
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
@@ -31,7 +32,6 @@ import CoreStore
// MARK: - ListObserverTests // MARK: - ListObserverTests
@available(macOS 10.12, *)
class ListObserverTests: BaseTestDataTestCase { class ListObserverTests: BaseTestDataTestCase {
@objc @objc
@@ -53,7 +53,7 @@ class ListObserverTests: BaseTestDataTestCase {
var events = 0 var events = 0
let willChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -67,7 +67,7 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 0 return events == 0
} }
) )
let didInsertSectionExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"), forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -87,7 +87,7 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 1 return events == 1
} }
) )
let didInsertObjectExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"), forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -119,7 +119,7 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 2 return events == 2
} }
) )
let didChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -184,7 +184,7 @@ class ListObserverTests: BaseTestDataTestCase {
var events = 0 var events = 0
let willChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -199,7 +199,7 @@ class ListObserverTests: BaseTestDataTestCase {
} }
) )
let didUpdateObjectExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"), forNotification: NSNotification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -250,7 +250,7 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 1 || events == 2 return events == 1 || events == 2
} }
) )
let didChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -329,7 +329,7 @@ class ListObserverTests: BaseTestDataTestCase {
var events = 0 var events = 0
let willChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -343,7 +343,7 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 0 return events == 0
} }
) )
let didMoveObjectExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"), forNotification: NSNotification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -376,7 +376,7 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 1 return events == 1
} }
) )
let didChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -437,7 +437,7 @@ class ListObserverTests: BaseTestDataTestCase {
var events = 0 var events = 0
let willChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -451,7 +451,7 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 0 return events == 0
} }
) )
let didUpdateObjectExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"), forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -480,7 +480,7 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 1 || events == 2 return events == 1 || events == 2
} }
) )
let didDeleteSectionExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"), forNotification: NSNotification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -508,7 +508,7 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 3 return events == 3
} }
) )
let didChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -551,7 +551,6 @@ class ListObserverTests: BaseTestDataTestCase {
// MARK: TestListObserver // MARK: TestListObserver
@available(macOS 10.12, *)
class TestListObserver: ListSectionObserver { class TestListObserver: ListSectionObserver {
// MARK: ListObserver // MARK: ListObserver
+3 -5
View File
@@ -31,7 +31,6 @@ import CoreStore
// MARK: - ObjectObserverTests // MARK: - ObjectObserverTests
@available(macOS 10.12, *)
class ObjectObserverTests: BaseTestDataTestCase { class ObjectObserverTests: BaseTestDataTestCase {
@objc @objc
@@ -57,7 +56,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
var events = 0 var events = 0
let willUpdateExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"), forNotification: NSNotification.Name(rawValue: "objectMonitor:willUpdateObject:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -74,7 +73,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
return events == 0 return events == 0
} }
) )
let didUpdateExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "objectMonitor:didUpdateObject:changedPersistentKeys:"), forNotification: NSNotification.Name(rawValue: "objectMonitor:didUpdateObject:changedPersistentKeys:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -154,7 +153,7 @@ class ObjectObserverTests: BaseTestDataTestCase {
var events = 0 var events = 0
let didDeleteExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"), forNotification: NSNotification.Name(rawValue: "objectMonitor:didDeleteObject:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -203,7 +202,6 @@ class ObjectObserverTests: BaseTestDataTestCase {
// MARK: TestObjectObserver // MARK: TestObjectObserver
@available(macOS 10.12, *)
class TestObjectObserver: ObjectObserver { class TestObjectObserver: ObjectObserver {
typealias ObjectEntityType = TestEntity1 typealias ObjectEntityType = TestEntity1
+1 -1
View File
@@ -31,7 +31,6 @@ import CoreStore
// MARK: - ObjectPublisherTests // MARK: - ObjectPublisherTests
@available(macOS 10.12, *)
class ObjectPublisherTests: BaseTestDataTestCase { class ObjectPublisherTests: BaseTestDataTestCase {
@objc @objc
@@ -144,6 +143,7 @@ class ObjectPublisherTests: BaseTestDataTestCase {
XCTFail() XCTFail()
} }
) )
self.waitAndCheckExpectations() self.waitAndCheckExpectations()
withExtendedLifetime(objectPublisher, {}) withExtendedLifetime(objectPublisher, {})
+1
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
+1
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
+6 -3
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
@@ -31,7 +32,6 @@ import CoreStore
//MARK: - SectionByTests //MARK: - SectionByTests
@available(macOS 10.12, *)
final class SectionByTests: XCTestCase { final class SectionByTests: XCTestCase {
@objc @objc
@@ -41,11 +41,14 @@ final class SectionByTests: XCTestCase {
let sectionBy = SectionBy<NSManagedObject>("key") let sectionBy = SectionBy<NSManagedObject>("key")
XCTAssertEqual(sectionBy.sectionKeyPath, "key") XCTAssertEqual(sectionBy.sectionKeyPath, "key")
XCTAssertEqual(sectionBy.sectionIndexTransformer("key"), "key") XCTAssertNil(sectionBy.sectionIndexTransformer("key"))
} }
do { do {
let sectionBy = SectionBy<NSManagedObject>("key") { $0.flatMap { "\($0):suffix" } } let sectionBy = SectionBy<NSManagedObject>(
"key",
sectionIndexTransformer: { $0.flatMap { "\($0):suffix" } }
)
XCTAssertEqual(sectionBy.sectionKeyPath, "key") XCTAssertEqual(sectionBy.sectionKeyPath, "key")
XCTAssertEqual(sectionBy.sectionIndexTransformer("key"), "key:suffix") XCTAssertEqual(sectionBy.sectionIndexTransformer("key"), "key:suffix")
XCTAssertNil(sectionBy.sectionIndexTransformer(nil)) XCTAssertNil(sectionBy.sectionIndexTransformer(nil))
+1
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
+3
View File
@@ -23,6 +23,9 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest
@testable @testable
import CoreStore import CoreStore
+26 -75
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
@@ -83,21 +84,11 @@ final class StorageInterfaceTests: XCTestCase {
let store = SQLiteStore() let store = SQLiteStore()
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType) XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertNil(store.configuration) XCTAssertNil(store.configuration)
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) { XCTAssertEqual(
store.storeOptions as NSDictionary?,
XCTAssertEqual( [NSSQLitePragmasOption: ["journal_mode": "WAL"],
store.storeOptions as NSDictionary?, NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
[NSSQLitePragmasOption: ["journal_mode": "WAL"], )
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(store.fileURL, SQLiteStore.defaultFileURL) XCTAssertEqual(store.fileURL, SQLiteStore.defaultFileURL)
XCTAssertTrue(store.migrationMappingProviders.isEmpty) XCTAssertTrue(store.migrationMappingProviders.isEmpty)
@@ -123,21 +114,11 @@ final class StorageInterfaceTests: XCTestCase {
) )
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType) XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1") XCTAssertEqual(store.configuration, "config1")
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) { XCTAssertEqual(
store.storeOptions as NSDictionary?,
XCTAssertEqual( [NSSQLitePragmasOption: ["journal_mode": "WAL"],
store.storeOptions as NSDictionary?, NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
[NSSQLitePragmasOption: ["journal_mode": "WAL"], )
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(store.fileURL, fileURL) XCTAssertEqual(store.fileURL, fileURL)
XCTAssertEqual(store.migrationMappingProviders as! [XcodeSchemaMappingProvider], [mappingProvider]) XCTAssertEqual(store.migrationMappingProviders as! [XcodeSchemaMappingProvider], [mappingProvider])
@@ -160,21 +141,11 @@ final class StorageInterfaceTests: XCTestCase {
) )
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType) XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1") XCTAssertEqual(store.configuration, "config1")
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) { XCTAssertEqual(
store.storeOptions as NSDictionary?,
XCTAssertEqual( [NSSQLitePragmasOption: ["journal_mode": "WAL"],
store.storeOptions as NSDictionary?, NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
[NSSQLitePragmasOption: ["journal_mode": "WAL"], )
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.defaultRootDirectory) XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.defaultRootDirectory)
XCTAssertEqual(store.fileURL.lastPathComponent, fileName) XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
@@ -209,21 +180,11 @@ final class StorageInterfaceTests: XCTestCase {
let store = SQLiteStore.legacy() let store = SQLiteStore.legacy()
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType) XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertNil(store.configuration) XCTAssertNil(store.configuration)
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) { XCTAssertEqual(
store.storeOptions as NSDictionary?,
XCTAssertEqual( [NSSQLitePragmasOption: ["journal_mode": "WAL"],
store.storeOptions as NSDictionary?, NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
[NSSQLitePragmasOption: ["journal_mode": "WAL"], )
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(store.fileURL, SQLiteStore.legacyDefaultFileURL) XCTAssertEqual(store.fileURL, SQLiteStore.legacyDefaultFileURL)
XCTAssertTrue(store.migrationMappingProviders.isEmpty) XCTAssertTrue(store.migrationMappingProviders.isEmpty)
@@ -246,21 +207,11 @@ final class StorageInterfaceTests: XCTestCase {
) )
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType) XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1") XCTAssertEqual(store.configuration, "config1")
if #available(iOS 11.0, macOS 10.13, tvOS 11.0, *) { XCTAssertEqual(
store.storeOptions as NSDictionary?,
XCTAssertEqual( [NSSQLitePragmasOption: ["journal_mode": "WAL"],
store.storeOptions as NSDictionary?, NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
[NSSQLitePragmasOption: ["journal_mode": "WAL"], )
NSBinaryStoreInsecureDecodingCompatibilityOption: true] as NSDictionary
)
}
else {
XCTAssertEqual(
store.storeOptions as NSDictionary?,
[NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary
)
}
XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.legacyDefaultRootDirectory) XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.legacyDefaultRootDirectory)
XCTAssertEqual(store.fileURL.lastPathComponent, fileName) XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
+6 -8
View File
@@ -383,8 +383,6 @@ final class TransactionTests: BaseTestCase {
} }
} }
@available(macOS 10.12, *)
@objc @objc
dynamic func test_ThatSynchronousTransactions_CanCommitWithoutWaitingForMerges() { dynamic func test_ThatSynchronousTransactions_CanCommitWithoutWaitingForMerges() {
@@ -400,7 +398,7 @@ final class TransactionTests: BaseTestCase {
XCTAssertFalse(monitor.hasObjects()) XCTAssertFalse(monitor.hasObjects())
var events = 0 var events = 0
let willChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorWillChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -414,7 +412,7 @@ final class TransactionTests: BaseTestCase {
return events == 0 return events == 0
} }
) )
let didInsertObjectExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"), forNotification: NSNotification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -444,7 +442,7 @@ final class TransactionTests: BaseTestCase {
return events == 1 return events == 1
} }
) )
let didChangeExpectation = self.expectation( _ = self.expectation(
forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"), forNotification: NSNotification.Name(rawValue: "listMonitorDidChange:"),
object: observer, object: observer,
handler: { (note) -> Bool in handler: { (note) -> Bool in
@@ -622,8 +620,8 @@ final class TransactionTests: BaseTestCase {
} }
) )
} }
self.waitAndCheckExpectations()
} }
self.waitAndCheckExpectations()
} }
@objc @objc
@@ -755,8 +753,8 @@ final class TransactionTests: BaseTestCase {
} }
) )
} }
self.waitAndCheckExpectations()
} }
self.waitAndCheckExpectations()
} }
@objc @objc
@@ -896,8 +894,8 @@ final class TransactionTests: BaseTestCase {
} }
) )
} }
self.waitAndCheckExpectations()
} }
self.waitAndCheckExpectations()
} }
@objc @objc
+1
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
+28 -34
View File
@@ -23,6 +23,7 @@
// SOFTWARE. // SOFTWARE.
// //
import CoreData
import XCTest import XCTest
@testable @testable
@@ -67,7 +68,7 @@ final class WhereTests: XCTestCase {
dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() { dynamic func test_ThatDynamicModelKeyPaths_CanBeCreated() {
XCTAssertAllEqual(String(keyPath: \TestEntity1.testEntityID), "testEntityID") XCTAssertAllEqual(String(keyPath: \TestEntity1.testEntityID), "testEntityID")
XCTAssertAllEqual(String(keyPath: \Animal.color), "color") XCTAssertAllEqual(String(keyPath: \Animal.$color), "color")
} }
@objc @objc
@@ -77,17 +78,10 @@ final class WhereTests: XCTestCase {
do { do {
// let keyPathBuilder = TestEntity1.keyPathBuilder()
// let kp = \TestEntity1.testToOne
// print(keyPathBuilder.testString)
// print(keyPathBuilder.testToOne)
// print(keyPathBuilder.testToOne.testEntityID)
XCTAssertAllEqual( XCTAssertAllEqual(
#keyPath(TestEntity1.testToOne.testEntityID), #keyPath(TestEntity1.testToOne.testEntityID),
(\TestEntity1.testToOne ~ \.testEntityID).description, (\TestEntity1.testToOne ~ \.testEntityID).description,
String(keyPath: \TestEntity1.testToOne ~ \.testEntityID) String(keyPath: \TestEntity1.testToOne ~ \.testEntityID)
// keyPathBuilder.testToOne.testEntityID.keyPathString
) )
XCTAssertAllEqual( XCTAssertAllEqual(
#keyPath(TestEntity1.testToOne.testToOne.testToManyUnordered), #keyPath(TestEntity1.testToOne.testToOne.testToManyUnordered),
@@ -104,18 +98,18 @@ final class WhereTests: XCTestCase {
XCTAssertAllEqual( XCTAssertAllEqual(
"master.pets", "master.pets",
(\Animal.master ~ \.pets).description, (\Animal.$master ~ \.$pets).description,
String(keyPath: \Animal.master ~ \.pets) String(keyPath: \Animal.$master ~ \.$pets)
) )
XCTAssertAllEqual( XCTAssertAllEqual(
"master.pets.species", "master.pets.species",
(\Animal.master ~ \.pets ~ \.species).description, (\Animal.$master ~ \.$pets ~ \.$species).description,
String(keyPath: \Animal.master ~ \.pets ~ \.species) String(keyPath: \Animal.$master ~ \.$pets ~ \.$species)
) )
XCTAssertAllEqual( XCTAssertAllEqual(
"master.pets.master", "master.pets.master",
(\Animal.master ~ \.pets ~ \.master).description, (\Animal.$master ~ \.$pets ~ \.$master).description,
String(keyPath: \Animal.master ~ \.pets ~ \.master) String(keyPath: \Animal.$master ~ \.$pets ~ \.$master)
) )
} }
} }
@@ -138,8 +132,8 @@ final class WhereTests: XCTestCase {
XCTAssertAllEqual( XCTAssertAllEqual(
"master.pets.@count", "master.pets.@count",
(\Animal.master ~ \.pets).count().description, (\Animal.$master ~ \.$pets).count().description,
String(keyPath: (\Animal.master ~ \.pets).count()) String(keyPath: (\Animal.$master ~ \.$pets).count())
) )
} }
} }
@@ -162,13 +156,13 @@ final class WhereTests: XCTestCase {
XCTAssertAllEqual( XCTAssertAllEqual(
"ANY master.pets", "ANY master.pets",
(\Animal.master ~ \.pets).any().description, (\Animal.$master ~ \.$pets).any().description,
String(keyPath: (\Animal.master ~ \.pets).any()) String(keyPath: (\Animal.$master ~ \.$pets).any())
) )
XCTAssertAllEqual( XCTAssertAllEqual(
"ANY master.pets.species", "ANY master.pets.species",
(\Animal.master ~ \.pets ~ \.species).any().description, (\Animal.$master ~ \.$pets ~ \.$species).any().description,
String(keyPath: (\Animal.master ~ \.pets ~ \.species).any()) String(keyPath: (\Animal.$master ~ \.$pets ~ \.$species).any())
) )
} }
} }
@@ -191,13 +185,13 @@ final class WhereTests: XCTestCase {
XCTAssertAllEqual( XCTAssertAllEqual(
"ALL master.pets", "ALL master.pets",
(\Animal.master ~ \.pets).all().description, (\Animal.$master ~ \.$pets).all().description,
String(keyPath: (\Animal.master ~ \.pets).all()) String(keyPath: (\Animal.$master ~ \.$pets).all())
) )
XCTAssertAllEqual( XCTAssertAllEqual(
"ALL master.pets.species", "ALL master.pets.species",
(\Animal.master ~ \.pets ~ \.species).all().description, (\Animal.$master ~ \.$pets ~ \.$species).all().description,
String(keyPath: (\Animal.master ~ \.pets ~ \.species).all()) String(keyPath: (\Animal.$master ~ \.$pets ~ \.$species).all())
) )
} }
} }
@@ -220,13 +214,13 @@ final class WhereTests: XCTestCase {
XCTAssertAllEqual( XCTAssertAllEqual(
"NONE master.pets", "NONE master.pets",
(\Animal.master ~ \.pets).none().description, (\Animal.$master ~ \.$pets).none().description,
String(keyPath: (\Animal.master ~ \.pets).none()) String(keyPath: (\Animal.$master ~ \.$pets).none())
) )
XCTAssertAllEqual( XCTAssertAllEqual(
"NONE master.pets.species", "NONE master.pets.species",
(\Animal.master ~ \.pets ~ \.species).none().description, (\Animal.$master ~ \.$pets ~ \.$species).none().description,
String(keyPath: (\Animal.master ~ \.pets ~ \.species).none()) String(keyPath: (\Animal.$master ~ \.$pets ~ \.$species).none())
) )
} }
} }
@@ -247,7 +241,7 @@ final class WhereTests: XCTestCase {
} }
do { do {
let whereClause: Where<Animal> = (\.master ~ \.name) == dummy let whereClause: Where<Animal> = (\.$master ~ \.$name) == dummy
let predicate = NSPredicate(format: "master.name == %@", dummy) let predicate = NSPredicate(format: "master.name == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate)) XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate) XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -265,7 +259,7 @@ final class WhereTests: XCTestCase {
} }
do { do {
let whereClause: Where<Animal> = (\.master ~ \.spouse ~ \.name) == dummy let whereClause: Where<Animal> = (\.$master ~ \.$spouse ~ \.$name) == dummy
let predicate = NSPredicate(format: "master.spouse.name == %@", dummy) let predicate = NSPredicate(format: "master.spouse.name == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate)) XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate) XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -283,7 +277,7 @@ final class WhereTests: XCTestCase {
} }
do { do {
let whereClause: Where<Animal> = (\.master ~ \.pets).count() == count let whereClause: Where<Animal> = (\.$master ~ \.$pets).count() == count
let predicate = NSPredicate(format: "master.pets.@count == %d", count) let predicate = NSPredicate(format: "master.pets.@count == %d", count)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate)) XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate) XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -301,7 +295,7 @@ final class WhereTests: XCTestCase {
} }
do { do {
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.species).any() == dummy let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).any() == dummy
let predicate = NSPredicate(format: "ANY master.pets.species == %@", dummy) let predicate = NSPredicate(format: "ANY master.pets.species == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate)) XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate) XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -319,7 +313,7 @@ final class WhereTests: XCTestCase {
} }
do { do {
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.species).all() == dummy let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).all() == dummy
let predicate = NSPredicate(format: "ALL master.pets.species == %@", dummy) let predicate = NSPredicate(format: "ALL master.pets.species == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate)) XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate) XCTAssertAllEqual(whereClause.predicate, predicate)
@@ -337,7 +331,7 @@ final class WhereTests: XCTestCase {
} }
do { do {
let whereClause: Where<Animal> = (\.master ~ \.pets ~ \.species).none() == dummy let whereClause: Where<Animal> = (\.$master ~ \.$pets ~ \.$species).none() == dummy
let predicate = NSPredicate(format: "NONE master.pets.species == %@", dummy) let predicate = NSPredicate(format: "NONE master.pets.species == %@", dummy)
XCTAssertAllEqual(whereClause, Where<Animal>(predicate)) XCTAssertAllEqual(whereClause, Where<Animal>(predicate))
XCTAssertAllEqual(whereClause.predicate, predicate) XCTAssertAllEqual(whereClause.predicate, predicate)
File diff suppressed because it is too large Load Diff
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<Scheme <Scheme
LastUpgradeVersion = "1020" LastUpgradeVersion = "1200"
version = "1.3"> version = "1.3">
<BuildAction <BuildAction
parallelizeBuildables = "YES" parallelizeBuildables = "YES"
@@ -14,10 +14,10 @@
buildForAnalyzing = "YES"> buildForAnalyzing = "YES">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0" BlueprintIdentifier = "B5A3911824E5429200E7E8BD"
BuildableName = "CoreStoreDemo.app" BuildableName = "Demo.app"
BlueprintName = "CoreStoreDemo" BlueprintName = "Demo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj"> ReferencedContainer = "container:Demo.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildActionEntry> </BuildActionEntry>
</BuildActionEntries> </BuildActionEntries>
@@ -27,15 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES"> shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables> <Testables>
</Testables> </Testables>
</TestAction> </TestAction>
@@ -53,10 +44,10 @@
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0" BlueprintIdentifier = "B5A3911824E5429200E7E8BD"
BuildableName = "CoreStoreDemo.app" BuildableName = "Demo.app"
BlueprintName = "CoreStoreDemo" BlueprintName = "Demo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj"> ReferencedContainer = "container:Demo.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
</LaunchAction> </LaunchAction>
@@ -70,10 +61,10 @@
runnableDebuggingMode = "0"> runnableDebuggingMode = "0">
<BuildableReference <BuildableReference
BuildableIdentifier = "primary" BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0" BlueprintIdentifier = "B5A3911824E5429200E7E8BD"
BuildableName = "CoreStoreDemo.app" BuildableName = "Demo.app"
BlueprintName = "CoreStoreDemo" BlueprintName = "Demo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj"> ReferencedContainer = "container:Demo.xcodeproj">
</BuildableReference> </BuildableReference>
</BuildableProductRunnable> </BuildableProductRunnable>
</ProfileAction> </ProfileAction>
+70
View File
@@ -0,0 +1,70 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<false/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UIStatusBarTintParameters</key>
<dict>
<key>UINavigationBar</key>
<dict>
<key>Style</key>
<string>UIBarStyleDefault</string>
<key>Translucent</key>
<false/>
</dict>
</dict>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>
+42
View File
@@ -0,0 +1,42 @@
# coding: utf-8
task :format do
require 'xcodeproj'
require 'fileutils'
project_path = 'Demo.xcodeproj'
ignore_targets = []
# http://www.rubydoc.info/github/CocoaPods/Xcodeproj
project = Xcodeproj::Project.open(project_path)
validTargets = project.targets.select { |target| target.respond_to?(:product_type) }
validTargets.each do |target|
if ignore_targets.include?(target.display_name)
next
end
case target.product_type
when 'com.apple.product-type.application', 'com.apple.product-type.framework'
target.source_build_phase.files.sort! do |f1, f2|
result = (f1.display_name.count "+") <=> (f2.display_name.count "+")
if result != 0
next -result
end
result = (f1.display_name.count "-") <=> (f2.display_name.count "-")
if result != 0
next -result
end
result = (f1.display_name.count ".") <=> (f2.display_name.count ".")
if result != 0
next result
end
(f1.display_name <=> f2.display_name)
end
end
end
project.save()
end
@@ -0,0 +1,60 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="16097.2" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES" initialViewController="01J-lp-oVM">
<device id="retina6_1" orientation="portrait" appearance="light"/>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="16087"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--View Controller-->
<scene sceneID="EHf-IW-A2E">
<objects>
<viewController interfaceStyle="dark" id="01J-lp-oVM" sceneMemberID="viewController">
<view key="view" contentMode="scaleToFill" id="Bp2-lt-3DL">
<rect key="frame" x="0.0" y="0.0" width="414" height="896"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2020 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="Yn3-8H-uzI">
<rect key="frame" x="20" y="827" width="374" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="scaleAspectFit" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="CoreStoreIcon" translatesAutoresizingMaskIntoConstraints="NO" id="IrK-8p-pit">
<rect key="frame" x="122" y="228.5" width="170" height="170"/>
<constraints>
<constraint firstAttribute="width" secondItem="IrK-8p-pit" secondAttribute="height" multiplier="1:1" id="WaM-8F-33r"/>
<constraint firstAttribute="width" constant="170" id="dlo-1N-ikz"/>
</constraints>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="8Vu-0U-3hd">
<rect key="frame" x="20" y="418.5" width="374" height="57.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Yn3-8H-uzI" firstAttribute="leading" secondItem="Bp2-lt-3DL" secondAttribute="leading" constant="20" symbolic="YES" id="7Dq-xP-k2v"/>
<constraint firstItem="IrK-8p-pit" firstAttribute="centerY" secondItem="Bp2-lt-3DL" secondAttribute="centerY" multiplier="0.7" id="HUz-XL-l27"/>
<constraint firstItem="IrK-8p-pit" firstAttribute="centerX" secondItem="Bp2-lt-3DL" secondAttribute="centerX" id="TSf-yM-hl1"/>
<constraint firstAttribute="centerX" secondItem="8Vu-0U-3hd" secondAttribute="centerX" id="TX2-HT-cKs"/>
<constraint firstItem="Z3i-EZ-UGs" firstAttribute="bottom" secondItem="Yn3-8H-uzI" secondAttribute="bottom" constant="14" id="hAb-SJ-Qnm"/>
<constraint firstAttribute="centerX" secondItem="Yn3-8H-uzI" secondAttribute="centerX" id="pNf-eo-RXZ"/>
<constraint firstItem="8Vu-0U-3hd" firstAttribute="leading" secondItem="Bp2-lt-3DL" secondAttribute="leading" constant="20" symbolic="YES" id="pef-yD-C5e"/>
<constraint firstItem="8Vu-0U-3hd" firstAttribute="top" secondItem="IrK-8p-pit" secondAttribute="bottom" constant="20" id="xQP-tq-hNL"/>
</constraints>
<viewLayoutGuide key="safeArea" id="Z3i-EZ-UGs"/>
</view>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="iYj-Kq-Ea1" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="53" y="375"/>
</scene>
</scenes>
<resources>
<image name="CoreStoreIcon" width="170" height="170"/>
</resources>
</document>
@@ -0,0 +1,115 @@
{
"images" : [
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "20x20"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "29x29"
},
{
"idiom" : "iphone",
"scale" : "2x",
"size" : "40x40"
},
{
"idiom" : "iphone",
"scale" : "3x",
"size" : "40x40"
},
{
"filename" : "Icon-60@2x.png",
"idiom" : "iphone",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon-60@3x-1.png",
"idiom" : "iphone",
"scale" : "3x",
"size" : "60x60"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "20x20"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "29x29"
},
{
"idiom" : "ipad",
"scale" : "1x",
"size" : "40x40"
},
{
"idiom" : "ipad",
"scale" : "2x",
"size" : "40x40"
},
{
"filename" : "Icon-76.png",
"idiom" : "ipad",
"scale" : "1x",
"size" : "76x76"
},
{
"filename" : "Icon-76@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "76x76"
},
{
"filename" : "Icon-83.5@2x.png",
"idiom" : "ipad",
"scale" : "2x",
"size" : "83.5x83.5"
},
{
"filename" : "Mask + Oval 1 + Oval 1 + Oval 1.png",
"idiom" : "ios-marketing",
"scale" : "1x",
"size" : "1024x1024"
},
{
"idiom" : "car",
"scale" : "2x",
"size" : "60x60"
},
{
"filename" : "Icon-60@3x.png",
"idiom" : "car",
"scale" : "3x",
"size" : "60x60"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Before

Width:  |  Height:  |  Size: 7.1 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.1 KiB

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
@@ -0,0 +1,15 @@
{
"images" : [
{
"filename" : "CoreStoreIcon.pdf",
"idiom" : "universal"
}
],
"info" : {
"author" : "xcode",
"version" : 1
},
"properties" : {
"template-rendering-intent" : "original"
}
}
@@ -0,0 +1,6 @@
{
"info" : {
"author" : "xcode",
"version" : 1
}
}
+33
View File
@@ -0,0 +1,33 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import UIKit
// MARK: - AppDelegate
@UIApplicationMain
@objc final class AppDelegate: UIResponder, UIApplicationDelegate {
// MARK: UIApplicationDelegate
@objc dynamic func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
return true
}
@objc dynamic func application(
_ application: UIApplication,
configurationForConnecting connectingSceneSession: UISceneSession,
options: UIScene.ConnectionOptions
) -> UISceneConfiguration {
return UISceneConfiguration(
name: "Default Configuration",
sessionRole: connectingSceneSession.role
)
}
}
@@ -0,0 +1,69 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Foundation
import UIKit
import Combine
// MARK: - ImageDownloader
final class ImageDownloader: ObservableObject {
// MARK: FilePrivate
private(set) var image: UIImage?
let url: URL?
init(url: URL?) {
self.url = url
guard let url = url else {
return
}
if let image = Self.cache[url] {
self.image = image
}
}
func fetchImage(completion: @escaping (UIImage) -> Void = { _ in }) {
guard let url = url else {
return
}
if let image = Self.cache[url] {
self.objectWillChange.send()
self.image = image
completion(image)
return
}
self.cancellable = URLSession.shared
.dataTaskPublisher(for: url)
.receive(on: DispatchQueue.main)
.sink(
receiveCompletion: { _ in },
receiveValue: { output in
if let image = UIImage(data: output.data) {
Self.cache[url] = image
self.objectWillChange.send()
self.image = image
completion(image)
}
}
)
}
// MARK: Private
private static var cache: [URL: UIImage] = [:]
private var cancellable: AnyCancellable?
}
@@ -0,0 +1,58 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import SwiftUI
// MARK: - InstructionsView
struct InstructionsView: View {
// MARK: Internal
init(_ rows: (header: String, description: String)...) {
self.rows = rows.map({ .init(header: $0, description: $1) })
}
// MARK: View
var body: some View {
ZStack(alignment: .center) {
RoundedRectangle(cornerRadius: 10, style: .continuous)
.fill(Color.white)
.shadow(color: Color(.sRGB, white: 0.5, opacity: 0.3), radius: 2, x: 1, y: 1)
VStack(alignment: .leading, spacing: 3) {
ForEach(self.rows, id: \.header) { row in
HStack(alignment: .firstTextBaseline, spacing: 5) {
Text(row.header)
.font(.callout)
.fontWeight(.bold)
Text(row.description)
.font(.footnote)
}
}
}
.foregroundColor(Color(.sRGB, white: 0, opacity: 0.8))
.padding(.horizontal, 10)
.padding(.vertical, 4)
}
.fixedSize()
}
// MARK: Private
private let rows: [InstructionsView.Row]
// MARK: - Row
struct Row: Hashable {
// MARK: Internal
let header: String
let description: String
}
}
+29
View File
@@ -0,0 +1,29 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import SwiftUI
// MARK: - LazyView
struct LazyView<Content: View>: View {
// MARK: Internal
init(_ load: @escaping () -> Content) {
self.load = load
}
// MARK: View
var body: Content {
self.load()
}
// MARK: Private
private let load: () -> Content
}
@@ -0,0 +1,71 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import SwiftUI
// MARK: - Menu
extension Menu {
// MARK: - Menu.ItemView
struct ItemView<Destination: View>: View {
// MARK: Internal
init(
title: String,
subtitle: String? = nil,
destination: @escaping () -> Destination
) {
self.title = title
self.subtitle = subtitle
self.destination = destination
}
// MARK: View
var body: some View {
NavigationLink(destination: LazyView(self.destination)) {
VStack(alignment: .leading) {
Text(self.title)
.font(.headline)
.foregroundColor(.primary)
self.subtitle.map {
Text($0)
.font(.subheadline)
.foregroundColor(.secondary)
}
}
}
}
// MARK: FilePrivate
fileprivate let title: String
fileprivate let subtitle: String?
fileprivate let destination: () -> Destination
}
}
#if DEBUG
struct _Demo_Menu_ItemView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
Menu.ItemView(
title: "Item Title",
subtitle: "A subtitle caption for this item",
destination: {
Color.blue
}
)
}
}
#endif
@@ -0,0 +1,134 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Foundation
import SwiftUI
// MARK: - Menu
extension Menu {
// MARK: - Menu.MainView
struct MainView: View {
// MARK: View
var body: some View {
NavigationView {
List {
Section(header: Text("Modern (CoreStoreObject subclasses)")) {
Menu.ItemView(
title: "Placemarks",
subtitle: "Making changes using Transactions",
destination: {
Modern.PlacemarksDemo.MainView()
}
)
Menu.ItemView(
title: "Time Zones",
subtitle: "Fetching objects and Querying raw values",
destination: {
Modern.TimeZonesDemo.MainView()
}
)
Menu.ItemView(
title: "Colors (UIKit)",
subtitle: "Observing list changes and single-object changes using DiffableDataSources",
destination: {
Modern.ColorsDemo.MainView(
listView: { listPublisher, onPaletteTapped in
Modern.ColorsDemo.UIKit.ListView(
listPublisher: listPublisher,
onPaletteTapped: onPaletteTapped
)
.edgesIgnoringSafeArea(.all)
},
detailView: { objectPublisher in
Modern.ColorsDemo.UIKit.DetailView(objectPublisher)
}
)
}
)
Menu.ItemView(
title: "Colors (SwiftUI)",
subtitle: "Observing list changes and single-object changes using SwiftUI bindings",
destination: {
Modern.ColorsDemo.MainView(
listView: { listPublisher, onPaletteTapped in
Modern.ColorsDemo.SwiftUI.ListView(
listPublisher: listPublisher,
onPaletteTapped: onPaletteTapped
)
},
detailView: { objectPublisher in
Modern.ColorsDemo.SwiftUI.DetailView(objectPublisher)
}
)
}
)
Menu.ItemView(
title: "Pokedex API",
subtitle: "Importing JSON data from external source",
destination: {
Modern.PokedexDemo.MainView(
listView: Modern.PokedexDemo.UIKit.ListView.init
)
}
)
}
Section(header: Text("Classic (NSManagedObject subclasses)")) {
Menu.ItemView(
title: "Colors",
subtitle: "Observing list changes and single-object changes using ListMonitor",
destination: {
Classic.ColorsDemo.MainView()
}
)
}
Section(header: Text("Advanced")) {
Menu.ItemView(
title: "Accounts",
subtitle: "Switching between multiple persistent stores",
destination: { EmptyView() }
)
.disabled(true)
Menu.ItemView(
title: "Evolution",
subtitle: "Migrating and reverse-migrating stores",
destination: {
Advanced.EvolutionDemo.MainView()
}
)
Menu.ItemView(
title: "Logger",
subtitle: "Implementing a custom logger",
destination: { EmptyView() }
)
.disabled(true)
}
}
.listStyle(GroupedListStyle())
.navigationBarTitle("CoreStore Demos")
Menu.PlaceholderView()
}
.navigationViewStyle(DoubleColumnNavigationViewStyle())
}
}
}
#if DEBUG
struct _Demo_Menu_MainView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
Menu.MainView()
}
}
#endif
@@ -0,0 +1,44 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Combine
import CoreStore
import SwiftUI
// MARK: - Menu
extension Menu {
// MARK: - Menu.PlaceholderView
struct PlaceholderView: UIViewControllerRepresentable {
// MARK: UIViewControllerRepresentable
typealias UIViewControllerType = UIViewController
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
return UIStoryboard(name: "LaunchScreen", bundle: nil).instantiateInitialViewController()!
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {}
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
}
}
#if DEBUG
struct _Demo_Menu_PlaceholderView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
return Menu.PlaceholderView()
}
}
#endif
@@ -0,0 +1,10 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Foundation
// MARK: - Menu
enum Menu {}
+36
View File
@@ -0,0 +1,36 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import SwiftUI
import UIKit
// MARK: - SceneDelegate
@objc final class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// MARK: UIWindowSceneDelegate
@objc dynamic var window: UIWindow?
// MARK: UISceneDelegate
@objc dynamic func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard case let scene as UIWindowScene = scene else {
return
}
let window = UIWindow(windowScene: scene)
window.rootViewController = UIHostingController(
rootView: Menu.MainView()
)
self.window = window
window.makeKeyAndVisible()
}
}
@@ -0,0 +1,10 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
// MARK: - Advanced
/**
Sample application of complex use cases
*/
enum Advanced {}
@@ -0,0 +1,30 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import Foundation
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
typealias CreatureType = Advanced_EvolutionDemo_CreatureType
}
// MARK: - Advanced.EvolutionDemo.CreatureType
protocol Advanced_EvolutionDemo_CreatureType: DynamicObject, CustomStringConvertible {
var dnaCode: Int64 { get set }
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource
static func count(in transaction: BaseDataTransaction) throws -> Int
static func create(in transaction: BaseDataTransaction) -> Self
func mutate(in transaction: BaseDataTransaction)
}
@@ -0,0 +1,168 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import Combine
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.CreaturesDataSource
/**
A type-erasing adapter to support different `ListPublisher` types
*/
final class CreaturesDataSource: ObservableObject {
// MARK: Internal
init<T: NSManagedObject & Advanced.EvolutionDemo.CreatureType>(
listPublisher: ListPublisher<T>,
dataStack: DataStack
) {
self.numberOfItems = {
listPublisher.snapshot.numberOfItems
}
self.itemDescriptionAtIndex = { index in
listPublisher.snapshot[index].object?.description
}
self.addItems = { count in
dataStack.perform(
asynchronous: { transaction in
let nextDNACode = try transaction.fetchCount(From<T>())
for offset in 0 ..< count {
let object = transaction.create(Into<T>())
object.dnaCode = .init(nextDNACode + offset)
object.mutate(in: transaction)
}
},
completion: { _ in }
)
}
self.mutateItemAtIndex = { index in
let object = listPublisher.snapshot[index]
dataStack.perform(
asynchronous: { transaction in
object
.asEditable(in: transaction)?
.mutate(in: transaction)
},
completion: { _ in }
)
}
self.deleteAllItems = {
dataStack.perform(
asynchronous: { transaction in
try transaction.deleteAll(From<T>())
},
completion: { _ in }
)
}
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.objectWillChange.send()
}
}
init<T: CoreStoreObject & Advanced.EvolutionDemo.CreatureType>(
listPublisher: ListPublisher<T>,
dataStack: DataStack
) {
self.numberOfItems = {
listPublisher.snapshot.numberOfItems
}
self.itemDescriptionAtIndex = { index in
listPublisher.snapshot[index].object?.description
}
self.addItems = { count in
dataStack.perform(
asynchronous: { transaction in
let nextDNACode = try transaction.fetchCount(From<T>())
for offset in 0 ..< count {
let object = transaction.create(Into<T>())
object.dnaCode = .init(nextDNACode + offset)
object.mutate(in: transaction)
}
},
completion: { _ in }
)
}
self.mutateItemAtIndex = { index in
let object = listPublisher.snapshot[index]
dataStack.perform(
asynchronous: { transaction in
object
.asEditable(in: transaction)?
.mutate(in: transaction)
},
completion: { _ in }
)
}
self.deleteAllItems = {
dataStack.perform(
asynchronous: { transaction in
try transaction.deleteAll(From<T>())
},
completion: { _ in }
)
}
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.objectWillChange.send()
}
}
func numberOfCreatures() -> Int {
return self.numberOfItems()
}
func creatureDescription(at index: Int) -> String? {
return self.itemDescriptionAtIndex(index)
}
func mutate(at index: Int) {
self.mutateItemAtIndex(index)
}
func add(count: Int) {
self.addItems(count)
}
func clear() {
self.deleteAllItems()
}
// MARK: Private
private let numberOfItems: () -> Int
private let itemDescriptionAtIndex: (Int) -> String?
private let mutateItemAtIndex: (Int) -> Void
private let addItems: (Int) -> Void
private let deleteAllItems: () -> Void
}
}
@@ -0,0 +1,99 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - AdvancedEvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - GeologicalPeriod
enum GeologicalPeriod: RawRepresentable, CaseIterable, Hashable, CustomStringConvertible {
// MARK: Internal
case ageOfInvertebrates
case ageOfFishes
case ageOfReptiles
case ageOfMammals
var version: ModelVersion {
return self.rawValue
}
var creatureType: Advanced.EvolutionDemo.CreatureType.Type {
switch self {
case .ageOfInvertebrates:
return Advanced.EvolutionDemo.V1.Creature.self
case .ageOfFishes:
return Advanced.EvolutionDemo.V2.Creature.self
case .ageOfReptiles:
return Advanced.EvolutionDemo.V3.Creature.self
case .ageOfMammals:
return Advanced.EvolutionDemo.V4.Creature.self
}
}
// MARK: CustomStringConvertible
var description: String {
switch self {
case .ageOfInvertebrates:
return "Invertebrates"
case .ageOfFishes:
return "Fishes"
case .ageOfReptiles:
return "Reptiles"
case .ageOfMammals:
return "Mammals"
}
}
// MARK: RawRepresentable
typealias RawValue = ModelVersion
var rawValue: ModelVersion {
switch self {
case .ageOfInvertebrates:
return Advanced.EvolutionDemo.V1.name
case .ageOfFishes:
return Advanced.EvolutionDemo.V2.name
case .ageOfReptiles:
return Advanced.EvolutionDemo.V3.name
case .ageOfMammals:
return Advanced.EvolutionDemo.V4.name
}
}
init?(rawValue: ModelVersion) {
switch rawValue {
case Advanced.EvolutionDemo.V1.name:
self = .ageOfInvertebrates
case Advanced.EvolutionDemo.V2.name:
self = .ageOfFishes
case Advanced.EvolutionDemo.V3.name:
self = .ageOfReptiles
case Advanced.EvolutionDemo.V4.name:
self = .ageOfMammals
default:
return nil
}
}
}
}
@@ -0,0 +1,75 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import SwiftUI
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.ItemView
struct ItemView: View {
// MARK: Internal
init(description: String?, mutate: @escaping () -> Void) {
self.description = description
self.mutate = mutate
}
// MARK: View
var body: some View {
HStack {
Text(self.description ?? "")
.font(.footnote)
.foregroundColor(.primary)
Spacer()
Button(
action: self.mutate,
label: {
Text("Mutate")
.foregroundColor(.accentColor)
.fontWeight(.bold)
}
)
.buttonStyle(PlainButtonStyle())
}
.disabled(self.description == nil)
}
// MARK: FilePrivate
fileprivate let description: String?
fileprivate let mutate: () -> Void
}
}
#if DEBUG
struct _Demo_Advanced_EvolutionDemo_ItemView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
Advanced.EvolutionDemo.ItemView(
description: """
dnaCode: 123
numberOfLimbs: 4
hasVertebrae: true
hasHead: true
hasTail: true
habitat: land
hasWings: false
""",
mutate: {}
)
}
}
#endif
@@ -0,0 +1,99 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import SwiftUI
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.ListView
struct ListView: View {
// MARK: View
var body: some View {
let dataSource = self.dataSource
return List {
ForEach(0 ..< dataSource.numberOfCreatures(), id: \.self) { (index) in
Advanced.EvolutionDemo.ItemView(
description: dataSource.creatureDescription(at: index),
mutate: {
dataSource.mutate(at: index)
}
)
}
}
.listStyle(PlainListStyle())
}
// MARK: Internal
init(
period: Advanced.EvolutionDemo.GeologicalPeriod,
dataStack: DataStack,
dataSource: Advanced.EvolutionDemo.CreaturesDataSource
) {
self.period = period
self.dataStack = dataStack
self.dataSource = dataSource
}
// MARK: Private
private let period: Advanced.EvolutionDemo.GeologicalPeriod
private let dataStack: DataStack
@ObservedObject
private var dataSource: Advanced.EvolutionDemo.CreaturesDataSource
}
}
#if DEBUG
struct _Demo_Advanced_EvolutionDemo_ListView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
let dataStack = DataStack(
CoreStoreSchema(
modelVersion: Advanced.EvolutionDemo.V4.name,
entities: [
Entity<Advanced.EvolutionDemo.V4.Creature>("Creature")
]
)
)
try! dataStack.addStorageAndWait(
SQLiteStore(fileName: "Advanced.EvolutionDemo.ListView.Preview.sqlite")
)
try! dataStack.perform(
synchronous: { transaction in
for dnaCode in 0 ..< 10 as Range<Int64> {
let object = transaction.create(Into<Advanced.EvolutionDemo.V4.Creature>())
object.dnaCode = dnaCode
object.mutate(in: transaction)
}
}
)
return Advanced.EvolutionDemo.ListView(
period: .ageOfMammals,
dataStack: dataStack,
dataSource: Advanced.EvolutionDemo.V4.Creature.dataSource(in: dataStack)
)
}
}
#endif
@@ -0,0 +1,78 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import SwiftUI
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.MainView
struct MainView: View {
// MARK: View
var body: some View {
let migrator = self.migrator
let listView: AnyView
if let current = migrator.current {
listView = AnyView(
Advanced.EvolutionDemo.ListView(
period: current.period,
dataStack: current.dataStack,
dataSource: current.dataSource
)
)
}
else {
listView = AnyView(
Advanced.EvolutionDemo.ProgressView(progress: migrator.progress)
)
}
return VStack(spacing: 0) {
HStack(alignment: .center, spacing: 0) {
Text("Age of")
.padding(.trailing)
Picker(selection: self.$migrator.currentPeriod, label: EmptyView()) {
ForEach(Advanced.EvolutionDemo.GeologicalPeriod.allCases, id: \.self) { period in
Text(period.description).tag(period)
}
}
.pickerStyle(SegmentedPickerStyle())
}
.padding()
listView
.edgesIgnoringSafeArea(.vertical)
}
.navigationBarTitle("Evolution")
.disabled(migrator.isBusy || migrator.current == nil)
}
// MARK: Private
@ObservedObject
private var migrator: Advanced.EvolutionDemo.Migrator = .init()
}
}
#if DEBUG
struct _Demo_Advanced_EvolutionDemo_MainView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
Advanced.EvolutionDemo.MainView()
}
}
#endif
@@ -0,0 +1,126 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import SwiftUI
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.ProgressView
struct ProgressView: View {
// MARK: Internal
init(progress: Progress?) {
self.progressObserver = .init(progress)
}
// MARK: View
var body: some View {
guard self.progressObserver.isMigrating else {
return AnyView(
VStack(alignment: .center) {
Text("Preparing creatures...")
.padding()
Spacer()
}
.padding()
)
}
return AnyView(
VStack(alignment: .leading) {
Text("Migrating: \(self.progressObserver.localizedDescription)")
.font(.headline)
.padding([.top, .horizontal])
Text("Progressive step: \(self.progressObserver.localizedAdditionalDescription)")
.font(.subheadline)
.padding(.horizontal)
GeometryReader { geometry in
ZStack(alignment: .leading) {
RoundedRectangle(cornerRadius: 4, style: .continuous)
.fill(Color.gray.opacity(0.2))
.frame(width: geometry.size.width, height: 8)
RoundedRectangle(cornerRadius: 4, style: .continuous)
.fill(Color.blue)
.frame(
width: geometry.size.width
* self.progressObserver.fractionCompleted,
height: 8
)
}
}
.fixedSize(horizontal: false, vertical: true)
.padding()
Spacer()
}
.padding()
)
}
// MARK: FilePrivate
@ObservedObject
private var progressObserver: ProgressObserver
// MARK: - ProgressObserver
fileprivate final class ProgressObserver: ObservableObject {
private(set) var fractionCompleted: CGFloat = 0
private(set) var localizedDescription: String = ""
private(set) var localizedAdditionalDescription: String = ""
var isMigrating: Bool {
return self.progress != nil
}
init(_ progress: Progress?) {
self.progress = progress
progress?.setProgressHandler { [weak self] (progess) in
guard let self = self else {
return
}
self.objectWillChange.send()
self.fractionCompleted = CGFloat(progress?.fractionCompleted ?? 0)
self.localizedDescription = progress?.localizedDescription ?? ""
self.localizedAdditionalDescription = progress?.localizedAdditionalDescription ?? ""
}
}
// MARK: Private
private let progress: Progress?
}
}
}
#if DEBUG
struct _Demo_Advanced_EvolutionDemo_ProgressView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
let progress = Progress(totalUnitCount: 10)
progress.completedUnitCount = 3
return Advanced.EvolutionDemo.ProgressView(
progress: progress
)
}
}
#endif
@@ -0,0 +1,63 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import UIKit
import CoreStore
// MARK: - Advanced.EvolutionDemo.V1.Creature
@objc(Advanced_EvolutionDemo_V1_Creature)
final class Advanced_EvolutionDemo_V1_Creature: NSManagedObject, Advanced.EvolutionDemo.CreatureType {
@NSManaged
dynamic var dnaCode: Int64
@NSManaged
dynamic var numberOfFlagella: Int32
// MARK: CustomStringConvertible
override var description: String {
return """
dnaCode: \(self.dnaCode)
numberOfFlagella: \(self.numberOfFlagella)
"""
}
// MARK: Advanced.EvolutionDemo.CreatureType
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
return .init(
listPublisher: dataStack.publishList(
From<Advanced.EvolutionDemo.V1.Creature>()
.orderBy(.descending(\.dnaCode))
),
dataStack: dataStack
)
}
static func count(in transaction: BaseDataTransaction) throws -> Int {
return try transaction.fetchCount(
From<Advanced.EvolutionDemo.V1.Creature>()
)
}
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V1.Creature {
return transaction.create(
Into<Advanced.EvolutionDemo.V1.Creature>()
)
}
func mutate(in transaction: BaseDataTransaction) {
self.numberOfFlagella = .random(in: 1...200)
}
}
@@ -0,0 +1,27 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo.V1
extension Advanced.EvolutionDemo.V1 {
// MARK: - Advanced.EvolutionDemo.V1.FromV2
enum FromV2 {
// MARK: Internal
static var mapping: XcodeSchemaMappingProvider {
return XcodeSchemaMappingProvider(
from: Advanced.EvolutionDemo.V2.name,
to: Advanced.EvolutionDemo.V1.name,
mappingModelBundle: Bundle(for: Advanced.EvolutionDemo.V1.Creature.self)
)
}
}
}
@@ -0,0 +1,25 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.V1
/**
Namespace for V1 models (`Advanced.EvolutionDemo.GeologicalPeriod.ageOfInvertebrates`)
*/
enum V1 {
// MARK: Internal
static let name: ModelVersion = "Advanced.EvolutionDemo.V1"
typealias Creature = Advanced_EvolutionDemo_V1_Creature
}
}
@@ -2,9 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
<dict> <dict>
<key>HasAskedToTakeAutomaticSnapshotBeforeSignificantChanges</key> <key>_XCCurrentVersionName</key>
<true/> <string>Advanced.EvolutionDemo.V1.xcdatamodel</string>
<key>SnapshotAutomaticallyBeforeSignificantChanges</key>
<true/>
</dict> </dict>
</plist> </plist>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V1_Creature" syncable="YES">
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numberOfFlagella" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
</entity>
<elements>
<element name="Creature" positionX="-27" positionY="18" width="128" height="73"/>
</elements>
</model>
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V2_Creature" syncable="YES">
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hasHead" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hasTail" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hasVertebrae" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="numberOfFlippers" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
</entity>
<elements>
<element name="Creature" positionX="-9" positionY="36" width="128" height="118"/>
</elements>
</model>
@@ -0,0 +1,78 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import UIKit
import CoreStore
// MARK: - Advanced.EvolutionDemo.V2.Creature
@objc(Advanced_EvolutionDemo_V2_Creature)
final class Advanced_EvolutionDemo_V2_Creature: NSManagedObject, Advanced.EvolutionDemo.CreatureType {
@NSManaged
dynamic var dnaCode: Int64
@NSManaged
dynamic var numberOfFlippers: Int32
@NSManaged
dynamic var hasVertebrae: Bool
@NSManaged
dynamic var hasHead: Bool
@NSManaged
dynamic var hasTail: Bool
// MARK: CustomStringConvertible
override var description: String {
return """
dnaCode: \(self.dnaCode)
numberOfFlippers: \(self.numberOfFlippers)
hasVertebrae: \(self.hasVertebrae)
hasHead: \(self.hasHead)
hasTail: \(self.hasTail)
"""
}
// MARK: Advanced.EvolutionDemo.CreatureType
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
return .init(
listPublisher: dataStack.publishList(
From<Advanced.EvolutionDemo.V2.Creature>()
.orderBy(.descending(\.dnaCode))
),
dataStack: dataStack
)
}
static func count(in transaction: BaseDataTransaction) throws -> Int {
return try transaction.fetchCount(
From<Advanced.EvolutionDemo.V2.Creature>()
)
}
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V2.Creature {
return transaction.create(
Into<Advanced.EvolutionDemo.V2.Creature>()
)
}
func mutate(in transaction: BaseDataTransaction) {
self.numberOfFlippers = .random(in: 1...4) * 2
self.hasVertebrae = .random()
self.hasHead = true
self.hasTail = .random()
}
}
@@ -0,0 +1,27 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo.V2
extension Advanced.EvolutionDemo.V2 {
// MARK: - Advanced.EvolutionDemo.V2.FromV1
enum FromV1 {
// MARK: Internal
static var mapping: XcodeSchemaMappingProvider {
return XcodeSchemaMappingProvider(
from: Advanced.EvolutionDemo.V1.name,
to: Advanced.EvolutionDemo.V2.name,
mappingModelBundle: Bundle(for: Advanced.EvolutionDemo.V1.Creature.self)
)
}
}
}
@@ -0,0 +1,40 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreData
import CoreStore
// MARK: - Advanced.EvolutionDemo.V2.FromV1MigrationPolicy
@objc(Advanced_EvolutionDemo_V2_FromV1MigrationPolicy)
final class Advanced_EvolutionDemo_V2_FromV1MigrationPolicy: NSEntityMigrationPolicy {
// MARK: NSEntityMigrationPolicy
override func createDestinationInstances(
forSource sInstance: NSManagedObject,
in mapping: NSEntityMapping,
manager: NSMigrationManager
) throws {
try super.createDestinationInstances(
forSource: sInstance,
in: mapping,
manager: manager
)
for dInstance in manager.destinationInstances(forEntityMappingName: mapping.name, sourceInstances: [sInstance]) {
dInstance.setValue(
Bool.random(),
forKey: #keyPath(Advanced.EvolutionDemo.V2.Creature.hasVertebrae)
)
dInstance.setValue(
Bool.random(),
forKey: #keyPath(Advanced.EvolutionDemo.V2.Creature.hasTail)
)
}
}
}
@@ -0,0 +1,41 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo.V2
extension Advanced.EvolutionDemo.V2 {
// MARK: - Advanced.EvolutionDemo.V2.FromV3
enum FromV3 {
// MARK: Internal
static var mapping: CustomSchemaMappingProvider {
return CustomSchemaMappingProvider(
from: Advanced.EvolutionDemo.V3.name,
to: Advanced.EvolutionDemo.V2.name,
entityMappings: [
.transformEntity(
sourceEntity: "Creature",
destinationEntity: "Creature",
transformer: { (source, createDestination) in
let destination = createDestination()
destination["dnaCode"] = source["dnaCode"]
destination["numberOfFlippers"] = source["numberOfLimbs"]
destination["hasVertebrae"] = source["hasVertebrae"]
destination["hasHead"] = source["hasHead"]
destination["hasTail"] = source["hasTail"]
}
)
]
)
}
}
}
@@ -0,0 +1,27 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.V2
/**
Namespace for V2 models (`Advanced.EvolutionDemo.GeologicalPeriod.ageOfFishes`)
*/
enum V2 {
// MARK: Internal
static let name: ModelVersion = "Advanced.EvolutionDemo.V2"
typealias Creature = Advanced_EvolutionDemo_V2_Creature
typealias FromV1MigrationPolicy = Advanced_EvolutionDemo_V2_FromV1MigrationPolicy
}
}
@@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V2_Creature" syncable="YES">
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="hasHead" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hasTail" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="hasVertebrae" attributeType="Boolean" defaultValueString="YES" usesScalarValueType="YES"/>
<attribute name="numberOfFlippers" attributeType="Integer 32" defaultValueString="2" usesScalarValueType="YES"/>
</entity>
<elements>
<element name="Creature" positionX="-45" positionY="0" width="128" height="118"/>
</elements>
</model>
@@ -0,0 +1,103 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import UIKit
import CoreStore
// MARK: - Advanced.EvolutionDemo.V3
extension Advanced.EvolutionDemo.V3 {
// MARK: - Advanced.EvolutionDemo.V3.Creature
final class Creature: CoreStoreObject, Advanced.EvolutionDemo.CreatureType {
// MARK: Internal
@Field.Stored("dnaCode")
var dnaCode: Int64 = 0
@Field.Stored("numberOfLimbs")
var numberOfLimbs: Int32 = 0
@Field.Stored("hasVertebrae")
var hasVertebrae: Bool = false
@Field.Stored("hasHead")
var hasHead: Bool = true
@Field.Stored("hasTail")
var hasTail: Bool = true
@Field.Stored("hasWings")
var hasWings: Bool = false
@Field.Stored("habitat")
var habitat: Habitat = .water
// MARK: - Habitat
enum Habitat: String, CaseIterable, ImportableAttributeType, FieldStorableType {
case water = "water"
case land = "land"
case amphibian = "amphibian"
}
// MARK: CustomStringConvertible
var description: String {
return """
dnaCode: \(self.dnaCode)
numberOfLimbs: \(self.numberOfLimbs)
hasVertebrae: \(self.hasVertebrae)
hasHead: \(self.hasHead)
hasTail: \(self.hasTail)
habitat: \(self.habitat)
hasWings: \(self.hasWings)
"""
}
// MARK: Advanced.EvolutionDemo.CreatureType
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
return .init(
listPublisher: dataStack.publishList(
From<Advanced.EvolutionDemo.V3.Creature>()
.orderBy(.descending(\.$dnaCode))
),
dataStack: dataStack
)
}
static func count(in transaction: BaseDataTransaction) throws -> Int {
return try transaction.fetchCount(
From<Advanced.EvolutionDemo.V3.Creature>()
)
}
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V3.Creature {
return transaction.create(
Into<Advanced.EvolutionDemo.V3.Creature>()
)
}
func mutate(in transaction: BaseDataTransaction) {
self.numberOfLimbs = .random(in: 1...4) * 2
self.hasVertebrae = .random()
self.hasHead = true
self.hasTail = .random()
self.habitat = Habitat.allCases.randomElement()!
self.hasWings = .random()
}
}
}
@@ -0,0 +1,43 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo.V3
extension Advanced.EvolutionDemo.V3 {
// MARK: - Advanced.EvolutionDemo.V3.FromV2
enum FromV2 {
// MARK: Internal
static var mapping: CustomSchemaMappingProvider {
return CustomSchemaMappingProvider(
from: Advanced.EvolutionDemo.V2.name,
to: Advanced.EvolutionDemo.V3.name,
entityMappings: [
.transformEntity(
sourceEntity: "Creature",
destinationEntity: "Creature",
transformer: { (source, createDestination) in
let destination = createDestination()
destination["dnaCode"] = source["dnaCode"]
destination["numberOfLimbs"] = source["numberOfFlippers"]
destination["hasVertebrae"] = source["hasVertebrae"]
destination["hasHead"] = source["hasHead"]
destination["hasTail"] = source["hasTail"]
destination["hasWings"] = Bool.random()
destination["habitat"] = Advanced.EvolutionDemo.V3.Creature.Habitat.allCases.randomElement()!.rawValue
}
)
]
)
}
}
}
@@ -0,0 +1,33 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo.V3
extension Advanced.EvolutionDemo.V3 {
// MARK: - Advanced.EvolutionDemo.V3.FromV4
enum FromV4 {
// MARK: Internal
static var mapping: CustomSchemaMappingProvider {
return CustomSchemaMappingProvider(
from: Advanced.EvolutionDemo.V4.name,
to: Advanced.EvolutionDemo.V3.name,
entityMappings: [
.transformEntity(
sourceEntity: "Creature",
destinationEntity: "Creature",
transformer: CustomSchemaMappingProvider.CustomMapping.inferredTransformation(_:_:)
)
]
)
}
}
}
@@ -0,0 +1,23 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.V3
/**
Namespace for V3 models (`Advanced.EvolutionDemo.GeologicalPeriod.ageOfReptiles`)
*/
enum V3 {
// MARK: Internal
static let name: ModelVersion = "Advanced.EvolutionDemo.V3"
}
}
@@ -0,0 +1,100 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import UIKit
import CoreStore
// MARK: - Advanced.EvolutionDemo.V4
extension Advanced.EvolutionDemo.V4 {
// MARK: - Advanced.EvolutionDemo.V4.Creature
final class Creature: CoreStoreObject, Advanced.EvolutionDemo.CreatureType {
// MARK: Internal
@Field.Stored("dnaCode")
var dnaCode: Int64 = 0
@Field.Stored("numberOfLimbs")
var numberOfLimbs: Int32 = 0
@Field.Stored("hasVertebrae")
var hasVertebrae: Bool = false
@Field.Stored("hasHead")
var hasHead: Bool = true
@Field.Stored("hasTail")
var hasTail: Bool = false
@Field.Stored("hasWings")
var hasWings: Bool = false
typealias Habitat = Advanced.EvolutionDemo.V3.Creature.Habitat
@Field.Stored("habitat")
var habitat: Habitat = .water
@Field.Stored("isWarmBlooded")
var isWarmBlooded: Bool = true
// MARK: CustomStringConvertible
var description: String {
return """
dnaCode: \(self.dnaCode)
numberOfLimbs: \(self.numberOfLimbs)
hasVertebrae: \(self.hasVertebrae)
hasHead: \(self.hasHead)
hasTail: \(self.hasTail)
habitat: \(self.habitat)
hasWings: \(self.hasWings)
"""
}
// MARK: Advanced.EvolutionDemo.CreatureType
static func dataSource(in dataStack: DataStack) -> Advanced.EvolutionDemo.CreaturesDataSource {
return .init(
listPublisher: dataStack.publishList(
From<Advanced.EvolutionDemo.V4.Creature>()
.orderBy(.descending(\.$dnaCode))
),
dataStack: dataStack
)
}
static func count(in transaction: BaseDataTransaction) throws -> Int {
return try transaction.fetchCount(
From<Advanced.EvolutionDemo.V4.Creature>()
)
}
static func create(in transaction: BaseDataTransaction) -> Advanced.EvolutionDemo.V4.Creature {
return transaction.create(
Into<Advanced.EvolutionDemo.V4.Creature>()
)
}
func mutate(in transaction: BaseDataTransaction) {
self.numberOfLimbs = .random(in: 1...4) * 2
self.hasVertebrae = .random()
self.hasHead = true
self.hasTail = .random()
self.habitat = Habitat.allCases.randomElement()!
self.hasWings = .random()
self.isWarmBlooded = .random()
}
}
}
@@ -0,0 +1,44 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo.V4
extension Advanced.EvolutionDemo.V4 {
// MARK: - Advanced.EvolutionDemo.V4.FromV3
enum FromV3 {
// MARK: Internal
static var mapping: CustomSchemaMappingProvider {
return CustomSchemaMappingProvider(
from: Advanced.EvolutionDemo.V3.name,
to: Advanced.EvolutionDemo.V4.name,
entityMappings: [
.transformEntity(
sourceEntity: "Creature",
destinationEntity: "Creature",
transformer: { (source, createDestination) in
let destination = createDestination()
destination.enumerateAttributes { (destinationAttribute, sourceAttribute) in
if let sourceAttribute = sourceAttribute {
destination[destinationAttribute] = source[sourceAttribute]
}
}
destination["isWarmBlooded"] = Bool.random()
}
)
]
)
}
}
}
@@ -0,0 +1,23 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.V4
/**
Namespace for V3 models (`Advanced.EvolutionDemo.GeologicalPeriod.ageOfMammals`)
*/
enum V4 {
// MARK: Internal
static let name: ModelVersion = "Advanced.EvolutionDemo.V4"
}
}
@@ -0,0 +1,24 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
// MARK: - Advanced
extension Advanced {
// MARK: - Advanced.EvolutionDemo
/**
Sample execution of progressive migrations. This example demonstrates the following concepts:
- How to inspect the current model version of the store (if it exists)
- How to do two-way migration chains (upgrades + downgrades)
- How to support multiple versions of the model on the same app
- How to migrate between `NSManagedObject` schema (`xcdatamodel` files) and `CoreStoreObject` schema.
- How to use `XcodeSchemaMappingProvider`s for `NSManagedObject` stores, and `CustomSchemaMappingProvider`s for `CoreStoreObject` stores
- How to manage migration models using namespacing technique
Note that ideally, your app should be supporting just the latest version of the model, and provide one-way progressive migrations from all the earlier versions.
*/
enum EvolutionDemo {}
}
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V1_Creature" syncable="YES" codeGenerationType="class">
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numberOfFlagella" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
</entity>
<elements>
<element name="Creature" positionX="-36" positionY="9" width="128" height="73"/>
</elements>
</model>
@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="16119" systemVersion="19F101" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="Creature" representedClassName="Advanced_EvolutionDemo_V1_Creature" syncable="YES" codeGenerationType="class">
<attribute name="dnaCode" attributeType="Integer 64" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="numberOfFlagella" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
</entity>
<elements>
<element name="Creature" positionX="-36" positionY="9" width="128" height="73"/>
</elements>
</model>
@@ -0,0 +1,260 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
import Foundation
import Combine
// MARK: - Advanced.EvolutionDemo
extension Advanced.EvolutionDemo {
// MARK: - Advanced.EvolutionDemo.Migrator
final class Migrator: ObservableObject {
/**
Sample 1: Creating a complex `DataStack` that contains all schema histories. The `exactCurrentModelVersion` will specify the target version (if required), and `migrationChain` will provide the upgrade/downgrade progressive migration path.
*/
private func createDataStack(
exactCurrentModelVersion: ModelVersion?,
migrationChain: MigrationChain
) -> DataStack {
let xcodeV1ToV2ModelSchema = XcodeDataModelSchema.from(
modelName: "Advanced.EvolutionDemo.V1",
bundle: Bundle(for: Advanced.EvolutionDemo.V1.Creature.self)
)
return DataStack(
schemaHistory: SchemaHistory(
allSchema: xcodeV1ToV2ModelSchema.allSchema
+ [
CoreStoreSchema(
modelVersion: Advanced.EvolutionDemo.V3.name,
entities: [
Entity<Advanced.EvolutionDemo.V3.Creature>("Creature")
]
),
CoreStoreSchema(
modelVersion: Advanced.EvolutionDemo.V4.name,
entities: [
Entity<Advanced.EvolutionDemo.V4.Creature>("Creature")
]
)
],
migrationChain: migrationChain,
exactCurrentModelVersion: exactCurrentModelVersion
)
)
}
/**
Sample 2: Creating a complex `SQLiteStore` that contains all schema mappings for both upgrade and downgrade cases.
*/
private func accessSQLiteStore() -> SQLiteStore {
let upgradeMappings: [SchemaMappingProvider] = [
Advanced.EvolutionDemo.V2.FromV1.mapping,
Advanced.EvolutionDemo.V3.FromV2.mapping,
Advanced.EvolutionDemo.V4.FromV3.mapping
]
let downgradeMappings: [SchemaMappingProvider] = [
Advanced.EvolutionDemo.V3.FromV4.mapping,
Advanced.EvolutionDemo.V2.FromV3.mapping,
Advanced.EvolutionDemo.V1.FromV2.mapping,
]
return SQLiteStore(
fileName: "Advanced.EvolutionDemo.sqlite",
configuration: nil,
migrationMappingProviders: upgradeMappings + downgradeMappings,
localStorageOptions: []
)
}
/**
Sample 3: Find the model version used by an existing `SQLiteStore`, or just return the latest version if the store is not created yet.
*/
private func findCurrentVersion() -> ModelVersion {
let allVersions = Advanced.EvolutionDemo.GeologicalPeriod.allCases
.map({ $0.version })
// Since we are only interested in finding current version, we'll assume an upgrading `MigrationChain`
let dataStack = self.createDataStack(
exactCurrentModelVersion: nil,
migrationChain: MigrationChain(allVersions)
)
let migrations = try! dataStack.requiredMigrationsForStorage(
self.accessSQLiteStore()
)
// If no migrations are needed, it means either the store is not created yet, or the store is already at the latest model version. In either case, we already know that the store will use the latest version
return migrations.first?.sourceVersion
?? allVersions.last!
}
// MARK: Internal
var currentPeriod: Advanced.EvolutionDemo.GeologicalPeriod = Advanced.EvolutionDemo.GeologicalPeriod.allCases.last! {
didSet {
self.selectModelVersion(self.currentPeriod)
}
}
private(set) var current: (
period: Advanced.EvolutionDemo.GeologicalPeriod,
dataStack: DataStack,
dataSource: Advanced.EvolutionDemo.CreaturesDataSource
)? {
willSet {
self.objectWillChange.send()
}
}
private(set) var isBusy: Bool = false
private(set) var progress: Progress?
init() {
self.synchronizeCurrentVersion()
}
// MARK: Private
private func synchronizeCurrentVersion() {
guard
let currentPeriod = Advanced.EvolutionDemo.GeologicalPeriod(rawValue: self.findCurrentVersion())
else {
self.selectModelVersion(self.currentPeriod)
return
}
self.selectModelVersion(currentPeriod)
}
private func selectModelVersion(_ period: Advanced.EvolutionDemo.GeologicalPeriod) {
let currentPeriod = self.current?.period
guard period != currentPeriod else {
return
}
self.objectWillChange.send()
self.isBusy = true
// explicitly trigger `NSPersistentStore` cleanup by deallocating the `DataStack`
self.current = nil
let migrationChain: MigrationChain
switch (currentPeriod?.version, period.version) {
case (nil, let newVersion):
migrationChain = [newVersion]
case (let currentVersion?, let newVersion):
let upgradeMigrationChain = Advanced.EvolutionDemo.GeologicalPeriod.allCases
.map({ $0.version })
let currentVersionIndex = upgradeMigrationChain.firstIndex(of: currentVersion)!
let newVersionIndex = upgradeMigrationChain.firstIndex(of: newVersion)!
migrationChain = MigrationChain(
currentVersionIndex > newVersionIndex
? upgradeMigrationChain.reversed()
: upgradeMigrationChain
)
}
let dataStack = self.createDataStack(
exactCurrentModelVersion: period.version,
migrationChain: migrationChain
)
let completion = { [weak self] () -> Void in
guard let self = self else {
return
}
self.objectWillChange.send()
defer {
self.isBusy = false
}
self.current = (
period: period,
dataStack: dataStack,
dataSource: period.creatureType.dataSource(in: dataStack)
)
self.currentPeriod = period
}
self.progress = dataStack.addStorage(
self.accessSQLiteStore(),
completion: { [weak self] result in
guard let self = self else {
return
}
guard case .success = result else {
self.objectWillChange.send()
self.isBusy = false
return
}
if self.progress == nil {
self.spawnCreatures(in: dataStack, period: period, completion: completion)
}
else {
completion()
}
}
)
}
private func spawnCreatures(
in dataStack: DataStack,
period: Advanced.EvolutionDemo.GeologicalPeriod,
completion: @escaping () -> Void
) {
dataStack.perform(
asynchronous: { (transaction) in
let creatureType = period.creatureType
for dnaCode in try creatureType.count(in: transaction) ..< 10000 {
let object = creatureType.create(in: transaction)
object.dnaCode = Int64(dnaCode)
object.mutate(in: transaction)
}
},
completion: { _ in completion() }
)
}
// MARK: - VersionMetadata
private struct VersionMetadata {
let label: String
let entityType: Advanced.EvolutionDemo.CreatureType.Type
let schemaHistory: SchemaHistory
}
}
}
@@ -0,0 +1,10 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
// MARK: - Classic
/**
Sample usages for `NSManagedObject` subclasses
*/
enum Classic {}
@@ -0,0 +1,79 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import Combine
import CoreStore
import SwiftUI
// MARK: - Classic.ColorsDemo
extension Classic.ColorsDemo {
// MARK: - Classic.ColorsDemo.DetailView
struct DetailView: UIViewControllerRepresentable {
// MARK: Internal
init(_ palette: ObjectMonitor<Classic.ColorsDemo.Palette>) {
self.palette = palette
}
// MARK: UIViewControllerRepresentable
typealias UIViewControllerType = Classic.ColorsDemo.DetailViewController
func makeUIViewController(context: Self.Context) -> UIViewControllerType {
return UIViewControllerType(self.palette)
}
func updateUIViewController(_ uiViewController: UIViewControllerType, context: Self.Context) {
uiViewController.palette = self.palette
}
static func dismantleUIViewController(_ uiViewController: UIViewControllerType, coordinator: Void) {}
func makeCoordinator() -> ObjectMonitor<Classic.ColorsDemo.Palette> {
return self.palette
}
// MARK: Private
private let palette: ObjectMonitor<Classic.ColorsDemo.Palette>
}
}
#if DEBUG
struct _Demo_Classic_ColorsDemo_DetailView_Preview: PreviewProvider {
// MARK: PreviewProvider
static var previews: some View {
try! Classic.ColorsDemo.dataStack.perform(
synchronous: { transaction in
guard (try transaction.fetchCount(From<Modern.ColorsDemo.Palette>())) <= 0 else {
return
}
let palette = transaction.create(Into<Modern.ColorsDemo.Palette>())
palette.setRandomHue()
}
)
return Classic.ColorsDemo.DetailView(
Classic.ColorsDemo.dataStack.monitorObject(
Classic.ColorsDemo.palettesMonitor[0, 0]
)
)
}
}
#endif
@@ -0,0 +1,36 @@
//
// Demo
// Copyright © 2020 John Rommel Estropia, Inc. All rights reserved.
import CoreStore
// MARK: - Classic.ColorsDemo
extension Classic.ColorsDemo {
// MARK: - Classic.ColorsDemo.Filter
enum Filter: String, CaseIterable {
case all = "All Colors"
case light = "Light Colors"
case dark = "Dark Colors"
func next() -> Filter {
let allCases = Self.allCases
return allCases[(allCases.firstIndex(of: self)! + 1) % allCases.count]
}
func whereClause() -> Where<Classic.ColorsDemo.Palette> {
switch self {
case .all: return .init()
case .light: return (\.brightness >= 0.6)
case .dark: return (\.brightness <= 0.4)
}
}
}
}

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