Merge branch 'develop' into prototype/propertyWrappers

# Conflicts:
#	CoreStoreTests/DynamicModelTests.swift
This commit is contained in:
John Estropia
2020-01-07 14:10:15 +09:00
140 changed files with 9785 additions and 2759 deletions

View File

@@ -1,41 +0,0 @@
language: objective-c
osx_image: xcode10.2
sudo: false
git:
submodules: false
notifications:
email: false
env:
global:
- LC_CTYPE=en_US.UTF-8
- LANG=en_US.UTF-8
matrix:
- DESTINATION="arch=x86_64" SCHEME="CoreStore OSX" SDK=macosx10.14 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=12.0,name=iPhone XS" SCHEME="CoreStore iOS" SDK=iphonesimulator12.0 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=11.0.1,name=iPhone 8" SCHEME="CoreStore iOS" SDK=iphonesimulator12.0 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=10.3.1,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator12.0 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=10.1,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator12.0 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=4.0,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator5.0 RUN_TESTS="NO" POD_LINT="NO"
- DESTINATION="OS=3.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator5.0 RUN_TESTS="NO" POD_LINT="NO"
- DESTINATION="OS=2.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator5.0 RUN_TESTS="NO" POD_LINT="NO"
- DESTINATION="OS=12.0,name=Apple TV 4K" SCHEME="CoreStore tvOS" SDK=appletvsimulator12.0 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=11.0,name=Apple TV 4K (at 1080p)" SCHEME="CoreStore tvOS" SDK=appletvsimulator12.0 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=10.2,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator12.0 RUN_TESTS="YES" POD_LINT="NO"
before_install:
- gem install cocoapods --no-rdoc --no-ri --no-document
- gem install xcpretty --no-rdoc --no-ri --no-document
- npm install ios-sim -g
- ios-sim start --devicetypeid "com.apple.CoreSimulator.SimDeviceType.iPhone-8, 11.0"
script:
- set -o pipefail
- xcodebuild -version
- xcodebuild -showsdks
- if [ $RUN_TESTS == "YES" ]; then
xcodebuild -workspace CoreStore.xcworkspace -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
xcodebuild -workspace CoreStore.xcworkspace -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
fi
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStoreDemo" -sdk "iphonesimulator12.0" -destination "OS=11.0.1,name=iPhone 8" -configuration Debug ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStoreDemo" -sdk "iphonesimulator12.0" -destination "OS=11.0.1,name=iPhone 8" -configuration Release ONLY_ACTIVE_ARCH=NO build | xcpretty -c;
- if [ $POD_LINT == "YES" ]; then
pod lib lint --quick;
fi

View File

@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "6.3.2"
s.swift_version = "5.0"
s.version = "7.0.2"
s.swift_version = "5.1"
s.license = "MIT"
s.homepage = "https://github.com/JohnEstropia/CoreStore"
s.documentation_url = "https://JohnEstropia.github.io/CoreStore"
@@ -18,5 +18,5 @@ Pod::Spec.new do |s|
s.public_header_files = "Sources/**/*.h"
s.frameworks = "Foundation", "CoreData"
s.requires_arc = true
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D DEBUG' }
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS[config=Debug]' => '-D DEBUG', 'OTHER_LDFLAGS' => '-weak_framework Combine -weak_framework SwiftUI' }
end

File diff suppressed because it is too large Load Diff

View File

@@ -27,6 +27,15 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B52DD1731BE1F8CC00949AFE"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore OSX"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
@@ -39,17 +48,6 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B52DD1731BE1F8CC00949AFE"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore OSX"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -70,8 +68,6 @@
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -41,18 +41,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
BuildableName = "CoreStoreTests.xctest"
BlueprintName = "CoreStoreTests iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -69,6 +57,18 @@
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
BuildableName = "CoreStoreTests.xctest"
BlueprintName = "CoreStoreTests iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -95,8 +95,6 @@
isEnabled = "NO">
</CommandLineArgument>
</CommandLineArguments>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -27,6 +27,15 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore tvOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<Testables>
<TestableReference
skipped = "NO">
@@ -39,17 +48,6 @@
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "82BA18881C4BBCBA00A0916E"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore tvOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -70,8 +68,6 @@
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -29,8 +29,6 @@
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -51,8 +49,6 @@
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -7,6 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
B50132242344E24300FC238B /* SwiftUIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50132232344E24300FC238B /* SwiftUIView.swift */; };
B50132282344E5E900FC238B /* SwiftUIContainerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B50132272344E5E900FC238B /* SwiftUIContainerViewController.swift */; };
B501323523477D2300FC238B /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B501323423477D2300FC238B /* SwiftUI.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */; };
B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */; };
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADD1AFDC71700F90881 /* Palette.swift */; };
@@ -23,6 +26,8 @@
B54AAD5B1AF4D26E00848AE0 /* Images.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */; };
B54AAD5E1AF4D26E00848AE0 /* LaunchScreen.xib in Resources */ = {isa = PBXBuildFile; fileRef = B54AAD5C1AF4D26E00848AE0 /* LaunchScreen.xib */; };
B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */; };
B5635D192357F9F700B80E6B /* CollectionViewDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D182357F9F700B80E6B /* CollectionViewDemoViewController.swift */; };
B5635D1B2357FA4B00B80E6B /* PaletteCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5635D1A2357FA4B00B80E6B /* PaletteCollectionViewCell.swift */; };
B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */; };
B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = B566E3311B11DF3200F4F0C6 /* UserAccount.swift */; };
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */; };
@@ -34,6 +39,8 @@
B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; };
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; };
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */; };
B5AA37EF2357D30300FFD4B9 /* ColorsDemo.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5AA37EE2357D30300FFD4B9 /* ColorsDemo.swift */; };
B5C37EE52357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5C37EE42357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift */; };
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */; };
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; };
B5E89AD11C5292A2003B04A9 /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -59,6 +66,9 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
B50132232344E24300FC238B /* SwiftUIView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIView.swift; sourceTree = "<group>"; };
B50132272344E5E900FC238B /* SwiftUIContainerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwiftUIContainerViewController.swift; sourceTree = "<group>"; };
B501323423477D2300FC238B /* SwiftUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = SwiftUI.framework; path = System/Library/Frameworks/SwiftUI.framework; sourceTree = SDKROOT; };
B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ListObserverDemoViewController.swift; sourceTree = "<group>"; };
B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ObjectObserverDemoViewController.swift; sourceTree = "<group>"; };
B503FADD1AFDC71700F90881 /* Palette.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Palette.swift; sourceTree = "<group>"; };
@@ -78,6 +88,8 @@
B54AAD5A1AF4D26E00848AE0 /* Images.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Images.xcassets; sourceTree = "<group>"; };
B54AAD5D1AF4D26E00848AE0 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/LaunchScreen.xib; sourceTree = "<group>"; };
B560070E1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV2ToV3MigrationPolicy.swift; sourceTree = "<group>"; };
B5635D182357F9F700B80E6B /* CollectionViewDemoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CollectionViewDemoViewController.swift; sourceTree = "<group>"; };
B5635D1A2357FA4B00B80E6B /* PaletteCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaletteCollectionViewCell.swift; sourceTree = "<group>"; };
B566E3291B117B1F00F4F0C6 /* StackSetupDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackSetupDemoViewController.swift; sourceTree = "<group>"; };
B566E3311B11DF3200F4F0C6 /* UserAccount.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UserAccount.swift; sourceTree = "<group>"; };
B56964C81B20AC780075EE4A /* CustomLoggerViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomLoggerViewController.swift; sourceTree = "<group>"; };
@@ -89,7 +101,9 @@
B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FetchingResultsViewController.swift; sourceTree = "<group>"; };
B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = "<group>"; };
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
B5AA37EE2357D30300FFD4B9 /* ColorsDemo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ColorsDemo.swift; sourceTree = "<group>"; };
B5BDC9211C202429008147CD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B5C37EE42357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaletteCollectionSectionHeaderView.swift; sourceTree = "<group>"; };
B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OrganismTableViewCell.swift; path = "CoreStoreDemo/MIgrations Demo/OrganismTableViewCell.swift"; sourceTree = SOURCE_ROOT; };
B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = "<group>"; };
B5EE25841B36E23C0000406B /* OrganismV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV1.swift; sourceTree = "<group>"; };
@@ -105,6 +119,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B501323523477D2300FC238B /* SwiftUI.framework in Frameworks */,
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */,
B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */,
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */,
@@ -114,14 +129,27 @@
/* End PBXFrameworksBuildPhase section */
/* Begin PBXGroup section */
B50132222344E20B00FC238B /* SwiftUI Demo */ = {
isa = PBXGroup;
children = (
B50132232344E24300FC238B /* SwiftUIView.swift */,
B50132272344E5E900FC238B /* SwiftUIContainerViewController.swift */,
);
path = "SwiftUI Demo";
sourceTree = "<group>";
};
B503FADA1AFDC71700F90881 /* List and Object Observers Demo */ = {
isa = PBXGroup;
children = (
B5AA37EE2357D30300FFD4B9 /* ColorsDemo.swift */,
B52977D81B120B80003D50A5 /* ObserversViewController.swift */,
B503FADB1AFDC71700F90881 /* ListObserverDemoViewController.swift */,
B5635D182357F9F700B80E6B /* CollectionViewDemoViewController.swift */,
B503FADC1AFDC71700F90881 /* ObjectObserverDemoViewController.swift */,
B503FADD1AFDC71700F90881 /* Palette.swift */,
B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */,
B5635D1A2357FA4B00B80E6B /* PaletteCollectionViewCell.swift */,
B5C37EE42357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift */,
);
path = "List and Object Observers Demo";
sourceTree = "<group>";
@@ -138,6 +166,7 @@
B52977E21B120F90003D50A5 /* Frameworks */ = {
isa = PBXGroup;
children = (
B501323423477D2300FC238B /* SwiftUI.framework */,
B52977E01B120F8A003D50A5 /* CoreLocation.framework */,
B5BDC9211C202429008147CD /* CoreStore.framework */,
B52977DE1B120F83003D50A5 /* MapKit.framework */,
@@ -166,6 +195,7 @@
isa = PBXGroup;
children = (
B54AAD4E1AF4D26E00848AE0 /* AppDelegate.swift */,
B50132222344E20B00FC238B /* SwiftUI Demo */,
B566E3271B117AE700F4F0C6 /* Stack Setup Demo */,
B503FADA1AFDC71700F90881 /* List and Object Observers Demo */,
B52977DB1B120F2C003D50A5 /* Transactions Demo */,
@@ -270,6 +300,7 @@
TargetAttributes = {
B54AAD481AF4D26E00848AE0 = {
CreatedOnToolsVersion = 6.3;
DevelopmentTeam = 2JT32EJ5BH;
LastSwiftMigration = 0900;
};
};
@@ -311,6 +342,7 @@
buildActionMask = 2147483647;
files = (
B56965181B2E20CC0075EE4A /* TimeZone.swift in Sources */,
B5C37EE52357FEBE0035A20D /* PaletteCollectionSectionHeaderView.swift in Sources */,
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */,
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */,
B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */,
@@ -322,6 +354,7 @@
B503FAE01AFDC71700F90881 /* ObjectObserverDemoViewController.swift in Sources */,
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */,
B56964C91B20AC780075EE4A /* CustomLoggerViewController.swift in Sources */,
B5635D192357F9F700B80E6B /* CollectionViewDemoViewController.swift in Sources */,
B566E32A1B117B1F00F4F0C6 /* StackSetupDemoViewController.swift in Sources */,
B56964DA1B231BCA0075EE4A /* MaleAccount.swift in Sources */,
B566E3321B11DF3200F4F0C6 /* UserAccount.swift in Sources */,
@@ -332,10 +365,14 @@
B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */,
B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */,
B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */,
B50132282344E5E900FC238B /* SwiftUIContainerViewController.swift in Sources */,
B5635D1B2357FA4B00B80E6B /* PaletteCollectionViewCell.swift in Sources */,
B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */,
B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */,
B5AA37EF2357D30300FFD4B9 /* ColorsDemo.swift in Sources */,
B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */,
B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */,
B50132242344E24300FC238B /* SwiftUIView.swift in Sources */,
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */,
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */,
);
@@ -473,6 +510,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = 2JT32EJ5BH;
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;
@@ -485,6 +523,7 @@
isa = XCBuildConfiguration;
buildSettings = {
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
DEVELOPMENT_TEAM = 2JT32EJ5BH;
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;

View File

@@ -27,8 +27,6 @@
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
@@ -38,8 +36,8 @@
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
<Testables>
</Testables>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
@@ -61,8 +59,6 @@
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"

View File

@@ -1,11 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14460.31" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Ni8-QF-XHB">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="15400" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Ni8-QF-XHB">
<device id="retina4_7" orientation="portrait" appearance="light"/>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14460.20"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="15404"/>
<capability name="collection view cell content view" minToolsVersion="11.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<customFonts key="customFonts">
@@ -68,10 +67,10 @@
</view>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="8b8-lM-Krq" detailTextLabel="hR1-Zb-BOk" style="IBUITableViewCellStyleValue1" id="dMt-nx-EO5">
<rect key="frame" x="0.0" y="172" width="375" height="44"/>
<rect key="frame" x="0.0" y="178" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="dMt-nx-EO5" id="gdK-VV-zNb">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8b8-lM-Krq">
@@ -121,15 +120,15 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="WUc-3Y-Quw">
<rect key="frame" x="0.0" y="324" width="375" height="343"/>
<rect key="frame" x="0.0" y="304" width="375" height="363"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="separatorColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="OrganismTableViewCell" id="WVb-th-o8c" customClass="OrganismTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="22" width="375" height="44"/>
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="WVb-th-o8c" id="JBq-Ml-a9p">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OQf-Bd-Zze">
@@ -170,25 +169,25 @@
</connections>
</tableView>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="XKA-Ub-c2X">
<rect key="frame" x="0.0" y="64" width="375" height="260"/>
<rect key="frame" x="0.0" y="44" width="375" height="260"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="i7U-bW-juB" customClass="UIButton">
<rect key="frame" x="0.0" y="0.0" width="375" height="260"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Organism" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="zxy-nY-P44">
<rect key="frame" x="20" y="90.5" width="335" height="26.5"/>
<rect key="frame" x="20" y="93.5" width="335" height="26.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="22"/>
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="attributes" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="6Ac-xl-ldZ">
<rect key="frame" x="20" y="131.5" width="335" height="20.5"/>
<rect key="frame" x="20" y="134.5" width="335" height="20.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="17"/>
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="plain" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="rAZ-eJ-sxy">
<rect key="frame" x="20" y="20" width="335" height="29"/>
<rect key="frame" x="20" y="20" width="335" height="32"/>
<segments>
<segment title="First"/>
<segment title="Second"/>
@@ -200,7 +199,7 @@
</connections>
</segmentedControl>
<progressView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="869-wx-Odb">
<rect key="frame" x="20" y="68" width="335" height="2"/>
<rect key="frame" x="20" y="71" width="335" height="2"/>
<color key="progressTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="trackTintColor" red="1" green="1" blue="1" alpha="0.20000000000000001" colorSpace="custom" customColorSpace="sRGB"/>
</progressView>
@@ -269,21 +268,21 @@
<tableViewSection id="wIP-Hn-YfF">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Q3n-Df-v1t" detailTextLabel="Hbn-cf-Y7m" style="IBUITableViewCellStyleSubtitle" id="AXm-KE-45G">
<rect key="frame" x="0.0" y="35" width="375" height="50"/>
<rect key="frame" x="0.0" y="10" width="375" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="AXm-KE-45G" id="9te-Wx-hkf">
<rect key="frame" x="0.0" y="0.0" width="341" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="348" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Accounts" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Q3n-Df-v1t">
<rect key="frame" x="16" y="6" width="82" height="24"/>
<rect key="frame" x="16" y="7" width="82" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Setting up multiple persistent store configurations" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hbn-cf-Y7m">
<rect key="frame" x="16" y="30" width="263.5" height="13.5"/>
<rect key="frame" x="16" y="31" width="263.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -296,21 +295,21 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="vpt-cT-gMo" detailTextLabel="ou9-TZ-8bf" style="IBUITableViewCellStyleSubtitle" id="fsb-zw-8Ii">
<rect key="frame" x="0.0" y="85" width="375" height="50"/>
<rect key="frame" x="0.0" y="60" width="375" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="fsb-zw-8Ii" id="Upm-AO-Fw3">
<rect key="frame" x="0.0" y="0.0" width="341" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="348" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Colors" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="vpt-cT-gMo">
<rect key="frame" x="16" y="6" width="56" height="24"/>
<rect key="frame" x="16" y="7" width="56" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observing list changes and single object changes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ou9-TZ-8bf">
<rect key="frame" x="16" y="30" width="260.5" height="13.5"/>
<rect key="frame" x="16" y="31" width="260.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -323,21 +322,21 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="UbU-Kd-yrY" detailTextLabel="uP1-Jc-o9v" style="IBUITableViewCellStyleSubtitle" id="ekW-PJ-mbo">
<rect key="frame" x="0.0" y="135" width="375" height="50"/>
<rect key="frame" x="0.0" y="110" width="375" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ekW-PJ-mbo" id="CYq-mg-PVS">
<rect key="frame" x="0.0" y="0.0" width="341" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="348" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Placemarks" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="UbU-Kd-yrY">
<rect key="frame" x="16" y="6" width="100.5" height="24"/>
<rect key="frame" x="16" y="7" width="100.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Making changes with transactions" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="uP1-Jc-o9v">
<rect key="frame" x="16" y="30" width="179" height="13.5"/>
<rect key="frame" x="16" y="31" width="179" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -350,21 +349,21 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="C8Y-0y-lEG" detailTextLabel="jZw-qE-0ws" style="IBUITableViewCellStyleSubtitle" id="ph1-8z-C1m">
<rect key="frame" x="0.0" y="185" width="375" height="50"/>
<rect key="frame" x="0.0" y="160" width="375" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="ph1-8z-C1m" id="nNz-rd-ksg">
<rect key="frame" x="0.0" y="0.0" width="341" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="348" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Time Zones" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="C8Y-0y-lEG">
<rect key="frame" x="16" y="6" width="101.5" height="24"/>
<rect key="frame" x="16" y="7" width="101.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws">
<rect key="frame" x="16" y="30" width="168.5" height="13.5"/>
<rect key="frame" x="16" y="31" width="168.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -377,21 +376,21 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="ZfY-Aq-Ykq" detailTextLabel="QzD-9b-k1j" style="IBUITableViewCellStyleSubtitle" id="wyK-rk-3tI">
<rect key="frame" x="0.0" y="235" width="375" height="50"/>
<rect key="frame" x="0.0" y="210" width="375" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="wyK-rk-3tI" id="fLd-KK-QcW">
<rect key="frame" x="0.0" y="0.0" width="341" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="348" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZfY-Aq-Ykq">
<rect key="frame" x="16" y="6" width="61" height="24"/>
<rect key="frame" x="16" y="7" width="61" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j">
<rect key="frame" x="16" y="30" width="159" height="13.5"/>
<rect key="frame" x="16" y="31" width="159" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -404,21 +403,21 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="hSG-mG-YBw" detailTextLabel="X9P-TQ-LYh" style="IBUITableViewCellStyleSubtitle" id="xTM-Cf-0if">
<rect key="frame" x="0.0" y="285" width="375" height="50"/>
<rect key="frame" x="0.0" y="260" width="375" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="xTM-Cf-0if" id="DfO-BW-krd">
<rect key="frame" x="0.0" y="0.0" width="341" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="348" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Evolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hSG-mG-YBw">
<rect key="frame" x="16" y="6" width="78.5" height="24"/>
<rect key="frame" x="16" y="7" width="78.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Migrating and de-migrating stores" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="X9P-TQ-LYh">
<rect key="frame" x="16" y="30" width="179.5" height="13.5"/>
<rect key="frame" x="16" y="31" width="179.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -430,6 +429,33 @@
<segue destination="iVv-Vc-nCL" kind="show" id="HbO-rU-Qfj"/>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="o2r-2N-Xda" detailTextLabel="2hh-MM-Oea" style="IBUITableViewCellStyleSubtitle" id="f25-dJ-AuT">
<rect key="frame" x="0.0" y="310" width="375" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="f25-dJ-AuT" id="86t-0w-JT9">
<rect key="frame" x="0.0" y="0.0" width="348" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="SwiftUI" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="o2r-2N-Xda">
<rect key="frame" x="16" y="7" width="60.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629840000001" green="0.1840757281" blue="0.245942995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Binding with SwiftUI and Combine" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="2hh-MM-Oea">
<rect key="frame" x="16" y="31" width="178" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629840000001" green="0.1840757281" blue="0.245942995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="rhi-tx-cSW" kind="show" id="kb3-NX-Aao"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
@@ -444,6 +470,38 @@
</objects>
<point key="canvasLocation" x="2634" y="2013"/>
</scene>
<!--SwiftUI-->
<scene sceneID="Uiz-5a-tKq">
<objects>
<viewController id="rhi-tx-cSW" customClass="SwiftUIContainerViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<layoutGuides>
<viewControllerLayoutGuide type="top" id="0Nq-lg-kDJ"/>
<viewControllerLayoutGuide type="bottom" id="72Q-TT-uXH"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="JvO-3D-U2t">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="Not supported on this device/OS version" textAlignment="center" lineBreakMode="tailTruncation" numberOfLines="0" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Z0H-X0-3xA">
<rect key="frame" x="36" y="274.5" width="303" height="118.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Bold" family="Helvetica Neue" pointSize="33"/>
<color key="textColor" white="0.66666666666666663" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<constraints>
<constraint firstItem="Z0H-X0-3xA" firstAttribute="centerY" secondItem="JvO-3D-U2t" secondAttribute="centerY" id="8Hp-yB-ciV"/>
<constraint firstItem="Z0H-X0-3xA" firstAttribute="leading" secondItem="JvO-3D-U2t" secondAttribute="leadingMargin" constant="20" id="l7i-J0-oG5"/>
<constraint firstAttribute="trailingMargin" secondItem="Z0H-X0-3xA" secondAttribute="trailing" constant="20" id="mrq-pp-Q06"/>
</constraints>
</view>
<navigationItem key="navigationItem" title="SwiftUI" id="gFf-uX-EUI"/>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="HSp-lA-NAM" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3694" y="4816"/>
</scene>
<!--Object Observer-->
<scene sceneID="Y7v-tc-8cX">
<objects>
@@ -453,18 +511,18 @@
<viewControllerLayoutGuide type="bottom" id="aI4-O3-OCi"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="w8K-eN-RvU">
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
<rect key="frame" x="16" y="69.5" width="343" height="70"/>
<rect key="frame" x="16" y="69.5" width="343" height="80"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="TIX-qi-B34"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" verticalCompressionResistancePriority="250" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumScaleFactor="0.5" adjustsLetterSpacingToFitWidth="YES" translatesAutoresizingMaskIntoConstraints="NO" id="Vfe-Yq-3Xa">
<rect key="frame" x="16" y="149.5" width="343" height="18"/>
<rect key="frame" x="16" y="159.5" width="343" height="18"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="4h9-ha-EzR"/>
</constraints>
@@ -473,37 +531,37 @@
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Hue" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="sgg-Md-Nf3">
<rect key="frame" x="16" y="187.5" width="74" height="18"/>
<rect key="frame" x="16" y="197.5" width="74" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Saturation" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="rry-vh-bRK">
<rect key="frame" x="16" y="225.5" width="74" height="18"/>
<rect key="frame" x="16" y="235.5" width="74" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Brightness" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="vTa-ly-eyO">
<rect key="frame" x="16" y="263.5" width="74" height="18"/>
<rect key="frame" x="16" y="273.5" width="74" height="18"/>
<fontDescription key="fontDescription" type="system" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" minValue="0.0" maxValue="359" translatesAutoresizingMaskIntoConstraints="NO" id="YQ6-fq-3Wb">
<rect key="frame" x="98" y="181.5" width="263" height="31"/>
<rect key="frame" x="98" y="191.5" width="263" height="31"/>
<connections>
<action selector="hueSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="9Hy-3h-llE"/>
</connections>
</slider>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="1" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="xXz-78-tAd">
<rect key="frame" x="98" y="219.5" width="263" height="31"/>
<rect key="frame" x="98" y="229.5" width="263" height="31"/>
<connections>
<action selector="saturationSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="qtU-ua-ZTc"/>
</connections>
</slider>
<slider opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" value="0.5" minValue="0.0" maxValue="1" translatesAutoresizingMaskIntoConstraints="NO" id="hpy-2d-eOP">
<rect key="frame" x="98" y="257.5" width="263" height="31"/>
<rect key="frame" x="98" y="267.5" width="263" height="31"/>
<connections>
<action selector="brightnessSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="F09-EP-2iD"/>
</connections>
@@ -566,7 +624,7 @@
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="49t-Sy-Rq7" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="5864" y="1324.5"/>
<point key="canvasLocation" x="6316" y="1267"/>
</scene>
<!--Colors-->
<scene sceneID="3lD-lX-hIc">
@@ -580,34 +638,55 @@
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf">
<rect key="frame" x="0.0" y="64" width="375" height="301.5"/>
<connections>
<segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/>
</connections>
</containerView>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp">
<rect key="frame" x="0.0" y="365.5" width="375" height="301.5"/>
<connections>
<segue destination="sll-yo-mBc" kind="embed" id="AAl-HS-dq2"/>
</connections>
</containerView>
<stackView opaque="NO" contentMode="scaleToFill" axis="vertical" distribution="fillEqually" translatesAutoresizingMaskIntoConstraints="NO" id="cce-yT-4dn">
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
<subviews>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="L5f-tW-lXf">
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
<connections>
<segue destination="5Fw-je-9gI" kind="embed" id="YcI-2Z-ijV"/>
</connections>
</containerView>
<containerView opaque="NO" contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="6So-f3-4Gp">
<rect key="frame" x="0.0" y="311.5" width="375" height="311.5"/>
<connections>
<segue destination="Rdw-NU-NaK" kind="embed" id="3Ob-Bj-yvz"/>
</connections>
</containerView>
</subviews>
</stackView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="6So-f3-4Gp" firstAttribute="top" secondItem="L5f-tW-lXf" secondAttribute="bottom" id="3m8-tj-Nd4"/>
<constraint firstAttribute="trailingMargin" secondItem="6So-f3-4Gp" secondAttribute="trailing" constant="-16" id="4L8-wZ-F59"/>
<constraint firstItem="L5f-tW-lXf" firstAttribute="height" secondItem="6So-f3-4Gp" secondAttribute="height" id="8XS-L3-hvN"/>
<constraint firstItem="LNL-mj-D7l" firstAttribute="top" secondItem="6So-f3-4Gp" secondAttribute="bottom" id="8wL-zm-wnt"/>
<constraint firstItem="L5f-tW-lXf" firstAttribute="leading" secondItem="6x3-vn-Egt" secondAttribute="leadingMargin" constant="-16" id="CbE-2f-7wk"/>
<constraint firstAttribute="trailingMargin" secondItem="L5f-tW-lXf" secondAttribute="trailing" constant="-16" id="dso-2g-fgA"/>
<constraint firstItem="6So-f3-4Gp" firstAttribute="leading" secondItem="6x3-vn-Egt" secondAttribute="leadingMargin" constant="-16" id="eXM-D3-NLv"/>
<constraint firstItem="L5f-tW-lXf" firstAttribute="top" secondItem="IML-3o-caw" secondAttribute="bottom" id="zJ5-sE-iJA"/>
<constraint firstAttribute="trailing" secondItem="cce-yT-4dn" secondAttribute="trailing" id="DE1-u6-1mr"/>
<constraint firstItem="LNL-mj-D7l" firstAttribute="top" secondItem="cce-yT-4dn" secondAttribute="bottom" id="WIZ-M3-bdr"/>
<constraint firstItem="cce-yT-4dn" firstAttribute="leading" secondItem="6x3-vn-Egt" secondAttribute="leading" id="ZiS-j0-ANp"/>
<constraint firstItem="cce-yT-4dn" firstAttribute="top" secondItem="IML-3o-caw" secondAttribute="bottom" id="zjs-CM-7r5"/>
</constraints>
</view>
<extendedEdge key="edgesForExtendedLayout" top="YES"/>
<navigationItem key="navigationItem" title="Colors" id="7Gd-Ad-Bzu"/>
<navigationItem key="navigationItem" title="Colors" id="7Gd-Ad-Bzu">
<rightBarButtonItems>
<barButtonItem title="▼" width="36" id="aPM-OC-EB8">
<connections>
<action selector="toggleBottomContainerView" destination="YOI-b7-Nxn" id="7fn-z6-pne"/>
</connections>
</barButtonItem>
<barButtonItem title="▲" width="36" id="6q3-Zk-A9p">
<connections>
<action selector="toggleTopContainerView" destination="YOI-b7-Nxn" id="APN-Kk-C3n"/>
</connections>
</barButtonItem>
</rightBarButtonItems>
</navigationItem>
<nil key="simulatedBottomBarMetrics"/>
<connections>
<outlet property="bottomContainerView" destination="6So-f3-4Gp" id="C7O-e2-k0l"/>
<outlet property="stackView" destination="cce-yT-4dn" id="r5B-BT-mUM"/>
<outlet property="toggleBottomBarButtonItem" destination="aPM-OC-EB8" id="kEV-gg-XOX"/>
<outlet property="toggleTopBarButtonItem" destination="6q3-Zk-A9p" id="usC-Zu-DQm"/>
<outlet property="topContainerView" destination="L5f-tW-lXf" id="DRV-mm-rBY"/>
</connections>
</viewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="C9h-Ba-WoL" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
@@ -620,7 +699,7 @@
<tabBarItem key="tabBarItem" title="Demo" image="second" id="3iQ-I2-4LW"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="00L-5k-Eno">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -638,19 +717,19 @@
</objects>
<point key="canvasLocation" x="1822" y="2013"/>
</scene>
<!--List Observer-->
<!--ListPublisher-->
<scene sceneID="gkX-bd-Rel">
<objects>
<tableViewController id="3AE-ED-0oj" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="DAz-BE-6Ca">
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="G3X-70-BCD" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="22" width="375" height="44"/>
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="G3X-70-BCD" id="aT8-nz-i5l">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="348" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uQX-PI-UWF">
@@ -668,6 +747,7 @@
</label>
</subviews>
<constraints>
<constraint firstAttribute="trailing" relation="greaterThanOrEqual" secondItem="HJC-5w-lIN" secondAttribute="trailing" id="8FZ-9e-AdN"/>
<constraint firstItem="HJC-5w-lIN" firstAttribute="height" secondItem="uQX-PI-UWF" secondAttribute="height" id="8Py-Ub-gQD"/>
<constraint firstItem="uQX-PI-UWF" firstAttribute="top" secondItem="aT8-nz-i5l" secondAttribute="topMargin" id="9e4-wD-lwf"/>
<constraint firstAttribute="centerY" secondItem="HJC-5w-lIN" secondAttribute="centerY" id="AeC-j4-DBE"/>
@@ -688,99 +768,14 @@
</connections>
</tableView>
<extendedEdge key="edgesForExtendedLayout" top="YES"/>
<navigationItem key="navigationItem" title="List Observer" id="JjF-x3-ixG"/>
<navigationItem key="navigationItem" title="ListPublisher" id="5kj-jr-HoL"/>
<connections>
<segue destination="dX3-kR-CYC" kind="show" identifier="ObjectObserverDemoViewController" id="hyN-De-zte"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="P5L-49-pOr" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="5085" y="1144.5"/>
</scene>
<!--List Observer-->
<scene sceneID="iDB-TD-It9">
<objects>
<tableViewController id="lCE-i6-UCT" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="Zba-8M-Zd7">
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="zSO-3e-OVq" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="22" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="zSO-3e-OVq" id="cHA-by-n4b">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5uq-Yi-XwH">
<rect key="frame" x="16" y="11" width="22" height="22"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstAttribute="width" secondItem="5uq-Yi-XwH" secondAttribute="height" multiplier="1:1" id="oOe-HC-VyN"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Zyu-PC-WmO">
<rect key="frame" x="48" y="11" width="34.5" height="22"/>
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="14"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="bottomMargin" secondItem="5uq-Yi-XwH" secondAttribute="bottom" id="2Bf-dJ-VmS"/>
<constraint firstItem="Zyu-PC-WmO" firstAttribute="leading" secondItem="5uq-Yi-XwH" secondAttribute="trailing" constant="10" id="6uD-S1-gkb"/>
<constraint firstAttribute="centerY" secondItem="Zyu-PC-WmO" secondAttribute="centerY" id="KQ6-bN-Og6"/>
<constraint firstItem="5uq-Yi-XwH" firstAttribute="top" secondItem="cHA-by-n4b" secondAttribute="topMargin" id="NqW-uA-mqj"/>
<constraint firstItem="Zyu-PC-WmO" firstAttribute="height" secondItem="5uq-Yi-XwH" secondAttribute="height" id="Yd5-bt-R8q"/>
<constraint firstItem="5uq-Yi-XwH" firstAttribute="leading" secondItem="cHA-by-n4b" secondAttribute="leadingMargin" id="a2a-Ol-0HV"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="colorView" destination="5uq-Yi-XwH" id="tgl-W1-A55"/>
<outlet property="label" destination="Zyu-PC-WmO" id="Oda-gD-ElI"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="lCE-i6-UCT" id="vTv-9f-P8U"/>
<outlet property="delegate" destination="lCE-i6-UCT" id="Ken-sI-O2f"/>
</connections>
</tableView>
<extendedEdge key="edgesForExtendedLayout" top="YES"/>
<navigationItem key="navigationItem" title="List Observer" id="koc-aK-cgD"/>
<connections>
<segue destination="dX3-kR-CYC" kind="show" identifier="ObjectObserverDemoViewController" id="fIB-GS-Ppk"/>
</connections>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="QAS-su-ZdM" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="5085" y="1546"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="DTD-lH-nr5">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="sll-yo-mBc" sceneMemberID="viewController">
<extendedEdge key="edgesForExtendedLayout" bottom="YES"/>
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" id="6XA-6M-yvZ">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" red="0.68773996829986572" green="0.71417498588562012" blue="0.73246318101882935" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes">
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.68773996829986572" green="0.71417498588562012" blue="0.73246318101882935" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</textAttributes>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="lCE-i6-UCT" kind="relationship" relationship="rootViewController" id="4Vz-ah-FHX"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="wJg-s1-Dpr" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="4404" y="1546"/>
<point key="canvasLocation" x="5156" y="1144"/>
</scene>
<!--Placemarks-->
<scene sceneID="LRD-q1-hw1">
@@ -791,11 +786,11 @@
<viewControllerLayoutGuide type="bottom" id="RZg-hi-T8O"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="k4s-iL-Krh">
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="623"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<mapView verifyAmbiguity="ignoreSizes" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
<rect key="frame" x="0.0" y="0.0" width="375" height="603"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="623"/>
<connections>
<outlet property="delegate" destination="jPl-fH-NlD" id="Sjn-YC-haS"/>
</connections>
@@ -832,14 +827,14 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<textView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" directionalLockEnabled="YES" alwaysBounceVertical="YES" indicatorStyle="white" editable="NO" translatesAutoresizingMaskIntoConstraints="NO" id="TpK-gX-CTN">
<rect key="frame" x="0.0" y="142" width="375" height="525"/>
<rect key="frame" x="0.0" y="125" width="375" height="542"/>
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.92706394195556641" green="0.72759377956390381" blue="0.064024783670902252" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<fontDescription key="fontDescription" name="AppleSDGothicNeo-Regular" family="Apple SD Gothic Neo" pointSize="15"/>
<textInputTraits key="textInputTraits" autocapitalizationType="sentences"/>
</textView>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bordered" momentary="YES" translatesAutoresizingMaskIntoConstraints="NO" id="4iq-B4-k0p">
<rect key="frame" x="20" y="94" width="335" height="29"/>
<rect key="frame" x="20" y="74" width="335" height="32"/>
<segments>
<segment title="Log"/>
<segment title="Error"/>
@@ -887,8 +882,8 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" id="wJo-mp-1pS">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" red="0.68773996829986572" green="0.71417498588562012" blue="0.73246318101882935" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" red="0.68627450980392157" green="0.71372549019607845" blue="0.73333333333333328" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.12941176470588234" green="0.18431372549019609" blue="0.24705882352941178" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes">
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.68773996829986572" green="0.71417498588562012" blue="0.73246318101882935" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -903,6 +898,80 @@
</objects>
<point key="canvasLocation" x="4404" y="1144.5"/>
</scene>
<!--ListPublisher-->
<scene sceneID="CXQ-vG-dtK">
<objects>
<collectionViewController id="EKY-1g-ALK" customClass="CollectionViewDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<collectionView key="view" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" dataMode="prototypes" id="hx3-j3-Q80">
<rect key="frame" x="0.0" y="0.0" width="375" height="311.5"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
<collectionViewFlowLayout key="collectionViewLayout" automaticEstimatedItemSize="YES" minimumLineSpacing="10" minimumInteritemSpacing="10" id="X6n-f9-yEl">
<size key="itemSize" width="50" height="50"/>
<size key="headerReferenceSize" width="50" height="50"/>
<size key="footerReferenceSize" width="0.0" height="0.0"/>
<inset key="sectionInset" minX="0.0" minY="0.0" maxX="0.0" maxY="0.0"/>
</collectionViewFlowLayout>
<cells>
<collectionViewCell opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="PaletteCollectionViewCell" id="x2U-Yq-KAx" customClass="PaletteCollectionViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="50" width="50" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<collectionViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" insetsLayoutMarginsFromSafeArea="NO" id="Alc-Is-Hc1">
<rect key="frame" x="0.0" y="0.0" width="50" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="NxF-xt-nas">
<rect key="frame" x="2" y="2" width="46" height="46"/>
<color key="backgroundColor" systemColor="systemBackgroundColor" cocoaTouchSystemColor="whiteColor"/>
</view>
</subviews>
<constraints>
<constraint firstItem="NxF-xt-nas" firstAttribute="leading" secondItem="Alc-Is-Hc1" secondAttribute="leading" constant="2" id="Hjb-0k-4HP"/>
<constraint firstAttribute="trailing" secondItem="NxF-xt-nas" secondAttribute="trailing" constant="2" id="fKI-q8-bbI"/>
<constraint firstItem="NxF-xt-nas" firstAttribute="top" secondItem="Alc-Is-Hc1" secondAttribute="top" constant="2" id="qS6-eD-5T1"/>
<constraint firstAttribute="bottom" secondItem="NxF-xt-nas" secondAttribute="bottom" constant="2" id="uWP-o0-nzA"/>
</constraints>
</collectionViewCellContentView>
<connections>
<outlet property="colorView" destination="NxF-xt-nas" id="ywq-kB-ycp"/>
</connections>
</collectionViewCell>
</cells>
<collectionReusableView key="sectionHeaderView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" reuseIdentifier="PaletteCollectionSectionHeaderView" id="bRa-K0-IUz" customClass="PaletteCollectionSectionHeaderView" customModule="CoreStoreDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="0.0" width="375" height="50"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="KHu-XO-jxd">
<rect key="frame" x="10" y="2" width="363" height="46"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Medium" family="Helvetica Neue" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstAttribute="trailing" secondItem="KHu-XO-jxd" secondAttribute="trailing" constant="2" id="Meb-ZG-6uP"/>
<constraint firstAttribute="bottom" secondItem="KHu-XO-jxd" secondAttribute="bottom" constant="2" id="SL5-cA-7nf"/>
<constraint firstItem="KHu-XO-jxd" firstAttribute="top" secondItem="bRa-K0-IUz" secondAttribute="top" constant="2" id="VFE-35-4gg"/>
<constraint firstItem="KHu-XO-jxd" firstAttribute="leading" secondItem="bRa-K0-IUz" secondAttribute="leading" constant="10" id="pzx-h0-rZd"/>
</constraints>
<connections>
<outlet property="label" destination="KHu-XO-jxd" id="qqK-Rm-eww"/>
</connections>
</collectionReusableView>
<connections>
<outlet property="dataSource" destination="EKY-1g-ALK" id="way-Q8-Xhb"/>
<outlet property="delegate" destination="EKY-1g-ALK" id="r9k-Fz-mWc"/>
</connections>
</collectionView>
<navigationItem key="navigationItem" title="ListPublisher" id="goy-Zu-94A"/>
<connections>
<segue destination="dX3-kR-CYC" kind="show" identifier="ObjectObserverDemoViewController" id="k4E-dJ-1lz"/>
</connections>
</collectionViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="Fmi-gN-RXz" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="5156" y="1484"/>
</scene>
<!--Time Zones-->
<scene sceneID="nFT-bo-7y9">
<objects>
@@ -916,7 +985,7 @@
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<tableView clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" translatesAutoresizingMaskIntoConstraints="NO" id="WGY-kX-mAx">
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<rect key="frame" x="0.0" y="44" width="375" height="623"/>
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="separatorColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<view key="tableHeaderView" contentMode="scaleToFill" id="iaH-1W-Sbo">
@@ -924,7 +993,7 @@
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bordered" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="YVj-dA-fyV">
<rect key="frame" x="20" y="26" width="335" height="29"/>
<rect key="frame" x="20" y="24.5" width="335" height="32"/>
<segments>
<segment title="Fetch"/>
<segment title="Query"/>
@@ -944,14 +1013,14 @@
</view>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="6db-P0-6Ms" style="IBUITableViewCellStyleDefault" id="vUr-WV-qur">
<rect key="frame" x="0.0" y="102" width="375" height="44"/>
<rect key="frame" x="0.0" y="108" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="vUr-WV-qur" id="Vr0-hE-cn9">
<rect key="frame" x="0.0" y="0.0" width="342" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="349" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6db-P0-6Ms">
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
<rect key="frame" x="15" y="0.0" width="326" height="44"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="18"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -998,21 +1067,21 @@
<color key="separatorColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="RgX-yK-1L2" detailTextLabel="QZ4-A2-x4h" style="IBUITableViewCellStyleSubtitle" id="uBt-Iy-nWP">
<rect key="frame" x="0.0" y="36" width="375" height="60"/>
<rect key="frame" x="0.0" y="28" width="375" height="60"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="uBt-Iy-nWP" id="6SD-ur-9zp">
<rect key="frame" x="0.0" y="0.0" width="375" height="59.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RgX-yK-1L2">
<rect key="frame" x="16" y="11" width="48.5" height="24"/>
<rect key="frame" x="16" y="12" width="48.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QZ4-A2-x4h">
<rect key="frame" x="16" y="35" width="31" height="13.5"/>
<rect key="frame" x="16" y="36" width="31" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -1044,21 +1113,21 @@
<color key="separatorColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="none" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="Syt-QJ-KXg" detailTextLabel="yHS-dP-IKS" style="IBUITableViewCellStyleSubtitle" id="q7Q-aF-Ftl">
<rect key="frame" x="0.0" y="36" width="375" height="60"/>
<rect key="frame" x="0.0" y="28" width="375" height="60"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="q7Q-aF-Ftl" id="fc3-eg-yes">
<rect key="frame" x="0.0" y="0.0" width="375" height="59.5"/>
<rect key="frame" x="0.0" y="0.0" width="375" height="60"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Syt-QJ-KXg">
<rect key="frame" x="16" y="11" width="48.5" height="24"/>
<rect key="frame" x="16" y="12" width="48.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="yHS-dP-IKS">
<rect key="frame" x="16" y="35" width="31" height="13.5"/>
<rect key="frame" x="16" y="36" width="31" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
@@ -1079,11 +1148,35 @@
</objects>
<point key="canvasLocation" x="4404" y="3055"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="s0N-is-CU8">
<objects>
<navigationController automaticallyAdjustsScrollViewInsets="NO" id="Rdw-NU-NaK" sceneMemberID="viewController">
<toolbarItems/>
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="clN-r9-yIR">
<rect key="frame" x="0.0" y="0.0" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" red="0.68627450980000004" green="0.71372549019999998" blue="0.73333333329999995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.12941176469999999" green="0.1840757281" blue="0.245942995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes">
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="21"/>
<color key="textColor" red="0.68627450980000004" green="0.71372549019999998" blue="0.73333333329999995" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</textAttributes>
</navigationBar>
<nil name="viewControllers"/>
<connections>
<segue destination="EKY-1g-ALK" kind="relationship" relationship="rootViewController" id="CG7-LF-MHV"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="qAN-O1-Rn4" userLabel="First Responder" customClass="UIResponder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="4404" y="1484"/>
</scene>
</scenes>
<resources>
<image name="second" width="30" height="30"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="fIB-GS-Ppk"/>
<segue reference="k4E-dJ-1lz"/>
</inferredMetricsTieBreakers>
</document>

View File

@@ -50,5 +50,7 @@
</array>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIUserInterfaceStyle</key>
<string>Light</string>
</dict>
</plist>

View File

@@ -0,0 +1,169 @@
//
// CollectionViewDemoViewController.swift
// CoreStoreDemo
//
// Created by John Estropia on 2019/10/17.
// Copyright © 2019 John Rommel Estropia. All rights reserved.
//
import UIKit
import CoreStore
// MARK: - CollectionViewDemoViewController
final class CollectionViewDemoViewController: UICollectionViewController {
// MARK: UIViewController
override func viewDidLoad() {
super.viewDidLoad()
let navigationItem = self.navigationItem
navigationItem.leftBarButtonItems = [
self.editButtonItem,
UIBarButtonItem(
barButtonSystemItem: .trash,
target: self,
action: #selector(self.resetBarButtonItemTouched(_:))
)
]
let filterBarButton = UIBarButtonItem(
title: ColorsDemo.filter.rawValue,
style: .plain,
target: self,
action: #selector(self.filterBarButtonItemTouched(_:))
)
navigationItem.rightBarButtonItems = [
UIBarButtonItem(
barButtonSystemItem: .add,
target: self,
action: #selector(self.addBarButtonItemTouched(_:))
),
UIBarButtonItem(
barButtonSystemItem: .refresh,
target: self,
action: #selector(self.shuffleBarButtonItemTouched(_:))
),
filterBarButton
]
self.filterBarButton = filterBarButton
self.dataSource = DiffableDataSource.CollectionViewAdapter<Palette>(
collectionView: self.collectionView,
dataStack: ColorsDemo.stack,
cellProvider: { (collectionView, indexPath, palette) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PaletteCollectionViewCell", for: indexPath) as! PaletteCollectionViewCell
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
return cell
},
supplementaryViewProvider: { (collectionView, kind, indexPath) in
switch kind {
case UICollectionView.elementKindSectionHeader:
let view = collectionView.dequeueReusableSupplementaryView(ofKind: kind, withReuseIdentifier: "PaletteCollectionSectionHeaderView", for: indexPath) as! PaletteCollectionSectionHeaderView
view.label?.text = ColorsDemo.palettes.snapshot.sectionIDs[indexPath.section]
return view
default:
return nil
}
}
)
ColorsDemo.palettes.addObserver(self) { [weak self] (listPublisher) in
guard let self = self else {
return
}
self.filterBarButton?.title = ColorsDemo.filter.rawValue
self.dataSource?.apply(listPublisher.snapshot, animatingDifferences: true)
}
self.dataSource?.apply(ColorsDemo.palettes.snapshot, animatingDifferences: false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
super.prepare(for: segue, sender: sender)
switch (segue.identifier, segue.destination, sender) {
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as ObjectPublisher<Palette>):
destinationViewController.setPalette(palette)
default:
break
}
}
// MARK: UICollectionViewDelegate
override func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
collectionView.deselectItem(at: indexPath, animated: true)
self.performSegue(
withIdentifier: "ObjectObserverDemoViewController",
sender: ColorsDemo.palettes.snapshot[indexPath]
)
}
// MARK: Private
private var filterBarButton: UIBarButtonItem?
private var dataSource: DiffableDataSource.CollectionViewAdapter<Palette>?
deinit {
ColorsDemo.palettes.removeObserver(self)
}
@IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) {
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
try transaction.deleteAll(From<Palette>())
},
completion: { _ in }
)
}
@IBAction private dynamic func filterBarButtonItemTouched(_ sender: AnyObject?) {
ColorsDemo.filter = ColorsDemo.filter.next()
}
@IBAction private dynamic func addBarButtonItemTouched(_ sender: AnyObject?) {
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
let palette = transaction.create(Into<Palette>())
palette.setInitialValues(in: transaction)
},
completion: { _ in }
)
}
@IBAction private dynamic func shuffleBarButtonItemTouched(_ sender: AnyObject?) {
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
for palette in try transaction.fetchAll(From<Palette>()) {
palette.hue .= Palette.randomHue()
palette.colorName .= nil
}
},
completion: { _ in }
)
}
}

View File

@@ -0,0 +1,88 @@
//
// ColorsDemo.swift
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2019/10/17.
// Copyright © 2019 John Rommel Estropia. All rights reserved.
//
import Foundation
import CoreStore
// MARK: - ColorsDemo
struct ColorsDemo {
enum Filter: String {
case all = "All Colors"
case light = "Light Colors"
case dark = "Dark Colors"
func next() -> Filter {
switch self {
case .all: return .light
case .light: return .dark
case .dark: return .all
}
}
func whereClause() -> Where<Palette> {
switch self {
case .all: return .init()
case .light: return (\Palette.brightness >= 0.9)
case .dark: return (\Palette.brightness <= 0.4)
}
}
}
static var filter = Filter.all {
didSet {
try! self.palettes.refetch(
From<Palette>()
.sectionBy(\.colorName)
.where(self.filter.whereClause())
.orderBy(.ascending(\.hue))
)
}
}
static let stack: DataStack = {
let dataStack = DataStack(
CoreStoreSchema(
modelVersion: "ColorsDemo",
entities: [
Entity<Palette>("Palette"),
],
versionLock: [
"Palette": [0x8c25aa53c7c90a28, 0xa243a34d25f1a3a7, 0x56565b6935b6055a, 0x4f988bb257bf274f]
]
)
)
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "ColorsDemo.sqlite",
localStorageOptions: .recreateStoreOnModelMismatch
)
)
return dataStack
}()
static let palettes: ListPublisher<Palette> = {
return ColorsDemo.stack.publishList(
From<Palette>()
.sectionBy(\.colorName)
.orderBy(.ascending(\.hue))
)
}()
}

View File

@@ -10,89 +10,34 @@ import UIKit
import CoreStore
struct ColorsDemo {
enum Filter: String {
case all = "All Colors"
case light = "Light Colors"
case dark = "Dark Colors"
func next() -> Filter {
switch self {
case .all: return .light
case .light: return .dark
case .dark: return .all
}
}
func whereClause() -> Where<Palette> {
switch self {
case .all: return .init()
case .light: return (\Palette.brightness >= 0.9)
case .dark: return (\Palette.brightness <= 0.4)
}
}
}
static var filter = Filter.all {
didSet {
self.palettes.refetch(
self.filter.whereClause(),
OrderBy<Palette>(.ascending(\.hue))
)
}
}
static let stack: DataStack = {
return DataStack(
CoreStoreSchema(
modelVersion: "ColorsDemo",
entities: [
Entity<Palette>("Palette"),
],
versionLock: [
"Palette": [0x8c25aa53c7c90a28, 0xa243a34d25f1a3a7, 0x56565b6935b6055a, 0x4f988bb257bf274f]
]
)
)
}()
static let palettes: ListMonitor<Palette> = {
try! ColorsDemo.stack.addStorageAndWait(
SQLiteStore(
fileName: "ColorsDemo.sqlite",
localStorageOptions: .recreateStoreOnModelMismatch
)
)
return ColorsDemo.stack.monitorSectionedList(
From<Palette>()
.sectionBy(\.colorName)
.orderBy(.ascending(\.hue))
)
}()
}
// MARK: - ListObserverDemoViewController
class ListObserverDemoViewController: UITableViewController, ListSectionObserver {
// MARK: NSObject
deinit {
ColorsDemo.palettes.removeObserver(self)
final class ListObserverDemoViewController: UITableViewController {
// MARK: - EditableDataSource
final class EditableDataSource: DiffableDataSource.TableViewAdapter<Palette> {
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
switch editingStyle {
case .delete:
let palette = ColorsDemo.palettes.snapshot[indexPath]
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
transaction.delete(palette)
},
completion: { _ in }
)
default:
break
}
}
}
// MARK: UIViewController
@@ -130,10 +75,28 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
filterBarButton
]
self.filterBarButton = filterBarButton
ColorsDemo.palettes.addObserver(self)
self.setTable(enabled: !ColorsDemo.palettes.isPendingRefetch)
self.dataSource = EditableDataSource(
tableView: self.tableView,
dataStack: ColorsDemo.stack,
cellProvider: { (tableView, indexPath, palette) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
return cell
}
)
ColorsDemo.palettes.addObserver(self) { [weak self] (listPublisher) in
guard let self = self else {
return
}
self.filterBarButton?.title = ColorsDemo.filter.rawValue
self.dataSource?.apply(listPublisher.snapshot, animatingDifferences: true)
}
self.dataSource?.apply(ColorsDemo.palettes.snapshot, animatingDifferences: false)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
@@ -142,8 +105,8 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
switch (segue.identifier, segue.destination, sender) {
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as Palette):
destinationViewController.palette = palette
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as ObjectPublisher<Palette>):
destinationViewController.setPalette(palette)
default:
break
@@ -151,30 +114,6 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
}
// MARK: UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return ColorsDemo.palettes.numberOfSections()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ColorsDemo.palettes.numberOfObjects(in: section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
let palette = ColorsDemo.palettes[indexPath]
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
return cell
}
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
@@ -183,104 +122,20 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
self.performSegue(
withIdentifier: "ObjectObserverDemoViewController",
sender: ColorsDemo.palettes[indexPath]
sender: ColorsDemo.palettes.snapshot[indexPath]
)
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
switch editingStyle {
case .delete:
let palette = ColorsDemo.palettes[indexPath]
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
transaction.delete(palette)
},
completion: { _ in }
)
default:
break
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return ColorsDemo.palettes.sectionInfo(at: section).name
}
// MARK: ListObserver
func listMonitorWillChange(_ monitor: ListMonitor<Palette>) {
self.tableView.beginUpdates()
}
func listMonitorDidChange(_ monitor: ListMonitor<Palette>) {
self.tableView.endUpdates()
}
func listMonitorWillRefetch(_ monitor: ListMonitor<Palette>) {
self.setTable(enabled: false)
}
func listMonitorDidRefetch(_ monitor: ListMonitor<Palette>) {
self.filterBarButton?.title = ColorsDemo.filter.rawValue
self.tableView.reloadData()
self.setTable(enabled: true)
}
// MARK: ListObjectObserver
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: IndexPath) {
self.tableView.insertRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: IndexPath) {
self.tableView.deleteRows(at: [indexPath], with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: IndexPath) {
if let cell = self.tableView.cellForRow(at: indexPath) as? PaletteTableViewCell {
let palette = ColorsDemo.palettes[indexPath]
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
}
}
func listMonitor(_ monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
self.tableView.moveRow(at: fromIndexPath, to: toIndexPath)
}
// MARK: ListSectionObserver
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
}
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
}
// MARK: Private
private var filterBarButton: UIBarButtonItem?
private var dataSource: DiffableDataSource.TableViewAdapter<Palette>?
deinit {
ColorsDemo.palettes.removeObserver(self)
}
@IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) {
@@ -311,8 +166,6 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
}
@IBAction private dynamic func shuffleBarButtonItemTouched(_ sender: AnyObject?) {
self.setTable(enabled: false)
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
@@ -322,28 +175,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
palette.colorName .= nil
}
},
completion: { _ in
self.setTable(enabled: true)
}
)
}
private func setTable(enabled: Bool) {
tableView.isUserInteractionEnabled = enabled
UIView.animate(
withDuration: 0.2,
delay: 0,
options: .beginFromCurrentState,
animations: { () -> Void in
if let tableView = self.tableView {
tableView.alpha = enabled ? 1.0 : 0.5
}
},
completion: nil
completion: { _ in }
)
}
}

View File

@@ -14,30 +14,23 @@ import CoreStore
class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
var palette: Palette? {
func setPalette<O: ObjectRepresentation>(_ newValue: O?) where O.ObjectType == Palette {
get {
guard self.monitor?.object?.objectID() != newValue?.objectID() else {
return self.monitor?.object
return
}
set {
if let newValue = newValue {
guard self.monitor?.object != newValue else {
return
}
self.monitor = newValue.asReadOnly(in: ColorsDemo.stack).map(ColorsDemo.stack.monitorObject(_:))
}
else {
if let palette = newValue {
self.monitor = ColorsDemo.stack.monitorObject(palette)
}
else {
self.monitor = nil
}
self.monitor = nil
}
}
// MARK: NSObject
deinit {

View File

@@ -27,4 +27,33 @@ class ObserversViewController: UIViewController {
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
}
// MARK: Private
@IBOutlet private dynamic weak var toggleTopBarButtonItem: UIBarButtonItem?
@IBOutlet private dynamic weak var toggleBottomBarButtonItem: UIBarButtonItem?
@IBOutlet private dynamic weak var stackView: UIStackView?
@IBOutlet private dynamic weak var topContainerView: UIView?
@IBOutlet private dynamic weak var bottomContainerView: UIView?
@IBAction private dynamic func toggleTopContainerView() {
UIView.animate(withDuration: 0.2) {
self.topContainerView!.isHidden.toggle()
}
self.toggleTopBarButtonItem!.isEnabled = !self.bottomContainerView!.isHidden
self.toggleBottomBarButtonItem!.isEnabled = !self.topContainerView!.isHidden
}
@IBAction private dynamic func toggleBottomContainerView() {
UIView.animate(withDuration: 0.2) {
self.bottomContainerView!.isHidden.toggle()
}
self.toggleTopBarButtonItem!.isEnabled = !self.bottomContainerView!.isHidden
self.toggleBottomBarButtonItem!.isEnabled = !self.topContainerView!.isHidden
}
}

View File

@@ -73,9 +73,12 @@ extension Palette {
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

View File

@@ -0,0 +1,17 @@
//
// PaletteCollectionSectionHeaderView.swift
// CoreStoreDemo
//
// Created by John Estropia on 2019/10/17.
// Copyright © 2019 John Rommel Estropia. All rights reserved.
//
import UIKit
// MARK: - PaletteCollectionSectionHeaderView
final class PaletteCollectionSectionHeaderView: UICollectionReusableView {
@IBOutlet weak var label: UILabel?
}

View File

@@ -0,0 +1,18 @@
//
// PaletteCollectionViewCell.swift
// CoreStoreDemo
//
// Created by John Estropia on 2019/10/17.
// Copyright © 2019 John Rommel Estropia. All rights reserved.
//
import UIKit
// MARK: - PaletteCollectionViewCell
final class PaletteCollectionViewCell: UICollectionViewCell {
@IBOutlet weak var colorView: UIView?
@IBOutlet weak var label: UILabel?
}

View File

@@ -8,7 +8,10 @@
import UIKit
class PaletteTableViewCell: UITableViewCell {
// MARK: - PaletteTableViewCell
final class PaletteTableViewCell: UITableViewCell {
@IBOutlet weak var colorView: UIView?
@IBOutlet weak var label: UILabel?

View File

@@ -31,7 +31,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
super.viewDidAppear(animated)
Shared.logger = self
CoreStoreDefaults.logger = self
let alert = UIAlertController(
title: "Logger Demo",
@@ -46,7 +46,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
super.viewDidDisappear(animated)
Shared.logger = DefaultLogger()
CoreStoreDefaults.logger = DefaultLogger()
}

View File

@@ -0,0 +1,53 @@
//
// SwiftUIContainerViewController.swift
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2019/10/02.
// Copyright © 2019 John Rommel Estropia. All rights reserved.
//
import UIKit
import CoreStore
#if canImport(SwiftUI)
import SwiftUI
#endif
#if canImport(Combine)
import Combine
#endif
import Compression
final class SwiftUIContainerViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
#if canImport(SwiftUI) && canImport(Combine)
if #available(iOS 13, *) {
let hostingController = UIHostingController(
rootView: SwiftUIView(
palettes: ColorsDemo.stack.publishList(
From<Palette>()
.sectionBy(\.colorName)
.orderBy(.ascending(\.hue))
)
)
.environment(\.dataStack, ColorsDemo.stack)
)
self.addChild(hostingController)
hostingController.view.autoresizingMask = [.flexibleWidth, .flexibleHeight]
hostingController.view.frame = self.view.bounds.inset(by: self.view.safeAreaInsets)
self.view.addSubview(hostingController.view)
hostingController.didMove(toParent: self)
}
#endif
}
}

View File

@@ -0,0 +1,184 @@
//
// SwiftUIView.swift
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2019/10/02.
// Copyright © 2019 John Rommel Estropia. All rights reserved.
//
#if canImport(SwiftUI) && canImport(Combine)
import SwiftUI
import Combine
import CoreStore
@available(iOS 13.0.0, *)
struct SwiftUIView: View {
@Environment(\.dataStack)
var dataStack: DataStack
@ObservedObject
var palettes: ListPublisher<Palette>
var body: some View {
NavigationView {
List {
ForEach(palettes.snapshot.sectionIDs, id: \.self) { (sectionID) in
Section(header: Text(sectionID)) {
ForEach(self.palettes.snapshot.items(inSectionWithID: sectionID), id: \.self) { palette in
NavigationLink(
destination: DetailView(palette: palette),
label: { ColorCell(palette: palette) }
)
}
.onDelete { itemIndices in
let objectIDsToDelete = self.palettes.snapshot.itemIDs(
inSectionWithID: sectionID,
atIndices: itemIndices
)
self.dataStack.perform(
asynchronous: { transaction in
transaction.delete(objectIDs: objectIDsToDelete)
},
completion: { _ in }
)
}
}
}
}
.navigationBarTitle(Text("SwiftUI (\(palettes.snapshot.numberOfItems) objects)"))
.navigationBarItems(
leading: EditButton(),
trailing: HStack {
Button(
action: {
self.dataStack.perform(
asynchronous: { transaction in
for palette in try transaction.fetchAll(From<Palette>()) {
palette.hue .= Palette.randomHue()
palette.colorName .= nil
}
},
completion: { _ in }
)
},
label: {
Image(systemName: "goforward")
}
)
.frame(width: 30)
Button(
action: {
self.dataStack.perform(
asynchronous: { transaction in
let palette = transaction.create(Into<Palette>())
palette.setInitialValues(in: transaction)
},
completion: { _ in }
)
},
label: {
Image(systemName: "plus")
}
)
.frame(width: 30)
}
)
.alert(
isPresented: $needsShowAlert,
content: {
Alert(
title: Text("SwiftUI Binding Demo"),
message: Text("This demo shows how to bind to ListPublisher and to CoreStoreObject when using SwiftUI"),
dismissButton: .cancel(Text("OK"))
)
}
)
.onAppear {
self.needsShowAlert = true
}
}
}
@State
private var needsShowAlert = false
}
@available(iOS 13.0.0, *)
struct ColorCell: View {
@ObservedObject
var palette: ObjectPublisher<Palette>
var body: some View {
HStack {
Color(palette.color ?? UIColor.clear)
.cornerRadius(5)
.frame(width: 30, height: 30, alignment: .leading)
Text(palette.colorText ?? "<Deleted>")
}
}
}
@available(iOS 13.0.0, *)
struct DetailView: View {
@Environment(\.dataStack)
var dataStack: DataStack
@ObservedObject
var palette: ObjectPublisher<Palette>
@State var hue: Float = 0
@State var saturation: Float = 0
@State var brightness: Float = 0
init(palette: ObjectPublisher<Palette>) {
self.palette = palette
self.hue = Float(palette.hue ?? 0)
self.saturation = palette.saturation ?? 0
self.brightness = palette.brightness ?? 0
}
var body: some View {
ZStack {
Color(palette.color ?? UIColor.clear)
.cornerRadius(20)
.padding(20)
VStack {
Text(palette.colorText ?? "<Deleted>")
.navigationBarTitle(Text("Color"))
Slider(value: $hue, in: 0.0 ... 359.0 as ClosedRange<Float>)
Slider(value: $saturation, in: 0.0 ... 1.0 as ClosedRange<Float>)
Slider(value: $brightness, in: 0.0 ... 0.1 as ClosedRange<Float>)
}
}
}
}
@available(iOS 13.0.0, *)
struct SwiftUIView_Previews: PreviewProvider {
static var previews: some View {
SwiftUIView(
palettes: ColorsDemo.stack.publishList(
From<Palette>()
.sectionBy(\.colorName)
.orderBy(.ascending(\.hue))
)
)
.environment(\.dataStack, ColorsDemo.stack)
}
}
#endif

View File

@@ -67,11 +67,11 @@ class BaseTestCase: XCTestCase {
@nonobjc
func expectLogger<T>(_ expectations: [TestLogger.Expectation], closure: () throws -> T) rethrows -> T {
Shared.logger = TestLogger(self.prepareLoggerExpectations(expectations))
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations(expectations))
defer {
self.checkExpectationsImmediately()
Shared.logger = TestLogger([:])
CoreStoreDefaults.logger = TestLogger([:])
}
return try closure()
}
@@ -79,17 +79,17 @@ class BaseTestCase: XCTestCase {
@nonobjc
func expectLogger(_ expectations: [TestLogger.Expectation: XCTestExpectation]) {
Shared.logger = TestLogger(expectations)
CoreStoreDefaults.logger = TestLogger(expectations)
}
@nonobjc
func expectError<T>(code: CoreStoreErrorCode, closure: () throws -> T) {
Shared.logger = TestLogger(self.prepareLoggerExpectations([.logError]))
CoreStoreDefaults.logger = TestLogger(self.prepareLoggerExpectations([.logError]))
defer {
self.checkExpectationsImmediately()
Shared.logger = TestLogger([:])
CoreStoreDefaults.logger = TestLogger([:])
}
do {
@@ -138,12 +138,12 @@ class BaseTestCase: XCTestCase {
super.setUp()
self.deleteStores()
Shared.logger = TestLogger([:])
CoreStoreDefaults.logger = TestLogger([:])
}
override func tearDown() {
Shared.logger = DefaultLogger()
CoreStoreDefaults.logger = DefaultLogger()
self.deleteStores()
super.tearDown()
}

View File

@@ -171,20 +171,72 @@ class DynamicModelTests: BaseTestDataTestCase {
animal.color .= .yellow
XCTAssertEqual(animal.color.value, Color.yellow)
for property in Animal.metaProperties(includeSuperclasses: true) {
switch property.keyPath {
case String(keyPath: \Animal.species):
XCTAssertTrue(property is ValueContainer<Animal>.Required<String>)
case String(keyPath: \Animal.master):
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>)
case String(keyPath: \Animal.color):
XCTAssertTrue(property is TransformableContainer<Animal>.Optional<Color>)
default:
XCTFail("Unknown KeyPath: \"\(property.keyPath)\"")
}
}
let dog = transaction.create(Into<Dog>())
XCTAssertEqual(dog.species.value, "Swift")
XCTAssertEqual(dog.nickname.value, nil)
XCTAssertEqual(dog.age, 1)
#if swift(>=5.1)
for property in Dog.metaProperties(includeSuperclasses: true) {
let dogKeyPathBuilder = Dog.keyPathBuilder()
XCTAssertEqual(dogKeyPathBuilder.species.keyPathString, "SELF.species")
XCTAssertEqual(dogKeyPathBuilder.master.title.keyPathString, "SELF.master.title")
XCTAssertEqual(dogKeyPathBuilder.master.spouse.pets.color.keyPathString, "SELF.master.spouse.pets.color")
switch property.keyPath {
#endif
case String(keyPath: \Dog.species):
XCTAssertTrue(property is ValueContainer<Animal>.Required<String>)
case String(keyPath: \Dog.master):
XCTAssertTrue(property is RelationshipContainer<Animal>.ToOne<Person>)
case String(keyPath: \Dog.color):
XCTAssertTrue(property is TransformableContainer<Animal>.Optional<Color>)
case String(keyPath: \Dog.nickname):
XCTAssertTrue(property is ValueContainer<Dog>.Optional<String>)
case String(keyPath: \Dog.age):
XCTAssertTrue(property is ValueContainer<Dog>.Required<Int>)
case String(keyPath: \Dog.friends):
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyOrdered<Dog>)
case String(keyPath: \Dog.friendedBy):
XCTAssertTrue(property is RelationshipContainer<Dog>.ToManyUnordered<Dog>)
default:
XCTFail("Unknown KeyPath: \"\(property.keyPath)\"")
}
}
// #if swift(>=5.1)
//
// 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
@@ -247,7 +299,7 @@ class DynamicModelTests: BaseTestDataTestCase {
XCTAssertEqual(person.name.value, "John")
XCTAssertEqual(person.displayName.value, "Mr. John") // Custom getter
let personSnapshot1 = person.createSnapshot()
let personSnapshot1 = person.asSnapshot(in: transaction)!
XCTAssertEqual(person.name.value, personSnapshot1.name)
XCTAssertEqual(person.title.value, personSnapshot1.title)
XCTAssertEqual(person.displayName.value, personSnapshot1.displayName)
@@ -259,10 +311,20 @@ class DynamicModelTests: BaseTestDataTestCase {
XCTAssertEqual(personSnapshot1.title, "Mr.")
XCTAssertEqual(personSnapshot1.displayName, "Mr. John")
let personSnapshot2 = person.createSnapshot()
let personSnapshot2 = person.asSnapshot(in: transaction)!
XCTAssertEqual(person.name.value, personSnapshot2.name)
XCTAssertEqual(person.title.value, personSnapshot2.title)
XCTAssertEqual(person.displayName.value, personSnapshot2.displayName)
var personSnapshot3 = personSnapshot2
personSnapshot3.name = "James"
XCTAssertEqual(personSnapshot1.name, "John")
XCTAssertEqual(personSnapshot1.displayName, "Mr. John")
XCTAssertEqual(personSnapshot2.name, "John")
XCTAssertEqual(personSnapshot2.displayName, "Sir John")
XCTAssertEqual(personSnapshot3.name, "James")
XCTAssertEqual(personSnapshot3.displayName, "Sir John")
person.pets.value.insert(dog)
XCTAssertEqual(person.pets.count, 1)

View File

@@ -0,0 +1,304 @@
//
// ListPublisherTests.swift
// CoreStore iOS
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import XCTest
@testable
import CoreStore
// MARK: - ListPublisherTests
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
class ListPublisherTests: BaseTestDataTestCase {
@objc
dynamic func test_ThatListPublishers_CanReceiveInsertNotifications() {
self.prepareStack { (stack) in
let observer = NSObject()
let listPublisher = stack.publishList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
)
XCTAssertFalse(listPublisher.snapshot.hasSections())
XCTAssertFalse(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.itemIDs.isEmpty)
let didChangeExpectation = self.expectation(description: "didChange")
listPublisher.addObserver(observer) { listPublisher in
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 1)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
let object = transaction.create(Into<TestEntity1>())
object.testBoolean = NSNumber(value: true)
object.testNumber = NSNumber(value: 1)
object.testDecimal = NSDecimalNumber(string: "1")
object.testString = "nil:TestEntity1:1"
object.testData = ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
withExtendedLifetime(listPublisher, {})
withExtendedLifetime(observer, {})
}
}
@objc
dynamic func test_ThatListPublishers_CanReceiveUpdateNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
let observer = NSObject()
let listPublisher = stack.publishList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
)
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
let didChangeExpectation = self.expectation(description: "didChange")
listPublisher.addObserver(observer) { listPublisher in
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
if let object = try transaction.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
object.testNumber = NSNumber(value: 11)
object.testDecimal = NSDecimalNumber(string: "11")
object.testString = "nil:TestEntity1:11"
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
}
else {
XCTFail()
}
if let object = try transaction.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
object.testNumber = NSNumber(value: 22)
object.testDecimal = NSDecimalNumber(string: "22")
object.testString = "nil:TestEntity1:22"
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
}
else {
XCTFail()
}
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
withExtendedLifetime(listPublisher, {})
withExtendedLifetime(observer, {})
}
}
@objc
dynamic func test_ThatListPublishers_CanReceiveMoveNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
let observer = NSObject()
let listPublisher = stack.publishList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
)
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
let didChangeExpectation = self.expectation(description: "didChange")
listPublisher.addObserver(observer) { listPublisher in
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 1)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 4)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
if let object = try transaction.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
object.testBoolean = NSNumber(value: true)
}
else {
XCTFail()
}
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
withExtendedLifetime(listPublisher, {})
withExtendedLifetime(observer, {})
}
}
@objc
dynamic func test_ThatListPublishers_CanReceiveDeleteNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
let observer = NSObject()
let listPublisher = stack.publishList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy<TestEntity1>(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
)
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 2)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 2)
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 1), 3)
let didChangeExpectation = self.expectation(description: "didChange")
listPublisher.addObserver(observer) { listPublisher in
XCTAssertTrue(listPublisher.snapshot.hasSections())
XCTAssertEqual(listPublisher.snapshot.numberOfSections, 1)
XCTAssertTrue(listPublisher.snapshot.hasItems())
XCTAssertTrue(listPublisher.snapshot.hasItems(inSectionIndex: 0))
XCTAssertEqual(listPublisher.snapshot.numberOfItems(inSectionIndex: 0), 3)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
let count = try transaction.deleteAll(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
)
XCTAssertEqual(count, 2)
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
}
}
}
#endif

View File

@@ -0,0 +1,154 @@
//
// ObjectPublisherTests.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
// MARK: - ObjectPublisherTests
@available(macOS 10.12, *)
class ObjectPublisherTests: BaseTestDataTestCase {
@objc
dynamic func test_ThatObjectPublishers_CanReceiveUpdateNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
guard let object = try stack.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
XCTFail()
return
}
let observer = NSObject()
let objectPublisher = stack.publishObject(object)
XCTAssertEqual(objectPublisher.object, object)
XCTAssertNotNil(objectPublisher.snapshot)
let didChangeExpectation = self.expectation(description: "didChange")
objectPublisher.addObserver(observer) { objectPublisher in
XCTAssertEqual(objectPublisher.object?.testNumber, NSNumber(value: 10))
XCTAssertEqual(objectPublisher.object?.testString, "nil:TestEntity1:10")
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
guard let object = transaction.edit(object) else {
XCTFail()
try transaction.cancel()
}
object.testNumber = NSNumber(value: 10)
object.testString = "nil:TestEntity1:10"
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
withExtendedLifetime(objectPublisher, {})
withExtendedLifetime(observer, {})
}
}
@objc
dynamic func test_ThatObjectPublishers_CanReceiveDeleteNotifications() {
self.prepareStack { (stack) in
self.prepareTestDataForStack(stack)
guard let object = try stack.fetchOne(
From<TestEntity1>(),
Where<TestEntity1>(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
XCTFail()
return
}
let observer = NSObject()
let objectPublisher = stack.publishObject(object)
XCTAssertEqual(objectPublisher.object, object)
XCTAssertNotNil(objectPublisher.snapshot)
let didChangeExpectation = self.expectation(description: "didChange")
objectPublisher.addObserver(observer) { objectPublisher in
XCTAssertNil(objectPublisher.object)
XCTAssertNil(objectPublisher.snapshot)
didChangeExpectation.fulfill()
}
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
guard let object = transaction.edit(object) else {
XCTFail()
try transaction.cancel()
}
transaction.delete(object)
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
withExtendedLifetime(objectPublisher, {})
withExtendedLifetime(observer, {})
}
}
}

View File

@@ -31,12 +31,12 @@ import CoreStore
// MARK: - XCTAssertAllEqual
private func XCTAssertAllEqual<D>(_ whereClauses: Where<D>...) {
private func XCTAssertAllEqual<O>(_ whereClauses: Where<O>...) {
XCTAssertAllEqual(whereClauses)
}
private func XCTAssertAllEqual<D>(_ whereClauses: [Where<D>]) {
private func XCTAssertAllEqual<O>(_ whereClauses: [Where<O>]) {
for i in whereClauses.indices {

View File

@@ -1,4 +1,4 @@
// swift-tools-version:4.2
// swift-tools-version:5.0
//
// Package.swift
// CoreStore
@@ -28,6 +28,9 @@ import PackageDescription
let package = Package(
name: "CoreStore",
platforms: [
.macOS(.v10_12), .iOS(.v10), .tvOS(.v10), .watchOS(.v3)
],
products: [
.library(name: "CoreStore", type: .static, targets: ["CoreStore"])
],
@@ -38,6 +41,13 @@ let package = Package(
dependencies: [],
path: "Sources",
exclude: ["CoreStoreBridge.h", "CoreStoreBridge.m"]
),
.testTarget(
name: "CoreStoreTests",
dependencies: ["CoreStore"],
path: "CoreStoreTests",
exclude: ["*.h", "*.m"]
)
]
],
swiftLanguageVersions: [.v5]
)

371
README.md
View File

@@ -5,7 +5,7 @@
Unleashing the real power of Core Data with the elegance and safety of Swift
<br />
<br />
<a href="https://app.bitrise.io/app/e736852157296019#/builds"><img alt="Build Status" src="https://img.shields.io/bitrise/e736852157296019/master.svg?label=build&token=vhgAmaiF3tWZoQyFLkKM7g" /></a>
<a href="https://app.bitrise.io/app/e736852157296019#/builds"><img alt="Build Status" src="https://img.shields.io/bitrise/e736852157296019/master.svg?label=build&token=vhgAmaiF3tWZoQyFLkKM7g&logo=bitrise" /></a>
<a href="https://github.com/JohnEstropia/CoreStore/commits"><img alt="Last Commit" src="https://img.shields.io/github/last-commit/johnestropia/corestore.svg?style=flat" /></a>
<a href="http://cocoadocs.org/docsets/CoreStore"><img alt="Platform" src="https://img.shields.io/cocoapods/p/CoreStore.svg?style=flat" /></a>
<a href="https://raw.githubusercontent.com/JohnEstropia/CoreStore/master/LICENSE"><img alt="License" src="https://img.shields.io/cocoapods/l/CoreStore.svg?style=flat" /></a>
@@ -14,15 +14,15 @@ Unleashing the real power of Core Data with the elegance and safety of Swift
<a href="https://github.com/Carthage/Carthage"><img alt="Carthage compatible" src="https://img.shields.io/badge/Carthage-compatible-16a085.svg?style=flat" /></a>
<a href="https://swift.org/source-compatibility/#current-list-of-projects"><img alt="Swift Package Manager compatible" src="https://img.shields.io/badge/Swift_Package_Manager-compatible-orange.svg?style=flat" /></a>
<br /><br />Contact<br />
<a href="http://swift-corestore-slack.herokuapp.com/"><img alt="Join us on Slack!" src="http://swift-corestore-slack.herokuapp.com/badge.svg" /></a>
<a href="https://twitter.com/JohnEstropia"><img alt="Reach me on Twitter!" src="https://img.shields.io/badge/twitter-%40JohnEstropia-3498db.svg" /></a>
<a href="http://swift-corestore-slack.herokuapp.com/"><img alt="Join us on Slack!" src="http://swift-corestore-slack.herokuapp.com/badge.svg?logo=slack" /></a>
<a href="https://twitter.com/JohnEstropia"><img alt="Reach me on Twitter!" src="https://img.shields.io/badge/twitter-%40JohnEstropia-3498db.svg?logo=twitter" /></a>
<br />
</p>
* **Swift 5.0:** iOS 10+ / macOS 10.12+ / watchOS 3.0+ / tvOS 10.0+
* Previously supported Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1)
* **Swift 5.1:** iOS 10+ / macOS 10.12+ / watchOS 3.0+ / tvOS 10.0+
* Previously supported Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1), [Swift 5.0](https://github.com/JohnEstropia/CoreStore/tree/6.3.2)
Upgrading from CoreStore 5.x (min iOS 9) to 6.x (min iOS 10)? Check out the [new features](#features) and make sure to read the [Change logs](https://github.com/JohnEstropia/CoreStore/releases).
Upgrading from CoreStore 6.x (swift 5.0) to 7.x (Swift 5.1)? Check out the [🆕 features](#features) and make sure to read the [Change logs](https://github.com/JohnEstropia/CoreStore/releases).
CoreStore is now part of the [Swift Source Compatibility projects](https://swift.org/source-compatibility/#current-list-of-projects).
@@ -33,17 +33,18 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
### Features
- 🆕**Backwards-portable DiffableDataSources implementation!** `UITableViews` and `UICollectionViews` now have a new ally: `ListPublisher`s provide diffable snapshots that make reloading animations very easy and very safe. Say goodbye to `UITableViews` and `UICollectionViews` reload errors!
- **💎Tight design around Swifts code elegance and type safety.** CoreStore fully utilizes Swift's community-driven language features.
- **🚦Safer concurrency architecture.** CoreStore makes it hard to fall into common concurrency mistakes. The main `NSManagedObjectContext` is strictly read-only, while all updates are done through serial *transactions*. *(See [Saving and processing transactions](#saving-and-processing-transactions))*
- **🔍Clean fetching and querying API.** Fetching objects is easy, but querying for raw aggregates (`min`, `max`, etc.) and raw property values is now just as convenient. *(See [Fetching and querying](#fetching-and-querying))*
- **🔭Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, `ListMonitor`s and `ObjectMonitor`s can have multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
- **🔭Type-safe, easy to configure observers.** You don't have to deal with the burden of setting up `NSFetchedResultsController`s and KVO. As an added bonus, list and object observable types all support multiple observers. This means you can have multiple view controllers efficiently share a single resource! *(See [Observing changes and notifications](#observing-changes-and-notifications))*
- **📥Efficient importing utilities.** Map your entities once with their corresponding import source (JSON for example), and importing from *transactions* becomes elegant. Uniquing is also done with an efficient find-and-replace algorithm. *(See [Importing data](#importing-data))*
- **🗑Say goodbye to *.xcdatamodeld* files!** The new `CoreStoreObject` is *the* replacement to `NSManagedObject`. `CoreStoreObject` subclasses can declare type-safe properties all in Swift code, no need to maintain separate resource files for the models. As bonus, these special properties support custom types, and can be used to create type-safe keypaths and queries. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects))*
- **🗑Say goodbye to *.xcdatamodeld* files!** While CoreStore supports `NSManagedObject`s, it offers `CoreStoreObject` whose subclasses can declare type-safe properties all in Swift code without the need to maintain separate resource files for the models. As bonus, these special properties support custom types, and can be used to create type-safe keypaths and queries. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects))*
- **🔗Progressive migrations.** No need to think how to migrate from all previous model versions to your latest model. Just tell the `DataStack` the sequence of version strings (`MigrationChain`s) and CoreStore will automatically use progressive migrations when needed. *(See [Migrations](#migrations))*
- **Easier custom migrations.** Say goodbye to *.xcmappingmodel* files; CoreStore can now infer entity mappings when possible, while still allowing an easy way to write custom mappings. *(See [Migrations](#migrations))*
- **📝Plug-in your own logging framework.** Although a default logger is built-in, all logging, asserting, and error reporting can be funneled to `CoreStoreLogger` protocol implementations. *(See [Logging and error reporting](#logging-and-error-reporting))*
- **⛓Heavy support for multiple persistent stores per data stack.** CoreStore lets you manage separate stores in a single `DataStack`, just the way *.xcdatamodeld* configurations are designed to. CoreStore will also manage one stack by default, but you can create and manage as many as you need. *(See [Setting up](#setting-up))*
- **🎯Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign different names for the entities and their class names.
- **🎯Free to name entities and their class names independently.** CoreStore gets around a restriction with other Core Data wrappers where the entity name should be the same as the `NSManagedObject` subclass name. CoreStore loads entity-to-class mappings from the managed object model file, so you can assign independent names for the entities and their class names.
- **📙Full Documentation.** No magic here; all public classes, functions, properties, etc. have detailed *Apple Docs*. This *README* also introduces a lot of concepts and explains a lot of CoreStore's behavior.
- **Informative (and pretty) logs.** All CoreStore and Core Data-related types now have very informative and pretty print outputs! *(See [Logging and error reporting](#logging-and-error-reporting))*
- **🎗Objective-C support!** Is your project transitioning from Objective-C to Swift but still can't quite fully convert some huge classes to Swift yet? CoreStore adjusts to the ever-increasing Swift adoption. While still written in pure Swift, all CoreStore types have their corresponding Objective-C-visible "bridging classes". *(See [Objective-C support](#objective-c-support))*
@@ -87,8 +88,10 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
- [Logging and error reporting](#logging-and-error-reporting)
- [Observing changes and notifications](#observing-changes-and-notifications)
- [Observe a single property](#observe-a-single-property)
- [Observe a single object](#observe-a-single-object)
- [Observe a list of objects](#observe-a-list-of-objects)
- 🆕[Observe a single object's updates](#observe-a-single-objects-updates)
- [Observe a single object's per-property updates](#observe-a-single-objects-per-property-updates)
- 🆕[Observe a diffable list](#observe-a-diffable-list)
- [Observe detailed list changes](#observe-detailed-list-changes)
- [Objective-C support](#objective-c-support)
- [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects)
- [`VersionLock`s](#versionlocks)
@@ -105,7 +108,7 @@ CoreStore was (and is) heavily shaped by real-world needs of developing data-dep
Setting-up with progressive migration support:
```swift
CoreStore.defaultStack = DataStack(
dataStack = DataStack(
xcodeModelName: "MyStore",
migrationChain: ["MyStore", "MyStoreV2", "MyStoreV3"]
)
@@ -113,7 +116,7 @@ CoreStore.defaultStack = DataStack(
Adding a store:
```swift
CoreStore.addStorage(
dataStack.addStorage(
SQLiteStore(fileName: "MyStore.sqlite"),
completion: { (result) -> Void in
// ...
@@ -123,7 +126,7 @@ CoreStore.addStorage(
Starting transactions:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let person = transaction.create(Into<MyPersonEntity>())
person.name = "John Smith"
@@ -140,12 +143,12 @@ CoreStore.perform(
Fetching objects (simple):
```swift
let people = try CoreStore.fetchAll(From<MyPersonEntity>())
let people = try dataStack.fetchAll(From<MyPersonEntity>())
```
Fetching objects (complex):
```swift
let people = try CoreStore.fetchAll(
let people = try dataStack.fetchAll(
From<MyPersonEntity>()
.where(\.age > 30),
.orderBy(.ascending(\.name), .descending(.\age)),
@@ -155,7 +158,7 @@ let people = try CoreStore.fetchAll(
Querying values:
```swift
let maxAge = try CoreStore.queryValue(
let maxAge = try dataStack.queryValue(
From<MyPersonEntity>()
.select(Int.self, .maximum(\.age))
)
@@ -174,15 +177,15 @@ If you are already familiar with the inner workings of CoreData, here is a mappi
| *Core Data* | *CoreStore* |
| --- | --- |
| `NSManagedObjectModel` / `NSPersistentStoreCoordinator`<br />(.xcdatamodeld file) | `DataStack` |
| `NSPersistentStore`<br />("Configuration"s in the .xcdatamodeld file) | `StorageInterface` implementations<br />(`InMemoryStore`, `SQLiteStore`, `ICloudStore`) |
| `NSPersistentContainer`<br />(.xcdatamodeld file) | `DataStack` |
| `NSPersistentStoreDescription`<br />("Configuration"s in the .xcdatamodeld file) | `StorageInterface` implementations<br />(`InMemoryStore`, `SQLiteStore`) |
| `NSManagedObjectContext` | `BaseDataTransaction` subclasses<br />(`SynchronousDataTransaction`, `AsynchronousDataTransaction`, `UnsafeDataTransaction`) |
A lot of Core Data wrapper libraries set up their `NSManagedObjectContext`s this way:
<img src="https://cloud.githubusercontent.com/assets/3029684/16707160/984ef25c-4600-11e6-869f-8db7d2c63668.png" alt="nested contexts" height=380 />
Nesting saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But <a href="http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/">in reality</a>, merging contexts is still by far faster than saving contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context, and only allows changes to be made within *transactions* on the child context:
Nesting saves from child context to the root context ensures maximum data integrity between contexts without blocking the main queue. But <a href="http://floriankugler.com/2013/04/29/concurrent-core-data-stack-performance-shootout/">in reality</a>, merging contexts is still by far faster than saving contexts. CoreStore's `DataStack` takes the best of both worlds by treating the main `NSManagedObjectContext` as a read-only context (or "viewContext"), and only allows changes to be made within *transactions* on the child context:
<img src="https://cloud.githubusercontent.com/assets/3029684/16707161/9adeb962-4600-11e6-8bc8-4ec85764dba4.png" alt="nested contexts and merge hybrid" height=292 />
@@ -193,10 +196,10 @@ This allows for a butter-smooth main thread, while still taking advantage of saf
## Setting up
The simplest way to initialize CoreStore is to add a default store to the default stack:
```swift
try CoreStore.addStorageAndWait()
try CoreStoreDefaults.dataStack.addStorageAndWait()
```
This one-liner does the following:
- Triggers the lazy-initialization of `CoreStore.defaultStack` with a default `DataStack`
- Triggers the lazy-initialization of `CoreStoreDefaults.dataStack` with a default `DataStack`
- Sets up the stack's `NSPersistentStoreCoordinator`, the root saving `NSManagedObjectContext`, and the read-only main `NSManagedObjectContext`
- Adds an `SQLiteStore` in the *"Application Support/<bundle id>"* directory (or the *"Caches/<bundle id>"* directory on tvOS) with the file name *"[App bundle name].sqlite"*
- Creates and returns the `NSPersistentStore` instance on success, or an `NSError` on failure
@@ -223,13 +226,13 @@ let migrationProgress = dataStack.addStorage(
}
)
CoreStore.defaultStack = dataStack // pass the dataStack to CoreStore for easier access later on
CoreStoreDefaults.dataStack = dataStack // pass the dataStack to CoreStore for easier access later on
```
> 💡If you have never heard of "Configurations", you'll find them in your *.xcdatamodeld* file
> <img src="https://cloud.githubusercontent.com/assets/3029684/8333192/e52cfaac-1acc-11e5-9902-08724f9f1324.png" alt="xcode configurations screenshot" height=212 />
In our sample code above, note that you don't need to do the `CoreStore.defaultStack = dataStack` line. You can just as well hold a reference to the `DataStack` like below and call all its instance methods directly:
In our sample code above, note that you don't need to do the `CoreStoreDefaults.dataStack = dataStack` line. You can just as well hold a reference to the `DataStack` like below and call all its instance methods directly:
```swift
class MyViewController: UIViewController {
let dataStack = DataStack(xcodeModelName: "MyModel") // keep reference to the stack
@@ -242,25 +245,7 @@ class MyViewController: UIViewController {
}
}
func methodToBeCalledLaterOn() {
let objects = self.dataStack.fetchAll(From(MyEntity))
print(objects)
}
}
```
The difference is when you set the stack as the `CoreStore.defaultStack`, you can call the stack's methods directly from `CoreStore` itself:
```swift
class MyViewController: UIViewController {
// elsewhere: CoreStore.defaultStack = DataStack(modelName: "MyModel")
override func viewDidLoad() {
super.viewDidLoad()
do {
try CoreStore.addStorageAndWait(SQLiteStore.self)
}
catch { // ...
}
}
func methodToBeCalledLaterOn() {
let objects = CoreStore.fetchAll(From<MyEntity>())
let objects = self.dataStack.fetchAll(From<MyEntity>())
print(objects)
}
}
@@ -268,12 +253,12 @@ class MyViewController: UIViewController {
> 💡By default, CoreStore will initialize `NSManagedObject`s from *.xcdatamodeld* files, but you can create models completely from source code using `CoreStoreObject`s and `CoreStoreSchema`. To use this feature, refer to [Type-safe `CoreStoreObject`s](#type-safe-corestoreobjects).
Notice that in our previous examples, `addStorageAndWait(_:)` and `addStorage(_:completion:)` both accept either `InMemoryStore`, `SQLiteStore`, or `ICloudStore`. These implement the `StorageInterface` protocol.
Notice that in our previous examples, `addStorageAndWait(_:)` and `addStorage(_:completion:)` both accept either `InMemoryStore`, or `SQLiteStore`. These implement the `StorageInterface` protocol.
### In-memory store
The most basic `StorageInterface` concrete type is the `InMemoryStore`, which just stores objects in memory. Since `InMemoryStore`s always start with a fresh empty data, they do not need any migration information.
```swift
try CoreStore.addStorageAndWait(
try dataStack.addStorageAndWait(
InMemoryStore(
configuration: "Config2" // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
)
@@ -283,7 +268,7 @@ try CoreStore.addStorageAndWait(
### Local Store
The most common `StorageInterface` you will probably use is the `SQLiteStore`, which saves data in a local SQLite file.
```swift
let migrationProgress = CoreStore.addStorage(
let migrationProgress = dataStack.addStorage(
SQLiteStore(
fileName: "MyStore.sqlite",
configuration: "Config2", // optional. Use entities from the "Config2" configuration in the .xcdatamodeld file
@@ -297,7 +282,7 @@ Refer to the *SQLiteStore.swift* source documentation for detailed explanations
CoreStore can decide the default values for these properties, so `SQLiteStore`s can be initialized with no arguments:
```swift
try CoreStore.addStorageAndWait(SQLiteStore())
try dataStack.addStorageAndWait(SQLiteStore())
```
The file-related properties above are actually requirements of another protocol that `SQLiteStore` implements, the `LocalStorage` protocol:
@@ -316,16 +301,7 @@ If you have custom `NSIncrementalStore` or `NSAtomicStore` subclasses, you can i
## Migrations
### Declaring model versions
Until CoreStore 4.0, model versions were always assumed to be declared in *.xcdatamodeld* files. The `DataStack` loads these for us by accepting the *.xcdatamodeld* file name and the `Bundle` where the files can be found:
```swift
CoreStore.defaultStack = DataStack(
xcodeModelName: "MyModel",
bundle: Bundle.main,
migrationChain: ["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"]
)
```
Starting CoreStore 4.0, model versions are now expressed as a first-class protocol, `DynamicSchema`. CoreStore currently supports the following schema classes:
Model versions are now expressed as a first-class protocol, `DynamicSchema`. CoreStore currently supports the following schema classes:
- **`XcodeDataModelSchema`**: a model version with entities loaded from a *.xcdatamodeld* file.
- **`CoreStoreSchema`**: a model version created with `CoreStoreObject` entities. *(See [Type-safe `CoreStoreObject`s](#type-safe-corestore-objects))*
- **`UnsafeDataModelSchema`**: a model version created with an existing `NSManagedObjectModel` instance.
@@ -334,7 +310,7 @@ All the `DynamicSchema` for all model versions are then collected within a singl
**Multiple model versions grouped in a *.xcdatamodeld* file (Core Data standard method)**
```swift
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
xcodeModelName: "MyModel",
bundle: Bundle.main,
migrationChain: ["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"]
@@ -354,7 +330,7 @@ class Person: CoreStoreObject {
// ...
}
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
@@ -391,7 +367,7 @@ let newSchema = CoreStoreSchema(
Entity<Person>("Person")
]
)
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
schemaHistory: SchemaHistory(
legacySchema + [newSchema],
migrationChain: ["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4", "V1"]
@@ -427,7 +403,7 @@ enum V1 {
}
}
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
@@ -450,7 +426,7 @@ CoreStore.defaultStack = DataStack(
### Starting migrations
We have seen `addStorageAndWait(...)` used to initialize our persistent store. As the method name's *~AndWait* suffix suggests though, this method blocks so it should not do long tasks such as store migrations. In fact CoreStore will only attempt a synchronous **lightweight** migration if you explicitly provide the `.allowSynchronousLightweightMigration` option:
We have seen `addStorageAndWait(...)` used to initialize our persistent store. As the method name's *~AndWait* suffix suggests though, this method blocks so it should not do long tasks such as data migrations. In fact CoreStore will only attempt a synchronous **lightweight** migration if you explicitly provide the `.allowSynchronousLightweightMigration` option:
```swift
try dataStack.addStorageAndWait(
SQLiteStore(
@@ -533,7 +509,7 @@ Sometimes migrations are huge and you may want prior information so your app cou
```swift
do {
let storage = SQLiteStorage(fileName: "MyStore.sqlite")
let migrationTypes: [MigrationType] = try CoreStore.requiredMigrationsForStorage(storage)
let migrationTypes: [MigrationType] = try dataStack.requiredMigrationsForStorage(storage)
if migrationTypes.count > 1
|| (migrationTypes.filter { $0.isHeavyweightMigration }.count) > 0 {
// ... will migrate more than once. Show special waiting screen
@@ -544,7 +520,7 @@ do {
else {
// ... Do nothing
}
CoreStore.addStorage(storage, completion: { /* ... */ })
dataStack.addStorage(storage, completion: { /* ... */ })
}
catch {
// ... either inspection of the store failed, or if no mapping model was found/inferred
@@ -560,11 +536,11 @@ Each `MigrationType` indicates the migration type for each step in the `Migratio
### Custom migrations
Before CoreStore 4.0, the only way to implement custom migrations is to use Core Data's standard method: declaring entity mappings through *.xcmappingmodel* files. Starting CoreStore 4.0, new ways to declare migration mappings have been added:
CoreStore offers several ways to declare migration mappings:
- `InferredSchemaMappingProvider`: The default mapping provider which tries to infer model migration between two `DynamicSchema` versions either by searching all *.xcmappingmodel* files from `Bundle.allBundles`, or by relying on lightweight migration if possible.
- `XcodeSchemaMappingProvider`: A mapping provider which loads entity mappings from *.xcmappingmodel* files in a specified `Bundle`.
- `CustomSchemaMappingProvider`: A mapping provider that infers mapping initially, but also accepts custom mappings for specified entities. This was added to support custom migrations with `CoreStoreObject`s as well, but may also be used with `NSManagedObject`s.
- `XcodeSchemaMappingProvider`: A mapping provider which loads entity mappings from *.xcmappingmodel* files in a specified `Bundle`.
- `InferredSchemaMappingProvider`: The default mapping provider which tries to infer model migration between two `DynamicSchema` versions either by searching all *.xcmappingmodel* files from `Bundle.allBundles`, or by relying on lightweight migration if possible.
These mapping providers conform to `SchemaMappingProvider` and can be passed to `SQLiteStore`'s initializer:
```swift
@@ -631,18 +607,7 @@ dataStack.perform(
}
)
```
or for the default stack, directly from `CoreStore`:
```swift
CoreStore.perform(
asynchronous: { (transaction) -> Void in
// make changes
},
completion: { (result) -> Void in
// ...
}
)
```
Transaction blocks automatically save changes once the block completes. To cancel and rollback a transaction, throw a `CoreStoreError.userCancelled` from inside the closure by calling `try transaction.cancel()`:
Transaction closures automatically save changes once the closures completes. To cancel and rollback a transaction, throw a `CoreStoreError.userCancelled` from inside the closure by calling `try transaction.cancel()`:
```swift
dataStack.perform(
asynchronous: { (transaction) -> Void in
@@ -668,7 +633,7 @@ The examples above use `perform(asynchronous:...)`, but there are actually 3 typ
#### Asynchronous transactions
are spawned from `perform(asynchronous:...)`. This method returns immediately and executes its closure from a background serial queue. The return value for the closure is declared as a generic type, so any value returned from the closure can be passed to the completion result:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Bool in
// make changes
return transaction.hasChanges
@@ -683,7 +648,7 @@ CoreStore.perform(
```
The success and failure can also be declared as separate handlers:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Int in
// make changes
return transaction.delete(objects)
@@ -704,7 +669,7 @@ Transactions created from `perform(asynchronous:...)` are instances of `Asynchro
#### Synchronous transactions
are created from `perform(synchronous:...)`. While the syntax is similar to its asynchronous counterpart, `perform(synchronous:...)` waits for its transaction block to complete before returning:
```swift
let hasChanges = CoreStore.perform(
let hasChanges = dataStack.perform(
synchronous: { (transaction) -> Bool in
// make changes
return transaction.hasChanges
@@ -717,14 +682,14 @@ Since `perform(synchronous:...)` technically blocks two queues (the caller's que
By default, `perform(synchronous:...)` will wait for observers such as `ListMonitor`s to be notified before the method returns. This may cause deadlocks, especially if you are calling this from the main thread. To reduce this risk, you may try to set the `waitForAllObservers:` parameter to `false`. Doing so tells the `SynchronousDataTransaction` to block only until it completes saving. It will not wait for other context's to receive those changes. This reduces deadlock risk but may have surprising side-effects:
```swift
CoreStore.perform(
dataStack.perform(
synchronous: { (transaction) in
let person = transaction.create(Into<Person>())
person.name = "John"
},
waitForAllObservers: false
)
let newPerson = CoreStore.fetchOne(From<Person>.where(\.name == "John"))
let newPerson = dataStack.fetchOne(From<Person>.where(\.name == "John"))
// newPerson may be nil!
// The DataStack may have not yet received the update notification.
```
@@ -733,7 +698,7 @@ Due to this complicated nature of synchronous transactions, if your app has very
#### Unsafe transactions
are special in that they do not enclose updates within a closure:
```swift
let transaction = CoreStore.beginUnsafe()
let transaction = dataStack.beginUnsafe()
// make changes
downloadJSONWithCompletion({ (json) -> Void in
@@ -779,7 +744,7 @@ Note that if you do explicitly specify the configuration name, CoreStore will on
After creating an object from the transaction, you can simply update its properties as normal:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let person = transaction.create(Into<MyPersonEntity>())
person.name = "John Smith"
@@ -790,7 +755,7 @@ CoreStore.perform(
```
To update an existing object, fetch the object's instance from the transaction:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let person = try transaction.fetchOne(
From<MyPersonEntity>()
@@ -807,7 +772,7 @@ CoreStore.perform(
```swift
let jane: MyPersonEntity = // ...
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
// WRONG: jane.age = jane.age + 1
// RIGHT:
@@ -822,7 +787,7 @@ This is also true when updating an object's relationships. Make sure that the ob
let jane: MyPersonEntity = // ...
let john: MyPersonEntity = // ...
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
// WRONG: jane.friends = [john]
// RIGHT:
@@ -840,7 +805,7 @@ Deleting an object is simpler because you can tell a transaction to delete an ob
```swift
let john: MyPersonEntity = // ...
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
transaction.delete(john)
},
@@ -852,7 +817,7 @@ or several objects at once:
let john: MyPersonEntity = // ...
let jane: MyPersonEntity = // ...
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
try transaction.delete(john, jane)
// try transaction.delete([john, jane]) is also allowed
@@ -862,7 +827,7 @@ CoreStore.perform(
```
If you do not have references yet to the objects to be deleted, transactions have a `deleteAll(...)` method you can pass a query to:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
try transaction.deleteAll(
From<MyPersonEntity>()
@@ -879,7 +844,7 @@ Always remember that the `DataStack` and individual transactions manage differen
```swift
let jane: MyPersonEntity = // ...
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let jane = transaction.edit(jane)!
jane.age = jane.age + 1
@@ -891,14 +856,14 @@ But `CoreStore`, `DataStack` and `BaseDataTransaction` have a very flexible `fet
```swift
let jane: MyPersonEntity = // ...
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> MyPersonEntity in
let jane = transaction.fetchExisting(jane)! // instance for transaction
jane.age = jane.age + 1
return jane
},
success: { (transactionJane) in
let jane = CoreStore.fetchExisting(transactionJane)! // instance for DataStack
let jane = dataStack.fetchExisting(transactionJane)! // instance for DataStack
print(jane.age)
},
failure: { (error) in
@@ -910,7 +875,7 @@ CoreStore.perform(
```swift
var peopleIDs: [NSManagedObjectID] = // ...
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let jane = try transaction.fetchOne(
From<MyPersonEntity>()
@@ -925,7 +890,7 @@ CoreStore.perform(
## Importing data
Some times, if not most of the time, the data that we save to Core Data comes from external sources such as web servers or external files. If you have for example a JSON dictionary, you may be extracting values as such:
Some times, if not most of the time, the data that we save to Core Data comes from external sources such as web servers or external files. If you have a JSON dictionary for example, you may be extracting values as such:
```swift
let json: [String: Any] = // ...
person.name = json["name"] as? NSString
@@ -934,7 +899,7 @@ person.age = json["age"] as? NSNumber
```
If you have many attributes, you don't want to keep repeating this mapping everytime you want to import data. CoreStore lets you write the data mapping code just once, and all you have to do is call `importObject(...)` or `importUniqueObject(...)` through `BaseDataTransaction` subclasses:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let json: [String: Any] = // ...
try! transaction.importObject(
@@ -976,7 +941,7 @@ typealias ImportSource = [String: Any]
```
This lets us call `importObject(_:source:)` with any `[String: Any]` type as the argument to `source`:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let json: [String: Any] = // ...
try! transaction.importObject(
@@ -998,7 +963,7 @@ func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) t
```
Transactions also let you import multiple objects at once using the `importObjects(_:sourceArray:)` method:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let jsonArray: [[String: Any]] = // ...
try! transaction.importObjects(
@@ -1025,7 +990,7 @@ func didInsert(from source: ImportSource, in transaction: BaseDataTransaction) t
```
Doing so can let you abandon an invalid transaction immediately:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let jsonArray: [[String: Any]] = // ...
@@ -1080,7 +1045,7 @@ For `ImportableUniqueObject`, the extraction and assignment of values should be
You can then create/update an object by calling a transaction's `importUniqueObject(...)` method:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let json: [String: Any] = // ...
try! transaction.importUniqueObject(
@@ -1095,7 +1060,7 @@ CoreStore.perform(
or multiple objects at once with the `importUniqueObjects(...)` method:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let jsonArray: [[String: AnyObject]] = // ...
try! transaction.importUniqueObjects(
@@ -1107,7 +1072,7 @@ CoreStore.perform(
completion: { _ in }
)
```
As with `ImportableObject`, you can control wether to skip importing an object by implementing
As with `ImportableObject`, you can control whether to skip importing an object by implementing
`shouldInsert(from:in:)` and `shouldUpdate(from:in:)`, or to cancel all objects by `throw`ing an error from the `uniqueID(from:in:)`, `didInsert(from:in:)` or `update(from:in:)` methods.
@@ -1125,17 +1090,17 @@ Before we dive in, be aware that CoreStore distinguishes between *fetching* and
#### `From` clause
The search conditions for fetches and queries are specified using *clauses*. All fetches and queries require a `From` clause that indicates the target entity type:
```swift
let people = try CoreStore.fetchAll(From<MyPersonEntity>())
let people = try dataStack.fetchAll(From<MyPersonEntity>())
```
`people` in the example above will be of type `[MyPersonEntity]`. The `From<MyPersonEntity>()` clause indicates a fetch to all persistent stores that `MyPersonEntity` belong to.
If the entity exists in multiple configurations and you need to only search from a particular configuration, indicate in the `From` clause the configuration name for the destination persistent store:
```swift
let people = try CoreStore.fetchAll(From<MyPersonEntity>("Config1")) // ignore objects in persistent stores other than the "Config1" configuration
let people = try dataStack.fetchAll(From<MyPersonEntity>("Config1")) // ignore objects in persistent stores other than the "Config1" configuration
```
or if the persistent store is the auto-generated "Default" configuration, specify `nil`:
```swift
let person = try CoreStore.fetchAll(From<MyPersonEntity>(nil))
let person = try dataStack.fetchAll(From<MyPersonEntity>(nil))
```
Now we know how to use a `From` clause, let's move on to fetching and querying.
@@ -1155,11 +1120,11 @@ Each method's purpose is straightforward, but we need to understand how to set t
The `Where` clause is CoreStore's `NSPredicate` wrapper. It specifies the search filter to use when fetching (or querying). It implements all initializers that `NSPredicate` does (except for `-predicateWithBlock:`, which Core Data does not support):
```swift
var people = try CoreStore.fetchAll(
var people = try dataStack.fetchAll(
From<MyPersonEntity>(),
Where<MyPersonEntity>("%K > %d", "age", 30) // string format initializer
)
people = try CoreStore.fetchAll(
people = try dataStack.fetchAll(
From<MyPersonEntity>(),
Where<MyPersonEntity>(true) // boolean initializer
)
@@ -1167,21 +1132,21 @@ people = try CoreStore.fetchAll(
If you do have an existing `NSPredicate` instance already, you can pass that to `Where` as well:
```swift
let predicate = NSPredicate(...)
var people = CoreStore.fetchAll(
var people = dataStack.fetchAll(
From<MyPersonEntity>(),
Where<MyPersonEntity>(predicate) // predicate initializer
)
```
Starting CoreStore 5.0, `Where` clauses became more type-safe and are now generic types. To avoid verbose repetition of the generic object type, fetch methods now support **Fetch Chain builders**. We can also use Swift's Smart KeyPaths as the `Where` clause expression:
`Where` clauses are generic types. To avoid verbose repetition of the generic object type, fetch methods support **Fetch Chain builders**. We can also use Swift's Smart KeyPaths as the `Where` clause expression:
```swift
var people = try CoreStore.fetchAll(
var people = try dataStack.fetchAll(
From<MyPersonEntity>()
.where(\.age > 30) // Type-safe!
)
```
`Where` clauses also implement the `&&`, `||`, and `!` logic operators, so you can provide logical conditions without writing too much `AND`, `OR`, and `NOT` strings:
```swift
var people = try CoreStore.fetchAll(
var people = try dataStack.fetchAll(
From<MyPersonEntity>()
.where(\.age > 30 && \.gender == "M")
)
@@ -1192,15 +1157,15 @@ If you do not provide a `Where` clause, all objects that belong to the specified
The `OrderBy` clause is CoreStore's `NSSortDescriptor` wrapper. Use it to specify attribute keys in which to sort the fetch (or query) results with.
```swift
var mostValuablePeople = try CoreStore.fetchAll(
var mostValuablePeople = try dataStack.fetchAll(
From<MyPersonEntity>(),
OrderBy<MyPersonEntity>(.descending("rating"), .ascending("surname"))
)
```
As seen above, `OrderBy` accepts a list of `SortKey` enumeration values, which can be either `.ascending` or `.descending`.
⭐️As with `Where` clauses, CoreStore 5.0 turned `OrderBy` clauses into generic types. To avoid verbose repetition of the generic object type, fetch methods now support **Fetch Chain builders**. We can also use Swift's Smart KeyPaths as the `OrderBy` clause expression:
As with `Where` clauses, `OrderBy` clauses are also generic types. To avoid verbose repetition of the generic object type, fetch methods support **Fetch Chain builders**. We can also use Swift's Smart KeyPaths as the `OrderBy` clause expression:
```swift
var people = try CoreStore.fetchAll(
var people = try dataStack.fetchAll(
From<MyPersonEntity>()
.orderBy(.descending(\.rating), .ascending(\.surname)) // Type-safe!
)
@@ -1212,7 +1177,7 @@ var orderBy = OrderBy<MyPersonEntity>(.descending(\.rating))
if sortFromYoungest {
orderBy += OrderBy(.ascending(\.age))
}
var mostValuablePeople = try CoreStore.fetchAll(
var mostValuablePeople = try dataStack.fetchAll(
From<MyPersonEntity>(),
orderBy
)
@@ -1222,7 +1187,7 @@ var mostValuablePeople = try CoreStore.fetchAll(
The `Tweak` clause lets you, uh, *tweak* the fetch (or query). `Tweak` exposes the `NSFetchRequest` in a closure where you can make changes to its properties:
```swift
var people = try CoreStore.fetchAll(
var people = try dataStack.fetchAll(
From<MyPersonEntity>(),
Where<MyPersonEntity>("age > %d", 30),
OrderBy<MyPersonEntity>(.ascending("surname")),
@@ -1235,7 +1200,7 @@ var people = try CoreStore.fetchAll(
```
`Tweak` also supports **Fetch Chain builders**:
```swift
var people = try CoreStore.fetchAll(
var people = try dataStack.fetchAll(
From<MyPersonEntity>(),
.where(\.age > 30)
.orderBy(.ascending(\.surname))
@@ -1266,24 +1231,24 @@ Setting up the `From`, `Where`, `OrderBy`, and `Tweak` clauses is similar to how
The `Select<T>` clause specifies the target attribute/aggregate key, as well as the expected return type:
```swift
let johnsAge = try CoreStore.queryValue(
let johnsAge = try dataStack.queryValue(
From<MyPersonEntity>(),
Select<Int>("age"),
Where("name == %@", "John Smith")
Where<MyPersonEntity>("name == %@", "John Smith")
)
```
The example above queries the "age" property for the first object that matches the `Where` condition. `johnsAge` will be bound to type `Int?`, as indicated by the `Select<Int>` generic type. For `queryValue(...)`, types that conform to `QueryableAttributeType` are allowed as the return type (and therefore as the generic type for `Select<T>`).
For `queryAttributes(...)`, only `NSDictionary` is valid for `Select`, thus you are allowed to omit the generic type:
```swift
let allAges = try CoreStore.queryAttributes(
let allAges = try dataStack.queryAttributes(
From<MyPersonEntity>(),
Select("age")
)
```
Starting CoreStore 5.0, query methods now support **Query Chain builders**. We can also use Swift's Smart KeyPaths to use in the expressions:
query methods also support **Query Chain builders**. We can also use Swift's Smart KeyPaths to use in the expressions:
```swift
let johnsAge = try CoreStore.queryValue(
let johnsAge = try dataStack.queryValue(
From<MyPersonEntity>()
.select(\.age) // binds the result to Int
.where(\.name == "John Smith")
@@ -1298,7 +1263,7 @@ If you only need a value for a particular attribute, you can just specify the ke
- `.sum(...)`
```swift
let oldestAge = try CoreStore.queryValue(
let oldestAge = try dataStack.queryValue(
From<MyPersonEntity>(),
Select<Int>(.maximum("age"))
)
@@ -1306,7 +1271,7 @@ let oldestAge = try CoreStore.queryValue(
For `queryAttributes(...)` which returns an array of dictionaries, you can specify multiple attributes/aggregates to `Select`:
```swift
let personJSON = try CoreStore.queryAttributes(
let personJSON = try dataStack.queryAttributes(
From<MyPersonEntity>(),
Select("name", "age")
)
@@ -1326,7 +1291,7 @@ let personJSON = try CoreStore.queryAttributes(
```
You can also include an aggregate as well:
```swift
let personJSON = try CoreStore.queryAttributes(
let personJSON = try dataStack.queryAttributes(
From<MyPersonEntity>(),
Select("name", .count("friends"))
)
@@ -1346,7 +1311,7 @@ which returns:
```
The `"count(friends)"` key name was automatically used by CoreStore, but you can specify your own key alias if you need:
```swift
let personJSON = try CoreStore.queryAttributes(
let personJSON = try dataStack.queryAttributes(
From<MyPersonEntity>(),
Select("name", .count("friends", as: "friendsCount"))
)
@@ -1369,15 +1334,15 @@ which now returns:
The `GroupBy` clause lets you group results by a specified attribute/aggregate. This is useful only for `queryAttributes(...)` since `queryValue(...)` just returns the first value.
```swift
let personJSON = try CoreStore.queryAttributes(
let personJSON = try dataStack.queryAttributes(
From<MyPersonEntity>(),
Select("age", .count("age", as: "count")),
GroupBy("age")
)
```
Starting CoreStore 5.0, `GroupBy` clauses are now also generic types and now support **Query Chain builders**. We can also use Swift's Smart KeyPaths to use in the expressions:
`GroupBy` clauses are also generic types and support **Query Chain builders**. We can also use Swift's Smart KeyPaths to use in the expressions:
```swift
let personJSON = try CoreStore.queryAttributes(
let personJSON = try dataStack.queryAttributes(
From<MyPersonEntity>()
.select(.attribute(\.age), .count(\.age, as: "count"))
.groupBy(\.age)
@@ -1407,9 +1372,9 @@ public protocol CoreStoreLogger {
func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
}
```
Implement this protocol with your custom class then pass the instance to `CoreStore.logger`:
Implement this protocol with your custom class then pass the instance to `CoreStoreDefaults.logger`:
```swift
CoreStore.logger = MyLogger()
CoreStoreDefaults.logger = MyLogger()
```
Doing so channels all logging calls to your logger.
@@ -1419,8 +1384,7 @@ Take special care when implementing `CoreStoreLogger`'s `assert(...)` and `abort
- `assert(...)`: The behavior between `DEBUG` and release builds, or `-O` and `-Onone`, are all left to the implementers' responsibility. CoreStore calls `CoreStoreLogger.assert(...)` only for invalid but usually recoverable errors (for example, early validation failures that may cause an error thrown and handled somewhere else)
- `abort(...)`: This method is *the* last-chance for your app to *synchronously* log a fatal error within CoreStore. The app will be terminated right after this function is called (CoreStore calls `fatalError()` internally)
Starting CoreStore 2.0, all CoreStore types now have very useful (and pretty formatted!) `print(...)` outputs.
All CoreStore types have very useful (and pretty formatted!) `print(...)` outputs.
A couple of examples, `ListMonitor`:
<img width="369" alt="screen shot 2016-07-10 at 22 56 44" src="https://cloud.githubusercontent.com/assets/3029684/16713994/ae06e702-46f1-11e6-83a8-dee48b480bab.png" />
@@ -1435,8 +1399,15 @@ These are all implemented with `CustomDebugStringConvertible.debugDescription`,
CoreStore provides type-safe wrappers for observing managed objects:
- `ObjectMonitor`: use to monitor changes to a single `NSManagedObject` or `CoreStoreObject` instance (instead of Key-Value Observing)
- `ListMonitor`: use to monitor changes to a list of `NSManagedObject` or `CoreStoreObject` instances (instead of `NSFetchedResultsController`)
| | 🆕[*ObjectPublisher*](#observe-a-single-objects-updates) | [*ObjectMonitor*](#observe-a-single-objects-per-property-updates) | 🆕[*ListPublisher*](#observe-a-diffable-list) | [*ListMonitor*](#observe-detailed-list-changes) |
| --- | --- | --- | --- | --- |
| *Number of objects* | 1 | 1 | N | N |
| *Allows multiple observers* | ✅ | ✅ | ✅ | ✅ |
| *Emits fine-grained changes* | ❌ | ✅ | ❌ | ✅ |
| *Emits DiffableDataSource snapshots* | ✅ | ❌ | ✅ | ❌ |
| *Delegate methods* | ❌ | ✅ | ❌ | ✅ |
| *Closure callback* | ✅ | ❌ | ✅ | ❌ |
| *SwiftUI support* | ✅ | ❌ | ✅ | ❌ |
### Observe a single property
To get notifications for single property changes in an object, there are two methods depending on the object's base class.
@@ -1457,10 +1428,35 @@ let observer = person.age.observe(options: [.new]) { (person, change)
For both methods, you will need to keep a reference to the returned `observer` for the duration of the observation.
### Observe a single object's updates
### Observe a single object
Observers of an `ObjectPublisher` can receive notifications if any of the object's property changes. You can create an `ObjectPublisher` from the object directly:
```swift
let objectPublisher: ObjectPublisher<Person> = person.asPublisher(in: dataStack)
```
or by indexing a `ListPublisher`'s `ListSnapshot`:
```swift
let listPublisher: ListPublisher<Person> = // ...
// ...
let objectPublisher = listPublisher.snapshot[indexPath]
```
(See [`ListPublisher` examples](#observe-a-diffable-list) below)
To observe an object itself as a whole, implement the `ObjectObserver` protocol and specify the `EntityType`:
To receive notifications, call the `ObjectPublisher`'s `addObserve(...)` method passing the owner of the callback closure:
```swift
objectPublisher.addObserver(self) { [weak self] (objectPublisher) in
let snapshot: ObjectSnapshot<Person> = objectPublisher.snapshot
// handle changes
}
```
Note that the owner instance will not be retained. You may call `ObjectPublisher.removeObserver(...)` explicitly to stop receiving notifications, but the `ObjectPublisher` also discontinues sending events to deallocated observers.
The `ObjectSnapshot` returned from the `ObjectPublisher.snapshot` property returns a full-copy `struct` of all properties of the object. This is ideal for managing states as they are thread-safe and are not affected by further changes to the actual object. `ObjectPublisher` automatically updates its `snapshot` value to the latest state of the object.
### Observe a single object's per-property updates
If you need to track specifically which properties change in an object, implement the `ObjectObserver` protocol and specify the `EntityType`:
```swift
class MyViewController: UIViewController, ObjectObserver {
func objectMonitor(monitor: ObjectMonitor<MyPersonEntity>, willUpdateObject object: MyPersonEntity) {
@@ -1476,10 +1472,10 @@ class MyViewController: UIViewController, ObjectObserver {
}
}
```
We then need to keep a `ObjectMonitor` instance and register our `ObjectObserver` as an observer:
We then need to keep an `ObjectMonitor` instance and register our `ObjectObserver` as an observer:
```swift
let person: MyPersonEntity = // ...
self.monitor = CoreStore.monitorObject(person)
self.monitor = dataStack.monitorObject(person)
self.monitor.addObserver(self)
```
The controller will then notify our observer whenever the object's attributes change. You can add multiple `ObjectObserver`s to a single `ObjectMonitor` without any problem. This means you can just share around the `ObjectMonitor` instance to different screens without problem.
@@ -1488,8 +1484,51 @@ You can get `ObjectMonitor`'s object through its `object` property. If the objec
While `ObjectMonitor` exposes `removeObserver(...)` as well, it only stores `weak` references of the observers and will safely unregister deallocated observers.
### Observe a list of objects
To observe a list of objects, implement one of the `ListObserver` protocols and specify the `EntityType`:
### Observe a diffable list
Observers of a `ListPublisher` can receive notifications whenever its fetched result set changes. You can create a `ListPublisher` by fetching from the `DataStack`:
```swift
let listPublisher = dataStack.listPublisher(
From<Person>()
.sectionBy(\.age") { "Age \($0)" } // sections are optional
.where(\.title == "Engineer")
.orderBy(.ascending(\.lastName))
)
```
To receive notifications, call the `ListPublisher`'s `addObserve(...)` method passing the owner of the callback closure:
```swift
listPublisher.addObserver(self) { [weak self] (listPublisher) in
let snapshot: ListSnapshot<Person> = listPublisher.snapshot
// handle changes
}
```
Note that the owner instance will not be retained. You may call `ListPublisher.removeObserver(...)` explicitly to stop receiving notifications, but the `ListPublisher` also discontinues sending events to deallocated observers.
The `ListSnapshot` returned from the `ListPublisher.snapshot` property returns a full-copy `struct` of all sections and `NSManagedObject` items in the list. This is ideal for managing states as they are thread-safe and are not affected by further changes to the result set. `ListPublisher` automatically updates its `snapshot` value to the latest state of the fetch.
Unlike `ListMonitor`s (See [`ListMonitor` examples](#observe-detailed-list-changes) below), a `ListPublisher` does not track detailed inserts, deletes, and moves. In return, a `ListPublisher` is a lot more lightweight and are designed to work well with `DiffableDataSource.TableViewAdapter`s and `DiffableDataSource.CollectionViewAdapter`s:
```swift
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
// ...
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot, animatingDifferences: true
)
}
```
### Observe detailed list changes
If you need to track each object's inserts, deletes, moves, and updates, implement one of the `ListObserver` protocols and specify the `EntityType`:
```swift
class MyViewController: UIViewController, ListObserver {
func listMonitorDidChange(monitor: ListMonitor<MyPersonEntity>) {
@@ -1513,10 +1552,10 @@ Including `ListObserver`, there are 3 observer protocols you can implement depen
- `ListObjectObserver`: in addition to `ListObserver` methods, also lets you handle object inserts, updates, and deletes:
```swift
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didMoveObject object: MyPersonEntity, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didInsertObject object: MyPersonEntity, toIndexPath indexPath: IndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didDeleteObject object: MyPersonEntity, fromIndexPath indexPath: IndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didUpdateObject object: MyPersonEntity, atIndexPath indexPath: IndexPath)
func listMonitor(_ monitor: ListMonitor<MyPersonEntity>, didMoveObject object: MyPersonEntity, fromIndexPath: IndexPath, toIndexPath: IndexPath)
```
- `ListSectionObserver`: in addition to `ListObjectObserver` methods, also lets you handle section inserts and deletes:
```swift
@@ -1526,7 +1565,7 @@ Including `ListObserver`, there are 3 observer protocols you can implement depen
We then need to create a `ListMonitor` instance and register our `ListObserver` as an observer:
```swift
self.monitor = CoreStore.monitorList(
self.monitor = dataStack.monitorList(
From<MyPersonEntity>()
.where(\.age > 30)
.orderBy(.ascending(\.name))
@@ -1545,7 +1584,7 @@ let firstPerson = self.monitor[0]
If the list needs to be grouped into sections, create the `ListMonitor` instance with the `monitorSectionedList(...)` method and a `SectionBy` clause:
```swift
self.monitor = CoreStore.monitorSectionedList(
self.monitor = dataStack.monitorSectionedList(
From<MyPersonEntity>()
.sectionBy(\.age)
.where(\.gender == "M")
@@ -1557,7 +1596,7 @@ A list controller created this way will group the objects by the attribute key i
The `SectionBy` clause can also be passed a closure to transform the section name into a displayable string:
```swift
self.monitor = CoreStore.monitorSectionedList(
self.monitor = dataStack.monitorSectionedList(
From<MyPersonEntity>()
.sectionBy(\.age) { (sectionName) -> String? in
"\(sectionName) years old"
@@ -1573,9 +1612,9 @@ func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -
}
```
To access the objects of a sectioned list, use an `NSIndexPath` or a tuple:
To access the objects of a sectioned list, use an `IndexPath` or a tuple:
```swift
let indexPath = NSIndexPath(forRow: 2, inSection: 1)
let indexPath = IndexPath(row: 2, section: 1)
let person1 = self.monitor[indexPath]
let person2 = self.monitor[1, 2]
// person1 and person2 are the same object
@@ -1590,7 +1629,7 @@ All CoreStore types are still written in pure Swift, but most core types have Ob
<tr><th>Swift</th><th>Objective-C</th></tr>
<tr>
<td><pre lang=swift>
try CoreStore.addStorageAndWait(SQLiteStore.self)
try dataStack.addStorageAndWait(SQLiteStore.self)
</pre></td>
<td><pre lang=objc>
NSError *error
@@ -1599,7 +1638,7 @@ NSError *error
</tr>
<tr>
<td><pre lang=swift>
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) in
// ...
},
@@ -1631,7 +1670,7 @@ All of these `CS`-prefixed bridging classes have very similar usage to the exist
For example, you may have a new, modern Swift class that holds a `ListMonitor`:
```swift
class MyViewController: UIViewController {
let monitor = CoreStore.monitorList(From<MyEntity>(), ...)
let monitor = dataStack.monitorList(From<MyEntity>(), ...)
// ...
}
```
@@ -1645,7 +1684,7 @@ Now let's say you have a legacy Objective-C class that previously uses `NSFetche
When you need to instantiate this class from Swift, you just call `bridgeToObjectiveC`:
```swift
class MyViewController: UIViewController {
let monitor = CoreStore.monitorList(From<MyEntity>(), ...)
let monitor = dataStack.monitorList(From<MyEntity>(), ...)
func showOldController() {
let controller = MYOldViewController(monitor: self.monitor.bridgeToObjectiveC)
self.presentViewController(controller, animated: true, completion: nil)
@@ -1719,7 +1758,7 @@ Also note how `Relationship`s are linked statically with the `inverse:` argument
To tell the `DataStack` about these types, add all `CoreStoreObject`s' entities to a `CoreStoreSchema`:
```swift
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
@@ -1729,13 +1768,13 @@ CoreStore.defaultStack = DataStack(
]
)
)
CoreStore.addStorage(/* ... */)
CoreStoreDefaults.dataStack.addStorage(/* ... */)
```
And that's all CoreStore needs to build the model; **we don't need *.xcdatamodeld* files anymore.**
These special properties' values can be accessed or mutated using `.value`:
```swift
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) in
let dog: Dog = transaction.fetchOne(From<Dog>())!
// ...
@@ -1755,7 +1794,7 @@ let keyPath: String = Dog.keyPath { $0.nickname }
```
as well as `Where` and `OrderBy` clauses
```swift
let puppies = try CoreStore.fetchAll(
let puppies = try dataStack.fetchAll(
From<Dog>()
.where(\.age < 1)
.orderBy(.ascending(\.age))
@@ -1790,7 +1829,7 @@ CoreStoreSchema(
```
You can also get this hash after the `DataStack` has been fully set up by printing to the console:
```swift
print(CoreStore.defaultStack.modelSchema.printCoreStoreSchema())
print(CoreStoreDefaults.dataStack.modelSchema.printCoreStoreSchema())
```
Once the version lock is set, any changes in the properties or to the model will trigger an assertion failure similar to this:
@@ -1801,8 +1840,8 @@ Once the version lock is set, any changes in the properties or to the model will
# Installation
- Requires:
- iOS 10 SDK and above
- Swift 5.0 (Xcode 10+)
- For previous Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1)
- Swift 5.1 (Xcode 11+)
- For previous Swift versions: [Swift 3.2](https://github.com/JohnEstropia/CoreStore/tree/4.2.3), [Swift 4.2](https://github.com/JohnEstropia/CoreStore/tree/6.2.1), [Swift 5.0](https://github.com/JohnEstropia/CoreStore/tree/6.3.2)
- Dependencies:
- *None*
- Other notes:
@@ -1811,7 +1850,7 @@ Once the version lock is set, any changes in the properties or to the model will
### Install with CocoaPods
In your `Podfile`, add
```
pod 'CoreStore', '~> 6.3'
pod 'CoreStore', '~> 7.0'
```
and run
```
@@ -1822,7 +1861,7 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
### Install with Carthage
In your `Cartfile`, add
```
github "JohnEstropia/CoreStore" >= 6.3.0
github "JohnEstropia/CoreStore" >= 7.0.0
```
and run
```
@@ -1833,7 +1872,7 @@ This installs CoreStore as a framework. Declare `import CoreStore` in your swift
#### Install with Swift Package Manager:
```swift
dependencies: [
.package(url: "https://github.com/JohnEstropia/CoreStore.git", from: "6.3.0"))
.package(url: "https://github.com/JohnEstropia/CoreStore.git", from: "7.0.0"))
]
```
Declare `import CoreStore` in your swift file to use the library.

View File

@@ -30,7 +30,7 @@ import CoreData
// MARK: - AsynchronousDataTransaction
/**
The `AsynchronousDataTransaction` provides an interface for `DynamicObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.perform(asynchronous:...)`, or from `CoreStore.perform(synchronous:...)`.
The `AsynchronousDataTransaction` provides an interface for `DynamicObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.perform(asynchronous:...)`.
*/
public final class AsynchronousDataTransaction: BaseDataTransaction {
@@ -66,7 +66,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- parameter into: the `Into` clause indicating the destination `NSManagedObject` or `CoreStoreObject` entity type and the destination configuration
- returns: a new `NSManagedObject` or `CoreStoreObject` instance of the specified entity type.
*/
public override func create<D>(_ into: Into<D>) -> D {
public override func create<O>(_ into: Into<O>) -> O {
Internals.assert(
!self.isCommitted,
@@ -82,7 +82,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- parameter object: the `NSManagedObject` or `CoreStoreObject` to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
public override func edit<D: DynamicObject>(_ object: D?) -> D? {
public override func edit<O: DynamicObject>(_ object: O?) -> O? {
Internals.assert(
!self.isCommitted,
@@ -99,7 +99,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
public override func edit<D>(_ into: Into<D>, _ objectID: NSManagedObjectID) -> D? {
public override func edit<O>(_ into: Into<O>, _ objectID: NSManagedObjectID) -> O? {
Internals.assert(
!self.isCommitted,
@@ -108,51 +108,50 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
return super.edit(into, objectID)
}
/**
Deletes a specified `NSManagedObject` or `CoreStoreObject`.
- parameter object: the `NSManagedObject` or `CoreStoreObject` to be deleted
Deletes the objects with the specified `NSManagedObjectID`s.
- parameter objectIDs: the `NSManagedObjectID`s of the objects to delete
*/
public override func delete<D: DynamicObject>(_ object: D?) {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entity of type \(Internals.typeName(object)) from an already committed \(Internals.typeName(self))."
)
super.delete(object)
}
/**
Deletes the specified `DynamicObject`s.
- parameter object1: the `DynamicObject` to be deleted
- parameter object2: another `DynamicObject` to be deleted
- parameter objects: other `DynamicObject`s to be deleted
*/
public override func delete<D: DynamicObject>(_ object1: D?, _ object2: D?, _ objects: D?...) {
public override func delete<S: Sequence>(objectIDs: S) where S.Iterator.Element: NSManagedObjectID {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
)
super.delete(([object1, object2] + objects).compactMap { $0 })
super.delete(objectIDs: objectIDs)
}
/**
Deletes the specified `DynamicObject`s.
- parameter objects: the `DynamicObject`s to be deleted
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by series of `ObjectRepresentation`s.
- parameter object: the `ObjectRepresentation` representing an `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter objects: other `ObjectRepresentation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public override func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: DynamicObject {
public override func delete<O: ObjectRepresentation>(_ object: O?, _ objects: O?...) {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
)
super.delete(([object] + objects).compactMap { $0 })
}
/**
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by an `ObjectRepresenation`.
- parameter objects: the `ObjectRepresenation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public override func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: ObjectRepresentation {
Internals.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(Internals.typeName(self))."
)
super.delete(objects)
}

View File

@@ -29,11 +29,10 @@ import CoreData
// MARK: - AttributeProtocol
internal protocol AttributeProtocol: AnyObject {
internal protocol AttributeProtocol: PropertyProtocol {
static var attributeType: NSAttributeType { get }
var keyPath: KeyPathString { get }
var isOptional: Bool { get }
var isTransient: Bool { get }
var allowsExternalBinaryDataStorage: Bool { get }
@@ -44,5 +43,5 @@ internal protocol AttributeProtocol: AnyObject {
var rawObject: CoreStoreManagedObject? { get set }
var getter: CoreStoreManagedObject.CustomGetter? { get }
var setter: CoreStoreManagedObject.CustomSetter? { get }
var valueForSnapshot: Any { get }
var valueForSnapshot: Any? { get }
}

View File

@@ -39,9 +39,9 @@ extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableObject` methods
- returns: the created `ImportableObject` instance, or `nil` if the import was ignored
*/
public func importObject<D: ImportableObject>(
_ into: Into<D>,
source: D.ImportSource) throws -> D? {
public func importObject<O: ImportableObject>(
_ into: Into<O>,
source: O.ImportSource) throws -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -65,13 +65,13 @@ extension BaseDataTransaction {
/**
Updates an existing `ImportableObject` by importing values from the specified import source.
- parameter object: the `NSManagedObject` to update
- parameter object: the `ImportableObject` to update
- parameter source: the object to import values from
- throws: an `Error` thrown from any of the `ImportableObject` methods
*/
public func importObject<D: ImportableObject>(
_ object: D,
source: D.ImportSource) throws {
public func importObject<O: ImportableObject>(
_ object: O,
source: O.ImportSource) throws {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -97,9 +97,9 @@ extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableObject` methods
- returns: the array of created `ImportableObject` instances
*/
public func importObjects<D: ImportableObject, S: Sequence>(
_ into: Into<D>,
sourceArray: S) throws -> [D] where S.Iterator.Element == D.ImportSource {
public func importObjects<O: ImportableObject, S: Sequence>(
_ into: Into<O>,
sourceArray: S) throws -> [O] where S.Iterator.Element == O.ImportSource {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -108,7 +108,7 @@ extension BaseDataTransaction {
return try autoreleasepool {
return try sourceArray.compactMap { (source) -> D? in
return try sourceArray.compactMap { (source) -> O? in
let entityType = into.entityClass
guard entityType.shouldInsert(from: source, in: self) else {
@@ -133,9 +133,9 @@ extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableUniqueObject` methods
- returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored
*/
public func importUniqueObject<D: ImportableUniqueObject>(
_ into: Into<D>,
source: D.ImportSource) throws -> D? {
public func importUniqueObject<O: ImportableUniqueObject>(
_ into: Into<O>,
source: O.ImportSource) throws -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -151,7 +151,7 @@ extension BaseDataTransaction {
return nil
}
if let object = try self.fetchOne(From(entityType), Where<D>(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) {
if let object = try self.fetchOne(From(entityType), Where<O>(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) {
guard entityType.shouldUpdate(from: source, in: self) else {
@@ -185,10 +185,10 @@ extension BaseDataTransaction {
- throws: an `Error` thrown from any of the `ImportableUniqueObject` methods
- returns: the array of created/updated `ImportableUniqueObject` instances
*/
public func importUniqueObjects<D: ImportableUniqueObject, S: Sequence>(
_ into: Into<D>,
public func importUniqueObjects<O: ImportableUniqueObject, S: Sequence>(
_ into: Into<O>,
sourceArray: S,
preProcess: @escaping (_ mapping: [D.UniqueIDType: D.ImportSource]) throws -> [D.UniqueIDType: D.ImportSource] = { $0 }) throws -> [D] where S.Iterator.Element == D.ImportSource {
preProcess: @escaping (_ mapping: [O.UniqueIDType: O.ImportSource]) throws -> [O.UniqueIDType: O.ImportSource] = { $0 }) throws -> [O] where S.Iterator.Element == O.ImportSource {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -198,10 +198,10 @@ extension BaseDataTransaction {
return try autoreleasepool {
let entityType = into.entityClass
var importSourceByID = Dictionary<D.UniqueIDType, D.ImportSource>()
var importSourceByID = Dictionary<O.UniqueIDType, O.ImportSource>()
let sortedIDs = try autoreleasepool {
return try sourceArray.compactMap { (source) -> D.UniqueIDType? in
return try sourceArray.compactMap { (source) -> O.UniqueIDType? in
guard let uniqueIDValue = try entityType.uniqueID(from: source, in: self) else {
@@ -214,13 +214,13 @@ extension BaseDataTransaction {
importSourceByID = try autoreleasepool { try preProcess(importSourceByID) }
var existingObjectsByID = Dictionary<D.UniqueIDType, D>()
var existingObjectsByID = Dictionary<O.UniqueIDType, O>()
try self
.fetchAll(From(entityType), Where<D>(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs))
.fetchAll(From(entityType), Where<O>(entityType.uniqueIDKeyPath, isMemberOf: sortedIDs))
.forEach { existingObjectsByID[$0.uniqueIDValue] = $0 }
var processedObjectIDs = Set<D.UniqueIDType>()
var result = [D]()
var processedObjectIDs = Set<O.UniqueIDType>()
var result = [O]()
for objectID in sortedIDs where !processedObjectIDs.contains(objectID) {

View File

@@ -39,7 +39,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s deleted
*/
@discardableResult
public func deleteAll<D>(_ from: From<D>, _ deleteClauses: DeleteClause...) throws -> Int {
public func deleteAll<O>(_ from: From<O>, _ deleteClauses: DeleteClause...) throws -> Int {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -56,7 +56,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s deleted
*/
@discardableResult
public func deleteAll<D>(_ from: From<D>, _ deleteClauses: [DeleteClause]) throws -> Int {
public func deleteAll<O>(_ from: From<O>, _ deleteClauses: [DeleteClause]) throws -> Int {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -93,7 +93,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter object: a reference to the object created/fetched outside the transaction
- returns: the `DynamicObject` instance if the object exists in the transaction, or `nil` if not found.
*/
public func fetchExisting<D: DynamicObject>(_ object: D) -> D? {
public func fetchExisting<O: DynamicObject>(_ object: O) -> O? {
return self.context.fetchExisting(object)
}
@@ -104,7 +104,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `DynamicObject` instance if the object exists in the transaction, or `nil` if not found.
*/
public func fetchExisting<D: DynamicObject>(_ objectID: NSManagedObjectID) -> D? {
public func fetchExisting<O: DynamicObject>(_ objectID: NSManagedObjectID) -> O? {
return self.context.fetchExisting(objectID)
}
@@ -115,7 +115,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter objects: an array of `DynamicObject`s created/fetched outside the transaction
- returns: the `DynamicObject` array for objects that exists in the transaction
*/
public func fetchExisting<D: DynamicObject, S: Sequence>(_ objects: S) -> [D] where S.Iterator.Element == D {
public func fetchExisting<O: DynamicObject, S: Sequence>(_ objects: S) -> [O] where S.Iterator.Element == O {
return self.context.fetchExisting(objects)
}
@@ -126,7 +126,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `DynamicObject` array for objects that exists in the transaction
*/
public func fetchExisting<D: DynamicObject, S: Sequence>(_ objectIDs: S) -> [D] where S.Iterator.Element == NSManagedObjectID {
public func fetchExisting<O: DynamicObject, S: Sequence>(_ objectIDs: S) -> [O] where S.Iterator.Element == NSManagedObjectID {
return self.context.fetchExisting(objectIDs)
}
@@ -139,7 +139,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchOne<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> D? {
public func fetchOne<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -156,7 +156,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchOne<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> D? {
public func fetchOne<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -195,7 +195,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchAll<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> [D] {
public func fetchAll<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [O] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -212,7 +212,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchAll<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> [D] {
public func fetchAll<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [O] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -251,7 +251,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchCount<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> Int {
public func fetchCount<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> Int {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -268,7 +268,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchCount<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> Int {
public func fetchCount<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> Int {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -307,7 +307,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchObjectID<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID? {
public func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -324,7 +324,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchObjectID<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID? {
public func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -363,7 +363,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchObjectIDs<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID] {
public func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -380,7 +380,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchObjectIDs<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID] {
public func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -425,7 +425,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func queryValue<D, U: QueryableAttributeType>(_ from: From<D>, _ selectClause: Select<D, U>, _ queryClauses: QueryClause...) throws -> U? {
public func queryValue<O, U: QueryableAttributeType>(_ from: From<O>, _ selectClause: Select<O, U>, _ queryClauses: QueryClause...) throws -> U? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -445,7 +445,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func queryValue<D, U: QueryableAttributeType>(_ from: From<D>, _ selectClause: Select<D, U>, _ queryClauses: [QueryClause]) throws -> U? {
public func queryValue<O, U: QueryableAttributeType>(_ from: From<O>, _ selectClause: Select<O, U>, _ queryClauses: [QueryClause]) throws -> U? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -489,7 +489,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func queryAttributes<D>(_ from: From<D>, _ selectClause: Select<D, NSDictionary>, _ queryClauses: QueryClause...) throws -> [[String: Any]] {
public func queryAttributes<O>(_ from: From<O>, _ selectClause: Select<O, NSDictionary>, _ queryClauses: QueryClause...) throws -> [[String: Any]] {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -509,7 +509,7 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func queryAttributes<D>(_ from: From<D>, _ selectClause: Select<D, NSDictionary>, _ queryClauses: [QueryClause]) throws -> [[String: Any]] {
public func queryAttributes<O>(_ from: From<O>, _ selectClause: Select<O, NSDictionary>, _ queryClauses: [QueryClause]) throws -> [[String: Any]] {
Internals.assert(
self.isRunningInAllowedQueue(),

View File

@@ -50,7 +50,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter into: the `Into` clause indicating the destination `NSManagedObject` or `CoreStoreObject` entity type and the destination configuration
- returns: a new `NSManagedObject` or `CoreStoreObject` instance of the specified entity type.
*/
public func create<D>(_ into: Into<D>) -> D {
public func create<O>(_ into: Into<O>) -> O {
let entityClass = into.entityClass
Internals.assert(
@@ -121,7 +121,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter object: the `NSManagedObject` or `CoreStoreObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
public func edit<D: DynamicObject>(_ object: D?) -> D? {
public func edit<O: DynamicObject>(_ object: O?) -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -141,7 +141,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
public func edit<D>(_ into: Into<D>, _ objectID: NSManagedObjectID) -> D? {
public func edit<O>(_ into: Into<O>, _ objectID: NSManagedObjectID) -> O? {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -154,49 +154,56 @@ public /*abstract*/ class BaseDataTransaction {
)
return self.fetchExisting(objectID)
}
/**
Deletes a specified `NSManagedObject` or `CoreStoreObject`.
- parameter object: the `NSManagedObject` or `CoreStoreObject` to be deleted
Deletes the objects with the specified `NSManagedObjectID`s.
- parameter objectIDs: the `NSManagedObjectID`s of the objects to delete
*/
public func delete<D: DynamicObject>(_ object: D?) {
public func delete<S: Sequence>(objectIDs: S) where S.Iterator.Element: NSManagedObjectID {
Internals.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete an entity outside its designated queue."
)
let context = self.context
object
.flatMap(context.fetchExisting)
.flatMap({ context.delete($0.cs_toRaw()) })
objectIDs.forEach {
context.fetchExisting($0).map(context.delete(_:))
}
}
/**
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s.
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by series of `ObjectRepresentation`s.
- parameter object1: the `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter object2: another `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter objects: other `NSManagedObject`s or `CoreStoreObject`s to be deleted
- parameter object: the `ObjectRepresentation` representing an `NSManagedObject` or `CoreStoreObject` to be deleted
- parameter objects: other `ObjectRepresentation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public func delete<D: DynamicObject>(_ object1: D?, _ object2: D?, _ objects: D?...) {
public func delete<O: ObjectRepresentation>(_ object: O?, _ objects: O?...) {
self.delete(([object1, object2] + objects).compactMap { $0 })
Internals.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete an entity outside its designated queue."
)
self.delete(([object] + objects).compactMap { $0 })
}
/**
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s.
Deletes the specified `NSManagedObject`s or `CoreStoreObject`s represented by an `ObjectRepresenation`.
- parameter objects: the `NSManagedObject`s or `CoreStoreObject`s to be deleted
- parameter objects: the `ObjectRepresenation`s representing `NSManagedObject`s or `CoreStoreObject`s to be deleted
*/
public func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: DynamicObject {
public func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: ObjectRepresentation {
Internals.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete entities outside their designated queue."
)
let context = self.context
objects.forEach { context.fetchExisting($0).flatMap({ context.delete($0.cs_toRaw()) }) }
objects.forEach {
$0.asEditable(in: self).map({ context.delete($0.cs_toRaw()) })
}
}
/**
@@ -217,10 +224,10 @@ public /*abstract*/ class BaseDataTransaction {
/**
Returns `true` if the object has any property values changed. This method should not be called after the `commit()` method was called.
- parameter entity: the `DynamicObject` instance
- parameter object: the `DynamicObject` instance
- returns: `true` if the object has any property values changed.
*/
public func objectHasPersistentChangedValues<D: DynamicObject>(_ entity: D) -> Bool {
public func objectHasPersistentChangedValues<O: DynamicObject>(_ object: O) -> Bool {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -230,7 +237,7 @@ public /*abstract*/ class BaseDataTransaction {
!self.isCommitted,
"Attempted to access inserted objects from an already committed \(Internals.typeName(self))."
)
return entity.cs_toRaw().hasPersistentChangedValues
return object.cs_toRaw().hasPersistentChangedValues
}
/**
@@ -239,7 +246,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `DynamicObject`s of the specified type that were inserted to the transaction.
*/
public func insertedObjects<D: DynamicObject>(_ entity: D.Type) -> Set<D> {
public func insertedObjects<O: DynamicObject>(_ entity: O.Type) -> Set<O> {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -276,7 +283,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `NSManagedObjectID`s of the specified type that were inserted to the transaction.
*/
public func insertedObjectIDs<D: DynamicObject>(_ entity: D.Type) -> Set<NSManagedObjectID> {
public func insertedObjectIDs<O: DynamicObject>(_ entity: O.Type) -> Set<NSManagedObjectID> {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -295,7 +302,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `DynamicObject`s of the specified type that were updated in the transaction.
*/
public func updatedObjects<D: DynamicObject>(_ entity: D.Type) -> Set<D> {
public func updatedObjects<O: DynamicObject>(_ entity: O.Type) -> Set<O> {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -332,7 +339,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `NSManagedObjectID`s of the specified type that were updated in the transaction.
*/
public func updatedObjectIDs<D: DynamicObject>(_ entity: D.Type) -> Set<NSManagedObjectID> {
public func updatedObjectIDs<O: DynamicObject>(_ entity: O.Type) -> Set<NSManagedObjectID> {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -351,7 +358,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `DynamicObject`s of the specified type that were deleted from the transaction.
*/
public func deletedObjects<D: DynamicObject>(_ entity: D.Type) -> Set<D> {
public func deletedObjects<O: DynamicObject>(_ entity: O.Type) -> Set<O> {
Internals.assert(
self.isRunningInAllowedQueue(),
@@ -389,7 +396,7 @@ public /*abstract*/ class BaseDataTransaction {
- parameter entity: the `DynamicObject` subclass to filter
- returns: a `Set` of pending `NSManagedObjectID`s of the specified type that were deleted from the transaction.
*/
public func deletedObjectIDs<D: DynamicObject>(_ entity: D.Type) -> Set<NSManagedObjectID> {
public func deletedObjectIDs<O: DynamicObject>(_ entity: O.Type) -> Set<NSManagedObjectID> {
Internals.assert(
self.isRunningInAllowedQueue(),

View File

@@ -38,7 +38,7 @@ extension CSCoreStore {
@objc
public static var modelVersion: String {
return CoreStore.modelVersion
return self.defaultStack.modelVersion
}
/**
@@ -47,7 +47,7 @@ extension CSCoreStore {
@objc
public static func entityTypesByNameForType(_ type: NSManagedObject.Type) -> [EntityName: NSManagedObject.Type] {
return CoreStore.entityTypesByName(for: type)
return self.defaultStack.bridgeToSwift.entityTypesByName(for: type)
}
/**
@@ -56,7 +56,7 @@ extension CSCoreStore {
@objc
public static func entityDescriptionForClass(_ type: NSManagedObject.Type) -> NSEntityDescription? {
return CoreStore.entityDescription(for: type)
return self.defaultStack.bridgeToSwift.entityDescription(for: type)
}
/**

View File

@@ -65,7 +65,7 @@ extension CSCoreStore {
return bridge {
CoreStore.beginUnsafe()
self.defaultStack.bridgeToSwift.beginUnsafe()
}
}
@@ -80,7 +80,7 @@ extension CSCoreStore {
return bridge {
CoreStore.beginUnsafe(supportsUndo: supportsUndo)
self.defaultStack.bridgeToSwift.beginUnsafe(supportsUndo: supportsUndo)
}
}
@@ -90,6 +90,6 @@ extension CSCoreStore {
@objc
public static func refreshAndMergeAllObjects() {
CoreStore.refreshAndMergeAllObjects()
self.defaultStack.refreshAndMergeAllObjects()
}
}

View File

@@ -46,8 +46,8 @@ public final class CSCoreStore: NSObject {
@objc
public static var defaultStack: CSDataStack {
get { return Shared.defaultStack.bridgeToObjectiveC }
set { Shared.defaultStack = newValue.bridgeToSwift }
get { return CoreStoreDefaults.dataStack.bridgeToObjectiveC }
set { CoreStoreDefaults.dataStack = newValue.bridgeToSwift }
}

View File

@@ -36,7 +36,7 @@ extension CSDataStack {
Creates a `CSObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
- returns: an `ObjectMonitor` that monitors changes to `object`
*/
@objc
public func monitorObject(_ object: NSManagedObject) -> CSObjectMonitor {

View File

@@ -145,7 +145,7 @@ public final class CSFrom: NSObject {
public let bridgeToSwift: From<NSManagedObject>
public init<D: NSManagedObject>(_ swiftValue: From<D>) {
public init<O: NSManagedObject>(_ swiftValue: From<O>) {
self.bridgeToSwift = swiftValue.downcast()
super.init()
@@ -155,7 +155,7 @@ public final class CSFrom: NSObject {
// MARK: - From
extension From where D: NSManagedObject {
extension From where O: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -104,7 +104,7 @@ public final class CSGroupBy: NSObject, CSQueryClause {
public let bridgeToSwift: GroupBy<NSManagedObject>
public init<D: NSManagedObject>(_ swiftValue: GroupBy<D>) {
public init<O: NSManagedObject>(_ swiftValue: GroupBy<O>) {
self.bridgeToSwift = swiftValue.downcast()
super.init()
@@ -114,7 +114,7 @@ public final class CSGroupBy: NSObject, CSQueryClause {
// MARK: - GroupBy
extension GroupBy where D: NSManagedObject {
extension GroupBy where O: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -112,7 +112,7 @@ public final class CSInto: NSObject {
public let bridgeToSwift: Into<NSManagedObject>
public required init<D: NSManagedObject>(_ swiftValue: Into<D>) {
public required init<O: NSManagedObject>(_ swiftValue: Into<O>) {
self.bridgeToSwift = swiftValue.downcast()
super.init()
@@ -122,7 +122,7 @@ public final class CSInto: NSObject {
// MARK: - Into
extension Into where D: NSManagedObject {
extension Into where O: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -112,7 +112,7 @@ public final class CSOrderBy: NSObject, CSFetchClause, CSQueryClause, CSDeleteCl
public let bridgeToSwift: OrderBy<NSManagedObject>
public init<D: NSManagedObject>(_ swiftValue: OrderBy<D>) {
public init<O: NSManagedObject>(_ swiftValue: OrderBy<O>) {
self.bridgeToSwift = swiftValue.downcast()
super.init()
@@ -122,7 +122,7 @@ public final class CSOrderBy: NSObject, CSFetchClause, CSQueryClause, CSDeleteCl
// MARK: - OrderBy
extension OrderBy where D: NSManagedObject {
extension OrderBy where O: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -76,7 +76,7 @@ public final class CSSectionBy: NSObject {
public let bridgeToSwift: SectionBy<NSManagedObject>
public init<D>(_ swiftValue: SectionBy<D>) {
public init<O>(_ swiftValue: SectionBy<O>) {
self.bridgeToSwift = swiftValue.downcast()
super.init()

View File

@@ -177,7 +177,7 @@ public final class CSSelectTerm: NSObject {
public let bridgeToSwift: SelectTerm<NSManagedObject>
public init<D: NSManagedObject>(_ swiftValue: SelectTerm<D>) {
public init<O: NSManagedObject>(_ swiftValue: SelectTerm<O>) {
self.bridgeToSwift = swiftValue.downcast()
super.init()
@@ -187,7 +187,7 @@ public final class CSSelectTerm: NSObject {
// MARK: - SelectTerm
extension SelectTerm where D: NSManagedObject {
extension SelectTerm where O: NSManagedObject {
// MARK: CoreStoreSwiftType
@@ -383,7 +383,7 @@ public final class CSSelect: NSObject {
// MARK: CoreStoreObjectiveCType
public init<D: NSManagedObject, T: QueryableAttributeType>(_ swiftValue: Select<D, T>) {
public init<O: NSManagedObject, T: QueryableAttributeType>(_ swiftValue: Select<O, T>) {
self.attributeType = T.cs_rawAttributeType
self.selectTerms = swiftValue.selectTerms.map({ $0.downcast() })
@@ -391,7 +391,7 @@ public final class CSSelect: NSObject {
super.init()
}
public init<D: NSManagedObject, T>(_ swiftValue: Select<D, T>) {
public init<O: NSManagedObject, T>(_ swiftValue: Select<O, T>) {
self.attributeType = .undefinedAttributeType
self.selectTerms = swiftValue.selectTerms.map({ $0.downcast() })
@@ -502,7 +502,7 @@ public final class CSSelect: NSObject {
// MARK: - Select
extension Select where D: NSManagedObject {
extension Select where O: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -151,7 +151,7 @@ public final class CSWhere: NSObject, CSFetchClause, CSQueryClause, CSDeleteClau
public let bridgeToSwift: Where<NSManagedObject>
public init<D: NSManagedObject>(_ swiftValue: Where<D>) {
public init<O: NSManagedObject>(_ swiftValue: Where<O>) {
self.bridgeToSwift = swiftValue.downcast()
super.init()
@@ -161,7 +161,7 @@ public final class CSWhere: NSObject, CSFetchClause, CSQueryClause, CSDeleteClau
// MARK: - Where
extension Where where D: NSManagedObject {
extension Where where O: NSManagedObject {
// MARK: CoreStoreSwiftType

View File

@@ -54,50 +54,6 @@ extension AsynchronousDataTransaction: CustomDebugStringConvertible, CoreStoreDe
}
// MARK: - CloudStorageOptions
extension CloudStorageOptions: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
var flags = [String]()
if self.contains(.recreateLocalStoreOnModelMismatch) {
flags.append(".recreateLocalStoreOnModelMismatch")
}
if self.contains(.allowSynchronousLightweightMigration) {
flags.append(".allowSynchronousLightweightMigration")
}
switch flags.count {
case 0:
return "[.none]"
case 1:
return "[.\(flags[0])]"
default:
var string = "[\n"
string.append(flags.joined(separator: ",\n"))
string.indent(1)
string.append("\n]")
return string
}
}
}
// MARK: - CoreStoreError
extension CoreStoreError: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
@@ -417,15 +373,34 @@ fileprivate struct CoreStoreFetchedSectionInfoWrapper: CoreStoreDebugStringConve
var coreStoreDumpString: String {
return createFormattedString(
"\"\(self.sectionInfo.name)\" (", ")",
("numberOfObjects", self.sectionInfo.numberOfObjects),
("indexTitle", self.sectionInfo.indexTitle as Any)
"\"\(self.sectionName)\" (", ")",
("numberOfObjects", self.numberOfObjects),
("indexTitle", self.sectionIndexTitle as Any)
)
}
// MARK: FilePrivate
let sectionInfo: NSFetchedResultsSectionInfo
fileprivate init(_ sectionInfo: NSFetchedResultsSectionInfo) {
self.sectionName = sectionInfo.name
self.numberOfObjects = sectionInfo.numberOfObjects
self.sectionIndexTitle = sectionInfo.indexTitle
}
fileprivate init(_ section: Internals.DiffableDataSourceSnapshot.Section) {
self.sectionName = section.differenceIdentifier
self.numberOfObjects = section.elements.count
self.sectionIndexTitle = nil
}
// MARK: Private
private let sectionName: String
private let sectionIndexTitle: String?
private let numberOfObjects: Int
}
@available(macOS 10.12, *)
@@ -453,6 +428,56 @@ extension ListMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConvert
}
// MARK: - ListPublisher
@available(macOS 10.12, *)
extension ListPublisher: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
return createFormattedString(
"(", ")",
("snapshot", self.snapshot)
)
}
}
// MARK: - ListSnapshot
extension ListSnapshot: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
return createFormattedString(
"(", ")",
("numberOfObjects", self.numberOfItems),
("sections", self.diffableSnapshot.sections.map(CoreStoreFetchedSectionInfoWrapper.init))
)
}
}
// MARK: - LocalStorageOptions
extension LocalStorageOptions: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
@@ -612,6 +637,56 @@ extension ObjectMonitor: CustomDebugStringConvertible, CoreStoreDebugStringConve
}
// MARK: - ObjectPublisher
extension ObjectPublisher: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
return createFormattedString(
"(", ")",
("objectID", self.objectID()),
("object", self.object as Any)
)
}
}
// MARK: - ObjectSnapshot
extension ObjectSnapshot: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
// MARK: CustomDebugStringConvertible
public var debugDescription: String {
return formattedDebugDescription(self)
}
// MARK: CoreStoreDebugStringConvertible
public var coreStoreDumpString: String {
return createFormattedString(
"(", ")",
("objectID", self.objectID()),
("dictionaryForValues", self.dictionaryForValues())
)
}
}
// MARK: - OrderBy
extension OrderBy: CustomDebugStringConvertible, CoreStoreDebugStringConvertible {
@@ -1204,7 +1279,7 @@ extension NSEntityDescription: CoreStoreDebugStringConvertible {
info.append(("compoundIndexes", self.compoundIndexes))
}
if #available(macOS 10.11, *) {
if #available(macOS 10.11, iOS 9.0, *) {
info.append(("uniquenessConstraints", self.uniquenessConstraints))
}

View File

@@ -35,7 +35,7 @@ extension Internals {
@inline(__always)
internal static func log(_ level: LogLevel, message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
Shared.logger.log(
CoreStoreDefaults.logger.log(
level: level,
message: message,
fileName: fileName,
@@ -47,7 +47,7 @@ extension Internals {
@inline(__always)
internal static func log(_ error: CoreStoreError, _ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
Shared.logger.log(
CoreStoreDefaults.logger.log(
error: error,
message: message,
fileName: fileName,
@@ -59,7 +59,7 @@ extension Internals {
@inline(__always)
internal static func assert( _ condition: @autoclosure () -> Bool, _ message: @autoclosure () -> String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
Shared.logger.assert(
CoreStoreDefaults.logger.assert(
condition(),
message: message(),
fileName: fileName,
@@ -71,7 +71,7 @@ extension Internals {
@inline(__always)
internal static func abort(_ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) -> Never {
Shared.logger.abort(
CoreStoreDefaults.logger.abort(
message,
fileName: fileName,
lineNumber: lineNumber,

View File

@@ -33,9 +33,9 @@ import CoreData
extension CoreStore {
/**
Asynchronously adds a `StorageInterface` to the `defaultStack`. Migrations are also initiated by default.
Asynchronously adds a `StorageInterface` to the `CoreStoreDefaults.dataStack`. Migrations are also initiated by default.
```
CoreStore.addStorage(
dataStack.addStorage(
InMemoryStore(configuration: "Config1"),
completion: { result in
switch result {
@@ -50,13 +50,13 @@ extension CoreStore {
*/
public static func addStorage<T>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) {
Shared.defaultStack.addStorage(storage, completion: completion)
CoreStoreDefaults.dataStack.addStorage(storage, completion: completion)
}
/**
Asynchronously adds a `LocalStorage` to the `defaultStack`. Migrations are also initiated by default.
Asynchronously adds a `LocalStorage` to the `CoreStoreDefaults.dataStack`. Migrations are also initiated by default.
```
let migrationProgress = CoreStore.addStorage(
let migrationProgress = dataStack.addStorage(
SQLiteStore(fileName: "core_data.sqlite", configuration: "Config1"),
completion: { result in
switch result {
@@ -72,43 +72,11 @@ extension CoreStore {
*/
public static func addStorage<T: LocalStorage>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) -> Progress? {
return Shared.defaultStack.addStorage(storage, completion: completion)
}
/**
Asynchronously adds a `CloudStorage` to the `defaultStack`. Migrations are also initiated by default.
```
guard let storage = ICloudStore(
ubiquitousContentName: "MyAppCloudData",
ubiquitousContentTransactionLogsSubdirectory: "logs/config1",
ubiquitousContainerID: "iCloud.com.mycompany.myapp.containername",
ubiquitousPeerToken: "9614d658014f4151a95d8048fb717cf0",
configuration: "Config1",
cloudStorageOptions: .recreateLocalStoreOnModelMismatch
) else {
// iCloud is not available on the device
return
}
let migrationProgress = dataStack.addStorage(
storage,
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storage: the cloud storage
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `CloudStorage` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `CloudStorage` was already added at the same URL and with the same configuration.
*/
public static func addStorage<T: CloudStorage>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) {
Shared.defaultStack.addStorage(storage, completion: completion)
return CoreStoreDefaults.dataStack.addStorage(storage, completion: completion)
}
/**
Migrates a local storage to match the `defaultStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
Migrates a local storage to match the `CoreStoreDefaults.dataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
- parameter storage: the local storage
- parameter completion: the closure to be executed on the main queue when the migration completes, either due to success or failure. The closure's `MigrationResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.failure` result if an error occurs asynchronously.
@@ -117,11 +85,11 @@ extension CoreStore {
*/
public static func upgradeStorageIfNeeded<T: LocalStorage>(_ storage: T, completion: @escaping (MigrationResult) -> Void) throws -> Progress? {
return try Shared.defaultStack.upgradeStorageIfNeeded(storage, completion: completion)
return try CoreStoreDefaults.dataStack.upgradeStorageIfNeeded(storage, completion: completion)
}
/**
Checks the migration steps required for the storage to match the `defaultStack`'s managed object model version.
Checks the migration steps required for the storage to match the `CoreStoreDefaults.dataStack`'s managed object model version.
- parameter storage: the local storage
- throws: a `CoreStoreError` value indicating the failure
@@ -129,6 +97,6 @@ extension CoreStore {
*/
public static func requiredMigrationsForStorage<T: LocalStorage>(_ storage: T) throws -> [MigrationType] {
return try Shared.defaultStack.requiredMigrationsForStorage(storage)
return try CoreStoreDefaults.dataStack.requiredMigrationsForStorage(storage)
}
}

View File

@@ -34,44 +34,44 @@ import CoreData
extension CoreStore {
/**
Using the `defaultStack`, creates an `ObjectMonitor` for the specified `DynamicObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
Using the `CoreStoreDefaults.dataStack`, creates an `ObjectMonitor` for the specified `DynamicObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
- parameter object: the `DynamicObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
- returns: an `ObjectMonitor` that monitors changes to `object`
*/
public static func monitorObject<D>(_ object: D) -> ObjectMonitor<D> {
public static func monitorObject<O: DynamicObject>(_ object: O) -> ObjectMonitor<O> {
return Shared.defaultStack.monitorObject(object)
return CoreStoreDefaults.dataStack.monitorObject(object)
}
/**
Using the `defaultStack`, creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
Using the `CoreStoreDefaults.dataStack`, creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorList<D>(_ from: From<D>, _ fetchClauses: FetchClause...) -> ListMonitor<D> {
public static func monitorList<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListMonitor<O> {
return Shared.defaultStack.monitorList(from, fetchClauses)
return CoreStoreDefaults.dataStack.monitorList(from, fetchClauses)
}
/**
Using the `defaultStack`, creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
Using the `CoreStoreDefaults.dataStack`, creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorList<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) -> ListMonitor<D> {
public static func monitorList<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListMonitor<O> {
return Shared.defaultStack.monitorList(from, fetchClauses)
return CoreStoreDefaults.dataStack.monitorList(from, fetchClauses)
}
/**
Creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let monitor = CoreStore.monitorList(
let monitor = dataStack.monitorList(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
@@ -82,38 +82,38 @@ extension CoreStore {
*/
public static func monitorList<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListMonitor<B.ObjectType> {
return Shared.defaultStack.monitorList(clauseChain.from, clauseChain.fetchClauses)
return CoreStoreDefaults.dataStack.monitorList(clauseChain.from, clauseChain.fetchClauses)
}
/**
Using the `defaultStack`, asynchronously creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
Using the `CoreStoreDefaults.dataStack`, asynchronously creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ fetchClauses: FetchClause...) {
public static func monitorList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ fetchClauses: FetchClause...) {
Shared.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
CoreStoreDefaults.dataStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
/**
Using the `defaultStack`, asynchronously creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
Using the `CoreStoreDefaults.dataStack`, asynchronously creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ fetchClauses: [FetchClause]) {
public static func monitorList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ fetchClauses: [FetchClause]) {
Shared.defaultStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
CoreStoreDefaults.dataStack.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
}
/**
Asynchronously creates a `ListMonitor` for a list of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
```
CoreStore.monitorList(
dataStack.monitorList(
createAsynchronously: { (monitor) in
self.monitor = monitor
},
@@ -127,7 +127,7 @@ extension CoreStore {
*/
public static func monitorList<B: FetchChainableBuilderType>(createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void, _ clauseChain: B) {
Shared.defaultStack.monitorList(
CoreStoreDefaults.dataStack.monitorList(
createAsynchronously: createAsynchronously,
clauseChain.from,
clauseChain.fetchClauses
@@ -135,35 +135,35 @@ extension CoreStore {
}
/**
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
Using the `CoreStoreDefaults.dataStack`, creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorSectionedList<D>(_ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: FetchClause...) -> ListMonitor<D> {
public static func monitorSectionedList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListMonitor<O> {
return Shared.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses)
return CoreStoreDefaults.dataStack.monitorSectionedList(from, sectionBy, fetchClauses)
}
/**
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
Using the `CoreStoreDefaults.dataStack`, creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public static func monitorSectionedList<D>(_ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: [FetchClause]) -> ListMonitor<D> {
public static func monitorSectionedList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListMonitor<O> {
return Shared.defaultStack.monitorSectionedList(from, sectionBy, fetchClauses)
return CoreStoreDefaults.dataStack.monitorSectionedList(from, sectionBy, fetchClauses)
}
/**
Creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified `SectionMonitorBuilderType` built from a chain of clauses.
```
let monitor = CoreStore.monitorSectionedList(
let monitor = dataStack.monitorSectionedList(
From<MyPersonEntity>()
.sectionBy(\.age, { "\($0!) years old" })
.where(\.age > 18)
@@ -175,7 +175,7 @@ extension CoreStore {
*/
public static func monitorSectionedList<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListMonitor<B.ObjectType> {
return Shared.defaultStack.monitorSectionedList(
return CoreStoreDefaults.dataStack.monitorSectionedList(
clauseChain.from,
clauseChain.sectionBy,
clauseChain.fetchClauses
@@ -183,35 +183,35 @@ extension CoreStore {
}
/**
Using the `defaultStack`, asynchronously creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
Using the `CoreStoreDefaults.dataStack`, asynchronously creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorSectionedList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: FetchClause...) {
public static func monitorSectionedList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) {
Shared.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
CoreStoreDefaults.dataStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}
/**
Using the `defaultStack`, asynchronously creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
Using the `CoreStoreDefaults.dataStack`, asynchronously creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list. Since `NSFetchedResultsController` greedily locks the persistent store on initial fetch, you may prefer this method instead of the synchronous counterpart to avoid deadlocks while background updates/saves are being executed.
- parameter createAsynchronously: the closure that receives the created `ListMonitor` instance
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public static func monitorSectionedList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: [FetchClause]) {
public static func monitorSectionedList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) {
Shared.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
CoreStoreDefaults.dataStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}
/**
Asynchronously creates a `ListMonitor` for a sectioned list of `DynamicObject`s that satisfy the specified `SectionMonitorBuilderType` built from a chain of clauses.
```
CoreStore.monitorSectionedList(
dataStack.monitorSectionedList(
createAsynchronously: { (monitor) in
self.monitor = monitor
},
@@ -226,7 +226,7 @@ extension CoreStore {
*/
public static func monitorSectionedList<B: SectionMonitorBuilderType>(createAsynchronously: @escaping (ListMonitor<B.ObjectType>) -> Void, _ clauseChain: B) {
Shared.defaultStack.monitorSectionedList(
CoreStoreDefaults.dataStack.monitorSectionedList(
createAsynchronously: createAsynchronously,
clauseChain.from,
clauseChain.sectionBy,

View File

@@ -33,79 +33,79 @@ import CoreData
extension CoreStore {
/**
Using the `defaultStack`, fetches the `DynamicObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
Using the `CoreStoreDefaults.dataStack`, fetches the `DynamicObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
- parameter object: a reference to the object created/fetched outside the `DataStack`
- returns: the `DynamicObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
public static func fetchExisting<D: DynamicObject>(_ object: D) -> D? {
public static func fetchExisting<O: DynamicObject>(_ object: O) -> O? {
return Shared.defaultStack.fetchExisting(object)
return CoreStoreDefaults.dataStack.fetchExisting(object)
}
/**
Using the `defaultStack`, fetches the `DynamicObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
Using the `CoreStoreDefaults.dataStack`, fetches the `DynamicObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `DynamicObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
public static func fetchExisting<D: DynamicObject>(_ objectID: NSManagedObjectID) -> D? {
public static func fetchExisting<O: DynamicObject>(_ objectID: NSManagedObjectID) -> O? {
return Shared.defaultStack.fetchExisting(objectID)
return CoreStoreDefaults.dataStack.fetchExisting(objectID)
}
/**
Using the `defaultStack`, fetches the `DynamicObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
Using the `CoreStoreDefaults.dataStack`, fetches the `DynamicObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
- parameter objects: an array of `DynamicObject`s created/fetched outside the `DataStack`
- returns: the `DynamicObject` array for objects that exists in the `DataStack`
*/
public static func fetchExisting<D: DynamicObject, S: Sequence>(_ objects: S) -> [D] where S.Iterator.Element == D {
public static func fetchExisting<O: DynamicObject, S: Sequence>(_ objects: S) -> [O] where S.Iterator.Element == O {
return Shared.defaultStack.fetchExisting(objects)
return CoreStoreDefaults.dataStack.fetchExisting(objects)
}
/**
Using the `defaultStack`, fetches the `DynamicObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
Using the `CoreStoreDefaults.dataStack`, fetches the `DynamicObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `DynamicObject` array for objects that exists in the `DataStack`
*/
public static func fetchExisting<D: DynamicObject, S: Sequence>(_ objectIDs: S) -> [D] where S.Iterator.Element == NSManagedObjectID {
public static func fetchExisting<O: DynamicObject, S: Sequence>(_ objectIDs: S) -> [O] where S.Iterator.Element == NSManagedObjectID {
return Shared.defaultStack.fetchExisting(objectIDs)
return CoreStoreDefaults.dataStack.fetchExisting(objectIDs)
}
/**
Using the `defaultStack`, fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchOne<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> D? {
public static func fetchOne<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> O? {
return try Shared.defaultStack.fetchOne(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchOne(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchOne<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> D? {
public static func fetchOne<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> O? {
return try Shared.defaultStack.fetchOne(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchOne(from, fetchClauses)
}
/**
Fetches the first `DynamicObject` instance that satisfies the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let youngestTeen = CoreStore.fetchOne(
let youngestTeen = dataStack.fetchOne(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
@@ -117,39 +117,39 @@ extension CoreStore {
*/
public static func fetchOne<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> B.ObjectType? {
return try Shared.defaultStack.fetchOne(clauseChain)
return try CoreStoreDefaults.dataStack.fetchOne(clauseChain)
}
/**
Using the `defaultStack`, fetches all `DynamicObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches all `DynamicObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchAll<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> [D] {
public static func fetchAll<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [O] {
return try Shared.defaultStack.fetchAll(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchAll(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches all `DynamicObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches all `DynamicObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchAll<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> [D] {
public static func fetchAll<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [O] {
return try Shared.defaultStack.fetchAll(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchAll(from, fetchClauses)
}
/**
Fetches all `DynamicObject` instances that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let people = CoreStore.fetchAll(
let people = dataStack.fetchAll(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
@@ -161,39 +161,39 @@ extension CoreStore {
*/
public static func fetchAll<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> [B.ObjectType] {
return try Shared.defaultStack.fetchAll(clauseChain)
return try CoreStoreDefaults.dataStack.fetchAll(clauseChain)
}
/**
Using the `defaultStack`, fetches the number of `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches the number of `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchCount<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> Int {
public static func fetchCount<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> Int {
return try Shared.defaultStack.fetchCount(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchCount(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the number of `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches the number of `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchCount<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> Int {
public static func fetchCount<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> Int {
return try Shared.defaultStack.fetchCount(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchCount(from, fetchClauses)
}
/**
Fetches the number of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let numberOfAdults = CoreStore.fetchCount(
let numberOfAdults = dataStack.fetchCount(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
@@ -205,39 +205,39 @@ extension CoreStore {
*/
public static func fetchCount<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> Int {
return try Shared.defaultStack.fetchCount(clauseChain)
return try CoreStoreDefaults.dataStack.fetchCount(clauseChain)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectID<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID? {
public static func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID? {
return try Shared.defaultStack.fetchObjectID(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchObjectID(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectID<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID? {
public static func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID? {
return try Shared.defaultStack.fetchObjectID(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchObjectID(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let youngestTeenID = CoreStore.fetchObjectID(
let youngestTeenID = dataStack.fetchObjectID(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
@@ -249,33 +249,33 @@ extension CoreStore {
*/
public static func fetchObjectID<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> NSManagedObjectID? {
return try Shared.defaultStack.fetchObjectID(clauseChain)
return try CoreStoreDefaults.dataStack.fetchObjectID(clauseChain)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectIDs<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID] {
public static func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID] {
return try Shared.defaultStack.fetchObjectIDs(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchObjectIDs(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func fetchObjectIDs<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID] {
public static func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID] {
return try Shared.defaultStack.fetchObjectIDs(from, fetchClauses)
return try CoreStoreDefaults.dataStack.fetchObjectIDs(from, fetchClauses)
}
/**
@@ -293,11 +293,11 @@ extension CoreStore {
*/
public static func fetchObjectIDs<B: FetchChainableBuilderType>(_ clauseChain: B) throws -> [NSManagedObjectID] {
return try Shared.defaultStack.fetchObjectIDs(clauseChain)
return try CoreStoreDefaults.dataStack.fetchObjectIDs(clauseChain)
}
/**
Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
@@ -307,13 +307,13 @@ extension CoreStore {
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryValue<D, U: QueryableAttributeType>(_ from: From<D>, _ selectClause: Select<D, U>, _ queryClauses: QueryClause...) throws -> U? {
public static func queryValue<O, U: QueryableAttributeType>(_ from: From<O>, _ selectClause: Select<O, U>, _ queryClauses: QueryClause...) throws -> U? {
return try Shared.defaultStack.queryValue(from, selectClause, queryClauses)
return try CoreStoreDefaults.dataStack.queryValue(from, selectClause, queryClauses)
}
/**
Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
@@ -323,9 +323,9 @@ extension CoreStore {
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryValue<D, U: QueryableAttributeType>(_ from: From<D>, _ selectClause: Select<D, U>, _ queryClauses: [QueryClause]) throws -> U? {
public static func queryValue<O, U: QueryableAttributeType>(_ from: From<O>, _ selectClause: Select<O, U>, _ queryClauses: [QueryClause]) throws -> U? {
return try Shared.defaultStack.queryValue(from, selectClause, queryClauses)
return try CoreStoreDefaults.dataStack.queryValue(from, selectClause, queryClauses)
}
/**
@@ -333,7 +333,7 @@ extension CoreStore {
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
```
let averageAdultAge = CoreStore.queryValue(
let averageAdultAge = dataStack.queryValue(
From<MyPersonEntity>()
.select(Int.self, .average(\.age))
.where(\.age > 18)
@@ -345,11 +345,11 @@ extension CoreStore {
*/
public static func queryValue<B: QueryChainableBuilderType>(_ clauseChain: B) throws -> B.ResultType? where B.ResultType: QueryableAttributeType {
return try Shared.defaultStack.queryValue(clauseChain)
return try CoreStoreDefaults.dataStack.queryValue(clauseChain)
}
/**
Using the `defaultStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
@@ -359,13 +359,13 @@ extension CoreStore {
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryAttributes<D>(_ from: From<D>, _ selectClause: Select<D, NSDictionary>, _ queryClauses: QueryClause...) throws -> [[String: Any]] {
public static func queryAttributes<O>(_ from: From<O>, _ selectClause: Select<O, NSDictionary>, _ queryClauses: QueryClause...) throws -> [[String: Any]] {
return try Shared.defaultStack.queryAttributes(from, selectClause, queryClauses)
return try CoreStoreDefaults.dataStack.queryAttributes(from, selectClause, queryClauses)
}
/**
Using the `defaultStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
Using the `CoreStoreDefaults.dataStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
@@ -375,9 +375,9 @@ extension CoreStore {
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public static func queryAttributes<D>(_ from: From<D>, _ selectClause: Select<D, NSDictionary>, _ queryClauses: [QueryClause]) throws -> [[String: Any]] {
public static func queryAttributes<O>(_ from: From<O>, _ selectClause: Select<O, NSDictionary>, _ queryClauses: [QueryClause]) throws -> [[String: Any]] {
return try Shared.defaultStack.queryAttributes(from, selectClause, queryClauses)
return try CoreStoreDefaults.dataStack.queryAttributes(from, selectClause, queryClauses)
}
/**
@@ -385,7 +385,7 @@ extension CoreStore {
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
```
let results = CoreStore.queryAttributes(
let results = dataStack.queryAttributes(
From<MyPersonEntity>()
.select(
NSDictionary.self,
@@ -406,6 +406,6 @@ extension CoreStore {
*/
public static func queryAttributes<B: QueryChainableBuilderType>(_ clauseChain: B) throws -> [[String: Any]] where B.ResultType == NSDictionary {
return try Shared.defaultStack.queryAttributes(clauseChain)
return try CoreStoreDefaults.dataStack.queryAttributes(clauseChain)
}
}

View File

@@ -33,111 +33,85 @@ import CoreData
extension CoreStore {
/**
Returns the `defaultStack`'s model version. The version string is the same as the name of a version-specific .xcdatamodeld file or `CoreStoreSchema`.
Returns the `CoreStoreDefaults.dataStack`'s model version. The version string is the same as the name of a version-specific .xcdatamodeld file or `CoreStoreSchema`.
*/
public static var modelVersion: String {
return Shared.defaultStack.modelVersion
return CoreStoreDefaults.dataStack.modelVersion
}
/**
Returns the entity name-to-class type mapping from the `defaultStack`'s model.
Returns the entity name-to-class type mapping from the `CoreStoreDefaults.dataStack`'s model.
*/
public static func entityTypesByName(for type: NSManagedObject.Type) -> [EntityName: NSManagedObject.Type] {
return Shared.defaultStack.entityTypesByName(for: type)
return CoreStoreDefaults.dataStack.entityTypesByName(for: type)
}
/**
Returns the entity name-to-class type mapping from the `defaultStack`'s model.
Returns the entity name-to-class type mapping from the `CoreStoreDefaults.dataStack`'s model.
*/
public static func entityTypesByName(for type: CoreStoreObject.Type) -> [EntityName: CoreStoreObject.Type] {
return Shared.defaultStack.entityTypesByName(for: type)
return CoreStoreDefaults.dataStack.entityTypesByName(for: type)
}
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `defaultStack`'s model.
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `CoreStoreDefaults.dataStack`'s model.
*/
public static func entityDescription(for type: NSManagedObject.Type) -> NSEntityDescription? {
return Shared.defaultStack.entityDescription(for: type)
return CoreStoreDefaults.dataStack.entityDescription(for: type)
}
/**
Returns the `NSEntityDescription` for the specified `CoreStoreObject` subclass from `defaultStack`'s model.
Returns the `NSEntityDescription` for the specified `CoreStoreObject` subclass from `CoreStoreDefaults.dataStack`'s model.
*/
public static func entityDescription(for type: CoreStoreObject.Type) -> NSEntityDescription? {
return Shared.defaultStack.entityDescription(for: type)
return CoreStoreDefaults.dataStack.entityDescription(for: type)
}
/**
Creates an `SQLiteStore` with default parameters and adds it to the `defaultStack`. This method blocks until completion.
Creates an `SQLiteStore` with default parameters and adds it to the `CoreStoreDefaults.dataStack`. This method blocks until completion.
```
try CoreStore.addStorageAndWait()
```
- returns: the local SQLite storage added to the `defaultStack`
- returns: the local SQLite storage added to the `CoreStoreDefaults.dataStack`
*/
@discardableResult
public static func addStorageAndWait() throws -> SQLiteStore {
return try Shared.defaultStack.addStorageAndWait(SQLiteStore())
return try CoreStoreDefaults.dataStack.addStorageAndWait(SQLiteStore())
}
/**
Adds a `StorageInterface` to the `defaultStack` and blocks until completion.
Adds a `StorageInterface` to the `CoreStoreDefaults.dataStack` and blocks until completion.
```
try CoreStore.addStorageAndWait(InMemoryStore(configuration: "Config1"))
```
- parameter storage: the `StorageInterface`
- throws: a `CoreStoreError` value indicating the failure
- returns: the `StorageInterface` added to the `defaultStack`
- returns: the `StorageInterface` added to the `CoreStoreDefaults.dataStack`
*/
@discardableResult
public static func addStorageAndWait<T: StorageInterface>(_ storage: T) throws -> T {
return try Shared.defaultStack.addStorageAndWait(storage)
return try CoreStoreDefaults.dataStack.addStorageAndWait(storage)
}
/**
Adds a `LocalStorage` to the `defaultStack` and blocks until completion.
Adds a `LocalStorage` to the `CoreStoreDefaults.dataStack` and blocks until completion.
```
try CoreStore.addStorageAndWait(SQLiteStore(configuration: "Config1"))
```
- parameter storage: the local storage
- throws: a `CoreStoreError` value indicating the failure
- returns: the local storage added to the `defaultStack`. Note that this may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
- returns: the local storage added to the `CoreStoreDefaults.dataStack`. Note that this may not always be the same instance as the parameter argument if a previous `LocalStorage` was already added at the same URL and with the same configuration.
*/
@discardableResult
public static func addStorageAndWait<T: LocalStorage>(_ storage: T) throws -> T {
return try Shared.defaultStack.addStorageAndWait(storage)
}
/**
Adds a `CloudStorage` to the `defaultStack` and blocks until completion.
```
guard let storage = ICloudStore(
ubiquitousContentName: "MyAppCloudData",
ubiquitousContentTransactionLogsSubdirectory: "logs/config1",
ubiquitousContainerID: "iCloud.com.mycompany.myapp.containername",
ubiquitousPeerToken: "9614d658014f4151a95d8048fb717cf0",
configuration: "Config1",
cloudStorageOptions: .recreateLocalStoreOnModelMismatch
) else {
// iCloud is not available on the device
return
}
try CoreStore.addStorageAndWait(storage)
```
- parameter storage: the local storage
- throws: a `CoreStoreError` value indicating the failure
- returns: the cloud storage added to the stack. Note that this may not always be the same instance as the parameter argument if a previous `CloudStorage` was already added at the same URL and with the same configuration.
*/
@discardableResult
public static func addStorageAndWait<T: CloudStorage>(_ storage: T) throws -> T {
return try Shared.defaultStack.addStorageAndWait(storage)
return try CoreStoreDefaults.dataStack.addStorageAndWait(storage)
}
}

View File

@@ -32,18 +32,18 @@ import Foundation
extension CoreStore {
/**
Using the `defaultStack`, performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the wrapped as `.success(T)` in the `completion`'s `Result<T>`. Any errors thrown from inside the `task` will be reported as `.failure(CoreStoreError)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
Using the `CoreStoreDefaults.dataStack`, performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the wrapped as `.success(T)` in the `completion`'s `Result<T>`. Any errors thrown from inside the `task` will be reported as `.failure(CoreStoreError)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter completion: the closure executed after the save completes. The `Result` argument of the closure will either wrap the return value of `task`, or any uncaught errors thrown from within `task`. Cancelled `task`s will be indicated by `.failure(error: CoreStoreError.userCancelled)`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
*/
public static func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void) {
Shared.defaultStack.perform(asynchronous: task, completion: completion)
CoreStoreDefaults.dataStack.perform(asynchronous: task, completion: completion)
}
/**
Using the `defaultStack`, performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the argument of the `success` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported in the `failure` closure. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
Using the `CoreStoreDefaults.dataStack`, performs a transaction asynchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the argument of the `success` closure. Any errors thrown from inside the `task` will be wrapped in a `CoreStoreError` and reported in the `failure` closure. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the asynchronous closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter success: the closure executed after the save succeeds. The `T` argument of the closure will be the value returned from `task`.
@@ -51,11 +51,11 @@ extension CoreStore {
*/
public static func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) {
Shared.defaultStack.perform(asynchronous: task, success: success, failure: failure)
CoreStoreDefaults.dataStack.perform(asynchronous: task, success: success, failure: failure)
}
/**
Using the `defaultStack`, performs a transaction synchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be thrown from `perform(synchronous:)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
Using the `CoreStoreDefaults.dataStack`, performs a transaction synchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be thrown from `perform(synchronous:)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- parameter task: the synchronous non-escaping closure where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- parameter waitForAllObservers: When `true`, this method waits for all observers to be notified of the changes before returning. This results in more predictable data update order, but may risk triggering deadlocks. When `false`, this method does not wait for observers to be notified of the changes before returning. This results in lower risk for deadlocks, but the updated data may not have been propagated to the `DataStack` after returning. Defaults to `true`.
@@ -64,25 +64,25 @@ extension CoreStore {
*/
public static func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T {
return try Shared.defaultStack.perform(synchronous: task, waitForAllObservers: waitForAllObservers)
return try CoreStoreDefaults.dataStack.perform(synchronous: task, waitForAllObservers: waitForAllObservers)
}
/**
Using the `defaultStack`, begins a non-contiguous transaction where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
Using the `CoreStoreDefaults.dataStack`, begins a non-contiguous transaction where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
- prameter supportsUndo: `undo()`, `redo()`, and `rollback()` methods are only available when this parameter is `true`, otherwise those method will raise an exception. Defaults to `false`. Note that turning on Undo support may heavily impact performance especially on iOS or watchOS where memory is limited.
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
*/
public static func beginUnsafe(supportsUndo: Bool = false) -> UnsafeDataTransaction {
return Shared.defaultStack.beginUnsafe(supportsUndo: supportsUndo)
return CoreStoreDefaults.dataStack.beginUnsafe(supportsUndo: supportsUndo)
}
/**
Refreshes all registered objects `NSManagedObject`s or `CoreStoreObject`s in the `defaultStack`.
Refreshes all registered objects `NSManagedObject`s or `CoreStoreObject`s in the `CoreStoreDefaults.dataStack`.
*/
public static func refreshAndMergeAllObjects() {
Shared.defaultStack.refreshAndMergeAllObjects()
CoreStoreDefaults.dataStack.refreshAndMergeAllObjects()
}
}

View File

@@ -34,17 +34,17 @@ import CoreData
@available(*, deprecated, message: "Call methods directly from the DataStack instead")
public enum CoreStore {
@available(*, unavailable, renamed: "Shared.logger")
@available(*, unavailable, renamed: "CoreStoreDefaults.logger")
public static var logger: CoreStoreLogger {
get { return Shared.logger }
set { Shared.logger = newValue }
get { return CoreStoreDefaults.logger }
set { CoreStoreDefaults.logger = newValue }
}
@available(*, unavailable, renamed: "Shared.defaultStack")
@available(*, unavailable, renamed: "CoreStoreDefaults.dataStack")
public static var defaultStack: DataStack {
get { return Shared.defaultStack }
set { Shared.defaultStack = newValue }
get { return CoreStoreDefaults.dataStack }
set { CoreStoreDefaults.dataStack = newValue }
}
}

View File

@@ -1,5 +1,5 @@
//
// Shared.swift
// CoreStoreDefaults.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
@@ -26,20 +26,25 @@
import Foundation
// MARK: - Shared
// MARK: - CoreStoreDefaults
/**
Global utilities
*/
public enum Shared {
public enum CoreStoreDefaults {
/**
The `CoreStoreLogger` instance to be used. The default logger is an instance of a `DefaultLogger`.
*/
public static var logger: CoreStoreLogger = DefaultLogger()
@available(*, deprecated, message: "Call methods directly from the DataStack instead")
public static var defaultStack: DataStack {
/**
The default `DataStack` instance to be used. If `defaultStack` is not set during the first time accessed, a default-configured `DataStack` will be created.
- SeeAlso: `DataStack`
- Note: Changing `dataStack` is thread safe, but it is recommended to setup `DataStacks` on a common queue (e.g. the main queue).
- Important: If `dataStack` is not set during the first time accessed, a default-configured `DataStack` will be created.
*/
public static var dataStack: DataStack {
get {

View File

@@ -43,7 +43,7 @@ public enum LogLevel {
// MARK: - CoreStoreLogger
/**
Custom loggers should implement the `CoreStoreLogger` protocol and pass its instance to `CoreStore.logger`. Calls to `log(...)`, `assert(...)`, and `abort(...)` are not tied to a specific queue/thread, so it is the implementer's job to handle thread-safety.
Custom loggers should implement the `CoreStoreLogger` protocol and pass its instance to `CoreStoreDefaults.logger`. Calls to `log(...)`, `assert(...)`, and `abort(...)` are not tied to a specific queue/thread, so it is the implementer's job to handle thread-safety.
*/
public protocol CoreStoreLogger {

View File

@@ -0,0 +1,29 @@
//
// CoreStoreObject+DataSources.swift
// CoreStore iOS
//
// Created by John Estropia on 2019/10/04.
// Copyright © 2019 John Rommel Estropia. All rights reserved.
//
#if canImport(UIKit) || canImport(AppKit)
#if canImport(Combine)
import Combine
// MARK: - ListPublisher: ObservableObject
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension CoreStoreObject: ObservableObject {
// MARK: ObservableObject
public var objectWillChange: ObservableObjectPublisher {
return self.cs_toRaw().objectWillChange
}
}
#endif
#endif

View File

@@ -33,7 +33,7 @@ extension ValueContainer.Required {
/**
Creates a `Where` clause by comparing if a property is equal to a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.nickname == "John" }))
let person = dataStack.fetchOne(From<Person>().where({ $0.nickname == "John" }))
```
*/
public static func == (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where<O> {
@@ -44,7 +44,7 @@ extension ValueContainer.Required {
/**
Creates a `Where` clause by comparing if a property is not equal to a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.nickname != "John" }))
let person = dataStack.fetchOne(From<Person>().where({ $0.nickname != "John" }))
```
*/
public static func != (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where<O> {
@@ -55,7 +55,7 @@ extension ValueContainer.Required {
/**
Creates a `Where` clause by comparing if a property is less than a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.age < 20 }))
let person = dataStack.fetchOne(From<Person>().where({ $0.age < 20 }))
```
*/
public static func < (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where<O> {
@@ -66,7 +66,7 @@ extension ValueContainer.Required {
/**
Creates a `Where` clause by comparing if a property is greater than a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.age > 20 }))
let person = dataStack.fetchOne(From<Person>().where({ $0.age > 20 }))
```
*/
public static func > (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where<O> {
@@ -77,7 +77,7 @@ extension ValueContainer.Required {
/**
Creates a `Where` clause by comparing if a property is less than or equal to a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.age <= 20 }))
let person = dataStack.fetchOne(From<Person>().where({ $0.age <= 20 }))
```
*/
public static func <= (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where<O> {
@@ -88,7 +88,7 @@ extension ValueContainer.Required {
/**
Creates a `Where` clause by comparing if a property is greater than or equal to a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.age >= 20 }))
let person = dataStack.fetchOne(From<Person>().where({ $0.age >= 20 }))
```
*/
public static func >= (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where<O> {
@@ -99,7 +99,7 @@ extension ValueContainer.Required {
/**
Creates a `Where` clause by checking if a sequence contains the value of a property
```
let dog = CoreStore.fetchOne(From<Dog>().where({ ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname }))
let dog = dataStack.fetchOne(From<Dog>().where({ ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname }))
```
*/
public static func ~= <S: Sequence>(_ sequence: S, _ attribute: ValueContainer<O>.Required<V>) -> Where<O> where S.Iterator.Element == V {
@@ -116,7 +116,7 @@ extension ValueContainer.Optional {
/**
Creates a `Where` clause by comparing if a property is equal to a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.nickname == "John" }))
let person = dataStack.fetchOne(From<Person>().where({ $0.nickname == "John" }))
```
*/
public static func == (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where<O> {
@@ -127,7 +127,7 @@ extension ValueContainer.Optional {
/**
Creates a `Where` clause by comparing if a property is not equal to a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.nickname != "John" }))
let person = dataStack.fetchOne(From<Person>().where({ $0.nickname != "John" }))
```
*/
public static func != (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where<O> {
@@ -138,7 +138,7 @@ extension ValueContainer.Optional {
/**
Creates a `Where` clause by comparing if a property is less than a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.age < 20 }))
let person = dataStack.fetchOne(From<Person>().where({ $0.age < 20 }))
```
*/
public static func < (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where<O> {
@@ -156,7 +156,7 @@ extension ValueContainer.Optional {
/**
Creates a `Where` clause by comparing if a property is greater than a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.age > 20 }))
let person = dataStack.fetchOne(From<Person>().where({ $0.age > 20 }))
```
*/
public static func > (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where<O> {
@@ -174,7 +174,7 @@ extension ValueContainer.Optional {
/**
Creates a `Where` clause by comparing if a property is less than or equal to a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.age <= 20 }))
let person = dataStack.fetchOne(From<Person>().where({ $0.age <= 20 }))
```
*/
public static func <= (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where<O> {
@@ -192,7 +192,7 @@ extension ValueContainer.Optional {
/**
Creates a `Where` clause by comparing if a property is greater than or equal to a value
```
let person = CoreStore.fetchOne(From<Person>().where({ $0.age >= 20 }))
let person = dataStack.fetchOne(From<Person>().where({ $0.age >= 20 }))
```
*/
public static func >= (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where<O> {
@@ -210,7 +210,7 @@ extension ValueContainer.Optional {
/**
Creates a `Where` clause by checking if a sequence contains the value of a property
```
let dog = CoreStore.fetchOne(From<Dog>().where({ ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname }))
let dog = dataStack.fetchOne(From<Dog>().where({ ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname }))
```
*/
public static func ~= <S: Sequence>(_ sequence: S, _ attribute: ValueContainer<O>.Optional<V>) -> Where<O> where S.Iterator.Element == V {
@@ -227,7 +227,7 @@ extension RelationshipContainer.ToOne {
/**
Creates a `Where` clause by comparing if a property is equal to a value
```
let dog = CoreStore.fetchOne(From<Dog>().where({ $0.master == me }))
let dog = dataStack.fetchOne(From<Dog>().where({ $0.master == me }))
```
*/
public static func == (_ relationship: RelationshipContainer<O>.ToOne<D>, _ object: D?) -> Where<O> {
@@ -238,7 +238,7 @@ extension RelationshipContainer.ToOne {
/**
Creates a `Where` clause by comparing if a property is not equal to a value
```
let dog = CoreStore.fetchOne(From<Dog>().where({ $0.master != me }))
let dog = dataStack.fetchOne(From<Dog>().where({ $0.master != me }))
```
*/
public static func != (_ relationship: RelationshipContainer<O>.ToOne<D>, _ object: D?) -> Where<O> {
@@ -249,7 +249,7 @@ extension RelationshipContainer.ToOne {
/**
Creates a `Where` clause by checking if a sequence contains the value of a property
```
let dog = CoreStore.fetchOne(From<Dog>().where({ [john, joe, bob] ~= $0.master }))
let dog = dataStack.fetchOne(From<Dog>().where({ [john, joe, bob] ~= $0.master }))
```
*/
public static func ~= <S: Sequence>(_ sequence: S, _ relationship: RelationshipContainer<O>.ToOne<D>) -> Where<O> where S.Iterator.Element == D {

View File

@@ -45,7 +45,7 @@ import Foundation
```
`CoreStoreObject` entities for a model version should be added to `CoreStoreSchema` instance.
```
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
@@ -63,7 +63,7 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
/**
Do not call this directly. This is exposed as public only as a required initializer.
- Important: subclasses that need a custom initializer should override both `init(_:)` and `init(asMeta:)`, and to call their corresponding super implementations.
- Important: subclasses that need a custom initializer should override both `init(rawObject:)` and `init(asMeta:)`, and to call their corresponding super implementations.
*/
public required init(rawObject: NSManagedObject) {
@@ -77,7 +77,7 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
/**
Do not call this directly. This is exposed as public only as a required initializer.
- Important: subclasses that need a custom initializer should override both `init(_:)` and `init(asMeta:)`, and to call their corresponding super implementations.
- Important: subclasses that need a custom initializer should override both `init(rawObject:)` and `init(asMeta:)`, and to call their corresponding super implementations.
*/
public required init(asMeta: Void) {
@@ -116,6 +116,27 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
internal let rawObject: CoreStoreManagedObject?
internal let isMeta: Bool
internal class func metaProperties(includeSuperclasses: Bool) -> [PropertyProtocol] {
func keyPaths(_ allKeyPaths: inout [PropertyProtocol], for dynamicType: CoreStoreObject.Type) {
allKeyPaths.append(contentsOf: dynamicType.meta.propertyProtocolsByName())
guard
includeSuperclasses,
case let superType as CoreStoreObject.Type = (dynamicType as AnyClass).superclass(),
superType != CoreStoreObject.self
else {
return
}
keyPaths(&allKeyPaths, for: superType)
}
var allKeyPaths: [PropertyProtocol] = []
keyPaths(&allKeyPaths, for: self)
return allKeyPaths
}
// MARK: Private
@@ -144,6 +165,22 @@ open /*abstract*/ class CoreStoreObject: DynamicObject, Hashable {
}
}
}
private func propertyProtocolsByName() -> [PropertyProtocol] {
Internals.assert(self.isMeta, "'propertyProtocolsByName()' accessed from non-meta instance of \(Internals.typeName(self))")
let cacheKey = ObjectIdentifier(Self.self)
if let properties = Static.propertiesCache[cacheKey] {
return properties
}
let values: [PropertyProtocol] = Mirror(reflecting: self)
.children
.compactMap({ $0.value as? PropertyProtocol })
Static.propertiesCache[cacheKey] = values
return values
}
}
@@ -187,4 +224,5 @@ fileprivate enum Static {
// MARK: FilePrivate
fileprivate static var metaCache: [ObjectIdentifier: Any] = [:]
fileprivate static var propertiesCache: [ObjectIdentifier: [PropertyProtocol]] = [:]
}

View File

@@ -43,7 +43,7 @@ import Foundation
let pet = Relationship.ToOne<Animal>("pet", inverse: { $0.master })
}
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
@@ -76,7 +76,7 @@ public final class CoreStoreSchema: DynamicSchema {
let pet = Relationship.ToOne<Animal>("pet", inverse: { $0.master })
}
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
@@ -120,7 +120,7 @@ public final class CoreStoreSchema: DynamicSchema {
let name = Value.Required<String>("name", initial: "")
}
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entityConfigurations: [
@@ -283,9 +283,9 @@ public final class CoreStoreSchema: DynamicSchema {
func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] {
var propertyDescriptions: [NSPropertyDescription] = []
for child in Mirror(reflecting: type.meta).children {
for property in type.metaProperties(includeSuperclasses: false) {
switch child.value {
switch property {
case let attribute as AttributeProtocol:
Internals.assert(
@@ -378,9 +378,10 @@ public final class CoreStoreSchema: DynamicSchema {
for (entity, entityDescription) in entityDescriptionsByEntity {
let relationshipsByName = relationshipsByNameByEntity[entity]!
for child in Mirror(reflecting: (entity.type as! CoreStoreObject.Type).meta).children {
let entityType = entity.type as! CoreStoreObject.Type
for property in entityType.metaProperties(includeSuperclasses: false) {
switch child.value {
switch property {
case let relationship as RelationshipProtocol:
let (destinationType, destinationKeyPath) = relationship.inverse
@@ -451,7 +452,7 @@ public final class CoreStoreSchema: DynamicSchema {
}
for (entity, entityDescription) in entityDescriptionsByEntity {
if #available(macOS 10.11, *) {
if #available(macOS 10.11, iOS 9.0, *) {
let uniqueConstraints = entity.uniqueConstraints.filter({ !$0.isEmpty })
if !uniqueConstraints.isEmpty {

View File

@@ -0,0 +1,239 @@
//
// DataStack+DataSources.swift
// CoreStore iOS
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
import CoreData
// MARK: - DataStack
extension DataStack {
/**
Creates an `ObjectPublisher` for the specified `DynamicObject`. Multiple objects may then register themselves to be notified when changes are made to the `DynamicObject`.
- parameter object: the `DynamicObject` to observe changes from
- returns: an `ObjectPublisher` that broadcasts changes to `object`
*/
public func publishObject<O: DynamicObject>(_ object: O) -> ObjectPublisher<O> {
return self.publishObject(object.cs_id())
}
/**
Creates an `ObjectPublisher` for a `DynamicObject` with the specified `ObjectID`. Multiple objects may then register themselves to be notified when changes are made to the `DynamicObject`.
- parameter objectID: the `ObjectID` of the object to observe changes from
- returns: an `ObjectPublisher` that broadcasts changes to `object`
*/
public func publishObject<O: DynamicObject>(_ objectID: O.ObjectID) -> ObjectPublisher<O> {
let context = self.unsafeContext()
return context.objectPublisher(objectID: objectID)
}
/**
Creates a `ListPublisher` for the specified `From` and `FetchClause`s. Multiple objects may then register themselves to be notified when changes are made to the fetched results.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
return self.publishList(from, fetchClauses)
}
/**
Creates a `ListPublisher` for the specified `From` and `FetchClause`s. Multiple objects may then register themselves to be notified when changes are made to the fetched results.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
return ListPublisher(
dataStack: self,
from: from,
sectionBy: nil,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(ListPublisher<O>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<O>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
}
)
}
/**
Creates a `ListPublisher` that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let listPublisher = dataStack.listPublisher(
From<MyPersonEntity>()
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
Multiple objects may then register themselves to be notified when changes are made to the fetched results.
```
listPublisher.addObserver(self) { (listPublisher) in
// handle changes
}
```
- parameter clauseChain: a `FetchChainableBuilderType` built from a chain of clauses
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(
clauseChain.from,
clauseChain.fetchClauses
)
}
/**
Creates a `ListPublisher` for a sectioned list that satisfy the fetch clauses. Multiple objects may then register themselves to be notified when changes are made to the fetched results.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
return self.publishList(
from,
sectionBy,
fetchClauses
)
}
/**
Creates a `ListPublisher` for a sectioned list that satisfy the fetch clauses. Multiple objects may then register themselves to be notified when changes are made to the fetched results.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
return ListPublisher(
dataStack: self,
from: from,
sectionBy: sectionBy,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(ListPublisher<O>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<O>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
}
)
}
/**
Creates a `ListPublisher` for a sectioned list that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
```
let listPublisher = dataStack.listPublisher(
From<MyPersonEntity>()
.sectionBy(\.age, { "\($0!) years old" })
.where(\.age > 18)
.orderBy(.ascending(\.age))
)
```
Multiple objects may then register themselves to be notified when changes are made to the fetched results.
```
listPublisher.addObserver(self) { (listPublisher) in
// handle changes
}
```
- parameter clauseChain: a `SectionMonitorBuilderType` built from a chain of clauses
- returns: a `ListPublisher` that broadcasts changes to the fetched results
*/
public func publishList<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(
clauseChain.from,
clauseChain.sectionBy,
clauseChain.fetchClauses
)
}
// MARK: - Deprecated
@available(*, deprecated, renamed: "publishObject(_:)")
public func objectPublisher<O: DynamicObject>(_ object: O) -> ObjectPublisher<O> {
return self.publishObject(object)
}
@available(*, deprecated, renamed: "publishList(_:_:)")
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
return self.publishList(from, fetchClauses)
}
@available(*, deprecated, renamed: "publishList(_:_:)")
public func listPublisher<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
return self.publishList(from, fetchClauses)
}
@available(*, deprecated, renamed: "publishList(_:)")
public func listPublisher<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(clauseChain)
}
@available(*, deprecated, renamed: "publishList(_:_:_:)")
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListPublisher<O> {
return self.publishList(from, sectionBy, fetchClauses)
}
@available(*, deprecated, renamed: "publishList(_:_:_:)")
public func listPublisher<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListPublisher<O> {
return self.publishList(from, sectionBy, fetchClauses)
}
@available(*, deprecated, renamed: "publishList(_:)")
public func listPublisher<B: SectionMonitorBuilderType>(_ clauseChain: B) -> ListPublisher<B.ObjectType> {
return self.publishList(clauseChain)
}
}
#endif

View File

@@ -248,142 +248,6 @@ extension DataStack {
}
}
/**
Asynchronously adds a `CloudStorage` to the stack. Migrations are also initiated by default.
```
guard let storage = ICloudStore(
ubiquitousContentName: "MyAppCloudData",
ubiquitousContentTransactionLogsSubdirectory: "logs/config1",
ubiquitousContainerID: "iCloud.com.mycompany.myapp.containername",
ubiquitousPeerToken: "9614d658014f4151a95d8048fb717cf0",
configuration: "Config1",
cloudStorageOptions: .recreateLocalStoreOnModelMismatch
) else {
// iCloud is not available on the device
return
}
dataStack.addStorage(
storage,
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storage: the cloud storage
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `SetupResult` argument indicates the result. Note that the `CloudStorage` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `CloudStorage` was already added at the same URL and with the same configuration.
*/
public func addStorage<T: CloudStorage>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) {
let cacheFileURL = storage.cacheFileURL
self.coordinator.performSynchronously {
if let _ = self.persistentStoreForStorage(storage) {
DispatchQueue.main.async {
completion(.success(storage))
}
return
}
if let persistentStore = self.coordinator.persistentStore(for: cacheFileURL as URL) {
if let existingStorage = persistentStore.storageInterface as? T,
storage.matchesPersistentStore(persistentStore) {
DispatchQueue.main.async {
completion(.success(existingStorage))
}
return
}
let error = CoreStoreError.differentStorageExistsAtURL(existingPersistentStoreURL: cacheFileURL)
Internals.log(
error,
"Failed to add \(Internals.typeName(storage)) at \"\(cacheFileURL)\" because a different \(Internals.typeName(NSPersistentStore.self)) at that URL already exists."
)
DispatchQueue.main.async {
completion(.failure(error))
}
return
}
do {
var cloudStorageOptions = storage.cloudStorageOptions
cloudStorageOptions.remove(.recreateLocalStoreOnModelMismatch)
let storeOptions = storage.dictionary(forOptions: cloudStorageOptions)
do {
_ = try self.createPersistentStoreFromStorage(
storage,
finalURL: cacheFileURL,
finalStoreOptions: storeOptions
)
DispatchQueue.main.async {
completion(.success(storage))
}
}
catch let error as NSError where storage.cloudStorageOptions.contains(.recreateLocalStoreOnModelMismatch) && error.isCoreDataMigrationError {
let finalStoreOptions = storage.dictionary(forOptions: storage.cloudStorageOptions)
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
ofType: type(of: storage).storeType,
at: cacheFileURL,
options: storeOptions
)
_ = try self.schemaHistory
.schema(for: metadata)
.flatMap({ try storage.cs_eraseStorageAndWait(soureModel: $0.rawModel()) })
_ = try self.createPersistentStoreFromStorage(
storage,
finalURL: cacheFileURL,
finalStoreOptions: finalStoreOptions
)
}
}
catch let error as NSError
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
do {
_ = try self.addStorageAndWait(storage)
DispatchQueue.main.async {
completion(.success(storage))
}
}
catch {
DispatchQueue.main.async {
completion(.failure(CoreStoreError(error)))
}
}
}
catch {
let storeError = CoreStoreError(error)
Internals.log(
storeError,
"Failed to load \(Internals.typeName(NSPersistentStore.self)) metadata."
)
DispatchQueue.main.async {
completion(.failure(storeError))
}
}
}
}
/**
Migrates a local storage to match the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
@@ -750,7 +614,7 @@ extension DataStack {
}
let fileManager = FileManager.default
let systemTemporaryDirectoryURL: URL
if #available(macOS 10.12, *) {
if #available(macOS 10.12, iOS 10.0, *) {
systemTemporaryDirectoryURL = fileManager.temporaryDirectory
}

View File

@@ -36,15 +36,15 @@ extension DataStack {
Creates an `ObjectMonitor` for the specified `DynamicObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `DynamicObject`.
- parameter object: the `DynamicObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
- returns: an `ObjectMonitor` that monitors changes to `object`
*/
public func monitorObject<D>(_ object: D) -> ObjectMonitor<D> {
public func monitorObject<O: DynamicObject>(_ object: O) -> ObjectMonitor<O> {
Internals.assert(
Thread.isMainThread,
"Attempted to observe objects from \(Internals.typeName(self)) outside the main thread."
)
return ObjectMonitor(dataStack: self, object: object)
return .init(objectID: object.cs_id(), context: self.unsafeContext())
}
/**
@@ -54,7 +54,7 @@ extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorList<D>(_ from: From<D>, _ fetchClauses: FetchClause...) -> ListMonitor<D> {
public func monitorList<O>(_ from: From<O>, _ fetchClauses: FetchClause...) -> ListMonitor<O> {
return self.monitorList(from, fetchClauses)
}
@@ -66,7 +66,7 @@ extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorList<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) -> ListMonitor<D> {
public func monitorList<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) -> ListMonitor<O> {
Internals.assert(
Thread.isMainThread,
@@ -82,7 +82,7 @@ extension DataStack {
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(ListMonitor<D>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<D>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
"An \(Internals.typeName(ListMonitor<O>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<O>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
}
)
@@ -102,7 +102,10 @@ extension DataStack {
*/
public func monitorList<B: FetchChainableBuilderType>(_ clauseChain: B) -> ListMonitor<B.ObjectType> {
return self.monitorList(clauseChain.from, clauseChain.fetchClauses)
return self.monitorList(
clauseChain.from,
clauseChain.fetchClauses
)
}
/**
@@ -112,9 +115,13 @@ extension DataStack {
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ fetchClauses: FetchClause...) {
public func monitorList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ fetchClauses: FetchClause...) {
self.monitorList(createAsynchronously: createAsynchronously, from, fetchClauses)
self.monitorList(
createAsynchronously: createAsynchronously,
from,
fetchClauses
)
}
/**
@@ -124,7 +131,7 @@ extension DataStack {
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ fetchClauses: [FetchClause]) {
public func monitorList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ fetchClauses: [FetchClause]) {
Internals.assert(
Thread.isMainThread,
@@ -140,7 +147,7 @@ extension DataStack {
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(ListMonitor<D>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<D>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
"An \(Internals.typeName(ListMonitor<O>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<O>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
},
createAsynchronously: createAsynchronously
@@ -180,9 +187,13 @@ extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorSectionedList<D>(_ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: FetchClause...) -> ListMonitor<D> {
public func monitorSectionedList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) -> ListMonitor<O> {
return self.monitorSectionedList(from, sectionBy, fetchClauses)
return self.monitorSectionedList(
from,
sectionBy,
fetchClauses
)
}
/**
@@ -193,7 +204,7 @@ extension DataStack {
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
public func monitorSectionedList<D>(_ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: [FetchClause]) -> ListMonitor<D> {
public func monitorSectionedList<O>(_ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) -> ListMonitor<O> {
Internals.assert(
Thread.isMainThread,
@@ -210,7 +221,7 @@ extension DataStack {
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(ListMonitor<D>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<D>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
"An \(Internals.typeName(ListMonitor<O>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<O>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
}
)
@@ -246,9 +257,14 @@ extension DataStack {
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorSectionedList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: FetchClause...) {
public func monitorSectionedList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: FetchClause...) {
self.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
self.monitorSectionedList(
createAsynchronously: createAsynchronously,
from,
sectionBy,
fetchClauses
)
}
/**
@@ -259,7 +275,7 @@ extension DataStack {
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
*/
public func monitorSectionedList<D>(createAsynchronously: @escaping (ListMonitor<D>) -> Void, _ from: From<D>, _ sectionBy: SectionBy<D>, _ fetchClauses: [FetchClause]) {
public func monitorSectionedList<O>(createAsynchronously: @escaping (ListMonitor<O>) -> Void, _ from: From<O>, _ sectionBy: SectionBy<O>, _ fetchClauses: [FetchClause]) {
Internals.assert(
Thread.isMainThread,
@@ -276,7 +292,7 @@ extension DataStack {
Internals.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(Internals.typeName(ListMonitor<D>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<D>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
"An \(Internals.typeName(ListMonitor<O>.self)) requires a sort information. Specify from a \(Internals.typeName(OrderBy<O>.self)) clause or any custom \(Internals.typeName(FetchClause.self)) that provides a sort descriptor."
)
},
createAsynchronously: createAsynchronously

View File

@@ -39,7 +39,7 @@ extension DataStack: FetchableSource, QueryableSource {
- parameter object: a reference to the object created/fetched outside the `DataStack`
- returns: the `DynamicObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
public func fetchExisting<D: DynamicObject>(_ object: D) -> D? {
public func fetchExisting<O: DynamicObject>(_ object: O) -> O? {
return self.mainContext.fetchExisting(object)
}
@@ -50,7 +50,7 @@ extension DataStack: FetchableSource, QueryableSource {
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `DynamicObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
public func fetchExisting<D: DynamicObject>(_ objectID: NSManagedObjectID) -> D? {
public func fetchExisting<O: DynamicObject>(_ objectID: NSManagedObjectID) -> O? {
return self.mainContext.fetchExisting(objectID)
}
@@ -61,7 +61,7 @@ extension DataStack: FetchableSource, QueryableSource {
- parameter objects: an array of `DynamicObject`s created/fetched outside the `DataStack`
- returns: the `DynamicObject` array for objects that exists in the `DataStack`
*/
public func fetchExisting<D: DynamicObject, S: Sequence>(_ objects: S) -> [D] where S.Iterator.Element == D {
public func fetchExisting<O: DynamicObject, S: Sequence>(_ objects: S) -> [O] where S.Iterator.Element == O {
return self.mainContext.fetchExisting(objects)
}
@@ -72,7 +72,7 @@ extension DataStack: FetchableSource, QueryableSource {
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `DynamicObject` array for objects that exists in the `DataStack`
*/
public func fetchExisting<D: DynamicObject, S: Sequence>(_ objectIDs: S) -> [D] where S.Iterator.Element == NSManagedObjectID {
public func fetchExisting<O: DynamicObject, S: Sequence>(_ objectIDs: S) -> [O] where S.Iterator.Element == NSManagedObjectID {
return self.mainContext.fetchExisting(objectIDs)
}
@@ -85,7 +85,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchOne<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> D? {
public func fetchOne<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> O? {
Internals.assert(
Thread.isMainThread,
@@ -102,7 +102,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchOne<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> D? {
public func fetchOne<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> O? {
Internals.assert(
Thread.isMainThread,
@@ -141,7 +141,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchAll<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> [D] {
public func fetchAll<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [O] {
Internals.assert(
Thread.isMainThread,
@@ -158,7 +158,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchAll<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> [D] {
public func fetchAll<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [O] {
Internals.assert(
Thread.isMainThread,
@@ -197,7 +197,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchCount<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> Int {
public func fetchCount<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> Int {
Internals.assert(
Thread.isMainThread,
@@ -214,7 +214,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchCount<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> Int {
public func fetchCount<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> Int {
Internals.assert(
Thread.isMainThread,
@@ -253,7 +253,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchObjectID<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID? {
public func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID? {
Internals.assert(
Thread.isMainThread,
@@ -270,7 +270,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchObjectID<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID? {
public func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID? {
Internals.assert(
Thread.isMainThread,
@@ -309,7 +309,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchObjectIDs<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID] {
public func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID] {
Internals.assert(
Thread.isMainThread,
@@ -326,7 +326,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func fetchObjectIDs<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID] {
public func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID] {
Internals.assert(
Thread.isMainThread,
@@ -371,7 +371,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func queryValue<D, U: QueryableAttributeType>(_ from: From<D>, _ selectClause: Select<D, U>, _ queryClauses: QueryClause...) throws -> U? {
public func queryValue<O, U: QueryableAttributeType>(_ from: From<O>, _ selectClause: Select<O, U>, _ queryClauses: QueryClause...) throws -> U? {
Internals.assert(
Thread.isMainThread,
@@ -391,7 +391,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the result of the the query, or `nil` if no match was found. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func queryValue<D, U: QueryableAttributeType>(_ from: From<D>, _ selectClause: Select<D, U>, _ queryClauses: [QueryClause]) throws -> U? {
public func queryValue<O, U: QueryableAttributeType>(_ from: From<O>, _ selectClause: Select<O, U>, _ queryClauses: [QueryClause]) throws -> U? {
Internals.assert(
Thread.isMainThread,
@@ -435,7 +435,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func queryAttributes<D>(_ from: From<D>, _ selectClause: Select<D, NSDictionary>, _ queryClauses: QueryClause...) throws -> [[String: Any]] {
public func queryAttributes<O>(_ from: From<O>, _ selectClause: Select<O, NSDictionary>, _ queryClauses: QueryClause...) throws -> [[String: Any]] {
Internals.assert(
Thread.isMainThread,
@@ -455,7 +455,7 @@ extension DataStack: FetchableSource, QueryableSource {
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
public func queryAttributes<D>(_ from: From<D>, _ selectClause: Select<D, NSDictionary>, _ queryClauses: [QueryClause]) throws -> [[String: Any]] {
public func queryAttributes<O>(_ from: From<O>, _ selectClause: Select<O, NSDictionary>, _ queryClauses: [QueryClause]) throws -> [[String: Any]] {
Internals.assert(
Thread.isMainThread,

View File

@@ -63,7 +63,7 @@ public final class DataStack: Equatable {
/**
Convenience initializer for `DataStack` that creates a `SchemaHistory` from a list of `DynamicSchema` versions.
```
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
XcodeDataModelSchema(modelName: "MyModelV1"),
CoreStoreSchema(
modelVersion: "MyModelV2",
@@ -92,7 +92,7 @@ public final class DataStack: Equatable {
/**
Initializes a `DataStack` from a `SchemaHistory` instance.
```
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
schemaHistory: SchemaHistory(
XcodeDataModelSchema(modelName: "MyModelV1"),
CoreStoreSchema(
@@ -370,99 +370,6 @@ public final class DataStack: Equatable {
}
}
/**
Adds a `CloudStorage` to the stack and blocks until completion.
```
guard let storage = ICloudStore(
ubiquitousContentName: "MyAppCloudData",
ubiquitousContentTransactionLogsSubdirectory: "logs/config1",
ubiquitousContainerID: "iCloud.com.mycompany.myapp.containername",
ubiquitousPeerToken: "9614d658014f4151a95d8048fb717cf0",
configuration: "Config1",
cloudStorageOptions: .recreateLocalStoreOnModelMismatch
) else {
// iCloud is not available on the device
return
}
try dataStack.addStorageAndWait(storage)
```
- parameter storage: the local storage
- throws: a `CoreStoreError` value indicating the failure
- returns: the cloud storage added to the stack. Note that this may not always be the same instance as the parameter argument if a previous `CloudStorage` was already added at the same URL and with the same configuration.
*/
@discardableResult
public func addStorageAndWait<T: CloudStorage>(_ storage: T) throws -> T {
return try self.coordinator.performSynchronously {
if let _ = self.persistentStoreForStorage(storage) {
return storage
}
let cacheFileURL = storage.cacheFileURL
if let persistentStore = self.coordinator.persistentStore(for: cacheFileURL as URL) {
if let existingStorage = persistentStore.storageInterface as? T,
storage.matchesPersistentStore(persistentStore) {
return existingStorage
}
let error = CoreStoreError.differentStorageExistsAtURL(existingPersistentStoreURL: cacheFileURL)
Internals.log(
error,
"Failed to add \(Internals.typeName(storage)) at \"\(cacheFileURL)\" because a different \(Internals.typeName(NSPersistentStore.self)) at that URL already exists."
)
throw error
}
do {
var cloudStorageOptions = storage.cloudStorageOptions
cloudStorageOptions.remove(.recreateLocalStoreOnModelMismatch)
let storeOptions = storage.dictionary(forOptions: cloudStorageOptions)
do {
_ = try self.createPersistentStoreFromStorage(
storage,
finalURL: cacheFileURL,
finalStoreOptions: storeOptions
)
return storage
}
catch let error as NSError where storage.cloudStorageOptions.contains(.recreateLocalStoreOnModelMismatch) && error.isCoreDataMigrationError {
let finalStoreOptions = storage.dictionary(forOptions: storage.cloudStorageOptions)
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
ofType: type(of: storage).storeType,
at: cacheFileURL,
options: storeOptions
)
_ = try self.schemaHistory
.schema(for: metadata)
.flatMap({ try storage.cs_eraseStorageAndWait(soureModel: $0.rawModel()) })
_ = try self.createPersistentStoreFromStorage(
storage,
finalURL: cacheFileURL,
finalStoreOptions: finalStoreOptions
)
return storage
}
}
catch {
let storeError = CoreStoreError(error)
Internals.log(
storeError,
"Failed to add \(Internals.typeName(storage)) to the stack."
)
throw storeError
}
}
}
// MARK: 3rd Party Utilities
@@ -472,7 +379,7 @@ public final class DataStack: Equatable {
enum Static {
static var myDataKey: Void?
}
CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject
CoreStoreDefaults.dataStack.userInfo[&Static.myDataKey] = myObject
```
- Important: Do not use this method to store thread-sensitive data.
*/

View File

@@ -0,0 +1,216 @@
//
// DiffableDataSource.BaseAdapter.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - BaseAdapter
/**
The `DiffableDataSource.BaseAdapter` serves as a superclass for consumers of `ListPublisher` and `ListSnapshot` diffable data.
```
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class BaseAdapter<O: DynamicObject, T: Target>: NSObject {
// MARK: Public
/**
The object type represented by this dataSource
*/
public typealias ObjectType = O
/**
The target to be updated by this dataSource
*/
public let target: T
/**
The `DataStack` where object fetches are performed
*/
public let dataStack: DataStack
/**
Initializes the `DiffableDataSource.BaseAdapter` object. This instance needs to be held on (retained) for as long as the target's lifecycle.
```
self.dataSource = DiffableDataSource.TableViewAdapterAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
*/
public init(target: T, dataStack: DataStack) {
self.target = target
self.dataStack = dataStack
self.dispatcher = Internals.DiffableDataUIDispatcher<O>(dataStack: dataStack)
}
/**
Clears the target.
- parameter animatingDifferences: if `true`, animations may be applied accordingly. Defaults to `true`.
*/
open func purge(animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
self.dispatcher.purge(
target: self.target,
animatingDifferences: animatingDifferences,
performUpdates: { target, changeset, setSections in
target.reload(
using: changeset,
animated: animatingDifferences,
setData: setSections
)
},
completion: completion
)
}
/**
Reloads the target using a `ListSnapshot`. This is typically from the `snapshot` property of a `ListPublisher`:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
``
- parameter snapshot: the `ListSnapshot` used to reload the target with. This is typically from the `snapshot` property of a `ListPublisher`.
- parameter animatingDifferences: if `true`, animations may be applied accordingly. Defaults to `true`.
*/
open func apply(_ snapshot: ListSnapshot<O>, animatingDifferences: Bool = true, completion: @escaping () -> Void = {}) {
let diffableSnapshot = snapshot.diffableSnapshot
self.dispatcher.apply(
diffableSnapshot,
target: self.target,
animatingDifferences: animatingDifferences,
performUpdates: { target, changeset, setSections in
target.reload(
using: changeset,
animated: animatingDifferences,
setData: setSections
)
},
completion: completion
)
}
/**
Returns the number of sections
- parameter indexPath: the `IndexPath` to search for
- returns: the number of sections
*/
public func numberOfSections() -> Int {
return self.dispatcher.numberOfSections()
}
/**
Returns the number of items at the specified section, or `nil` if the section is not found
- parameter section: the section index to search for
- returns: the number of items at the specified section, or `nil` if the section is not found
*/
public func numberOfItems(inSection section: Int) -> Int? {
return self.dispatcher.numberOfItems(inSection: section)
}
/**
Returns the section identifier at the specified index, or `nil` if not found
- parameter section: the section index to search for
- returns: the section identifier at the specified indec, or `nil` if not found
*/
public func sectionID(for section: Int) -> String? {
return self.dispatcher.sectionIdentifier(inSection: section)
}
/**
Returns the object identifier for the item at the specified `IndexPath`, or `nil` if not found
- parameter indexPath: the `IndexPath` to search for
- returns: the object identifier for the item at the specified `IndexPath`, or `nil` if not found
*/
public func itemID(for indexPath: IndexPath) -> O.ObjectID? {
return self.dispatcher.itemIdentifier(for: indexPath)
}
/**
Returns the `IndexPath` for the item with the specified object identifier, or `nil` if not found
- parameter itemID: the object identifier to search for
- returns: the `IndexPath` for the item with the specified object identifier, or `nil` if not found
*/
public func indexPath(for itemID: O.ObjectID) -> IndexPath? {
return self.dispatcher.indexPath(for: itemID)
}
// MARK: Internal
internal let dispatcher: Internals.DiffableDataUIDispatcher<O>
}
}

View File

@@ -0,0 +1,232 @@
//
// DiffableDataSource.CollectionViewAdapter-AppKit.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(AppKit) && os(macOS)
import AppKit
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - CollectionView
/**
The `DiffableDataSource.CollectionViewAdapter` serves as a `NSCollectionViewDataSource` that handles `ListPublisher` snapshots for a `NSCollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `NSCollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
itemProvider: { (collectionView, indexPath, person) in
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
item.setPerson(person)
return item
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<NSCollectionView>>, NSCollectionViewDataSource {
// MARK: Public
/**
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `NSCollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
itemProvider: { (collectionView, indexPath, person) in
let item = collectionView.makeItem(withIdentifier: .collectionViewItem, for: indexPath) as! PersonItem
item.setPerson(person)
return item
}
)
```
- parameter collectionView: the `NSCollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter itemProvider: a closure that configures and returns the `NSCollectionViewItem` for the object
*/
@nonobjc
public init(collectionView: NSCollectionView, dataStack: DataStack, itemProvider: @escaping (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?, supplementaryViewProvider: @escaping (NSCollectionView, String, IndexPath) -> NSView? = { _, _, _ in nil }) {
self.itemProvider = itemProvider
self.supplementaryViewProvider = supplementaryViewProvider
super.init(target: .init(collectionView), dataStack: dataStack)
collectionView.dataSource = self
}
// MARK: - NSCollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: NSCollectionView) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: NSCollectionView, numberOfItemsInSection section: Int) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, itemForRepresentedObjectAt indexPath: IndexPath) -> NSCollectionViewItem {
guard let objectID = self.itemID(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
}
guard let item = self.itemProvider(collectionView, indexPath, object) else {
Internals.abort("\(Internals.typeName(NSCollectionViewDataSource.self)) returned a `nil` item for \(Internals.typeName(IndexPath.self)) \(indexPath)")
}
return item
}
@objc
open dynamic func collectionView(_ collectionView: NSCollectionView, viewForSupplementaryElementOfKind kind: NSCollectionView.SupplementaryElementKind, at indexPath: IndexPath) -> NSView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
return NSView()
}
return view
}
// MARK: Private
private let itemProvider: (NSCollectionView, IndexPath, O) -> NSCollectionViewItem?
private let supplementaryViewProvider: (NSCollectionView, String, IndexPath) -> NSView?
}
// MARK: - DefaultCollectionViewTarget
public struct DefaultCollectionViewTarget<T: NSCollectionView>: Target {
// MARK: Public
public typealias Base = T
public private(set) weak var base: Base?
public init(_ base: Base) {
self.base = base
}
// MARK: DiffableDataSource.Target:
public var shouldSuspendBatchUpdates: Bool {
return self.base?.window == nil
}
public func deleteSections(at indices: IndexSet, animated: Bool) {
self.base?.deleteSections(indices)
}
public func insertSections(at indices: IndexSet, animated: Bool) {
self.base?.insertSections(indices)
}
public func reloadSections(at indices: IndexSet, animated: Bool) {
self.base?.reloadSections(indices)
}
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
self.base?.moveSection(index, toSection: newIndex)
}
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.deleteItems(at: Set(indexPaths))
}
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.insertItems(at: Set(indexPaths))
}
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.reloadItems(at: Set(indexPaths))
}
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
self.base?.animator().performBatchUpdates(updates, completionHandler: nil)
}
public func reloadData() {
self.base?.reloadData()
}
}
}
// MARK: - Deprecated
extension DiffableDataSource {
@available(*, deprecated, renamed: "CollectionViewAdapter")
public typealias CollectionView = CollectionViewAdapter
}
#endif

View File

@@ -0,0 +1,231 @@
//
// DiffableDataSource.CollectionViewAdapter-UIKit.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) && (os(iOS) || os(tvOS))
import UIKit
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - CollectionView
/**
The `DiffableDataSource.CollectionViewAdapter` serves as a `UICollectionViewDataSource` that handles `ListPublisher` snapshots for a `UICollectionView`. Subclasses of `DiffableDataSource.CollectionViewAdapter` may override some `UICollectionViewDataSource` methods as needed.
The `DiffableDataSource.CollectionViewAdapter` instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
`DiffableDataSource.CollectionViewAdapter` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class CollectionViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultCollectionViewTarget<UICollectionView>>, UICollectionViewDataSource {
// MARK: Public
/**
Initializes the `DiffableDataSource.CollectionViewAdapter`. This instance needs to be held on (retained) for as long as the `UICollectionView`'s lifecycle.
```
self.dataSource = DiffableDataSource.CollectionViewAdapter<Person>(
collectionView: self.collectionView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (collectionView, indexPath, person) in
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
- parameter collectionView: the `UICollectionView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.CollectionViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UICollectionViewCell` for the object
- parameter supplementaryViewProvider: an optional closure for providing `UICollectionReusableView` supplementary views. If not set, defaults to returning `nil`
*/
public init(collectionView: UICollectionView, dataStack: DataStack, cellProvider: @escaping (UICollectionView, IndexPath, O) -> UICollectionViewCell?, supplementaryViewProvider: @escaping (UICollectionView, String, IndexPath) -> UICollectionReusableView? = { _, _, _ in nil }) {
self.cellProvider = cellProvider
self.supplementaryViewProvider = supplementaryViewProvider
super.init(target: .init(collectionView), dataStack: dataStack)
collectionView.dataSource = self
}
// MARK: - UICollectionViewDataSource
@objc
public dynamic func numberOfSections(in collectionView: UICollectionView) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
guard let objectID = self.itemID(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
}
guard let cell = self.cellProvider(collectionView, indexPath, object) else {
Internals.abort("\(Internals.typeName(UICollectionViewDataSource.self)) returned a `nil` cell for \(Internals.typeName(IndexPath.self)) \(indexPath)")
}
return cell
}
@objc
open dynamic func collectionView(_ collectionView: UICollectionView, viewForSupplementaryElementOfKind kind: String, at indexPath: IndexPath) -> UICollectionReusableView {
guard let view = self.supplementaryViewProvider(collectionView, kind, indexPath) else {
return UICollectionReusableView()
}
return view
}
// MARK: Private
private let cellProvider: (UICollectionView, IndexPath, O) -> UICollectionViewCell?
private let supplementaryViewProvider: (UICollectionView, String, IndexPath) -> UICollectionReusableView?
}
// MARK: - DefaultCollectionViewTarget
public struct DefaultCollectionViewTarget<T: UICollectionView>: Target {
// MARK: Public
public typealias Base = T
public private(set) weak var base: Base?
public init(_ base: Base) {
self.base = base
}
// MARK: DiffableDataSource.Target
public var shouldSuspendBatchUpdates: Bool {
return self.base?.window == nil
}
public func deleteSections(at indices: IndexSet, animated: Bool) {
self.base?.deleteSections(indices)
}
public func insertSections(at indices: IndexSet, animated: Bool) {
self.base?.insertSections(indices)
}
public func reloadSections(at indices: IndexSet, animated: Bool) {
self.base?.reloadSections(indices)
}
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
self.base?.moveSection(index, toSection: newIndex)
}
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.deleteItems(at: indexPaths)
}
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.insertItems(at: indexPaths)
}
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.reloadItems(at: indexPaths)
}
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
self.base?.moveItem(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
self.base?.performBatchUpdates(updates, completion: nil)
}
public func reloadData() {
self.base?.reloadData()
}
}
}
// MARK: - Deprecated
extension DiffableDataSource {
@available(*, deprecated, renamed: "CollectionViewAdapter")
public typealias CollectionView = CollectionViewAdapter
}
#endif

View File

@@ -0,0 +1,266 @@
//
// DiffableDataSource.TableViewAdapter-UIKit.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) && (os(iOS) || os(tvOS))
import UIKit
import CoreData
// MARK: - DiffableDataSource
extension DiffableDataSource {
// MARK: - TableViewAdapter
/**
The `DiffableDataSource.TableViewAdapterAdapter` serves as a `UITableViewDataSource` that handles `ListPublisher` snapshots for a `UITableView`. Subclasses of `DiffableDataSource.TableViewAdapter` may override some `UITableViewDataSource` methods as needed.
The `DiffableDataSource.TableViewAdapterAdapter` instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
```
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
The dataSource can then apply changes from a `ListPublisher` as shown:
```
listPublisher.addObserver(self) { [weak self] (listPublisher) in
self?.dataSource?.apply(
listPublisher.snapshot,
animatingDifferences: true
)
}
```
`DiffableDataSource.TableViewAdapter` fully handles the reload animations.
- SeeAlso: CoreStore's DiffableDataSource implementation is based on https://github.com/ra1028/DiffableDataSources
*/
open class TableViewAdapter<O: DynamicObject>: BaseAdapter<O, DefaultTableViewTarget<UITableView>>, UITableViewDataSource {
// MARK: Publi
/**
Initializes the `DiffableDataSource.TableViewAdapter`. This instance needs to be held on (retained) for as long as the `UITableView`'s lifecycle.
```
self.dataSource = DiffableDataSource.TableViewAdapter<Person>(
tableView: self.tableView,
dataStack: CoreStoreDefaults.dataStack,
cellProvider: { (tableView, indexPath, person) in
let cell = tableView.dequeueReusableCell(withIdentifier: "PersonCell") as! PersonCell
cell.setPerson(person)
return cell
}
)
```
- parameter tableView: the `UITableView` to set the `dataSource` of. This instance is not retained by the `DiffableDataSource.TableViewAdapter`.
- parameter dataStack: the `DataStack` instance that the dataSource will fetch objects from
- parameter cellProvider: a closure that configures and returns the `UITableViewCell` for the object
*/
public init(tableView: UITableView, dataStack: DataStack, cellProvider: @escaping (UITableView, IndexPath, O) -> UITableViewCell?) {
self.cellProvider = cellProvider
super.init(target: .init(tableView), dataStack: dataStack)
tableView.dataSource = self
}
/**
The target `UITableView`
*/
public var tableView: UITableView? {
return self.target.base
}
// MARK: - UITableViewDataSource
@objc
public dynamic func numberOfSections(in tableView: UITableView) -> Int {
return self.numberOfSections()
}
@objc
public dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.numberOfItems(inSection: section) ?? 0
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sectionID(for: section)
}
@objc
open dynamic func tableView(_ tableView: UITableView, titleForFooterInSection section: Int) -> String? {
return nil
}
@objc
open dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let objectID = self.itemID(for: indexPath) else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) already removed from list")
}
guard let object = self.dataStack.fetchExisting(objectID) as O? else {
Internals.abort("Object at \(Internals.typeName(IndexPath.self)) \(indexPath) has been deleted")
}
guard let cell = self.cellProvider(tableView, indexPath, object) else {
Internals.abort("\(Internals.typeName(UITableViewDataSource.self)) returned a `nil` cell for \(Internals.typeName(IndexPath.self)) \(indexPath)")
}
return cell
}
@objc
open dynamic func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
return true
}
@objc
open dynamic func tableView(_ tableView: UITableView, editingStyleForRowAt indexPath: IndexPath) -> UITableViewCell.EditingStyle {
return .delete
}
@objc
open dynamic func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {}
// MARK: Private
@nonobjc
private let cellProvider: (UITableView, IndexPath, O) -> UITableViewCell?
}
// MARK: - DefaultTableViewTarget
public struct DefaultTableViewTarget<T: UITableView>: Target {
// MARK: Public
public typealias Base = T
public private(set) weak var base: Base?
public init(_ base: Base) {
self.base = base
}
// MARK: DiffableDataSource.Target
public var shouldSuspendBatchUpdates: Bool {
return self.base?.window == nil
}
public func deleteSections(at indices: IndexSet, animated: Bool) {
self.base?.deleteSections(indices, with: .automatic)
}
public func insertSections(at indices: IndexSet, animated: Bool) {
self.base?.insertSections(indices, with: .automatic)
}
public func reloadSections(at indices: IndexSet, animated: Bool) {
self.base?.reloadSections(indices, with: .automatic)
}
public func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool) {
self.base?.moveSection(index, toSection: newIndex)
}
public func deleteItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.deleteRows(at: indexPaths, with: .automatic)
}
public func insertItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.insertRows(at: indexPaths, with: .automatic)
}
public func reloadItems(at indexPaths: [IndexPath], animated: Bool) {
self.base?.reloadRows(at: indexPaths, with: .automatic)
}
public func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool) {
self.base?.moveRow(at: indexPath, to: newIndexPath)
}
public func performBatchUpdates(updates: () -> Void, animated: Bool) {
guard let base = self.base else {
return
}
if #available(iOS 11.0, tvOS 11.0, *) {
base.performBatchUpdates(updates)
}
else {
base.beginUpdates()
updates()
base.endUpdates()
}
}
public func reloadData() {
self.base?.reloadData()
}
}
}
// MARK: - Deprecated
extension DiffableDataSource {
@available(*, deprecated, renamed: "TableViewAdapter")
public typealias TableView = TableViewAdapter
}
#endif

View File

@@ -0,0 +1,211 @@
//
// DiffableDataSource.Target.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - DiffableDataSourc
extension DiffableDataSource {
// MARK: - Target
/**
The `DiffableDataSource.Target` protocol allows custom views to consume `ListSnapshot` diffable data similar to how `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` reloads data for their corresponding views.
*/
public typealias Target = DiffableDataSourceTarget
}
// MARK: - DiffableDataSource.Target
/**
The `DiffableDataSource.Target` protocol allows custom views to consume `ListSnapshot` diffable data similar to how `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` reloads data for their corresponding views.
*/
public protocol DiffableDataSourceTarget {
// MARK: Public
/**
Whether `reloadData()` should be executed instead of `performBatchUpdates(updates:animated:)`.
*/
var shouldSuspendBatchUpdates: Bool { get }
/**
Deletes one or more sections.
*/
func deleteSections(at indices: IndexSet, animated: Bool)
/**
Inserts one or more sections
*/
func insertSections(at indices: IndexSet, animated: Bool)
/**
Reloads the specified sections.
*/
func reloadSections(at indices: IndexSet, animated: Bool)
/**
Moves a section to a new location.
*/
func moveSection(at index: IndexSet.Element, to newIndex: IndexSet.Element, animated: Bool)
/**
Deletes the items specified by an array of index paths.
*/
func deleteItems(at indexPaths: [IndexPath], animated: Bool)
/**
Inserts items at the locations identified by an array of index paths.
*/
func insertItems(at indexPaths: [IndexPath], animated: Bool)
/**
Reloads the specified items.
*/
func reloadItems(at indexPaths: [IndexPath], animated: Bool)
/**
Moves the item at a specified location to a destination location.
*/
func moveItem(at indexPath: IndexPath, to newIndexPath: IndexPath, animated: Bool)
/**
Animates multiple insert, delete, reload, and move operations as a group.
*/
func performBatchUpdates(updates: () -> Void, animated: Bool)
/**
Reloads all sections and items.
*/
func reloadData()
}
extension DiffableDataSource.Target {
// MARK: Internal
internal func reload<C, O>(
using stagedChangeset: Internals.DiffableDataUIDispatcher<O>.StagedChangeset<C>,
animated: Bool,
interrupt: ((Internals.DiffableDataUIDispatcher<O>.Changeset<C>) -> Bool)? = nil,
setData: (C) -> Void
) {
if self.shouldSuspendBatchUpdates, let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
for changeset in stagedChangeset {
if let interrupt = interrupt,
interrupt(changeset),
let data = stagedChangeset.last?.data {
setData(data)
self.reloadData()
return
}
self.performBatchUpdates(
updates: {
setData(changeset.data)
if !changeset.sectionDeleted.isEmpty {
self.deleteSections(
at: IndexSet(changeset.sectionDeleted),
animated: animated
)
}
if !changeset.sectionInserted.isEmpty {
self.insertSections(
at: IndexSet(changeset.sectionInserted),
animated: animated
)
}
if !changeset.sectionUpdated.isEmpty {
self.reloadSections(
at: IndexSet(changeset.sectionUpdated),
animated: animated
)
}
for (source, target) in changeset.sectionMoved {
self.moveSection(
at: source,
to: target,
animated: animated
)
}
if !changeset.elementDeleted.isEmpty {
self.deleteItems(
at: changeset.elementDeleted.map {
IndexPath(item: $0.element, section: $0.section)
},
animated: animated
)
}
if !changeset.elementInserted.isEmpty {
self.insertItems(
at: changeset.elementInserted.map {
IndexPath(item: $0.element, section: $0.section)
},
animated: animated
)
}
if !changeset.elementUpdated.isEmpty {
self.reloadItems(
at: changeset.elementUpdated.map {
IndexPath(item: $0.element, section: $0.section)
},
animated: animated
)
}
for (source, target) in changeset.elementMoved {
self.moveItem(
at: IndexPath(item: source.element, section: source.section),
to: IndexPath(item: target.element, section: target.section),
animated: animated
)
}
},
animated: animated
)
}
}
}

View File

@@ -0,0 +1,35 @@
//
// File.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if os(iOS) || os(tvOS) || os(macOS)
// MARK: - DiffableDataSource
/**
Namespace for diffable data source types. See `DiffableDataSource.TableViewAdapter` and `DiffableDataSource.CollectionViewAdapter` for actual implementations
*/
public enum DiffableDataSource {}
#endif

View File

@@ -0,0 +1,62 @@
//
// DiffableDataSourceSnapshotProtocol.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - DiffableDataSourceSnapshotProtocol
internal protocol DiffableDataSourceSnapshotProtocol {
init()
var numberOfItems: Int { get }
var numberOfSections: Int { get }
var sectionIdentifiers: [String] { get }
var itemIdentifiers: [NSManagedObjectID] { get }
func numberOfItems(inSection identifier: String) -> Int
func itemIdentifiers(inSection identifier: String) -> [NSManagedObjectID]
func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> String?
func indexOfItem(_ identifier: NSManagedObjectID) -> Int?
func indexOfSection(_ identifier: String) -> Int?
mutating func appendItems(_ identifiers: [NSManagedObjectID], toSection sectionIdentifier: String?)
mutating func insertItems(_ identifiers: [NSManagedObjectID], beforeItem beforeIdentifier: NSManagedObjectID)
mutating func insertItems(_ identifiers: [NSManagedObjectID], afterItem afterIdentifier: NSManagedObjectID)
mutating func deleteItems(_ identifiers: [NSManagedObjectID])
mutating func deleteAllItems()
mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID)
mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID)
mutating func reloadItems(_ identifiers: [NSManagedObjectID])
mutating func appendSections(_ identifiers: [String])
mutating func insertSections(_ identifiers: [String], beforeSection toIdentifier: String)
mutating func insertSections(_ identifiers: [String], afterSection toIdentifier: String)
mutating func deleteSections(_ identifiers: [String])
mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String)
mutating func moveSection(_ identifier: String, afterSection toIdentifier: String)
mutating func reloadSections(_ identifiers: [String])
}

View File

@@ -0,0 +1,62 @@
//
// Differentiable.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - Differentiable
@usableFromInline
internal protocol Differentiable {
associatedtype DifferenceIdentifier: Hashable
var differenceIdentifier: DifferenceIdentifier { get }
func isContentEqual(to source: Self) -> Bool
}
extension Differentiable where Self: AnyObject {
// MARK: Internal
internal var differenceIdentifier: ObjectIdentifier {
return .init(self)
}
}
// MARK: - DifferentiableSection
@usableFromInline
internal protocol DifferentiableSection: Differentiable {
associatedtype Collection: Swift.Collection where Collection.Element: Differentiable
var elements: Collection { get }
init<S: Sequence>(source: Self, elements: S) where S.Element == Collection.Element
}

View File

@@ -33,12 +33,21 @@ import CoreData
All CoreStore's utilities are designed around `DynamicObject` instances. `NSManagedObject` and `CoreStoreObject` instances all conform to `DynamicObject`.
*/
public protocol DynamicObject: AnyObject {
/**
The object ID for this instance
*/
typealias ObjectID = NSManagedObjectID
/**
Used internally by CoreStore. Do not call directly.
*/
static func cs_forceCreate(entityDescription: NSEntityDescription, into context: NSManagedObjectContext, assignTo store: NSPersistentStore) -> Self
/**
Used internally by CoreStore. Do not call directly.
*/
static func cs_snapshotDictionary(id: ObjectID, context: NSManagedObjectContext) -> [String: Any]?
/**
Used internally by CoreStore. Do not call directly.
*/
@@ -52,22 +61,17 @@ public protocol DynamicObject: AnyObject {
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_id() -> NSManagedObjectID
func cs_toRaw() -> NSManagedObject
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_toRaw() -> NSManagedObject
func cs_id() -> ObjectID
}
extension DynamicObject {
// MARK: Internal
internal static func keyPathBuilder() -> DynamicObjectMeta<Never, Self> {
return .init(keyPathString: "SELF")
}
// MARK: Internal
internal func runtimeType() -> Self.Type {
@@ -92,6 +96,21 @@ extension NSManagedObject: DynamicObject {
}
return object
}
public class func cs_snapshotDictionary(id: ObjectID, context: NSManagedObjectContext) -> [String: Any]? {
guard let object = context.fetchExisting(id) as NSManagedObject? else {
return nil
}
let rawObject = object.cs_toRaw()
var dictionary = rawObject.dictionaryWithValues(forKeys: Array(rawObject.entity.attributesByName.keys))
for case (let key, let target as NSManagedObject) in rawObject.dictionaryWithValues(forKeys: Array(rawObject.entity.relationshipsByName.keys)) {
dictionary[key] = target.objectID
}
return dictionary
}
public class func cs_fromRaw(object: NSManagedObject) -> Self {
@@ -103,24 +122,14 @@ extension NSManagedObject: DynamicObject {
return object.isKind(of: self)
}
public func cs_id() -> NSManagedObjectID {
return self.objectID
}
public func cs_toRaw() -> NSManagedObject {
return self
}
}
extension DynamicObject where Self: NSManagedObject {
// MARK: Public
public func createSnapshot() -> ObjectSnapshot<Self> {
public func cs_id() -> ObjectID {
return ObjectSnapshot(from: self)
return self.objectID
}
}
@@ -141,6 +150,46 @@ extension CoreStoreObject {
}
return self.cs_fromRaw(object: object)
}
public class func cs_snapshotDictionary(id: ObjectID, context: NSManagedObjectContext) -> [String: Any]? {
func initializeAttributes(mirror: Mirror, object: Self, into attributes: inout [KeyPathString: Any]) {
if let superClassMirror = mirror.superclassMirror {
initializeAttributes(
mirror: superClassMirror,
object: object,
into: &attributes
)
}
for child in mirror.children {
switch child.value {
case let property as AttributeProtocol:
attributes[property.keyPath] = property.valueForSnapshot
case let property as RelationshipProtocol:
attributes[property.keyPath] = property.valueForSnapshot
default:
continue
}
}
}
guard let object = context.fetchExisting(id) as CoreStoreObject? else {
return nil
}
var values: [KeyPathString: Any] = [:]
initializeAttributes(
mirror: Mirror(reflecting: object),
object: object as! Self,
into: &values
)
return values
}
public class func cs_fromRaw(object: NSManagedObject) -> Self {
@@ -166,23 +215,13 @@ extension CoreStoreObject {
return (self as AnyClass).isSubclass(of: type as AnyClass)
}
public func cs_id() -> NSManagedObjectID {
return self.rawObject!.objectID
}
public func cs_toRaw() -> NSManagedObject {
return self.rawObject!
}
}
extension DynamicObject where Self: CoreStoreObject {
// MARK: Public
public func createSnapshot() -> ObjectSnapshot<Self> {
public func cs_id() -> ObjectID {
return ObjectSnapshot(from: self)
return self.rawObject!.objectID
}
}

View File

@@ -44,7 +44,7 @@ import ObjectiveC
let pet = Relationship.ToOne<Animal>("pet", inverse: { $0.master })
}
CoreStore.defaultStack = DataStack(
CoreStoreDefaults.dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [

View File

@@ -0,0 +1,75 @@
//
// EnvironmentValues+DataSources.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(SwiftUI) && canImport(Combine)
import SwiftUI
import Combine
import CoreData
// MARK: - EnvironmentValues
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
extension EnvironmentValues {
// MARK: Public
/**
The `DataStack` instance injected to `self`:
```
@Environment(\.dataStack)
var dataStack: DataStack
```
*/
public var dataStack: DataStack {
get {
return self[DataStackKey.self]
}
set {
self[DataStackKey.self] = newValue
}
}
// MARK: - DataStackEnvironmentKey
@available(iOS 13.0, tvOS 13.0, watchOS 6.0, macOS 10.15, *)
fileprivate struct DataStackKey: EnvironmentKey {
// MARK: FilePrivate
fileprivate static var defaultValue: DataStack {
return CoreStoreDefaults.dataStack
}
}
}
#endif

View File

@@ -39,14 +39,20 @@ import CoreData
)
```
*/
public struct FetchChainBuilder<D: DynamicObject>: FetchChainableBuilderType {
public struct FetchChainBuilder<O: DynamicObject>: FetchChainableBuilderType {
// MARK: FetchChainableBuilderType
public typealias ObjectType = D
public typealias ObjectType = O
public var from: From<D>
public var from: From<O>
public var fetchClauses: [FetchClause] = []
// MARK: Deprecated
@available(*, deprecated, renamed: "O")
public typealias D = O
}

View File

@@ -40,7 +40,7 @@ public protocol FetchableSource: AnyObject {
- parameter object: a reference to the object created/fetched outside the `FetchableSource`'s context
- returns: the `DynamicObject` instance if the object exists in the `FetchableSource`'s context, or `nil` if not found.
*/
func fetchExisting<D: DynamicObject>(_ object: D) -> D?
func fetchExisting<O: DynamicObject>(_ object: O) -> O?
/**
Fetches the `DynamicObject` instance in the `FetchableSource`'s context from an `NSManagedObjectID`.
@@ -48,7 +48,7 @@ public protocol FetchableSource: AnyObject {
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `DynamicObject` instance if the object exists in the `FetchableSource`, or `nil` if not found.
*/
func fetchExisting<D: DynamicObject>(_ objectID: NSManagedObjectID) -> D?
func fetchExisting<O: DynamicObject>(_ objectID: NSManagedObjectID) -> O?
/**
Fetches the `DynamicObject` instances in the `FetchableSource`'s context from references created from another managed object context.
@@ -56,7 +56,7 @@ public protocol FetchableSource: AnyObject {
- parameter objects: an array of `DynamicObject`s created/fetched outside the `FetchableSource`'s context
- returns: the `DynamicObject` array for objects that exists in the `FetchableSource`
*/
func fetchExisting<D: DynamicObject, S: Sequence>(_ objects: S) -> [D] where S.Iterator.Element == D
func fetchExisting<O: DynamicObject, S: Sequence>(_ objects: S) -> [O] where S.Iterator.Element == O
/**
Fetches the `DynamicObject` instances in the `FetchableSource`'s context from a list of `NSManagedObjectID`.
@@ -64,7 +64,7 @@ public protocol FetchableSource: AnyObject {
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `DynamicObject` array for objects that exists in the `FetchableSource`'s context
*/
func fetchExisting<D: DynamicObject, S: Sequence>(_ objectIDs: S) -> [D] where S.Iterator.Element == NSManagedObjectID
func fetchExisting<O: DynamicObject, S: Sequence>(_ objectIDs: S) -> [O] where S.Iterator.Element == NSManagedObjectID
/**
Fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
@@ -74,7 +74,7 @@ public protocol FetchableSource: AnyObject {
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchOne<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> D?
func fetchOne<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> O?
/**
Fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
@@ -84,7 +84,7 @@ public protocol FetchableSource: AnyObject {
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchOne<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> D?
func fetchOne<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> O?
/**
Fetches the first `DynamicObject` instance that satisfies the specified `FetchChainableBuilderType` built from a chain of clauses.
@@ -109,7 +109,7 @@ public protocol FetchableSource: AnyObject {
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchAll<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> [D]
func fetchAll<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [O]
/**
Fetches all `DynamicObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
@@ -119,7 +119,7 @@ public protocol FetchableSource: AnyObject {
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchAll<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> [D]
func fetchAll<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [O]
/**
Fetches all `DynamicObject` instances that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
@@ -144,7 +144,7 @@ public protocol FetchableSource: AnyObject {
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchCount<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> Int
func fetchCount<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> Int
/**
Fetches the number of `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
@@ -154,7 +154,7 @@ public protocol FetchableSource: AnyObject {
- returns: the number of `DynamicObject`s that satisfy the specified `FetchClause`s
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchCount<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> Int
func fetchCount<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> Int
/**
Fetches the number of `DynamicObject`s that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.
@@ -179,7 +179,7 @@ public protocol FetchableSource: AnyObject {
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchObjectID<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID?
func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> NSManagedObjectID?
/**
Fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
@@ -189,7 +189,7 @@ public protocol FetchableSource: AnyObject {
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s, or `nil` if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchObjectID<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID?
func fetchObjectID<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> NSManagedObjectID?
/**
Fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchChainableBuilderType` built from a chain of clauses.
@@ -214,7 +214,7 @@ public protocol FetchableSource: AnyObject {
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchObjectIDs<D>(_ from: From<D>, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID]
func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: FetchClause...) throws -> [NSManagedObjectID]
/**
Fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
@@ -224,7 +224,7 @@ public protocol FetchableSource: AnyObject {
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s, or an empty array if no match was found
- throws: `CoreStoreError.persistentStoreNotFound` if the specified entity could not be found in any store's schema.
*/
func fetchObjectIDs<D>(_ from: From<D>, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID]
func fetchObjectIDs<O>(_ from: From<O>, _ fetchClauses: [FetchClause]) throws -> [NSManagedObjectID]
/**
Fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchChainableBuilderType` built from a chain of clauses.

View File

@@ -37,7 +37,7 @@ extension From {
- parameter clause: the `Where` clause to create a `FetchChainBuilder` with
- returns: a `FetchChainBuilder` that starts with the specified `Where` clause
*/
public func `where`(_ clause: Where<D>) -> FetchChainBuilder<D> {
public func `where`(_ clause: Where<O>) -> FetchChainBuilder<O> {
return self.fetchChain(appending: clause)
}
@@ -49,9 +49,9 @@ extension From {
- parameter args: the arguments for `format`
- returns: a `FetchChainBuilder` with a predicate using the specified string format and arguments
*/
public func `where`(format: String, _ args: Any...) -> FetchChainBuilder<D> {
public func `where`(format: String, _ args: Any...) -> FetchChainBuilder<O> {
return self.fetchChain(appending: Where<D>(format, argumentArray: args))
return self.fetchChain(appending: Where<O>(format, argumentArray: args))
}
/**
@@ -61,9 +61,9 @@ extension From {
- parameter argumentArray: the arguments for `format`
- returns: a `FetchChainBuilder` with a predicate using the specified string format and arguments
*/
public func `where`(format: String, argumentArray: [Any]?) -> FetchChainBuilder<D> {
public func `where`(format: String, argumentArray: [Any]?) -> FetchChainBuilder<O> {
return self.fetchChain(appending: Where<D>(format, argumentArray: argumentArray))
return self.fetchChain(appending: Where<O>(format, argumentArray: argumentArray))
}
/**
@@ -72,7 +72,7 @@ extension From {
- parameter clause: the `OrderBy` clause to create a `FetchChainBuilder` with
- returns: a `FetchChainBuilder` that starts with the specified `OrderBy` clause
*/
public func orderBy(_ clause: OrderBy<D>) -> FetchChainBuilder<D> {
public func orderBy(_ clause: OrderBy<O>) -> FetchChainBuilder<O> {
return self.fetchChain(appending: clause)
}
@@ -84,9 +84,9 @@ extension From {
- parameter sortKeys: a series of other `SortKey`s
- returns: a `FetchChainBuilder` with a series of `SortKey`s
*/
public func orderBy(_ sortKey: OrderBy<D>.SortKey, _ sortKeys: OrderBy<D>.SortKey...) -> FetchChainBuilder<D> {
public func orderBy(_ sortKey: OrderBy<O>.SortKey, _ sortKeys: OrderBy<O>.SortKey...) -> FetchChainBuilder<O> {
return self.fetchChain(appending: OrderBy<D>([sortKey] + sortKeys))
return self.fetchChain(appending: OrderBy<O>([sortKey] + sortKeys))
}
/**
@@ -95,9 +95,9 @@ extension From {
- parameter sortKeys: a series of `SortKey`s
- returns: a `FetchChainBuilder` with a series of `SortKey`s
*/
public func orderBy(_ sortKeys: [OrderBy<D>.SortKey]) -> FetchChainBuilder<D> {
public func orderBy(_ sortKeys: [OrderBy<O>.SortKey]) -> FetchChainBuilder<O> {
return self.fetchChain(appending: OrderBy<D>(sortKeys))
return self.fetchChain(appending: OrderBy<O>(sortKeys))
}
/**
@@ -106,7 +106,7 @@ extension From {
- parameter fetchRequest: the block to customize the `NSFetchRequest`
- returns: a `FetchChainBuilder` with closure where the `NSFetchRequest` may be configured
*/
public func tweak(_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void) -> FetchChainBuilder<D> {
public func tweak(_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void) -> FetchChainBuilder<O> {
return self.fetchChain(appending: Tweak(fetchRequest))
}
@@ -117,7 +117,7 @@ extension From {
- parameter clause: the `FetchClause` to add to the `FetchChainBuilder`
- returns: a `FetchChainBuilder` containing the specified `FetchClause`
*/
public func appending(_ clause: FetchClause) -> FetchChainBuilder<D> {
public func appending(_ clause: FetchClause) -> FetchChainBuilder<O> {
return self.fetchChain(appending: clause)
}
@@ -128,7 +128,7 @@ extension From {
- parameter clauses: the `FetchClause`s to add to the `FetchChainBuilder`
- returns: a `FetchChainBuilder` containing the specified `FetchClause`s
*/
public func appending<S: Sequence>(contentsOf clauses: S) -> FetchChainBuilder<D> where S.Element == FetchClause {
public func appending<S: Sequence>(contentsOf clauses: S) -> FetchChainBuilder<O> where S.Element == FetchClause {
return self.fetchChain(appending: clauses)
}
@@ -139,7 +139,7 @@ extension From {
- parameter clause: the `Select` clause to create a `QueryChainBuilder` with
- returns: a `QueryChainBuilder` that starts with the specified `Select` clause
*/
public func select<R>(_ clause: Select<D, R>) -> QueryChainBuilder<D, R> {
public func select<R>(_ clause: Select<O, R>) -> QueryChainBuilder<O, R> {
return .init(
from: self,
@@ -156,7 +156,7 @@ extension From {
- parameter selectTerms: a series of `SelectTerm`s
- returns: a `QueryChainBuilder` that starts with a `Select` clause created from the specified `SelectTerm`s
*/
public func select<R>(_ resultType: R.Type, _ selectTerm: SelectTerm<D>, _ selectTerms: SelectTerm<D>...) -> QueryChainBuilder<D, R> {
public func select<R>(_ resultType: R.Type, _ selectTerm: SelectTerm<O>, _ selectTerms: SelectTerm<O>...) -> QueryChainBuilder<O, R> {
return self.select(resultType, [selectTerm] + selectTerms)
}
@@ -168,7 +168,7 @@ extension From {
- parameter selectTerms: a series of `SelectTerm`s
- returns: a `QueryChainBuilder` that starts with a `Select` clause created from the specified `SelectTerm`s
*/
public func select<R>(_ resultType: R.Type, _ selectTerms: [SelectTerm<D>]) -> QueryChainBuilder<D, R> {
public func select<R>(_ resultType: R.Type, _ selectTerms: [SelectTerm<O>]) -> QueryChainBuilder<O, R> {
return .init(
from: self,
@@ -184,7 +184,7 @@ extension From {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy(_ clause: SectionBy<D>) -> SectionMonitorChainBuilder<D> {
public func sectionBy(_ clause: SectionBy<O>) -> SectionMonitorChainBuilder<O> {
return .init(
from: self,
@@ -200,7 +200,7 @@ extension From {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy(_ sectionKeyPath: KeyPathString) -> SectionMonitorChainBuilder<D> {
public func sectionBy(_ sectionKeyPath: KeyPathString) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(sectionKeyPath, { $0 })
}
@@ -214,7 +214,7 @@ extension From {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy(_ sectionKeyPath: KeyPathString, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<D> {
public func sectionBy(_ sectionKeyPath: KeyPathString, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
return .init(
from: self,
@@ -226,21 +226,21 @@ extension From {
// MARK: Private
private func fetchChain(appending clause: FetchClause) -> FetchChainBuilder<D> {
private func fetchChain(appending clause: FetchClause) -> FetchChainBuilder<O> {
return .init(from: self, fetchClauses: [clause])
}
private func fetchChain<S: Sequence>(appending clauses: S) -> FetchChainBuilder<D> where S.Element == FetchClause {
private func fetchChain<S: Sequence>(appending clauses: S) -> FetchChainBuilder<O> where S.Element == FetchClause {
return .init(from: self, fetchClauses: Array(clauses))
}
}
// MARK: - From where D: NSManagedObject
// MARK: - From where O: NSManagedObject
extension From where D: NSManagedObject {
extension From where O: NSManagedObject {
/**
Creates a `QueryChainBuilder` that starts with a `Select` clause created from the specified key path
@@ -248,9 +248,9 @@ extension From where D: NSManagedObject {
- parameter keyPath: the keyPath to query the value for
- returns: a `QueryChainBuilder` that starts with a `Select` clause created from the specified key path
*/
public func select<R>(_ keyPath: KeyPath<D, R>) -> QueryChainBuilder<D, R> {
public func select<R>(_ keyPath: KeyPath<O, R>) -> QueryChainBuilder<O, R> {
return self.select(R.self, [SelectTerm<D>.attribute(keyPath)])
return self.select(R.self, [SelectTerm<O>.attribute(keyPath)])
}
/**
@@ -260,7 +260,7 @@ extension From where D: NSManagedObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, T>) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, T>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(sectionKeyPath._kvcKeyPathString!, { $0 })
}
@@ -274,16 +274,16 @@ extension From where D: NSManagedObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, T>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, T>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(sectionKeyPath._kvcKeyPathString!, sectionIndexTransformer)
}
}
// MARK: - From where D: CoreStoreObject
// MARK: - From where O: CoreStoreObject
extension From where D: CoreStoreObject {
extension From where O: CoreStoreObject {
/**
Creates a `FetchChainBuilder` that starts with the specified `Where` clause
@@ -291,12 +291,12 @@ extension From where D: CoreStoreObject {
- parameter clause: a closure that returns a `Where` clause
- returns: a `FetchChainBuilder` that starts with the specified `Where` clause
*/
public func `where`<T: AnyWhereClause>(_ clause: (D) -> T) -> FetchChainBuilder<D> {
public func `where`<T: AnyWhereClause>(_ clause: (O) -> T) -> FetchChainBuilder<O> {
return self.fetchChain(appending: clause(D.meta))
return self.fetchChain(appending: clause(O.meta))
}
public func `where`(combinedByAnd clause: Where<D>, _ others: Where<D>...) -> FetchChainBuilder<D> {
public func `where`(combinedByAnd clause: Where<O>, _ others: Where<O>...) -> FetchChainBuilder<O> {
return self.fetchChain(appending: ([clause] + others).combinedByAnd())
}
@@ -307,9 +307,9 @@ extension From where D: CoreStoreObject {
- parameter keyPath: the keyPath to query the value for
- returns: a `QueryChainBuilder` that starts with a `Select` clause created from the specified key path
*/
public func select<R>(_ keyPath: KeyPath<D, ValueContainer<D>.Required<R>>) -> QueryChainBuilder<D, R> {
public func select<R>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<R>>) -> QueryChainBuilder<O, R> {
return self.select(R.self, [SelectTerm<D>.attribute(keyPath)])
return self.select(R.self, [SelectTerm<O>.attribute(keyPath)])
}
/**
@@ -318,9 +318,9 @@ extension From where D: CoreStoreObject {
- parameter keyPath: the keyPath to query the value for
- returns: a `QueryChainBuilder` that starts with a `Select` clause created from the specified key path
*/
public func select<R>(_ keyPath: KeyPath<D, ValueContainer<D>.Optional<R>>) -> QueryChainBuilder<D, R> {
public func select<R>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<R>>) -> QueryChainBuilder<O, R> {
return self.select(R.self, [SelectTerm<D>.attribute(keyPath)])
return self.select(R.self, [SelectTerm<O>.attribute(keyPath)])
}
/**
@@ -329,9 +329,9 @@ extension From where D: CoreStoreObject {
- parameter keyPath: the keyPath to query the value for
- returns: a `QueryChainBuilder` that starts with a `Select` clause created from the specified key path
*/
public func select<R>(_ keyPath: KeyPath<D, TransformableContainer<D>.Required<R>>) -> QueryChainBuilder<D, R> {
public func select<R>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<R>>) -> QueryChainBuilder<O, R> {
return self.select(R.self, [SelectTerm<D>.attribute(keyPath)])
return self.select(R.self, [SelectTerm<O>.attribute(keyPath)])
}
/**
@@ -340,9 +340,9 @@ extension From where D: CoreStoreObject {
- parameter keyPath: the keyPath to query the value for
- returns: a `QueryChainBuilder` that starts with a `Select` clause created from the specified key path
*/
public func select<R>(_ keyPath: KeyPath<D, TransformableContainer<D>.Optional<R>>) -> QueryChainBuilder<D, R> {
public func select<R>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<R>>) -> QueryChainBuilder<O, R> {
return self.select(R.self, [SelectTerm<D>.attribute(keyPath)])
return self.select(R.self, [SelectTerm<O>.attribute(keyPath)])
}
/**
@@ -352,9 +352,9 @@ extension From where D: CoreStoreObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, ValueContainer<D>.Required<T>>) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(D.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
}
/**
@@ -364,9 +364,9 @@ extension From where D: CoreStoreObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, ValueContainer<D>.Optional<T>>) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(D.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
}
/**
@@ -376,9 +376,9 @@ extension From where D: CoreStoreObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, TransformableContainer<D>.Required<T>>) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(D.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
}
/**
@@ -388,9 +388,9 @@ extension From where D: CoreStoreObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, TransformableContainer<D>.Optional<T>>) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(D.meta[keyPath: sectionKeyPath].keyPath, { $0 })
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, { $0 })
}
/**
@@ -402,9 +402,9 @@ extension From where D: CoreStoreObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, ValueContainer<D>.Required<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Required<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(D.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
@@ -416,9 +416,9 @@ extension From where D: CoreStoreObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, ValueContainer<D>.Optional<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, ValueContainer<O>.Optional<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(D.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
@@ -430,9 +430,9 @@ extension From where D: CoreStoreObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, TransformableContainer<D>.Required<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Required<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(D.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
/**
@@ -444,9 +444,9 @@ extension From where D: CoreStoreObject {
- returns: a `SectionMonitorChainBuilder` that is sectioned by the specified key path
*/
@available(macOS 10.12, *)
public func sectionBy<T>(_ sectionKeyPath: KeyPath<D, TransformableContainer<D>.Optional<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<D> {
public func sectionBy<T>(_ sectionKeyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>, _ sectionIndexTransformer: @escaping (_ sectionName: String?) -> String?) -> SectionMonitorChainBuilder<O> {
return self.sectionBy(D.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
return self.sectionBy(O.meta[keyPath: sectionKeyPath].keyPath, sectionIndexTransformer)
}
}
@@ -461,7 +461,7 @@ extension FetchChainBuilder {
- parameter clause: a `Where` clause to add to the fetch builder
- returns: a new `FetchChainBuilder` containing the `Where` clause
*/
public func `where`(_ clause: Where<D>) -> FetchChainBuilder<D> {
public func `where`(_ clause: Where<O>) -> FetchChainBuilder<O> {
return self.fetchChain(appending: clause)
}
@@ -473,9 +473,9 @@ extension FetchChainBuilder {
- parameter args: the arguments for `format`
- returns: a new `FetchChainBuilder` containing the `Where` clause
*/
public func `where`(format: String, _ args: Any...) -> FetchChainBuilder<D> {
public func `where`(format: String, _ args: Any...) -> FetchChainBuilder<O> {
return self.fetchChain(appending: Where<D>(format, argumentArray: args))
return self.fetchChain(appending: Where<O>(format, argumentArray: args))
}
/**
@@ -485,9 +485,9 @@ extension FetchChainBuilder {
- parameter argumentArray: the arguments for `format`
- returns: a new `FetchChainBuilder` containing the `Where` clause
*/
public func `where`(format: String, argumentArray: [Any]?) -> FetchChainBuilder<D> {
public func `where`(format: String, argumentArray: [Any]?) -> FetchChainBuilder<O> {
return self.fetchChain(appending: Where<D>(format, argumentArray: argumentArray))
return self.fetchChain(appending: Where<O>(format, argumentArray: argumentArray))
}
/**
@@ -496,7 +496,7 @@ extension FetchChainBuilder {
- parameter clause: the `OrderBy` clause to add
- returns: a new `FetchChainBuilder` containing the `OrderBy` clause
*/
public func orderBy(_ clause: OrderBy<D>) -> FetchChainBuilder<D> {
public func orderBy(_ clause: OrderBy<O>) -> FetchChainBuilder<O> {
return self.fetchChain(appending: clause)
}
@@ -508,9 +508,9 @@ extension FetchChainBuilder {
- parameter sortKeys: a series of other `SortKey`s
- returns: a new `FetchChainBuilder` containing the `OrderBy` clause
*/
public func orderBy(_ sortKey: OrderBy<D>.SortKey, _ sortKeys: OrderBy<D>.SortKey...) -> FetchChainBuilder<D> {
public func orderBy(_ sortKey: OrderBy<O>.SortKey, _ sortKeys: OrderBy<O>.SortKey...) -> FetchChainBuilder<O> {
return self.fetchChain(appending: OrderBy<D>([sortKey] + sortKeys))
return self.fetchChain(appending: OrderBy<O>([sortKey] + sortKeys))
}
/**
@@ -519,9 +519,9 @@ extension FetchChainBuilder {
- parameter sortKeys: a series of `SortKey`s
- returns: a new `FetchChainBuilder` containing the `OrderBy` clause
*/
public func orderBy(_ sortKeys: [OrderBy<D>.SortKey]) -> FetchChainBuilder<D> {
public func orderBy(_ sortKeys: [OrderBy<O>.SortKey]) -> FetchChainBuilder<O> {
return self.fetchChain(appending: OrderBy<D>(sortKeys))
return self.fetchChain(appending: OrderBy<O>(sortKeys))
}
/**
@@ -530,7 +530,7 @@ extension FetchChainBuilder {
- parameter fetchRequest: the block to customize the `NSFetchRequest`
- returns: a new `FetchChainBuilder` containing the `Tweak` clause
*/
public func tweak(_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void) -> FetchChainBuilder<D> {
public func tweak(_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void) -> FetchChainBuilder<O> {
return self.fetchChain(appending: Tweak(fetchRequest))
}
@@ -541,7 +541,7 @@ extension FetchChainBuilder {
- parameter clause: the `FetchClause` to add to the `FetchChainBuilder`
- returns: a new `FetchChainBuilder` containing the `FetchClause`
*/
public func appending(_ clause: FetchClause) -> FetchChainBuilder<D> {
public func appending(_ clause: FetchClause) -> FetchChainBuilder<O> {
return self.fetchChain(appending: clause)
}
@@ -552,7 +552,7 @@ extension FetchChainBuilder {
- parameter clauses: the `FetchClause`s to add to the `FetchChainBuilder`
- returns: a new `FetchChainBuilder` containing the `FetchClause`s
*/
public func appending<S: Sequence>(contentsOf clauses: S) -> FetchChainBuilder<D> where S.Element == FetchClause {
public func appending<S: Sequence>(contentsOf clauses: S) -> FetchChainBuilder<O> where S.Element == FetchClause {
return self.fetchChain(appending: clauses)
}
@@ -560,7 +560,7 @@ extension FetchChainBuilder {
// MARK: Private
private func fetchChain(appending clause: FetchClause) -> FetchChainBuilder<D> {
private func fetchChain(appending clause: FetchClause) -> FetchChainBuilder<O> {
return .init(
from: self.from,
@@ -568,7 +568,7 @@ extension FetchChainBuilder {
)
}
private func fetchChain<S: Sequence>(appending clauses: S) -> FetchChainBuilder<D> where S.Element == FetchClause {
private func fetchChain<S: Sequence>(appending clauses: S) -> FetchChainBuilder<O> where S.Element == FetchClause {
return .init(
from: self.from,
@@ -578,13 +578,13 @@ extension FetchChainBuilder {
}
// MARK: - FetchChainBuilder where D: CoreStoreObject
// MARK: - FetchChainBuilder where O: CoreStoreObject
extension FetchChainBuilder where D: CoreStoreObject {
extension FetchChainBuilder where O: CoreStoreObject {
public func `where`<T: AnyWhereClause>(_ clause: (D) -> T) -> FetchChainBuilder<D> {
public func `where`<T: AnyWhereClause>(_ clause: (O) -> T) -> FetchChainBuilder<O> {
return self.fetchChain(appending: clause(D.meta))
return self.fetchChain(appending: clause(O.meta))
}
}
@@ -599,7 +599,7 @@ extension QueryChainBuilder {
- parameter clause: a `Where` clause to add to the query builder
- returns: a new `QueryChainBuilder` containing the `Where` clause
*/
public func `where`(_ clause: Where<D>) -> QueryChainBuilder<D, R> {
public func `where`(_ clause: Where<O>) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: clause)
}
@@ -611,9 +611,9 @@ extension QueryChainBuilder {
- parameter args: the arguments for `format`
- returns: a new `QueryChainBuilder` containing the `Where` clause
*/
public func `where`(format: String, _ args: Any...) -> QueryChainBuilder<D, R> {
public func `where`(format: String, _ args: Any...) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: Where<D>(format, argumentArray: args))
return self.queryChain(appending: Where<O>(format, argumentArray: args))
}
/**
@@ -623,9 +623,9 @@ extension QueryChainBuilder {
- parameter argumentArray: the arguments for `format`
- returns: a new `QueryChainBuilder` containing the `Where` clause
*/
public func `where`(format: String, argumentArray: [Any]?) -> QueryChainBuilder<D, R> {
public func `where`(format: String, argumentArray: [Any]?) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: Where<D>(format, argumentArray: argumentArray))
return self.queryChain(appending: Where<O>(format, argumentArray: argumentArray))
}
/**
@@ -634,7 +634,7 @@ extension QueryChainBuilder {
- parameter clause: the `OrderBy` clause to add
- returns: a new `QueryChainBuilder` containing the `OrderBy` clause
*/
public func orderBy(_ clause: OrderBy<D>) -> QueryChainBuilder<D, R> {
public func orderBy(_ clause: OrderBy<O>) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: clause)
}
@@ -646,9 +646,9 @@ extension QueryChainBuilder {
- parameter sortKeys: a series of other `SortKey`s
- returns: a new `QueryChainBuilder` containing the `OrderBy` clause
*/
public func orderBy(_ sortKey: OrderBy<D>.SortKey, _ sortKeys: OrderBy<D>.SortKey...) -> QueryChainBuilder<D, R> {
public func orderBy(_ sortKey: OrderBy<O>.SortKey, _ sortKeys: OrderBy<O>.SortKey...) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: OrderBy<D>([sortKey] + sortKeys))
return self.queryChain(appending: OrderBy<O>([sortKey] + sortKeys))
}
/**
@@ -657,9 +657,9 @@ extension QueryChainBuilder {
- parameter sortKeys: a series of `SortKey`s
- returns: a new `QueryChainBuilder` containing the `OrderBy` clause
*/
public func orderBy(_ sortKeys: [OrderBy<D>.SortKey]) -> QueryChainBuilder<D, R> {
public func orderBy(_ sortKeys: [OrderBy<O>.SortKey]) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: OrderBy<D>(sortKeys))
return self.queryChain(appending: OrderBy<O>(sortKeys))
}
/**
@@ -668,7 +668,7 @@ extension QueryChainBuilder {
- parameter fetchRequest: the block to customize the `NSFetchRequest`
- returns: a new `QueryChainBuilder` containing the `Tweak` clause
*/
public func tweak(_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void) -> QueryChainBuilder<D, R> {
public func tweak(_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: Tweak(fetchRequest))
}
@@ -679,7 +679,7 @@ extension QueryChainBuilder {
- parameter clause: a `GroupBy` clause to add to the query builder
- returns: a new `QueryChainBuilder` containing the `GroupBy` clause
*/
public func groupBy(_ clause: GroupBy<D>) -> QueryChainBuilder<D, R> {
public func groupBy(_ clause: GroupBy<O>) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: clause)
}
@@ -691,9 +691,9 @@ extension QueryChainBuilder {
- parameter keyPaths: other key paths to group the query results with
- returns: a new `QueryChainBuilder` containing the `GroupBy` clause
*/
public func groupBy(_ keyPath: KeyPathString, _ keyPaths: KeyPathString...) -> QueryChainBuilder<D, R> {
public func groupBy(_ keyPath: KeyPathString, _ keyPaths: KeyPathString...) -> QueryChainBuilder<O, R> {
return self.groupBy(GroupBy<D>([keyPath] + keyPaths))
return self.groupBy(GroupBy<O>([keyPath] + keyPaths))
}
/**
@@ -702,9 +702,9 @@ extension QueryChainBuilder {
- parameter keyPaths: a series of key paths to group the query results with
- returns: a new `QueryChainBuilder` containing the `GroupBy` clause
*/
public func groupBy(_ keyPaths: [KeyPathString]) -> QueryChainBuilder<D, R> {
public func groupBy(_ keyPaths: [KeyPathString]) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: GroupBy<D>(keyPaths))
return self.queryChain(appending: GroupBy<O>(keyPaths))
}
/**
@@ -713,7 +713,7 @@ extension QueryChainBuilder {
- parameter clause: the `QueryClause` to add to the `QueryChainBuilder`
- returns: a new `QueryChainBuilder` containing the `QueryClause`
*/
public func appending(_ clause: QueryClause) -> QueryChainBuilder<D, R> {
public func appending(_ clause: QueryClause) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: clause)
}
@@ -724,7 +724,7 @@ extension QueryChainBuilder {
- parameter clauses: the `QueryClause`s to add to the `QueryChainBuilder`
- returns: a new `QueryChainBuilder` containing the `QueryClause`s
*/
public func appending<S: Sequence>(contentsOf clauses: S) -> QueryChainBuilder<D, R> where S.Element == QueryClause {
public func appending<S: Sequence>(contentsOf clauses: S) -> QueryChainBuilder<O, R> where S.Element == QueryClause {
return self.queryChain(appending: clauses)
}
@@ -732,7 +732,7 @@ extension QueryChainBuilder {
// MARK: Private
private func queryChain(appending clause: QueryClause) -> QueryChainBuilder<D, R> {
private func queryChain(appending clause: QueryClause) -> QueryChainBuilder<O, R> {
return .init(
from: self.from,
@@ -741,7 +741,7 @@ extension QueryChainBuilder {
)
}
private func queryChain<S: Sequence>(appending clauses: S) -> QueryChainBuilder<D, R> where S.Element == QueryClause {
private func queryChain<S: Sequence>(appending clauses: S) -> QueryChainBuilder<O, R> where S.Element == QueryClause {
return .init(
from: self.from,
@@ -752,9 +752,9 @@ extension QueryChainBuilder {
}
// MARK: - QueryChainBuilder where D: NSManagedObject
// MARK: - QueryChainBuilder where O: NSManagedObject
extension QueryChainBuilder where D: NSManagedObject {
extension QueryChainBuilder where O: NSManagedObject {
/**
Adds a `GroupBy` clause to the `QueryChainBuilder`
@@ -762,16 +762,16 @@ extension QueryChainBuilder where D: NSManagedObject {
- parameter keyPath: a key path to group the query results with
- returns: a new `QueryChainBuilder` containing the `GroupBy` clause
*/
public func groupBy<T>(_ keyPath: KeyPath<D, T>) -> QueryChainBuilder<D, R> {
public func groupBy<T>(_ keyPath: KeyPath<O, T>) -> QueryChainBuilder<O, R> {
return self.groupBy(GroupBy<D>(keyPath))
return self.groupBy(GroupBy<O>(keyPath))
}
}
// MARK: - QueryChainBuilder where D: CoreStoreObject
// MARK: - QueryChainBuilder where O: CoreStoreObject
extension QueryChainBuilder where D: CoreStoreObject {
extension QueryChainBuilder where O: CoreStoreObject {
/**
Adds a `Where` clause to the `QueryChainBuilder`
@@ -779,9 +779,9 @@ extension QueryChainBuilder where D: CoreStoreObject {
- parameter clause: a `Where` clause to add to the query builder
- returns: a new `QueryChainBuilder` containing the `Where` clause
*/
public func `where`<T: AnyWhereClause>(_ clause: (D) -> T) -> QueryChainBuilder<D, R> {
public func `where`<T: AnyWhereClause>(_ clause: (O) -> T) -> QueryChainBuilder<O, R> {
return self.queryChain(appending: clause(D.meta))
return self.queryChain(appending: clause(O.meta))
}
/**
@@ -790,9 +790,9 @@ extension QueryChainBuilder where D: CoreStoreObject {
- parameter keyPath: a key path to group the query results with
- returns: a new `QueryChainBuilder` containing the `GroupBy` clause
*/
public func groupBy<T>(_ keyPath: KeyPath<D, ValueContainer<D>.Required<T>>) -> QueryChainBuilder<D, R> {
public func groupBy<T>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<T>>) -> QueryChainBuilder<O, R> {
return self.groupBy(GroupBy<D>(keyPath))
return self.groupBy(GroupBy<O>(keyPath))
}
/**
@@ -801,9 +801,9 @@ extension QueryChainBuilder where D: CoreStoreObject {
- parameter keyPath: a key path to group the query results with
- returns: a new `QueryChainBuilder` containing the `GroupBy` clause
*/
public func groupBy<T>(_ keyPath: KeyPath<D, ValueContainer<D>.Optional<T>>) -> QueryChainBuilder<D, R> {
public func groupBy<T>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<T>>) -> QueryChainBuilder<O, R> {
return self.groupBy(GroupBy<D>(keyPath))
return self.groupBy(GroupBy<O>(keyPath))
}
/**
@@ -812,9 +812,9 @@ extension QueryChainBuilder where D: CoreStoreObject {
- parameter keyPath: a key path to group the query results with
- returns: a new `QueryChainBuilder` containing the `GroupBy` clause
*/
public func groupBy<T>(_ keyPath: KeyPath<D, TransformableContainer<D>.Required<T>>) -> QueryChainBuilder<D, R> {
public func groupBy<T>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<T>>) -> QueryChainBuilder<O, R> {
return self.groupBy(GroupBy<D>(keyPath))
return self.groupBy(GroupBy<O>(keyPath))
}
/**
@@ -823,9 +823,9 @@ extension QueryChainBuilder where D: CoreStoreObject {
- parameter keyPath: a key path to group the query results with
- returns: a new `QueryChainBuilder` containing the `GroupBy` clause
*/
public func groupBy<T>(_ keyPath: KeyPath<D, TransformableContainer<D>.Optional<T>>) -> QueryChainBuilder<D, R> {
public func groupBy<T>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>) -> QueryChainBuilder<O, R> {
return self.groupBy(GroupBy<D>(keyPath))
return self.groupBy(GroupBy<O>(keyPath))
}
}
@@ -841,7 +841,7 @@ extension SectionMonitorChainBuilder {
- parameter clause: a `Where` clause to add to the fetch builder
- returns: a new `SectionMonitorChainBuilder` containing the `Where` clause
*/
public func `where`(_ clause: Where<D>) -> SectionMonitorChainBuilder<D> {
public func `where`(_ clause: Where<O>) -> SectionMonitorChainBuilder<O> {
return self.sectionMonitorChain(appending: clause)
}
@@ -853,9 +853,9 @@ extension SectionMonitorChainBuilder {
- parameter args: the arguments for `format`
- returns: a new `SectionMonitorChainBuilder` containing the `Where` clause
*/
public func `where`(format: String, _ args: Any...) -> SectionMonitorChainBuilder<D> {
public func `where`(format: String, _ args: Any...) -> SectionMonitorChainBuilder<O> {
return self.sectionMonitorChain(appending: Where<D>(format, argumentArray: args))
return self.sectionMonitorChain(appending: Where<O>(format, argumentArray: args))
}
/**
@@ -865,9 +865,9 @@ extension SectionMonitorChainBuilder {
- parameter argumentArray: the arguments for `format`
- returns: a new `SectionMonitorChainBuilder` containing the `Where` clause
*/
public func `where`(format: String, argumentArray: [Any]?) -> SectionMonitorChainBuilder<D> {
public func `where`(format: String, argumentArray: [Any]?) -> SectionMonitorChainBuilder<O> {
return self.sectionMonitorChain(appending: Where<D>(format, argumentArray: argumentArray))
return self.sectionMonitorChain(appending: Where<O>(format, argumentArray: argumentArray))
}
/**
@@ -876,7 +876,7 @@ extension SectionMonitorChainBuilder {
- parameter clause: the `OrderBy` clause to add
- returns: a new `SectionMonitorChainBuilder` containing the `OrderBy` clause
*/
public func orderBy(_ clause: OrderBy<D>) -> SectionMonitorChainBuilder<D> {
public func orderBy(_ clause: OrderBy<O>) -> SectionMonitorChainBuilder<O> {
return self.sectionMonitorChain(appending: clause)
}
@@ -888,9 +888,9 @@ extension SectionMonitorChainBuilder {
- parameter sortKeys: a series of other `SortKey`s
- returns: a new `SectionMonitorChainBuilder` containing the `OrderBy` clause
*/
public func orderBy(_ sortKey: OrderBy<D>.SortKey, _ sortKeys: OrderBy<D>.SortKey...) -> SectionMonitorChainBuilder<D> {
public func orderBy(_ sortKey: OrderBy<O>.SortKey, _ sortKeys: OrderBy<O>.SortKey...) -> SectionMonitorChainBuilder<O> {
return self.sectionMonitorChain(appending: OrderBy<D>([sortKey] + sortKeys))
return self.sectionMonitorChain(appending: OrderBy<O>([sortKey] + sortKeys))
}
/**
@@ -899,9 +899,9 @@ extension SectionMonitorChainBuilder {
- parameter sortKeys: a series of `SortKey`s
- returns: a new `SectionMonitorChainBuilder` containing the `OrderBy` clause
*/
public func orderBy(_ sortKeys: [OrderBy<D>.SortKey]) -> SectionMonitorChainBuilder<D> {
public func orderBy(_ sortKeys: [OrderBy<O>.SortKey]) -> SectionMonitorChainBuilder<O> {
return self.sectionMonitorChain(appending: OrderBy<D>(sortKeys))
return self.sectionMonitorChain(appending: OrderBy<O>(sortKeys))
}
/**
@@ -910,7 +910,7 @@ extension SectionMonitorChainBuilder {
- parameter fetchRequest: the block to customize the `NSFetchRequest`
- returns: a new `SectionMonitorChainBuilder` containing the `Tweak` clause
*/
public func tweak(_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void) -> SectionMonitorChainBuilder<D> {
public func tweak(_ fetchRequest: @escaping (NSFetchRequest<NSFetchRequestResult>) -> Void) -> SectionMonitorChainBuilder<O> {
return self.sectionMonitorChain(appending: Tweak(fetchRequest))
}
@@ -921,7 +921,7 @@ extension SectionMonitorChainBuilder {
- parameter clause: the `QueryClause` to add to the `SectionMonitorChainBuilder`
- returns: a new `SectionMonitorChainBuilder` containing the `QueryClause`
*/
public func appending(_ clause: FetchClause) -> SectionMonitorChainBuilder<D> {
public func appending(_ clause: FetchClause) -> SectionMonitorChainBuilder<O> {
return self.sectionMonitorChain(appending: clause)
}
@@ -932,7 +932,7 @@ extension SectionMonitorChainBuilder {
- parameter clauses: the `QueryClause`s to add to the `SectionMonitorChainBuilder`
- returns: a new `SectionMonitorChainBuilder` containing the `QueryClause`s
*/
public func appending<S: Sequence>(contentsOf clauses: S) -> SectionMonitorChainBuilder<D> where S.Element == FetchClause {
public func appending<S: Sequence>(contentsOf clauses: S) -> SectionMonitorChainBuilder<O> where S.Element == FetchClause {
return self.sectionMonitorChain(appending: clauses)
}
@@ -940,7 +940,7 @@ extension SectionMonitorChainBuilder {
// MARK: Private
private func sectionMonitorChain(appending clause: FetchClause) -> SectionMonitorChainBuilder<D> {
private func sectionMonitorChain(appending clause: FetchClause) -> SectionMonitorChainBuilder<O> {
return .init(
from: self.from,
@@ -949,7 +949,7 @@ extension SectionMonitorChainBuilder {
)
}
private func sectionMonitorChain<S: Sequence>(appending clauses: S) -> SectionMonitorChainBuilder<D> where S.Element == FetchClause {
private func sectionMonitorChain<S: Sequence>(appending clauses: S) -> SectionMonitorChainBuilder<O> where S.Element == FetchClause {
return .init(
from: self.from,
@@ -960,10 +960,10 @@ extension SectionMonitorChainBuilder {
}
// MARK: - SectionMonitorChainBuilder where D: CoreStoreObject
// MARK: - SectionMonitorChainBuilder where O: CoreStoreObject
@available(macOS 10.12, *)
extension SectionMonitorChainBuilder where D: CoreStoreObject {
extension SectionMonitorChainBuilder where O: CoreStoreObject {
/**
Adds a `Where` clause to the `SectionMonitorChainBuilder`
@@ -971,8 +971,8 @@ extension SectionMonitorChainBuilder where D: CoreStoreObject {
- parameter clause: a `Where` clause to add to the fetch builder
- returns: a new `SectionMonitorChainBuilder` containing the `Where` clause
*/
public func `where`<T: AnyWhereClause>(_ clause: (D) -> T) -> SectionMonitorChainBuilder<D> {
public func `where`<T: AnyWhereClause>(_ clause: (O) -> T) -> SectionMonitorChainBuilder<O> {
return self.sectionMonitorChain(appending: clause(D.meta))
return self.sectionMonitorChain(appending: clause(O.meta))
}
}

View File

@@ -39,12 +39,12 @@ import CoreData
let person = transaction.fetchOne(From<Person>("Configuration1"))
```
*/
public struct From<D: DynamicObject> {
public struct From<O: DynamicObject> {
/**
The associated `NSManagedObject` or `CoreStoreObject` entity class
*/
public let entityClass: D.Type
public let entityClass: O.Type
/**
The `NSPersistentStore` configuration names to associate objects from.
@@ -60,7 +60,7 @@ public struct From<D: DynamicObject> {
*/
public init() {
self.init(entityClass: D.self, configurations: nil)
self.init(entityClass: O.self, configurations: nil)
}
/**
@@ -70,7 +70,7 @@ public struct From<D: DynamicObject> {
```
- parameter entity: the associated `NSManagedObject` or `CoreStoreObject` type
*/
public init(_ entity: D.Type) {
public init(_ entity: O.Type) {
self.init(entityClass: entity, configurations: nil)
}
@@ -85,7 +85,7 @@ public struct From<D: DynamicObject> {
*/
public init(_ configuration: ModelConfiguration, _ otherConfigurations: ModelConfiguration...) {
self.init(entityClass: D.self, configurations: [configuration] + otherConfigurations)
self.init(entityClass: O.self, configurations: [configuration] + otherConfigurations)
}
/**
@@ -97,7 +97,7 @@ public struct From<D: DynamicObject> {
*/
public init(_ configurations: [ModelConfiguration]) {
self.init(entityClass: D.self, configurations: configurations)
self.init(entityClass: O.self, configurations: configurations)
}
/**
@@ -109,7 +109,7 @@ public struct From<D: DynamicObject> {
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject` or `CoreStoreObject`'s entity type. Set to `nil` to use the default configuration.
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
*/
public init(_ entity: D.Type, _ configuration: ModelConfiguration, _ otherConfigurations: ModelConfiguration...) {
public init(_ entity: O.Type, _ configuration: ModelConfiguration, _ otherConfigurations: ModelConfiguration...) {
self.init(entityClass: entity, configurations: [configuration] + otherConfigurations)
}
@@ -122,7 +122,7 @@ public struct From<D: DynamicObject> {
- parameter entity: the associated `NSManagedObject` or `CoreStoreObject` type
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject` or `CoreStoreObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ entity: D.Type, _ configurations: [ModelConfiguration]) {
public init(_ entity: O.Type, _ configurations: [ModelConfiguration]) {
self.init(entityClass: entity, configurations: configurations)
}
@@ -132,7 +132,7 @@ public struct From<D: DynamicObject> {
internal let findPersistentStores: (_ context: NSManagedObjectContext) -> [NSPersistentStore]?
internal init(entityClass: D.Type, configurations: [ModelConfiguration]?, findPersistentStores: @escaping (_ context: NSManagedObjectContext) -> [NSPersistentStore]?) {
internal init(entityClass: O.Type, configurations: [ModelConfiguration]?, findPersistentStores: @escaping (_ context: NSManagedObjectContext) -> [NSPersistentStore]?) {
self.entityClass = entityClass
self.configurations = configurations
@@ -186,7 +186,7 @@ public struct From<D: DynamicObject> {
// MARK: Private
private init(entityClass: D.Type, configurations: [ModelConfiguration]?) {
private init(entityClass: O.Type, configurations: [ModelConfiguration]?) {
self.entityClass = entityClass
self.configurations = configurations
@@ -211,4 +211,10 @@ public struct From<D: DynamicObject> {
}
}
}
// MARK: Deprecated
@available(*, deprecated, renamed: "O")
public typealias D = O
}

View File

@@ -1,111 +0,0 @@
//
// Functions.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: Associated Objects
@inline(__always)
internal func cs_getAssociatedObjectForKey<T: AnyObject>(_ key: UnsafeRawPointer, inObject object: Any) -> T? {
switch objc_getAssociatedObject(object, key) {
case let associatedObject as T:
return associatedObject
case let associatedObject as WeakObject:
return associatedObject.object as? T
default:
return nil
}
}
@inline(__always)
internal func cs_setAssociatedRetainedObject<T: AnyObject>(_ associatedObject: T?, forKey key: UnsafeRawPointer, inObject object: Any) {
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
@inline(__always)
internal func cs_setAssociatedCopiedObject<T: AnyObject>(_ associatedObject: T?, forKey key: UnsafeRawPointer, inObject object: Any) {
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
@inline(__always)
internal func cs_setAssociatedWeakObject<T: AnyObject>(_ associatedObject: T?, forKey key: UnsafeRawPointer, inObject object: Any) {
if let associatedObject = associatedObject {
objc_setAssociatedObject(object, key, WeakObject(associatedObject), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
else {
objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
// MARK: Printing Utilities
@inline(__always)
internal func cs_typeName<T>(_ value: T) -> String {
return "'\(String(reflecting: type(of: value)))'"
}
@inline(__always)
internal func cs_typeName<T>(_ value: T.Type) -> String {
return "'\(value)'"
}
@inline(__always)
internal func cs_typeName(_ value: AnyClass) -> String {
return "'\(value)'"
}
@inline(__always)
internal func cs_typeName(_ name: String) -> String {
return "<\(name)>"
}
@inline(__always)
internal func cs_typeName(_ name: String?) -> String {
return "<\(name ?? "unknown")>"
}
// MARK: Functional
@inline(__always)
internal func cs_lazy<T>(_ closure: () -> T) -> T {
return closure()
}

View File

@@ -32,7 +32,7 @@ import CoreData
/**
The `GroupBy` clause specifies that the result of a query be grouped accoording to the specified key path.
*/
public struct GroupBy<D: DynamicObject>: GroupByClause, QueryClause, Hashable {
public struct GroupBy<O: DynamicObject>: GroupByClause, QueryClause, Hashable {
/**
Initializes a `GroupBy` clause with an empty list of key path strings
@@ -66,7 +66,7 @@ public struct GroupBy<D: DynamicObject>: GroupByClause, QueryClause, Hashable {
// MARK: GroupByClause
public typealias ObjectType = D
public typealias ObjectType = O
public let keyPaths: [KeyPathString]
@@ -101,31 +101,37 @@ public struct GroupBy<D: DynamicObject>: GroupByClause, QueryClause, Hashable {
hasher.combine(self.keyPaths)
}
// MARK: Deprecated
@available(*, deprecated, renamed: "O")
public typealias D = O
}
extension GroupBy where D: NSManagedObject {
extension GroupBy where O: NSManagedObject {
/**
Initializes a `GroupBy` clause with a key path
- parameter keyPath: a key path to group results with
*/
public init<T>(_ keyPath: KeyPath<D, T>) {
public init<T>(_ keyPath: KeyPath<O, T>) {
self.init([keyPath._kvcKeyPathString!])
}
}
extension GroupBy where D: CoreStoreObject {
extension GroupBy where O: CoreStoreObject {
/**
Initializes a `GroupBy` clause with a key path
- parameter keyPath: a key path to group results with
*/
public init<T>(_ keyPath: KeyPath<D, ValueContainer<D>.Required<T>>) {
public init<T>(_ keyPath: KeyPath<O, ValueContainer<O>.Required<T>>) {
self.init([D.meta[keyPath: keyPath].keyPath])
self.init([O.meta[keyPath: keyPath].keyPath])
}
/**
@@ -133,9 +139,9 @@ extension GroupBy where D: CoreStoreObject {
- parameter keyPath: a key path to group results with
*/
public init<T>(_ keyPath: KeyPath<D, ValueContainer<D>.Optional<T>>) {
public init<T>(_ keyPath: KeyPath<O, ValueContainer<O>.Optional<T>>) {
self.init([D.meta[keyPath: keyPath].keyPath])
self.init([O.meta[keyPath: keyPath].keyPath])
}
/**
@@ -143,9 +149,9 @@ extension GroupBy where D: CoreStoreObject {
- parameter keyPath: a key path to group results with
*/
public init<T>(_ keyPath: KeyPath<D, TransformableContainer<D>.Required<T>>) {
public init<T>(_ keyPath: KeyPath<O, TransformableContainer<O>.Required<T>>) {
self.init([D.meta[keyPath: keyPath].keyPath])
self.init([O.meta[keyPath: keyPath].keyPath])
}
/**
@@ -153,9 +159,9 @@ extension GroupBy where D: CoreStoreObject {
- parameter keyPath: a key path to group results with
*/
public init<T>(_ keyPath: KeyPath<D, TransformableContainer<D>.Optional<T>>) {
public init<T>(_ keyPath: KeyPath<O, TransformableContainer<O>.Optional<T>>) {
self.init([D.meta[keyPath: keyPath].keyPath])
self.init([O.meta[keyPath: keyPath].keyPath])
}
}

View File

@@ -37,7 +37,7 @@ import CoreData
// ...
}
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let json: NSDictionary = // ...
let person = try transaction.importObject(

View File

@@ -38,7 +38,7 @@ import CoreData
// ...
}
CoreStore.perform(
dataStack.perform(
asynchronous: { (transaction) -> Void in
let json: NSDictionary = // ...
let person = try transaction.importUniqueObject(

View File

@@ -15,7 +15,7 @@
<key>CFBundlePackageType</key>
<string>FMWK</string>
<key>CFBundleShortVersionString</key>
<string>6.3.2</string>
<string>$(MARKETING_VERSION)</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>

View File

@@ -0,0 +1,54 @@
//
// Internals.Closure.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - Internals
extension Internals {
// MARK: - Closure
internal final class Closure<T, U> {
// MARK: FilePrivate
internal init(_ closure: @escaping (T) -> U) {
self.closure = closure
}
internal func invoke(with argument: T) -> U {
return self.closure(argument)
}
// MARK: Private
private let closure: (T) -> U
}
}

View File

@@ -42,7 +42,7 @@ extension Internals {
internal let typedFetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>
@nonobjc
internal convenience init<D>(dataStack: DataStack, fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>, from: From<D>, sectionBy: SectionBy<D>? = nil, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal convenience init<O>(dataStack: DataStack, fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>, from: From<O>, sectionBy: SectionBy<O>? = nil, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
self.init(
context: dataStack.mainContext,
@@ -54,7 +54,7 @@ extension Internals {
}
@nonobjc
internal init<D>(context: NSManagedObjectContext, fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>, from: From<D>, sectionBy: SectionBy<D>? = nil, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
internal init<O>(context: NSManagedObjectContext, fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>, from: From<O>, sectionBy: SectionBy<O>? = nil, applyFetchClauses: @escaping (_ fetchRequest: Internals.CoreStoreFetchRequest<NSManagedObject>) -> Void) {
_ = try? from.applyToFetchRequest(
fetchRequest,
@@ -82,6 +82,11 @@ extension Internals {
try self.reapplyAffectedStores(self.typedFetchRequest, self.managedObjectContext)
try self.performFetch()
if case let delegate as FetchedDiffableDataSourceSnapshotDelegate = self.delegate {
delegate.initialFetch()
}
}
@nonobjc

View File

@@ -0,0 +1,641 @@
//
// Internals.DiffableDataSourceSnapshot.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import CoreData
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
// MARK: - Internals
extension Internals {
// MARK: - DiffableDataSourceSnapshot
// Implementation based on https://github.com/ra1028/DiffableDataSources
internal struct DiffableDataSourceSnapshot: DiffableDataSourceSnapshotProtocol {
// MARK: Internal
init(sections: [NSFetchedResultsSectionInfo], fetchOffset: Int, fetchLimit: Int) {
self.structure = .init(
sections: sections,
fetchOffset: Swift.max(0, fetchOffset),
fetchLimit: (fetchLimit > 0) ? fetchLimit : nil
)
}
var sections: [Section] {
get {
return self.structure.sections
}
set {
self.structure.sections = newValue
}
}
// MARK: DiffableDataSourceSnapshotProtocol
init() {
self.structure = .init()
}
var numberOfItems: Int {
return self.structure.allItemIDs.count
}
var numberOfSections: Int {
return self.structure.allSectionIDs.count
}
var sectionIdentifiers: [String] {
return self.structure.allSectionIDs
}
var itemIdentifiers: [NSManagedObjectID] {
return self.structure.allItemIDs
}
var updatedItemIdentifiers: Set<NSManagedObjectID> {
return self.structure.reloadedItems
}
func numberOfItems(inSection identifier: String) -> Int {
return self.itemIdentifiers(inSection: identifier).count
}
func itemIdentifiers(inSection identifier: String) -> [NSManagedObjectID] {
return self.structure.items(in: identifier)
}
func sectionIdentifier(containingItem identifier: NSManagedObjectID) -> String? {
return self.structure.section(containing: identifier)
}
func indexOfItem(_ identifier: NSManagedObjectID) -> Int? {
return self.structure.allItemIDs.firstIndex(of: identifier)
}
func indexOfSection(_ identifier: String) -> Int? {
return self.structure.allSectionIDs.firstIndex(of: identifier)
}
mutating func appendItems<C: Collection>(_ identifiers: C, toSection sectionIdentifier: String?) where C.Element == NSManagedObjectID {
self.structure.append(itemIDs: identifiers, to: sectionIdentifier)
}
mutating func insertItems<C: Collection>(_ identifiers: C, beforeItem beforeIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID {
self.structure.insert(itemIDs: identifiers, before: beforeIdentifier)
}
mutating func insertItems<C: Collection>(_ identifiers: C, afterItem afterIdentifier: NSManagedObjectID) where C.Element == NSManagedObjectID {
self.structure.insert(itemIDs: identifiers, after: afterIdentifier)
}
mutating func deleteItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
self.structure.remove(itemIDs: identifiers)
}
mutating func deleteAllItems() {
self.structure.removeAllItems()
}
mutating func moveItem(_ identifier: NSManagedObjectID, beforeItem toIdentifier: NSManagedObjectID) {
self.structure.move(itemID: identifier, before: toIdentifier)
}
mutating func moveItem(_ identifier: NSManagedObjectID, afterItem toIdentifier: NSManagedObjectID) {
self.structure.move(itemID: identifier, after: toIdentifier)
}
mutating func reloadItems<S: Sequence>(_ identifiers: S) where S.Element == NSManagedObjectID {
self.structure.update(itemIDs: identifiers)
}
mutating func appendSections<C: Collection>(_ identifiers: C) where C.Element == String {
self.structure.append(sectionIDs: identifiers)
}
mutating func insertSections<C: Collection>(_ identifiers: C, beforeSection toIdentifier: String) where C.Element == String {
self.structure.insert(sectionIDs: identifiers, before: toIdentifier)
}
mutating func insertSections<C: Collection>(_ identifiers: C, afterSection toIdentifier: String) where C.Element == String {
self.structure.insert(sectionIDs: identifiers, after: toIdentifier)
}
mutating func deleteSections<S: Sequence>(_ identifiers: S) where S.Element == String {
self.structure.remove(sectionIDs: identifiers)
}
mutating func moveSection(_ identifier: String, beforeSection toIdentifier: String) {
self.structure.move(sectionID: identifier, before: toIdentifier)
}
mutating func moveSection(_ identifier: String, afterSection toIdentifier: String) {
self.structure.move(sectionID: identifier, after: toIdentifier)
}
mutating func reloadSections<S: Sequence>(_ identifiers: S) where S.Element == String {
self.structure.update(sectionIDs: identifiers)
}
// MARK: Private
private var structure: BackingStructure
// MARK: - Section
internal struct Section: DifferentiableSection, Equatable {
var isReloaded: Bool
init(differenceIdentifier: String, items: [Item] = [], isReloaded: Bool = false) {
self.differenceIdentifier = differenceIdentifier
self.elements = items
self.isReloaded = isReloaded
}
// MARK: Differentiable
let differenceIdentifier: String
func isContentEqual(to source: Section) -> Bool {
return !self.isReloaded
&& self.differenceIdentifier == source.differenceIdentifier
}
// MARK: DifferentiableSection
var elements: [Item] = []
init<S: Sequence>(source: Section, elements: S) where S.Element == Item {
self.init(
differenceIdentifier: source.differenceIdentifier,
items: Array(elements),
isReloaded: source.isReloaded
)
}
}
// MARK: - Item
internal struct Item: Differentiable, Equatable {
var isReloaded: Bool
init(differenceIdentifier: NSManagedObjectID, isReloaded: Bool = false) {
self.differenceIdentifier = differenceIdentifier
self.isReloaded = isReloaded
}
// MARK: Differentiable
let differenceIdentifier: NSManagedObjectID
func isContentEqual(to source: Item) -> Bool {
return !self.isReloaded
&& self.differenceIdentifier == source.differenceIdentifier
}
}
// MARK: - BackingStructure
fileprivate struct BackingStructure {
// MARK: Internal
var sections: [Section]
private(set) var reloadedItems: Set<NSManagedObjectID>
init() {
self.sections = []
self.reloadedItems = []
}
init(sections: [NSFetchedResultsSectionInfo], fetchOffset: Int, fetchLimit: Int?) {
let sliceItems: (_ array: [Any], _ offset: Int) -> Array<Any>.SubSequence
if let fetchLimit = fetchLimit {
var remainingCount = fetchLimit
sliceItems = {
let slice = $0[$1...].prefix(remainingCount)
remainingCount -= slice.count
return slice
}
}
else {
sliceItems = { $0[$1...] }
}
var newSections: [Internals.DiffableDataSourceSnapshot.Section] = []
var ignoreCount = fetchOffset
for section in sections {
let objects = section.objects ?? []
guard objects.indices.contains(ignoreCount) else {
ignoreCount -= objects.count
continue
}
let items = sliceItems(objects, ignoreCount)
.map({ Item(differenceIdentifier: ($0 as! NSManagedObject).objectID) })
ignoreCount = 0
guard !items.isEmpty else {
continue
}
newSections.append(
Section(differenceIdentifier: section.name, items: items)
)
}
self.sections = newSections
self.reloadedItems = []
}
var allSectionIDs: [String] {
return self.sections.map({ $0.differenceIdentifier })
}
var allItemIDs: [NSManagedObjectID] {
return self.sections.lazy.flatMap({ $0.elements }).map({ $0.differenceIdentifier })
}
func items(in sectionID: String) -> [NSManagedObjectID] {
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
Internals.abort("Section \"\(sectionID)\" does not exist")
}
return self.sections[sectionIndex].elements.map({ $0.differenceIdentifier })
}
func section(containing itemID: NSManagedObjectID) -> String? {
return self.itemPositionMap(itemID)?.section.differenceIdentifier
}
mutating func append<C: Collection>(itemIDs: C, to sectionID: String?) where C.Element == NSManagedObjectID {
let index: Array<Section>.Index
if let sectionID = sectionID {
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
Internals.abort("Section \"\(sectionID)\" does not exist")
}
index = sectionIndex
}
else {
let section = self.sections
guard !section.isEmpty else {
Internals.abort("No sections exist")
}
index = section.index(before: section.endIndex)
}
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[index].elements.append(contentsOf: items)
}
mutating func insert<C: Collection>(itemIDs: C, before beforeItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
Internals.abort("Item \(beforeItemID) does not exist")
}
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[itemPosition.sectionIndex].elements
.insert(contentsOf: items, at: itemPosition.itemRelativeIndex)
}
mutating func insert<C: Collection>(itemIDs: C, after afterItemID: NSManagedObjectID) where C.Element == NSManagedObjectID {
guard let itemPosition = self.itemPositionMap(afterItemID) else {
Internals.abort("Item \(afterItemID) does not exist")
}
let itemIndex = self.sections[itemPosition.sectionIndex].elements
.index(after: itemPosition.itemRelativeIndex)
let items = itemIDs.lazy.map({ Item(differenceIdentifier: $0) })
self.sections[itemPosition.sectionIndex].elements
.insert(contentsOf: items, at: itemIndex)
}
mutating func remove<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
let itemPositionMap = self.itemPositionMap()
var removeIndexSetMap: [Int: IndexSet] = [:]
for itemID in itemIDs {
guard let itemPosition = itemPositionMap[itemID] else {
continue
}
removeIndexSetMap[itemPosition.sectionIndex, default: []]
.insert(itemPosition.itemRelativeIndex)
}
for (sectionIndex, removeIndexSet) in removeIndexSetMap {
for range in removeIndexSet.rangeView.reversed() {
self.sections[sectionIndex].elements.removeSubrange(range)
}
}
}
mutating func removeAllItems() {
for sectionIndex in self.sections.indices {
self.sections[sectionIndex].elements.removeAll()
}
}
mutating func removeAllEmptySections() {
self.sections.removeAll(where: { $0.elements.isEmpty })
}
mutating func move(itemID: NSManagedObjectID, before beforeItemID: NSManagedObjectID) {
guard let removed = self.remove(itemID: itemID) else {
Internals.abort("Item \(itemID) does not exist")
}
guard let itemPosition = self.itemPositionMap(beforeItemID) else {
Internals.abort("Item \(beforeItemID) does not exist")
}
self.sections[itemPosition.sectionIndex].elements
.insert(removed, at: itemPosition.itemRelativeIndex)
}
mutating func move(itemID: NSManagedObjectID, after afterItemID: NSManagedObjectID) {
guard let removed = self.remove(itemID: itemID) else {
Internals.abort("Item \(itemID) does not exist")
}
guard let itemPosition = self.itemPositionMap(afterItemID) else {
Internals.abort("Item \(afterItemID) does not exist")
}
let itemIndex = self.sections[itemPosition.sectionIndex].elements
.index(after: itemPosition.itemRelativeIndex)
self.sections[itemPosition.sectionIndex].elements
.insert(removed, at: itemIndex)
}
mutating func update<S: Sequence>(itemIDs: S) where S.Element == NSManagedObjectID {
let itemPositionMap = self.itemPositionMap()
var newItemIDs: Set<NSManagedObjectID> = []
for itemID in itemIDs {
guard let itemPosition = itemPositionMap[itemID] else {
continue
}
self.sections[itemPosition.sectionIndex]
.elements[itemPosition.itemRelativeIndex].isReloaded = true
newItemIDs.insert(itemID)
}
self.reloadedItems.formUnion(newItemIDs)
}
mutating func append<C: Collection>(sectionIDs: C) where C.Element == String {
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
self.sections.append(contentsOf: newSections)
}
mutating func insert<C: Collection>(sectionIDs: C, before beforeSectionID: String) where C.Element == String {
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
Internals.abort("Section \"\(beforeSectionID)\" does not exist")
}
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func insert<C: Collection>(sectionIDs: C, after afterSectionID: String) where C.Element == String {
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
Internals.abort("Section \"\(afterSectionID)\" does not exist")
}
let sectionIndex = self.sections.index(after: beforeIndex)
let newSections = sectionIDs.lazy.map({ Section(differenceIdentifier: $0) })
self.sections.insert(contentsOf: newSections, at: sectionIndex)
}
mutating func remove<S: Sequence>(sectionIDs: S) where S.Element == String {
for sectionID in sectionIDs {
self.remove(sectionID: sectionID)
}
}
mutating func move(sectionID: String, before beforeSectionID: String) {
guard let removed = self.remove(sectionID: sectionID) else {
Internals.abort("Section \"\(sectionID)\" does not exist")
}
guard let sectionIndex = self.sectionIndex(of: beforeSectionID) else {
Internals.abort("Section \"\(beforeSectionID)\" does not exist")
}
self.sections.insert(removed, at: sectionIndex)
}
mutating func move(sectionID: String, after afterSectionID: String) {
guard let removed = self.remove(sectionID: sectionID) else {
Internals.abort("Section \"\(sectionID)\" does not exist")
}
guard let beforeIndex = self.sectionIndex(of: afterSectionID) else {
Internals.abort("Section \"\(afterSectionID)\" does not exist")
}
let sectionIndex = self.sections.index(after: beforeIndex)
self.sections.insert(removed, at: sectionIndex)
}
mutating func update<S: Sequence>(sectionIDs: S) where S.Element == String {
for sectionID in sectionIDs {
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
continue
}
self.sections[sectionIndex].isReloaded = true
}
}
// MARK: Private
private func sectionIndex(of sectionID: String) -> Array<Section>.Index? {
return self.sections.firstIndex(where: { $0.differenceIdentifier == sectionID })
}
@discardableResult
private mutating func remove(itemID: NSManagedObjectID) -> Item? {
guard let itemPosition = self.itemPositionMap(itemID) else {
return nil
}
return self.sections[itemPosition.sectionIndex].elements
.remove(at: itemPosition.itemRelativeIndex)
}
@discardableResult
private mutating func remove(sectionID: String) -> Section? {
guard let sectionIndex = self.sectionIndex(of: sectionID) else {
return nil
}
return self.sections.remove(at: sectionIndex)
}
private func itemPositionMap(_ itemID: NSManagedObjectID) -> ItemPosition? {
let sections = self.sections
for (sectionIndex, section) in sections.enumerated() {
for (itemRelativeIndex, item) in section.elements.enumerated() {
guard item.differenceIdentifier == itemID else {
continue
}
return ItemPosition(
item: item,
itemRelativeIndex: itemRelativeIndex,
section: section,
sectionIndex: sectionIndex
)
}
}
return nil
}
private func itemPositionMap() -> [NSManagedObjectID: ItemPosition] {
return self.sections.enumerated().reduce(into: [:]) { result, section in
for (itemRelativeIndex, item) in section.element.elements.enumerated() {
result[item.differenceIdentifier] = ItemPosition(
item: item,
itemRelativeIndex: itemRelativeIndex,
section: section.element,
sectionIndex: section.offset
)
}
}
}
// MARK: - ItemPosition
fileprivate struct ItemPosition {
let item: Item
let itemRelativeIndex: Int
let section: Section
let sectionIndex: Int
}
}
}
}
#endif

View File

@@ -0,0 +1,142 @@
//
// Internals.DiffableDataUIDispatcher.Changeset.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
// MARK: - Internals.DiffableDataUIDispatcher
extension Internals.DiffableDataUIDispatcher {
// MARK: - ChangeSet
// Implementation based on https://github.com/ra1028/DifferenceKit
internal struct Changeset<C: Collection>: Equatable where C: Equatable {
var data: C
var sectionDeleted: [Int]
var sectionInserted: [Int]
var sectionUpdated: [Int]
var sectionMoved: [(source: Int, target: Int)]
var elementDeleted: [ElementPath]
var elementInserted: [ElementPath]
var elementUpdated: [ElementPath]
var elementMoved: [(source: ElementPath, target: ElementPath)]
@inlinable
init(
data: C,
sectionDeleted: [Int] = [],
sectionInserted: [Int] = [],
sectionUpdated: [Int] = [],
sectionMoved: [(source: Int, target: Int)] = [],
elementDeleted: [ElementPath] = [],
elementInserted: [ElementPath] = [],
elementUpdated: [ElementPath] = [],
elementMoved: [(source: ElementPath, target: ElementPath)] = []
) {
self.data = data
self.sectionDeleted = sectionDeleted
self.sectionInserted = sectionInserted
self.sectionUpdated = sectionUpdated
self.sectionMoved = sectionMoved
self.elementDeleted = elementDeleted
self.elementInserted = elementInserted
self.elementUpdated = elementUpdated
self.elementMoved = elementMoved
}
@inlinable
var sectionChangeCount: Int {
return self.sectionDeleted.count
+ self.sectionInserted.count
+ self.sectionUpdated.count
+ self.sectionMoved.count
}
@inlinable
var elementChangeCount: Int {
return self.elementDeleted.count
+ self.elementInserted.count
+ self.elementUpdated.count
+ self.elementMoved.count
}
@inlinable
var changeCount: Int {
return self.sectionChangeCount + self.elementChangeCount
}
@inlinable
var hasSectionChanges: Bool {
return self.sectionChangeCount > 0
}
@inlinable
var hasElementChanges: Bool {
return self.elementChangeCount > 0
}
@inlinable
var hasChanges: Bool {
return self.changeCount > 0
}
// MARK: Equatable
static func == (lhs: Changeset, rhs: Changeset) -> Bool {
return lhs.data == rhs.data
&& Set(lhs.sectionDeleted) == Set(rhs.sectionDeleted)
&& Set(lhs.sectionInserted) == Set(rhs.sectionInserted)
&& Set(lhs.sectionUpdated) == Set(rhs.sectionUpdated)
&& Set(lhs.sectionMoved.map(HashablePair.init)) == Set(rhs.sectionMoved.map(HashablePair.init))
&& Set(lhs.elementDeleted) == Set(rhs.elementDeleted)
&& Set(lhs.elementInserted) == Set(rhs.elementInserted)
&& Set(lhs.elementUpdated) == Set(rhs.elementUpdated)
&& Set(lhs.elementMoved.map(HashablePair.init)) == Set(rhs.elementMoved.map(HashablePair.init))
}
// MARK: - HashablePair
private struct HashablePair<H: Hashable>: Hashable {
let first: H
let second: H
}
}
}
#endif

View File

@@ -0,0 +1,325 @@
//
// Internals.DiffableDataUIDispatcher.DiffResult.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
// MARK: - Internals.DiffableDataUIDispatcher
extension Internals.DiffableDataUIDispatcher {
// MARK: - DiffResult
// Implementation based on https://github.com/ra1028/DifferenceKit
@usableFromInline
internal struct DiffResult<Index> {
@usableFromInline
internal let deleted: [Index]
@usableFromInline
internal let inserted: [Index]
@usableFromInline
internal let updated: [Index]
@usableFromInline
internal let moved: [(source: Index, target: Index)]
@usableFromInline
internal let sourceTraces: ContiguousArray<Trace<Int>>
@usableFromInline
internal let targetReferences: ContiguousArray<Int?>
@inlinable
@discardableResult
static func diff<E: Differentiable>(
source: ContiguousArray<E>,
target: ContiguousArray<E>,
useTargetIndexForUpdated: Bool,
mapIndex: (Int) -> Index,
updatedElementsPointer: UnsafeMutablePointer<ContiguousArray<E>>? = nil,
notDeletedElementsPointer: UnsafeMutablePointer<ContiguousArray<E>>? = nil
) -> DiffResult<Index> {
var deleted = [Index]()
var inserted = [Index]()
var updated = [Index]()
var moved = [(source: Index, target: Index)]()
var sourceTraces = ContiguousArray<Trace<Int>>()
var sourceIdentifiers = ContiguousArray<E.DifferenceIdentifier>()
var targetReferences = ContiguousArray<Int?>(repeating: nil, count: target.count)
sourceTraces.reserveCapacity(source.count)
sourceIdentifiers.reserveCapacity(source.count)
for sourceElement in source {
sourceTraces.append(Trace())
sourceIdentifiers.append(sourceElement.differenceIdentifier)
}
sourceIdentifiers.withUnsafeBufferPointer { bufferPointer in
var sourceOccurrencesTable = [TableKey<E.DifferenceIdentifier>: Occurrence](minimumCapacity: source.count)
for sourceIndex in sourceIdentifiers.indices {
let pointer = bufferPointer.baseAddress!.advanced(by: sourceIndex)
let key = TableKey(pointer: pointer)
switch sourceOccurrencesTable[key] {
case .none:
sourceOccurrencesTable[key] = .unique(index: sourceIndex)
case .unique(let otherIndex)?:
let reference = IndicesReference([otherIndex, sourceIndex])
sourceOccurrencesTable[key] = .duplicate(reference: reference)
case .duplicate(let reference)?:
reference.push(sourceIndex)
}
}
for targetIndex in target.indices {
var targetIdentifier = target[targetIndex].differenceIdentifier
let key = TableKey(pointer: &targetIdentifier)
switch sourceOccurrencesTable[key] {
case .none:
break
case .unique(let sourceIndex)?:
if case .none = sourceTraces[sourceIndex].reference {
targetReferences[targetIndex] = sourceIndex
sourceTraces[sourceIndex].reference = targetIndex
}
case .duplicate(let reference)?:
if let sourceIndex = reference.next() {
targetReferences[targetIndex] = sourceIndex
sourceTraces[sourceIndex].reference = targetIndex
}
}
}
}
var offsetByDelete = 0
var untrackedSourceIndex: Int? = 0
for sourceIndex in source.indices {
sourceTraces[sourceIndex].deleteOffset = offsetByDelete
if let targetIndex = sourceTraces[sourceIndex].reference {
let targetElement = target[targetIndex]
updatedElementsPointer?.pointee.append(targetElement)
notDeletedElementsPointer?.pointee.append(targetElement)
}
else {
let sourceElement = source[sourceIndex]
deleted.append(mapIndex(sourceIndex))
sourceTraces[sourceIndex].isTracked = true
offsetByDelete += 1
updatedElementsPointer?.pointee.append(sourceElement)
}
}
for targetIndex in target.indices {
untrackedSourceIndex = untrackedSourceIndex.flatMap { index in
sourceTraces.suffix(from: index).firstIndex { !$0.isTracked }
}
if let sourceIndex = targetReferences[targetIndex] {
sourceTraces[sourceIndex].isTracked = true
let sourceElement = source[sourceIndex]
let targetElement = target[targetIndex]
if !targetElement.isContentEqual(to: sourceElement) {
updated.append(mapIndex(useTargetIndexForUpdated ? targetIndex : sourceIndex))
}
if sourceIndex != untrackedSourceIndex {
let deleteOffset = sourceTraces[sourceIndex].deleteOffset
moved.append((source: mapIndex(sourceIndex - deleteOffset), target: mapIndex(targetIndex)))
}
}
else {
inserted.append(mapIndex(targetIndex))
}
}
return DiffResult(
deleted: deleted,
inserted: inserted,
updated: updated,
moved: moved,
sourceTraces: sourceTraces,
targetReferences: targetReferences
)
}
// MARK: Private
@inlinable
internal init(
deleted: [Index] = [],
inserted: [Index] = [],
updated: [Index] = [],
moved: [(source: Index, target: Index)] = [],
sourceTraces: ContiguousArray<Trace<Int>>,
targetReferences: ContiguousArray<Int?>
) {
self.deleted = deleted
self.inserted = inserted
self.updated = updated
self.moved = moved
self.sourceTraces = sourceTraces
self.targetReferences = targetReferences
}
// MARK: - Trace
// Implementation based on https://github.com/ra1028/DifferenceKit
@usableFromInline
internal struct Trace<Index> {
@usableFromInline
internal var reference: Index?
@usableFromInline
internal var deleteOffset = 0
@usableFromInline
internal var isTracked = false
@inlinable
init() {}
}
// MARK: - Occurrence
// Implementation based on https://github.com/ra1028/DifferenceKit
@usableFromInline
internal enum Occurrence {
case unique(index: Int)
case duplicate(reference: IndicesReference)
}
// MARK: - IndicesReference
// Implementation based on https://github.com/ra1028/DifferenceKit
@usableFromInline
internal final class IndicesReference {
@usableFromInline
internal var indices: ContiguousArray<Int>
@usableFromInline
internal var position = 0
@inlinable
internal init(_ indices: ContiguousArray<Int>) {
self.indices = indices
}
@inlinable
internal func push(_ index: Int) {
self.indices.append(index)
}
@inlinable
internal func next() -> Int? {
guard self.position < self.indices.endIndex else {
return nil
}
defer {
self.position += 1
}
return self.indices[self.position]
}
}
// MARK: - TableKey
// Implementation based on https://github.com/ra1028/DifferenceKit
@usableFromInline
internal struct TableKey<T: Hashable>: Hashable {
@usableFromInline
internal let pointeeHashValue: Int
@usableFromInline
internal let pointer: UnsafePointer<T>
@inlinable
internal init(pointer: UnsafePointer<T>) {
self.pointeeHashValue = pointer.pointee.hashValue
self.pointer = pointer
}
// MARK: Equatable
@inlinable
internal static func == (lhs: TableKey, rhs: TableKey) -> Bool {
return lhs.pointeeHashValue == rhs.pointeeHashValue
&& (lhs.pointer.distance(to: rhs.pointer) == 0
|| lhs.pointer.pointee == rhs.pointer.pointee)
}
// MARK: Hashable
@inlinable
internal func hash(into hasher: inout Hasher) {
hasher.combine(pointeeHashValue)
}
}
}
}
#endif

View File

@@ -0,0 +1,517 @@
//
// Internals.DiffableDataUIDispatcher.StagedChangeset.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import Foundation
// MARK: - Internals.DiffableDataUIDispatcher
extension Internals.DiffableDataUIDispatcher {
// MARK: - StagedChangeset
// Implementation based on https://github.com/ra1028/DifferenceKit
internal struct StagedChangeset<C: Collection>: ExpressibleByArrayLiteral, Equatable, RandomAccessCollection, RangeReplaceableCollection where C: Equatable {
@usableFromInline
var changesets: ContiguousArray<Changeset<C>>
@inlinable
init<S: Sequence>(_ changesets: S) where S.Element == Changeset<C> {
self.changesets = ContiguousArray(changesets)
}
// MARK: ExpressibleByArrayLiteral
@inlinable
init(arrayLiteral elements: Changeset<C>...) {
self.init(elements)
}
// MARK: Equatable
@inlinable
static func == (lhs: StagedChangeset, rhs: StagedChangeset) -> Bool {
return lhs.changesets == rhs.changesets
}
// MARK: Sequence
typealias Element = Changeset<C>
// MARK: RandomAccessCollection
@inlinable
var startIndex: Int {
return self.changesets.startIndex
}
@inlinable
var endIndex: Int {
return self.changesets.endIndex
}
@inlinable
func index(after i: Int) -> Int {
return self.changesets.index(after: i)
}
@inlinable
subscript(position: Int) -> Changeset<C> {
get { return self.changesets[position] }
set { self.changesets[position] = newValue }
}
// MARK: RangeReplaceableCollection
@inlinable
init() {
self.init([])
}
@inlinable
mutating func replaceSubrange<C2: Collection, R: RangeExpression>(_ subrange: R, with newElements: C2) where C2.Element == Changeset<C>, R.Bound == Int {
self.changesets.replaceSubrange(subrange, with: newElements)
}
}
}
// MARK: - Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: Differentiable
extension Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: Differentiable {
@inlinable
internal init(source: C, target: C) {
self.init(source: source, target: target, section: 0)
}
@inlinable
internal init(source: C, target: C, section: Int) {
typealias Changeset = Internals.DiffableDataUIDispatcher<O>.Changeset
typealias ElementPath = Internals.DiffableDataUIDispatcher<O>.ElementPath
typealias DiffResult = Internals.DiffableDataUIDispatcher<O>.DiffResult
let sourceElements = ContiguousArray(source)
let targetElements = ContiguousArray(target)
if sourceElements.isEmpty && targetElements.isEmpty {
self.init()
return
}
if !sourceElements.isEmpty && targetElements.isEmpty {
self.init(
[
Changeset(
data: target,
elementDeleted: sourceElements.indices.map {
ElementPath(
element: $0,
section: section
)
}
)
]
)
return
}
if sourceElements.isEmpty && !targetElements.isEmpty {
self.init(
[
Changeset(
data: target,
elementInserted: targetElements.indices.map {
ElementPath(
element: $0,
section: section
)
}
)
]
)
return
}
var firstStageElements = ContiguousArray<C.Element>()
var secondStageElements = ContiguousArray<C.Element>()
let result = DiffResult.diff(
source: sourceElements,
target: targetElements,
useTargetIndexForUpdated: false,
mapIndex: { ElementPath(element: $0, section: section) },
updatedElementsPointer: &firstStageElements,
notDeletedElementsPointer: &secondStageElements
)
var changesets = ContiguousArray<Changeset<C>>()
if !result.updated.isEmpty {
changesets.append(
Changeset(
data: C(firstStageElements),
elementUpdated: result.updated
)
)
}
if !result.deleted.isEmpty {
changesets.append(
Changeset(
data: C(secondStageElements),
elementDeleted: result.deleted
)
)
}
if !result.inserted.isEmpty || !result.moved.isEmpty {
changesets.append(
Changeset(
data: target,
elementInserted: result.inserted,
elementMoved: result.moved
)
)
}
if !changesets.isEmpty {
let index = changesets.index(before: changesets.endIndex)
changesets[index].data = target
}
self.init(changesets)
}
}
// MARK: - Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: DifferentiableSection
extension Internals.DiffableDataUIDispatcher.StagedChangeset where C: RangeReplaceableCollection, C.Element: DifferentiableSection {
@inlinable
internal init(source: C, target: C) {
typealias Section = C.Element
typealias SectionIdentifier = C.Element.DifferenceIdentifier
typealias Element = C.Element.Collection.Element
typealias ElementIdentifier = C.Element.Collection.Element.DifferenceIdentifier
typealias Changeset = Internals.DiffableDataUIDispatcher<O>.Changeset
typealias ElementPath = Internals.DiffableDataUIDispatcher<O>.ElementPath
typealias DiffResult = Internals.DiffableDataUIDispatcher<O>.DiffResult
typealias Trace = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.Trace
typealias TableKey = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.TableKey
typealias Occurrence = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.Occurrence
typealias IndicesReference = Internals.DiffableDataUIDispatcher<O>.DiffResult<Section>.IndicesReference
let sourceSections = ContiguousArray(source)
let targetSections = ContiguousArray(target)
let contiguousSourceSections = ContiguousArray(sourceSections.map { ContiguousArray($0.elements) })
let contiguousTargetSections = ContiguousArray(targetSections.map { ContiguousArray($0.elements) })
var firstStageSections = sourceSections
var secondStageSections = ContiguousArray<Section>()
var thirdStageSections = ContiguousArray<Section>()
var fourthStageSections = ContiguousArray<Section>()
var sourceElementTraces = contiguousSourceSections.map { section in
ContiguousArray(repeating: Trace<ElementPath>(), count: section.count)
}
var targetElementReferences = contiguousTargetSections.map { section in
ContiguousArray<ElementPath?>(repeating: nil, count: section.count)
}
let flattenSourceCount = contiguousSourceSections.reduce(into: 0) { $0 += $1.count }
var flattenSourceIdentifiers = ContiguousArray<ElementIdentifier>()
var flattenSourceElementPaths = ContiguousArray<ElementPath>()
thirdStageSections.reserveCapacity(contiguousTargetSections.count)
fourthStageSections.reserveCapacity(contiguousTargetSections.count)
flattenSourceIdentifiers.reserveCapacity(flattenSourceCount)
flattenSourceElementPaths.reserveCapacity(flattenSourceCount)
let sectionResult = DiffResult.diff(
source: sourceSections,
target: targetSections,
useTargetIndexForUpdated: true,
mapIndex: { $0 }
)
var elementDeleted = [ElementPath]()
var elementInserted = [ElementPath]()
var elementUpdated = [ElementPath]()
var elementMoved = [(source: ElementPath, target: ElementPath)]()
for sourceSectionIndex in contiguousSourceSections.indices {
for sourceElementIndex in contiguousSourceSections[sourceSectionIndex].indices {
let sourceElementPath = ElementPath(element: sourceElementIndex, section: sourceSectionIndex)
let sourceElement = contiguousSourceSections[sourceElementPath]
flattenSourceIdentifiers.append(sourceElement.differenceIdentifier)
flattenSourceElementPaths.append(sourceElementPath)
}
}
flattenSourceIdentifiers.withUnsafeBufferPointer { bufferPointer in
var sourceOccurrencesTable = [TableKey<ElementIdentifier>: Occurrence](minimumCapacity: flattenSourceCount)
for flattenSourceIndex in flattenSourceIdentifiers.indices {
let pointer = bufferPointer.baseAddress!.advanced(by: flattenSourceIndex)
let key = TableKey(pointer: pointer)
switch sourceOccurrencesTable[key] {
case .none:
sourceOccurrencesTable[key] = .unique(index: flattenSourceIndex)
case .unique(let otherIndex)?:
let reference = IndicesReference([otherIndex, flattenSourceIndex])
sourceOccurrencesTable[key] = .duplicate(reference: reference)
case .duplicate(let reference)?:
reference.push(flattenSourceIndex)
}
}
for targetSectionIndex in contiguousTargetSections.indices {
let targetElements = contiguousTargetSections[targetSectionIndex]
for targetElementIndex in targetElements.indices {
var targetIdentifier = targetElements[targetElementIndex].differenceIdentifier
let key = TableKey(pointer: &targetIdentifier)
switch sourceOccurrencesTable[key] {
case .none:
break
case .unique(let flattenSourceIndex)?:
let sourceElementPath = flattenSourceElementPaths[flattenSourceIndex]
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
if case .none = sourceElementTraces[sourceElementPath].reference {
targetElementReferences[targetElementPath] = sourceElementPath
sourceElementTraces[sourceElementPath].reference = targetElementPath
}
case .duplicate(let reference)?:
if let flattenSourceIndex = reference.next() {
let sourceElementPath = flattenSourceElementPaths[flattenSourceIndex]
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
targetElementReferences[targetElementPath] = sourceElementPath
sourceElementTraces[sourceElementPath].reference = targetElementPath
}
}
}
}
}
for sourceSectionIndex in contiguousSourceSections.indices {
let sourceSection = sourceSections[sourceSectionIndex]
let sourceElements = contiguousSourceSections[sourceSectionIndex]
var firstStageElements = sourceElements
if case .some = sectionResult.sourceTraces[sourceSectionIndex].reference {
var offsetByDelete = 0
var secondStageElements = ContiguousArray<Element>()
for sourceElementIndex in sourceElements.indices {
let sourceElementPath = ElementPath(element: sourceElementIndex, section: sourceSectionIndex)
sourceElementTraces[sourceElementPath].deleteOffset = offsetByDelete
if let targetElementPath = sourceElementTraces[sourceElementPath].reference,
case .some = sectionResult.targetReferences[targetElementPath.section] {
let targetElement = contiguousTargetSections[targetElementPath]
firstStageElements[sourceElementIndex] = targetElement
secondStageElements.append(targetElement)
continue
}
elementDeleted.append(sourceElementPath)
sourceElementTraces[sourceElementPath].isTracked = true
offsetByDelete += 1
}
let secondStageSection = Section(source: sourceSection, elements: secondStageElements)
secondStageSections.append(secondStageSection)
}
let firstStageSection = Section(source: sourceSection, elements: firstStageElements)
firstStageSections[sourceSectionIndex] = firstStageSection
}
for targetSectionIndex in contiguousTargetSections.indices {
guard let sourceSectionIndex = sectionResult.targetReferences[targetSectionIndex] else {
thirdStageSections.append(targetSections[targetSectionIndex])
fourthStageSections.append(targetSections[targetSectionIndex])
continue
}
var untrackedSourceIndex: Int? = 0
let targetElements = contiguousTargetSections[targetSectionIndex]
let sectionDeleteOffset = sectionResult.sourceTraces[sourceSectionIndex].deleteOffset
let thirdStageSection = secondStageSections[sourceSectionIndex - sectionDeleteOffset]
thirdStageSections.append(thirdStageSection)
var fourthStageElements = ContiguousArray<Element>()
fourthStageElements.reserveCapacity(targetElements.count)
for targetElementIndex in targetElements.indices {
untrackedSourceIndex = untrackedSourceIndex.flatMap { index in
sourceElementTraces[sourceSectionIndex].suffix(from: index).firstIndex { !$0.isTracked }
}
let targetElementPath = ElementPath(element: targetElementIndex, section: targetSectionIndex)
let targetElement = contiguousTargetSections[targetElementPath]
guard
let sourceElementPath = targetElementReferences[targetElementPath],
let movedSourceSectionIndex = sectionResult.sourceTraces[sourceElementPath.section].reference
else {
fourthStageElements.append(targetElement)
elementInserted.append(targetElementPath)
continue
}
sourceElementTraces[sourceElementPath].isTracked = true
let sourceElement = contiguousSourceSections[sourceElementPath]
fourthStageElements.append(targetElement)
if !targetElement.isContentEqual(to: sourceElement) {
elementUpdated.append(sourceElementPath)
}
if sourceElementPath.section != sourceSectionIndex || sourceElementPath.element != untrackedSourceIndex {
let deleteOffset = sourceElementTraces[sourceElementPath].deleteOffset
let moveSourceElementPath = ElementPath(element: sourceElementPath.element - deleteOffset, section: movedSourceSectionIndex)
elementMoved.append((source: moveSourceElementPath, target: targetElementPath))
}
}
let fourthStageSection = Section(source: thirdStageSection, elements: fourthStageElements)
fourthStageSections.append(fourthStageSection)
}
var changesets = ContiguousArray<Changeset<C>>()
if !elementUpdated.isEmpty {
changesets.append(
Changeset(
data: C(firstStageSections),
elementUpdated: elementUpdated
)
)
}
if !sectionResult.deleted.isEmpty || !elementDeleted.isEmpty {
changesets.append(
Changeset(
data: C(secondStageSections),
sectionDeleted: sectionResult.deleted,
elementDeleted: elementDeleted
)
)
}
if !sectionResult.inserted.isEmpty || !sectionResult.moved.isEmpty {
changesets.append(
Changeset(
data: C(thirdStageSections),
sectionInserted: sectionResult.inserted,
sectionMoved: sectionResult.moved
)
)
}
if !elementInserted.isEmpty || !elementMoved.isEmpty {
changesets.append(
Changeset(
data: C(fourthStageSections),
elementInserted: elementInserted,
elementMoved: elementMoved
)
)
}
if !sectionResult.updated.isEmpty {
changesets.append(
Changeset(
data: target,
sectionUpdated: sectionResult.updated
)
)
}
if !changesets.isEmpty {
let index = changesets.index(before: changesets.endIndex)
changesets[index].data = target
}
self.init(changesets)
}
}
// MARK: - MutableCollection
extension MutableCollection where Element: MutableCollection, Index == Int, Element.Index == Int {
@inlinable
internal subscript<O>(path: Internals.DiffableDataUIDispatcher<O>.ElementPath) -> Element.Element {
get { return self[path.section][path.element] }
set { self[path.section][path.element] = newValue }
}
}
#endif

View File

@@ -0,0 +1,299 @@
//
// Internals.DiffableDataUIDispatcher.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
#if canImport(UIKit) || canImport(AppKit)
import CoreData
#if canImport(QuartzCore)
import QuartzCore
#endif
// MARK: - Internals
extension Internals {
// MARK: Internal
// Implementation based on https://github.com/ra1028/DiffableDataSources
@usableFromInline
internal final class DiffableDataUIDispatcher<O: DynamicObject> {
// MARK: Internal
typealias ObjectType = O
init(dataStack: DataStack) {
self.dataStack = dataStack
}
func purge<Target: DiffableDataSource.Target>(
target: Target?,
animatingDifferences: Bool,
performUpdates: @escaping (
Target,
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
) -> Void,
completion: @escaping () -> Void
) {
self.apply(
.init(),
target: target,
animatingDifferences: animatingDifferences,
performUpdates: performUpdates,
completion: completion
)
}
func apply<Target: DiffableDataSource.Target>(
_ snapshot: DiffableDataSourceSnapshot,
target: Target?,
animatingDifferences: Bool,
performUpdates: @escaping (
Target,
StagedChangeset<[Internals.DiffableDataSourceSnapshot.Section]>,
@escaping ([Internals.DiffableDataSourceSnapshot.Section]) -> Void
) -> Void,
completion: @escaping () -> Void
) {
self.dispatcher.dispatch { [weak self] in
guard let self = self else {
return
}
self.currentSnapshot = snapshot
let newSections = snapshot.sections
guard let target = target else {
self.sections = newSections
return
}
let performDiffingUpdates: () -> Void = {
let changeset = StagedChangeset(source: self.sections, target: newSections)
performUpdates(target, changeset) { sections in
self.sections = sections
}
}
#if canImport(QuartzCore)
CATransaction.begin()
CATransaction.setCompletionBlock(completion)
if !animatingDifferences {
CATransaction.setDisableActions(true)
}
performDiffingUpdates()
CATransaction.commit()
#else
performDiffingUpdates()
completion()
#endif
}
}
func snapshot() -> DiffableDataSourceSnapshot {
var snapshot: DiffableDataSourceSnapshot = .init()
snapshot.sections = self.currentSnapshot.sections
return snapshot
}
func sectionIdentifier(inSection section: Int) -> String? {
guard self.sections.indices.contains(section) else {
return nil
}
return self.sections[section].differenceIdentifier
}
func itemIdentifier(for indexPath: IndexPath) -> O.ObjectID? {
guard self.sections.indices.contains(indexPath.section) else {
return nil
}
let items = self.sections[indexPath.section].elements
guard items.indices.contains(indexPath.item) else {
return nil
}
return items[indexPath.item].differenceIdentifier
}
func indexPath(for itemIdentifier: O.ObjectID) -> IndexPath? {
let indexPathMap: [O.ObjectID: IndexPath] = self.sections.enumerated().reduce(into: [:]) { result, section in
for (itemIndex, item) in section.element.elements.enumerated() {
result[item.differenceIdentifier] = IndexPath(
item: itemIndex,
section: section.offset
)
}
}
return indexPathMap[itemIdentifier]
}
func numberOfSections() -> Int {
return self.sections.count
}
func numberOfItems(inSection section: Int) -> Int? {
guard self.sections.indices.contains(section) else {
return nil
}
return self.sections[section].elements.count
}
// MARK: Private
private let dispatcher: MainThreadSerialDispatcher = .init()
private let dataStack: DataStack
private var currentSnapshot: Internals.DiffableDataSourceSnapshot = .init()
private var sections: [Internals.DiffableDataSourceSnapshot.Section] = []
// MARK: - ElementPath
@usableFromInline
internal struct ElementPath: Hashable {
@usableFromInline
var element: Int
@usableFromInline
var section: Int
@inlinable
init(element: Int, section: Int) {
self.element = element
self.section = section
}
}
// MARK: - MainThreadSerialDispatcher
fileprivate final class MainThreadSerialDispatcher {
// MARK: FilePrivate
fileprivate init() {}
fileprivate func dispatch(_ action: @escaping () -> Void) {
let count = self.executingCount.incrementAndGet()
if Thread.isMainThread && count == 1 {
action()
self.executingCount.decrement()
}
else {
DispatchQueue.main.async { [weak self] in
guard let self = self else {
return
}
action()
self.executingCount.decrement()
}
}
}
// MARK: Private
private let executingCount: AtomicInt = .init()
// MARK: - AtomicInt
fileprivate class AtomicInt {
// MARK: FilePrivate
fileprivate func incrementAndGet() -> Int {
self.lock.wait()
defer {
self.lock.signal()
}
self.value += 1
return self.value
}
fileprivate func decrement() {
self.lock.wait()
defer {
self.lock.signal()
}
self.value -= 1
}
// MARK: Private
private let lock = DispatchSemaphore(value: 1)
private var value = 0
}
}
}
}
#endif

View File

@@ -0,0 +1,136 @@
//
// Internals.FetchedDiffableDataSourceSnapshotDelegate.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if canImport(UIKit)
import UIKit
#elseif canImport(AppKit)
import AppKit
#endif
// MARK: - FetchedDiffableDataSourceSnapshot
internal protocol FetchedDiffableDataSourceSnapshotHandler: AnyObject {
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChangeContentWith snapshot: Internals.DiffableDataSourceSnapshot)
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String?) -> String?
}
// MARK: - Internal
extension Internals {
// MARK: - FetchedDiffableDataSourceSnapshotDelegate
internal final class FetchedDiffableDataSourceSnapshotDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: Internal
@nonobjc
internal weak var handler: FetchedDiffableDataSourceSnapshotHandler?
@nonobjc
internal weak var fetchedResultsController: Internals.CoreStoreFetchedResultsController? {
didSet {
oldValue?.delegate = nil
self.fetchedResultsController?.delegate = self
}
}
deinit {
self.fetchedResultsController?.delegate = nil
}
internal func initialFetch() {
guard let fetchedResultsController = self.fetchedResultsController else {
return
}
self.controllerDidChangeContent(fetchedResultsController.dynamicCast())
}
// MARK: NSFetchedResultsControllerDelegate
@objc
dynamic func controllerDidChangeContent(_ controller: NSFetchedResultsController<NSFetchRequestResult>) {
var snapshot = Internals.DiffableDataSourceSnapshot(
sections: controller.sections ?? [],
fetchOffset: controller.fetchRequest.fetchOffset,
fetchLimit: controller.fetchRequest.fetchLimit
)
snapshot.reloadSections(self.reloadedSectionIDs)
snapshot.reloadItems(self.reloadedItemIDs)
self.handler?.controller(
controller,
didChangeContentWith: snapshot
)
self.reloadedItemIDs.removeAll()
self.reloadedSectionIDs.removeAll()
}
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, sectionIndexTitleForSectionName sectionName: String) -> String? {
return self.handler?.controller(
controller,
sectionIndexTitleForSectionName: sectionName
)
}
@objc
dynamic func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange anObject: Any, at indexPath: IndexPath?, for type: NSFetchedResultsChangeType, newIndexPath: IndexPath?) {
let object = anObject as! NSManagedObject
self.reloadedItemIDs.append(object.objectID)
}
func controller(_ controller: NSFetchedResultsController<NSFetchRequestResult>, didChange sectionInfo: NSFetchedResultsSectionInfo, atSectionIndex sectionIndex: Int, for type: NSFetchedResultsChangeType) {
self.reloadedSectionIDs.append(sectionInfo.name)
}
// MARK: Private
private var reloadedItemIDs: [NSManagedObjectID] = []
private var reloadedSectionIDs: [String] = []
}
}

View File

@@ -0,0 +1,94 @@
//
// Internals.LazyNonmutating.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - Internals
extension Internals {
// MARK: - LazyNonmutating
@propertyWrapper
internal final class LazyNonmutating<Value> {
// MARK: Internal
init(_ initializer: @escaping () -> Value) {
self.initializer = initializer
}
init(uninitialized: Void) {
self.initializer = { fatalError() }
}
func initialize(_ initializer: @escaping () -> Value) {
self.initializer = initializer
}
func reset(_ initializer: @escaping () -> Value) {
self.initializer = initializer
self.initializedValue = nil
}
// MARK: @propertyWrapper
var wrappedValue: Value {
get {
if let initializedValue = self.initializedValue {
return initializedValue
}
let initializedValue = self.initializer()
self.initializedValue = initializedValue
return initializedValue
}
set {
self.initializer = { newValue }
self.initializedValue = newValue
}
}
var projectedValue: Internals.LazyNonmutating<Value> {
return self
}
// MARK: Private
private var initializer: () -> Value
private var initializedValue: Value? = nil
}
}

View File

@@ -34,7 +34,7 @@ extension Internals {
internal final class NotificationObserver {
// MARK: Public
// MARK: Internal
let observer: NSObjectProtocol

View File

@@ -0,0 +1,90 @@
//
// Internals.SharedNotificationObserver.swift
// CoreStore
//
// Copyright © 2018 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - Internal
extension Internals {
// MARK: - SharedNotificationObserver
internal final class SharedNotificationObserver<T> {
// MARK: Internal
internal init(notificationName: Notification.Name, object: Any?, queue: OperationQueue? = nil, sharedValue: @escaping (_ note: Notification) -> T) {
self.observer = NotificationCenter.default.addObserver(
forName: notificationName,
object: object,
queue: queue,
using: { [weak self] (notification) in
guard let self = self else {
return
}
let value = sharedValue(notification)
self.notifyObservers(value)
}
)
}
deinit {
self.observer.map(NotificationCenter.default.removeObserver(_:))
self.observers.removeAllObjects()
}
internal func addObserver<U: AnyObject>(_ observer: U, closure: @escaping (T) -> Void) {
self.observers.setObject(Closure<T, Void>(closure), forKey: observer)
}
internal func removeObserver<U: AnyObject>(_ observer: U) {
self.observers.removeObject(forKey: observer)
}
// MARK: Private
private var observer: NSObjectProtocol!
private let observers: NSMapTable<AnyObject, Closure<T, Void>> = .weakToStrongObjects()
private func notifyObservers(_ sharedValue: T) {
guard let enumerator = self.observers.objectEnumerator() else {
return
}
for closure in enumerator {
(closure as! Closure<T, Void>).invoke(with: sharedValue)
}
}
}
}

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