Compare commits

..

1 Commits

Author SHA1 Message Date
John Estropia
81c5b0c650 fix iOS 10 NSFetchedResultsController bug for iOS 7-supporting branch 2016-11-08 10:04:49 +09:00
231 changed files with 14655 additions and 24589 deletions

3
.gitmodules vendored
View File

@@ -0,0 +1,3 @@
[submodule "Carthage/Checkouts/GCDKit"]
path = Carthage/Checkouts/GCDKit
url = https://github.com/JohnEstropia/GCDKit.git

View File

@@ -1 +0,0 @@
3.1

View File

@@ -1,5 +1,5 @@
language: objective-c
osx_image: xcode8.3
osx_image: xcode7.3
sudo: false
git:
submodules: false
@@ -10,22 +10,20 @@ env:
- LC_CTYPE=en_US.UTF-8
- LANG=en_US.UTF-8
matrix:
- DESTINATION="OS=10.3,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=10.1,name=iPhone 7" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.3,name=iPhone 5S" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.3,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.3,name=iPhone 4S" SCHEME="CoreStore iOS" SDK=iphonesimulator10.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="arch=x86_64" SCHEME="CoreStore OSX" SDK=macosx10.12 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=3.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator3.2 RUN_TESTS="NO" POD_LINT="NO"
- DESTINATION="OS=2.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator3.2 RUN_TESTS="NO" POD_LINT="NO"
- DESTINATION="OS=10.2,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator10.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=9.2,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator10.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=9.3,name=iPhone 6s" SCHEME="CoreStore iOS" SDK=iphonesimulator9.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator9.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator9.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.3,name=iPhone 5S" SCHEME="CoreStore iOS" SDK=iphonesimulator9.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.2,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator9.3 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="CoreStore iOS" SDK=iphonesimulator9.3 RUN_TESTS="YES" POD_LINT="YES"
- DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="CoreStore iOS7" SDK=iphonesimulator9.3 RUN_TESTS="YES" POD_LINT="YES"
- DESTINATION="arch=x86_64" SCHEME="CoreStore OSX" SDK=macosx10.11 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=2.2,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator2.2 RUN_TESTS="NO" POD_LINT="NO"
- DESTINATION="OS=9.2,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
before_install:
- gem install cocoapods --no-rdoc --no-ri --no-document --quiet
- gem install xcpretty --no-rdoc --no-ri --no-document --quiet
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.23.0/Carthage.pkg"
- curl -OlL "https://github.com/Carthage/Carthage/releases/download/0.11/Carthage.pkg"
- sudo installer -pkg "Carthage.pkg" -target /
- rm "Carthage.pkg"
before_script:
@@ -38,8 +36,8 @@ script:
xcodebuild -workspace CoreStore.xcworkspace -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Debug ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
xcodebuild -workspace CoreStore.xcworkspace -scheme "$SCHEME" -sdk "$SDK" -destination "$DESTINATION" -configuration Release ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
fi
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator10.3" -destination "OS=10.3,name=iPhone 7" -configuration Debug ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator10.3" -destination "OS=10.3,name=iPhone 7" -configuration Release ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator9.3" -destination "OS=9.3,name=iPhone 6s" -configuration Debug ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator9.3" -destination "OS=9.3,name=iPhone 6s" -configuration Release ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
- if [ $POD_LINT == "YES" ]; then
pod lib lint --quick;
fi

View File

@@ -0,0 +1 @@
github "JohnEstropia/GCDKit" == 1.2.6

View File

@@ -0,0 +1 @@
github "JohnEstropia/GCDKit" "1.2.6"

1
Carthage/Checkouts/GCDKit vendored Submodule

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "4.0.5"
s.version = "2.0.7"
s.license = "MIT"
s.summary = "Unleashing the real power of Core Data with the elegance and safety of Swift"
s.homepage = "https://github.com/JohnEstropia/CoreStore"
@@ -16,5 +16,9 @@ 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 USE_FRAMEWORKS -D DEBUG',
'OTHER_SWIFT_FLAGS[config=Release]' => '-D USE_FRAMEWORKS',
'GCC_PREPROCESSOR_DEFINITIONS' => 'USE_FRAMEWORKS=1' }
s.dependency "GCDKit", "1.2.6"
end

File diff suppressed because it is too large Load Diff

View File

@@ -10,10 +10,10 @@
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "F347F55F-7F5C-4476-9148-6E902F06E4AD",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStoreLibraries\/GCDKit",
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore\/"
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
"DVTSourceControlWorkspaceBlueprintVersion" : 204,
"DVTSourceControlWorkspaceBlueprintVersion" : 203,
"DVTSourceControlWorkspaceBlueprintRelativePathToProjectKey" : "CoreStore.xcodeproj",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoriesKey" : [
{

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0710"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -11,7 +11,8 @@
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
buildForAnalyzing = "YES"
hideIssues = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
@@ -25,7 +26,8 @@
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
buildForAnalyzing = "NO"
hideIssues = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
@@ -63,11 +65,6 @@
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
<AdditionalOption
key = "NSZombieEnabled"
value = "YES"
isEnabled = "YES">
</AdditionalOption>
</AdditionalOptions>
</TestAction>
<LaunchAction

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
@@ -14,10 +14,24 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
BlueprintIdentifier = "B5D9E2ED1CA2C317007A9D52"
BuildableName = "CoreStore_iOS7.framework"
BlueprintName = "CoreStore iOS7"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
BuildableName = "CoreStoreTests.xctest"
BlueprintName = "CoreStoreTests iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</BuildActionEntry>
</BuildActionEntries>
@@ -28,14 +42,24 @@
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"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
@@ -51,16 +75,15 @@
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
BlueprintIdentifier = "B5D9E2ED1CA2C317007A9D52"
BuildableName = "CoreStore_iOS7.framework"
BlueprintName = "CoreStore iOS7"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
@@ -70,19 +93,9 @@
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
<BuildableProductRunnable
runnableDebuggingMode = "0">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B54AAD481AF4D26E00848AE0"
BuildableName = "CoreStoreDemo.app"
BlueprintName = "CoreStoreDemo"
ReferencedContainer = "container:CoreStoreDemo.xcodeproj">
</BuildableReference>
</BuildableProductRunnable>
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Debug">
buildConfiguration = "Release">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0720"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0800"
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"

View File

@@ -7,4 +7,7 @@
<FileRef
location = "group:CoreStoreDemo/CoreStoreDemo.xcodeproj">
</FileRef>
<FileRef
location = "group:Carthage/Checkouts/GCDKit/GCDKit.xcodeproj">
</FileRef>
</Workspace>

View File

@@ -12,6 +12,7 @@
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADD1AFDC71700F90881 /* Palette.swift */; };
B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B503FADE1AFDC71700F90881 /* PaletteTableViewCell.swift */; };
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C111B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel */; };
B5125C141B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel in Sources */ = {isa = PBXBuildFile; fileRef = B5125C131B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel */; };
B52977D91B120B80003D50A5 /* ObserversViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977D81B120B80003D50A5 /* ObserversViewController.swift */; };
B52977DD1B120F3B003D50A5 /* TransactionsDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B52977DC1B120F3B003D50A5 /* TransactionsDemoViewController.swift */; };
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B52977DE1B120F83003D50A5 /* MapKit.framework */; };
@@ -35,6 +36,8 @@
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; };
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */; };
B5E599321B5240F50084BD5F /* OrganismTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */; };
B5E89ACD1C52929C003B04A9 /* GCDKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9241C202429008147CD /* GCDKit.framework */; };
B5E89ACE1C52929C003B04A9 /* GCDKit.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9241C202429008147CD /* GCDKit.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; };
B5E89AD11C5292A2003B04A9 /* CoreStore.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = B5BDC9211C202429008147CD /* CoreStore.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
B5EE25851B36E23C0000406B /* OrganismV1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5EE25841B36E23C0000406B /* OrganismV1.swift */; };
@@ -51,6 +54,7 @@
dstPath = "";
dstSubfolderSpec = 10;
files = (
B5E89ACE1C52929C003B04A9 /* GCDKit.framework in Embed Frameworks */,
B5E89AD11C5292A2003B04A9 /* CoreStore.framework in Embed Frameworks */,
);
name = "Embed Frameworks";
@@ -90,6 +94,7 @@
B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = QueryingResultsViewController.swift; sourceTree = "<group>"; };
B56965281B3582D30075EE4A /* MigrationDemo.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemo.xcdatamodel; sourceTree = "<group>"; };
B5BDC9211C202429008147CD /* CoreStore.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = CoreStore.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B5BDC9241C202429008147CD /* GCDKit.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = GCDKit.framework; sourceTree = BUILT_PRODUCTS_DIR; };
B5E599311B5240F50084BD5F /* OrganismTableViewCell.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = OrganismTableViewCell.swift; path = "CoreStoreDemo/MIgrations Demo/OrganismTableViewCell.swift"; sourceTree = SOURCE_ROOT; };
B5EE25801B36E1B00000406B /* MigrationDemoV2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = MigrationDemoV2.xcdatamodel; sourceTree = "<group>"; };
B5EE25841B36E23C0000406B /* OrganismV1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OrganismV1.swift; sourceTree = "<group>"; };
@@ -105,6 +110,7 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
B5E89ACD1C52929C003B04A9 /* GCDKit.framework in Frameworks */,
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */,
B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */,
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */,
@@ -140,6 +146,7 @@
children = (
B52977E01B120F8A003D50A5 /* CoreLocation.framework */,
B5BDC9211C202429008147CD /* CoreStore.framework */,
B5BDC9241C202429008147CD /* GCDKit.framework */,
B52977DE1B120F83003D50A5 /* MapKit.framework */,
);
name = Frameworks;
@@ -265,12 +272,11 @@
isa = PBXProject;
attributes = {
LastSwiftUpdateCheck = 0700;
LastUpgradeCheck = 0800;
LastUpgradeCheck = 0700;
ORGANIZATIONNAME = "John Rommel Estropia";
TargetAttributes = {
B54AAD481AF4D26E00848AE0 = {
CreatedOnToolsVersion = 6.3;
LastSwiftMigration = 0800;
};
};
};
@@ -330,6 +336,7 @@
B503FAE11AFDC71700F90881 /* Palette.swift in Sources */,
B503FAE21AFDC71700F90881 /* PaletteTableViewCell.swift in Sources */,
B560070F1B3EC90F00A9A8F9 /* OrganismV2ToV3MigrationPolicy.swift in Sources */,
B5125C141B521BA7003A42C7 /* OrganismV3ToV2.xcmappingmodel in Sources */,
B503FADF1AFDC71700F90881 /* ListObserverDemoViewController.swift in Sources */,
B54AAD4F1AF4D26E00848AE0 /* AppDelegate.swift in Sources */,
B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */,
@@ -376,10 +383,8 @@
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@@ -423,10 +428,8 @@
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
CLANG_WARN_EMPTY_BODY = YES;
CLANG_WARN_ENUM_CONVERSION = YES;
CLANG_WARN_INFINITE_RECURSION = YES;
CLANG_WARN_INT_CONVERSION = YES;
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
CLANG_WARN_SUSPICIOUS_MOVE = YES;
CLANG_WARN_UNREACHABLE_CODE = YES;
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
@@ -458,7 +461,6 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_VERSION = 3.0;
};
name = Debug;
};
@@ -471,8 +473,6 @@
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;
PRODUCT_NAME = "$(TARGET_NAME)";
SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule";
SWIFT_VERSION = 3.0;
};
name = Release;
};

View File

@@ -18,9 +18,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
var window: UIWindow?
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplicationLaunchOptionsKey: Any]? = nil) -> Bool {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
application.statusBarStyle = .LightContent
application.statusBarStyle = .lightContent
return true
}
}

View File

@@ -1,28 +1,39 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="12113" systemVersion="16C67" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES" initialViewController="Ni8-QF-XHB">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="10112" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="12078"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10083"/>
<capability name="Aspect ratio constraints" minToolsVersion="5.1"/>
<capability name="Constraints to layout margins" minToolsVersion="6.0"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<customFonts key="customFonts">
<array key="AppleSDGothicNeo.ttc">
<mutableArray key="AppleSDGothicNeo.ttc">
<string>AppleSDGothicNeo-Regular</string>
</array>
<array key="HelveticaNeue.ttc">
<string>HelveticaNeue</string>
</mutableArray>
<mutableArray key="HelveticaNeue.ttc">
<string>HelveticaNeue-Bold</string>
</array>
<array key="HelveticaNeueLights.ttc">
<string>HelveticaNeue-Bold</string>
<string>HelveticaNeue</string>
<string>HelveticaNeue</string>
</mutableArray>
<mutableArray key="HelveticaNeueLights.ttc">
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Thin</string>
</array>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
</mutableArray>
</customFonts>
<scenes>
<!--SNS Accounts-->
@@ -30,34 +41,34 @@
<objects>
<tableViewController id="AW4-lY-JNk" customClass="StackSetupDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="S3A-lm-AuA">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<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"/>
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<view key="tableHeaderView" contentMode="scaleToFill" id="yud-WH-MPa">
<rect key="frame" x="0.0" y="0.0" width="375" height="150"/>
<rect key="frame" x="0.0" y="64" width="600" height="150"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="sns" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="czc-nd-es9">
<rect key="frame" x="20" y="20" width="335" height="26.5"/>
<rect key="frame" x="20" y="20" width="560" 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"/>
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="name" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="1eh-91-O2N">
<rect key="frame" x="20" y="70" width="41" height="20.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="17"/>
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="friends" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p2y-0T-hQs">
<rect key="frame" x="314.5" y="72" width="40.5" height="17"/>
<rect key="frame" x="539.5" y="72" width="40.5" height="17"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="14"/>
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="1eh-91-O2N" firstAttribute="top" secondItem="czc-nd-es9" secondAttribute="bottom" constant="23.5" id="F6q-Jt-glI"/>
<constraint firstItem="p2y-0T-hQs" firstAttribute="leading" relation="greaterThanOrEqual" secondItem="1eh-91-O2N" secondAttribute="trailing" constant="20" id="GVS-6o-rmj"/>
@@ -71,24 +82,24 @@
</view>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="8b8-lM-Krq" detailTextLabel="hR1-Zb-BOk" style="IBUITableViewCellStyleValue1" id="dMt-nx-EO5">
<rect key="frame" x="0.0" y="172" width="375" height="44"/>
<rect key="frame" x="0.0" y="236" width="600" 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="600" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="8b8-lM-Krq">
<rect key="frame" x="15" y="13" width="28.5" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Subtitle" textAlignment="right" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hR1-Zb-BOk">
<rect key="frame" x="308" y="13" width="52" height="19"/>
<rect key="frame" x="533" y="13" width="52" height="19"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="16"/>
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.55686274509803924" green="0.55686274509803924" blue="0.57647058823529407" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -120,23 +131,23 @@
<viewControllerLayoutGuide type="bottom" id="t9A-zf-Iew"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="75P-2m-6cr">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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"/>
<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"/>
<rect key="frame" x="0.0" y="324" width="600" height="276"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="calibratedRGB"/>
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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="22" width="600" 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="600" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<button opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="OQf-Bd-Zze">
<rect key="frame" x="295" y="8" width="72" height="27.5"/>
<rect key="frame" x="520" y="8" width="72" height="27.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<inset key="contentEdgeInsets" minX="7" minY="0.0" maxX="7" maxY="0.0"/>
<state key="normal" title="mutate!"/>
@@ -145,9 +156,9 @@
</connections>
</button>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="VZk-6K-4ut">
<rect key="frame" x="15" y="8" width="270" height="27.5"/>
<rect key="frame" x="15" y="8" width="495" height="27.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Thin" family="Helvetica Neue" pointSize="17"/>
<color key="textColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -173,42 +184,42 @@
</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="64" width="600" 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"/>
<rect key="frame" x="0.0" y="0.0" width="600" 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="90.5" width="560" 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"/>
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" 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="131.5" width="560" 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"/>
<color key="textColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" 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="560" height="29"/>
<segments>
<segment title="First"/>
<segment title="Second"/>
<segment title=""/>
</segments>
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<connections>
<action selector="segmentedControlValueChanged:" destination="iVv-Vc-nCL" eventType="valueChanged" id="RwG-kW-RPg"/>
</connections>
</segmentedControl>
<progressView opaque="NO" clipsSubviews="YES" contentMode="scaleToFill" verticalHuggingPriority="750" progress="0.5" translatesAutoresizingMaskIntoConstraints="NO" id="869-wx-Odb">
<rect key="frame" x="20" y="68" width="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"/>
<rect key="frame" x="20" y="68" width="560" height="2"/>
<color key="progressTintColor" red="1" green="1" blue="1" alpha="1" colorSpace="calibratedRGB"/>
<color key="trackTintColor" white="1" alpha="0.20000000000000001" colorSpace="calibratedWhite"/>
</progressView>
</subviews>
<color key="backgroundColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="zxy-nY-P44" firstAttribute="leading" secondItem="i7U-bW-juB" secondAttribute="leading" constant="20" id="1u1-Tq-hRn"/>
<constraint firstItem="6Ac-xl-ldZ" firstAttribute="top" secondItem="zxy-nY-P44" secondAttribute="bottom" constant="14.5" id="39B-9l-O3g"/>
@@ -226,7 +237,7 @@
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="height" constant="260" id="90R-Mf-iAB"/>
<constraint firstItem="i7U-bW-juB" firstAttribute="leading" secondItem="XKA-Ub-c2X" secondAttribute="leading" id="e9h-f4-c37"/>
@@ -235,7 +246,7 @@
</constraints>
</view>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="trailing" secondItem="XKA-Ub-c2X" secondAttribute="trailing" id="U0P-51-KT9"/>
<constraint firstAttribute="trailing" secondItem="WUc-3Y-Quw" secondAttribute="trailing" id="i7I-7B-97K"/>
@@ -265,31 +276,31 @@
<objects>
<tableViewController id="t0d-B0-B7U" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="static" style="grouped" separatorStyle="default" rowHeight="50" sectionHeaderHeight="10" sectionFooterHeight="10" id="uHB-Yr-ujV">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" red="0.93725490196078431" green="0.93725490196078431" blue="0.95686274509803926" alpha="1" colorSpace="calibratedRGB"/>
<sections>
<tableViewSection id="wIP-Hn-YfF">
<cells>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Q3n-Df-v1t" detailTextLabel="Hbn-cf-Y7m" style="IBUITableViewCellStyleSubtitle" id="AXm-KE-45G">
<rect key="frame" x="0.0" y="35" width="375" height="50"/>
<rect key="frame" x="0.0" y="99" width="600" 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="342" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Accounts" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Q3n-Df-v1t">
<rect key="frame" x="15" y="6" width="82" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901960784313" green="0.24313725490196078" blue="0.31372549019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Setting up multiple persistent store configurations" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Hbn-cf-Y7m">
<rect key="frame" x="15" y="30" width="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"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -299,24 +310,24 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="vpt-cT-gMo" detailTextLabel="ou9-TZ-8bf" style="IBUITableViewCellStyleSubtitle" id="fsb-zw-8Ii">
<rect key="frame" x="0.0" y="85" width="375" height="50"/>
<rect key="frame" x="0.0" y="149" width="600" 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="342" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Colors" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="vpt-cT-gMo">
<rect key="frame" x="15" y="6" width="56" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Observing list changes and single object changes" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ou9-TZ-8bf">
<rect key="frame" x="15" y="30" width="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"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -326,24 +337,24 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="UbU-Kd-yrY" detailTextLabel="uP1-Jc-o9v" style="IBUITableViewCellStyleSubtitle" id="ekW-PJ-mbo">
<rect key="frame" x="0.0" y="135" width="375" height="50"/>
<rect key="frame" x="0.0" y="199" width="600" 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="342" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Placemarks" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="UbU-Kd-yrY">
<rect key="frame" x="15" y="6" width="100.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Making changes with transactions" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="uP1-Jc-o9v">
<rect key="frame" x="15" y="30" width="179" 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"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -353,24 +364,24 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="C8Y-0y-lEG" detailTextLabel="jZw-qE-0ws" style="IBUITableViewCellStyleSubtitle" id="ph1-8z-C1m">
<rect key="frame" x="0.0" y="185" width="375" height="50"/>
<rect key="frame" x="0.0" y="249" width="600" 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="342" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Time Zones" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="C8Y-0y-lEG">
<rect key="frame" x="15" y="6" width="101.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Fetching objects and raw values" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="jZw-qE-0ws">
<rect key="frame" x="15" y="30" width="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"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -380,24 +391,24 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="ZfY-Aq-Ykq" detailTextLabel="QzD-9b-k1j" style="IBUITableViewCellStyleSubtitle" id="wyK-rk-3tI">
<rect key="frame" x="0.0" y="235" width="375" height="50"/>
<rect key="frame" x="0.0" y="299" width="600" 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="342" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="ZfY-Aq-Ykq">
<rect key="frame" x="15" y="6" width="61" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Implementing a custom logger" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QzD-9b-k1j">
<rect key="frame" x="15" y="30" width="159" 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"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -407,24 +418,24 @@
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="hSG-mG-YBw" detailTextLabel="X9P-TQ-LYh" style="IBUITableViewCellStyleSubtitle" id="xTM-Cf-0if">
<rect key="frame" x="0.0" y="285" width="375" height="50"/>
<rect key="frame" x="0.0" y="349" width="600" 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="342" height="49.5"/>
<rect key="frame" x="0.0" y="0.0" width="567" height="49.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Evolution" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="hSG-mG-YBw">
<rect key="frame" x="15" y="6" width="78.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Migrating and de-migrating stores" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="X9P-TQ-LYh">
<rect key="frame" x="15" y="30" width="179.5" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -456,69 +467,69 @@
<viewControllerLayoutGuide type="bottom" id="aI4-O3-OCi"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="w8K-eN-RvU">
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
<rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" misplaced="YES" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
<view contentMode="scaleToFill" verticalCompressionResistancePriority="250" translatesAutoresizingMaskIntoConstraints="NO" id="NhC-oM-bkd">
<rect key="frame" x="20" y="69.5" width="552" height="36.5"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<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="20" y="116" width="552" height="18"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" id="4h9-ha-EzR"/>
</constraints>
<fontDescription key="fontDescription" type="boldSystem" pointSize="15"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<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="20" y="154" 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"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<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="20" y="192" 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"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<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="20" y="230" 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"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<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="102" y="148" width="472" 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="102" y="186" width="472" 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="102" y="224" width="472" height="31"/>
<connections>
<action selector="brightnessSliderValueDidChange:" destination="dX3-kR-CYC" eventType="valueChanged" id="F09-EP-2iD"/>
</connections>
</slider>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p4O-tf-dgt">
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="p4O-tf-dgt">
<rect key="frame" x="20" y="49" width="552" height="20.5"/>
<fontDescription key="fontDescription" type="boldSystem" pointSize="17"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.0" green="0.0" blue="0.0" alpha="1" colorSpace="calibratedRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="p4O-tf-dgt" firstAttribute="trailing" secondItem="w8K-eN-RvU" secondAttribute="trailingMargin" id="022-ll-1s1"/>
<constraint firstItem="vTa-ly-eyO" firstAttribute="top" secondItem="rry-vh-bRK" secondAttribute="bottom" constant="20" id="1hd-Ti-CnT"/>
@@ -580,23 +591,23 @@
<viewControllerLayoutGuide type="bottom" id="LNL-mj-D7l"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="6x3-vn-Egt">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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"/>
<rect key="frame" x="4" y="64" width="592" height="268"/>
<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"/>
<rect key="frame" x="4" y="332" width="592" height="268"/>
<connections>
<segue destination="sll-yo-mBc" kind="embed" id="AAl-HS-dq2"/>
</connections>
</containerView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<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"/>
@@ -625,11 +636,11 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" id="00L-5k-Eno">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" red="0.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"/>
<color key="tintColor" red="0.92549019610000005" green="0.94117647059999998" blue="0.94509803920000002" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<textAttributes key="titleTextAttributes">
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="24"/>
<color key="textColor" red="0.90744441747665405" green="0.9265514612197876" blue="0.93116652965545654" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</textAttributes>
</navigationBar>
<nil name="viewControllers"/>
@@ -646,19 +657,19 @@
<objects>
<tableViewController id="3AE-ED-0oj" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="DAz-BE-6Ca">
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
<rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
<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="66" width="592" 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="342" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="559" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="uQX-PI-UWF">
<rect key="frame" x="8" y="8" width="27" height="27"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="width" secondItem="uQX-PI-UWF" secondAttribute="height" multiplier="1:1" id="9qA-iN-Neb"/>
</constraints>
@@ -666,7 +677,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="HJC-5w-lIN">
<rect key="frame" x="45" y="8" width="34.5" height="27"/>
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="14"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -705,20 +716,20 @@
<objects>
<tableViewController id="lCE-i6-UCT" customClass="ListObserverDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="Zba-8M-Zd7">
<rect key="frame" x="0.0" y="0.0" width="375" height="301.5"/>
<rect key="frame" x="0.0" y="0.0" width="592" height="268"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<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"/>
<rect key="frame" x="0.0" y="66" width="592" 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="342" height="43.5"/>
<rect key="frame" x="0.0" y="0.0" width="559" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="5uq-Yi-XwH">
<rect key="frame" x="8" y="8" width="27" height="27"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="width" secondItem="5uq-Yi-XwH" secondAttribute="height" multiplier="1:1" id="oOe-HC-VyN"/>
</constraints>
@@ -726,7 +737,7 @@
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="Zyu-PC-WmO">
<rect key="frame" x="45" y="8" width="34.5" height="27"/>
<fontDescription key="fontDescription" name="HelveticaNeue" family="Helvetica Neue" pointSize="14"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -769,11 +780,11 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" id="6XA-6M-yvZ">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" red="0.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.74117647060000003" green="0.76470588240000004" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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"/>
<color key="textColor" red="0.74117647060000003" green="0.76470588240000004" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</textAttributes>
</navigationBar>
<nil name="viewControllers"/>
@@ -794,17 +805,17 @@
<viewControllerLayoutGuide type="bottom" id="RZg-hi-T8O"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="k4s-iL-Krh">
<rect key="frame" x="0.0" y="64" width="375" height="603"/>
<rect key="frame" x="0.0" y="64" width="600" height="536"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" ambiguous="YES" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
<rect key="frame" x="0.0" y="0.0" width="375" height="536"/>
<mapView clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleToFill" mapType="standard" translatesAutoresizingMaskIntoConstraints="NO" id="V2U-0R-Ts0">
<rect key="frame" x="0.0" y="0.0" width="600" height="536"/>
<connections>
<outlet property="delegate" destination="jPl-fH-NlD" id="Sjn-YC-haS"/>
</connections>
</mapView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="RZg-hi-T8O" firstAttribute="top" secondItem="V2U-0R-Ts0" secondAttribute="bottom" id="GcS-Jz-Wcm"/>
<constraint firstItem="V2U-0R-Ts0" firstAttribute="top" secondItem="k4s-iL-Krh" secondAttribute="top" id="S5Z-Da-V6J"/>
@@ -831,30 +842,30 @@
<viewControllerLayoutGuide type="bottom" id="Cwn-Jd-4Lr"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="n9M-Je-Dj0">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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"/>
<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"/>
<rect key="frame" x="0.0" y="142" width="600" height="458"/>
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.94509803921568625" green="0.7686274509803922" blue="0.058823529411764705" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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="94" width="560" height="29"/>
<segments>
<segment title="Log"/>
<segment title="Error"/>
<segment title="Assert"/>
</segments>
<color key="tintColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<action selector="segmentedControlValueChanged:" destination="5yy-0N-QDU" eventType="valueChanged" id="2pp-Vt-2Os"/>
</connections>
</segmentedControl>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="4iq-B4-k0p" firstAttribute="leading" secondItem="n9M-Je-Dj0" secondAttribute="leading" constant="20" id="6SM-eN-NxP"/>
<constraint firstItem="TpK-gX-CTN" firstAttribute="leading" secondItem="n9M-Je-Dj0" secondAttribute="leading" id="AAw-IG-taz"/>
@@ -890,11 +901,11 @@
<navigationBar key="navigationBar" contentMode="scaleToFill" id="wJo-mp-1pS">
<rect key="frame" x="0.0" y="0.0" width="320" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<color key="tintColor" red="0.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.74117647058823533" green="0.76470588235294112" blue="0.7803921568627451" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="barTintColor" red="0.17254901960784313" green="0.24313725490196078" blue="0.31372549019607843" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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"/>
<color key="textColor" red="0.74117647060000003" green="0.76470588240000004" blue="0.78039215689999997" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</textAttributes>
</navigationBar>
<nil name="viewControllers"/>
@@ -915,15 +926,15 @@
<viewControllerLayoutGuide type="bottom" id="DlN-cN-JXd"/>
</layoutGuides>
<view key="view" contentMode="scaleToFill" id="eC3-ql-d2o">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<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"/>
<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"/>
<rect key="frame" x="0.0" y="64" width="600" height="536"/>
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<view key="tableHeaderView" contentMode="scaleToFill" id="iaH-1W-Sbo">
<rect key="frame" x="0.0" y="0.0" width="375" height="80"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="80"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<subviews>
<segmentedControl opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="left" contentVerticalAlignment="top" segmentControlStyle="bordered" selectedSegmentIndex="0" translatesAutoresizingMaskIntoConstraints="NO" id="YVj-dA-fyV">
@@ -932,13 +943,13 @@
<segment title="Fetch"/>
<segment title="Query"/>
</segments>
<color key="tintColor" red="0.15542715787887573" green="0.2203737199306488" blue="0.2959403395652771" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="tintColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<connections>
<action selector="segmentedControlValueChanged:" destination="qbj-MK-nIY" eventType="valueChanged" id="Wok-dl-uq7"/>
</connections>
</segmentedControl>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="0.80000000000000004" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="0.80000000000000004" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="YVj-dA-fyV" firstAttribute="leading" secondItem="iaH-1W-Sbo" secondAttribute="leading" constant="20" id="1dX-t7-dyR"/>
<constraint firstAttribute="centerY" secondItem="YVj-dA-fyV" secondAttribute="centerY" id="HXU-Z7-jfu"/>
@@ -947,17 +958,17 @@
</view>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="UITableViewCell" textLabel="6db-P0-6Ms" style="IBUITableViewCellStyleDefault" id="vUr-WV-qur">
<rect key="frame" x="0.0" y="102" width="375" height="44"/>
<rect key="frame" x="0.0" y="102" width="600" 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="567" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="Title" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="6db-P0-6Ms">
<rect key="frame" x="15" y="0.0" width="325" height="43.5"/>
<rect key="frame" x="15" y="0.0" width="550" height="43.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="18"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -970,7 +981,7 @@
</connections>
</tableView>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstItem="WGY-kX-mAx" firstAttribute="top" secondItem="dgu-PC-LiB" secondAttribute="bottom" id="0pS-cU-ibk"/>
<constraint firstAttribute="trailing" secondItem="WGY-kX-mAx" secondAttribute="trailing" id="NeY-g5-CaB"/>
@@ -995,30 +1006,30 @@
<objects>
<tableViewController id="tIs-pN-OgO" customClass="FetchingResultsViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="60" sectionHeaderHeight="36" sectionFooterHeight="22" id="tVl-tT-UDk">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<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"/>
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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="100" width="600" 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="600" height="59.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="RgX-yK-1L2">
<rect key="frame" x="15" y="11" width="48.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="QZ4-A2-x4h">
<rect key="frame" x="15" y="35" width="31" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
@@ -1041,30 +1052,30 @@
<objects>
<tableViewController id="o9D-Xm-13g" customClass="QueryingResultsViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" rowHeight="60" sectionHeaderHeight="36" sectionFooterHeight="22" id="bMh-zR-xwu">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<rect key="frame" x="0.0" y="0.0" width="600" height="600"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<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"/>
<color key="backgroundColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="separatorColor" red="0.20392156859999999" green="0.28627450980000002" blue="0.36862745099999999" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<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="100" width="600" 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="600" height="59.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="name" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Syt-QJ-KXg">
<rect key="frame" x="15" y="11" width="48.5" height="24"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" name="HelveticaNeue-Light" family="Helvetica Neue" pointSize="20"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" text="offset" lineBreakMode="wordWrap" numberOfLines="0" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="yHS-dP-IKS">
<rect key="frame" x="15" y="35" width="31" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.13079629838466644" green="0.184075728058815" blue="0.24594299495220184" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>

View File

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

View File

@@ -19,27 +19,29 @@ private struct Static {
SQLiteStore(
fileName: "TimeZoneDemo.sqlite",
configuration: "FetchingAndQueryingDemo",
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
_ = try? dataStack.perform(
synchronous: { (transaction) in
dataStack.beginSynchronous { (transaction) -> Void in
transaction.deleteAll(From(TimeZone))
for name in NSTimeZone.knownTimeZoneNames() {
transaction.deleteAll(From<TimeZone>())
let rawTimeZone = NSTimeZone(name: name)!
let cachedTimeZone = transaction.create(Into(TimeZone))
for name in NSTimeZone.knownTimeZoneNames {
let rawTimeZone = NSTimeZone(name: name)!
let cachedTimeZone = transaction.create(Into<TimeZone>())
cachedTimeZone.name = rawTimeZone.name
cachedTimeZone.abbreviation = rawTimeZone.abbreviation ?? ""
cachedTimeZone.secondsFromGMT = Int32(rawTimeZone.secondsFromGMT)
cachedTimeZone.hasDaylightSavingTime = rawTimeZone.isDaylightSavingTime
cachedTimeZone.daylightSavingTimeOffset = rawTimeZone.daylightSavingTimeOffset
}
cachedTimeZone.name = rawTimeZone.name
cachedTimeZone.abbreviation = rawTimeZone.abbreviation ?? ""
cachedTimeZone.secondsFromGMT = Int32(rawTimeZone.secondsFromGMT)
cachedTimeZone.hasDaylightSavingTime = rawTimeZone.daylightSavingTime
cachedTimeZone.daylightSavingTimeOffset = rawTimeZone.daylightSavingTimeOffset
}
)
transaction.commitAndWait()
}
return dataStack
}()
}
@@ -51,7 +53,7 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
// MARK: UIViewController
override func viewDidAppear(_ animated: Bool) {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
@@ -65,27 +67,27 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
let alert = UIAlertController(
title: "Fetch and Query Demo",
message: "This demo shows how to execute fetches and queries.\n\nEach menu item executes and displays a preconfigured fetch/query.",
preferredStyle: .alert
preferredStyle: .Alert
)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepare(for: segue, sender: sender)
super.prepareForSegue(segue, sender: sender)
if let indexPath = sender as? IndexPath {
if let indexPath = sender as? NSIndexPath {
switch segue.destination {
switch segue.destinationViewController {
case let controller as FetchingResultsViewController:
let item = self.fetchingItems[indexPath.row]
controller.set(timeZones: item.fetch(), title: item.title)
controller.setTimeZones(item.fetch(), title: item.title)
case let controller as QueryingResultsViewController:
let item = self.queryingItems[indexPath.row]
controller.set(value: item.query(), title: item.title)
controller.setValue(item.query(), title: item.title)
default:
break
@@ -96,14 +98,14 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
// MARK: UITableViewDataSource
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch self.segmentedControl?.selectedSegmentIndex {
case Section.fetching.rawValue?:
case Section.Fetching.rawValue?:
return self.fetchingItems.count
case Section.querying.rawValue?:
case Section.Querying.rawValue?:
return self.queryingItems.count
default:
@@ -111,16 +113,16 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
}
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell")!
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell")!
switch self.segmentedControl?.selectedSegmentIndex {
case Section.fetching.rawValue?:
case Section.Fetching.rawValue?:
cell.textLabel?.text = self.fetchingItems[indexPath.row].title
case Section.querying.rawValue?:
case Section.Querying.rawValue?:
cell.textLabel?.text = self.queryingItems[indexPath.row].title
default:
@@ -133,17 +135,17 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
// MARK: UITableViewDelegate
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
switch self.segmentedControl?.selectedSegmentIndex {
case Section.fetching.rawValue?:
self.performSegue(withIdentifier: "FetchingResultsViewController", sender: indexPath)
case Section.Fetching.rawValue?:
self.performSegueWithIdentifier("FetchingResultsViewController", sender: indexPath)
case Section.querying.rawValue?:
self.performSegue(withIdentifier: "QueryingResultsViewController", sender: indexPath)
case Section.Querying.rawValue?:
self.performSegueWithIdentifier("QueryingResultsViewController", sender: indexPath)
default:
break
@@ -155,8 +157,8 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
private enum Section: Int {
case fetching
case querying
case Fetching
case Querying
}
private let fetchingItems = [
@@ -165,8 +167,8 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
fetch: { () -> [TimeZone] in
return Static.timeZonesStack.fetchAll(
From<TimeZone>(),
OrderBy(.ascending(#keyPath(TimeZone.name)))
From(TimeZone),
OrderBy(.Ascending("name"))
)!
}
),
@@ -175,9 +177,9 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
fetch: { () -> [TimeZone] in
return Static.timeZonesStack.fetchAll(
From<TimeZone>(),
Where("%K BEGINSWITH[c] %@", #keyPath(TimeZone.name), "Asia"),
OrderBy(.ascending(#keyPath(TimeZone.secondsFromGMT)))
From(TimeZone),
Where("%K BEGINSWITH[c] %@", "name", "Asia"),
OrderBy(.Ascending("secondsFromGMT"))
)!
}
),
@@ -186,10 +188,10 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
fetch: { () -> [TimeZone] in
return Static.timeZonesStack.fetchAll(
From<TimeZone>(),
Where("%K BEGINSWITH[c] %@", #keyPath(TimeZone.name), "America")
|| Where("%K BEGINSWITH[c] %@", #keyPath(TimeZone.name), "Europe"),
OrderBy(.ascending(#keyPath(TimeZone.secondsFromGMT)))
From(TimeZone),
Where("%K BEGINSWITH[c] %@", "name", "America")
|| Where("%K BEGINSWITH[c] %@", "name", "Europe"),
OrderBy(.Ascending("secondsFromGMT"))
)!
}
),
@@ -198,9 +200,9 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
fetch: { () -> [TimeZone] in
return Static.timeZonesStack.fetchAll(
From<TimeZone>(),
!Where("%K BEGINSWITH[c] %@", #keyPath(TimeZone.name), "America"),
OrderBy(.ascending(#keyPath(TimeZone.secondsFromGMT)))
From(TimeZone),
!Where("%K BEGINSWITH[c] %@", "name", "America"),
OrderBy(.Ascending("secondsFromGMT"))
)!
}
),
@@ -209,9 +211,9 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
fetch: { () -> [TimeZone] in
return Static.timeZonesStack.fetchAll(
From<TimeZone>(),
From(TimeZone),
Where("hasDaylightSavingTime", isEqualTo: true),
OrderBy(.ascending(#keyPath(TimeZone.name)))
OrderBy(.Ascending("name"))
)!
}
)
@@ -220,60 +222,60 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
private let queryingItems = [
(
title: "Number of Time Zones",
query: { () -> Any in
query: { () -> AnyObject in
return Static.timeZonesStack.queryValue(
From<TimeZone>(),
Select<NSNumber>(.count(#keyPath(TimeZone.name)))
From(TimeZone),
Select<NSNumber>(.Count("name"))
)!
}
),
(
title: "Abbreviation For Tokyo's Time Zone",
query: { () -> Any in
query: { () -> AnyObject in
return Static.timeZonesStack.queryValue(
From<TimeZone>(),
Select<String>(#keyPath(TimeZone.abbreviation)),
Where("%K ENDSWITH[c] %@", #keyPath(TimeZone.name), "Tokyo")
From(TimeZone),
Select<String>("abbreviation"),
Where("%K ENDSWITH[c] %@", "name", "Tokyo")
)!
}
),
(
title: "All Abbreviations",
query: { () -> Any in
query: { () -> AnyObject in
return Static.timeZonesStack.queryAttributes(
From<TimeZone>(),
Select<NSDictionary>(#keyPath(TimeZone.name), #keyPath(TimeZone.abbreviation)),
OrderBy(.ascending(#keyPath(TimeZone.name)))
From(TimeZone),
Select<NSDictionary>("name", "abbreviation"),
OrderBy(.Ascending("name"))
)!
}
),
(
title: "Number of Countries per Time Zone",
query: { () -> Any in
query: { () -> AnyObject in
return Static.timeZonesStack.queryAttributes(
From<TimeZone>(),
Select<NSDictionary>(.count(#keyPath(TimeZone.abbreviation)), #keyPath(TimeZone.abbreviation)),
GroupBy(#keyPath(TimeZone.abbreviation)),
OrderBy(.ascending(#keyPath(TimeZone.secondsFromGMT)), .ascending(#keyPath(TimeZone.name)))
From(TimeZone),
Select<NSDictionary>(.Count("abbreviation"), "abbreviation"),
GroupBy("abbreviation"),
OrderBy(.Ascending("secondsFromGMT"), .Ascending("name"))
)!
}
),
(
title: "Number of Countries with Summer Time",
query: { () -> Any in
query: { () -> AnyObject in
return Static.timeZonesStack.queryAttributes(
From<TimeZone>(),
From(TimeZone),
Select<NSDictionary>(
.count(#keyPath(TimeZone.hasDaylightSavingTime), as: "numberOfCountries"),
#keyPath(TimeZone.hasDaylightSavingTime)
.Count("hasDaylightSavingTime", As: "numberOfCountries"),
"hasDaylightSavingTime"
),
GroupBy(#keyPath(TimeZone.hasDaylightSavingTime)),
OrderBy(.descending(#keyPath(TimeZone.hasDaylightSavingTime)))
GroupBy("hasDaylightSavingTime"),
OrderBy(.Descending("hasDaylightSavingTime"))
)!
}
)
@@ -284,7 +286,7 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
@IBOutlet dynamic weak var segmentedControl: UISegmentedControl?
@IBOutlet dynamic weak var tableView: UITableView?
@IBAction dynamic func segmentedControlValueChanged(_ sender: AnyObject?) {
@IBAction dynamic func segmentedControlValueChanged(sender: AnyObject?) {
self.tableView?.reloadData()
}

View File

@@ -14,7 +14,7 @@ class FetchingResultsViewController: UITableViewController {
// MARK: Public
func set(timeZones: [TimeZone]?, title: String) {
func setTimeZones(timeZones: [TimeZone]?, title: String) {
self.timeZones += timeZones ?? []
self.sectionTitle = title
@@ -36,14 +36,14 @@ class FetchingResultsViewController: UITableViewController {
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.timeZones.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath)
let timeZone = self.timeZones[indexPath.row]
cell.textLabel?.text = timeZone.name
@@ -55,7 +55,7 @@ class FetchingResultsViewController: UITableViewController {
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sectionTitle
}

View File

@@ -12,23 +12,23 @@ class QueryingResultsViewController: UITableViewController {
// MARK: Public
func set(value: Any?, title: String) {
func setValue(value: AnyObject?, title: String) {
switch value {
case (let array as [Any])?:
self.values = array.map { (item: Any) -> (title: String, detail: String) in
case (let array as [AnyObject])?:
self.values = array.map { (item: AnyObject) -> (title: String, detail: String) in
(
title: String(describing: item),
detail: String(reflecting: type(of: item))
title: item.description,
detail: String(reflecting: item.dynamicType)
)
}
case let item?:
self.values = [
(
title: String(describing: item),
detail: String(reflecting: type(of: item))
title: item.description,
detail: String(reflecting: item.dynamicType)
)
]
@@ -55,14 +55,14 @@ class QueryingResultsViewController: UITableViewController {
// MARK: UITableViewDataSource
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.values.count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell", for: indexPath)
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell", forIndexPath: indexPath)
let value = self.values[indexPath.row]
@@ -75,7 +75,7 @@ class QueryingResultsViewController: UITableViewController {
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return self.sectionTitle
}

View File

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

View File

@@ -10,21 +10,21 @@ import UIKit
import CoreStore
struct ColorsDemo {
private struct Static {
enum Filter: String {
case all = "All Colors"
case light = "Light Colors"
case dark = "Dark Colors"
case All = "All Colors"
case Light = "Light Colors"
case Dark = "Dark Colors"
func next() -> Filter {
switch self {
case .all: return .light
case .light: return .dark
case .dark: return .all
case All: return .Light
case Light: return .Dark
case Dark: return .All
}
}
@@ -32,51 +32,35 @@ struct ColorsDemo {
switch self {
case .all: return Where(true)
case .light: return Palette.where({ $0.brightness >= 0.9 })
case .dark: return Palette.where({ $0.brightness <= 0.4 })
case .All: return Where(true)
case .Light: return Where("brightness >= 0.9")
case .Dark: return Where("brightness <= 0.4")
}
}
}
static var filter = Filter.all {
static var filter = Filter.All {
didSet {
self.palettes.refetch(
self.filter.whereClause(),
Palette.orderBy(ascending: { $0.hue })
)
self.palettes.refetch(self.filter.whereClause())
}
}
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(
try! CoreStore.addStorageAndWait(
SQLiteStore(
fileName: "ColorsDemo.sqlite",
localStorageOptions: .recreateStoreOnModelMismatch
configuration: "ObservingDemo",
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
return ColorsDemo.stack.monitorSectionedList(
From<Palette>(),
SectionBy(Palette.keyPath({ $0.colorName })),
Palette.orderBy(ascending: { $0.hue })
return CoreStore.monitorSectionedList(
From(Palette),
SectionBy("colorName"),
OrderBy(.Ascending("hue"))
)
}()
}
@@ -90,7 +74,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
deinit {
ColorsDemo.palettes.removeObserver(self)
Static.palettes.removeObserver(self)
}
@@ -102,23 +86,23 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
let navigationItem = self.navigationItem
navigationItem.leftBarButtonItems = [
self.editButtonItem,
self.editButtonItem(),
UIBarButtonItem(
barButtonSystemItem: .trash,
barButtonSystemItem: .Trash,
target: self,
action: #selector(self.resetBarButtonItemTouched(_:))
)
]
let filterBarButton = UIBarButtonItem(
title: ColorsDemo.filter.rawValue,
style: .plain,
title: Static.filter.rawValue,
style: .Plain,
target: self,
action: #selector(self.filterBarButtonItemTouched(_:))
)
navigationItem.rightBarButtonItems = [
UIBarButtonItem(
barButtonSystemItem: .add,
barButtonSystemItem: .Add,
target: self,
action: #selector(self.addBarButtonItemTouched(_:))
),
@@ -126,16 +110,16 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
]
self.filterBarButton = filterBarButton
ColorsDemo.palettes.addObserver(self)
Static.palettes.addObserver(self)
self.setTable(enabled: !ColorsDemo.palettes.isPendingRefetch)
self.setTableEnabled(!Static.palettes.isPendingRefetch)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
super.prepare(for: segue, sender: sender)
super.prepareForSegue(segue, sender: sender)
switch (segue.identifier, segue.destination, sender) {
switch (segue.identifier, segue.destinationViewController, sender) {
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as Palette):
destinationViewController.palette = palette
@@ -148,21 +132,21 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
// MARK: UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return ColorsDemo.palettes.numberOfSections()
return Static.palettes.numberOfSections()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return ColorsDemo.palettes.numberOfObjectsInSection(section)
return Static.palettes.numberOfObjectsInSection(section)
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "PaletteTableViewCell") as! PaletteTableViewCell
let cell = tableView.dequeueReusableCellWithIdentifier("PaletteTableViewCell") as! PaletteTableViewCell
let palette = ColorsDemo.palettes[indexPath]
let palette = Static.palettes[indexPath]
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
@@ -172,106 +156,103 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
tableView.deselectRowAtIndexPath(indexPath, animated: true)
self.performSegue(
withIdentifier: "ObjectObserverDemoViewController",
sender: ColorsDemo.palettes[indexPath]
self.performSegueWithIdentifier(
"ObjectObserverDemoViewController",
sender: Static.palettes[indexPath]
)
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
override func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
switch editingStyle {
case .delete:
let palette = ColorsDemo.palettes[indexPath]
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
transaction.delete(palette)
},
completion: { _ in }
)
case .Delete:
let palette = Static.palettes[indexPath]
CoreStore.beginAsynchronous{ (transaction) -> Void in
transaction.delete(palette)
transaction.commit { (result) -> Void in }
}
default:
break
}
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
return ColorsDemo.palettes.sectionInfoAtIndex(section).name
return Static.palettes.sectionInfoAtIndex(section).name
}
// MARK: ListObserver
func listMonitorWillChange(_ monitor: ListMonitor<Palette>) {
func listMonitorWillChange(monitor: ListMonitor<Palette>) {
self.tableView.beginUpdates()
}
func listMonitorDidChange(_ monitor: ListMonitor<Palette>) {
func listMonitorDidChange(monitor: ListMonitor<Palette>) {
self.tableView.endUpdates()
}
func listMonitorWillRefetch(_ monitor: ListMonitor<Palette>) {
func listMonitorWillRefetch(monitor: ListMonitor<Palette>) {
self.setTable(enabled: false)
self.setTableEnabled(false)
}
func listMonitorDidRefetch(_ monitor: ListMonitor<Palette>) {
func listMonitorDidRefetch(monitor: ListMonitor<Palette>) {
self.filterBarButton?.title = ColorsDemo.filter.rawValue
self.filterBarButton?.title = Static.filter.rawValue
self.tableView.reloadData()
self.setTable(enabled: true)
self.setTableEnabled(true)
}
// MARK: ListObjectObserver
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: IndexPath) {
func listMonitor(monitor: ListMonitor<Palette>, didInsertObject object: Palette, toIndexPath indexPath: NSIndexPath) {
self.tableView.insertRows(at: [indexPath], with: .automatic)
self.tableView.insertRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: IndexPath) {
func listMonitor(monitor: ListMonitor<Palette>, didDeleteObject object: Palette, fromIndexPath indexPath: NSIndexPath) {
self.tableView.deleteRows(at: [indexPath], with: .automatic)
self.tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: .Automatic)
}
func listMonitor(_ monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: IndexPath) {
func listMonitor(monitor: ListMonitor<Palette>, didUpdateObject object: Palette, atIndexPath indexPath: NSIndexPath) {
if let cell = self.tableView.cellForRow(at: indexPath) as? PaletteTableViewCell {
if let cell = self.tableView.cellForRowAtIndexPath(indexPath) as? PaletteTableViewCell {
let palette = ColorsDemo.palettes[indexPath]
let palette = Static.palettes[indexPath]
cell.colorView?.backgroundColor = palette.color
cell.label?.text = palette.colorText
}
}
func listMonitor(_ monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
func listMonitor(monitor: ListMonitor<Palette>, didMoveObject object: Palette, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
self.tableView.deleteRows(at: [fromIndexPath], with: .automatic)
self.tableView.insertRows(at: [toIndexPath], with: .automatic)
self.tableView.deleteRowsAtIndexPaths([fromIndexPath], withRowAnimation: .Automatic)
self.tableView.insertRowsAtIndexPaths([toIndexPath], withRowAnimation: .Automatic)
}
// MARK: ListSectionObserver
func listMonitor(_ monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
func listMonitor(monitor: ListMonitor<Palette>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
self.tableView.insertSections(IndexSet(integer: sectionIndex), with: .automatic)
self.tableView.insertSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
}
func listMonitor(_ monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
func listMonitor(monitor: ListMonitor<Palette>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
self.tableView.deleteSections(IndexSet(integer: sectionIndex), with: .automatic)
self.tableView.deleteSections(NSIndexSet(index: sectionIndex), withRowAnimation: .Automatic)
}
@@ -279,46 +260,43 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
private var filterBarButton: UIBarButtonItem?
@IBAction private dynamic func resetBarButtonItemTouched(_ sender: AnyObject?) {
@IBAction private dynamic func resetBarButtonItemTouched(sender: AnyObject?) {
ColorsDemo.stack.perform(
asynchronous: { (transaction) in
transaction.deleteAll(From<Palette>())
},
completion: { _ in }
)
CoreStore.beginAsynchronous { (transaction) -> Void in
transaction.deleteAll(From(Palette))
transaction.commit()
}
}
@IBAction private dynamic func filterBarButtonItemTouched(_ sender: AnyObject?) {
@IBAction private dynamic func filterBarButtonItemTouched(sender: AnyObject?) {
ColorsDemo.filter = ColorsDemo.filter.next()
Static.filter = Static.filter.next()
}
@IBAction private dynamic func addBarButtonItemTouched(_ sender: AnyObject?) {
@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 }
)
CoreStore.beginAsynchronous { (transaction) -> Void in
let palette = transaction.create(Into(Palette))
palette.setInitialValues()
transaction.commit()
}
}
private func setTable(enabled: Bool) {
private func setTableEnabled(enabled: Bool) {
UIView.animate(
withDuration: 0.2,
UIView.animateWithDuration(
0.2,
delay: 0,
options: .beginFromCurrentState,
options: .BeginFromCurrentState,
animations: { () -> Void in
if let tableView = self.tableView {
tableView.alpha = enabled ? 1.0 : 0.5
tableView.isUserInteractionEnabled = enabled
tableView.userInteractionEnabled = enabled
}
},
completion: nil

View File

@@ -29,7 +29,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
if let palette = newValue {
self.monitor = ColorsDemo.stack.monitorObject(palette)
self.monitor = CoreStore.monitorObject(palette)
}
else {
@@ -50,22 +50,22 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
required init?(coder aDecoder: NSCoder) {
if let palette = ColorsDemo.stack.fetchOne(From<Palette>(), Palette.orderBy(ascending: { $0.hue })) {
if let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue"))) {
self.monitor = ColorsDemo.stack.monitorObject(palette)
self.monitor = CoreStore.monitorObject(palette)
}
else {
_ = try? ColorsDemo.stack.perform(
synchronous: { (transaction) in
let palette = transaction.create(Into<Palette>())
palette.setInitialValues(in: transaction)
}
)
CoreStore.beginSynchronous { (transaction) -> Void in
let palette = transaction.create(Into(Palette))
palette.setInitialValues()
transaction.commitAndWait()
}
let palette = ColorsDemo.stack.fetchOne(From<Palette>(), Palette.orderBy(ascending: { $0.hue }))!
self.monitor = ColorsDemo.stack.monitorObject(palette)
let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))!
self.monitor = CoreStore.monitorObject(palette)
}
super.init(coder: aDecoder)
@@ -85,24 +85,24 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
// MARK: ObjectObserver
func objectMonitor(_ monitor: ObjectMonitor<Palette>, didUpdateObject object: Palette, changedPersistentKeys: Set<KeyPath>) {
func objectMonitor(monitor: ObjectMonitor<Palette>, didUpdateObject object: Palette, changedPersistentKeys: Set<KeyPath>) {
self.reloadPaletteInfo(object, changedKeys: changedPersistentKeys)
}
func objectMonitor(_ monitor: ObjectMonitor<Palette>, didDeleteObject object: Palette) {
func objectMonitor(monitor: ObjectMonitor<Palette>, didDeleteObject object: Palette) {
self.navigationItem.rightBarButtonItem?.isEnabled = false
self.navigationItem.rightBarButtonItem?.enabled = false
self.colorNameLabel?.alpha = 0.3
self.colorView?.alpha = 0.3
self.hsbLabel?.text = "Deleted"
self.hsbLabel?.textColor = UIColor.red
self.hsbLabel?.textColor = UIColor.redColor()
self.hueSlider?.isEnabled = false
self.saturationSlider?.isEnabled = false
self.brightnessSlider?.isEnabled = false
self.hueSlider?.enabled = false
self.saturationSlider?.enabled = false
self.brightnessSlider?.enabled = false
}
@@ -118,65 +118,57 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
@IBOutlet weak var saturationSlider: UISlider?
@IBOutlet weak var brightnessSlider: UISlider?
@IBAction dynamic func hueSliderValueDidChange(_ sender: AnyObject?) {
@IBAction dynamic func hueSliderValueDidChange(sender: AnyObject?) {
let hue = self.hueSlider?.value ?? 0
ColorsDemo.stack.perform(
asynchronous: { [weak self] (transaction) in
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
if let palette = transaction.edit(self?.monitor?.object) {
if let palette = transaction.edit(self?.monitor?.object) {
palette.hue .= Int(hue)
}
},
completion: { _ in }
)
palette.hue = Int32(hue)
transaction.commit()
}
}
}
@IBAction dynamic func saturationSliderValueDidChange(_ sender: AnyObject?) {
@IBAction dynamic func saturationSliderValueDidChange(sender: AnyObject?) {
let saturation = self.saturationSlider?.value ?? 0
ColorsDemo.stack.perform(
asynchronous: { [weak self] (transaction) in
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
if let palette = transaction.edit(self?.monitor?.object) {
if let palette = transaction.edit(self?.monitor?.object) {
palette.saturation .= saturation
}
},
completion: { _ in }
)
palette.saturation = saturation
transaction.commit()
}
}
}
@IBAction dynamic func brightnessSliderValueDidChange(_ sender: AnyObject?) {
@IBAction dynamic func brightnessSliderValueDidChange(sender: AnyObject?) {
let brightness = self.brightnessSlider?.value ?? 0
ColorsDemo.stack.perform(
asynchronous: { [weak self] (transaction) in
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
if let palette = transaction.edit(self?.monitor?.object) {
if let palette = transaction.edit(self?.monitor?.object) {
palette.brightness .= brightness
}
},
completion: { _ in }
)
palette.brightness = brightness
transaction.commit()
}
}
}
@IBAction dynamic func deleteBarButtonTapped(_ sender: AnyObject?) {
@IBAction dynamic func deleteBarButtonTapped(sender: AnyObject?) {
ColorsDemo.stack.perform(
asynchronous: { [weak self] (transaction) in
transaction.delete(self?.monitor?.object)
},
completion: { _ in }
)
CoreStore.beginAsynchronous { [weak self] (transaction) -> Void in
transaction.delete(self?.monitor?.object)
transaction.commit()
}
}
func reloadPaletteInfo(_ palette: Palette, changedKeys: Set<String>?) {
func reloadPaletteInfo(palette: Palette, changedKeys: Set<String>?) {
self.colorNameLabel?.text = palette.colorName.value
self.colorNameLabel?.text = palette.colorName
let color = palette.color
self.colorNameLabel?.textColor = color
@@ -184,17 +176,17 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
self.hsbLabel?.text = palette.colorText
if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.hue }) == true {
if changedKeys == nil || changedKeys?.contains("hue") == true {
self.hueSlider?.value = Float(palette.hue.value)
self.hueSlider?.value = Float(palette.hue)
}
if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.saturation }) == true {
if changedKeys == nil || changedKeys?.contains("saturation") == true {
self.saturationSlider?.value = palette.saturation.value
self.saturationSlider?.value = palette.saturation
}
if changedKeys == nil || changedKeys?.contains(Palette.keyPath{ $0.brightness }) == true {
if changedKeys == nil || changedKeys?.contains("brightness") == true {
self.brightnessSlider?.value = palette.brightness.value
self.brightnessSlider?.value = palette.brightness
}
}
}

View File

@@ -15,16 +15,16 @@ class ObserversViewController: UIViewController {
// MARK: UIViewController
override func viewDidAppear(_ animated: Bool) {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let alert = UIAlertController(
title: "Observers Demo",
message: "This demo shows how to observe changes to a list of objects. The top and bottom view controllers both observe a single shared \"ListMonitor\" instance.\n\nTap on a row to see how to observe changes made to a single object using a \"ObjectMonitor\".",
preferredStyle: .alert
preferredStyle: .Alert
)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
}

View File

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

View File

@@ -8,6 +8,7 @@
import UIKit
import CoreStore
import GCDKit
// MARK: - CustomLoggerViewController
@@ -33,47 +34,47 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
CoreStore.logger = self
}
override func viewDidAppear(_ animated: Bool) {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let alert = UIAlertController(
title: "Logger Demo",
message: "This demo shows how to plug-in any logging framework to CoreStore.\n\nThe view controller implements CoreStoreLogger and appends all logs to the text view.",
preferredStyle: .alert
preferredStyle: .Alert
)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
// MARK: CoreStoreLogger
func log(level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
DispatchQueue.main.async { [weak self] in
GCDQueue.Main.async { [weak self] in
let levelString: String
switch level {
case .trace: levelString = "Trace"
case .notice: levelString = "Notice"
case .warning: levelString = "Warning"
case .fatal: levelString = "Fatal"
case .Trace: levelString = "Trace"
case .Notice: levelString = "Notice"
case .Warning: levelString = "Warning"
case .Fatal: levelString = "Fatal"
}
self?.textView?.insertText("\((String(describing: fileName) as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Log:\(levelString)] \(message)\n\n")
}
}
func log(error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func log(error error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
DispatchQueue.main.async { [weak self] in
GCDQueue.Main.async { [weak self] in
self?.textView?.insertText("\((String(describing: fileName) as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Error] \(message): \(error)\n\n")
}
}
func assert(_ condition: @autoclosure () -> Bool, message: @autoclosure () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
if condition() {
@@ -81,9 +82,9 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
}
let messageString = message()
DispatchQueue.main.async { [weak self] in
GCDQueue.Main.async { [weak self] in
self?.textView?.insertText("\((String(describing: fileName) as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(messageString)\n\n")
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(messageString)\n\n")
}
}
@@ -93,14 +94,15 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
@IBOutlet dynamic weak var textView: UITextView?
@IBOutlet dynamic weak var segmentedControl: UISegmentedControl?
@IBAction dynamic func segmentedControlValueChanged(_ sender: AnyObject?) {
@IBAction dynamic func segmentedControlValueChanged(sender: AnyObject?) {
switch self.segmentedControl?.selectedSegmentIndex {
case 0?:
let request = NSFetchRequest<NSFetchRequestResult>()
Where(true).applyToFetchRequest(request)
Where(false).applyToFetchRequest(request)
self.dataStack.beginAsynchronous { (transaction) -> Void in
transaction.create(Into(Palette))
}
case 1?:
_ = try? dataStack.addStorageAndWait(
@@ -111,9 +113,10 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
)
case 2?:
DispatchQueue.global(qos: .background).async {
self.dataStack.beginAsynchronous { (transaction) -> Void in
_ = self.dataStack.fetchOne(From<Place>())
transaction.commit()
transaction.commit()
}
default:

View File

@@ -12,7 +12,7 @@ import CoreStore
// MARK: - MigrationsDemoViewController
class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewDataSource, UITableViewDelegate {
class MigrationsDemoViewController: UIViewController {
// MARK: UIViewController
@@ -22,31 +22,31 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
if let segmentedControl = self.segmentedControl {
for (index, model) in self.models.enumerated() {
for (index, model) in self.models.enumerate() {
segmentedControl.setTitle(
model.label,
forSegmentAt: index
forSegmentAtIndex: index
)
}
}
self.set(dataStack: nil, model: nil, scrollToSelection: false)
self.setDataStack(nil, model: nil, scrollToSelection: false)
}
override func viewDidAppear(_ animated: Bool) {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let alert = UIAlertController(
title: "Migrations Demo",
message: "This demo shows how to run progressive migrations and how to support multiple model versions in a single project.\n\nThe persistent store contains 10000 organisms, which gain/lose properties when the migration evolves/devolves them.\n\nYou can use the \"mutate\" button to change an organism's properties then migrate to a different model to see how its value gets affected.",
preferredStyle: .alert
preferredStyle: .Alert
)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
let modelMetadata = withExtendedLifetime(DataStack(xcodeModelName: "MigrationDemo")) {
let modelMetadata = withExtendedLifetime(DataStack(modelName: "MigrationDemo")) {
(dataStack: DataStack) -> ModelMetadata in
let models = self.models
@@ -60,7 +60,7 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
}
for model in models {
if model.schemaHistory.currentModelVersion == storeVersion {
if model.version == storeVersion {
return model
}
@@ -72,121 +72,32 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
self.selectModelVersion(modelMetadata)
}
// MARK: ListObserver
func listMonitorWillChange(_ monitor: ListMonitor<NSManagedObject>) { }
func listMonitorDidChange(_ monitor: ListMonitor<NSManagedObject>) {
if self.lastSelectedIndexPath == nil,
let numberOfObjectsInSection = self.listMonitor?.numberOfObjectsInSection(0),
numberOfObjectsInSection > 0 {
self.tableView?.reloadData()
self.setSelectedIndexPath(IndexPath(row: 0, section: 0), scrollToSelection: false)
}
else {
self.updateDisplay(reloadData: true, scrollToSelection: true, animated: true)
}
}
func listMonitorDidRefetch(_ monitor: ListMonitor<NSManagedObject>) {
self.listMonitorDidChange(monitor)
}
// MARK: UITableViewDataSource
@objc dynamic func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listMonitor?.numberOfObjectsInSection(0) ?? 0
}
@objc dynamic func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "OrganismTableViewCell", for: indexPath) as! OrganismTableViewCell
let dna = (self.listMonitor?[indexPath] as? OrganismProtocol)?.dna.description ?? ""
cell.dnaLabel?.text = "DNA: \(dna)"
cell.mutateButtonHandler = { [weak self] _ -> Void in
guard let `self` = self,
let dataStack = self.dataStack,
let organism = self.listMonitor?[indexPath] else {
return
}
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
self.setEnabled(false)
dataStack.perform(
asynchronous: { (transaction) in
let organism = transaction.edit(organism) as! OrganismProtocol
organism.mutate()
},
completion: { [weak self] _ in
self?.setEnabled(true)
}
)
}
return cell
}
// MARK: UITableViewDelegate
@objc dynamic func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
}
// MARK: Private
private typealias ModelMetadata = (label: String, entityType: NSManagedObject.Type, schemaHistory: SchemaHistory)
private typealias ModelMetadata = (label: String, version: String, entityType: AnyClass, migrationChain: MigrationChain)
private let models: [ModelMetadata] = [
(
label: "Model V1",
version: "MigrationDemo",
entityType: OrganismV1.self,
schemaHistory: SchemaHistory(
XcodeDataModelSchema.from(
modelName: "MigrationDemo",
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
),
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
)
migrationChain: ["MigrationDemoV3", "MigrationDemoV2", "MigrationDemo"]
),
(
label: "Model V2",
version: "MigrationDemoV2",
entityType: OrganismV2.self,
schemaHistory: SchemaHistory(
XcodeDataModelSchema.from(
modelName: "MigrationDemo",
migrationChain: [
"MigrationDemo": "MigrationDemoV2",
"MigrationDemoV3": "MigrationDemoV2"
]
),
migrationChain: [
"MigrationDemo": "MigrationDemoV2",
"MigrationDemoV3": "MigrationDemoV2"
]
)
migrationChain: [
"MigrationDemo": "MigrationDemoV2",
"MigrationDemoV3": "MigrationDemoV2"
]
),
(
label: "Model V3",
version: "MigrationDemoV3",
entityType: OrganismV3.self,
schemaHistory: SchemaHistory(
XcodeDataModelSchema.from(
modelName: "MigrationDemo",
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
),
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
)
migrationChain: ["MigrationDemo", "MigrationDemoV2", "MigrationDemoV3"]
)
]
@@ -202,13 +113,13 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
return self._dataStack
}
private var _lastSelectedIndexPath: IndexPath?
private var lastSelectedIndexPath: IndexPath? {
private var _lastSelectedIndexPath: NSIndexPath?
private var lastSelectedIndexPath: NSIndexPath? {
return self._lastSelectedIndexPath
}
private func setSelectedIndexPath(_ indexPath: IndexPath, scrollToSelection: Bool) {
private func setSelectedIndexPath(indexPath: NSIndexPath, scrollToSelection: Bool) {
self._lastSelectedIndexPath = indexPath
self.updateDisplay(reloadData: false, scrollToSelection: scrollToSelection, animated: true)
@@ -221,7 +132,7 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
@IBOutlet private dynamic weak var progressView: UIProgressView?
@IBOutlet private dynamic weak var tableView: UITableView?
@IBAction private dynamic func segmentedControlValueChanged(_ sender: AnyObject?) {
@IBAction private dynamic func segmentedControlValueChanged(sender: AnyObject?) {
guard let index = self.segmentedControl?.selectedSegmentIndex else {
@@ -231,46 +142,23 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
self.selectModelVersion(self.models[index])
}
private func selectModelVersion(_ model: ModelMetadata) {
private func selectModelVersion(model: ModelMetadata) {
if self.dataStack?.modelVersion == model.schemaHistory.currentModelVersion {
if self.dataStack?.modelVersion == model.version {
return
}
self.set(dataStack: nil, model: nil, scrollToSelection: false) // explicitly trigger NSPersistentStore cleanup by deallocating the stack
self.setDataStack(nil, model: nil, scrollToSelection: false) // explicitly trigger NSPersistentStore cleanup by deallocating the stack
let dataStack = DataStack(schemaHistory: model.schemaHistory)
let dataStack = DataStack(
modelName: "MigrationDemo",
migrationChain: model.migrationChain
)
self.setEnabled(false)
let progress = dataStack.addStorage(
SQLiteStore(
fileName: "MigrationDemo.sqlite",
migrationMappingProviders: [
CustomSchemaMappingProvider(
from: "MigrationDemoV3",
to: "MigrationDemoV2",
entityMappings: [
.transformEntity(
sourceEntity: "Organism",
destinationEntity: "Organism",
transformer: { (source, createDestination) in
let destination = createDestination()
destination.enumerateAttributes { (attribute, sourceAttribute) in
if let sourceAttribute = sourceAttribute {
destination[attribute] = source[sourceAttribute]
}
}
destination["numberOfFlippers"] = source["numberOfLimbs"]
}
)
]
)
]
),
SQLiteStore(fileName: "MigrationDemo.sqlite"),
completion: { [weak self] (result) -> Void in
guard let `self` = self else {
@@ -278,17 +166,18 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
return
}
guard case .success = result else {
guard case .Success = result else {
self.setEnabled(true)
return
}
self.set(dataStack: dataStack, model: model, scrollToSelection: true)
self.setDataStack(dataStack, model: model, scrollToSelection: true)
let count = dataStack.queryValue(
From(model.entityType),
Select<Int>(.count(#keyPath(OrganismV1.dna))))!
Select<Int>(.Count("dna"))
)
if count > 0 {
self.setEnabled(true)
@@ -297,26 +186,25 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
for i: Int64 in 0 ..< 20 {
dataStack.perform(
asynchronous: { (transaction) in
dataStack.beginAsynchronous { (transaction) -> Void in
for j: Int64 in 0 ..< 500 {
for j: Int64 in 0 ..< 500 {
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
organism.dna = (i * 500) + j + 1
organism.mutate()
}
},
completion: { _ in }
)
let organism = transaction.create(Into(model.entityType)) as! OrganismProtocol
organism.dna = (i * 500) + j + 1
organism.mutate()
}
transaction.commit()
}
}
dataStack.perform(
asynchronous: { _ in },
completion: { [weak self] _ in
dataStack.beginAsynchronous { [weak self] (transaction) -> Void in
transaction.commit { _ in
self?.setEnabled(true)
}
)
}
}
}
)
@@ -330,45 +218,39 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
}
}
private func setEnabled(_ enabled: Bool) {
private func setEnabled(enabled: Bool) {
UIView.animate(
withDuration: 0.2,
UIView.animateWithDuration(
0.2,
delay: 0,
options: .beginFromCurrentState,
options: .BeginFromCurrentState,
animations: { () -> Void in
let navigationItem = self.navigationItem
navigationItem.leftBarButtonItem?.isEnabled = enabled
navigationItem.rightBarButtonItem?.isEnabled = enabled
navigationItem.leftBarButtonItem?.enabled = enabled
navigationItem.rightBarButtonItem?.enabled = enabled
navigationItem.hidesBackButton = !enabled
self.segmentedControl?.isEnabled = enabled
self.segmentedControl?.enabled = enabled
if let tableView = self.tableView {
tableView.alpha = enabled ? 1.0 : 0.5
tableView.isUserInteractionEnabled = enabled
tableView.userInteractionEnabled = enabled
}
},
completion: nil
)
}
private func set(dataStack: DataStack?, model: ModelMetadata?, scrollToSelection: Bool) {
private func setDataStack(dataStack: DataStack?, model: ModelMetadata?, scrollToSelection: Bool) {
if let dataStack = dataStack, let model = model {
self.segmentedControl?.selectedSegmentIndex = self.models
.index(
where: { (_, _, schemaHistory) -> Bool in
schemaHistory.currentModelVersion == model.schemaHistory.currentModelVersion
}
)!
self.segmentedControl?.selectedSegmentIndex = self.models.map { $0.version }.indexOf(model.version)!
self._dataStack = dataStack
let listMonitor = dataStack.monitorList(From(model.entityType), OrderBy(.descending("dna")))
let listMonitor = dataStack.monitorList(From(model.entityType), OrderBy(.Descending("dna")))
listMonitor.addObserver(self)
self._listMonitor = listMonitor
@@ -376,7 +258,7 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
if listMonitor.numberOfObjectsInSection(0) > 0 {
self.setSelectedIndexPath(IndexPath(row: 0, section: 0), scrollToSelection: true)
self.setSelectedIndexPath(NSIndexPath(forRow: 0, inSection: 0), scrollToSelection: true)
}
}
}
@@ -390,14 +272,14 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
self.updateDisplay(reloadData: true, scrollToSelection: scrollToSelection, animated: false)
}
private func reloadTableHeaderWithProgress(_ progress: Progress) {
private func reloadTableHeaderWithProgress(progress: NSProgress) {
self.progressView?.setProgress(Float(progress.fractionCompleted), animated: true)
self.titleLabel?.text = "Migrating: \(progress.localizedDescription ?? "")"
self.organismLabel?.text = "Progressive step \(progress.localizedAdditionalDescription ?? "")"
self.titleLabel?.text = "Migrating: \(progress.localizedDescription)"
self.organismLabel?.text = "Progressive step \(progress.localizedAdditionalDescription)"
}
private func updateDisplay(reloadData: Bool, scrollToSelection: Bool, animated: Bool) {
private func updateDisplay(reloadData reloadData: Bool, scrollToSelection: Bool, animated: Bool) {
var lines = [String]()
var organismType = ""
@@ -405,14 +287,14 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
for property in organism.entity.properties {
let value = organism.value(forKey: property.name) ?? NSNull()
let value: AnyObject = organism.valueForKey(property.name) ?? NSNull()
lines.append("\(property.name): \(value)")
}
organismType = organism.entity.managedObjectClassName
}
self.titleLabel?.text = organismType
self.organismLabel?.text = lines.joined(separator: "\n")
self.organismLabel?.text = lines.joinWithSeparator("\n")
self.progressView?.progress = 0
self.headerContainer?.setNeedsLayout()
@@ -429,13 +311,87 @@ class MigrationsDemoViewController: UIViewController, ListObserver, UITableViewD
tableView.layoutIfNeeded()
if let indexPath = self.lastSelectedIndexPath,
indexPath.row < tableView.numberOfRows(inSection: 0) {
if let indexPath = self.lastSelectedIndexPath where indexPath.row < tableView.numberOfRowsInSection(0) {
tableView.selectRow(at: indexPath,
tableView.selectRowAtIndexPath(indexPath,
animated: scrollToSelection && animated,
scrollPosition: scrollToSelection ? .middle : .none
scrollPosition: scrollToSelection ? .Middle : .None
)
}
}
}
// MARK: - MigrationsDemoViewController: ListObserver
extension MigrationsDemoViewController: ListObserver {
// MARK: ListObserver
func listMonitorWillChange(monitor: ListMonitor<NSManagedObject>) { }
func listMonitorDidChange(monitor: ListMonitor<NSManagedObject>) {
if self.lastSelectedIndexPath == nil && self.listMonitor?.numberOfObjectsInSection(0) > 0 {
self.tableView?.reloadData()
self.setSelectedIndexPath(NSIndexPath(forRow: 0, inSection: 0), scrollToSelection: false)
}
else {
self.updateDisplay(reloadData: true, scrollToSelection: true, animated: true)
}
}
}
// MARK: - MigrationsDemoViewController: UITableViewDataSource, UITableViewDelegate
extension MigrationsDemoViewController: UITableViewDataSource, UITableViewDelegate {
// MARK: UITableViewDataSource
@objc dynamic func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.listMonitor?.numberOfObjectsInSection(0) ?? 0
}
@objc dynamic func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCellWithIdentifier("OrganismTableViewCell", forIndexPath: indexPath) as! OrganismTableViewCell
let dna = (self.listMonitor?[indexPath] as? OrganismProtocol)?.dna.description ?? ""
cell.dnaLabel?.text = "DNA: \(dna)"
cell.mutateButtonHandler = { [weak self] _ -> Void in
guard let `self` = self,
let dataStack = self.dataStack,
let organism = self.listMonitor?[indexPath] else {
return
}
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
self.setEnabled(false)
dataStack.beginAsynchronous { [weak self] (transaction) -> Void in
let organism = transaction.edit(organism) as! OrganismProtocol
organism.mutate()
transaction.commit { _ -> Void in
self?.setEnabled(true)
}
}
}
return cell
}
// MARK: UITableViewDelegate
@objc dynamic func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
self.setSelectedIndexPath(indexPath, scrollToSelection: false)
}
}

View File

@@ -13,4 +13,4 @@ protocol OrganismProtocol: class {
var dna: Int64 { get set }
func mutate()
}
}

View File

@@ -15,7 +15,7 @@ class OrganismTableViewCell: UITableViewCell {
var mutateButtonHandler: (() -> Void)?
@IBAction dynamic func mutateButtonTouchUpInside(_ sender: UIButton?) {
@IBAction dynamic func mutateButtonTouchUpInside(sender: UIButton?) {
self.mutateButtonHandler?()
}

View File

@@ -10,20 +10,14 @@ import CoreData
class OrganismV2ToV3MigrationPolicy: NSEntityMigrationPolicy {
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
override func createDestinationInstancesForSourceInstance(sInstance: NSManagedObject, entityMapping mapping: NSEntityMapping, manager: NSMigrationManager) throws {
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
try super.createDestinationInstancesForSourceInstance(sInstance, entityMapping: mapping, manager: manager)
for dInstance in manager.destinationInstances(forEntityMappingName: mapping.name, sourceInstances: [sInstance]) {
for dInstance in manager.destinationInstancesForEntityMappingNamed(mapping.name, sourceInstances: [sInstance]) {
dInstance.setValue(
false,
forKey: #keyPath(OrganismV3.hasVertebrae)
)
dInstance.setValue(
sInstance.value(forKey: #keyPath(OrganismV2.numberOfFlippers)),
forKey: #keyPath(OrganismV3.numberOfLimbs)
)
dInstance.setValue(false, forKey: "hasVertebrae")
dInstance.setValue(sInstance.valueForKey("numberOfFlippers"), forKey: "numberOfLimbs")
}
}
}

View File

@@ -1,4 +1,4 @@
<?xml version="1.0"?>
<?xml version="1.0" standalone="no"?>
<!DOCTYPE database SYSTEM "file:///System/Library/DTDs/CoreData.dtd">
<database>
@@ -10,7 +10,7 @@
<plist version="1.0">
<dict>
<key>NSPersistenceFrameworkVersion</key>
<integer>754</integer>
<integer>526</integer>
<key>NSStoreModelVersionHashes</key>
<dict>
<key>XDDevAttributeMapping</key>

View File

@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="11198.3" systemVersion="15F34" minimumToolsVersion="Xcode 4.3" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14E46" minimumToolsVersion="Xcode 4.3">
<entity name="Organism" representedClassName="CoreStoreDemo.OrganismV1" syncable="YES">
<attribute name="dna" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
<attribute name="hasHead" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="hasTail" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="dna" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="hasHead" optional="YES" attributeType="Boolean" syncable="YES"/>
<attribute name="hasTail" optional="YES" attributeType="Boolean" syncable="YES"/>
</entity>
<elements>
<element name="Organism" positionX="-36" positionY="9" width="128" height="90"/>

View File

@@ -1,10 +1,10 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="11198.3" systemVersion="15F34" minimumToolsVersion="Xcode 4.3" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14E46" minimumToolsVersion="Xcode 4.3">
<entity name="Organism" representedClassName="CoreStoreDemo.OrganismV2" syncable="YES">
<attribute name="dna" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
<attribute name="hasHead" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="hasTail" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="numberOfFlippers" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" syncable="YES"/>
<attribute name="dna" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="hasHead" attributeType="Boolean" syncable="YES"/>
<attribute name="hasTail" attributeType="Boolean" syncable="YES"/>
<attribute name="numberOfFlippers" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
</entity>
<elements>
<element name="Organism" positionX="-36" positionY="9" width="128" height="105"/>

View File

@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="12141" systemVersion="16E195" minimumToolsVersion="Xcode 4.3" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="7701" systemVersion="14E46" minimumToolsVersion="Xcode 4.3">
<entity name="Organism" representedClassName="CoreStoreDemo.OrganismV3" syncable="YES">
<attribute name="dna" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
<attribute name="hasHead" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="hasTail" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="hasVertebrae" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="numberOfLimbs" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="NO" elementID="numberOfFlippers" syncable="YES"/>
<attribute name="dna" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="hasHead" attributeType="Boolean" syncable="YES"/>
<attribute name="hasTail" attributeType="Boolean" syncable="YES"/>
<attribute name="hasVertebrae" attributeType="Boolean" syncable="YES"/>
<attribute name="numberOfLimbs" attributeType="Integer 32" defaultValueString="0" syncable="YES"/>
</entity>
<elements>
<element name="Organism" positionX="-36" positionY="9" width="128" height="120"/>

View File

@@ -17,76 +17,77 @@ private struct Static {
static let facebookStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "StackSetupDemo")
let dataStack = DataStack(modelName: "StackSetupDemo")
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_FB_Male.sqlite",
configuration: maleConfiguration,
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_FB_Female.sqlite",
configuration: femaleConfiguration,
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
_ = try? dataStack.perform(
synchronous: { (transaction) in
transaction.deleteAll(From<UserAccount>())
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
account1.accountType = "Facebook"
account1.name = "John Smith HCD"
account1.friends = 42
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
account2.accountType = "Facebook"
account2.name = "Jane Doe HCD"
account2.friends = 314
}
)
dataStack.beginSynchronous { (transaction) -> Void in
transaction.deleteAll(From(UserAccount))
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
account1.accountType = "Facebook"
account1.name = "John Smith HCD"
account1.friends = 42
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
account2.accountType = "Facebook"
account2.name = "Jane Doe HCD"
account2.friends = 314
transaction.commitAndWait()
}
return dataStack
}()
static let twitterStack: DataStack = {
let dataStack = DataStack(xcodeModelName: "StackSetupDemo")
let dataStack = DataStack(modelName: "StackSetupDemo")
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_TW_Male.sqlite",
configuration: maleConfiguration,
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "AccountsDemo_TW_Female.sqlite",
configuration: femaleConfiguration,
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
_ = try? dataStack.perform(
synchronous: { (transaction) in
transaction.deleteAll(From<UserAccount>())
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
account1.accountType = "Twitter"
account1.name = "#johnsmith_hcd"
account1.friends = 7
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
account2.accountType = "Twitter"
account2.name = "#janedoe_hcd"
account2.friends = 100
}
)
dataStack.beginSynchronous { (transaction) -> Void in
transaction.deleteAll(From(UserAccount))
let account1 = transaction.create(Into<MaleAccount>(maleConfiguration))
account1.accountType = "Twitter"
account1.name = "#johnsmith_hcd"
account1.friends = 7
let account2 = transaction.create(Into<FemaleAccount>(femaleConfiguration))
account2.accountType = "Twitter"
account2.name = "#janedoe_hcd"
account2.friends = 100
transaction.commitAndWait()
}
return dataStack
}()
}
@@ -99,53 +100,53 @@ private struct Static {
class StackSetupDemoViewController: UITableViewController {
let accounts = [
Static.facebookStack.fetchAll(From(UserAccount.self)) ?? [],
Static.twitterStack.fetchAll(From(UserAccount.self)) ?? []
Static.facebookStack.fetchAll(From(UserAccount)) ?? [],
Static.twitterStack.fetchAll(From(UserAccount)) ?? []
]
// MARK: UIViewController
override func viewWillAppear(_ animated: Bool) {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
self.tableView.reloadData()
let indexPath = IndexPath(row: 0, section: 0)
self.tableView.selectRow(at: indexPath, animated: false, scrollPosition: .none)
self.updateDetails(account: self.accounts[indexPath.section][indexPath.row])
let indexPath = NSIndexPath(forRow: 0, inSection: 0)
self.tableView.selectRowAtIndexPath(indexPath, animated: false, scrollPosition: .None)
self.updateDetailsWithAccount(self.accounts[indexPath.section][indexPath.row])
}
override func viewDidAppear(_ animated: Bool) {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let alert = UIAlertController(
title: "Setup Demo",
message: "This demo shows how to initialize 2 DataStacks with 2 configurations each, for a total of 4 SQLite files, each with 1 instance of a \"UserAccount\" entity.",
preferredStyle: .alert
preferredStyle: .Alert
)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
// MARK: UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
override func numberOfSectionsInTableView(tableView: UITableView) -> Int {
return self.accounts.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
override func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.accounts[section].count
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
override func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "UITableViewCell")!
let cell = tableView.dequeueReusableCellWithIdentifier("UITableViewCell")!
let account = self.accounts[indexPath.section][indexPath.row]
cell.textLabel?.text = account.name
@@ -157,13 +158,13 @@ class StackSetupDemoViewController: UITableViewController {
// MARK: UITableViewDelegate
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
override func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
let account = self.accounts[indexPath.section][indexPath.row]
self.updateDetails(account: account)
self.updateDetailsWithAccount(account)
}
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
override func tableView(tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
switch section {
@@ -187,7 +188,7 @@ class StackSetupDemoViewController: UITableViewController {
@IBOutlet private dynamic weak var nameLabel: UILabel?
@IBOutlet private dynamic weak var friendsLabel: UILabel?
private func updateDetails(account: UserAccount) {
private func updateDetailsWithAccount(account: UserAccount) {
self.accountTypeLabel?.text = account.accountType
self.nameLabel?.text = account.name

View File

@@ -11,6 +11,7 @@ import CoreLocation
import MapKit
import AddressBookUI
import CoreStore
import GCDKit
private struct Static {
@@ -21,21 +22,21 @@ private struct Static {
SQLiteStore(
fileName: "PlaceDemo.sqlite",
configuration: "TransactionsDemo",
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
var place = CoreStore.fetchOne(From<Place>())
var place = CoreStore.fetchOne(From(Place))
if place == nil {
_ = try? CoreStore.perform(
synchronous: { (transaction) in
let place = transaction.create(Into<Place>())
place.setInitialValues()
}
)
place = CoreStore.fetchOne(From<Place>())
CoreStore.beginSynchronous { (transaction) -> Void in
let place = transaction.create(Into(Place))
place.setInitialValues()
transaction.commitAndWait()
}
place = CoreStore.fetchOne(From(Place))
}
return CoreStore.monitorObject(place!)
@@ -70,33 +71,33 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
Static.placeController.addObserver(self)
self.navigationItem.rightBarButtonItem = UIBarButtonItem(
barButtonSystemItem: .refresh,
barButtonSystemItem: .Refresh,
target: self,
action: #selector(self.refreshButtonTapped(_:))
)
}
override func viewDidAppear(_ animated: Bool) {
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
let alert = UIAlertController(
title: "Transactions Demo",
message: "This demo shows how to use the 3 types of transactions to save updates: synchronous, asynchronous, and unsafe.\n\nTap and hold on the map to change the pin location.",
preferredStyle: .alert
preferredStyle: .Alert
)
alert.addAction(UIAlertAction(title: "OK", style: .cancel, handler: nil))
self.present(alert, animated: true, completion: nil)
alert.addAction(UIAlertAction(title: "OK", style: .Cancel, handler: nil))
self.presentViewController(alert, animated: true, completion: nil)
}
override func viewWillAppear(_ animated: Bool) {
override func viewWillAppear(animated: Bool) {
super.viewWillAppear(animated)
if let mapView = self.mapView, let place = Static.placeController.object {
mapView.addAnnotation(place)
mapView.setCenter(place.coordinate, animated: false)
mapView.setCenterCoordinate(place.coordinate, animated: false)
mapView.selectAnnotation(place, animated: false)
}
}
@@ -104,14 +105,14 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
// MARK: MKMapViewDelegate
func mapView(_ mapView: MKMapView, viewFor annotation: MKAnnotation) -> MKAnnotationView? {
func mapView(mapView: MKMapView, viewForAnnotation annotation: MKAnnotation) -> MKAnnotationView? {
let identifier = "MKAnnotationView"
var annotationView: MKPinAnnotationView! = mapView.dequeueReusableAnnotationView(withIdentifier: identifier) as? MKPinAnnotationView
var annotationView: MKPinAnnotationView! = mapView.dequeueReusableAnnotationViewWithIdentifier(identifier) as? MKPinAnnotationView
if annotationView == nil {
annotationView = MKPinAnnotationView(annotation: annotation, reuseIdentifier: identifier)
annotationView.isEnabled = true
annotationView.enabled = true
annotationView.canShowCallout = true
annotationView.animatesDrop = true
}
@@ -126,28 +127,28 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
// MARK: ObjectObserver
func objectMonitor(_ monitor: ObjectMonitor<Place>, willUpdateObject object: Place) {
func objectMonitor(monitor: ObjectMonitor<Place>, willUpdateObject object: Place) {
// none
}
func objectMonitor(_ monitor: ObjectMonitor<Place>, didUpdateObject object: Place, changedPersistentKeys: Set<KeyPath>) {
func objectMonitor(monitor: ObjectMonitor<Place>, didUpdateObject object: Place, changedPersistentKeys: Set<KeyPath>) {
if let mapView = self.mapView {
mapView.removeAnnotations(mapView.annotations)
mapView.removeAnnotations(mapView.annotations ?? [])
mapView.addAnnotation(object)
mapView.setCenter(object.coordinate, animated: true)
mapView.setCenterCoordinate(object.coordinate, animated: true)
mapView.selectAnnotation(object, animated: true)
if changedPersistentKeys.contains(#keyPath(Place.latitude)) || changedPersistentKeys.contains(#keyPath(Place.longitude)) {
if changedPersistentKeys.contains("latitude") || changedPersistentKeys.contains("longitude") {
self.geocode(place: object)
self.geocodePlace(object)
}
}
}
func objectMonitor(_ monitor: ObjectMonitor<Place>, didDeleteObject object: Place) {
func objectMonitor(monitor: ObjectMonitor<Place>, didDeleteObject object: Place) {
// none
}
@@ -159,39 +160,34 @@ class TransactionsDemoViewController: UIViewController, MKMapViewDelegate, Objec
@IBOutlet weak var mapView: MKMapView?
@IBAction dynamic func longPressGestureRecognized(_ sender: AnyObject?) {
@IBAction dynamic func longPressGestureRecognized(sender: AnyObject?) {
if let mapView = self.mapView,
let gesture = sender as? UILongPressGestureRecognizer,
gesture.state == .began {
if let mapView = self.mapView, let gesture = sender as? UILongPressGestureRecognizer where gesture.state == .Began {
let coordinate = mapView.convert(
gesture.location(in: mapView),
toCoordinateFrom: mapView
)
CoreStore.perform(
asynchronous: { (transaction) in
let place = transaction.edit(Static.placeController.object)
place?.coordinate = coordinate
},
completion: { _ in }
let coordinate = mapView.convertPoint(
gesture.locationInView(mapView),
toCoordinateFromView: mapView
)
CoreStore.beginAsynchronous { (transaction) -> Void in
let place = transaction.edit(Static.placeController.object)
place?.coordinate = coordinate
transaction.commit { (_) -> Void in }
}
}
}
@IBAction dynamic func refreshButtonTapped(_ sender: AnyObject?) {
@IBAction dynamic func refreshButtonTapped(sender: AnyObject?) {
_ = try? CoreStore.perform(
synchronous: { (transaction) in
let place = transaction.edit(Static.placeController.object)
place?.setInitialValues()
}
)
CoreStore.beginSynchronous { (transaction) -> Void in
let place = transaction.edit(Static.placeController.object)
place?.setInitialValues()
transaction.commitAndWait()
}
}
func geocode(place: Place) {
func geocodePlace(place: Place) {
let transaction = CoreStore.beginUnsafe()

View File

@@ -36,12 +36,11 @@ class BaseTestCase: XCTestCase {
// MARK: Internal
@nonobjc
@discardableResult
func prepareStack<T>(configurations: [ModelConfiguration] = [nil], _ closure: (_ dataStack: DataStack) -> T) -> T {
func prepareStack<T>(configurations configurations: [String?] = [nil], @noescape _ closure: (dataStack: DataStack) -> T) -> T {
let stack = DataStack(
xcodeModelName: "Model",
bundle: Bundle(for: type(of: self))
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType)
)
do {
@@ -50,10 +49,10 @@ class BaseTestCase: XCTestCase {
try stack.addStorageAndWait(
SQLiteStore(
fileURL: SQLiteStore.defaultRootDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathComponent("\(type(of: self))_\(($0 ?? "-null-")).sqlite"),
.URLByAppendingPathComponent(NSUUID().UUIDString)
.URLByAppendingPathComponent("\(self.dynamicType)_\(($0 ?? "-null-")).sqlite"),
configuration: $0,
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
)
}
@@ -62,11 +61,11 @@ class BaseTestCase: XCTestCase {
XCTFail(error.coreStoreDumpString)
}
return closure(stack)
return closure(dataStack: stack)
}
@nonobjc
func expectLogger<T>(_ expectations: [TestLogger.Expectation], closure: () -> T) -> T {
func expectLogger<T>(expectations: [TestLogger.Expectation], @noescape closure: () -> T) -> T {
CoreStore.logger = TestLogger(self.prepareLoggerExpectations(expectations))
defer {
@@ -78,18 +77,18 @@ class BaseTestCase: XCTestCase {
}
@nonobjc
func expectLogger(_ expectations: [TestLogger.Expectation: XCTestExpectation]) {
func expectLogger(expectations: [TestLogger.Expectation: XCTestExpectation]) {
CoreStore.logger = TestLogger(expectations)
}
@nonobjc
func prepareLoggerExpectations(_ expectations: [TestLogger.Expectation]) -> [TestLogger.Expectation: XCTestExpectation] {
func prepareLoggerExpectations(expectations: [TestLogger.Expectation]) -> [TestLogger.Expectation: XCTestExpectation] {
var testExpectations: [TestLogger.Expectation: XCTestExpectation] = [:]
for expectation in expectations {
testExpectations[expectation] = self.expectation(description: "Logger Expectation: \(expectation)")
testExpectations[expectation] = self.expectationWithDescription("Logger Expectation: \(expectation)")
}
return testExpectations
}
@@ -97,13 +96,13 @@ class BaseTestCase: XCTestCase {
@nonobjc
func checkExpectationsImmediately() {
self.waitForExpectations(timeout: 0, handler: { _ in })
self.waitForExpectationsWithTimeout(0, handler: nil)
}
@nonobjc
func waitAndCheckExpectations() {
self.waitForExpectations(timeout: 10, handler: {_ in })
self.waitForExpectationsWithTimeout(10, handler: nil)
}
// MARK: XCTestCase
@@ -127,7 +126,7 @@ class BaseTestCase: XCTestCase {
private func deleteStores() {
_ = try? FileManager.default.removeItem(at: SQLiteStore.defaultRootDirectory)
_ = try? NSFileManager.defaultManager().removeItemAtURL(SQLiteStore.defaultRootDirectory)
}
}
@@ -138,11 +137,11 @@ class TestLogger: CoreStoreLogger {
enum Expectation {
case logWarning
case logFatal
case logError
case assertionFailure
case fatalError
case LogWarning
case LogFatal
case LogError
case AssertionFailure
case FatalError
}
init(_ expectations: [Expectation: XCTestExpectation]) {
@@ -153,35 +152,33 @@ class TestLogger: CoreStoreLogger {
// MARK: CoreStoreLogger
var enableObjectConcurrencyDebugging: Bool = true
func log(level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
switch level {
case .warning: self.fulfill(.logWarning)
case .fatal: self.fulfill(.logFatal)
case .Warning: self.fulfill(.LogWarning)
case .Fatal: self.fulfill(.LogFatal)
default: break
}
}
func log(error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func log(error error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
self.fulfill(.logError)
self.fulfill(.LogError)
}
func assert(_ condition: @autoclosure () -> Bool, message: @autoclosure () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
if condition() {
return
}
self.fulfill(.assertionFailure)
self.fulfill(.AssertionFailure)
}
func abort(_ message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func abort(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
self.fulfill(.fatalError)
self.fulfill(.FatalError)
}
@@ -189,7 +186,7 @@ class TestLogger: CoreStoreLogger {
private var expectations: [Expectation: XCTestExpectation]
private func fulfill(_ expectation: Expectation) {
private func fulfill(expectation: Expectation) {
if let instance = self.expectations[expectation] {

View File

@@ -17,61 +17,60 @@ import CoreStore
class BaseTestDataTestCase: BaseTestCase {
@nonobjc
let dateFormatter: DateFormatter = cs_lazy {
let dateFormatter: NSDateFormatter = {
let formatter = DateFormatter()
formatter.locale = Locale(identifier: "en_US_POSIX")
formatter.timeZone = TimeZone(identifier: "UTC")
formatter.calendar = Calendar(identifier: Calendar.Identifier.gregorian)
let formatter = NSDateFormatter()
formatter.locale = NSLocale(localeIdentifier: "en_US_POSIX")
formatter.timeZone = NSTimeZone(name: "UTC")
formatter.calendar = NSCalendar(identifier: NSCalendarIdentifierGregorian)
formatter.dateFormat = "yyyy'-'MM'-'dd'T'HH':'mm':'ssZ"
return formatter
}
}()
@nonobjc
func prepareTestDataForStack(_ stack: DataStack, configurations: [ModelConfiguration] = [nil]) {
func prepareTestDataForStack(stack: DataStack, configurations: [String?] = [nil]) {
try! stack.perform(
synchronous: { (transaction) in
stack.beginSynchronous { (transaction) in
for (configurationIndex, configuration) in configurations.enumerate() {
for (configurationIndex, configuration) in configurations.enumerated() {
let configurationOrdinal = configurationIndex + 1
if configuration == nil || configuration == "Config1" {
let configurationOrdinal = configurationIndex + 1
if configuration == nil || configuration == "Config1" {
for idIndex in 1 ... 5 {
for idIndex in 1 ... 5 {
let object = transaction.create(Into<TestEntity1>(configuration))
object.testEntityID = NSNumber(value: (configurationOrdinal * 100) + idIndex)
object.testNumber = NSNumber(value: idIndex)
object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = NSNumber(value: (idIndex % 2) == 1)
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity1:\(idIndex)"
object.testString = string
object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue)
}
let object = transaction.create(Into<TestEntity1>(configuration))
object.testEntityID = NSNumber(integer: (configurationOrdinal * 100) + idIndex)
object.testNumber = idIndex
object.testDate = self.dateFormatter.dateFromString("2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = (idIndex % 2) == 1
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity1:\(idIndex)"
object.testString = string
object.testData = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
}
if configuration == nil || configuration == "Config2" {
}
if configuration == nil || configuration == "Config2" {
for idIndex in 1 ... 5 {
for idIndex in 1 ... 5 {
let object = transaction.create(Into<TestEntity2>(configuration))
object.testEntityID = NSNumber(value: (configurationOrdinal * 200) + idIndex)
object.testNumber = NSNumber(value: idIndex)
object.testDate = self.dateFormatter.date(from: "2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = NSNumber(value: (idIndex % 2) == 1)
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity2:\(idIndex)"
object.testString = string
object.testData = (string as NSString).data(using: String.Encoding.utf8.rawValue)
}
let object = transaction.create(Into<TestEntity2>(configuration))
object.testEntityID = NSNumber(integer: (configurationOrdinal * 200) + idIndex)
object.testNumber = idIndex
object.testDate = self.dateFormatter.dateFromString("2000-\(configurationOrdinal)-\(idIndex)T00:00:00Z")
object.testBoolean = (idIndex % 2) == 1
object.testDecimal = NSDecimalNumber(string: "\(idIndex)")
let string = "\(configuration ?? "nil"):TestEntity2:\(idIndex)"
object.testString = string
object.testData = (string as NSString).dataUsingEncoding(NSUTF8StringEncoding)
}
}
}
)
transaction.commitAndWait()
}
}
}

View File

@@ -163,7 +163,7 @@
- (void)test_ThatDataStacks_BridgeCorrectly {
CSDataStack *dataStack = [[CSDataStack alloc]
initWithXcodeModelName:@"Model"
initWithModelName:@"Model"
bundle:[NSBundle bundleForClass:[self class]]
versionChain:nil];
XCTAssertNotNil(dataStack);
@@ -201,7 +201,7 @@
[CSCoreStore
setDefaultStack:[[CSDataStack alloc]
initWithXcodeModelName:@"Model"
initWithModelName:@"Model"
bundle:[NSBundle bundleForClass:[self class]]
versionChain:nil]];
[CSCoreStore
@@ -212,27 +212,15 @@
CSUnsafeDataTransaction *transaction = [CSCoreStore beginUnsafe];
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSUnsafeDataTransaction class]]);
NSError *error;
BOOL result = [transaction commitAndWaitWithError:&error];
XCTAssertTrue(result);
XCTAssertNil(error);
}
{
XCTestExpectation *expectation = [self expectationWithDescription:@"sync"];
NSError *error;
BOOL result = [CSCoreStore
beginSynchronous:^(CSSynchronousDataTransaction * _Nonnull transaction) {
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSSynchronousDataTransaction class]]);
NSError *error;
XCTAssertTrue([transaction commitAndWaitWithError:&error]);
XCTAssertNil(error);
[expectation fulfill];
}
error:&error];
XCTAssertTrue(result);
XCTAssertNil(error);
[CSCoreStore beginSynchronous:^(CSSynchronousDataTransaction * _Nonnull transaction) {
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSSynchronousDataTransaction class]]);
[expectation fulfill];
}];
}
{
XCTestExpectation *expectation = [self expectationWithDescription:@"async"];
@@ -240,15 +228,7 @@
XCTAssertNotNil(transaction);
XCTAssert([transaction isKindOfClass:[CSAsynchronousDataTransaction class]]);
[transaction
commitWithSuccess:^{
[expectation fulfill];
}
failure:^(CSError *error){
XCTFail();
}];
[expectation fulfill];
}];
}
[self waitForExpectationsWithTimeout:10 handler:nil];

View File

@@ -1,91 +0,0 @@
//
// ConvenienceTests.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
@testable
import CoreStore
// MARK: - ConvenienceTests
@available(OSX 10.12, *)
class ConvenienceTests: BaseTestCase {
@objc
dynamic func test_ThatDataStacks_CanCreateFetchedResultsControllers() {
self.prepareStack { (stack) in
let controller = stack.createFetchedResultsController(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testString)),
Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100),
OrderBy(.ascending(#keyPath(TestEntity1.testString))),
Tweak { $0.fetchLimit = 10 }
)
XCTAssertEqual(controller.managedObjectContext, stack.mainContext)
XCTAssertEqual(controller.fetchRequest.entity?.managedObjectClassName, NSStringFromClass(TestEntity1.self))
XCTAssertEqual(controller.sectionNameKeyPath, #keyPath(TestEntity1.testString))
XCTAssertEqual(
controller.fetchRequest.sortDescriptors!,
OrderBy(.ascending(#keyPath(TestEntity1.testString))).sortDescriptors
)
XCTAssertEqual(
controller.fetchRequest.predicate,
Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100).predicate
)
XCTAssertEqual(controller.fetchRequest.fetchLimit, 10)
}
}
@objc
dynamic func test_ThatUnsafeDataTransactions_CanCreateFetchedResultsControllers() {
self.prepareStack { (stack) in
_ = withExtendedLifetime(stack.beginUnsafe()) { (transaction: UnsafeDataTransaction) in
let controller = transaction.createFetchedResultsController(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testString)),
Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100),
OrderBy(.ascending(#keyPath(TestEntity1.testString))),
Tweak { $0.fetchLimit = 10 }
)
XCTAssertEqual(controller.managedObjectContext, transaction.context)
XCTAssertEqual(controller.fetchRequest.entity?.managedObjectClassName, NSStringFromClass(TestEntity1.self))
XCTAssertEqual(controller.sectionNameKeyPath, #keyPath(TestEntity1.testString))
XCTAssertEqual(
controller.fetchRequest.sortDescriptors!,
OrderBy(.ascending(#keyPath(TestEntity1.testString))).sortDescriptors
)
XCTAssertEqual(
controller.fetchRequest.predicate,
Where("%@ > %d", #keyPath(TestEntity1.testEntityID), 100).predicate
)
XCTAssertEqual(controller.fetchRequest.fetchLimit, 10)
}
}
}
}

View File

@@ -1,259 +0,0 @@
//
// DynamicModelTests.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import XCTest
@testable
import CoreStore
#if os(OSX)
typealias Color = NSColor
#else
typealias Color = UIColor
#endif
class Animal: CoreStoreObject {
let species = Value.Required<String>("species", default: "Swift")
let master = Relationship.ToOne<Person>("master")
let color = Transformable.Optional<Color>("color")
}
class Dog: Animal {
let nickname = Value.Optional<String>("nickname")
let age = Value.Required<Int>("age", default: 1)
let friends = Relationship.ToManyOrdered<Dog>("friends")
let friendedBy = Relationship.ToManyUnordered<Dog>("friendedBy", inverse: { $0.friends })
}
class Person: CoreStoreObject {
let title = Value.Required<String>(
"title",
default: "Mr.",
customSetter: { (`self`, setValue, originalNewValue) in
setValue(originalNewValue)
self.displayName .= nil
}
)
let name = Value.Required<String>(
"name",
customSetter: { (`self`, setValue, originalNewValue) in
setValue(originalNewValue)
self.displayName .= nil
}
)
let displayName = Value.Optional<String>(
"displayName",
isTransient: true,
customGetter: Person.cachedDisplayName(_:_:),
affectedByKeyPaths: Person.keyPathsAffectingDisplayName()
)
let pets = Relationship.ToManyUnordered<Animal>("pets", inverse: { $0.master })
static func cachedDisplayName(_ instance: Person, _ getValue: () -> String?) -> String? {
if let cached = getValue() {
return cached
}
let primitiveValue = "\(instance.title.value) \(instance.name.value)"
instance.displayName .= primitiveValue
return primitiveValue
}
static func keyPathsAffectingDisplayName() -> Set<String> {
return [
self.keyPath({ $0.title }),
self.keyPath({ $0.name })
]
}
}
// MARK: - DynamicModelTests
class DynamicModelTests: BaseTestDataTestCase {
func testDynamicModels_CanBeDeclaredCorrectly() {
let dataStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<Animal>("Animal"),
Entity<Dog>("Dog"),
Entity<Person>("Person")
],
versionLock: [
"Animal": [0x1b59d511019695cf, 0xdeb97e86c5eff179, 0x1cfd80745646cb3, 0x4ff99416175b5b9a],
"Dog": [0xe3f0afeb109b283a, 0x29998d292938eb61, 0x6aab788333cfc2a3, 0x492ff1d295910ea7],
"Person": [0x66d8bbfd8b21561f, 0xcecec69ecae3570f, 0xc4b73d71256214ef, 0x89b99bfe3e013e8b]
]
)
)
self.prepareStack(dataStack, configurations: [nil]) { (stack) in
let k1 = Animal.keyPath({ $0.species })
XCTAssertEqual(k1, "species")
let k2 = Dog.keyPath({ $0.species })
XCTAssertEqual(k2, "species")
let k3 = Dog.keyPath({ $0.nickname })
XCTAssertEqual(k3, "nickname")
let updateDone = self.expectation(description: "update-done")
let fetchDone = self.expectation(description: "fetch-done")
stack.perform(
asynchronous: { (transaction) in
let animal = transaction.create(Into<Animal>())
XCTAssertEqual(animal.species.value, "Swift")
XCTAssertTrue(type(of: animal.species.value) == String.self)
animal.species .= "Sparrow"
XCTAssertEqual(animal.species.value, "Sparrow")
animal.color .= .yellow
XCTAssertEqual(animal.color.value, Color.yellow)
let dog = transaction.create(Into<Dog>())
XCTAssertEqual(dog.species.value, "Swift")
XCTAssertEqual(dog.nickname.value, nil)
XCTAssertEqual(dog.age.value, 1)
dog.species .= "Dog"
XCTAssertEqual(dog.species.value, "Dog")
dog.nickname .= "Spot"
XCTAssertEqual(dog.nickname.value, "Spot")
let person = transaction.create(Into<Person>())
XCTAssertTrue(person.pets.value.isEmpty)
XCTAssertEqual(
object_getClass(person.rawObject!).keyPathsForValuesAffectingValue(forKey: "displayName"),
["title", "name"]
)
person.name .= "Joe"
XCTAssertEqual(person.rawObject!.value(forKey: "name") as! String?, "Joe")
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "Mr. Joe")
person.rawObject!.setValue("AAAA", forKey: "displayName")
XCTAssertEqual(person.rawObject!.value(forKey: "displayName") as! String?, "AAAA")
person.name .= "John"
XCTAssertEqual(person.name.value, "John")
XCTAssertEqual(person.displayName.value, "Mr. John") // Custom getter
person.title .= "Sir"
XCTAssertEqual(person.displayName.value, "Sir John")
person.pets.value.insert(dog)
XCTAssertEqual(person.pets.count, 1)
XCTAssertEqual(person.pets.value.first, dog)
XCTAssertEqual(person.pets.value.first?.master.value, person)
XCTAssertEqual(dog.master.value, person)
XCTAssertEqual(dog.master.value?.pets.value.first, dog)
},
success: {
updateDone.fulfill()
},
failure: { _ in
XCTFail()
}
)
stack.perform(
asynchronous: { (transaction) in
let p1 = Animal.where({ $0.species == "Sparrow" })
XCTAssertEqual(p1.predicate, NSPredicate(format: "%K == %@", "species", "Sparrow"))
let bird = transaction.fetchOne(From<Animal>(), p1)
XCTAssertNotNil(bird)
XCTAssertEqual(bird!.species.value, "Sparrow")
let p2 = Dog.where({ $0.nickname == "Spot" })
XCTAssertEqual(p2.predicate, NSPredicate(format: "%K == %@", "nickname", "Spot"))
let dog = transaction.fetchOne(From<Dog>(), p2)
XCTAssertNotNil(dog)
XCTAssertEqual(dog!.nickname.value, "Spot")
XCTAssertEqual(dog!.species.value, "Dog")
let person = transaction.fetchOne(From<Person>())
XCTAssertNotNil(person)
XCTAssertEqual(person!.pets.value.first, dog)
let p3 = Dog.where({ $0.age == 10 })
XCTAssertEqual(p3.predicate, NSPredicate(format: "%K == %d", "age", 10))
},
success: {
fetchDone.fulfill()
},
failure: { _ in
XCTFail()
}
)
self.waitAndCheckExpectations()
}
}
@nonobjc
func prepareStack(_ dataStack: DataStack, configurations: [ModelConfiguration] = [nil], _ closure: (_ dataStack: DataStack) -> Void) {
do {
try configurations.forEach { (configuration) in
try dataStack.addStorageAndWait(
SQLiteStore(
fileURL: SQLiteStore.defaultRootDirectory
.appendingPathComponent(UUID().uuidString)
.appendingPathComponent("\(type(of: self))_\((configuration ?? "-null-")).sqlite"),
configuration: configuration,
localStorageOptions: .recreateStoreOnModelMismatch
)
)
}
}
catch let error as NSError {
XCTFail(error.coreStoreDumpString)
}
closure(dataStack)
}
}

View File

@@ -36,33 +36,33 @@ final class ErrorTests: XCTestCase {
@objc
dynamic func test_ThatUnknownErrors_BridgeCorrectly() {
let error = CoreStoreError.unknown
let error = CoreStoreError.Unknown
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.unknownError.rawValue)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.UnknownError.rawValue)
let userInfo: NSDictionary = [:]
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.unknownError.rawValue)
XCTAssertEqual(objcError.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.UnknownError.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.unknownError.rawValue)
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.UnknownError.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
@objc
dynamic func test_ThatDifferentStorageExistsAtURLErrors_BridgeCorrectly() {
let dummyURL = URL(string: "file:///test1/test2.sqlite")!
let dummyURL = NSURL(string: "file:///test1/test2.sqlite")!
let error = CoreStoreError.differentStorageExistsAtURL(existingPersistentStoreURL: dummyURL)
let error = CoreStoreError.DifferentStorageExistsAtURL(existingPersistentStoreURL: dummyURL)
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.differentStorageExistsAtURL.rawValue)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue)
let userInfo: NSDictionary = [
"existingPersistentStoreURL": dummyURL
@@ -70,59 +70,54 @@ final class ErrorTests: XCTestCase {
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.differentStorageExistsAtURL.rawValue)
XCTAssertEqual(objcError.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.differentStorageExistsAtURL.rawValue)
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
@objc
dynamic func test_ThatMappingModelNotFoundErrors_BridgeCorrectly() {
let dummyURL = URL(string: "file:///test1/test2.sqlite")!
let dummyURL = NSURL(string: "file:///test1/test2.sqlite")!
let schemaHistory = SchemaHistory(
XcodeDataModelSchema.from(
modelName: "Model",
bundle: Bundle(for: type(of: self))
)
)
let model = NSManagedObjectModel.fromBundle(NSBundle(forClass: self.dynamicType), modelName: "Model")
let version = "1.0.0"
let error = CoreStoreError.mappingModelNotFound(localStoreURL: dummyURL, targetModel: schemaHistory.rawModel, targetModelVersion: version)
let error = CoreStoreError.MappingModelNotFound(localStoreURL: dummyURL, targetModel: model, targetModelVersion: version)
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.mappingModelNotFound.rawValue)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.MappingModelNotFound.rawValue)
let userInfo: NSDictionary = [
"localStoreURL": dummyURL,
"targetModel": schemaHistory.rawModel,
"targetModel": model,
"targetModelVersion": version
]
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.mappingModelNotFound.rawValue)
XCTAssertEqual(objcError.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.MappingModelNotFound.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.mappingModelNotFound.rawValue)
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.MappingModelNotFound.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
@objc
dynamic func test_ThatProgressiveMigrationRequiredErrors_BridgeCorrectly() {
let dummyURL = URL(string: "file:///test1/test2.sqlite")!
let dummyURL = NSURL(string: "file:///test1/test2.sqlite")!
let error = CoreStoreError.progressiveMigrationRequired(localStoreURL: dummyURL)
let error = CoreStoreError.ProgressiveMigrationRequired(localStoreURL: dummyURL)
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.progressiveMigrationRequired.rawValue)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue)
let userInfo: NSDictionary = [
"localStoreURL": dummyURL
@@ -130,14 +125,14 @@ final class ErrorTests: XCTestCase {
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.progressiveMigrationRequired.rawValue)
XCTAssertEqual(objcError.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.progressiveMigrationRequired.rawValue)
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
@objc
@@ -149,12 +144,12 @@ final class ErrorTests: XCTestCase {
userInfo: [
"key1": "value1",
"key2": 2,
"key3": Date()
"key3": NSDate()
]
)
let error = CoreStoreError(internalError)
XCTAssertEqual((error as NSError).domain, CoreStoreErrorDomain)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.internalError.rawValue)
XCTAssertEqual((error as NSError).code, CoreStoreErrorCode.InternalError.rawValue)
let userInfo: NSDictionary = [
"NSError": internalError
@@ -162,13 +157,13 @@ final class ErrorTests: XCTestCase {
let objcError = error.bridgeToObjectiveC
XCTAssertEqual(error, objcError.bridgeToSwift)
XCTAssertEqual(objcError.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.internalError.rawValue)
XCTAssertEqual(objcError.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError.code, CoreStoreErrorCode.InternalError.rawValue)
XCTAssertEqual(objcError.userInfo, userInfo)
let objcError2 = objcError.bridgeToSwift.bridgeToObjectiveC
XCTAssertEqual(error, objcError2.bridgeToSwift)
XCTAssertEqual(objcError2.domain, CoreStoreErrorDomain)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.internalError.rawValue)
XCTAssertEqual(objcError2.userInfo as NSDictionary, userInfo)
XCTAssertEqual(objcError2.code, CoreStoreErrorCode.InternalError.rawValue)
XCTAssertEqual(objcError2.userInfo, userInfo)
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -38,7 +38,7 @@ final class FromTests: BaseTestCase {
do {
let from = From<NSManagedObject>()
let from = From()
XCTAssert(from.entityClass === NSManagedObject.self)
XCTAssertNil(from.configurations)
}
@@ -74,33 +74,33 @@ final class FromTests: BaseTestCase {
let from = From<TestEntity1>()
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
}
do {
let from = From<TestEntity1>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
}
@@ -115,102 +115,102 @@ final class FromTests: BaseTestCase {
let from = From<TestEntity1>()
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config1")
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>()
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
}
@@ -225,99 +225,99 @@ final class FromTests: BaseTestCase {
let from = From<TestEntity1>()
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(Set(affectedConfigurations), ["PF_DEFAULT_CONFIGURATION_NAME", "Config1"] as Set)
}
do {
let from = From<TestEntity1>("Config1")
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>()
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["PF_DEFAULT_CONFIGURATION_NAME"])
}
do {
let from = From<TestEntity2>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
}
@@ -332,96 +332,96 @@ final class FromTests: BaseTestCase {
let from = From<TestEntity1>()
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config1")
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config1"])
}
do {
let from = From<TestEntity1>("Config2")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>()
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config2"])
}
do {
let from = From<TestEntity2>("Config1")
let request = CoreStoreFetchRequest()
let storesFound = self.expectLogger([.logWarning]) {
let request = NSFetchRequest()
let storesFound = self.expectLogger([.LogWarning]) {
from.applyToFetchRequest(request, context: dataStack.mainContext)
}
XCTAssertFalse(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertTrue(affectedConfigurations.isEmpty)
}
do {
let from = From<TestEntity2>("Config2")
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
let storesFound = from.applyToFetchRequest(request, context: dataStack.mainContext)
XCTAssertTrue(storesFound)
XCTAssertNotNil(request.entity)
XCTAssertNotNil(request.safeAffectedStores)
XCTAssertNotNil(request.affectedStores)
XCTAssert(from.entityClass == NSClassFromString(request.entity!.managedObjectClassName))
let affectedConfigurations = request.safeAffectedStores!.map { $0.configurationName }
let affectedConfigurations = request.affectedStores!.map { $0.configurationName }
XCTAssertEqual(affectedConfigurations, ["Config2"])
}
}

View File

@@ -66,10 +66,10 @@ final class GroupByTests: BaseTestCase {
self.prepareStack { (dataStack) in
let groupBy = GroupBy(#keyPath(TestEntity1.testString))
let groupBy = GroupBy("testString")
let request = CoreStoreFetchRequest()
_ = From<TestEntity1>().applyToFetchRequest(request, context: dataStack.mainContext)
let request = NSFetchRequest()
_ = From(TestEntity1).applyToFetchRequest(request, context: dataStack.mainContext)
groupBy.applyToFetchRequest(request)
XCTAssertNotNil(request.propertiesToGroupBy)

File diff suppressed because it is too large Load Diff

View File

@@ -36,7 +36,7 @@ final class IntoTests: XCTestCase {
@objc
dynamic func test_ThatIntoClauseConstants_AreCorrect() {
XCTAssertEqual(DataStack.defaultConfigurationName, "PF_DEFAULT_CONFIGURATION_NAME")
XCTAssertEqual(Into<NSManagedObject>.defaultConfigurationName, "PF_DEFAULT_CONFIGURATION_NAME")
}
@objc
@@ -44,7 +44,7 @@ final class IntoTests: XCTestCase {
do {
let into = Into<NSManagedObject>()
let into = Into()
XCTAssert(into.entityClass === NSManagedObject.self)
XCTAssertNil(into.configuration)
XCTAssertTrue(into.inferStoreIfPossible)
@@ -58,7 +58,14 @@ final class IntoTests: XCTestCase {
}
do {
let into = Into(TestEntity1.self)
let into = Into(TestEntity1)
XCTAssert(into.entityClass === TestEntity1.self)
XCTAssertNil(into.configuration)
XCTAssertTrue(into.inferStoreIfPossible)
}
do {
let into = Into(TestEntity1.self as AnyClass)
XCTAssert(into.entityClass === TestEntity1.self)
XCTAssertNil(into.configuration)
XCTAssertTrue(into.inferStoreIfPossible)
@@ -77,6 +84,13 @@ final class IntoTests: XCTestCase {
XCTAssertEqual(into.configuration, "Config1")
XCTAssertFalse(into.inferStoreIfPossible)
}
do {
let into = Into(TestEntity1.self as AnyClass, "Config1")
XCTAssert(into.entityClass === TestEntity1.self)
XCTAssertEqual(into.configuration, "Config1")
XCTAssertFalse(into.inferStoreIfPossible)
}
}
@objc
@@ -84,30 +98,43 @@ final class IntoTests: XCTestCase {
do {
let into = Into<NSManagedObject>()
let into = Into()
XCTAssertEqual(into, Into())
XCTAssertEqual(into, Into<NSManagedObject>())
XCTAssertEqual(into, Into(NSManagedObject.self))
XCTAssertNotEqual(into, Into<NSManagedObject>(TestEntity1.self))
XCTAssertEqual(into, Into(NSManagedObject.self as AnyClass))
XCTAssertFalse(into == Into<TestEntity1>())
XCTAssertNotEqual(into, Into<NSManagedObject>("Config1"))
}
do {
let into = Into<TestEntity1>()
XCTAssertEqual(into, Into<TestEntity1>())
XCTAssertEqual(into, Into(TestEntity1.self))
XCTAssertEqual(into, Into(TestEntity1))
XCTAssertEqual(into, Into(TestEntity1.self as AnyClass))
XCTAssertFalse(into == Into<TestEntity2>())
XCTAssertNotEqual(into, Into<TestEntity1>("Config1"))
}
do {
let into = Into(TestEntity1.self)
let into = Into(TestEntity1)
XCTAssertEqual(into, Into<TestEntity1>())
XCTAssertEqual(into, Into(TestEntity1.self as AnyClass))
XCTAssertFalse(into == Into<TestEntity2>())
XCTAssertNotEqual(into, Into<TestEntity1>("Config1"))
}
do {
let into = Into(TestEntity1.self as AnyClass)
XCTAssert(into == Into<TestEntity1>())
XCTAssertEqual(into, Into(TestEntity1.self))
XCTAssertEqual(into, Into(TestEntity1))
XCTAssertFalse(into == Into<TestEntity2>())
XCTAssertFalse(into == Into<TestEntity1>("Config1"))
}
do {
let into = Into<TestEntity1>("Config1")
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
XCTAssertEqual(into, Into(TestEntity1.self as AnyClass, "Config1"))
XCTAssertFalse(into == Into<TestEntity2>("Config1"))
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
}
do {
@@ -115,14 +142,16 @@ final class IntoTests: XCTestCase {
let into = Into(TestEntity1.self, "Config1")
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
XCTAssertEqual(into, Into<TestEntity1>("Config1"))
XCTAssertFalse(into == Into<TestEntity2>("Config1"))
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
}
do {
let into = Into(TestEntity1.self, "Config1")
XCTAssertEqual(into, Into<TestEntity1>("Config1"))
let into = Into(TestEntity1.self as AnyClass, "Config1")
XCTAssert(into == Into<TestEntity1>("Config1"))
XCTAssertEqual(into, Into(TestEntity1.self, "Config1"))
XCTAssertNotEqual(into, Into<TestEntity1>("Config2"))
XCTAssertFalse(into == Into<TestEntity2>("Config1"))
XCTAssertFalse(into == Into<TestEntity1>("Config2"))
}
}
@@ -131,9 +160,45 @@ final class IntoTests: XCTestCase {
do {
let into = Into<NSManagedObject>()
let into = Into()
let objcInto = into.bridgeToObjectiveC
XCTAssertEqual(into, objcInto.bridgeToSwift)
}
do {
let into = Into<TestEntity1>()
let objcInto = into.bridgeToObjectiveC
XCTAssertTrue(into == objcInto.bridgeToSwift)
}
do {
let into = Into(TestEntity1.self as AnyClass)
let objcInto = into.bridgeToObjectiveC
XCTAssertEqual(into, objcInto.bridgeToSwift)
}
do {
let into = Into(TestEntity1.self as AnyClass)
let objcInto = into.bridgeToObjectiveC
XCTAssertEqual(into, objcInto.bridgeToSwift)
}
do {
let into = Into<TestEntity1>("Config1")
let objcInto = into.bridgeToObjectiveC
XCTAssertTrue(into == objcInto.bridgeToSwift)
}
do {
let into = Into(TestEntity1.self, "Config1")
let objcInto = into.bridgeToObjectiveC
XCTAssertTrue(into == objcInto.bridgeToSwift)
}
do {
let into = Into(TestEntity1.self as AnyClass, "Config1")
let objcInto = into.bridgeToObjectiveC
XCTAssertTrue(into == objcInto.bridgeToSwift)
}
}
}

View File

@@ -29,9 +29,10 @@ import XCTest
import CoreStore
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - ListObserverTests
@available(OSX 10.12, *)
class ListObserverTests: BaseTestDataTestCase {
@objc
@@ -41,9 +42,9 @@ class ListObserverTests: BaseTestDataTestCase {
let observer = TestListObserver()
let monitor = stack.monitorSectionedList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
From(TestEntity1),
SectionBy("testBoolean"),
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
)
monitor.addObserver(observer)
@@ -53,13 +54,13 @@ class ListObserverTests: BaseTestDataTestCase {
var events = 0
let willChangeExpectation = self.expectation(
forNotification: "listMonitorWillChange:",
let willChangeExpectation = self.expectationForNotification(
"listMonitorWillChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
@@ -67,14 +68,14 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 0
}
)
let didInsertSectionExpectation = self.expectation(
forNotification: "listMonitor:didInsertSection:toSectionIndex:",
let didInsertSectionExpectation = self.expectationForNotification(
"listMonitor:didInsertSection:toSectionIndex:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 1)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
(note.userInfo ?? [:]),
[
"sectionInfo": monitor.sectionInfoAtIndex(0),
"sectionIndex": 0
@@ -87,8 +88,8 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 1
}
)
let didInsertObjectExpectation = self.expectation(
forNotification: "listMonitor:didInsertObject:toIndexPath:",
let didInsertObjectExpectation = self.expectationForNotification(
"listMonitor:didInsertObject:toIndexPath:",
object: observer,
handler: { (note) -> Bool in
@@ -97,21 +98,21 @@ class ListObserverTests: BaseTestDataTestCase {
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["indexPath", "object"]
)
let indexPath = userInfo?["indexPath"] as? NSIndexPath
XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
XCTAssertEqual(indexPath?.section, 0)
XCTAssertEqual(indexPath?.row, 0)
let object = userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
XCTAssertEqual(object?.testNumber, NSNumber(value: 1))
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 1))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "1"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:1")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).data(using: String.Encoding.utf8.rawValue)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-01T00:00:00Z")!)
XCTAssertEqual(object?.testData, ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!)
defer {
events += 1
@@ -119,12 +120,12 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 2
}
)
let didChangeExpectation = self.expectation(
forNotification: "listMonitorDidChange:",
let didChangeExpectation = self.expectationForNotification(
"listMonitorDidChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
@@ -132,30 +133,30 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 3
}
)
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
let object = transaction.create(Into(TestEntity1))
object.testBoolean = NSNumber(bool: true)
object.testNumber = NSNumber(integer: 1)
object.testDecimal = NSDecimalNumber(string: "1")
object.testString = "nil:TestEntity1:1"
object.testData = ("nil:TestEntity1:1" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
object.testDate = self.dateFormatter.dateFromString("2000-01-01T00:00:00Z")!
transaction.commit { (result) 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()
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
)
}
self.waitAndCheckExpectations()
}
}
@@ -169,9 +170,9 @@ class ListObserverTests: BaseTestDataTestCase {
let observer = TestListObserver()
let monitor = stack.monitorSectionedList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
From(TestEntity1),
SectionBy("testBoolean"),
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
)
monitor.addObserver(observer)
@@ -184,13 +185,13 @@ class ListObserverTests: BaseTestDataTestCase {
var events = 0
let willChangeExpectation = self.expectation(
forNotification: "listMonitorWillChange:",
let willChangeExpectation = self.expectationForNotification(
"listMonitorWillChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
@@ -200,8 +201,8 @@ class ListObserverTests: BaseTestDataTestCase {
)
for _ in 1 ... 2 {
let didUpdateObjectExpectation = self.expectation(
forNotification: "listMonitor:didUpdateObject:atIndexPath:",
let didUpdateObjectExpectation = self.expectationForNotification(
"listMonitor:didUpdateObject:atIndexPath:",
object: observer,
handler: { (note) -> Bool in
@@ -210,7 +211,7 @@ class ListObserverTests: BaseTestDataTestCase {
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["indexPath", "object"]
)
@@ -219,27 +220,27 @@ class ListObserverTests: BaseTestDataTestCase {
switch object?.testEntityID {
case NSNumber(value: 101)?:
XCTAssertEqual(indexPath?.index(atPosition: 0), 1)
XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
case NSNumber(integer: 101)?:
XCTAssertEqual(indexPath?.section, 1)
XCTAssertEqual(indexPath?.row, 0)
XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
XCTAssertEqual(object?.testNumber, NSNumber(value: 11))
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 11))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "11"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:11")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!)
XCTAssertEqual(object?.testData, ("nil:TestEntity1:11" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-11T00:00:00Z")!)
case NSNumber(value: 102)?:
XCTAssertEqual(indexPath?.index(atPosition: 0), 0)
XCTAssertEqual(indexPath?.index(atPosition: 1), 0)
case NSNumber(integer: 102)?:
XCTAssertEqual(indexPath?.section, 0)
XCTAssertEqual(indexPath?.row, 0)
XCTAssertEqual(object?.testBoolean, NSNumber(value: false))
XCTAssertEqual(object?.testNumber, NSNumber(value: 22))
XCTAssertEqual(object?.testBoolean, NSNumber(bool: false))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 22))
XCTAssertEqual(object?.testDecimal, NSDecimalNumber(string: "22"))
XCTAssertEqual(object?.testString, "nil:TestEntity1:22")
XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!)
XCTAssertEqual(object?.testData, ("nil:TestEntity1:22" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!)
XCTAssertEqual(object?.testDate, self.dateFormatter.dateFromString("2000-01-22T00:00:00Z")!)
default:
XCTFail()
@@ -252,13 +253,13 @@ class ListObserverTests: BaseTestDataTestCase {
}
)
}
let didChangeExpectation = self.expectation(
forNotification: "listMonitorDidChange:",
let didChangeExpectation = self.expectationForNotification(
"listMonitorDidChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 3)
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
@@ -266,50 +267,50 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 3
}
)
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
if let object = transaction.fetchOne(
From(TestEntity1),
Where("testEntityID", isEqualTo: 101)) {
if let object = transaction.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) {
object.testNumber = NSNumber(value: 11)
object.testDecimal = NSDecimalNumber(string: "11")
object.testString = "nil:TestEntity1:11"
object.testData = ("nil:TestEntity1:11" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-11T00:00:00Z")!
}
else {
XCTFail()
}
if let object = transaction.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
object.testNumber = NSNumber(value: 22)
object.testDecimal = NSDecimalNumber(string: "22")
object.testString = "nil:TestEntity1:22"
object.testData = ("nil:TestEntity1:22" as NSString).data(using: String.Encoding.utf8.rawValue)!
object.testDate = self.dateFormatter.date(from: "2000-01-22T00:00:00Z")!
}
else {
XCTFail()
}
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
object.testNumber = NSNumber(integer: 11)
object.testDecimal = NSDecimalNumber(string: "11")
object.testString = "nil:TestEntity1:11"
object.testData = ("nil:TestEntity1:11" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
object.testDate = self.dateFormatter.dateFromString("2000-01-11T00:00:00Z")!
}
else {
XCTFail()
}
)
if let object = transaction.fetchOne(
From(TestEntity1),
Where("testEntityID", isEqualTo: 102)) {
object.testNumber = NSNumber(integer: 22)
object.testDecimal = NSDecimalNumber(string: "22")
object.testString = "nil:TestEntity1:22"
object.testData = ("nil:TestEntity1:22" as NSString).dataUsingEncoding(NSUTF8StringEncoding)!
object.testDate = self.dateFormatter.dateFromString("2000-01-22T00:00:00Z")!
}
else {
XCTFail()
}
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
@@ -323,21 +324,21 @@ class ListObserverTests: BaseTestDataTestCase {
let observer = TestListObserver()
let monitor = stack.monitorSectionedList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
From(TestEntity1),
SectionBy("testBoolean"),
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
)
monitor.addObserver(observer)
var events = 0
let willChangeExpectation = self.expectation(
forNotification: "listMonitorWillChange:",
let willChangeExpectation = self.expectationForNotification(
"listMonitorWillChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
@@ -345,8 +346,8 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 0
}
)
let didMoveObjectExpectation = self.expectation(
forNotification: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:",
let didMoveObjectExpectation = self.expectationForNotification(
"listMonitor:didMoveObject:fromIndexPath:toIndexPath:",
object: observer,
handler: { (note) -> Bool in
@@ -355,21 +356,21 @@ class ListObserverTests: BaseTestDataTestCase {
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["fromIndexPath", "toIndexPath", "object"]
)
let fromIndexPath = userInfo?["fromIndexPath"] as? NSIndexPath
XCTAssertEqual(fromIndexPath?.index(atPosition: 0), 0)
XCTAssertEqual(fromIndexPath?.index(atPosition: 1), 0)
XCTAssertEqual(fromIndexPath?.section, 0)
XCTAssertEqual(fromIndexPath?.row, 0)
let toIndexPath = userInfo?["toIndexPath"] as? NSIndexPath
XCTAssertEqual(toIndexPath?.index(atPosition: 0), 1)
XCTAssertEqual(toIndexPath?.index(atPosition: 1), 1)
XCTAssertEqual(toIndexPath?.section, 1)
XCTAssertEqual(toIndexPath?.row, 1)
let object = userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.testEntityID, NSNumber(value: 102))
XCTAssertEqual(object?.testBoolean, NSNumber(value: true))
XCTAssertEqual(object?.testEntityID, NSNumber(integer: 102))
XCTAssertEqual(object?.testBoolean, NSNumber(bool: true))
defer {
@@ -378,13 +379,13 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 1
}
)
let didChangeExpectation = self.expectation(
forNotification: "listMonitorDidChange:",
let didChangeExpectation = self.expectationForNotification(
"listMonitorDidChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 2)
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
@@ -392,32 +393,32 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 2
}
)
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
if let object = transaction.fetchOne(
From(TestEntity1),
Where("testEntityID", isEqualTo: 102)) {
if let object = transaction.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 102)) {
object.testBoolean = NSNumber(value: true)
}
else {
XCTFail()
}
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
object.testBoolean = NSNumber(bool: true)
}
else {
XCTFail()
}
)
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
@@ -431,21 +432,21 @@ class ListObserverTests: BaseTestDataTestCase {
let observer = TestListObserver()
let monitor = stack.monitorSectionedList(
From<TestEntity1>(),
SectionBy(#keyPath(TestEntity1.testBoolean)),
OrderBy(.ascending(#keyPath(TestEntity1.testBoolean)), .ascending(#keyPath(TestEntity1.testEntityID)))
From(TestEntity1),
SectionBy("testBoolean"),
OrderBy(.Ascending("testBoolean"), .Ascending("testEntityID"))
)
monitor.addObserver(observer)
var events = 0
let willChangeExpectation = self.expectation(
forNotification: "listMonitorWillChange:",
let willChangeExpectation = self.expectationForNotification(
"listMonitorWillChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
@@ -455,8 +456,8 @@ class ListObserverTests: BaseTestDataTestCase {
)
for _ in 1 ... 2 {
let didUpdateObjectExpectation = self.expectation(
forNotification: "listMonitor:didDeleteObject:fromIndexPath:",
let didUpdateObjectExpectation = self.expectationForNotification(
"listMonitor:didDeleteObject:fromIndexPath:",
object: observer,
handler: { (note) -> Bool in
@@ -465,17 +466,17 @@ class ListObserverTests: BaseTestDataTestCase {
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["indexPath", "object"]
)
let indexPath = userInfo?["indexPath"] as? NSIndexPath
XCTAssertEqual(indexPath?.section, 0)
XCTAssert(indexPath?.index(atPosition: 1) == 0 || indexPath?.index(atPosition: 1) == 1)
XCTAssert(indexPath?.row == 0 || indexPath?.row == 1)
let object = userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.isDeleted, true)
XCTAssertEqual(object?.deleted, true)
defer {
@@ -485,8 +486,8 @@ class ListObserverTests: BaseTestDataTestCase {
}
)
}
let didDeleteSectionExpectation = self.expectation(
forNotification: "listMonitor:didDeleteSection:fromSectionIndex:",
let didDeleteSectionExpectation = self.expectationForNotification(
"listMonitor:didDeleteSection:fromSectionIndex:",
object: observer,
handler: { (note) -> Bool in
@@ -495,16 +496,16 @@ class ListObserverTests: BaseTestDataTestCase {
let userInfo = note.userInfo
XCTAssertNotNil(userInfo)
XCTAssertEqual(
Set(userInfo?.keys.map({ $0 as! String }) ?? []),
Set(((userInfo as? [String: AnyObject]) ?? [:]).keys),
["sectionInfo", "sectionIndex"]
)
let sectionInfo = userInfo?["sectionInfo"] as? NSFetchedResultsSectionInfo
let sectionInfo = userInfo?["sectionInfo"]
XCTAssertNotNil(sectionInfo)
XCTAssertEqual(sectionInfo?.name, "0")
let sectionIndex = userInfo?["sectionIndex"]
XCTAssertEqual(sectionIndex as? NSNumber, NSNumber(value: 0))
XCTAssertEqual(sectionIndex as? NSNumber, NSNumber(integer: 0))
defer {
@@ -513,13 +514,13 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 3
}
)
let didChangeExpectation = self.expectation(
forNotification: "listMonitorDidChange:",
let didChangeExpectation = self.expectationForNotification(
"listMonitorDidChange:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 4)
XCTAssertEqual((note.userInfo as NSDictionary?) ?? [:], NSDictionary())
XCTAssertEqual((note.userInfo ?? [:]), NSDictionary())
defer {
events += 1
@@ -527,26 +528,26 @@ class ListObserverTests: BaseTestDataTestCase {
return events == 4
}
)
let saveExpectation = self.expectation(description: "save")
stack.perform(
asynchronous: { (transaction) -> Bool in
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
transaction.deleteAll(
From(TestEntity1),
Where("testBoolean", isEqualTo: false)
)
transaction.commit { (result) in
transaction.deleteAll(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testBoolean), isEqualTo: false)
)
return transaction.hasChanges
},
success: { (hasChanges) in
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
},
failure: { _ in
XCTFail()
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
)
}
self.waitAndCheckExpectations()
}
}
@@ -555,44 +556,43 @@ class ListObserverTests: BaseTestDataTestCase {
// MARK: TestListObserver
@available(OSX 10.12, *)
class TestListObserver: ListSectionObserver {
// MARK: ListObserver
typealias ListEntityType = TestEntity1
func listMonitorWillChange(_ monitor: ListMonitor<TestEntity1>) {
func listMonitorWillChange(monitor: ListMonitor<TestEntity1>) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitorWillChange:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitorWillChange:",
object: self,
userInfo: [:]
)
}
func listMonitorDidChange(_ monitor: ListMonitor<TestEntity1>) {
func listMonitorDidChange(monitor: ListMonitor<TestEntity1>) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitorDidChange:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitorDidChange:",
object: self,
userInfo: [:]
)
}
func listMonitorWillRefetch(_ monitor: ListMonitor<TestEntity1>) {
func listMonitorWillRefetch(monitor: ListMonitor<TestEntity1>) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitorWillRefetch:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitorWillRefetch:",
object: self,
userInfo: [:]
)
}
func listMonitorDidRefetch(_ monitor: ListMonitor<TestEntity1>) {
func listMonitorDidRefetch(monitor: ListMonitor<TestEntity1>) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitorDidRefetch:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitorDidRefetch:",
object: self,
userInfo: [:]
)
@@ -601,10 +601,10 @@ class TestListObserver: ListSectionObserver {
// MARK: ListObjectObserver
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didInsertObject object: TestEntity1, toIndexPath indexPath: IndexPath) {
func listMonitor(monitor: ListMonitor<TestEntity1>, didInsertObject object: TestEntity1, toIndexPath indexPath: NSIndexPath) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitor:didInsertObject:toIndexPath:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didInsertObject:toIndexPath:",
object: self,
userInfo: [
"object": object,
@@ -613,10 +613,10 @@ class TestListObserver: ListSectionObserver {
)
}
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteObject object: TestEntity1, fromIndexPath indexPath: IndexPath) {
func listMonitor(monitor: ListMonitor<TestEntity1>, didDeleteObject object: TestEntity1, fromIndexPath indexPath: NSIndexPath) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitor:didDeleteObject:fromIndexPath:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didDeleteObject:fromIndexPath:",
object: self,
userInfo: [
"object": object,
@@ -625,10 +625,10 @@ class TestListObserver: ListSectionObserver {
)
}
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didUpdateObject object: TestEntity1, atIndexPath indexPath: IndexPath) {
func listMonitor(monitor: ListMonitor<TestEntity1>, didUpdateObject object: TestEntity1, atIndexPath indexPath: NSIndexPath) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitor:didUpdateObject:atIndexPath:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didUpdateObject:atIndexPath:",
object: self,
userInfo: [
"object": object,
@@ -638,10 +638,10 @@ class TestListObserver: ListSectionObserver {
}
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didMoveObject object: TestEntity1, fromIndexPath: IndexPath, toIndexPath: IndexPath) {
func listMonitor(monitor: ListMonitor<TestEntity1>, didMoveObject object: TestEntity1, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitor:didMoveObject:fromIndexPath:toIndexPath:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didMoveObject:fromIndexPath:toIndexPath:",
object: self,
userInfo: [
"object": object,
@@ -654,10 +654,10 @@ class TestListObserver: ListSectionObserver {
// MARK: ListSectionObserver
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
func listMonitor(monitor: ListMonitor<TestEntity1>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitor:didInsertSection:toSectionIndex:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didInsertSection:toSectionIndex:",
object: self,
userInfo: [
"sectionInfo": sectionInfo,
@@ -666,10 +666,10 @@ class TestListObserver: ListSectionObserver {
)
}
func listMonitor(_ monitor: ListMonitor<TestEntity1>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
func listMonitor(monitor: ListMonitor<TestEntity1>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "listMonitor:didDeleteSection:fromSectionIndex:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"listMonitor:didDeleteSection:fromSectionIndex:",
object: self,
userInfo: [
"sectionInfo": sectionInfo,
@@ -678,3 +678,5 @@ class TestListObserver: ListSectionObserver {
)
}
}
#endif

View File

@@ -37,8 +37,8 @@ final class MigrationChainTests: XCTestCase {
dynamic func test_ThatNilMigrationChains_HaveNoVersions() {
let chain: MigrationChain = nil
XCTAssertTrue(chain.isValid)
XCTAssertTrue(chain.isEmpty)
XCTAssertTrue(chain.valid)
XCTAssertTrue(chain.empty)
XCTAssertFalse(chain.contains("version1"))
XCTAssertNil(chain.nextVersionFrom("version1"))
@@ -48,8 +48,8 @@ final class MigrationChainTests: XCTestCase {
dynamic func test_ThatStringMigrationChains_HaveOneVersion() {
let chain: MigrationChain = "version1"
XCTAssertTrue(chain.isValid)
XCTAssertTrue(chain.isEmpty)
XCTAssertTrue(chain.valid)
XCTAssertTrue(chain.empty)
XCTAssertTrue(chain.contains("version1"))
XCTAssertFalse(chain.contains("version2"))
@@ -62,8 +62,8 @@ final class MigrationChainTests: XCTestCase {
dynamic func test_ThatArrayMigrationChains_HaveLinearVersions() {
let chain: MigrationChain = ["version1", "version2", "version3", "version4"]
XCTAssertTrue(chain.isValid)
XCTAssertFalse(chain.isEmpty)
XCTAssertTrue(chain.valid)
XCTAssertFalse(chain.empty)
XCTAssertTrue(chain.contains("version1"))
XCTAssertTrue(chain.contains("version2"))
@@ -86,8 +86,8 @@ final class MigrationChainTests: XCTestCase {
"version2": "version3",
"version3": "version4"
]
XCTAssertTrue(chain.isValid)
XCTAssertFalse(chain.isEmpty)
XCTAssertTrue(chain.valid)
XCTAssertFalse(chain.empty)
XCTAssertTrue(chain.contains("version1"))
XCTAssertTrue(chain.contains("version2"))

View File

@@ -1,23 +1,23 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="11759" systemVersion="16C67" minimumToolsVersion="Xcode 4.3" sourceLanguage="Objective-C" userDefinedModelVersionIdentifier="">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10174" systemVersion="15F34" minimumToolsVersion="Xcode 4.3">
<entity name="TestEntity1AAA" representedClassName="CoreStoreTests.TestEntity1" syncable="YES">
<attribute name="testBoolean" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="testBoolean" optional="YES" attributeType="Boolean" syncable="YES"/>
<attribute name="testData" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="testDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="testDate" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="testDecimal" optional="YES" attributeType="Decimal" syncable="YES"/>
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="testNil" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="testNumber" optional="YES" attributeType="Integer 32" usesScalarValueType="NO" syncable="YES"/>
<attribute name="testNumber" optional="YES" attributeType="Integer 32" syncable="YES"/>
<attribute name="testString" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<entity name="TestEntity2" representedClassName="CoreStoreTests.TestEntity2" syncable="YES">
<attribute name="testBoolean" optional="YES" attributeType="Boolean" usesScalarValueType="NO" syncable="YES"/>
<attribute name="testBoolean" optional="YES" attributeType="Boolean" syncable="YES"/>
<attribute name="testData" optional="YES" attributeType="Binary" syncable="YES"/>
<attribute name="testDate" optional="YES" attributeType="Date" usesScalarValueType="NO" syncable="YES"/>
<attribute name="testDate" optional="YES" attributeType="Date" syncable="YES"/>
<attribute name="testDecimal" optional="YES" attributeType="Decimal" syncable="YES"/>
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" usesScalarValueType="NO" syncable="YES"/>
<attribute name="testEntityID" optional="YES" attributeType="Integer 64" syncable="YES"/>
<attribute name="testNil" optional="YES" attributeType="String" syncable="YES"/>
<attribute name="testNumber" optional="YES" attributeType="Integer 32" usesScalarValueType="NO" syncable="YES"/>
<attribute name="testNumber" optional="YES" attributeType="Integer 32" syncable="YES"/>
<attribute name="testString" optional="YES" attributeType="String" syncable="YES"/>
</entity>
<configuration name="Config1">

View File

@@ -29,9 +29,10 @@ import XCTest
import CoreStore
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - ObjectObserverTests
@available(OSX 10.12, *)
class ObjectObserverTests: BaseTestDataTestCase {
@objc
@@ -42,8 +43,8 @@ class ObjectObserverTests: BaseTestDataTestCase {
self.prepareTestDataForStack(stack)
guard let object = stack.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
From(TestEntity1),
Where("testEntityID", isEqualTo: 101)) else {
XCTFail()
return
@@ -57,14 +58,14 @@ class ObjectObserverTests: BaseTestDataTestCase {
var events = 0
let willUpdateExpectation = self.expectation(
forNotification: "objectMonitor:willUpdateObject:",
let willUpdateExpectation = self.expectationForNotification(
"objectMonitor:willUpdateObject:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
(note.userInfo ?? [:]),
["object": object] as NSDictionary
)
defer {
@@ -74,26 +75,26 @@ class ObjectObserverTests: BaseTestDataTestCase {
return events == 0
}
)
let didUpdateExpectation = self.expectation(
forNotification: "objectMonitor:didUpdateObject:changedPersistentKeys:",
let didUpdateExpectation = self.expectationForNotification(
"objectMonitor:didUpdateObject:changedPersistentKeys:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 1)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
(note.userInfo ?? [:]),
[
"object": object,
"changedPersistentKeys": Set(
[
#keyPath(TestEntity1.testNumber),
#keyPath(TestEntity1.testString)
"testNumber",
"testString"
]
)
] as NSDictionary
)
let object = note.userInfo?["object"] as? TestEntity1
XCTAssertEqual(object?.testNumber, NSNumber(value: 10))
XCTAssertEqual(object?.testNumber, NSNumber(integer: 10))
XCTAssertEqual(object?.testString, "nil:TestEntity1:10")
defer {
@@ -103,30 +104,30 @@ class ObjectObserverTests: BaseTestDataTestCase {
return events == 1
}
)
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
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
guard let object = transaction.edit(object) else {
XCTFail()
return
}
)
object.testNumber = NSNumber(integer: 10)
object.testString = "nil:TestEntity1:10"
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
@@ -139,8 +140,8 @@ class ObjectObserverTests: BaseTestDataTestCase {
self.prepareTestDataForStack(stack)
guard let object = stack.fetchOne(
From<TestEntity1>(),
Where(#keyPath(TestEntity1.testEntityID), isEqualTo: 101)) else {
From(TestEntity1),
Where("testEntityID", isEqualTo: 101)) else {
XCTFail()
return
@@ -154,14 +155,14 @@ class ObjectObserverTests: BaseTestDataTestCase {
var events = 0
let didDeleteExpectation = self.expectation(
forNotification: "objectMonitor:didDeleteObject:",
let didDeleteExpectation = self.expectationForNotification(
"objectMonitor:didDeleteObject:",
object: observer,
handler: { (note) -> Bool in
XCTAssertEqual(events, 0)
XCTAssertEqual(
((note.userInfo as NSDictionary?) ?? [:]),
(note.userInfo ?? [:]),
["object": object] as NSDictionary
)
defer {
@@ -171,30 +172,30 @@ class ObjectObserverTests: BaseTestDataTestCase {
return events == 0
}
)
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)
XCTAssertTrue(monitor.isObjectDeleted)
saveExpectation.fulfill()
},
failure: { _ in
let saveExpectation = self.expectationWithDescription("save")
stack.beginAsynchronous { (transaction) in
guard let object = transaction.edit(object) else {
XCTFail()
return
}
)
transaction.delete(object)
transaction.commit { (result) in
switch result {
case .Success(let hasChanges):
XCTAssertTrue(hasChanges)
XCTAssertTrue(monitor.isObjectDeleted)
saveExpectation.fulfill()
case .Failure:
XCTFail()
}
}
}
self.waitAndCheckExpectations()
}
}
@@ -203,15 +204,14 @@ class ObjectObserverTests: BaseTestDataTestCase {
// MARK: TestObjectObserver
@available(OSX 10.12, *)
class TestObjectObserver: ObjectObserver {
typealias ObjectEntityType = TestEntity1
func objectMonitor(_ monitor: ObjectMonitor<TestEntity1>, willUpdateObject object: TestEntity1) {
func objectMonitor(monitor: ObjectMonitor<TestEntity1>, willUpdateObject object: TestEntity1) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "objectMonitor:willUpdateObject:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"objectMonitor:willUpdateObject:",
object: self,
userInfo: [
"object": object
@@ -219,10 +219,10 @@ class TestObjectObserver: ObjectObserver {
)
}
func objectMonitor(_ monitor: ObjectMonitor<TestEntity1>, didUpdateObject object: TestEntity1, changedPersistentKeys: Set<KeyPath>) {
func objectMonitor(monitor: ObjectMonitor<TestEntity1>, didUpdateObject object: TestEntity1, changedPersistentKeys: Set<KeyPath>) {
NotificationCenter.default.post(
name: NSNotification.Name(rawValue: "objectMonitor:didUpdateObject:changedPersistentKeys:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"objectMonitor:didUpdateObject:changedPersistentKeys:",
object: self,
userInfo: [
"object": object,
@@ -231,10 +231,10 @@ class TestObjectObserver: ObjectObserver {
)
}
func objectMonitor(_ monitor: ObjectMonitor<TestEntity1>, didDeleteObject object: TestEntity1) {
func objectMonitor(monitor: ObjectMonitor<TestEntity1>, didDeleteObject object: TestEntity1) {
NotificationCenter.default.post(
name: Notification.Name(rawValue: "objectMonitor:didDeleteObject:"),
NSNotificationCenter.defaultCenter().postNotificationName(
"objectMonitor:didDeleteObject:",
object: self,
userInfo: [
"object": object
@@ -242,3 +242,5 @@ class TestObjectObserver: ObjectObserver {
)
}
}
#endif

View File

@@ -39,7 +39,7 @@ final class OrderByTests: XCTestCase {
do {
let orderBy = OrderBy()
XCTAssertEqual(orderBy, OrderBy([NSSortDescriptor]()))
XCTAssertEqual(orderBy, OrderBy([] as [NSSortDescriptor]))
XCTAssertNotEqual(orderBy, OrderBy(NSSortDescriptor(key: "key", ascending: false)))
XCTAssertTrue(orderBy.sortDescriptors.isEmpty)
}
@@ -48,9 +48,9 @@ final class OrderByTests: XCTestCase {
let sortDescriptor = NSSortDescriptor(key: "key1", ascending: true)
let orderBy = OrderBy(sortDescriptor)
XCTAssertEqual(orderBy, OrderBy(sortDescriptor))
XCTAssertEqual(orderBy, OrderBy(.ascending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(.ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.descending("key1")))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.Descending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(NSSortDescriptor(key: "key1", ascending: false)))
XCTAssertEqual(orderBy, OrderBy([sortDescriptor]))
XCTAssertEqual(orderBy.sortDescriptors, [sortDescriptor])
@@ -63,7 +63,7 @@ final class OrderByTests: XCTestCase {
]
let orderBy = OrderBy(sortDescriptors)
XCTAssertEqual(orderBy, OrderBy(sortDescriptors))
XCTAssertEqual(orderBy, OrderBy(.ascending("key1"), .descending("key2")))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
XCTAssertNotEqual(
orderBy,
OrderBy(
@@ -73,30 +73,30 @@ final class OrderByTests: XCTestCase {
]
)
)
XCTAssertNotEqual(orderBy, OrderBy(.ascending("key1"), .ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.ascending("key1"), .descending("key3")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key3")))
XCTAssertEqual(orderBy.sortDescriptors, sortDescriptors)
}
do {
let orderBy = OrderBy(.ascending("key1"))
let orderBy = OrderBy(.Ascending("key1"))
let sortDescriptor = NSSortDescriptor(key: "key1", ascending: true)
XCTAssertEqual(orderBy, OrderBy(sortDescriptor))
XCTAssertEqual(orderBy, OrderBy(.ascending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(.descending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(.ascending("key2")))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(.Descending("key1")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key2")))
XCTAssertEqual(orderBy, OrderBy([sortDescriptor]))
XCTAssertEqual(orderBy.sortDescriptors, [sortDescriptor])
}
do {
let orderBy = OrderBy(.ascending("key1"), .descending("key2"))
let orderBy = OrderBy(.Ascending("key1"), .Descending("key2"))
let sortDescriptors = [
NSSortDescriptor(key: "key1", ascending: true),
NSSortDescriptor(key: "key2", ascending: false)
]
XCTAssertEqual(orderBy, OrderBy(sortDescriptors))
XCTAssertEqual(orderBy, OrderBy(.ascending("key1"), .descending("key2")))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
XCTAssertNotEqual(
orderBy,
OrderBy(
@@ -106,20 +106,20 @@ final class OrderByTests: XCTestCase {
]
)
)
XCTAssertNotEqual(orderBy, OrderBy(.ascending("key1"), .ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.ascending("key1"), .descending("key3")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key3")))
XCTAssertEqual(orderBy.sortDescriptors, sortDescriptors)
}
do {
let sortKeys: [SortKey] = [.ascending("key1"), .descending("key2")]
let sortKeys: [SortKey] = [.Ascending("key1"), .Descending("key2")]
let orderBy = OrderBy(sortKeys)
let sortDescriptors = [
NSSortDescriptor(key: "key1", ascending: true),
NSSortDescriptor(key: "key2", ascending: false)
]
XCTAssertEqual(orderBy, OrderBy(sortDescriptors))
XCTAssertEqual(orderBy, OrderBy(.ascending("key1"), .descending("key2")))
XCTAssertEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
XCTAssertNotEqual(
orderBy,
OrderBy(
@@ -129,8 +129,8 @@ final class OrderByTests: XCTestCase {
]
)
)
XCTAssertNotEqual(orderBy, OrderBy(.ascending("key1"), .ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.ascending("key1"), .descending("key3")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Ascending("key2")))
XCTAssertNotEqual(orderBy, OrderBy(.Ascending("key1"), .Descending("key3")))
XCTAssertEqual(orderBy.sortDescriptors, sortDescriptors)
}
}
@@ -138,15 +138,15 @@ final class OrderByTests: XCTestCase {
@objc
dynamic func test_ThatOrderByClauseOperations_ComputeCorrectly() {
let orderBy1 = OrderBy(.ascending("key1"))
let orderBy2 = OrderBy(.descending("key2"))
let orderBy3 = OrderBy(.ascending("key3"))
let orderBy1 = OrderBy(.Ascending("key1"))
let orderBy2 = OrderBy(.Descending("key2"))
let orderBy3 = OrderBy(.Ascending("key3"))
do {
let plusOrderBy = orderBy1 + orderBy2 + orderBy3
XCTAssertEqual(plusOrderBy, OrderBy(.ascending("key1"), .descending("key2"), .ascending("key3")))
XCTAssertEqual(plusOrderBy, OrderBy(.ascending("key1")) + OrderBy(.descending("key2"), .ascending("key3")))
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2"), .Ascending("key3")))
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1")) + OrderBy(.Descending("key2"), .Ascending("key3")))
XCTAssertNotEqual(plusOrderBy, orderBy1 + orderBy3 + orderBy2)
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy1 + orderBy3)
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy3 + orderBy1)
@@ -158,14 +158,14 @@ final class OrderByTests: XCTestCase {
var plusOrderBy = orderBy1
plusOrderBy += orderBy2
XCTAssertEqual(plusOrderBy, OrderBy(.ascending("key1"), .descending("key2")))
XCTAssertEqual(plusOrderBy, OrderBy(.ascending("key1")) + OrderBy(.descending("key2")))
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2")))
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1")) + OrderBy(.Descending("key2")))
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy1)
XCTAssertEqual(plusOrderBy.sortDescriptors, orderBy1.sortDescriptors + orderBy2.sortDescriptors)
plusOrderBy += orderBy3
XCTAssertEqual(plusOrderBy, OrderBy(.ascending("key1"), .descending("key2"), .ascending("key3")))
XCTAssertEqual(plusOrderBy, OrderBy(.ascending("key1"), .descending("key2")) + OrderBy(.ascending("key3")))
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2"), .Ascending("key3")))
XCTAssertEqual(plusOrderBy, OrderBy(.Ascending("key1"), .Descending("key2")) + OrderBy(.Ascending("key3")))
XCTAssertNotEqual(plusOrderBy, orderBy1 + orderBy3 + orderBy2)
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy1 + orderBy3)
XCTAssertNotEqual(plusOrderBy, orderBy2 + orderBy3 + orderBy1)
@@ -178,8 +178,8 @@ final class OrderByTests: XCTestCase {
@objc
dynamic func test_ThatOrderByClauses_ApplyToFetchRequestsCorrectly() {
let orderBy = OrderBy(.ascending("key"))
let request = CoreStoreFetchRequest()
let orderBy = OrderBy(.Ascending("key"))
let request = NSFetchRequest()
orderBy.applyToFetchRequest(request)
XCTAssertNotNil(request.sortDescriptors)
XCTAssertEqual(request.sortDescriptors ?? [], orderBy.sortDescriptors)

File diff suppressed because it is too large Load Diff

View File

@@ -29,9 +29,10 @@ import XCTest
import CoreStore
#if os(iOS) || os(watchOS) || os(tvOS)
//MARK: - SectionByTests
@available(OSX 10.12, *)
final class SectionByTests: XCTestCase {
@objc
@@ -41,14 +42,16 @@ final class SectionByTests: XCTestCase {
let sectionBy = SectionBy("key")
XCTAssertEqual(sectionBy.sectionKeyPath, "key")
XCTAssertEqual(sectionBy.sectionIndexTransformer("key"), "key")
XCTAssertEqual(sectionBy.sectionIndexTransformer(sectionName: "key"), "key")
}
do {
let sectionBy = SectionBy("key") { $0.flatMap { "\($0):suffix" } }
XCTAssertEqual(sectionBy.sectionKeyPath, "key")
XCTAssertEqual(sectionBy.sectionIndexTransformer("key"), "key:suffix")
XCTAssertNil(sectionBy.sectionIndexTransformer(nil))
XCTAssertEqual(sectionBy.sectionIndexTransformer(sectionName: "key"), "key:suffix")
XCTAssertNil(sectionBy.sectionIndexTransformer(sectionName: nil))
}
}
}
#endif

View File

@@ -39,17 +39,17 @@ final class SelectTests: XCTestCase {
do {
let term: SelectTerm = "attribute"
XCTAssertEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute2"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
XCTAssertEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._attribute(let key):
case ._Attribute(let key):
XCTAssertEqual(key, "attribute")
default:
@@ -58,17 +58,17 @@ final class SelectTests: XCTestCase {
}
do {
let term = SelectTerm.attribute("attribute")
XCTAssertNotEqual(term, SelectTerm.attribute("attribute2"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Attribute("attribute")
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._attribute(let key):
case ._Attribute(let key):
XCTAssertEqual(key, "attribute")
default:
@@ -82,23 +82,23 @@ final class SelectTests: XCTestCase {
do {
let term = SelectTerm.average("attribute")
XCTAssertEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.average("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Average("attribute")
XCTAssertEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "average:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "average(attribute)")
XCTAssertTrue(nativeType == .decimalAttributeType)
XCTAssertTrue(nativeType == .DecimalAttributeType)
default:
XCTFail()
@@ -106,23 +106,23 @@ final class SelectTests: XCTestCase {
}
do {
let term = SelectTerm.average("attribute", as: "alias")
XCTAssertEqual(term, SelectTerm.average("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.average("attribute", as: "alias2"))
XCTAssertNotEqual(term, SelectTerm.average("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Average("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Average("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "average:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .decimalAttributeType)
XCTAssertTrue(nativeType == .DecimalAttributeType)
default:
XCTFail()
@@ -135,23 +135,23 @@ final class SelectTests: XCTestCase {
do {
let term = SelectTerm.count("attribute")
XCTAssertEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.count("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Count("attribute")
XCTAssertEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "count:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "count(attribute)")
XCTAssertTrue(nativeType == .integer64AttributeType)
XCTAssertTrue(nativeType == .Integer64AttributeType)
default:
XCTFail()
@@ -159,23 +159,23 @@ final class SelectTests: XCTestCase {
}
do {
let term = SelectTerm.count("attribute", as: "alias")
XCTAssertEqual(term, SelectTerm.count("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.count("attribute", as: "alias2"))
XCTAssertNotEqual(term, SelectTerm.count("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Count("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Count("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "count:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .integer64AttributeType)
XCTAssertTrue(nativeType == .Integer64AttributeType)
default:
XCTFail()
@@ -188,23 +188,23 @@ final class SelectTests: XCTestCase {
do {
let term = SelectTerm.maximum("attribute")
XCTAssertEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Maximum("attribute")
XCTAssertEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "max:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "max(attribute)")
XCTAssertTrue(nativeType == .undefinedAttributeType)
XCTAssertTrue(nativeType == .UndefinedAttributeType)
default:
XCTFail()
@@ -212,23 +212,23 @@ final class SelectTests: XCTestCase {
}
do {
let term = SelectTerm.maximum("attribute", as: "alias")
XCTAssertEqual(term, SelectTerm.maximum("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute", as: "alias2"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Maximum("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Maximum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "max:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .undefinedAttributeType)
XCTAssertTrue(nativeType == .UndefinedAttributeType)
default:
XCTFail()
@@ -241,23 +241,23 @@ final class SelectTests: XCTestCase {
do {
let term = SelectTerm.minimum("attribute")
XCTAssertEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Minimum("attribute")
XCTAssertEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "min:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "min(attribute)")
XCTAssertTrue(nativeType == .undefinedAttributeType)
XCTAssertTrue(nativeType == .UndefinedAttributeType)
default:
XCTFail()
@@ -265,23 +265,23 @@ final class SelectTests: XCTestCase {
}
do {
let term = SelectTerm.minimum("attribute", as: "alias")
XCTAssertEqual(term, SelectTerm.minimum("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute", as: "alias2"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Minimum("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Minimum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "min:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .undefinedAttributeType)
XCTAssertTrue(nativeType == .UndefinedAttributeType)
default:
XCTFail()
@@ -294,23 +294,23 @@ final class SelectTests: XCTestCase {
do {
let term = SelectTerm.sum("attribute")
XCTAssertEqual(term, SelectTerm.sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Sum("attribute")
XCTAssertEqual(term, SelectTerm.Sum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "sum:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "sum(attribute)")
XCTAssertTrue(nativeType == .decimalAttributeType)
XCTAssertTrue(nativeType == .DecimalAttributeType)
default:
XCTFail()
@@ -318,23 +318,23 @@ final class SelectTests: XCTestCase {
}
do {
let term = SelectTerm.sum("attribute", as: "alias")
XCTAssertEqual(term, SelectTerm.sum("attribute", as: "alias"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute", as: "alias2"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.objectID())
let term = SelectTerm.Sum("attribute", As: "alias")
XCTAssertEqual(term, SelectTerm.Sum("attribute", As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute", As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute2"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
switch term {
case ._aggregate(let function, let keyPath, let alias, let nativeType):
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
XCTAssertEqual(function, "sum:")
XCTAssertEqual(keyPath, "attribute")
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .decimalAttributeType)
XCTAssertTrue(nativeType == .DecimalAttributeType)
default:
XCTFail()
@@ -347,20 +347,20 @@ final class SelectTests: XCTestCase {
do {
let term = SelectTerm.objectID()
XCTAssertEqual(term, SelectTerm.objectID())
XCTAssertNotEqual(term, SelectTerm.objectID(as: "alias"))
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
let term = SelectTerm.ObjectID()
XCTAssertEqual(term, SelectTerm.ObjectID())
XCTAssertNotEqual(term, SelectTerm.ObjectID(As: "alias"))
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
switch term {
case ._identity(let alias, let nativeType):
case ._Identity(let alias, let nativeType):
XCTAssertEqual(alias, "objectID")
XCTAssertTrue(nativeType == .objectIDAttributeType)
XCTAssertTrue(nativeType == .ObjectIDAttributeType)
default:
XCTFail()
@@ -368,21 +368,21 @@ final class SelectTests: XCTestCase {
}
do {
let term = SelectTerm.objectID(as: "alias")
XCTAssertEqual(term, SelectTerm.objectID(as: "alias"))
XCTAssertNotEqual(term, SelectTerm.objectID(as: "alias2"))
XCTAssertNotEqual(term, SelectTerm.objectID())
XCTAssertNotEqual(term, SelectTerm.attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.average("attribute"))
XCTAssertNotEqual(term, SelectTerm.count("attribute"))
XCTAssertNotEqual(term, SelectTerm.maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.sum("attribute"))
let term = SelectTerm.ObjectID(As: "alias")
XCTAssertEqual(term, SelectTerm.ObjectID(As: "alias"))
XCTAssertNotEqual(term, SelectTerm.ObjectID(As: "alias2"))
XCTAssertNotEqual(term, SelectTerm.ObjectID())
XCTAssertNotEqual(term, SelectTerm.Attribute("attribute"))
XCTAssertNotEqual(term, SelectTerm.Average("attribute"))
XCTAssertNotEqual(term, SelectTerm.Count("attribute"))
XCTAssertNotEqual(term, SelectTerm.Maximum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Minimum("attribute"))
XCTAssertNotEqual(term, SelectTerm.Sum("attribute"))
switch term {
case ._identity(let alias, let nativeType):
case ._Identity(let alias, let nativeType):
XCTAssertEqual(alias, "alias")
XCTAssertTrue(nativeType == .objectIDAttributeType)
XCTAssertTrue(nativeType == .ObjectIDAttributeType)
default:
XCTFail()
@@ -393,9 +393,9 @@ final class SelectTests: XCTestCase {
@objc
dynamic func test_ThatSelectClauses_ConfigureCorrectly() {
let term1 = SelectTerm.attribute("attribute1")
let term2 = SelectTerm.attribute("attribute2")
let term3 = SelectTerm.attribute("attribute3")
let term1 = SelectTerm.Attribute("attribute1")
let term2 = SelectTerm.Attribute("attribute2")
let term3 = SelectTerm.Attribute("attribute3")
do {
let select = Select<Int>(term1, term2, term3)

View File

@@ -29,51 +29,43 @@ import CoreStore
// MARK: - SetupTests
class SetupTests: BaseTestDataTestCase {
class SetupTests: BaseTestCase {
@objc
dynamic func test_ThatDataStacks_ConfigureCorrectly() {
do {
let schemaHistory = SchemaHistory(
XcodeDataModelSchema.from(
modelName: "Model",
bundle: Bundle(for: type(of: self))
)
)
let stack = DataStack(schemaHistory: schemaHistory)
XCTAssertEqual(stack.coordinator.managedObjectModel, schemaHistory.rawModel)
let model = NSManagedObjectModel.mergedModelFromBundles([NSBundle(forClass: self.dynamicType)])!
let stack = DataStack(model: model, migrationChain: nil)
XCTAssertEqual(stack.coordinator.managedObjectModel, model)
XCTAssertEqual(stack.rootSavingContext.persistentStoreCoordinator, stack.coordinator)
XCTAssertNil(stack.rootSavingContext.parent)
XCTAssertFalse(stack.rootSavingContext.isDataStackContext)
XCTAssertFalse(stack.rootSavingContext.isTransactionContext)
XCTAssertEqual(stack.mainContext.parent, stack.rootSavingContext)
XCTAssertTrue(stack.mainContext.isDataStackContext)
XCTAssertFalse(stack.mainContext.isTransactionContext)
XCTAssertEqual(stack.schemaHistory.rawModel, schemaHistory.rawModel)
XCTAssertTrue(stack.schemaHistory.migrationChain.isValid)
XCTAssertTrue(stack.schemaHistory.migrationChain.isEmpty)
XCTAssertTrue(stack.schemaHistory.migrationChain.rootVersions.isEmpty)
XCTAssertTrue(stack.schemaHistory.migrationChain.leafVersions.isEmpty)
XCTAssertNil(stack.rootSavingContext.parentContext)
XCTAssertEqual(stack.mainContext.parentContext, stack.rootSavingContext)
XCTAssertEqual(stack.model, model)
XCTAssertTrue(stack.migrationChain.valid)
XCTAssertTrue(stack.migrationChain.empty)
XCTAssertTrue(stack.migrationChain.rootVersions.isEmpty)
XCTAssertTrue(stack.migrationChain.leafVersions.isEmpty)
CoreStore.defaultStack = stack
XCTAssertEqual(CoreStore.defaultStack, stack)
}
do {
let migrationChain: MigrationChain = ["version1", "version2", "version3", "Model"]
let migrationChain: MigrationChain = ["version1", "version2", "version3"]
let stack = self.expectLogger([.logWarning]) {
let stack = self.expectLogger([.LogWarning]) {
DataStack(
xcodeModelName: "Model",
bundle: Bundle(for: type(of: self)),
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType),
migrationChain: migrationChain
)
}
XCTAssertEqual(stack.modelVersion, "Model")
XCTAssertEqual(stack.schemaHistory.migrationChain, migrationChain)
XCTAssertEqual(stack.migrationChain, migrationChain)
CoreStore.defaultStack = stack
XCTAssertEqual(CoreStore.defaultStack, stack)
@@ -84,8 +76,8 @@ class SetupTests: BaseTestDataTestCase {
dynamic func test_ThatInMemoryStores_SetupCorrectly() {
let stack = DataStack(
xcodeModelName: "Model",
bundle: Bundle(for: type(of: self))
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType)
)
do {
@@ -139,8 +131,8 @@ class SetupTests: BaseTestDataTestCase {
dynamic func test_ThatSQLiteStores_SetupCorrectly() {
let stack = DataStack(
xcodeModelName: "Model",
bundle: Bundle(for: type(of: self))
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType)
)
do {
@@ -162,7 +154,7 @@ class SetupTests: BaseTestDataTestCase {
let sqliteStore = SQLiteStore(
fileName: "ConfigStore1.sqlite",
configuration: "Config1",
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
do {
@@ -181,7 +173,7 @@ class SetupTests: BaseTestDataTestCase {
let sqliteStore = SQLiteStore(
fileName: "ConfigStore2.sqlite",
configuration: "Config2",
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
do {
@@ -197,74 +189,16 @@ class SetupTests: BaseTestDataTestCase {
}
}
@objc
dynamic func test_ThatSQLiteStores_DeleteFilesCorrectly() {
let fileManager = FileManager.default
let sqliteStore = SQLiteStore()
func createStore() throws -> [String: Any] {
do {
let stack = DataStack(
xcodeModelName: "Model",
bundle: Bundle(for: type(of: self))
)
try! stack.addStorageAndWait(sqliteStore)
self.prepareTestDataForStack(stack)
}
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
return try NSPersistentStoreCoordinator.metadataForPersistentStore(
ofType: type(of: sqliteStore).storeType,
at: sqliteStore.fileURL,
options: sqliteStore.storeOptions
)
}
do {
let metadata = try createStore()
let stack = DataStack(
xcodeModelName: "Model",
bundle: Bundle(for: type(of: self))
)
try sqliteStore.cs_eraseStorageAndWait(
metadata: metadata,
soureModelHint: stack.schemaHistory.schema(for: metadata)?.rawModel()
)
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
}
catch {
XCTFail()
}
do {
let metadata = try createStore()
try sqliteStore.cs_eraseStorageAndWait(metadata: metadata, soureModelHint: nil)
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
}
catch {
XCTFail()
}
}
@objc
dynamic func test_ThatLegacySQLiteStores_SetupCorrectly() {
let stack = DataStack(
xcodeModelName: "Model",
bundle: Bundle(for: type(of: self))
modelName: "Model",
bundle: NSBundle(forClass: self.dynamicType)
)
do {
let sqliteStore = SQLiteStore.legacy()
let sqliteStore = SQLiteStore()
do {
try stack.addStorageAndWait(sqliteStore)
@@ -279,10 +213,10 @@ class SetupTests: BaseTestDataTestCase {
}
do {
let sqliteStore = SQLiteStore.legacy(
let sqliteStore = SQLiteStore(
fileName: "ConfigStore1.sqlite",
configuration: "Config1",
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
do {
@@ -298,10 +232,10 @@ class SetupTests: BaseTestDataTestCase {
}
do {
let sqliteStore = SQLiteStore.legacy(
let sqliteStore = SQLiteStore(
fileName: "ConfigStore2.sqlite",
configuration: "Config2",
localStorageOptions: .recreateStoreOnModelMismatch
localStorageOptions: .RecreateStoreOnModelMismatch
)
do {
@@ -316,62 +250,4 @@ class SetupTests: BaseTestDataTestCase {
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
}
}
@objc
dynamic func test_ThatLegacySQLiteStores_DeleteFilesCorrectly() {
let fileManager = FileManager.default
let sqliteStore = SQLiteStore.legacy()
func createStore() throws -> [String: Any] {
do {
let stack = DataStack(
xcodeModelName: "Model",
bundle: Bundle(for: type(of: self))
)
try! stack.addStorageAndWait(sqliteStore)
self.prepareTestDataForStack(stack)
}
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
return try NSPersistentStoreCoordinator.metadataForPersistentStore(
ofType: type(of: sqliteStore).storeType,
at: sqliteStore.fileURL,
options: sqliteStore.storeOptions
)
}
do {
let metadata = try createStore()
let stack = DataStack(
xcodeModelName: "Model",
bundle: Bundle(for: type(of: self))
)
try sqliteStore.cs_eraseStorageAndWait(
metadata: metadata,
soureModelHint: stack.schemaHistory.schema(for: metadata)?.rawModel()
)
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
}
catch {
XCTFail()
}
do {
let metadata = try createStore()
try sqliteStore.cs_eraseStorageAndWait(metadata: metadata, soureModelHint: nil)
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
}
catch {
XCTFail()
}
}
}

View File

@@ -37,7 +37,7 @@ final class StorageInterfaceTests: XCTestCase {
dynamic func test_ThatDefaultInMemoryStores_ConfigureCorrectly() {
let store = InMemoryStore()
XCTAssertEqual(type(of: store).storeType, NSInMemoryStoreType)
XCTAssertEqual(store.dynamicType.storeType, NSInMemoryStoreType)
XCTAssertNil(store.configuration)
XCTAssertNil(store.storeOptions)
}
@@ -46,7 +46,7 @@ final class StorageInterfaceTests: XCTestCase {
dynamic func test_ThatCustomInMemoryStores_ConfigureCorrectly() {
let store = InMemoryStore(configuration: "config1")
XCTAssertEqual(type(of: store).storeType, NSInMemoryStoreType)
XCTAssertEqual(store.dynamicType.storeType, NSInMemoryStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertNil(store.storeOptions)
}
@@ -55,23 +55,24 @@ final class StorageInterfaceTests: XCTestCase {
dynamic func test_ThatSQLiteStoreDefaultDirectories_AreCorrect() {
#if os(tvOS)
let systemDirectorySearchPath = FileManager.SearchPathDirectory.cachesDirectory
let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
#else
let systemDirectorySearchPath = FileManager.SearchPathDirectory.applicationSupportDirectory
let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
#endif
let defaultSystemDirectory = FileManager.default
.urls(for: systemDirectorySearchPath, in: .userDomainMask).first!
let defaultSystemDirectory = NSFileManager
.defaultManager()
.URLsForDirectory(systemDirectorySearchPath, inDomains: .UserDomainMask).first!
let defaultRootDirectory = defaultSystemDirectory.appendingPathComponent(
Bundle.main.bundleIdentifier ?? "com.CoreStore.DataStack",
let defaultRootDirectory = defaultSystemDirectory.URLByAppendingPathComponent(
NSBundle.mainBundle().bundleIdentifier ?? "com.CoreStore.DataStack",
isDirectory: true
)
let applicationName = (Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String) ?? "CoreData"
let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
let defaultFileURL = defaultRootDirectory
.appendingPathComponent(applicationName, isDirectory: false)
.appendingPathExtension("sqlite")
.URLByAppendingPathComponent(applicationName, isDirectory: false)
.URLByAppendingPathExtension("sqlite")
XCTAssertEqual(SQLiteStore.defaultRootDirectory, defaultRootDirectory)
XCTAssertEqual(SQLiteStore.defaultFileURL, defaultFileURL)
@@ -81,120 +82,137 @@ final class StorageInterfaceTests: XCTestCase {
dynamic func test_ThatDefaultSQLiteStores_ConfigureCorrectly() {
let store = SQLiteStore()
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertNil(store.configuration)
XCTAssertEqual(store.storeOptions as NSDictionary?, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL, SQLiteStore.defaultFileURL)
XCTAssertTrue(store.migrationMappingProviders.isEmpty)
XCTAssertEqual(store.localStorageOptions, .none)
XCTAssertEqual(store.mappingModelBundles, NSBundle.allBundles())
XCTAssertEqual(store.localStorageOptions, [.None])
}
@objc
dynamic func test_ThatFileURLSQLiteStores_ConfigureCorrectly() {
let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
.appendingPathComponent(NSUUID().uuidString, isDirectory: false)!
.appendingPathExtension("db")
let mappingProvider = XcodeSchemaMappingProvider(
from: "V1", to: "V2",
mappingModelBundle: Bundle(for: type(of: self))
)
.URLByAppendingPathComponent(NSUUID().UUIDString, isDirectory: false)
.URLByAppendingPathExtension("db")
let bundles = [NSBundle(forClass: self.dynamicType)]
let store = SQLiteStore(
fileURL: fileURL,
configuration: "config1",
migrationMappingProviders: [mappingProvider],
localStorageOptions: .recreateStoreOnModelMismatch
mappingModelBundles: bundles,
localStorageOptions: .RecreateStoreOnModelMismatch
)
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertEqual(store.storeOptions as NSDictionary?, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL, fileURL)
XCTAssertEqual(store.migrationMappingProviders as! [XcodeSchemaMappingProvider], [mappingProvider])
XCTAssertEqual(store.localStorageOptions, [.recreateStoreOnModelMismatch])
XCTAssertEqual(store.mappingModelBundles, bundles)
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
}
@objc
dynamic func test_ThatFileNameSQLiteStores_ConfigureCorrectly() {
let fileName = UUID().uuidString + ".db"
let mappingProvider = XcodeSchemaMappingProvider(
from: "V1", to: "V2",
mappingModelBundle: Bundle(for: type(of: self))
)
let fileName = NSUUID().UUIDString + ".db"
let bundles = [NSBundle(forClass: self.dynamicType)]
let store = SQLiteStore(
fileName: fileName,
configuration: "config1",
migrationMappingProviders: [mappingProvider],
localStorageOptions: .recreateStoreOnModelMismatch
mappingModelBundles: bundles,
localStorageOptions: .RecreateStoreOnModelMismatch
)
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertEqual(store.storeOptions as NSDictionary?, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.defaultRootDirectory)
XCTAssertEqual(store.fileURL.URLByDeletingLastPathComponent, SQLiteStore.defaultRootDirectory)
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
XCTAssertEqual(store.migrationMappingProviders as! [XcodeSchemaMappingProvider], [mappingProvider])
XCTAssertEqual(store.localStorageOptions, [.recreateStoreOnModelMismatch])
XCTAssertEqual(store.mappingModelBundles, bundles)
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
}
@objc
dynamic func test_ThatLegacySQLiteStoreDefaultDirectories_AreCorrect() {
#if os(tvOS)
let systemDirectorySearchPath = FileManager.SearchPathDirectory.cachesDirectory
let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
#else
let systemDirectorySearchPath = FileManager.SearchPathDirectory.applicationSupportDirectory
let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
#endif
let legacyDefaultRootDirectory = FileManager.default.urls(
for: systemDirectorySearchPath,
in: .userDomainMask).first!
let legacyDefaultRootDirectory = NSFileManager.defaultManager().URLsForDirectory(
systemDirectorySearchPath,
inDomains: .UserDomainMask
).first!
let legacyDefaultFileURL = legacyDefaultRootDirectory
.appendingPathComponent(DataStack.applicationName, isDirectory: false)
.appendingPathExtension("sqlite")
.URLByAppendingPathComponent(DataStack.applicationName, isDirectory: false)
.URLByAppendingPathExtension("sqlite")
XCTAssertEqual(SQLiteStore.legacyDefaultRootDirectory, legacyDefaultRootDirectory)
XCTAssertEqual(SQLiteStore.legacyDefaultFileURL, legacyDefaultFileURL)
XCTAssertEqual(LegacySQLiteStore.defaultRootDirectory, legacyDefaultRootDirectory)
XCTAssertEqual(LegacySQLiteStore.defaultFileURL, legacyDefaultFileURL)
}
@objc
dynamic func test_ThatDefaultLegacySQLiteStores_ConfigureCorrectly() {
let store = SQLiteStore.legacy()
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
let store = LegacySQLiteStore()
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertNil(store.configuration)
XCTAssertEqual(store.storeOptions as NSDictionary?, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL, SQLiteStore.legacyDefaultFileURL)
XCTAssertTrue(store.migrationMappingProviders.isEmpty)
XCTAssertEqual(store.localStorageOptions, .none)
XCTAssertEqual(store.fileURL, LegacySQLiteStore.defaultFileURL)
XCTAssertEqual(store.mappingModelBundles, NSBundle.allBundles())
XCTAssertEqual(store.localStorageOptions, [.None])
}
@objc
dynamic func test_ThatFileURLLegacySQLiteStores_ConfigureCorrectly() {
let fileURL = NSURL(fileURLWithPath: NSTemporaryDirectory())
.URLByAppendingPathComponent(NSUUID().UUIDString, isDirectory: false)
.URLByAppendingPathExtension("db")
let bundles = [NSBundle(forClass: self.dynamicType)]
let store = LegacySQLiteStore(
fileURL: fileURL,
configuration: "config1",
mappingModelBundles: bundles,
localStorageOptions: .RecreateStoreOnModelMismatch
)
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL, fileURL)
XCTAssertEqual(store.mappingModelBundles, bundles)
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
}
@objc
dynamic func test_ThatFileNameLegacySQLiteStores_ConfigureCorrectly() {
let fileName = UUID().uuidString + ".db"
let mappingProvider = XcodeSchemaMappingProvider(
from: "V1", to: "V2",
mappingModelBundle: Bundle(for: type(of: self))
)
let store = SQLiteStore.legacy(
let fileName = NSUUID().UUIDString + ".db"
let bundles = [NSBundle(forClass: self.dynamicType)]
let store = LegacySQLiteStore(
fileName: fileName,
configuration: "config1",
migrationMappingProviders: [mappingProvider],
localStorageOptions: .recreateStoreOnModelMismatch
mappingModelBundles: bundles,
localStorageOptions: .RecreateStoreOnModelMismatch
)
XCTAssertEqual(type(of: store).storeType, NSSQLiteStoreType)
XCTAssertEqual(store.dynamicType.storeType, NSSQLiteStoreType)
XCTAssertEqual(store.configuration, "config1")
XCTAssertEqual(store.storeOptions as NSDictionary?, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.storeOptions, [NSSQLitePragmasOption: ["journal_mode": "WAL"]] as NSDictionary)
XCTAssertEqual(store.fileURL.deletingLastPathComponent(), SQLiteStore.legacyDefaultRootDirectory)
XCTAssertEqual(store.fileURL.URLByDeletingLastPathComponent, LegacySQLiteStore.defaultRootDirectory)
XCTAssertEqual(store.fileURL.lastPathComponent, fileName)
XCTAssertEqual(store.migrationMappingProviders as! [XcodeSchemaMappingProvider], [mappingProvider])
XCTAssertEqual(store.localStorageOptions, [.recreateStoreOnModelMismatch])
XCTAssertEqual(store.mappingModelBundles, bundles)
XCTAssertEqual(store.localStorageOptions, [.RecreateStoreOnModelMismatch])
}
}

View File

@@ -31,9 +31,9 @@ class TestEntity1: NSManagedObject {
@NSManaged var testEntityID: NSNumber?
@NSManaged var testString: String?
@NSManaged var testNumber: NSNumber?
@NSManaged var testDate: Date?
@NSManaged var testDate: NSDate?
@NSManaged var testBoolean: NSNumber?
@NSManaged var testDecimal: NSDecimalNumber?
@NSManaged var testData: Data?
@NSManaged var testData: NSData?
@NSManaged var testNil: String?
}

View File

@@ -31,9 +31,9 @@ class TestEntity2: NSManagedObject {
@NSManaged var testEntityID: NSNumber?
@NSManaged var testString: String?
@NSManaged var testNumber: NSNumber?
@NSManaged var testDate: Date?
@NSManaged var testDate: NSDate?
@NSManaged var testBoolean: NSNumber?
@NSManaged var testDecimal: NSDecimalNumber?
@NSManaged var testData: Data?
@NSManaged var testData: NSData?
@NSManaged var testNil: String?
}

File diff suppressed because it is too large Load Diff

View File

@@ -43,7 +43,7 @@ final class TweakTests: XCTestCase {
$0.fetchLimit = 200
$0.predicate = predicate
}
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
tweak.applyToFetchRequest(request)
XCTAssertEqual(request.fetchOffset, 100)
XCTAssertEqual(request.fetchLimit, 200)

View File

@@ -29,25 +29,6 @@ import XCTest
import CoreStore
// MARK: - XCTAssertAllEqual
private func XCTAssertAllEqual(_ whereClauses: Where...) {
XCTAssertAllEqual(whereClauses)
}
private func XCTAssertAllEqual(_ whereClauses: [Where]) {
for i in whereClauses.indices {
for j in whereClauses.indices where j != i {
XCTAssertEqual(whereClauses[i], whereClauses[j])
}
}
}
//MARK: - WhereTests
final class WhereTests: XCTestCase {
@@ -106,123 +87,6 @@ final class WhereTests: XCTestCase {
}
}
@objc
dynamic func test_ThatWhereClauses_BridgeArgumentsCorrectly() {
do {
let value: Int = 100
XCTAssertAllEqual(
Where("%K == %d", "key", value),
Where("%K == %d", "key", value as AnyObject),
Where("%K == %d", "key", NSNumber(value: value)),
Where("%K == %@", "key", value),
Where("%K == %@", "key", value as AnyObject),
Where("%K == %@", "key", NSNumber(value: value)),
Where("key", isEqualTo: value),
Where("key", isEqualTo: NSNumber(value: value))
)
}
do {
let value = NSNumber(value: 100)
XCTAssertAllEqual(
Where("%K == %d", "key", value),
Where("%K == %d", "key", value as AnyObject),
Where("%K == %d", "key", value.intValue),
Where("%K == %@", "key", value),
Where("%K == %@", "key", value as AnyObject),
Where("%K == %@", "key", value.intValue),
Where("key", isEqualTo: value),
Where("key", isEqualTo: value.intValue)
)
}
do {
let value: Int64 = Int64.max
XCTAssertAllEqual(
Where("%K == %d", "key", value),
Where("%K == %d", "key", value as AnyObject),
Where("%K == %d", "key", NSNumber(value: value)),
Where("%K == %@", "key", value),
Where("%K == %@", "key", value as AnyObject),
Where("%K == %@", "key", NSNumber(value: value)),
Where("key", isEqualTo: value),
Where("key", isEqualTo: NSNumber(value: value))
)
}
do {
let value = NSNumber(value: Int64.max)
XCTAssertAllEqual(
Where("%K == %d", "key", value),
Where("%K == %d", "key", value as AnyObject),
Where("%K == %d", "key", value.int64Value),
Where("%K == %@", "key", value),
Where("%K == %@", "key", value as AnyObject),
Where("%K == %@", "key", value.int64Value),
Where("key", isEqualTo: value),
Where("key", isEqualTo: value.int64Value)
)
}
do {
let value: String = "value"
XCTAssertAllEqual(
Where("%K == %s", "key", value),
Where("%K == %s", "key", value as AnyObject),
Where("%K == %s", "key", NSString(string: value)),
Where("%K == %@", "key", value),
Where("%K == %@", "key", value as AnyObject),
Where("%K == %@", "key", NSString(string: value)),
Where("key", isEqualTo: value),
Where("key", isEqualTo: value as NSString),
Where("key", isEqualTo: NSString(string: value))
)
}
do {
let value = NSString(string: "value")
XCTAssertAllEqual(
Where("%K == %s", "key", value),
Where("%K == %s", "key", value as String),
Where("%K == %s", "key", value as String as AnyObject),
Where("%K == %@", "key", value),
Where("%K == %@", "key", value as String),
Where("%K == %@", "key", value as String as AnyObject),
Where("key", isEqualTo: value),
Where("key", isEqualTo: value as String),
Where("key", isEqualTo: value as String as NSString)
)
}
do {
let value: [Int] = [100, 200]
XCTAssertAllEqual(
Where("%K IN %@", "key", value),
Where("%K IN %@", "key", value as AnyObject),
Where("%K IN %@", "key", value as [AnyObject]),
Where("%K IN %@", "key", value as NSArray),
Where("%K IN %@", "key", NSArray(array: value)),
Where("%K IN %@", "key", value as AnyObject as! NSArray),
Where("key", isMemberOf: value)
)
}
do {
let value: [Int64] = [Int64.min, 100, Int64.max]
XCTAssertAllEqual(
Where("%K IN %@", "key", value),
Where("%K IN %@", "key", value as AnyObject),
Where("%K IN %@", "key", value as [AnyObject]),
Where("%K IN %@", "key", value as NSArray),
Where("%K IN %@", "key", NSArray(array: value)),
Where("%K IN %@", "key", value as AnyObject as! NSArray),
Where("key", isMemberOf: value)
)
}
}
@objc
dynamic func test_ThatWhereClauseOperations_ComputeCorrectly() {
@@ -234,7 +98,7 @@ final class WhereTests: XCTestCase {
let notWhere = !whereClause1
let notPredicate = NSCompoundPredicate(
type: .not,
type: .NotPredicateType,
subpredicates: [whereClause1.predicate]
)
XCTAssertEqual(notWhere.predicate, notPredicate)
@@ -244,10 +108,10 @@ final class WhereTests: XCTestCase {
let andWhere = whereClause1 && whereClause2 && whereClause3
let andPredicate = NSCompoundPredicate(
type: .and,
type: .AndPredicateType,
subpredicates: [
NSCompoundPredicate(
type: .and,
type: .AndPredicateType,
subpredicates: [whereClause1.predicate, whereClause2.predicate]
),
whereClause3.predicate
@@ -260,10 +124,10 @@ final class WhereTests: XCTestCase {
let orWhere = whereClause1 || whereClause2 || whereClause3
let orPredicate = NSCompoundPredicate(
type: .or,
type: .OrPredicateType,
subpredicates: [
NSCompoundPredicate(
type: .or,
type: .OrPredicateType,
subpredicates: [whereClause1.predicate, whereClause2.predicate]
),
whereClause3.predicate
@@ -278,7 +142,7 @@ final class WhereTests: XCTestCase {
dynamic func test_ThatWhereClauses_ApplyToFetchRequestsCorrectly() {
let whereClause = Where("key", isEqualTo: "value")
let request = CoreStoreFetchRequest()
let request = NSFetchRequest()
whereClause.applyToFetchRequest(request)
XCTAssertNotNil(request.predicate)
XCTAssertEqual(request.predicate, whereClause.predicate)

View File

@@ -41,5 +41,11 @@ targets = []
let package = Package(
name: "CoreStore",
targets: targets,
dependencies: [
.Package(
url: "https://github.com/JohnEstropia/GCDKit.git",
"1.2.6"
)
],
exclude: ["Carthage", "CoreStoreDemo", "Sources/libA/images"]
)

1111
README.md

File diff suppressed because it is too large Load Diff

View File

@@ -1,274 +0,0 @@
//
// AsynchronousDataTransaction.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// 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:...)`.
*/
public final class AsynchronousDataTransaction: BaseDataTransaction {
/**
Cancels a transaction by throwing `CoreStoreError.userCancelled`.
```
try transaction.cancel()
```
- Important: Never use `try?` or `try!` on a `cancel()` call. Always use `try`. Using `try?` will swallow the cancellation and the transaction will proceed to commit as normal. Using `try!` will crash the app as `cancel()` will *always* throw an error.
*/
public func cancel() throws -> Never {
throw CoreStoreError.userCancelled
}
// MARK: - Result
/**
The `Result` contains the success or failure information for a completed transaction
*/
public enum Result<T> {
/**
`Result<T>.success` indicates that the transaction succeeded, either because the save succeeded or because there were no changes to save. The associated `userInfo` is the value returned from the transaction closure.
*/
case success(userInfo: T)
/**
`Result<T>.failure` indicates that the transaction either failed or was cancelled. The associated object for this value is a `CoreStoreError` enum value.
*/
case failure(error: CoreStoreError)
/**
Returns `true` if the result indicates `.success`, `false` if the result is `.failure`.
*/
public var boolValue: Bool {
switch self {
case .success: return true
case .failure: return false
}
}
// MARK: Internal
internal init(userInfo: T) {
self = .success(userInfo: userInfo)
}
internal init(error: CoreStoreError) {
self = .failure(error: error)
}
}
// MARK: -
// MARK: BaseDataTransaction
/**
Creates a new `NSManagedObject` or `CoreStoreObject` with the specified entity type.
- 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<T: DynamicObject>(_ into: Into<T>) -> T {
CoreStore.assert(
!self.isCommitted,
"Attempted to create an entity of type \(cs_typeName(into.entityClass)) from an already committed \(cs_typeName(self))."
)
return super.create(into)
}
/**
Returns an editable proxy of a specified `NSManagedObject` or `CoreStoreObject`.
- parameter object: the `NSManagedObject` or `CoreStoreObject` to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
public override func edit<T: DynamicObject>(_ object: T?) -> T? {
CoreStore.assert(
!self.isCommitted,
"Attempted to update an entity of type \(cs_typeName(object)) from an already committed \(cs_typeName(self))."
)
return super.edit(object)
}
/**
Returns an editable proxy of the object with the specified `NSManagedObjectID`.
- parameter into: an `Into` clause specifying the entity type
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject` or `CoreStoreObject`.
*/
public override func edit<T: DynamicObject>(_ into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
CoreStore.assert(
!self.isCommitted,
"Attempted to update an entity of type \(cs_typeName(into.entityClass)) from an already committed \(cs_typeName(self))."
)
return super.edit(into, objectID)
}
/**
Deletes a specified `NSManagedObject` or `CoreStoreObject`.
- parameter object: the `NSManagedObject` or `CoreStoreObject` to be deleted
*/
public override func delete<T: DynamicObject>(_ object: T?) {
CoreStore.assert(
!self.isCommitted,
"Attempted to delete an entity of type \(cs_typeName(object)) from an already committed \(cs_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<T: DynamicObject>(_ object1: T?, _ object2: T?, _ objects: T?...) {
CoreStore.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(cs_typeName(self))."
)
super.delete(([object1, object2] + objects).flatMap { $0 })
}
/**
Deletes the specified `DynamicObject`s.
- parameter objects: the `DynamicObject`s to be deleted
*/
public override func delete<S: Sequence>(_ objects: S) where S.Iterator.Element: DynamicObject {
CoreStore.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(cs_typeName(self))."
)
super.delete(objects)
}
// MARK: Internal
internal init(mainContext: NSManagedObjectContext, queue: DispatchQueue) {
super.init(mainContext: mainContext, queue: queue, supportsUndo: false, bypassesQueueing: false)
}
internal func autoCommit(_ completion: @escaping (_ hasChanges: Bool, _ error: CoreStoreError?) -> Void) {
self.isCommitted = true
let group = DispatchGroup()
group.enter()
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
completion(result.0, result.1)
self.result = result
group.leave()
}
group.wait()
}
// MARK: Deprecated
@available(*, deprecated, message: "Use the new auto-commiting methods `DataStack.perform(asynchronous:completion:)` or `DataStack.perform(asynchronous:success:failure:)`. Please read the documentation on the behavior of the new methods.")
public func commit(_ completion: @escaping (_ result: SaveResult) -> Void = { _ in }) {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
)
self.autoCommit { (result) in
switch result {
case (let hasChanges, nil): completion(SaveResult(hasChanges: hasChanges))
case (_, let error?): completion(SaveResult(error))
}
}
}
@available(*, deprecated, message: "Secondary tasks spawned from AsynchronousDataTransactions and SynchronousDataTransactions are no longer supported. ")
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
CoreStore.assert(
self.transactionQueue.cs_isCurrentExecutionContext(),
"Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to begin a child transaction from an already committed \(cs_typeName(self))."
)
let childTransaction = SynchronousDataTransaction(
mainContext: self.context,
queue: self.childTransactionQueue
)
childTransaction.transactionQueue.cs_sync {
closure(childTransaction)
if !childTransaction.isCommitted && childTransaction.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(childTransaction)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
switch childTransaction.result {
case nil: return nil
case (let hasChanges, nil)?: return SaveResult(hasChanges: hasChanges)
case (_, let error?)?: return SaveResult(error)
}
}
}

View File

@@ -1,51 +0,0 @@
//
// CSDynamicSchema.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - CSDynamicSchema
/**
The `CSDynamicSchema` serves as the Objective-C bridging type for `DynamicSchema`.
- SeeAlso: `DynamicSchema`
*/
@objc
public protocol CSDynamicSchema {
/**
The version string for this model schema.
*/
@objc
var modelVersion: ModelVersion { get }
/**
Do not call this directly. The `NSManagedObjectModel` for this schema may be created lazily and using this method directly may affect the integrity of the model.
*/
@objc
func rawModel() -> NSManagedObjectModel
}

View File

@@ -1,147 +0,0 @@
//
// CSSaveResult.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CSSaveResult
@available(*, deprecated, message: "Use APIs that report failures with `CSError`s instead.")
@objc
public final class CSSaveResult: NSObject, CoreStoreObjectiveCType {
@objc
public var isSuccess: Bool {
return self.bridgeToSwift.boolValue
}
@objc
public var isFailure: Bool {
return !self.bridgeToSwift.boolValue
}
@objc
public var hasChanges: Bool {
guard case .success(let hasChanges) = self.bridgeToSwift else {
return false
}
return hasChanges
}
@objc
public var error: NSError? {
guard case .failure(let error) = self.bridgeToSwift else {
return nil
}
return error.bridgeToObjectiveC
}
@objc
public func handleSuccess(_ success: (_ hasChanges: Bool) -> Void, failure: (_ error: NSError) -> Void) {
switch self.bridgeToSwift {
case .success(let hasChanges):
success(hasChanges)
case .failure(let error):
failure(error.bridgeToObjectiveC)
}
}
@objc
public func handleSuccess(_ success: (_ hasChanges: Bool) -> Void) {
guard case .success(let hasChanges) = self.bridgeToSwift else {
return
}
success(hasChanges)
}
@objc
public func handleFailure(_ failure: (_ error: NSError) -> Void) {
guard case .failure(let error) = self.bridgeToSwift else {
return
}
failure(error.bridgeToObjectiveC)
}
// MARK: NSObject
public override var hash: Int {
return self.bridgeToSwift.hashValue
}
public override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? CSSaveResult else {
return false
}
return self.bridgeToSwift == object.bridgeToSwift
}
public override var description: String {
return "(\(String(reflecting: type(of: self)))) \(self.bridgeToSwift.coreStoreDumpString)"
}
// MARK: CoreStoreObjectiveCType
public let bridgeToSwift: SaveResult
public required init(_ swiftValue: SaveResult) {
self.bridgeToSwift = swiftValue
super.init()
}
}
// MARK: - SaveResult
@available(*, deprecated, message: "Use the new DataStack.perform(asynchronous:...) and DataStack.perform(synchronous:...) family of APIs")
extension SaveResult: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType
public var bridgeToObjectiveC: CSSaveResult {
return CSSaveResult(self)
}
}

View File

@@ -1,115 +0,0 @@
//
// CSUnsafeDataModelSchema.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - CSUnsafeDataModelSchema
/**
The `CSUnsafeDataModelSchema` serves as the Objective-C bridging type for `UnsafeDataModelSchema`.
- SeeAlso: `UnsafeDataModelSchema`
*/
@objc
public final class CSUnsafeDataModelSchema: NSObject, CSDynamicSchema, CoreStoreObjectiveCType {
/**
Initializes a `CSUnsafeDataModelSchema` from an `NSManagedObjectModel`.
- parameter modelName: the model version, typically the file name of an *.xcdatamodeld file (without the file extension)
- parameter model: the `NSManagedObjectModel`
*/
@objc
public required init(modelName: ModelVersion, model: NSManagedObjectModel) {
self.bridgeToSwift = UnsafeDataModelSchema(
modelName: modelName,
model: model
)
}
// MARK: NSObject
public override var hash: Int {
return ObjectIdentifier(self.bridgeToSwift).hashValue
}
public override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? CSUnsafeDataModelSchema else {
return false
}
return self.bridgeToSwift === object.bridgeToSwift
}
public override var description: String {
return "(\(String(reflecting: type(of: self)))) \(self.bridgeToSwift.coreStoreDumpString)"
}
// MARK: CSDynamicSchema
@objc
public var modelVersion: ModelVersion {
return self.bridgeToSwift.modelVersion
}
@objc
public func rawModel() -> NSManagedObjectModel {
return self.bridgeToSwift.rawModel()
}
// MARK: CoreStoreObjectiveCType
public let bridgeToSwift: UnsafeDataModelSchema
public required init(_ swiftValue: UnsafeDataModelSchema) {
self.bridgeToSwift = swiftValue
super.init()
}
}
// MARK: - UnsafeDataModelSchema
extension UnsafeDataModelSchema: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType
public var bridgeToObjectiveC: CSUnsafeDataModelSchema {
return CSUnsafeDataModelSchema(self)
}
}

View File

@@ -1,115 +0,0 @@
//
// CSXcodeDataModelSchema.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - CSXcodeDataModelSchema
/**
The `CSXcodeDataModelSchema` serves as the Objective-C bridging type for `XcodeDataModelSchema`.
- SeeAlso: `XcodeDataModelSchema`
*/
@objc
public final class CSXcodeDataModelSchema: NSObject, CSDynamicSchema, CoreStoreObjectiveCType {
/**
Initializes an `CSXcodeDataModelSchema` from an *.xcdatamodeld file URL.
- parameter modelName: the model version, typically the file name of an *.xcdatamodeld file (without the file extension)
- parameter modelVersionFileURL: the file URL that points to the .xcdatamodeld's "momd" file.
*/
@objc
public required init(modelName: ModelVersion, modelVersionFileURL: URL) {
self.bridgeToSwift = XcodeDataModelSchema(
modelName: modelName,
modelVersionFileURL: modelVersionFileURL
)
}
// MARK: NSObject
public override var hash: Int {
return ObjectIdentifier(self.bridgeToSwift).hashValue
}
public override func isEqual(_ object: Any?) -> Bool {
guard let object = object as? CSXcodeDataModelSchema else {
return false
}
return self.bridgeToSwift === object.bridgeToSwift
}
public override var description: String {
return "(\(String(reflecting: type(of: self)))) \(self.bridgeToSwift.coreStoreDumpString)"
}
// MARK: CSDynamicSchema
@objc
public var modelVersion: ModelVersion {
return self.bridgeToSwift.modelVersion
}
@objc
public func rawModel() -> NSManagedObjectModel {
return self.bridgeToSwift.rawModel()
}
// MARK: CoreStoreObjectiveCType
public let bridgeToSwift: XcodeDataModelSchema
public required init(_ swiftValue: XcodeDataModelSchema) {
self.bridgeToSwift = swiftValue
super.init()
}
}
// MARK: - XcodeDataModelSchema
extension XcodeDataModelSchema: CoreStoreSwiftType {
// MARK: CoreStoreSwiftType
public var bridgeToObjectiveC: CSXcodeDataModelSchema {
return CSXcodeDataModelSchema(self)
}
}

View File

@@ -0,0 +1,223 @@
//
// NSManagedObject+Convenience.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - NSFetchedResultsController
public extension NSFetchedResultsController {
/**
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter dataStack: the `DataStack` to observe objects from
- 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: an `NSFetchedResultsController` that observes a `DataStack`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
return self.createFromContext(
dataStack.mainContext,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter dataStack: the `DataStack` to observe objects from
- 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: an `NSFetchedResultsController` that observes a `DataStack`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return self.createFromContext(
dataStack.mainContext,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter dataStack: the `DataStack` to observe objects from
- 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: an `NSFetchedResultsController` that observes a `DataStack`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
return self.createFromContext(
dataStack.mainContext,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: nil,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from a `DataStack`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter dataStack: the `DataStack` to observe objects from
- 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: an `NSFetchedResultsController` that observes a `DataStack`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(dataStack: DataStack, _ from: From<T>, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return self.createFromContext(
dataStack.mainContext,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: nil,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
- 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: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
return self.createFromContext(
transaction.context,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
- 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: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return self.createFromContext(
transaction.context,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: sectionBy,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
- 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: an `NSFetchedResultsController` that observes an `UnsafeDataTransaction`
*/
@nonobjc
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ fetchClauses: FetchClause...) -> NSFetchedResultsController {
return self.createFromContext(
transaction.context,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: nil,
fetchClauses: fetchClauses
)
}
/**
Utility for creating an `NSFetchedResultsController` from an `UnsafeDataTransaction`. This is useful when an `NSFetchedResultsController` is preferred over the overhead of `ListMonitor`s abstraction.
- parameter transaction: the `UnsafeDataTransaction` to observe objects from
- 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.
*/
@nonobjc
public static func createFor<T: NSManagedObject>(transaction: UnsafeDataTransaction, _ from: From<T>, _ fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return self.createFromContext(
transaction.context,
fetchRequest: CoreStoreFetchRequest(),
from: from,
sectionBy: nil,
fetchClauses: fetchClauses
)
}
// MARK: Internal
@nonobjc
internal static func createFromContext<T: NSManagedObject>(context: NSManagedObjectContext, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) -> NSFetchedResultsController {
return CoreStoreFetchedResultsController(
context: context,
fetchRequest: fetchRequest,
from: from,
sectionBy: sectionBy,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
CoreStore.assert(
fetchRequest.sortDescriptors?.isEmpty == false,
"An \(cs_typeName(NSFetchedResultsController)) requires a sort information. Specify from a \(cs_typeName(OrderBy)) clause or any custom \(cs_typeName(FetchClause)) that provides a sort descriptor."
)
}
)
}
}
#endif

View File

@@ -0,0 +1,82 @@
//
// NSManagedObject+Convenience.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - NSManagedObject
public extension NSManagedObject {
/**
Provides a convenience wrapper for accessing `primitiveValueForKey(...)` with proper calls to `willAccessValueForKey(...)` and `didAccessValueForKey(...)`. This is useful when implementing accessor methods for transient attributes.
- parameter KVCKey: the KVC key
- returns: the primitive value for the KVC key
*/
@nonobjc
@warn_unused_result
public func accessValueForKVCKey(KVCKey: KeyPath) -> AnyObject? {
self.willAccessValueForKey(KVCKey)
let primitiveValue: AnyObject? = self.primitiveValueForKey(KVCKey)
self.didAccessValueForKey(KVCKey)
return primitiveValue
}
/**
Provides a convenience wrapper for setting `setPrimitiveValue(...)` with proper calls to `willChangeValueForKey(...)` and `didChangeValueForKey(...)`. This is useful when implementing mutator methods for transient attributes.
- parameter value: the value to set the KVC key with
- parameter KVCKey: the KVC key
*/
@nonobjc
public func setValue(value: AnyObject?, forKVCKey KVCKey: KeyPath) {
self.willChangeValueForKey(KVCKey)
self.setPrimitiveValue(value, forKey: KVCKey)
self.didChangeValueForKey(KVCKey)
}
/**
Re-faults the object to use the latest values from the persistent store
*/
@nonobjc
public func refreshAsFault() {
self.managedObjectContext?.refreshObject(self, mergeChanges: false)
}
/**
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
*/
@nonobjc
public func refreshAndMerge() {
self.managedObjectContext?.refreshObject(self, mergeChanges: true)
}
}

View File

@@ -1,5 +1,5 @@
//
// Progress+Convenience.swift
// NSProgress+Convenience.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
@@ -24,19 +24,22 @@
//
import Foundation
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - Progress
// MARK: - NSProgress
public extension Progress {
public extension NSProgress {
/**
Sets a closure that the `Progress` calls whenever its `fractionCompleted` changes. You can use this instead of setting up KVO.
Sets a closure that the `NSProgress` calls whenever its `fractionCompleted` changes. You can use this instead of setting up KVO.
- parameter closure: the closure to execute on progress change
*/
@nonobjc
public func setProgressHandler(_ closure: ((_ progress: Progress) -> Void)?) {
public func setProgressHandler(closure: ((progress: NSProgress) -> Void)?) {
self.progressObserver.progressHandler = closure
}
@@ -78,9 +81,8 @@ public extension Progress {
@objc
private final class ProgressObserver: NSObject {
private unowned let progress: Progress
fileprivate var progressHandler: ((_ progress: Progress) -> Void)? {
private unowned let progress: NSProgress
private var progressHandler: ((progress: NSProgress) -> Void)? {
didSet {
@@ -94,19 +96,19 @@ private final class ProgressObserver: NSObject {
self.progress.addObserver(
self,
forKeyPath: #keyPath(Progress.fractionCompleted),
options: [.initial, .new],
forKeyPath: "fractionCompleted",
options: [.Initial, .New],
context: nil
)
}
else {
self.progress.removeObserver(self, forKeyPath: #keyPath(Progress.fractionCompleted))
self.progress.removeObserver(self, forKeyPath: "fractionCompleted")
}
}
}
fileprivate init(_ progress: Progress) {
private init(_ progress: NSProgress) {
self.progress = progress
super.init()
@@ -117,22 +119,20 @@ private final class ProgressObserver: NSObject {
if let _ = self.progressHandler {
self.progressHandler = nil
self.progress.removeObserver(self, forKeyPath: #keyPath(Progress.fractionCompleted))
self.progress.removeObserver(self, forKeyPath: "fractionCompleted")
}
}
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
guard let progress = object as? Progress,
progress == self.progress,
keyPath == #keyPath(Progress.fractionCompleted) else {
return
guard let progress = object as? NSProgress where progress == self.progress && keyPath == "fractionCompleted" else {
return
}
DispatchQueue.main.async { [weak self] () -> Void in
GCDQueue.Main.async { [weak self] () -> Void in
self?.progressHandler?(progress)
self?.progressHandler?(progress: progress)
}
}
}

View File

@@ -1,81 +0,0 @@
//
// CoreDataNativeType.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreDataNativeType
/**
Objective-C Foundation types that are natively supported by Core Data managed attributes all conform to `CoreDataNativeType`.
*/
@objc
public protocol CoreDataNativeType: class, NSObjectProtocol, AnyObject {}
// MARK: - NSNumber
extension NSNumber: CoreDataNativeType {}
// MARK: - NSString
extension NSString: CoreDataNativeType {}
// MARK: - NSDate
extension NSDate: CoreDataNativeType {}
// MARK: - NSData
extension NSData: CoreDataNativeType {}
// MARK: - NSSet
extension NSSet: CoreDataNativeType {}
// MARK: - NSOrderedSet
extension NSOrderedSet: CoreDataNativeType {}
// MARK: - NSManagedObject
extension NSManagedObject: CoreDataNativeType {}
// MARK: - NSManagedObjectID
extension NSManagedObjectID: CoreDataNativeType {}
// MARK: - NSNull
extension NSNull: CoreDataNativeType {}

View File

@@ -1,133 +0,0 @@
//
// CoreStore+Migration.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStore
public extension CoreStore {
/**
Asynchronously adds a `StorageInterface` to the `defaultStack`. Migrations are also initiated by default.
```
CoreStore.addStorage(
InMemoryStore(configuration: "Config1"),
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storage: the 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 `StorageInterface` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
*/
public static func addStorage<T: StorageInterface>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) {
self.defaultStack.addStorage(storage, completion: completion)
}
/**
Asynchronously adds a `LocalStorage` to the `defaultStack`. Migrations are also initiated by default.
```
let migrationProgress = CoreStore.addStorage(
SQLiteStore(fileName: "core_data.sqlite", configuration: "Config1"),
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storage: the local 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 `LocalStorage` associated to the `SetupResult.success` 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: a `Progress` instance if a migration has started, or `nil` if either no migrations are required or if a failure occured.
*/
public static func addStorage<T: LocalStorage>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) -> Progress? {
return self.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) {
self.defaultStack.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.
- 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.
- throws: a `CoreStoreError` value indicating the failure
- returns: a `Progress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func upgradeStorageIfNeeded<T: LocalStorage>(_ storage: T, completion: @escaping (MigrationResult) -> Void) throws -> Progress? {
return try self.defaultStack.upgradeStorageIfNeeded(storage, completion: completion)
}
/**
Checks the migration steps required for the storage to match the `defaultStack`'s managed object model version.
- parameter storage: the local storage
- throws: a `CoreStoreError` value indicating the failure
- returns: a `MigrationType` array indicating the migration steps required for the store, or an empty array if the file does not exist yet. Otherwise, an error is thrown if either inspection of the store failed, or if no mapping model was found/inferred.
*/
public static func requiredMigrationsForStorage<T: LocalStorage>(_ storage: T) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForStorage(storage)
}
}

View File

@@ -1,160 +0,0 @@
//
// CoreStore+Setup.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStore
public 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`.
*/
public static var modelVersion: String {
return self.defaultStack.modelVersion
}
/**
Returns the entity name-to-class type mapping from the `defaultStack`'s model.
*/
public static func entityTypesByName(for type: NSManagedObject.Type) -> [EntityName: NSManagedObject.Type] {
return self.defaultStack.entityTypesByName(for: type)
}
/**
Returns the entity name-to-class type mapping from the `defaultStack`'s model.
*/
public static func entityTypesByName(for type: CoreStoreObject.Type) -> [EntityName: CoreStoreObject.Type] {
return self.defaultStack.entityTypesByName(for: type)
}
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `defaultStack`'s model.
*/
public static func entityDescription(for type: NSManagedObject.Type) -> NSEntityDescription? {
return self.defaultStack.entityDescription(for: type)
}
/**
Returns the `NSEntityDescription` for the specified `CoreStoreObject` subclass from `defaultStack`'s model.
*/
public static func entityDescription(for type: CoreStoreObject.Type) -> NSEntityDescription? {
return self.defaultStack.entityDescription(for: type)
}
/**
Creates an `SQLiteStore` with default parameters and adds it to the `defaultStack`. This method blocks until completion.
```
try CoreStore.addStorageAndWait()
```
- returns: the local SQLite storage added to the `defaultStack`
*/
@discardableResult
public static func addStorageAndWait() throws -> SQLiteStore {
return try self.defaultStack.addStorageAndWait(SQLiteStore())
}
/**
Adds a `StorageInterface` to the `defaultStack` 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`
*/
@discardableResult
public static func addStorageAndWait<T: StorageInterface>(_ storage: T) throws -> T {
return try self.defaultStack.addStorageAndWait(storage)
}
/**
Adds a `LocalStorage` to the `defaultStack` 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.
*/
@discardableResult
public static func addStorageAndWait<T: LocalStorage>(_ storage: T) throws -> T {
return try self.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 self.defaultStack.addStorageAndWait(storage)
}
// MARK: Deprecated
@available(*, deprecated, message: "Use the new CoreStore.entityTypesByName(for:) method passing `NSManagedObject.self` as argument.")
public static var entityTypesByName: [EntityName: NSManagedObject.Type] {
return self.defaultStack.entityTypesByName
}
// MARK: Obsolete
@available(*, obsoleted: 3.1, renamed: "entityDescription(for:)")
public static func entityDescriptionForType(_ type: NSManagedObject.Type) -> NSEntityDescription? {
return self.entityDescription(for: type)
}
}

View File

@@ -1,103 +0,0 @@
//
// CoreStore+Transaction.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - CoreStore
public extension CoreStore {
/**
Using the `defaultStack`, 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(userInfo: T)` in the `completion`'s `Result<T>`. Any errors thrown from inside the `task` will be reported as `.failure(error: 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) {
self.defaultStack.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`.
- 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`.
- parameter failure: the closure executed if the save fails or if any errors are thrown within `task`. Cancelled `task`s will be indicated by `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, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) {
self.defaultStack.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 rethrown 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`.
- throws: a `CoreStoreError` value indicating the failure. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
- returns: the value returned from `task`
*/
public static func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T {
return try self.defaultStack.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.
- 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 self.defaultStack.beginUnsafe(supportsUndo: supportsUndo)
}
/**
Refreshes all registered objects `NSManagedObject`s or `CoreStoreObject`s in the `defaultStack`.
*/
public static func refreshAndMergeAllObjects() {
self.defaultStack.refreshAndMergeAllObjects()
}
// MARK: Deprecated
@available(*, deprecated, message: "Use the new auto-commiting methods `perform(asynchronous:completion:)` or `perform(asynchronous:success:failure:)`. Please read the documentation on the behavior of the new methods.")
public static func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) {
self.defaultStack.beginAsynchronous(closure)
}
@available(*, deprecated, message: "Use the new auto-commiting method `perform(synchronous:)`. Please read the documentation on the behavior of the new methods.")
@discardableResult
public static func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
return self.defaultStack.beginSynchronous(closure)
}
}

View File

@@ -24,6 +24,9 @@
//
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - CoreStore
@@ -42,10 +45,10 @@ public enum CoreStore {
get {
self.defaultStackBarrierQueue.sync(flags: .barrier) {
self.defaultStackBarrierQueue.barrierSync {
if self.defaultStackInstance == nil {
self.defaultStackInstance = DataStack()
}
}
@@ -53,7 +56,7 @@ public enum CoreStore {
}
set {
self.defaultStackBarrierQueue.async(flags: .barrier) {
self.defaultStackBarrierQueue.barrierAsync {
self.defaultStackInstance = newValue
}
@@ -63,7 +66,7 @@ public enum CoreStore {
// MARK: Private
private static let defaultStackBarrierQueue = DispatchQueue.concurrent("com.coreStore.defaultStackBarrierQueue")
private static let defaultStackBarrierQueue = GCDQueue.createConcurrent("com.coreStore.defaultStackBarrierQueue")
private static var defaultStackInstance: DataStack?
}

View File

@@ -32,157 +32,59 @@ import CoreData
/**
All errors thrown from CoreStore are expressed in `CoreStoreError` enum values.
*/
public enum CoreStoreError: Error, CustomNSError, Hashable {
public enum CoreStoreError: ErrorType, Hashable {
/**
A failure occured because of an unknown error.
*/
case unknown
case Unknown
/**
The `NSPersistentStore` could not be initialized because another store existed at the specified `NSURL`.
*/
case differentStorageExistsAtURL(existingPersistentStoreURL: URL)
case DifferentStorageExistsAtURL(existingPersistentStoreURL: NSURL)
/**
An `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case mappingModelNotFound(localStoreURL: URL, targetModel: NSManagedObjectModel, targetModelVersion: String)
case MappingModelNotFound(localStoreURL: NSURL, targetModel: NSManagedObjectModel, targetModelVersion: String)
/**
Progressive migrations are disabled for a store, but an `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case progressiveMigrationRequired(localStoreURL: URL)
case ProgressiveMigrationRequired(localStoreURL: NSURL)
/**
An internal SDK call failed with the specified `NSError`.
*/
case internalError(NSError: NSError)
/**
The transaction was terminated by a user-thrown `Error`.
*/
case userError(error: Error)
/**
The transaction was cancelled by the user.
*/
case userCancelled
case InternalError(NSError: NSError)
// MARK: CustomNSError
// MARK: ErrorType
public static var errorDomain: String {
public var _domain: String {
return CoreStoreErrorDomain
}
public var errorCode: Int {
public var _code: Int {
switch self {
case .unknown:
return CoreStoreErrorCode.unknownError.rawValue
case .Unknown:
return CoreStoreErrorCode.UnknownError.rawValue
case .differentStorageExistsAtURL:
return CoreStoreErrorCode.differentStorageExistsAtURL.rawValue
case .DifferentStorageExistsAtURL:
return CoreStoreErrorCode.DifferentPersistentStoreExistsAtURL.rawValue
case .mappingModelNotFound:
return CoreStoreErrorCode.mappingModelNotFound.rawValue
case .MappingModelNotFound:
return CoreStoreErrorCode.MappingModelNotFound.rawValue
case .progressiveMigrationRequired:
return CoreStoreErrorCode.progressiveMigrationRequired.rawValue
case .ProgressiveMigrationRequired:
return CoreStoreErrorCode.ProgressiveMigrationRequired.rawValue
case .internalError:
return CoreStoreErrorCode.internalError.rawValue
case .userError:
return CoreStoreErrorCode.userError.rawValue
case .userCancelled:
return CoreStoreErrorCode.userCancelled.rawValue
}
}
public var errorUserInfo: [String : Any] {
switch self {
case .unknown:
return [:]
case .differentStorageExistsAtURL(let existingPersistentStoreURL):
return [
"existingPersistentStoreURL": existingPersistentStoreURL
]
case .mappingModelNotFound(let localStoreURL, let targetModel, let targetModelVersion):
return [
"localStoreURL": localStoreURL,
"targetModel": targetModel,
"targetModelVersion": targetModelVersion
]
case .progressiveMigrationRequired(let localStoreURL):
return [
"localStoreURL": localStoreURL
]
case .internalError(let nsError):
return [
"NSError": nsError
]
case .userError(let error):
return [
"Error": error
]
case .userCancelled:
return [:]
}
}
// MARK: Equatable
public static func == (lhs: CoreStoreError, rhs: CoreStoreError) -> Bool {
switch (lhs, rhs) {
case (.unknown, .unknown):
return true
case (.differentStorageExistsAtURL(let url1), .differentStorageExistsAtURL(let url2)):
return url1 == url2
case (.mappingModelNotFound(let url1, let model1, let version1), .mappingModelNotFound(let url2, let model2, let version2)):
return url1 == url2 && model1 == model2 && version1 == version2
case (.progressiveMigrationRequired(let url1), .progressiveMigrationRequired(let url2)):
return url1 == url2
case (.internalError(let NSError1), .internalError(let NSError2)):
return NSError1.isEqual(NSError2)
case (.userError(let error1), .userError(let error2)):
switch (error1, error2) {
case (let error1 as AnyHashable, let error2 as AnyHashable):
return error1 == error2
case (let error1 as NSError, let error2 as NSError):
return error1.isEqual(error2)
default:
return false // shouldn't happen
}
case (.userCancelled, .userCancelled):
return true
default:
return false
case .InternalError:
return CoreStoreErrorCode.InternalError.rawValue
}
}
@@ -194,35 +96,57 @@ public enum CoreStoreError: Error, CustomNSError, Hashable {
let code = self._code
switch self {
case .unknown:
case .Unknown:
return code.hashValue
case .differentStorageExistsAtURL(let existingPersistentStoreURL):
case .DifferentStorageExistsAtURL(let existingPersistentStoreURL):
return code.hashValue ^ existingPersistentStoreURL.hashValue
case .mappingModelNotFound(let localStoreURL, let targetModel, let targetModelVersion):
case .MappingModelNotFound(let localStoreURL, let targetModel, let targetModelVersion):
return code.hashValue ^ localStoreURL.hashValue ^ targetModel.hashValue ^ targetModelVersion.hashValue
case .progressiveMigrationRequired(let localStoreURL):
case .ProgressiveMigrationRequired(let localStoreURL):
return code.hashValue ^ localStoreURL.hashValue
case .internalError(let nsError):
return code.hashValue ^ nsError.hashValue
case .userError(let error):
return code.hashValue ^ (error as NSError).hashValue
case .userCancelled:
return code.hashValue
case .InternalError(let NSError):
return code.hashValue ^ NSError.hashValue
}
}
// MARK: Internal
internal init(_ error: Error?) {
internal init(_ error: ErrorType?) {
self = error.flatMap({ $0.bridgeToSwift }) ?? .unknown
self = error.flatMap { $0.bridgeToSwift } ?? .Unknown
}
}
// MARK: - CoreStoreError: Equatable
@warn_unused_result
public func == (lhs: CoreStoreError, rhs: CoreStoreError) -> Bool {
switch (lhs, rhs) {
case (.Unknown, .Unknown):
return true
case (.DifferentStorageExistsAtURL(let url1), .DifferentStorageExistsAtURL(let url2)):
return url1 == url2
case (.MappingModelNotFound(let url1, let model1, let version1), .MappingModelNotFound(let url2, let model2, let version2)):
return url1 == url2 && model1 == model2 && version1 == version2
case (.ProgressiveMigrationRequired(let url1), .ProgressiveMigrationRequired(let url2)):
return url1 == url2
case (.InternalError(let NSError1), .InternalError(let NSError2)):
return NSError1 == NSError2
default:
return false
}
}
@@ -246,37 +170,27 @@ public enum CoreStoreErrorCode: Int {
/**
A failure occured because of an unknown error.
*/
case unknownError
case UnknownError
/**
The `NSPersistentStore` could note be initialized because another store existed at the specified `NSURL`.
*/
case differentStorageExistsAtURL
case DifferentPersistentStoreExistsAtURL
/**
An `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case mappingModelNotFound
case MappingModelNotFound
/**
Progressive migrations are disabled for a store, but an `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case progressiveMigrationRequired
case ProgressiveMigrationRequired
/**
An internal SDK call failed with the specified "NSError" userInfo key.
*/
case internalError
/**
The transaction was terminated by a user-thrown `Error` specified by "Error" userInfo key.
*/
case userError
/**
The transaction was cancelled by the user.
*/
case userCancelled
case InternalError
}
@@ -288,20 +202,26 @@ public extension NSError {
internal var isCoreDataMigrationError: Bool {
guard self.domain == CocoaError.errorDomain else {
return false
}
switch CocoaError.Code(rawValue: self.code) {
case CocoaError.Code.persistentStoreIncompatibleSchema,
CocoaError.Code.persistentStoreIncompatibleVersionHash,
CocoaError.Code.migrationMissingSourceModel,
CocoaError.Code.migration:
return true
default:
return false
}
let code = self.code
return (code == NSPersistentStoreIncompatibleVersionHashError
|| code == NSMigrationMissingSourceModelError
|| code == NSMigrationError)
&& self.domain == NSCocoaErrorDomain
}
// MARK: Deprecated
/**
Deprecated. Use `CoreStoreError` enum values instead.
If the error's domain is equal to `CoreStoreErrorDomain`, returns the associated `CoreStoreErrorCode`. For other domains, returns `nil`.
*/
@available(*, deprecated=2.0.0, message="Use CoreStoreError enum values instead.")
public var coreStoreErrorCode: CoreStoreErrorCode? {
return (self.domain == CoreStoreErrorDomain
? CoreStoreErrorCode(rawValue: self.code)
: nil)
}
}

View File

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

View File

@@ -1,99 +0,0 @@
//
// CoreStoreObject+Convenience.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStoreObject
public extension CoreStoreObject {
/**
Exposes a `FetchableSource` that can fetch sibling objects of this `CoreStoreObject` instance. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the obejct's parent is already deallocated.
- Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `fetchSource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `FetchableSource.unsafeContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time.
- returns: a `FetchableSource` that can fetch sibling objects of this `CoreStoreObject` instance. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the object's parent is already deallocated.
*/
@nonobjc
public func fetchSource() -> FetchableSource? {
guard let context = self.rawObject!.managedObjectContext else {
return nil
}
if context.isTransactionContext {
return context.parentTransaction
}
if context.isDataStackContext {
return context.parentStack
}
return context
}
/**
Exposes a `QueryableSource` that can query attributes and aggregate values. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the obejct's parent is already deallocated.
- Warning: Future implementations may change the instance returned by this method depending on the timing or condition that `querySource()` was called. Do not make assumptions that the instance will be a specific instance. If the `NSManagedObjectContext` instance is desired, use the `QueryableSource.unsafeContext()` method to get the correct instance. Also, do not assume that the `fetchSource()` and `querySource()` return the same instance all the time.
- returns: a `QueryableSource` that can query attributes and aggregate values. This may be the `DataStack`, a `BaseDataTransaction`, the `NSManagedObjectContext` itself, or `nil` if the object's parent is already deallocated.
*/
@nonobjc
public func querySource() -> QueryableSource? {
guard let context = self.rawObject!.managedObjectContext else {
return nil
}
if context.isTransactionContext {
return context.parentTransaction
}
if context.isDataStackContext {
return context.parentStack
}
return context
}
/**
Re-faults the object to use the latest values from the persistent store
*/
@nonobjc
public func refreshAsFault() {
let rawObject = self.rawObject!
rawObject.managedObjectContext?.refresh(rawObject, mergeChanges: false)
}
/**
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
*/
@nonobjc
public func refreshAndMerge() {
let rawObject = self.rawObject!
rawObject.managedObjectContext?.refresh(rawObject, mergeChanges: true)
}
}

View File

@@ -1,420 +0,0 @@
//
// CoreStoreObject+Querying.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - DynamicObject
public extension DynamicObject where Self: CoreStoreObject {
/**
Extracts the keyPath string from a `CoreStoreObject.Value` property.
```
let keyPath: String = Person.keyPath { $0.nickname }
```
*/
public static func keyPath<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Required<V>) -> String {
return attribute(self.meta).keyPath
}
/**
Extracts the keyPath string from a `CoreStoreObject.Value` property.
```
let keyPath: String = Person.keyPath { $0.nickname }
```
*/
public static func keyPath<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Optional<V>) -> String {
return attribute(self.meta).keyPath
}
/**
Extracts the keyPath string from a `CoreStoreObject.Relationship` property.
```
let keyPath: String = Person.keyPath { $0.pets }
```
*/
public static func keyPath<O: CoreStoreObject, D: CoreStoreObject>(_ relationship: (Self) -> RelationshipContainer<O>.ToOne<D>) -> String {
return relationship(self.meta).keyPath
}
/**
Extracts the keyPath string from a `CoreStoreObject.Relationship` property.
```
let keyPath: String = Person.keyPath { $0.pets }
```
*/
public static func keyPath<O: CoreStoreObject, D: CoreStoreObject>(_ relationship: (Self) -> RelationshipContainer<O>.ToManyOrdered<D>) -> String {
return relationship(self.meta).keyPath
}
/**
Extracts the keyPath string from a `CoreStoreObject.Relationship` property.
```
let keyPath: String = Person.keyPath { $0.pets }
```
*/
public static func keyPath<O: CoreStoreObject, D: CoreStoreObject>(_ relationship: (Self) -> RelationshipContainer<O>.ToManyUnordered<D>) -> String {
return relationship(self.meta).keyPath
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.nickname == "John" })
```
*/
public static func `where`(_ condition: (Self) -> Where) -> Where {
return condition(self.meta)
}
/**
Creates an `OrderBy` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchAll(From<Person>(), Person.orderBy(ascending: { $0.age }))
```
*/
public static func orderBy<O: CoreStoreObject, V: ImportableAttributeType>(ascending attribute: (Self) -> ValueContainer<O>.Required<V>) -> OrderBy {
return OrderBy(.ascending(attribute(self.meta).keyPath))
}
/**
Creates an `OrderBy` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchAll(From<Person>(), Person.orderBy(ascending: { $0.age }))
```
*/
public static func orderBy<O: CoreStoreObject, V: ImportableAttributeType>(ascending attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.ascending(attribute(self.meta).keyPath))
}
/**
Creates an `OrderBy` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchAll(From<Person>(), Person.orderBy(descending: { $0.age }))
```
*/
public static func orderBy<O: CoreStoreObject, V: ImportableAttributeType>(descending attribute: (Self) -> ValueContainer<O>.Required<V>) -> OrderBy {
return OrderBy(.descending(attribute(self.meta).keyPath))
}
/**
Creates an `OrderBy` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchAll(From<Person>(), Person.orderBy(descending: { $0.age }))
```
*/
public static func orderBy<O: CoreStoreObject, V: ImportableAttributeType>(descending attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.descending(attribute(self.meta).keyPath))
}
// MARK: Deprecated
@available(*, deprecated, renamed: "orderBy(ascending:)")
public static func ascending<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.ascending(attribute(self.meta).keyPath))
}
@available(*, deprecated, renamed: "orderBy(descending:)")
public static func descending<O: CoreStoreObject, V: ImportableAttributeType>(_ attribute: (Self) -> ValueContainer<O>.Optional<V>) -> OrderBy {
return OrderBy(.descending(attribute(self.meta).keyPath))
}
}
// MARK: - ValueContainer.Required
public extension ValueContainer.Required {
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.nickname == "John" })
```
*/
@inline(__always)
public static func == (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where(attribute.keyPath, isEqualTo: value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.nickname != "John" })
```
*/
@inline(__always)
public static func != (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return !Where(attribute.keyPath, isEqualTo: value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age < 20 })
```
*/
@inline(__always)
public static func < (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where("%K < %@", attribute.keyPath, value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age > 20 })
```
*/
@inline(__always)
public static func > (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where("%K > %@", attribute.keyPath, value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age <= 20 })
```
*/
@inline(__always)
public static func <= (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where("%K <= %@", attribute.keyPath, value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age >= 20 })
```
*/
@inline(__always)
public static func >= (_ attribute: ValueContainer<O>.Required<V>, _ value: V) -> Where {
return Where("%K >= %@", attribute.keyPath, value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname })
```
*/
@inline(__always)
public static func ~= <S: Sequence>(_ sequence: S, _ attribute: ValueContainer<O>.Required<V>) -> Where where S.Iterator.Element == V {
return Where(attribute.keyPath, isMemberOf: sequence)
}
}
// MARK: - ValueContainer.Optional
public extension ValueContainer.Optional {
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.nickname == "John" })
```
*/
@inline(__always)
public static func == (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
return Where(attribute.keyPath, isEqualTo: value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.nickname != "John" })
```
*/
@inline(__always)
public static func != (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
return !Where(attribute.keyPath, isEqualTo: value)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age < 20 })
```
*/
@inline(__always)
public static func < (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
if let value = value {
return Where("%K < %@", attribute.keyPath, value)
}
else {
return Where("%K < nil", attribute.keyPath)
}
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age > 20 })
```
*/
@inline(__always)
public static func > (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
if let value = value {
return Where("%K > %@", attribute.keyPath, value)
}
else {
return Where("%K > nil", attribute.keyPath)
}
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age <= 20 })
```
*/
@inline(__always)
public static func <= (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
if let value = value {
return Where("%K <= %@", attribute.keyPath, value)
}
else {
return Where("%K <= nil", attribute.keyPath)
}
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let person = CoreStore.fetchOne(From<Person>(), Person.where { $0.age >= 20 })
```
*/
@inline(__always)
public static func >= (_ attribute: ValueContainer<O>.Optional<V>, _ value: V?) -> Where {
if let value = value {
return Where("%K >= %@", attribute.keyPath, value)
}
else {
return Where("%K >= nil", attribute.keyPath)
}
}
/**
Creates a `Where` clause from a `CoreStoreObject.Value` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { ["Pluto", "Snoopy", "Scooby"] ~= $0.nickname })
```
*/
@inline(__always)
public static func ~= <S: Sequence>(_ sequence: S, _ attribute: ValueContainer<O>.Optional<V>) -> Where where S.Iterator.Element == V {
return Where(attribute.keyPath, isMemberOf: sequence)
}
}
// MARK: - RelationshipContainer.ToOne
public extension RelationshipContainer.ToOne {
/**
Creates a `Where` clause from a `CoreStoreObject.Relationship` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { $0.master == me })
```
*/
@inline(__always)
public static func == (_ relationship: RelationshipContainer<O>.ToOne<D>, _ object: D?) -> Where {
return Where(relationship.keyPath, isEqualTo: object)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Relationship` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { $0.master != me })
```
*/
@inline(__always)
public static func != (_ relationship: RelationshipContainer<O>.ToOne<D>, _ object: D?) -> Where {
return !Where(relationship.keyPath, isEqualTo: object)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Relationship` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { $0.master ~= me })
```
*/
@inline(__always)
public static func ~= (_ relationship: RelationshipContainer<O>.ToOne<D>, _ object: D?) -> Where {
return Where(relationship.keyPath, isEqualTo: object)
}
/**
Creates a `Where` clause from a `CoreStoreObject.Relationship` property.
```
let dog = CoreStore.fetchOne(From<Dog>(), Dog.where { [john, joe, bob] ~= $0.master })
```
*/
@inline(__always)
public static func ~= <S: Sequence>(_ sequence: S, _ relationship: RelationshipContainer<O>.ToOne<D>) -> Where where S.Iterator.Element == D {
return Where(relationship.keyPath, isMemberOf: sequence)
}
}

View File

@@ -1,137 +0,0 @@
//
// CoreStoreObject.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - CoreStoreObject
/**
The `CoreStoreObject` is an abstract class for creating CoreStore-managed objects that are more type-safe and more convenient than `NSManagedObject` subclasses. The model entities for `CoreStoreObject` subclasses are inferred from the Swift declaration themselves; no .xcdatamodeld files are needed. To declare persisted attributes and relationships for the `CoreStoreObject` subclass, declare properties of type `Value.Required<T>`, `Value.Optional<T>` for values, or `Relationship.ToOne<T>`, `Relationship.ToManyOrdered<T>`, `Relationship.ToManyUnordered<T>` for relationships.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let master = Relationship.ToOne<Person>("master")
}
class Person: CoreStoreObject {
let name = Value.Required<String>("name")
let pet = Relationship.ToOne<Animal>("pet", inverse: { $0.master })
}
```
`CoreStoreObject` entities for a model version should be added to `CoreStoreSchema` instance.
```
CoreStore.defaultStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<Animal>("Animal"),
Entity<Person>("Person")
]
)
)
```
- SeeAlso: CoreStoreSchema
- SeeAlso: CoreStoreObject.Value
- SeeAlso: CoreStoreObject.Relationship
*/
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.
*/
public required init(rawObject: NSManagedObject) {
self.isMeta = false
self.rawObject = (rawObject as! CoreStoreManagedObject)
self.initializeAttributes(Mirror(reflecting: self), self)
}
/**
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.
*/
public required init(asMeta: Void) {
self.isMeta = true
self.rawObject = nil
}
// MARK: Equatable
public static func == (lhs: CoreStoreObject, rhs: CoreStoreObject) -> Bool {
guard lhs.isMeta == rhs.isMeta else {
return false
}
if lhs.isMeta {
return type(of: lhs) == type(of: rhs)
}
return lhs.rawObject!.isEqual(rhs.rawObject!)
}
// MARK: Hashable
public var hashValue: Int {
return ObjectIdentifier(self).hashValue
^ (self.isMeta ? 0 : self.rawObject!.hashValue)
}
// MARK: Internal
internal let rawObject: CoreStoreManagedObject?
internal let isMeta: Bool
// MARK: Private
private func initializeAttributes(_ mirror: Mirror, _ parentObject: CoreStoreObject) {
_ = mirror.superclassMirror.flatMap({ self.initializeAttributes($0, parentObject) })
for child in mirror.children {
switch child.value {
case let property as AttributeProtocol:
property.parentObject = parentObject
case let property as RelationshipProtocol:
property.parentObject = parentObject
default:
continue
}
}
}
}

View File

@@ -1,553 +0,0 @@
//
// CoreStoreSchema.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - CoreStoreSchema
/**
The `CoreStoreSchema` describes models written for `CoreStoreObject` Swift class declarations for a particular model version. `CoreStoreObject` entities for a model version should be added to `CoreStoreSchema` instance.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let master = Relationship.ToOne<Person>("master")
}
class Person: CoreStoreObject {
let name = Value.Required<String>("name")
let pet = Relationship.ToOne<Animal>("pet", inverse: { $0.master })
}
CoreStore.defaultStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<Animal>("Animal"),
Entity<Person>("Person")
],
versionLock: [
"Animal": [0x2698c812ebbc3b97, 0x751e3fa3f04cf9, 0x51fd460d3babc82, 0x92b4ba735b5a3053],
"Person": [0xae4060a59f990ef0, 0x8ac83a6e1411c130, 0xa29fea58e2e38ab6, 0x2071bb7e33d77887]
]
)
)
```
- SeeAlso: CoreStoreObject
- SeeAlso: Entity
*/
public final class CoreStoreSchema: DynamicSchema {
/**
Initializes a `CoreStoreSchema`. Using this initializer only if the entities don't need to be assigned to particular "Configurations". To use multiple configurations (for example, to separate entities in different `StorageInterface`s), use the `init(modelVersion:entitiesByConfiguration:versionLock:)` initializer.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let master = Relationship.ToOne<Person>("master")
}
class Person: CoreStoreObject {
let name = Value.Required<String>("name")
let pet = Relationship.ToOne<Animal>("pet", inverse: { $0.master })
}
CoreStore.defaultStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<Animal>("Animal"),
Entity<Person>("Person")
],
versionLock: [
"Animal": [0x2698c812ebbc3b97, 0x751e3fa3f04cf9, 0x51fd460d3babc82, 0x92b4ba735b5a3053],
"Person": [0xae4060a59f990ef0, 0x8ac83a6e1411c130, 0xa29fea58e2e38ab6, 0x2071bb7e33d77887]
]
)
)
```
- parameter modelVersion: the model version for the schema. This string should be unique from other `DynamicSchema`'s model versions.
- parameter entities: an array of `Entity<T>` pertaining to all `CoreStoreObject` subclasses to be added to the schema version.
- parameter versionLock: an optional list of `VersionLock` hashes for each entity name in the `entities` array. If any `DynamicEntity` doesn't match its version lock hash, an assertion will be raised.
*/
public convenience init(modelVersion: ModelVersion, entities: [DynamicEntity], versionLock: VersionLock? = nil) {
var entityConfigurations: [DynamicEntity: Set<String>] = [:]
for entity in entities {
entityConfigurations[entity] = []
}
self.init(
modelVersion: modelVersion,
entityConfigurations: entityConfigurations,
versionLock: versionLock
)
}
/**
Initializes a `CoreStoreSchema`. Using this initializer if multiple "Configurations" (for example, to separate entities in different `StorageInterface`s) are needed. To add an entity only to the default configuration, assign an empty set to its configurations list. Note that regardless of the set configurations, all entities will be added to the default configuration.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
}
class Person: CoreStoreObject {
let name = Value.Required<String>("name")
}
CoreStore.defaultStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entityConfigurations: [
Entity<Animal>("Animal"): [],
Entity<Person>("Person"): ["People"]
],
versionLock: [
"Animal": [0x2698c812ebbc3b97, 0x751e3fa3f04cf9, 0x51fd460d3babc82, 0x92b4ba735b5a3053],
"Person": [0xae4060a59f990ef0, 0x8ac83a6e1411c130, 0xa29fea58e2e38ab6, 0x2071bb7e33d77887]
]
)
)
```
- parameter modelVersion: the model version for the schema. This string should be unique from other `DynamicSchema`'s model versions.
- parameter entityConfigurations: a dictionary with `Entity<T>` pertaining to all `CoreStoreObject` subclasses and the corresponding list of "Configurations" they should be added to. To add an entity only to the default configuration, assign an empty set to its configurations list. Note that regardless of the set configurations, all entities will be added to the default configuration.
- parameter versionLock: an optional list of `VersionLock` hashes for each entity name in the `entities` array. If any `DynamicEntity` doesn't match its version lock hash, an assertion will be raised.
*/
public required init(modelVersion: ModelVersion, entityConfigurations: [DynamicEntity: Set<String>], versionLock: VersionLock? = nil) {
var actualEntitiesByConfiguration: [String: Set<DynamicEntity>] = [:]
for (entity, configurations) in entityConfigurations {
for configuration in configurations {
var entities: Set<DynamicEntity>
if let existingEntities = actualEntitiesByConfiguration[configuration] {
entities = existingEntities
}
else {
entities = []
}
entities.insert(entity)
actualEntitiesByConfiguration[configuration] = entities
}
}
let allEntities = Set(entityConfigurations.keys)
actualEntitiesByConfiguration[DataStack.defaultConfigurationName] = allEntities
CoreStore.assert(
cs_lazy {
let expectedCount = allEntities.count
return Set(allEntities.map({ ObjectIdentifier($0.type) })).count == expectedCount
&& Set(allEntities.map({ $0.entityName })).count == expectedCount
},
"Ambiguous entity types or entity names were found in the model. Ensure that the entity types and entity names are unique to each other. Entities: \(allEntities)"
)
self.modelVersion = modelVersion
self.entitiesByConfiguration = actualEntitiesByConfiguration
self.allEntities = allEntities
if let versionLock = versionLock {
CoreStore.assert(
versionLock == VersionLock(entityVersionHashesByName: self.rawModel().entityVersionHashesByName),
"A \(cs_typeName(VersionLock.self)) was provided for the \(cs_typeName(CoreStoreSchema.self)) with version \"\(modelVersion)\", but the actual hashes do not match. This may result in unwanted migrations or unusable persistent stores.\nExpected lock values: \(versionLock)\nActual lock values: \(VersionLock(entityVersionHashesByName: self.rawModel().entityVersionHashesByName))"
)
}
else {
#if DEBUG
CoreStore.log(
.notice,
message: "These are hashes for the \(cs_typeName(CoreStoreSchema.self)) with version name \"\(modelVersion)\". Copy the dictionary below and pass it to the \(cs_typeName(CoreStoreSchema.self)) initializer's \"versionLock\" argument:\nversionLock: \(VersionLock(entityVersionHashesByName: self.rawModel().entityVersionHashesByName))"
)
#endif
}
}
// MARK: - DynamicSchema
public let modelVersion: ModelVersion
public func rawModel() -> NSManagedObjectModel {
return CoreStoreSchema.barrierQueue.sync(flags: .barrier) {
if let cachedRawModel = self.cachedRawModel {
return cachedRawModel
}
let rawModel = NSManagedObjectModel()
var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
var allCustomGettersSetters: [DynamicEntity: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]] = [:]
for entity in self.allEntities {
let (entityDescription, customGetterSetterByKeyPaths) = self.entityDescription(
for: entity,
initializer: CoreStoreSchema.firstPassCreateEntityDescription(from:in:)
)
entityDescriptionsByEntity[entity] = (entityDescription.copy() as! NSEntityDescription)
allCustomGettersSetters[entity] = customGetterSetterByKeyPaths
}
CoreStoreSchema.secondPassConnectRelationshipAttributes(for: entityDescriptionsByEntity)
CoreStoreSchema.thirdPassConnectInheritanceTree(for: entityDescriptionsByEntity)
CoreStoreSchema.fourthPassSynthesizeManagedObjectClasses(
for: entityDescriptionsByEntity,
allCustomGettersSetters: allCustomGettersSetters
)
rawModel.entities = entityDescriptionsByEntity.values.sorted(by: { $0.name! < $1.name! })
for (configuration, entities) in self.entitiesByConfiguration {
rawModel.setEntities(
entities
.map({ entityDescriptionsByEntity[$0]! })
.sorted(by: { $0.name! < $1.name! }),
forConfigurationName: configuration
)
}
self.cachedRawModel = rawModel
return rawModel
}
}
// MARK: Internal
internal let entitiesByConfiguration: [String: Set<DynamicEntity>]
// MARK: Private
private static let barrierQueue = DispatchQueue.concurrent("com.coreStore.coreStoreDataModelBarrierQueue")
private let allEntities: Set<DynamicEntity>
private var entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription] = [:]
private var customGettersSettersByEntity: [DynamicEntity: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]] = [:]
private weak var cachedRawModel: NSManagedObjectModel?
private func entityDescription(for entity: DynamicEntity, initializer: (DynamicEntity, ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter])) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]) {
if let cachedEntityDescription = self.entityDescriptionsByEntity[entity] {
return (cachedEntityDescription, self.customGettersSettersByEntity[entity] ?? [:])
}
let modelVersion = self.modelVersion
let (entityDescription, customGetterSetterByKeyPaths) = withoutActuallyEscaping(initializer, do: { $0(entity, modelVersion) })
self.entityDescriptionsByEntity[entity] = entityDescription
self.customGettersSettersByEntity[entity] = customGetterSetterByKeyPaths
return (entityDescription, customGetterSetterByKeyPaths)
}
private static func firstPassCreateEntityDescription(from entity: DynamicEntity, in modelVersion: ModelVersion) -> (entity: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]) {
let entityDescription = NSEntityDescription()
entityDescription.coreStoreEntity = entity
entityDescription.name = entity.entityName
entityDescription.isAbstract = entity.isAbstract
entityDescription.versionHashModifier = entity.versionHashModifier
entityDescription.managedObjectClassName = CoreStoreManagedObject.cs_subclassName(for: entity, in: modelVersion)
var keyPathsByAffectedKeyPaths: [KeyPath: Set<KeyPath>] = [:]
var customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter] = [:]
func createProperties(for type: CoreStoreObject.Type) -> [NSPropertyDescription] {
var propertyDescriptions: [NSPropertyDescription] = []
for child in Mirror(reflecting: type.meta).children {
switch child.value {
case let attribute as AttributeProtocol:
let description = NSAttributeDescription()
description.name = attribute.keyPath
description.attributeType = type(of: attribute).attributeType
description.isOptional = attribute.isOptional
description.isIndexed = attribute.isIndexed
description.defaultValue = attribute.defaultValue()
description.isTransient = attribute.isTransient
description.versionHashModifier = attribute.versionHashModifier
description.renamingIdentifier = attribute.renamingIdentifier
propertyDescriptions.append(description)
keyPathsByAffectedKeyPaths[attribute.keyPath] = attribute.affectedByKeyPaths()
customGetterSetterByKeyPaths[attribute.keyPath] = (attribute.getter, attribute.setter)
case let relationship as RelationshipProtocol:
let description = NSRelationshipDescription()
description.name = relationship.keyPath
description.minCount = relationship.minCount
description.maxCount = relationship.maxCount
description.isOrdered = relationship.isOrdered
description.deleteRule = relationship.deleteRule
description.versionHashModifier = relationship.versionHashModifier
description.renamingIdentifier = relationship.renamingIdentifier
propertyDescriptions.append(description)
keyPathsByAffectedKeyPaths[relationship.keyPath] = relationship.affectedByKeyPaths()
default:
continue
}
}
return propertyDescriptions
}
entityDescription.properties = createProperties(for: entity.type as! CoreStoreObject.Type)
entityDescription.keyPathsByAffectedKeyPaths = keyPathsByAffectedKeyPaths
return (entityDescription, customGetterSetterByKeyPaths)
}
private static func secondPassConnectRelationshipAttributes(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) {
var relationshipsByNameByEntity: [DynamicEntity: [String: NSRelationshipDescription]] = [:]
for (entity, entityDescription) in entityDescriptionsByEntity {
relationshipsByNameByEntity[entity] = entityDescription.relationshipsByName
}
func findEntity(for type: CoreStoreObject.Type) -> DynamicEntity {
var matchedEntities: Set<DynamicEntity> = []
for (entity, _) in entityDescriptionsByEntity where entity.type == type {
matchedEntities.insert(entity)
}
if matchedEntities.count == 1 {
return matchedEntities.first!
}
if matchedEntities.isEmpty {
CoreStore.abort(
"No \(cs_typeName("Entity<\(type)>")) instance found in the \(cs_typeName(CoreStoreSchema.self))."
)
}
else {
CoreStore.abort(
"Ambiguous entity types or entity names were found in the model. Ensure that the entity types and entity names are unique to each other. Entities: \(matchedEntities)"
)
}
}
func findInverseRelationshipMatching(destinationEntity: DynamicEntity, destinationKeyPath: String) -> NSRelationshipDescription {
for case (destinationKeyPath, let relationshipDescription) in relationshipsByNameByEntity[destinationEntity]! {
return relationshipDescription
}
CoreStore.abort(
"The inverse relationship for \"\(destinationEntity.type).\(destinationKeyPath)\" could not be found. Make sure to set the `inverse:` initializer argument for one of the paired \(cs_typeName("Relationship.ToOne<T>")), \(cs_typeName("Relationship.ToManyOrdered<T>")), or \(cs_typeName("Relationship.ToManyUnozrdered<T>"))"
)
}
for (entity, entityDescription) in entityDescriptionsByEntity {
let relationshipsByName = relationshipsByNameByEntity[entity]!
for child in Mirror(reflecting: (entity.type as! CoreStoreObject.Type).meta).children {
switch child.value {
case let relationship as RelationshipProtocol:
let (destinationType, destinationKeyPath) = relationship.inverse
let destinationEntity = findEntity(for: destinationType)
let description = relationshipsByName[relationship.keyPath]!
description.destinationEntity = entityDescriptionsByEntity[destinationEntity]!
if let destinationKeyPath = destinationKeyPath() {
let inverseRelationshipDescription = findInverseRelationshipMatching(
destinationEntity: destinationEntity,
destinationKeyPath: destinationKeyPath
)
description.inverseRelationship = inverseRelationshipDescription
inverseRelationshipDescription.inverseRelationship = description
inverseRelationshipDescription.destinationEntity = entityDescription
description.destinationEntity!.properties = description.destinationEntity!.properties
}
default:
continue
}
}
}
for (entity, entityDescription) in entityDescriptionsByEntity {
for (name, relationshipDescription) in entityDescription.relationshipsByName {
CoreStore.assert(
relationshipDescription.destinationEntity != nil,
"The destination entity for relationship \"\(entity.type).\(name)\" could not be resolved."
)
CoreStore.assert(
relationshipDescription.inverseRelationship != nil,
"The inverse relationship for \"\(entity.type).\(name)\" could not be found. Make sure to set the `inverse:` argument of the initializer for one of the paired \(cs_typeName("Relationship.ToOne<T>")), \(cs_typeName("Relationship.ToManyOrdered<T>")), or \(cs_typeName("Relationship.ToManyUnozrdered<T>"))"
)
}
}
}
private static func thirdPassConnectInheritanceTree(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription]) {
func connectBaseEntity(mirror: Mirror, entityDescription: NSEntityDescription) {
guard let superclassMirror = mirror.superclassMirror,
let superType = superclassMirror.subjectType as? CoreStoreObject.Type,
superType != CoreStoreObject.self else {
return
}
for (superEntity, superEntityDescription) in entityDescriptionsByEntity where superEntity.type == superType {
if !superEntityDescription.subentities.contains(entityDescription) {
superEntityDescription.subentities.append(entityDescription)
}
connectBaseEntity(mirror: superclassMirror, entityDescription: superEntityDescription)
}
}
for (entity, entityDescription) in entityDescriptionsByEntity {
connectBaseEntity(
mirror: Mirror(reflecting: (entity.type as! CoreStoreObject.Type).meta),
entityDescription: entityDescription
)
}
}
private static func fourthPassSynthesizeManagedObjectClasses(for entityDescriptionsByEntity: [DynamicEntity: NSEntityDescription], allCustomGettersSetters: [DynamicEntity: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]]) {
func createManagedObjectSubclass(for entityDescription: NSEntityDescription, customGetterSetterByKeyPaths: [KeyPath: CoreStoreManagedObject.CustomGetterSetter]?) {
let superEntity = entityDescription.superentity
let className = entityDescription.managedObjectClassName!
guard case nil = NSClassFromString(className) as! CoreStoreManagedObject.Type? else {
return
}
if let superEntity = superEntity {
createManagedObjectSubclass(
for: superEntity,
customGetterSetterByKeyPaths: superEntity.coreStoreEntity.flatMap({ allCustomGettersSetters[$0] })
)
}
let superClass = cs_lazy { () -> CoreStoreManagedObject.Type in
if let superClassName = superEntity?.managedObjectClassName,
let superClass = NSClassFromString(superClassName) {
return superClass as! CoreStoreManagedObject.Type
}
return CoreStoreManagedObject.self
}
let managedObjectClass: AnyClass = className.withCString {
return objc_allocateClassPair(superClass, $0, 0)!
}
defer {
objc_registerClassPair(managedObjectClass)
}
func capitalize(_ string: String) -> String {
return string.replacingCharacters(
in: Range(uncheckedBounds: (string.startIndex, string.index(after: string.startIndex))),
with: String(string[string.startIndex]).uppercased()
)
}
for (attributeName, customGetterSetters) in (customGetterSetterByKeyPaths ?? [:])
where customGetterSetters.getter != nil || customGetterSetters.setter != nil {
if let getter = customGetterSetters.getter {
let getterName = "\(attributeName)"
guard class_addMethod(
managedObjectClass,
NSSelectorFromString(getterName),
imp_implementationWithBlock(getter),
"@@:") else {
CoreStore.abort("Could not dynamically add getter method \"\(getterName)\" to class \(cs_typeName(managedObjectClass))")
}
}
if let setter = customGetterSetters.setter {
let setterName = "set\(capitalize(attributeName)):"
guard class_addMethod(
managedObjectClass,
NSSelectorFromString(setterName),
imp_implementationWithBlock(setter),
"v@:@") else {
CoreStore.abort("Could not dynamically add setter method \"\(setterName)\" to class \(cs_typeName(managedObjectClass))")
}
}
}
let newSelector = NSSelectorFromString("cs_keyPathsForValuesAffectingValueForKey:")
let keyPathsByAffectedKeyPaths = entityDescription.keyPathsByAffectedKeyPaths
let keyPathsForValuesAffectingValue: @convention(block) (Any, String) -> Set<String> = { (instance, keyPath) in
if let keyPaths = keyPathsByAffectedKeyPaths[keyPath] {
return keyPaths
}
return []
}
let origSelector = #selector(NSManagedObject.keyPathsForValuesAffectingValue(forKey:))
let metaClass: AnyClass = object_getClass(managedObjectClass)!
let origMethod = class_getClassMethod(managedObjectClass, origSelector)
let origImp = method_getImplementation(origMethod)
let newImp = imp_implementationWithBlock(keyPathsForValuesAffectingValue)
if class_addMethod(metaClass, origSelector, newImp, method_getTypeEncoding(origMethod)) {
class_replaceMethod(metaClass, newSelector, origImp, method_getTypeEncoding(origMethod))
}
else {
let newMethod = class_getClassMethod(managedObjectClass, newSelector)
method_exchangeImplementations(origMethod, newMethod)
}
}
for (dynamicEntity, entityDescription) in entityDescriptionsByEntity {
createManagedObjectSubclass(
for: entityDescription,
customGetterSetterByKeyPaths: allCustomGettersSetters[dynamicEntity]
)
}
}
}

View File

@@ -1,66 +0,0 @@
//
// CoreStoreStrings.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - XcodeDataModelFileName
/**
A `String` that pertains to the name of an *.xcdatamodeld file (without the file extension).
*/
public typealias XcodeDataModelFileName = String
// MARK: - ModelConfiguration
/**
An `Optional<String>` that pertains to the name of a "Configuration" which particular groups of entities may belong to. When `nil`, pertains to the default configuration which includes all entities.
*/
public typealias ModelConfiguration = String?
// MARK: - ModelVersion
/**
An `String` that pertains to the name of a versioned *.xcdatamodeld file (without the file extension). Model version strings don't necessarily have to be numeric or ordered in any way. The migration sequence will always be decided by (or the lack of) the `MigrationChain`.
*/
public typealias ModelVersion = String
// MARK: - EntityName
/**
An `String` that pertains to an Entity name.
*/
public typealias EntityName = String
// MARK: - ClassName
/**
An `String` that pertains to a dynamically-accessable class name (usable with NSClassFromString(...)).
*/
public typealias ClassName = String

View File

@@ -1,753 +0,0 @@
//
// CustomSchemaMappingProvider.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - CustomSchemaMappingProvider
/**
A `SchemaMappingProvider` that accepts custom mappings for some entities. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible.
*/
public class CustomSchemaMappingProvider: Hashable, SchemaMappingProvider {
/**
The source model version for the mapping.
*/
public let sourceVersion: ModelVersion
/**
The destination model version for the mapping.
*/
public let destinationVersion: ModelVersion
/**
Creates a `CustomSchemaMappingProvider`
- parameter sourceVersion: the source model version for the mapping
- parameter destinationVersion: the destination model version for the mapping
- parameter entityMappings: a list of `CustomMapping`s. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible. Any conflicts or ambiguity will raise an assertion.
*/
public required init(from sourceVersion: ModelVersion, to destinationVersion: ModelVersion, entityMappings: Set<CustomMapping> = []) {
CoreStore.assert(
cs_lazy {
let sources = entityMappings.flatMap({ $0.entityMappingSourceEntity })
let destinations = entityMappings.flatMap({ $0.entityMappingDestinationEntity })
return sources.count == Set(sources).count
&& destinations.count == Set(destinations).count
},
"Duplicate source/destination entities found in provided \"entityMappings\" argument."
)
self.sourceVersion = sourceVersion
self.destinationVersion = destinationVersion
self.entityMappings = entityMappings
}
// MARK: - CustomMapping
/**
Provides the type of mapping for an entity. Mappings of entities with no `CustomMapping` provided will be automatically calculated if possible. Any conflicts or ambiguity will raise an assertion.
*/
public enum CustomMapping: Hashable {
/**
The `sourceEntity` is meant to be removed from the source `DynamicSchema` and should not be migrated to the destination `DynamicSchema`.
*/
case deleteEntity(sourceEntity: EntityName)
/**
The `destinationEntity` is newly added to the destination `DynamicSchema` and has no mapping from the source `DynamicSchema`.
*/
case insertEntity(destinationEntity: EntityName)
/**
The `DynamicSchema`s entity has no changes and can be copied directly from `sourceEntity` to `destinationEntity`.
*/
case copyEntity(sourceEntity: EntityName, destinationEntity: EntityName)
/**
The `DynamicSchema`s entity needs transformations from `sourceEntity` to `destinationEntity`. The `transformer` closure will be used to apply the changes. The `CustomMapping.inferredTransformation` method can be used directly as the `transformer` if the changes can be inferred (i.e. lightweight).
*/
case transformEntity(sourceEntity: EntityName, destinationEntity: EntityName, transformer: Transformer)
/**
The closure type for `CustomMapping.transformEntity`.
- parameter sourceObject: a proxy object representing the source entity. The properties can be accessed via keyPath.
- parameter createDestinationObject: the closure to create the object for the destination entity. The `CustomMapping.inferredTransformation` method can be used directly as the `transformer` if the changes can be inferred (i.e. lightweight). The object is created lazily and executing the closure multiple times will return the same instance. The destination object's properties can be accessed and updated via keyPath.
*/
public typealias Transformer = (_ sourceObject: UnsafeSourceObject, _ createDestinationObject: () -> UnsafeDestinationObject) throws -> Void
/**
The `CustomMapping.inferredTransformation` method can be used directly as the `transformer` if the changes can be inferred (i.e. lightweight).
*/
public static func inferredTransformation(_ sourceObject: UnsafeSourceObject, _ createDestinationObject: () -> UnsafeDestinationObject) throws -> Void {
let destinationObject = createDestinationObject()
destinationObject.enumerateAttributes { (attribute, sourceAttribute) in
if let sourceAttribute = sourceAttribute {
destinationObject[attribute] = sourceObject[sourceAttribute]
}
}
}
// MARK: Equatable
public static func == (lhs: CustomMapping, rhs: CustomMapping) -> Bool {
switch (lhs, rhs) {
case (.deleteEntity(let sourceEntity1), .deleteEntity(let sourceEntity2)):
return sourceEntity1 == sourceEntity2
case (.insertEntity(let destinationEntity1), .insertEntity(let destinationEntity2)):
return destinationEntity1 == destinationEntity2
case (.copyEntity(let sourceEntity1, let destinationEntity1), .copyEntity(let sourceEntity2, let destinationEntity2)):
return sourceEntity1 == sourceEntity2
&& destinationEntity1 == destinationEntity2
case (.transformEntity(let sourceEntity1, let destinationEntity1, _), .transformEntity(let sourceEntity2, let destinationEntity2, _)):
return sourceEntity1 == sourceEntity2
&& destinationEntity1 == destinationEntity2
default:
return false
}
}
// MARK: Hashable
public var hashValue: Int {
switch self {
case .deleteEntity(let sourceEntity):
return sourceEntity.hashValue
case .insertEntity(let destinationEntity):
return destinationEntity.hashValue
case .copyEntity(let sourceEntity, let destinationEntity):
return sourceEntity.hashValue
^ destinationEntity.hashValue
case .transformEntity(let sourceEntity, let destinationEntity, _):
return sourceEntity.hashValue
^ destinationEntity.hashValue
}
}
// MARK: FilePrivate
fileprivate var entityMappingSourceEntity: EntityName? {
switch self {
case .deleteEntity(let sourceEntity),
.copyEntity(let sourceEntity, _),
.transformEntity(let sourceEntity, _, _):
return sourceEntity
case .insertEntity:
return nil
}
}
fileprivate var entityMappingDestinationEntity: EntityName? {
switch self {
case .insertEntity(let destinationEntity),
.copyEntity(_, let destinationEntity),
.transformEntity(_, let destinationEntity, _):
return destinationEntity
case .deleteEntity:
return nil
}
}
}
// MARK: - UnsafeSourceObject
/**
The read-only proxy object used for the source object in a mapping's `Transformer` closure. Properties can be accessed either by keyPath string or by `NSAttributeDescription`.
*/
public final class UnsafeSourceObject {
/**
Accesses the property value via its keyPath.
*/
public subscript(attribute: KeyPath) -> Any? {
return self.rawObject.cs_accessValueForKVCKey(attribute)
}
/**
Accesses the property value via its `NSAttributeDescription`, which can be accessed from the `enumerateAttributes(_:)` method.
*/
public subscript(attribute: NSAttributeDescription) -> Any? {
return self.rawObject.cs_accessValueForKVCKey(attribute.name)
}
/**
Enumerates the all `NSAttributeDescription`s. The `attribute` argument can be used as the subscript key to access the property.
*/
public func enumerateAttributes(_ closure: (_ attribute: NSAttributeDescription) -> Void) {
func enumerate(_ entity: NSEntityDescription, _ closure: (NSAttributeDescription) -> Void) {
if let superEntity = entity.superentity {
enumerate(superEntity, closure)
}
for case let attribute as NSAttributeDescription in entity.properties {
closure(attribute)
}
}
enumerate(self.rawObject.entity, closure)
}
// MARK: Internal
internal init(_ rawObject: NSManagedObject) {
self.rawObject = rawObject
}
// MARK: Private
private let rawObject: NSManagedObject
}
// MARK: - UnsafeDestinationObject
/**
The read-write proxy object used for the destination object that can be created in a mapping's `Transformer` closure. Properties can be accessed and mutated either through keyPath string or by `NSAttributeDescription`.
*/
public final class UnsafeDestinationObject {
/**
Accesses or mutates the property value via its keyPath.
*/
public subscript(attribute: KeyPath) -> Any? {
get { return self.rawObject.cs_accessValueForKVCKey(attribute) }
set { self.rawObject.cs_setValue(newValue, forKVCKey: attribute) }
}
/**
Accesses or mutates the property value via its `NSAttributeDescription`, which can be accessed from the `enumerateAttributes(_:)` method.
*/
public subscript(attribute: NSAttributeDescription) -> Any? {
get { return self.rawObject.cs_accessValueForKVCKey(attribute.name) }
set { self.rawObject.cs_setValue(newValue, forKVCKey: attribute.name) }
}
/**
Enumerates the all `NSAttributeDescription`s. The `attribute` argument can be used as the subscript key to access and mutate the property. The `sourceAttribute` can be used to access properties from the source `UnsafeSourceObject`.
*/
public func enumerateAttributes(_ closure: (_ attribute: NSAttributeDescription, _ sourceAttribute: NSAttributeDescription?) -> Void) {
func enumerate(_ entity: NSEntityDescription, _ closure: (_ attribute: NSAttributeDescription, _ sourceAttribute: NSAttributeDescription?) -> Void) {
if let superEntity = entity.superentity {
enumerate(superEntity, closure)
}
for case let attribute as NSAttributeDescription in entity.properties {
closure(attribute, self.sourceAttributesByDestinationKey[attribute.name])
}
}
enumerate(self.rawObject.entity, closure)
}
// MARK: Internal
internal init(_ rawObject: NSManagedObject, _ sourceAttributesByDestinationKey: [KeyPath: NSAttributeDescription]) {
self.rawObject = rawObject
self.sourceAttributesByDestinationKey = sourceAttributesByDestinationKey
}
// MARK: FilePrivate
fileprivate let rawObject: NSManagedObject
fileprivate let sourceAttributesByDestinationKey: [KeyPath: NSAttributeDescription]
}
// MARK: Equatable
public static func == (lhs: CustomSchemaMappingProvider, rhs: CustomSchemaMappingProvider) -> Bool {
return lhs.sourceVersion == rhs.sourceVersion
&& lhs.destinationVersion == rhs.destinationVersion
&& type(of: lhs) == type(of: rhs)
}
// MARK: Hashable
public var hashValue: Int {
return self.sourceVersion.hashValue
^ self.destinationVersion.hashValue
}
// MARK: SchemaMappingProvider
public func cs_createMappingModel(from sourceSchema: DynamicSchema, to destinationSchema: DynamicSchema, storage: LocalStorage) throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) {
let sourceModel = sourceSchema.rawModel()
let destinationModel = destinationSchema.rawModel()
let mappingModel = NSMappingModel()
let (deleteMappings, insertMappings, copyMappings, transformMappings) = self.resolveEntityMappings(
sourceModel: sourceModel,
destinationModel: destinationModel
)
func expression(forSource sourceEntity: NSEntityDescription) -> NSExpression {
return NSExpression(format: "FETCH(FUNCTION($\(NSMigrationManagerKey), \"fetchRequestForSourceEntityNamed:predicateString:\" , \"\(sourceEntity.name!)\", \"\(NSPredicate(value: true))\"), FUNCTION($\(NSMigrationManagerKey), \"\(#selector(getter: NSMigrationManager.sourceContext))\"), \(false))")
}
let sourceEntitiesByName = sourceModel.entitiesByName
let destinationEntitiesByName = destinationModel.entitiesByName
var entityMappings: [NSEntityMapping] = []
for case .deleteEntity(let sourceEntityName) in deleteMappings {
let sourceEntity = sourceEntitiesByName[sourceEntityName]!
let entityMapping = NSEntityMapping()
entityMapping.sourceEntityName = sourceEntity.name
entityMapping.sourceEntityVersionHash = sourceEntity.versionHash
entityMapping.mappingType = .removeEntityMappingType
entityMapping.sourceExpression = expression(forSource: sourceEntity)
entityMappings.append(entityMapping)
}
for case .insertEntity(let destinationEntityName) in insertMappings {
let destinationEntity = destinationEntitiesByName[destinationEntityName]!
let entityMapping = NSEntityMapping()
entityMapping.destinationEntityName = destinationEntity.name
entityMapping.destinationEntityVersionHash = destinationEntity.versionHash
entityMapping.mappingType = .addEntityMappingType
entityMapping.attributeMappings = autoreleasepool { () -> [NSPropertyMapping] in
var attributeMappings: [NSPropertyMapping] = []
for (_, destinationAttribute) in destinationEntity.attributesByName {
let propertyMapping = NSPropertyMapping()
propertyMapping.name = destinationAttribute.name
attributeMappings.append(propertyMapping)
}
return attributeMappings
}
entityMapping.relationshipMappings = autoreleasepool { () -> [NSPropertyMapping] in
var relationshipMappings: [NSPropertyMapping] = []
for (_, destinationRelationship) in destinationEntity.relationshipsByName {
let propertyMapping = NSPropertyMapping()
propertyMapping.name = destinationRelationship.name
relationshipMappings.append(propertyMapping)
}
return relationshipMappings
}
entityMappings.append(entityMapping)
}
for case .copyEntity(let sourceEntityName, let destinationEntityName) in copyMappings {
let sourceEntity = sourceEntitiesByName[sourceEntityName]!
let destinationEntity = destinationEntitiesByName[destinationEntityName]!
let entityMapping = NSEntityMapping()
entityMapping.sourceEntityName = sourceEntity.name
entityMapping.sourceEntityVersionHash = sourceEntity.versionHash
entityMapping.destinationEntityName = destinationEntity.name
entityMapping.destinationEntityVersionHash = destinationEntity.versionHash
entityMapping.mappingType = .copyEntityMappingType
entityMapping.sourceExpression = expression(forSource: sourceEntity)
entityMapping.attributeMappings = autoreleasepool { () -> [NSPropertyMapping] in
let sourceAttributes = sourceEntity.cs_resolvedAttributeRenamingIdentities()
let destinationAttributes = destinationEntity.cs_resolvedAttributeRenamingIdentities()
var attributeMappings: [NSPropertyMapping] = []
for (renamingIdentifier, destination) in destinationAttributes {
let sourceAttribute = sourceAttributes[renamingIdentifier]!.attribute
let destinationAttribute = destination.attribute
let propertyMapping = NSPropertyMapping()
propertyMapping.name = destinationAttribute.name
propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationSourceObjectKey), \"\(#selector(NSManagedObject.value(forKey:)))\", \"\(sourceAttribute.name)\")")
attributeMappings.append(propertyMapping)
}
return attributeMappings
}
entityMapping.relationshipMappings = autoreleasepool { () -> [NSPropertyMapping] in
let sourceRelationships = sourceEntity.cs_resolvedRelationshipRenamingIdentities()
let destinationRelationships = destinationEntity.cs_resolvedRelationshipRenamingIdentities()
var relationshipMappings: [NSPropertyMapping] = []
for (renamingIdentifier, destination) in destinationRelationships {
let sourceRelationship = sourceRelationships[renamingIdentifier]!.relationship
let destinationRelationship = destination.relationship
let sourceRelationshipName = sourceRelationship.name
let propertyMapping = NSPropertyMapping()
propertyMapping.name = destinationRelationship.name
propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationManagerKey), \"destinationInstancesForSourceRelationshipNamed:sourceInstances:\", \"\(sourceRelationshipName)\", FUNCTION($\(NSMigrationSourceObjectKey), \"\(#selector(NSManagedObject.value(forKey:)))\", \"\(sourceRelationshipName)\"))")
relationshipMappings.append(propertyMapping)
}
return relationshipMappings
}
entityMappings.append(entityMapping)
}
for case .transformEntity(let sourceEntityName, let destinationEntityName, let transformEntity) in transformMappings {
let sourceEntity = sourceEntitiesByName[sourceEntityName]!
let destinationEntity = destinationEntitiesByName[destinationEntityName]!
let entityMapping = NSEntityMapping()
entityMapping.sourceEntityName = sourceEntity.name
entityMapping.sourceEntityVersionHash = sourceEntity.versionHash
entityMapping.destinationEntityName = destinationEntity.name
entityMapping.destinationEntityVersionHash = destinationEntity.versionHash
entityMapping.mappingType = .customEntityMappingType
entityMapping.sourceExpression = expression(forSource: sourceEntity)
entityMapping.entityMigrationPolicyClassName = NSStringFromClass(CustomEntityMigrationPolicy.self)
var userInfo: [AnyHashable: Any] = [
CustomEntityMigrationPolicy.UserInfoKey.transformer: transformEntity
]
autoreleasepool {
let sourceAttributes = sourceEntity.cs_resolvedAttributeRenamingIdentities()
let destinationAttributes = destinationEntity.cs_resolvedAttributeRenamingIdentities()
let transformedRenamingIdentifiers = Set(destinationAttributes.keys)
.intersection(sourceAttributes.keys)
var sourceAttributesByDestinationKey: [KeyPath: NSAttributeDescription] = [:]
for renamingIdentifier in transformedRenamingIdentifiers {
let sourceAttribute = sourceAttributes[renamingIdentifier]!.attribute
let destinationAttribute = destinationAttributes[renamingIdentifier]!.attribute
sourceAttributesByDestinationKey[destinationAttribute.name] = sourceAttribute
}
userInfo[CustomEntityMigrationPolicy.UserInfoKey.sourceAttributesByDestinationKey] = sourceAttributesByDestinationKey
}
entityMapping.relationshipMappings = autoreleasepool { () -> [NSPropertyMapping] in
let sourceRelationships = sourceEntity.cs_resolvedRelationshipRenamingIdentities()
let destinationRelationships = destinationEntity.cs_resolvedRelationshipRenamingIdentities()
let transformedRenamingIdentifiers = Set(destinationRelationships.keys)
.intersection(sourceRelationships.keys)
var relationshipMappings: [NSPropertyMapping] = []
for renamingIdentifier in transformedRenamingIdentifiers {
let sourceRelationship = sourceRelationships[renamingIdentifier]!.relationship
let destinationRelationship = destinationRelationships[renamingIdentifier]!.relationship
let sourceRelationshipName = sourceRelationship.name
let destinationRelationshipName = destinationRelationship.name
let propertyMapping = NSPropertyMapping()
propertyMapping.name = destinationRelationshipName
propertyMapping.valueExpression = NSExpression(format: "FUNCTION($\(NSMigrationManagerKey), \"destinationInstancesForSourceRelationshipNamed:sourceInstances:\", \"\(sourceRelationshipName)\", FUNCTION($\(NSMigrationSourceObjectKey), \"\(#selector(NSManagedObject.value(forKey:)))\", \"\(sourceRelationshipName)\"))")
relationshipMappings.append(propertyMapping)
}
return relationshipMappings
}
entityMapping.userInfo = userInfo
entityMappings.append(entityMapping)
}
mappingModel.entityMappings = entityMappings
return (
mappingModel,
.heavyweight(
sourceVersion: self.sourceVersion,
destinationVersion: self.destinationVersion
)
)
}
// MARK: Private
// MARK: - CustomEntityMigrationPolicy
private final class CustomEntityMigrationPolicy: NSEntityMigrationPolicy {
// MARK: NSEntityMigrationPolicy
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
let userInfo = mapping.userInfo!
let transformer = userInfo[CustomEntityMigrationPolicy.UserInfoKey.transformer]! as! CustomMapping.Transformer
let sourceAttributesByDestinationKey = userInfo[CustomEntityMigrationPolicy.UserInfoKey.sourceAttributesByDestinationKey] as! [KeyPath: NSAttributeDescription]
var destinationObject: UnsafeDestinationObject?
try transformer(
UnsafeSourceObject(sInstance),
{
if let destinationObject = destinationObject {
return destinationObject
}
let rawObject = NSEntityDescription.insertNewObject(
forEntityName: mapping.destinationEntityName!,
into: manager.destinationContext
)
destinationObject = UnsafeDestinationObject(rawObject, sourceAttributesByDestinationKey)
return destinationObject!
}
)
if let dInstance = destinationObject?.rawObject {
manager.associate(sourceInstance: sInstance, withDestinationInstance: dInstance, for: mapping)
}
}
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
}
// MARK: FilePrivate
fileprivate enum UserInfoKey {
fileprivate static let transformer = "CoreStore.CustomEntityMigrationPolicy.transformer"
fileprivate static let sourceAttributesByDestinationKey = "CoreStore.CustomEntityMigrationPolicy.sourceAttributesByDestinationKey"
}
}
// MARK: -
private let entityMappings: Set<CustomMapping>
private func resolveEntityMappings(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel) -> (delete: Set<CustomMapping>, insert: Set<CustomMapping>, copy: Set<CustomMapping>, transform: Set<CustomMapping>) {
var deleteMappings: Set<CustomMapping> = []
var insertMappings: Set<CustomMapping> = []
var copyMappings: Set<CustomMapping> = []
var transformMappings: Set<CustomMapping> = []
var allMappedSourceKeys: [KeyPath: KeyPath] = [:]
var allMappedDestinationKeys: [KeyPath: KeyPath] = [:]
let sourceRenamingIdentifiers = sourceModel.cs_resolvedRenamingIdentities()
let sourceEntityNames = sourceModel.entitiesByName
let destinationRenamingIdentifiers = destinationModel.cs_resolvedRenamingIdentities()
let destinationEntityNames = destinationModel.entitiesByName
let removedRenamingIdentifiers = Set(sourceRenamingIdentifiers.keys)
.subtracting(destinationRenamingIdentifiers.keys)
let addedRenamingIdentifiers = Set(destinationRenamingIdentifiers.keys)
.subtracting(sourceRenamingIdentifiers.keys)
let transformedRenamingIdentifiers = Set(destinationRenamingIdentifiers.keys)
.subtracting(addedRenamingIdentifiers)
.subtracting(removedRenamingIdentifiers)
// First pass: resolve source-destination entities
for mapping in self.entityMappings {
switch mapping {
case .deleteEntity(let sourceEntity):
CoreStore.assert(
sourceEntityNames[sourceEntity] != nil,
"A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' passed to \(cs_typeName(CustomSchemaMappingProvider.self)) could not be mapped to any \(cs_typeName(NSEntityDescription.self)) from the source \(cs_typeName(NSManagedObjectModel.self))."
)
CoreStore.assert(
allMappedSourceKeys[sourceEntity] == nil,
"Duplicate \(cs_typeName(CustomMapping.self))s found for source entity name \"\(sourceEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))."
)
deleteMappings.insert(mapping)
allMappedSourceKeys[sourceEntity] = ""
case .insertEntity(let destinationEntity):
CoreStore.assert(
destinationEntityNames[destinationEntity] != nil,
"A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' passed to \(cs_typeName(CustomSchemaMappingProvider.self)) could not be mapped to any \(cs_typeName(NSEntityDescription.self)) from the destination \(cs_typeName(NSManagedObjectModel.self))."
)
CoreStore.assert(
allMappedDestinationKeys[destinationEntity] == nil,
"Duplicate \(cs_typeName(CustomMapping.self))s found for destination entity name \"\(destinationEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))."
)
insertMappings.insert(mapping)
allMappedDestinationKeys[destinationEntity] = ""
case .transformEntity(let sourceEntity, let destinationEntity, _):
CoreStore.assert(
sourceEntityNames[sourceEntity] != nil,
"A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' passed to \(cs_typeName(CustomSchemaMappingProvider.self)) could not be mapped to any \(cs_typeName(NSEntityDescription.self)) from the source \(cs_typeName(NSManagedObjectModel.self))."
)
CoreStore.assert(
destinationEntityNames[destinationEntity] != nil,
"A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' passed to \(cs_typeName(CustomSchemaMappingProvider.self)) could not be mapped to any \(cs_typeName(NSEntityDescription.self)) from the destination \(cs_typeName(NSManagedObjectModel.self))."
)
CoreStore.assert(
allMappedSourceKeys[sourceEntity] == nil,
"Duplicate \(cs_typeName(CustomMapping.self))s found for source entity name \"\(sourceEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))."
)
CoreStore.assert(
allMappedDestinationKeys[destinationEntity] == nil,
"Duplicate \(cs_typeName(CustomMapping.self))s found for destination entity name \"\(destinationEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))."
)
transformMappings.insert(mapping)
allMappedSourceKeys[sourceEntity] = destinationEntity
allMappedDestinationKeys[destinationEntity] = sourceEntity
case .copyEntity(let sourceEntity, let destinationEntity):
CoreStore.assert(
sourceEntityNames[sourceEntity] != nil,
"A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' passed to \(cs_typeName(CustomSchemaMappingProvider.self)) could not be mapped to any \(cs_typeName(NSEntityDescription.self)) from the source \(cs_typeName(NSManagedObjectModel.self))."
)
CoreStore.assert(
destinationEntityNames[destinationEntity] != nil,
"A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' passed to \(cs_typeName(CustomSchemaMappingProvider.self)) could not be mapped to any \(cs_typeName(NSEntityDescription.self)) from the destination \(cs_typeName(NSManagedObjectModel.self))."
)
CoreStore.assert(
sourceEntityNames[sourceEntity]!.versionHash == destinationEntityNames[destinationEntity]!.versionHash,
"A \(cs_typeName(CustomMapping.self)) with value '\(mapping)' was passed to \(cs_typeName(CustomSchemaMappingProvider.self)) but the \(cs_typeName(NSEntityDescription.self))'s \"versionHash\" of the source and destination entities do not match."
)
CoreStore.assert(
allMappedSourceKeys[sourceEntity] == nil,
"Duplicate \(cs_typeName(CustomMapping.self))s found for source entity name \"\(sourceEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))."
)
CoreStore.assert(
allMappedDestinationKeys[destinationEntity] == nil,
"Duplicate \(cs_typeName(CustomMapping.self))s found for destination entity name \"\(destinationEntity)\" in \(cs_typeName(CustomSchemaMappingProvider.self))."
)
copyMappings.insert(mapping)
allMappedSourceKeys[sourceEntity] = destinationEntity
allMappedDestinationKeys[destinationEntity] = sourceEntity
}
}
for renamingIdentifier in transformedRenamingIdentifiers {
let sourceEntity = sourceRenamingIdentifiers[renamingIdentifier]!.entity
let destinationEntity = destinationRenamingIdentifiers[renamingIdentifier]!.entity
let sourceEntityName = sourceEntity.name!
let destinationEntityName = destinationEntity.name!
switch (allMappedSourceKeys[sourceEntityName], allMappedDestinationKeys[destinationEntityName]) {
case (nil, nil):
if sourceEntity.versionHash == destinationEntity.versionHash {
copyMappings.insert(
.copyEntity(
sourceEntity: sourceEntityName,
destinationEntity: destinationEntityName
)
)
}
else {
transformMappings.insert(
.transformEntity(
sourceEntity: sourceEntityName,
destinationEntity: destinationEntityName,
transformer: CustomMapping.inferredTransformation
)
)
}
allMappedSourceKeys[sourceEntityName] = destinationEntityName
allMappedDestinationKeys[destinationEntityName] = sourceEntityName
case (""?, nil):
insertMappings.insert(.insertEntity(destinationEntity: destinationEntityName))
allMappedDestinationKeys[destinationEntityName] = ""
case (nil, ""?):
deleteMappings.insert(.deleteEntity(sourceEntity: sourceEntityName))
allMappedSourceKeys[sourceEntityName] = ""
default:
continue
}
}
for renamingIdentifier in removedRenamingIdentifiers {
let sourceEntity = sourceRenamingIdentifiers[renamingIdentifier]!.entity
let sourceEntityName = sourceEntity.name!
switch allMappedSourceKeys[sourceEntityName] {
case nil:
deleteMappings.insert(.deleteEntity(sourceEntity: sourceEntityName))
allMappedSourceKeys[sourceEntityName] = ""
default:
continue
}
}
for renamingIdentifier in addedRenamingIdentifiers {
let destinationEntity = destinationRenamingIdentifiers[renamingIdentifier]!.entity
let destinationEntityName = destinationEntity.name!
switch allMappedDestinationKeys[destinationEntityName] {
case nil:
insertMappings.insert(.insertEntity(destinationEntity: destinationEntityName))
allMappedDestinationKeys[destinationEntityName] = ""
default:
continue
}
}
return (deleteMappings, insertMappings, copyMappings, transformMappings)
}
}

View File

@@ -1,772 +0,0 @@
//
// DataStack+Migration.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - DataStack
public extension DataStack {
/**
Asynchronously adds a `StorageInterface` to the stack. Migrations are also initiated by default.
```
dataStack.addStorage(
InMemoryStore(configuration: "Config1"),
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storage: the 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 `StorageInterface` associated to the `SetupResult.success` may not always be the same instance as the parameter argument if a previous `StorageInterface` was already added at the same URL and with the same configuration.
*/
public func addStorage<T: StorageInterface>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) {
self.coordinator.performAsynchronously {
if let _ = self.persistentStoreForStorage(storage) {
DispatchQueue.main.async {
completion(SetupResult(storage))
}
return
}
do {
_ = try self.createPersistentStoreFromStorage(
storage,
finalURL: nil,
finalStoreOptions: storage.storeOptions
)
DispatchQueue.main.async {
completion(SetupResult(storage))
}
}
catch {
let storeError = CoreStoreError(error)
CoreStore.log(
storeError,
"Failed to add \(cs_typeName(storage)) to the stack."
)
DispatchQueue.main.async {
completion(SetupResult(storeError))
}
}
}
}
/**
Asynchronously adds a `LocalStorage` to the stack. Migrations are also initiated by default.
```
let migrationProgress = dataStack.addStorage(
SQLiteStore(fileName: "core_data.sqlite", configuration: "Config1"),
completion: { result in
switch result {
case .success(let storage): // ...
case .failure(let error): // ...
}
}
)
```
- parameter storage: the local 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 `LocalStorage` associated to the `SetupResult.success` 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: a `Progress` instance if a migration has started, or `nil` if either no migrations are required or if a failure occured.
*/
public func addStorage<T: LocalStorage>(_ storage: T, completion: @escaping (SetupResult<T>) -> Void) -> Progress? {
let fileURL = storage.fileURL
CoreStore.assert(
fileURL.isFileURL,
"The specified URL for the \(cs_typeName(storage)) is invalid: \"\(fileURL)\""
)
return self.coordinator.performSynchronously {
if let _ = self.persistentStoreForStorage(storage) {
DispatchQueue.main.async {
completion(SetupResult(storage))
}
return nil
}
if let persistentStore = self.coordinator.persistentStore(for: fileURL as URL) {
if let existingStorage = persistentStore.storageInterface as? T,
storage.matchesPersistentStore(persistentStore) {
DispatchQueue.main.async {
completion(SetupResult(existingStorage))
}
return nil
}
let error = CoreStoreError.differentStorageExistsAtURL(existingPersistentStoreURL: fileURL)
CoreStore.log(
error,
"Failed to add \(cs_typeName(storage)) at \"\(fileURL)\" because a different \(cs_typeName(NSPersistentStore.self)) at that URL already exists."
)
DispatchQueue.main.async {
completion(SetupResult(error))
}
return nil
}
do {
try FileManager.default.createDirectory(
at: fileURL.deletingLastPathComponent(),
withIntermediateDirectories: true,
attributes: nil
)
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
ofType: type(of: storage).storeType,
at: fileURL as URL,
options: storage.storeOptions
)
return self.upgradeStorageIfNeeded(
storage,
metadata: metadata,
completion: { (result) -> Void in
if case .failure(.internalError(let error)) = result {
if storage.localStorageOptions.contains(.recreateStoreOnModelMismatch) && error.isCoreDataMigrationError {
do {
try storage.cs_eraseStorageAndWait(
metadata: metadata,
soureModelHint: self.schemaHistory.schema(for: metadata)?.rawModel()
)
_ = try self.addStorageAndWait(storage)
DispatchQueue.main.async {
completion(SetupResult(storage))
}
}
catch {
completion(SetupResult(error))
}
return
}
completion(SetupResult(error))
return
}
do {
_ = try self.addStorageAndWait(storage)
DispatchQueue.main.async {
completion(SetupResult(storage))
}
}
catch {
completion(SetupResult(error))
}
}
)
}
catch let error as NSError
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
do {
_ = try self.addStorageAndWait(storage)
DispatchQueue.main.async {
completion(SetupResult(storage))
}
}
catch {
DispatchQueue.main.async {
completion(SetupResult(error))
}
}
return nil
}
catch {
let storeError = CoreStoreError(error)
CoreStore.log(
storeError,
"Failed to load SQLite \(cs_typeName(NSPersistentStore.self)) metadata."
)
DispatchQueue.main.async {
completion(SetupResult(storeError))
}
return nil
}
}
}
/**
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(SetupResult(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(SetupResult(existingStorage))
}
return
}
let error = CoreStoreError.differentStorageExistsAtURL(existingPersistentStoreURL: cacheFileURL)
CoreStore.log(
error,
"Failed to add \(cs_typeName(storage)) at \"\(cacheFileURL)\" because a different \(cs_typeName(NSPersistentStore.self)) at that URL already exists."
)
DispatchQueue.main.async {
completion(SetupResult(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(SetupResult(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(SetupResult(storage))
}
}
catch {
DispatchQueue.main.async {
completion(SetupResult(error))
}
}
}
catch {
let storeError = CoreStoreError(error)
CoreStore.log(
storeError,
"Failed to load \(cs_typeName(NSPersistentStore.self)) metadata."
)
DispatchQueue.main.async {
completion(SetupResult(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.
- 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.
- throws: a `CoreStoreError` value indicating the failure
- returns: a `Progress` instance if a migration has started, or `nil` is no migrations are required
*/
public func upgradeStorageIfNeeded<T: LocalStorage>(_ storage: T, completion: @escaping (MigrationResult) -> Void) throws -> Progress? {
return try self.coordinator.performSynchronously {
let fileURL = storage.fileURL
do {
CoreStore.assert(
self.persistentStoreForStorage(storage) == nil,
"Attempted to migrate an already added \(cs_typeName(storage)) at URL \"\(fileURL)\""
)
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
ofType: type(of: storage).storeType,
at: fileURL as URL,
options: storage.storeOptions
)
return self.upgradeStorageIfNeeded(
storage,
metadata: metadata,
completion: completion
)
}
catch {
let metadataError = CoreStoreError(error)
CoreStore.log(
metadataError,
"Failed to load \(cs_typeName(storage)) metadata from URL \"\(fileURL)\"."
)
throw metadataError
}
}
}
/**
Checks the migration steps required for the storage to match the `DataStack`'s managed object model version.
- parameter storage: the local storage
- throws: a `CoreStoreError` value indicating the failure
- returns: a `MigrationType` array indicating the migration steps required for the store, or an empty array if the file does not exist yet. Otherwise, an error is thrown if either inspection of the store failed, or if no mapping model was found/inferred.
*/
public func requiredMigrationsForStorage<T: LocalStorage>(_ storage: T) throws -> [MigrationType] {
return try self.coordinator.performSynchronously {
let fileURL = storage.fileURL
CoreStore.assert(
self.persistentStoreForStorage(storage) == nil,
"Attempted to query required migrations for an already added \(cs_typeName(storage)) at URL \"\(fileURL)\""
)
do {
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
ofType: type(of: storage).storeType,
at: fileURL as URL,
options: storage.storeOptions
)
guard let migrationSteps = self.computeMigrationFromStorage(storage, metadata: metadata) else {
let error = CoreStoreError.mappingModelNotFound(
localStoreURL: fileURL,
targetModel: self.schemaHistory.rawModel,
targetModelVersion: self.modelVersion
)
CoreStore.log(
error,
"Failed to find migration steps from the \(cs_typeName(storage)) at URL \"\(fileURL)\" to version model \"\(self.modelVersion)\"."
)
throw error
}
if migrationSteps.count > 1 && storage.localStorageOptions.contains(.preventProgressiveMigration) {
let error = CoreStoreError.progressiveMigrationRequired(localStoreURL: fileURL)
CoreStore.log(
error,
"Failed to find migration mapping from the \(cs_typeName(storage)) at URL \"\(fileURL)\" to version model \"\(self.modelVersion)\" without requiring progessive migrations."
)
throw error
}
return migrationSteps.map { $0.migrationType }
}
catch let error as NSError
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
return []
}
catch {
let metadataError = CoreStoreError(error)
CoreStore.log(
metadataError,
"Failed to load \(cs_typeName(storage)) metadata from URL \"\(fileURL)\"."
)
throw metadataError
}
}
}
// MARK: Private
private func upgradeStorageIfNeeded<T: LocalStorage>(_ storage: T, metadata: [String: Any], completion: @escaping (MigrationResult) -> Void) -> Progress? {
guard let migrationSteps = self.computeMigrationFromStorage(storage, metadata: metadata) else {
let error = CoreStoreError.mappingModelNotFound(
localStoreURL: storage.fileURL,
targetModel: self.schemaHistory.rawModel,
targetModelVersion: self.modelVersion
)
CoreStore.log(
error,
"Failed to find migration steps from \(cs_typeName(storage)) at URL \"\(storage.fileURL)\" to version model \"\(self.schemaHistory.rawModel)\"."
)
DispatchQueue.main.async {
completion(MigrationResult(error))
}
return nil
}
let numberOfMigrations: Int64 = Int64(migrationSteps.count)
if numberOfMigrations == 0 {
DispatchQueue.main.async {
completion(MigrationResult([]))
return
}
return nil
}
else if numberOfMigrations > 1 && storage.localStorageOptions.contains(.preventProgressiveMigration) {
let error = CoreStoreError.progressiveMigrationRequired(localStoreURL: storage.fileURL)
CoreStore.log(
error,
"Failed to find migration mapping from the \(cs_typeName(storage)) at URL \"\(storage.fileURL)\" to version model \"\(self.modelVersion)\" without requiring progessive migrations."
)
DispatchQueue.main.async {
completion(MigrationResult(error))
}
return nil
}
let migrationTypes = migrationSteps.map { $0.migrationType }
var migrationResult: MigrationResult?
var operations = [Operation]()
var cancelled = false
let progress = Progress(parent: nil, userInfo: nil)
progress.totalUnitCount = numberOfMigrations
for (sourceModel, destinationModel, mappingModel, migrationType) in migrationSteps {
progress.becomeCurrent(withPendingUnitCount: 1)
let childProgress = Progress(parent: progress, userInfo: nil)
childProgress.totalUnitCount = 100
operations.append(
BlockOperation { [weak self] in
guard let `self` = self, !cancelled else {
return
}
autoreleasepool {
do {
try self.startMigrationForStorage(
storage,
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
progress: childProgress
)
}
catch {
let migrationError = CoreStoreError(error)
CoreStore.log(
migrationError,
"Failed to migrate version model \"\(migrationType.sourceVersion)\" to version \"\(migrationType.destinationVersion)\"."
)
migrationResult = MigrationResult(migrationError)
cancelled = true
}
}
DispatchQueue.main.async {
_ = withExtendedLifetime(childProgress) { (_: Progress) -> Void in }
}
}
)
progress.resignCurrent()
}
let migrationOperation = BlockOperation()
migrationOperation.qualityOfService = .utility
operations.forEach { migrationOperation.addDependency($0) }
migrationOperation.addExecutionBlock { () -> Void in
DispatchQueue.main.async {
progress.setProgressHandler(nil)
completion(migrationResult ?? MigrationResult(migrationTypes))
return
}
}
operations.append(migrationOperation)
self.migrationQueue.addOperations(operations, waitUntilFinished: false)
return progress
}
private func computeMigrationFromStorage<T: LocalStorage>(_ storage: T, metadata: [String: Any]) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? {
let schemaHistory = self.schemaHistory
if schemaHistory.rawModel.isConfiguration(withName: storage.configuration, compatibleWithStoreMetadata: metadata) {
return []
}
guard let initialSchema = schemaHistory.schema(for: metadata) else {
return nil
}
var currentVersion = initialSchema.modelVersion
let migrationChain: MigrationChain = schemaHistory.migrationChain.isEmpty
? [currentVersion: schemaHistory.currentModelVersion]
: schemaHistory.migrationChain
var migrationSteps = [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]()
while let nextVersion = migrationChain.nextVersionFrom(currentVersion),
let sourceSchema = schemaHistory.schema(for: currentVersion),
sourceSchema.modelVersion != schemaHistory.currentModelVersion,
let destinationSchema = schemaHistory.schema(for: nextVersion) {
let mappingProviders = storage.migrationMappingProviders
do {
try withExtendedLifetime((sourceSchema.rawModel(), destinationSchema.rawModel())) { (sourceModel, destinationModel) in
let mapping = try mappingProviders.findMapping(
sourceSchema: sourceSchema,
destinationSchema: destinationSchema,
storage: storage
)
migrationSteps.append(
(
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mapping.mappingModel,
migrationType: mapping.migrationType
)
)
}
}
catch {
return nil
}
currentVersion = nextVersion
}
if migrationSteps.last?.destinationModel == schemaHistory.rawModel {
return migrationSteps
}
return nil
}
private func startMigrationForStorage<T: LocalStorage>(_ storage: T, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, progress: Progress) throws {
let fileURL = storage.fileURL
let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
.appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.CoreStore.DataStack")
.appendingPathComponent(ProcessInfo().globallyUniqueString)
let fileManager = FileManager.default
try! fileManager.createDirectory(
at: temporaryDirectoryURL,
withIntermediateDirectories: true,
attributes: nil
)
let temporaryFileURL = temporaryDirectoryURL.appendingPathComponent(
fileURL.lastPathComponent,
isDirectory: false
)
let migrationManager = MigrationManager(
sourceModel: sourceModel,
destinationModel: destinationModel,
progress: progress
)
do {
try migrationManager.migrateStore(
from: fileURL,
sourceType: type(of: storage).storeType,
options: nil,
with: mappingModel,
toDestinationURL: temporaryFileURL,
destinationType: type(of: storage).storeType,
destinationOptions: nil
)
}
catch {
_ = try? fileManager.removeItem(at: temporaryFileURL)
throw CoreStoreError(error)
}
do {
try fileManager.replaceItem(
at: fileURL as URL,
withItemAt: temporaryFileURL,
backupItemName: nil,
options: [],
resultingItemURL: nil
)
progress.completedUnitCount = progress.totalUnitCount
}
catch {
_ = try? fileManager.removeItem(at: temporaryFileURL)
throw CoreStoreError(error)
}
}
}
// MARK: - FilePrivate
fileprivate extension Array where Element == SchemaMappingProvider {
func findMapping(sourceSchema: DynamicSchema, destinationSchema: DynamicSchema, storage: LocalStorage) throws -> (mappingModel: NSMappingModel, migrationType: MigrationType) {
for element in self {
switch element {
case let element as CustomSchemaMappingProvider
where element.sourceVersion == sourceSchema.modelVersion && element.destinationVersion == destinationSchema.modelVersion:
return try element.cs_createMappingModel(from: sourceSchema, to: destinationSchema, storage: storage)
case let element as XcodeSchemaMappingProvider
where element.sourceVersion == sourceSchema.modelVersion && element.destinationVersion == destinationSchema.modelVersion:
return try element.cs_createMappingModel(from: sourceSchema, to: destinationSchema, storage: storage)
default:
continue
}
}
return try InferredSchemaMappingProvider()
.cs_createMappingModel(from: sourceSchema, to: destinationSchema, storage: storage)
}
}

View File

@@ -1,219 +0,0 @@
//
// DataStack+Transaction.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - DataStack
public extension 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(userInfo: T)` in the `completion`'s `Result<T>`. Any errors thrown from inside the `task` will be reported as `.failure(error: 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 func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, completion: @escaping (AsynchronousDataTransaction.Result<T>) -> Void) {
self.perform(
asynchronous: task,
success: { completion(.init(userInfo: $0)) },
failure: { completion(.init(error: $0)) }
)
}
/**
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`.
- parameter failure: the closure executed if the save fails or if any errors are thrown within `task`. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
*/
public func perform<T>(asynchronous task: @escaping (_ transaction: AsynchronousDataTransaction) throws -> T, success: @escaping (T) -> Void, failure: @escaping (CoreStoreError) -> Void) {
let transaction = AsynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue
)
transaction.transactionQueue.cs_async {
defer {
withExtendedLifetime((self, transaction), {})
}
let userInfo: T
do {
userInfo = try task(transaction)
}
catch let error as CoreStoreError {
DispatchQueue.main.async { failure(error) }
return
}
catch let error {
DispatchQueue.main.async { failure(.userError(error: error)) }
return
}
transaction.autoCommit { (_, error) in
if let error = error {
failure(error)
}
else {
success(userInfo)
}
}
}
}
/**
Performs a transaction synchronously where `NSManagedObject` or `CoreStoreObject` creates, updates, and deletes can be made. The changes are commited automatically after the `task` closure returns. On success, the value returned from closure will be the return value of `perform(synchronous:)`. Any errors thrown from inside the `task` will be rethrown from `perform(synchronous:)`. To cancel/rollback changes, call `try transaction.cancel()`, which throws a `CoreStoreError.userCancelled`.
- 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`.
- throws: a `CoreStoreError` value indicating the failure. Cancelled `task`s will be indicated by `CoreStoreError.userCancelled`. Custom errors thrown by the user will be wrapped in `CoreStoreError.userError(error: Error)`.
- returns: the value returned from `task`
*/
public func perform<T>(synchronous task: ((_ transaction: SynchronousDataTransaction) throws -> T), waitForAllObservers: Bool = true) throws -> T {
let transaction = SynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue
)
return try transaction.transactionQueue.cs_sync {
defer {
withExtendedLifetime((self, transaction), {})
}
let userInfo: T
do {
userInfo = try withoutActuallyEscaping(task, do: { try $0(transaction) })
}
catch let error as CoreStoreError {
throw error
}
catch let error {
throw CoreStoreError.userError(error: error)
}
if case (_, let error?) = transaction.autoCommit(waitForMerge: waitForAllObservers) {
throw error
}
else {
return userInfo
}
}
}
/**
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 func beginUnsafe(supportsUndo: Bool = false) -> UnsafeDataTransaction {
return UnsafeDataTransaction(
mainContext: self.rootSavingContext,
queue: DispatchQueue.serial("com.coreStore.dataStack.unsafeTransactionQueue", qos: .userInitiated),
supportsUndo: supportsUndo
)
}
/**
Refreshes all registered objects `NSManagedObject`s or `CoreStoreObject`s in the `DataStack`.
*/
public func refreshAndMergeAllObjects() {
CoreStore.assert(
Thread.isMainThread,
"Attempted to refresh entities outside their designated queue."
)
self.mainContext.refreshAndMergeAllObjects()
}
// MARK: Deprecated
@available(*, deprecated, message: "Use the new auto-commiting methods `perform(asynchronous:completion:)` or `perform(asynchronous:success:failure:)`. Please read the documentation on the behavior of the new methods.")
public func beginAsynchronous(_ closure: @escaping (_ transaction: AsynchronousDataTransaction) -> Void) {
let transaction = AsynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue
)
transaction.transactionQueue.cs_async {
closure(transaction)
if !transaction.isCommitted && transaction.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(transaction)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
}
@available(*, deprecated, message: "Use the new auto-commiting method `perform(synchronous:)`. Please read the documentation on the behavior of the new methods.")
@discardableResult
public func beginSynchronous(_ closure: @escaping (_ transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
let transaction = SynchronousDataTransaction(
mainContext: self.rootSavingContext,
queue: self.childTransactionQueue
)
transaction.transactionQueue.cs_sync {
closure(transaction)
if !transaction.isCommitted && transaction.hasChanges {
CoreStore.log(
.warning,
message: "The closure for the \(cs_typeName(transaction)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
switch transaction.result {
case nil: return nil
case (let hasChanges, nil)?: return SaveResult(hasChanges: hasChanges)
case (_, let error?)?: return SaveResult(error)
}
}
}

View File

@@ -1,665 +0,0 @@
//
// DataStack.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - DataStack
/**
The `DataStack` encapsulates the data model for the Core Data stack. Each `DataStack` can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own `NSPersistentStoreCoordinator`, a root `NSManagedObjectContext` for disk saves, and a shared `NSManagedObjectContext` designed as a read-only model interface for `NSManagedObjects`.
*/
public final class DataStack: Equatable {
/**
Convenience initializer for `DataStack` that creates a `SchemaHistory` from the model with the specified `modelName` in the specified `bundle`.
- parameter xcodeModelName: the name of the (.xcdatamodeld) model file. If not specified, the application name (CFBundleName) will be used if it exists, or "CoreData" if it the bundle name was not set (e.g. in Unit Tests).
- parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used.
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
*/
public convenience init(xcodeModelName: XcodeDataModelFileName = DataStack.applicationName, bundle: Bundle = Bundle.main, migrationChain: MigrationChain = nil) {
self.init(
schemaHistory: SchemaHistory(
XcodeDataModelSchema.from(
modelName: xcodeModelName,
bundle: bundle,
migrationChain: migrationChain
),
migrationChain: migrationChain
)
)
}
/**
Convenience initializer for `DataStack` that creates a `SchemaHistory` from a list of `DynamicSchema` versions.
```
CoreStore.defaultStack = DataStack(
XcodeDataModelSchema(modelName: "MyModelV1"),
CoreStoreSchema(
modelVersion: "MyModelV2",
entities: [
Entity<Animal>("Animal"),
Entity<Person>("Person")
]
),
migrationChain: ["MyModelV1", "MyModelV2"]
)
```
- parameter schema: an instance of `DynamicSchema`
- parameter otherSchema: a list of other `DynamicSchema` instances that represent present/previous/future model versions, in any order
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for progressive migrations. If not specified, will default to a non-migrating data stack.
*/
public convenience init(_ schema: DynamicSchema, _ otherSchema: DynamicSchema..., migrationChain: MigrationChain = nil) {
self.init(
schemaHistory: SchemaHistory(
allSchema: [schema] + otherSchema,
migrationChain: migrationChain
)
)
}
/**
Initializes a `DataStack` from a `SchemaHistory` instance.
```
CoreStore.defaultStack = DataStack(
schemaHistory: SchemaHistory(
XcodeDataModelSchema(modelName: "MyModelV1"),
CoreStoreSchema(
modelVersion: "MyModelV2",
entities: [
Entity<Animal>("Animal"),
Entity<Person>("Person")
]
),
migrationChain: ["MyModelV1", "MyModelV2"]
)
)
```
- parameter schemaHistory: the `SchemaHistory` for the stack
*/
public required init(schemaHistory: SchemaHistory) {
// TODO: test before release (rolled back)
// _ = DataStack.isGloballyInitialized
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: schemaHistory.rawModel)
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
self.schemaHistory = schemaHistory
self.rootSavingContext.parentStack = self
self.mainContext.isDataStackContext = true
}
/**
Returns the `DataStack`'s current model version. `StorageInterface`s added to the stack will be migrated to this version.
*/
public var modelVersion: String {
return self.schemaHistory.currentModelVersion
}
/**
Returns the `DataStack`'s current model schema. `StorageInterface`s added to the stack will be migrated to this version.
*/
public var modelSchema: DynamicSchema {
return self.schemaHistory.schemaByVersion[self.schemaHistory.currentModelVersion]!
}
/**
Returns the entity name-to-class type mapping from the `DataStack`'s model.
*/
public func entityTypesByName(for type: NSManagedObject.Type) -> [EntityName: NSManagedObject.Type] {
var entityTypesByName: [EntityName: NSManagedObject.Type] = [:]
for (entityIdentifier, entityDescription) in self.schemaHistory.entityDescriptionsByEntityIdentifier {
switch entityIdentifier.category {
case .coreData:
let actualType = NSClassFromString(entityDescription.managedObjectClassName!)! as! NSManagedObject.Type
if (actualType as AnyClass).isSubclass(of: type) {
entityTypesByName[entityDescription.name!] = actualType
}
case .coreStore:
continue
}
}
return entityTypesByName
}
/**
Returns the entity name-to-class type mapping from the `DataStack`'s model.
*/
public func entityTypesByName(for type: CoreStoreObject.Type) -> [EntityName: CoreStoreObject.Type] {
var entityTypesByName: [EntityName: CoreStoreObject.Type] = [:]
for (entityIdentifier, entityDescription) in self.schemaHistory.entityDescriptionsByEntityIdentifier {
switch entityIdentifier.category {
case .coreData:
continue
case .coreStore:
guard let anyEntity = entityDescription.coreStoreEntity else {
continue
}
let actualType = anyEntity.type
if (actualType as AnyClass).isSubclass(of: type) {
entityTypesByName[entityDescription.name!] = (actualType as! CoreStoreObject.Type)
}
}
}
return entityTypesByName
}
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass.
*/
public func entityDescription(for type: NSManagedObject.Type) -> NSEntityDescription? {
return self.entityDescription(for: EntityIdentifier(type))
}
/**
Returns the `NSEntityDescription` for the specified `CoreStoreObject` subclass.
*/
public func entityDescription(for type: CoreStoreObject.Type) -> NSEntityDescription? {
return self.entityDescription(for: EntityIdentifier(type))
}
/**
Returns the `NSManagedObjectID` for the specified object URI if it exists in the persistent store.
*/
public func objectID(forURIRepresentation url: URL) -> NSManagedObjectID? {
return self.coordinator.managedObjectID(forURIRepresentation: url)
}
/**
Creates an `SQLiteStore` with default parameters and adds it to the stack. This method blocks until completion.
```
try dataStack.addStorageAndWait()
```
- throws: a `CoreStoreError` value indicating the failure
- returns: the local SQLite storage added to the stack
*/
@discardableResult
public func addStorageAndWait() throws -> SQLiteStore {
return try self.addStorageAndWait(SQLiteStore())
}
/**
Adds a `StorageInterface` to the stack and blocks until completion.
```
try dataStack.addStorageAndWait(InMemoryStore(configuration: "Config1"))
```
- parameter storage: the `StorageInterface`
- throws: a `CoreStoreError` value indicating the failure
- returns: the `StorageInterface` added to the stack
*/
@discardableResult
public func addStorageAndWait<T: StorageInterface>(_ storage: T) throws -> T {
do {
return try self.coordinator.performSynchronously {
if let _ = self.persistentStoreForStorage(storage) {
return storage
}
_ = try self.createPersistentStoreFromStorage(
storage,
finalURL: nil,
finalStoreOptions: storage.storeOptions
)
return storage
}
}
catch {
let storeError = CoreStoreError(error)
CoreStore.log(
storeError,
"Failed to add \(cs_typeName(storage)) to the stack."
)
throw storeError
}
}
/**
Adds a `LocalStorage` to the stack and blocks until completion.
```
try dataStack.addStorageAndWait(SQLiteStore(configuration: "Config1"))
```
- parameter storage: the local storage
- throws: a `CoreStoreError` value indicating the failure
- returns: the local storage added to the stack. 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 func addStorageAndWait<T: LocalStorage>(_ storage: T) throws -> T {
return try self.coordinator.performSynchronously {
let fileURL = storage.fileURL
CoreStore.assert(
fileURL.isFileURL,
"The specified store URL for the \"\(cs_typeName(storage))\" is invalid: \"\(fileURL)\""
)
if let _ = self.persistentStoreForStorage(storage) {
return storage
}
if let persistentStore = self.coordinator.persistentStore(for: fileURL as URL) {
if let existingStorage = persistentStore.storageInterface as? T,
storage.matchesPersistentStore(persistentStore) {
return existingStorage
}
let error = CoreStoreError.differentStorageExistsAtURL(existingPersistentStoreURL: fileURL)
CoreStore.log(
error,
"Failed to add \(cs_typeName(storage)) at \"\(fileURL)\" because a different \(cs_typeName(NSPersistentStore.self)) at that URL already exists."
)
throw error
}
do {
var localStorageOptions = storage.localStorageOptions
localStorageOptions.remove(.recreateStoreOnModelMismatch)
let storeOptions = storage.dictionary(forOptions: localStorageOptions)
do {
try FileManager.default.createDirectory(
at: fileURL.deletingLastPathComponent(),
withIntermediateDirectories: true,
attributes: nil
)
_ = try self.createPersistentStoreFromStorage(
storage,
finalURL: fileURL,
finalStoreOptions: storeOptions
)
return storage
}
catch let error as NSError where storage.localStorageOptions.contains(.recreateStoreOnModelMismatch) && error.isCoreDataMigrationError {
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(
ofType: type(of: storage).storeType,
at: fileURL,
options: storeOptions
)
try storage.cs_eraseStorageAndWait(
metadata: metadata,
soureModelHint: self.schemaHistory.schema(for: metadata)?.rawModel()
)
let finalStoreOptions = storage.dictionary(forOptions: storage.localStorageOptions)
_ = try self.createPersistentStoreFromStorage(
storage,
finalURL: fileURL,
finalStoreOptions: finalStoreOptions
)
return storage
}
}
catch {
let storeError = CoreStoreError(error)
CoreStore.log(
storeError,
"Failed to add \(cs_typeName(storage)) to the stack."
)
throw storeError
}
}
}
/**
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)
CoreStore.log(
error,
"Failed to add \(cs_typeName(storage)) at \"\(cacheFileURL)\" because a different \(cs_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)
CoreStore.log(
storeError,
"Failed to add \(cs_typeName(storage)) to the stack."
)
throw storeError
}
}
}
// MARK: 3rd Party Utilities
/**
Allow external libraries to store custom data in the `DataStack`. App code should rarely have a need for this.
```
enum Static {
static var myDataKey: Void?
}
CoreStore.defaultStack.userInfo[&Static.myDataKey] = myObject
```
- Important: Do not use this method to store thread-sensitive data.
*/
public let userInfo = UserInfo()
// MARK: Equatable
public static func == (lhs: DataStack, rhs: DataStack) -> Bool {
return lhs === rhs
}
// MARK: Internal
internal static var defaultConfigurationName = "PF_DEFAULT_CONFIGURATION_NAME"
internal static let applicationName = (Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String) ?? "CoreData"
internal let coordinator: NSPersistentStoreCoordinator
internal let rootSavingContext: NSManagedObjectContext
internal let mainContext: NSManagedObjectContext
internal let schemaHistory: SchemaHistory
internal let childTransactionQueue = DispatchQueue.serial("com.coreStore.dataStack.childTransactionQueue")
internal let storeMetadataUpdateQueue = DispatchQueue.concurrent("com.coreStore.persistentStoreBarrierQueue")
internal let migrationQueue: OperationQueue = cs_lazy {
let migrationQueue = OperationQueue()
migrationQueue.maxConcurrentOperationCount = 1
migrationQueue.name = "com.coreStore.migrationOperationQueue"
migrationQueue.qualityOfService = .utility
migrationQueue.underlyingQueue = DispatchQueue.serial("com.coreStore.migrationQueue", qos: .userInitiated)
return migrationQueue
}
internal func persistentStoreForStorage(_ storage: StorageInterface) -> NSPersistentStore? {
return self.coordinator.persistentStores
.filter { $0.storageInterface === storage }
.first
}
internal func persistentStores(for entityIdentifier: EntityIdentifier) -> [NSPersistentStore]? {
var returnValue: [NSPersistentStore]? = nil
self.storeMetadataUpdateQueue.sync(flags: .barrier) {
returnValue = self.finalConfigurationsByEntityIdentifier[entityIdentifier]?
.map({ self.persistentStoresByFinalConfiguration[$0]! }) ?? []
}
return returnValue
}
internal func persistentStore(for entityIdentifier: EntityIdentifier, configuration: ModelConfiguration, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
return self.storeMetadataUpdateQueue.sync(flags: .barrier) { () -> (store: NSPersistentStore?, isAmbiguous: Bool) in
let configurationsForEntity = self.finalConfigurationsByEntityIdentifier[entityIdentifier] ?? []
if let configuration = configuration {
if configurationsForEntity.contains(configuration) {
return (store: self.persistentStoresByFinalConfiguration[configuration], isAmbiguous: false)
}
else if !inferStoreIfPossible {
return (store: nil, isAmbiguous: false)
}
}
switch configurationsForEntity.count {
case 0:
return (store: nil, isAmbiguous: false)
case 1 where inferStoreIfPossible:
return (store: self.persistentStoresByFinalConfiguration[configurationsForEntity.first!], isAmbiguous: false)
default:
return (store: nil, isAmbiguous: true)
}
}
}
internal func createPersistentStoreFromStorage(_ storage: StorageInterface, finalURL: URL?, finalStoreOptions: [AnyHashable: Any]?) throws -> NSPersistentStore {
let persistentStore = try self.coordinator.addPersistentStore(
ofType: type(of: storage).storeType,
configurationName: storage.configuration,
at: finalURL,
options: finalStoreOptions
)
persistentStore.storageInterface = storage
self.storeMetadataUpdateQueue.async(flags: .barrier) {
let configurationName = persistentStore.configurationName
self.persistentStoresByFinalConfiguration[configurationName] = persistentStore
for entityDescription in (self.coordinator.managedObjectModel.entities(forConfigurationName: configurationName) ?? []) {
let managedObjectClassName = entityDescription.managedObjectClassName!
CoreStore.assert(
NSClassFromString(managedObjectClassName) != nil,
"The class \(cs_typeName(managedObjectClassName)) for the entity \(cs_typeName(entityDescription.name)) does not exist. Check if the subclass type and module name are properly configured."
)
let entityIdentifier = EntityIdentifier(entityDescription)
if self.finalConfigurationsByEntityIdentifier[entityIdentifier] == nil {
self.finalConfigurationsByEntityIdentifier[entityIdentifier] = []
}
self.finalConfigurationsByEntityIdentifier[entityIdentifier]?.insert(configurationName)
}
}
storage.cs_didAddToDataStack(self)
return persistentStore
}
internal func entityDescription(for entityIdentifier: EntityIdentifier) -> NSEntityDescription? {
return self.schemaHistory.entityDescriptionsByEntityIdentifier[entityIdentifier]
}
// MARK: Private
// TODO: test before release (rolled back)
// private static let isGloballyInitialized: Bool = {
//
// NSManagedObject.cs_swizzleMethodsForLogging()
// return true
// }()
private var persistentStoresByFinalConfiguration = [String: NSPersistentStore]()
private var finalConfigurationsByEntityIdentifier = [EntityIdentifier: Set<String>]()
deinit {
let coordinator = self.coordinator
coordinator.performAsynchronously {
withExtendedLifetime(coordinator) { coordinator in
coordinator.persistentStores.forEach {
_ = try? coordinator.remove($0)
}
}
}
}
// MARK: Deprecated
@available(*, deprecated, renamed: "init(xcodeModelName:bundle:migrationChain:)")
public convenience init(modelName: XcodeDataModelFileName, bundle: Bundle = Bundle.main, migrationChain: MigrationChain = nil) {
self.init(
xcodeModelName: modelName,
bundle: bundle,
migrationChain: migrationChain
)
}
@available(*, deprecated, message: "Use the new DataStack.init(schemaHistory:) initializer passing an UnsafeDataModelSchema instance as argument")
public convenience init(model: NSManagedObjectModel, migrationChain: MigrationChain = nil) {
let modelVersion = migrationChain.leafVersions.first!
self.init(
schemaHistory: SchemaHistory(
allSchema: [
UnsafeDataModelSchema(
modelName: modelVersion,
model: model
)
],
migrationChain: migrationChain,
exactCurrentModelVersion: modelVersion
)
)
}
@available(*, deprecated, message: "Use the new DataStack.entityTypesByName(for:) method passing `NSManagedObject.self` as argument.")
public var entityTypesByName: [EntityName: NSManagedObject.Type] {
return self.entityTypesByName(for: NSManagedObject.self)
}
// MARK: Obsolete
@available(*, obsoleted: 3.1, renamed: "entityDescription(for:)")
public func entityDescriptionForType(_ type: NSManagedObject.Type) -> NSEntityDescription? {
return self.entityDescription(for: type)
}
@available(*, obsoleted: 3.1, renamed: "objectID(forURIRepresentation:)")
public func objectIDForURIRepresentation(_ url: URL) -> NSManagedObjectID? {
return self.objectID(forURIRepresentation: url)
}
}

View File

@@ -1,97 +0,0 @@
//
// DispatchQueue+CoreStore.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - DispatchQueue
internal extension DispatchQueue {
@nonobjc @inline(__always)
internal static func serial(_ label: String, qos: DispatchQoS = .default) -> DispatchQueue {
return DispatchQueue(
label: label,
qos: qos,
attributes: [],
autoreleaseFrequency: .inherit,
target: nil
)
}
@nonobjc @inline(__always)
internal static func concurrent(_ label: String, qos: DispatchQoS = .default) -> DispatchQueue {
return DispatchQueue(
label: label,
qos: qos,
attributes: .concurrent,
autoreleaseFrequency: .inherit,
target: nil
)
}
@nonobjc
internal func cs_isCurrentExecutionContext() -> Bool {
enum Static {
static let specificKey = DispatchSpecificKey<ObjectIdentifier>()
}
let specific = ObjectIdentifier(self)
self.setSpecific(key: Static.specificKey, value: specific)
return DispatchQueue.getSpecific(key: Static.specificKey) == specific
}
@nonobjc @inline(__always)
internal func cs_sync<T>(_ closure: () throws -> T) rethrows -> T {
return try self.sync { try autoreleasepool(invoking: closure) }
}
@nonobjc @inline(__always)
internal func cs_async(_ closure: @escaping () -> Void) {
self.async { autoreleasepool(invoking: closure) }
}
@nonobjc @inline(__always)
internal func cs_barrierSync<T>(_ closure: () throws -> T) rethrows -> T {
return try self.sync(flags: .barrier) { try autoreleasepool(invoking: closure) }
}
@nonobjc @inline(__always)
internal func cs_barrierAsync(_ closure: @escaping () -> Void) {
self.async(flags: .barrier) { autoreleasepool(invoking: closure) }
}
// MARK: Private
}

View File

@@ -1,168 +0,0 @@
//
// DynamicObject.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - DynamicObject
/**
All CoreStore's utilities are designed around `DynamicObject` instances. `NSManagedObject` and `CoreStoreObject` instances all conform to `DynamicObject`.
*/
public protocol DynamicObject: class {
/**
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_fromRaw(object: NSManagedObject) -> Self
/**
Used internally by CoreStore. Do not call directly.
*/
static func cs_matches(object: NSManagedObject) -> Bool
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_id() -> NSManagedObjectID
/**
Used internally by CoreStore. Do not call directly.
*/
func cs_toRaw() -> NSManagedObject
}
// MARK: - NSManagedObject
extension NSManagedObject: DynamicObject {
// MARK: DynamicObject
public class func cs_forceCreate(entityDescription: NSEntityDescription, into context: NSManagedObjectContext, assignTo store: NSPersistentStore) -> Self {
let object = self.init(entity: entityDescription, insertInto: context)
defer {
context.assign(object, to: store)
}
return object
}
public class func cs_fromRaw(object: NSManagedObject) -> Self {
@inline(__always)
func forceCast<T: NSManagedObject>(_ value: Any) -> T {
return value as! T
}
return forceCast(object)
}
public static func cs_matches(object: NSManagedObject) -> Bool {
return object.isKind(of: self)
}
public func cs_id() -> NSManagedObjectID {
return self.objectID
}
public func cs_toRaw() -> NSManagedObject {
return self
}
}
// MARK: - CoreStoreObject
extension CoreStoreObject {
// MARK: DynamicObject
public class func cs_forceCreate(entityDescription: NSEntityDescription, into context: NSManagedObjectContext, assignTo store: NSPersistentStore) -> Self {
let type = NSClassFromString(entityDescription.managedObjectClassName!)! as! NSManagedObject.Type
let object = type.init(entity: entityDescription, insertInto: context)
defer {
context.assign(object, to: store)
}
return self.cs_fromRaw(object: object)
}
public class func cs_fromRaw(object: NSManagedObject) -> Self {
if let coreStoreObject = object.coreStoreObject {
@inline(__always)
func forceCast<T: CoreStoreObject>(_ value: CoreStoreObject) -> T {
return value as! T
}
return forceCast(coreStoreObject)
}
let coreStoreObject = self.init(rawObject: object)
object.coreStoreObject = coreStoreObject
return coreStoreObject
}
public static func cs_matches(object: NSManagedObject) -> Bool {
guard let type = object.entity.coreStoreEntity?.type else {
return false
}
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!
}
}
// MARK: - Internal
internal extension DynamicObject where Self: CoreStoreObject {
internal static var meta: Self {
return self.init(asMeta: ())
}
}

View File

@@ -1,296 +0,0 @@
//
// DynamicSchema+Convenience.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - DynamicSchema
public extension DynamicSchema {
/**
Prints the `DynamicSchema` as their corresponding `CoreStoreObject` Swift declarations. This is useful for converting current `XcodeDataModelSchema`-based models into the new `CoreStoreSchema` framework. Additional adjustments may need to be done to the generated source code; for example: `Transformable` concrete types need to be provided, as well as `default` values.
- Important: After transitioning to the new `CoreStoreSchema` framework, it is recommended to add the new schema as a new version that the existing versions' `XcodeDataModelSchema` can migrate to. It is discouraged to load existing SQLite files created with `XcodeDataModelSchema` directly into a `CoreStoreSchema`.
- returns: a string that represents the source code for the `DynamicSchema` as their corresponding `CoreStoreObject` Swift declarations.
*/
public func printCoreStoreSchema() -> String {
let model = self.rawModel()
let entitiesByName = model.entitiesByName
var output = "/// Generated by CoreStore on \(DateFormatter.localizedString(from: Date(), dateStyle: .short, timeStyle: .short))\n"
var addedInverse: Set<String> = []
for (entityName, entity) in entitiesByName {
let superName: String
if let superEntity = entity.superentity {
superName = superEntity.name!
}
else {
superName = String(describing: CoreStoreObject.self)
}
output.append("class \(entityName): \(superName) {\n")
defer {
output.append("}\n")
}
let attributesByName = entity.attributesByName
if !attributesByName.isEmpty {
output.append(" \n")
for (attributeName, attribute) in attributesByName {
let containerType: String
if attribute.attributeType == .transformableAttributeType {
if attribute.isOptional {
containerType = "Transformable.Optional"
}
else {
containerType = "Transformable.Required"
}
}
else {
if attribute.isOptional {
containerType = "Value.Optional"
}
else {
containerType = "Value.Required"
}
}
let valueType: Any.Type
var defaultString = ""
switch attribute.attributeType {
case .integer16AttributeType:
valueType = Int16.self
if let defaultValue = (attribute.defaultValue as! Int16.ImportableNativeType?).flatMap(Int16.cs_fromImportableNativeType),
defaultValue != Int16.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .integer32AttributeType:
valueType = Int32.self
if let defaultValue = (attribute.defaultValue as! Int32.ImportableNativeType?).flatMap(Int32.cs_fromImportableNativeType),
defaultValue != Int32.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .integer64AttributeType:
valueType = Int64.self
if let defaultValue = (attribute.defaultValue as! Int64.ImportableNativeType?).flatMap(Int64.cs_fromImportableNativeType),
defaultValue != Int64.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .decimalAttributeType:
valueType = NSDecimalNumber.self
if let defaultValue = (attribute.defaultValue as! NSDecimalNumber.ImportableNativeType?).flatMap(NSDecimalNumber.cs_fromImportableNativeType),
defaultValue != NSDecimalNumber.cs_emptyValue() {
defaultString = ", default: NSDecimalNumber(string: \"\(defaultValue.description(withLocale: nil))\")"
}
case .doubleAttributeType:
valueType = Double.self
if let defaultValue = (attribute.defaultValue as! Double.ImportableNativeType?).flatMap(Double.cs_fromImportableNativeType),
defaultValue != Double.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .floatAttributeType:
valueType = Float.self
if let defaultValue = (attribute.defaultValue as! Float.ImportableNativeType?).flatMap(Float.cs_fromImportableNativeType),
defaultValue != Float.cs_emptyValue() {
defaultString = ", default: \(defaultValue)"
}
case .stringAttributeType:
valueType = String.self
if let defaultValue = (attribute.defaultValue as! String.ImportableNativeType?).flatMap(String.cs_fromImportableNativeType),
defaultValue != String.cs_emptyValue() {
// TODO: escape strings
defaultString = ", default: \"\(defaultValue)\""
}
case .booleanAttributeType:
valueType = Bool.self
if let defaultValue = (attribute.defaultValue as! Bool.ImportableNativeType?).flatMap(Bool.cs_fromImportableNativeType),
defaultValue != Bool.cs_emptyValue() {
defaultString = ", default: \(defaultValue ? "true" : "false")"
}
case .dateAttributeType:
valueType = Date.self
if let defaultValue = (attribute.defaultValue as! Date.ImportableNativeType?).flatMap(Date.cs_fromImportableNativeType) {
defaultString = ", default: Date(timeIntervalSinceReferenceDate: \(defaultValue.timeIntervalSinceReferenceDate))"
}
case .binaryDataAttributeType:
valueType = Data.self
if let defaultValue = (attribute.defaultValue as! Data.ImportableNativeType?).flatMap(Data.cs_fromImportableNativeType),
defaultValue != Data.cs_emptyValue() {
let count = defaultValue.count
let bytes = defaultValue.withUnsafeBytes { (pointer: UnsafePointer<UInt8>) in
return (0 ..< (count / MemoryLayout<UInt8>.size))
.map({ "\("0x\(String(pointer[$0], radix: 16, uppercase: false))")" })
}
defaultString = ", default: Data(bytes: [\(bytes.joined(separator: ", "))])"
}
case .transformableAttributeType:
if let attributeValueClassName = attribute.attributeValueClassName {
valueType = NSClassFromString(attributeValueClassName)!
}
else {
valueType = (NSCoding & NSCopying).self
}
if let defaultValue = attribute.defaultValue {
defaultString = ", default: /* \"\(defaultValue)\" */"
}
else if !attribute.isOptional {
defaultString = ", default: /* required */"
}
default:
fatalError("Unsupported attribute type: \(attribute.attributeType.rawValue)")
}
let indexedString = attribute.isIndexed ? ", isIndexed: true" : ""
let transientString = attribute.isTransient ? ", isTransient: true" : ""
// TODO: escape strings
let versionHashModifierString = attribute.versionHashModifier
.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
// TODO: escape strings
let renamingIdentifierString = attribute.renamingIdentifier
.flatMap({ ($0 == attributeName ? "" : ", renamingIdentifier: \"\($0)\"") as String }) ?? ""
output.append(" let \(attributeName) = \(containerType)<\(String(describing: valueType))>(\"\(attributeName)\"\(indexedString)\(defaultString)\(transientString)\(versionHashModifierString)\(renamingIdentifierString))\n")
}
}
let relationshipsByName = entity.relationshipsByName
if !relationshipsByName.isEmpty {
output.append(" \n")
for (relationshipName, relationship) in relationshipsByName {
let containerType: String
var minCountString = ""
var maxCountString = ""
if relationship.isToMany {
let minCount = relationship.minCount
let maxCount = relationship.maxCount
if relationship.isOrdered {
containerType = "Relationship.ToManyOrdered"
}
else {
containerType = "Relationship.ToManyUnordered"
}
if minCount > 0 {
minCountString = ", minCount: \(minCount)"
}
if maxCount > 0 {
maxCountString = ", maxCount: \(maxCount)"
}
}
else {
containerType = "Relationship.ToOne"
}
var inverseString = ""
let relationshipQualifier = "\(entityName).\(relationshipName)"
if !addedInverse.contains(relationshipQualifier),
let inverseRelationship = relationship.inverseRelationship {
inverseString = ", inverse: { $0.\(inverseRelationship.name) }"
addedInverse.insert("\(relationship.destinationEntity!.name!).\(inverseRelationship.name)")
}
var deleteRuleString = ""
if relationship.deleteRule != .nullifyDeleteRule {
switch relationship.deleteRule {
case .cascadeDeleteRule:
deleteRuleString = ", deleteRule: .cascade"
case .denyDeleteRule:
deleteRuleString = ", deleteRule: .deny"
case .nullifyDeleteRule:
deleteRuleString = ", deleteRule: .nullify"
default:
fatalError("Unsupported delete rule \((relationship.deleteRule)) for relationship \"\(relationshipQualifier)\"")
}
}
let versionHashModifierString = relationship.versionHashModifier
.flatMap({ ", versionHashModifier: \"\($0)\"" }) ?? ""
let renamingIdentifierString = relationship.renamingIdentifier
.flatMap({ ($0 == relationshipName ? "" : ", renamingIdentifier: \"\($0)\"") as String }) ?? ""
output.append(" let \(relationshipName) = \(containerType)<\(relationship.destinationEntity!.name!)>(\"\(relationshipName)\"\(inverseString)\(deleteRuleString)\(minCountString)\(maxCountString)\(versionHashModifierString)\(renamingIdentifierString))\n")
}
}
}
output.append("\n\n\n")
output.append("CoreStoreSchema(\n")
output.append(" modelVersion: \"\(self.modelVersion)\",\n")
output.append(" entities: [\n")
for (entityName, entity) in entitiesByName {
var abstractString = ""
if entity.isAbstract {
abstractString = ", isAbstract: true"
}
var versionHashModifierString = ""
if let versionHashModifier = entity.versionHashModifier {
versionHashModifierString = ", versionHashModifier: \"\(versionHashModifier)\""
}
output.append(" Entity<\(entityName)>(\"\(entityName)\"\(abstractString)\(versionHashModifierString)),\n")
}
output.append(" ],\n")
output.append(" versionLock: \(VersionLock(entityVersionHashesByName: model.entityVersionHashesByName).description.components(separatedBy: "\n").joined(separator: "\n "))\n")
output.append(")\n\n")
return output
}
}

View File

@@ -1,49 +0,0 @@
//
// DynamicSchema.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - DynamicSchema
/**
`DynamicSchema` are types that provide `NSManagedObjectModel` instances for a single model version. CoreStore currently supports the following concrete types:
- `XcodeDataModelSchema`: describes models loaded from a .xcdatamodeld file.
- `UnsafeDataModelSchema`: describes models loaded directly from an existing `NSManagedObjectModel`. It is not advisable to continue using this model as its metadata are not available to CoreStore.
- `CoreStoreSchema`: describes models written for `CoreStoreObject` Swift class declarations.
*/
public protocol DynamicSchema {
/**
The version string for this model schema.
*/
var modelVersion: ModelVersion { get }
/**
Do not call this directly. The `NSManagedObjectModel` for this schema may be created lazily and using this method directly may affect the integrity of the model.
*/
func rawModel() -> NSManagedObjectModel
}

View File

@@ -1,151 +0,0 @@
//
// Entity.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
import ObjectiveC
// MARK: Entity
/**
The `Entity<O>` contains `NSEntityDescription` metadata for `CoreStoreObject` subclasses. Pass the `Entity` instances to `CoreStoreSchema` initializer.
```
class Animal: CoreStoreObject {
let species = Value.Required<String>("species")
let nickname = Value.Optional<String>("nickname")
let master = Relationship.ToOne<Person>("master")
}
class Person: CoreStoreObject {
let name = Value.Required<String>("name")
let pet = Relationship.ToOne<Animal>("pet", inverse: { $0.master })
}
CoreStore.defaultStack = DataStack(
CoreStoreSchema(
modelVersion: "V1",
entities: [
Entity<Animal>("Animal"),
Entity<Person>("Person")
]
)
)
```
- SeeAlso: CoreStoreSchema
- SeeAlso: CoreStoreObject
*/
public final class Entity<O: DynamicObject>: DynamicEntity {
/**
Initializes an `Entity`. Always provide a concrete generic type to `Entity`.
```
Entity<Animal>("Animal")
```
- parameter entityName: the `NSEntityDescription` name to use for the entity
- parameter isAbstract: set to `true` if the entity is meant to be an abstract class and can only be initialized with subclass types.
- parameter versionHashModifier: The version hash modifier for the entity. Used to mark or denote an entity as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where, for example, the structure of an entity is unchanged but the format or content of data has changed.)
*/
public convenience init(_ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) {
self.init(O.self, entityName, isAbstract: isAbstract, versionHashModifier: versionHashModifier)
}
/**
Initializes an `Entity`.
```
Entity(Animal.self, "Animal")
```
- parameter type: the `DynamicObject` type associated with the entity
- parameter entityName: the `NSEntityDescription` name to use for the entity
- parameter isAbstract: set to `true` if the entity is meant to be an abstract class and can only be initialized with subclass types.
- parameter versionHashModifier: The version hash modifier for the entity. Used to mark or denote an entity as being a different "version" than another even if all of the values which affect persistence are equal. (Such a difference is important in cases where, for example, the structure of an entity is unchanged but the format or content of data has changed.)
*/
public init(_ type: O.Type, _ entityName: String, isAbstract: Bool = false, versionHashModifier: String? = nil) {
super.init(type: type, entityName: entityName, isAbstract: isAbstract, versionHashModifier: versionHashModifier)
}
}
// MARK: - DynamicEntity
/**
Use concrete instances of `Entity<O>` in API that accept `DynamicEntity` arguments.
*/
public /*abstract*/ class DynamicEntity: Hashable {
/**
Do not use directly.
*/
public let type: DynamicObject.Type
/**
Do not use directly.
*/
public let entityName: EntityName
/**
Do not use directly.
*/
public let isAbstract: Bool
/**
Do not use directly.
*/
public let versionHashModifier: String?
// MARK: Equatable
public static func == (lhs: DynamicEntity, rhs: DynamicEntity) -> Bool {
return lhs.type == rhs.type
&& lhs.entityName == rhs.entityName
&& lhs.isAbstract == rhs.isAbstract
&& lhs.versionHashModifier == rhs.versionHashModifier
}
// MARK: Hashable
public var hashValue: Int {
return ObjectIdentifier(self.type).hashValue
^ self.entityName.hashValue
^ self.isAbstract.hashValue
^ (self.versionHashModifier ?? "").hashValue
}
// MARK: Internal
internal init(type: DynamicObject.Type, entityName: String, isAbstract: Bool = false, versionHashModifier: String?) {
self.type = type
self.entityName = entityName
self.isAbstract = isAbstract
self.versionHashModifier = versionHashModifier
}
}

View File

@@ -1,106 +0,0 @@
//
// EntityIdentifier.swift
// CoreStore
//
// Copyright © 2017 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import CoreData
import Foundation
// MARK: - EntityIdentifier
internal struct EntityIdentifier: Hashable {
// MARK: - Category
internal enum Category: Int {
case coreData
case coreStore
}
// MARK: -
internal let category: Category
internal let interfacedClassName: String
internal init(_ type: NSManagedObject.Type) {
self.category = .coreData
self.interfacedClassName = NSStringFromClass(type)
}
internal init(_ type: CoreStoreObject.Type) {
self.category = .coreStore
self.interfacedClassName = NSStringFromClass(type)
}
internal init(_ type: DynamicObject.Type) {
switch type {
case let type as NSManagedObject.Type:
self.init(type)
case let type as CoreStoreObject.Type:
self.init(type)
default:
CoreStore.abort("\(cs_typeName(DynamicObject.self)) is not meant to be implemented by external types.")
}
}
internal init(_ entityDescription: NSEntityDescription) {
if let anyEntity = entityDescription.coreStoreEntity {
self.category = .coreStore
self.interfacedClassName = NSStringFromClass(anyEntity.type)
}
else {
self.category = .coreData
self.interfacedClassName = entityDescription.managedObjectClassName!
}
}
// MARK: Equatable
static func == (lhs: EntityIdentifier, rhs: EntityIdentifier) -> Bool {
return lhs.category == rhs.category
&& lhs.interfacedClassName == rhs.interfacedClassName
}
// MARK: Hashable
var hashValue: Int {
return self.category.hashValue
^ self.interfacedClassName.hashValue
}
}

View File

@@ -1,163 +0,0 @@
//
// FetchableSource.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - FetchableSource
/**
Encapsulates containers which manages an internal `NSManagedObjectContext`, such as `DataStack`s and transactions, that can be used for fetching objects. CoreStore provides implementations for this protocol and should be used as a read-only abstraction.
*/
public protocol FetchableSource: class {
/**
Fetches the `DynamicObject` instance in the `FetchableSource`'s context from a reference created from another managed object context.
- 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<T: DynamicObject>(_ object: T) -> T?
/**
Fetches the `DynamicObject` instance in the `FetchableSource`'s context from an `NSManagedObjectID`.
- 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<T: DynamicObject>(_ objectID: NSManagedObjectID) -> T?
/**
Fetches the `DynamicObject` instances in the `FetchableSource`'s context from references created from another managed object context.
- 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<T: DynamicObject, S: Sequence>(_ objects: S) -> [T] where S.Iterator.Element == T
/**
Fetches the `DynamicObject` instances in the `FetchableSource`'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 `FetchableSource`'s context
*/
func fetchExisting<T: DynamicObject, S: Sequence>(_ objectIDs: S) -> [T] where S.Iterator.Element == NSManagedObjectID
/**
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
*/
func fetchOne<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> T?
/**
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
*/
func fetchOne<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> T?
/**
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
*/
func fetchAll<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> [T]?
/**
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
*/
func fetchAll<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> [T]?
/**
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 `DynamicObject`s that satisfy the specified `FetchClause`s
*/
func fetchCount<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> Int?
/**
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 `DynamicObject`s that satisfy the specified `FetchClause`s
*/
func fetchCount<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> Int?
/**
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
*/
func fetchObjectID<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID?
/**
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
*/
func fetchObjectID<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID?
/**
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
*/
func fetchObjectIDs<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]?
/**
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
*/
func fetchObjectIDs<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]?
/**
The internal `NSManagedObjectContext` managed by this `FetchableSource`. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases.
*/
func unsafeContext() -> NSManagedObjectContext
}

View File

@@ -29,99 +29,92 @@ import CoreData
// MARK: - DataTransaction
extension BaseDataTransaction: FetchableSource, QueryableSource {
public extension BaseDataTransaction {
/**
Deletes all `DynamicObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `DynamicObject`s deleted
*/
@discardableResult
public func deleteAll<T: DynamicObject>(_ from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.deleteAll(from, deleteClauses)
}
/**
Deletes all `DynamicObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `DynamicObject`s deleted
*/
@discardableResult
public func deleteAll<T: DynamicObject>(_ from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.deleteAll(from, deleteClauses)
}
// MARK: FetchableSource
/**
Fetches the `DynamicObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context.
Fetches the `NSManagedObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context.
- parameter object: a reference to the object created/fetched outside the transaction
- returns: the `DynamicObject` instance if the object exists in the transaction, or `nil` if not found.
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
*/
public func fetchExisting<T: DynamicObject>(_ object: T) -> T? {
@warn_unused_result
public func fetchExisting<T: NSManagedObject>(object: T) -> T? {
return self.context.fetchExisting(object)
do {
return (try self.context.existingObjectWithID(object.objectID) as! T)
}
catch _ {
return nil
}
}
/**
Fetches the `DynamicObject` instance in the transaction's context from an `NSManagedObjectID`.
Fetches the `NSManagedObject` instance in the transaction's context from an `NSManagedObjectID`.
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `DynamicObject` instance if the object exists in the transaction, or `nil` if not found.
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
*/
public func fetchExisting<T: DynamicObject>(_ objectID: NSManagedObjectID) -> T? {
@warn_unused_result
public func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
return self.context.fetchExisting(objectID)
do {
return (try self.context.existingObjectWithID(objectID) as! T)
}
catch _ {
return nil
}
}
/**
Fetches the `DynamicObject` instances in the transaction's context from references created from a transaction or from a different managed object context.
Fetches the `NSManagedObject` instances in the transaction's context from references created from a transaction or from a different managed object context.
- parameter objects: an array of `DynamicObject`s created/fetched outside the transaction
- returns: the `DynamicObject` array for objects that exists in the transaction
- parameter objects: an array of `NSManagedObject`s created/fetched outside the transaction
- returns: the `NSManagedObject` array for objects that exists in the transaction
*/
public func fetchExisting<T: DynamicObject, S: Sequence>(_ objects: S) -> [T] where S.Iterator.Element == T {
@warn_unused_result
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == T>(objects: S) -> [T] {
return self.context.fetchExisting(objects)
return objects.flatMap { (try? self.context.existingObjectWithID($0.objectID)) as? T }
}
/**
Fetches the `DynamicObject` instances in the transaction's context from a list of `NSManagedObjectID`.
Fetches the `NSManagedObject` instances in the transaction'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 transaction
- returns: the `NSManagedObject` array for objects that exists in the transaction
*/
public func fetchExisting<T: DynamicObject, S: Sequence>(_ objectIDs: S) -> [T] where S.Iterator.Element == NSManagedObjectID {
@warn_unused_result
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == NSManagedObjectID>(objectIDs: S) -> [T] {
return self.context.fetchExisting(objectIDs)
return objectIDs.flatMap { (try? self.context.existingObjectWithID($0)) as? T }
}
/**
Fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/
public func fetchOne<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> T? {
@warn_unused_result
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
return self.fetchOne(from, fetchClauses)
}
/**
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
@@ -131,29 +124,27 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
}
/**
Fetches the first `DynamicObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `DynamicObject` instance that satisfies the specified `FetchClause`s
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/
public func fetchOne<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
@warn_unused_result
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchOne(from, fetchClauses)
return self.fetchAll(from, fetchClauses)
}
/**
Fetches all `DynamicObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/
public func fetchAll<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
@warn_unused_result
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
@@ -163,61 +154,58 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
}
/**
Fetches all `DynamicObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `DynamicObject` instances that satisfy the specified `FetchClause`s
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
public func fetchAll<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
@warn_unused_result
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchAll(from, fetchClauses)
return self.fetchCount(from, fetchClauses)
}
/**
Fetches the number of `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number `DynamicObject`s that satisfy the specified `FetchClause`s
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
public func fetchCount<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
@warn_unused_result
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchCount(from, fetchClauses)
}
/**
Fetches the number of `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number `DynamicObject`s that satisfy the specified `FetchClause`s
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/
public func fetchCount<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
@warn_unused_result
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchCount(from, fetchClauses)
return self.fetchObjectID(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/
public func fetchObjectID<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
@warn_unused_result
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
@@ -227,29 +215,27 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
}
/**
Fetches the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `DynamicObject` that satisfies the specified `FetchClause`s
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
public func fetchObjectID<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
@warn_unused_result
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchObjectID(from, fetchClauses)
return self.fetchObjectIDs(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
public func fetchObjectIDs<T: DynamicObject>(_ from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
@warn_unused_result
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
@@ -259,23 +245,38 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
}
/**
Fetches the `NSManagedObjectID` for all `DynamicObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter 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
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `NSManagedObject`s deleted
*/
public func fetchObjectIDs<T: DynamicObject>(_ from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(cs_typeName(self)) outside its designated queue."
"Attempted to delete from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.fetchObjectIDs(from, fetchClauses)
return self.context.deleteAll(from, deleteClauses)
}
// MARK: QueryableSource
/**
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `NSManagedObject`s deleted
*/
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.deleteAll(from, deleteClauses)
}
/**
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
@@ -287,12 +288,14 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
public func queryValue<T: DynamicObject, U: QueryableAttributeType>(_ from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
@warn_unused_result
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.queryValue(from, selectClause, queryClauses)
}
@@ -306,12 +309,14 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
public func queryValue<T: DynamicObject, U: QueryableAttributeType>(_ from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
@warn_unused_result
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.queryValue(from, selectClause, queryClauses)
}
@@ -325,12 +330,14 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
public func queryAttributes<T: DynamicObject>(_ from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[String: Any]]? {
@warn_unused_result
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.queryAttributes(from, selectClause, queryClauses)
}
@@ -344,32 +351,14 @@ extension BaseDataTransaction: FetchableSource, QueryableSource {
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
public func queryAttributes<T: DynamicObject>(_ from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[String: Any]]? {
@warn_unused_result
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to query from a \(cs_typeName(self)) outside its designated queue."
)
return self.context.queryAttributes(from, selectClause, queryClauses)
}
// MARK: FetchableSource, QueryableSource
/**
The internal `NSManagedObjectContext` managed by this instance. Using this context directly should typically be avoided, and is provided by CoreStore only for extremely specialized cases.
*/
public func unsafeContext() -> NSManagedObjectContext {
return self.context
}
// MARK: Obsoleted
@available(*, obsoleted: 3.1, renamed: "unsafeContext()")
public func internalContext() -> NSManagedObjectContext {
fatalError()
}
}

View File

@@ -0,0 +1,373 @@
//
// From.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - From
/**
A `From` clause specifies the source entity and source persistent store for fetch and query methods. A common usage is to just indicate the entity:
```
let person = transaction.fetchOne(From(MyPersonEntity))
```
For cases where multiple `NSPersistentStore`s contain the same entity, the source configuration's name needs to be specified as well:
```
let person = transaction.fetchOne(From<MyPersonEntity>("Configuration1"))
```
*/
public struct From<T: NSManagedObject> {
/**
The associated `NSManagedObject` entity class
*/
public let entityClass: AnyClass
/**
The `NSPersistentStore` configuration names to associate objects from.
May contain `String`s to pertain to named configurations, or `nil` to pertain to the default configuration
*/
public let configurations: [String?]?
/**
Initializes a `From` clause.
```
let people = transaction.fetchAll(From<MyPersonEntity>())
```
*/
public init(){
self.init(entityClass: T.self, configurations: nil)
}
/**
Initializes a `From` clause with the specified entity type.
```
let people = transaction.fetchAll(From<MyPersonEntity>())
```
- parameter entity: the associated `NSManagedObject` type
*/
public init(_ entity: T.Type) {
self.init(entityClass: entity, configurations: nil)
}
/**
Initializes a `From` clause with the specified entity class.
```
let people = transaction.fetchAll(From<MyPersonEntity>())
```
- parameter entityClass: the associated `NSManagedObject` entity class
*/
public init(_ entityClass: AnyClass) {
CoreStore.assert(
entityClass is T.Type,
"Attempted to create generic type \(cs_typeName(From<T>)) with entity class \(cs_typeName(entityClass))"
)
self.init(entityClass: entityClass, configurations: nil)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From<MyPersonEntity>(nil, "Configuration1"))
```
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
*/
public init(_ configuration: String?, _ otherConfigurations: String?...) {
self.init(entityClass: T.self, configurations: [configuration] + otherConfigurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From<MyPersonEntity>(["Configuration1", "Configuration2"]))
```
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ configurations: [String?]) {
self.init(entityClass: T.self, configurations: configurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From(MyPersonEntity.self, nil, "Configuration1"))
```
- parameter entity: the associated `NSManagedObject` type
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
*/
public init(_ entity: T.Type, _ configuration: String?, _ otherConfigurations: String?...) {
self.init(entityClass: entity, configurations: [configuration] + otherConfigurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From(MyPersonEntity.self, ["Configuration1", "Configuration1"]))
```
- parameter entity: the associated `NSManagedObject` type
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ entity: T.Type, _ configurations: [String?]) {
self.init(entityClass: entity, configurations: configurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From(MyPersonEntity.self, nil, "Configuration1"))
```
- parameter entity: the associated `NSManagedObject` entity class
- parameter configuration: the `NSPersistentStore` configuration name to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
- parameter otherConfigurations: an optional list of other configuration names to associate objects from (see `configuration` parameter)
*/
public init(_ entityClass: AnyClass, _ configuration: String?, _ otherConfigurations: String?...) {
CoreStore.assert(
entityClass is T.Type,
"Attempted to create generic type \(cs_typeName(From<T>)) with entity class \(cs_typeName(entityClass))"
)
self.init(entityClass: entityClass, configurations: [configuration] + otherConfigurations)
}
/**
Initializes a `From` clause with the specified configurations.
```
let people = transaction.fetchAll(From(MyPersonEntity.self, ["Configuration1", "Configuration1"]))
```
- parameter entity: the associated `NSManagedObject` entity class
- parameter configurations: a list of `NSPersistentStore` configuration names to associate objects from. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ entityClass: AnyClass, _ configurations: [String?]) {
CoreStore.assert(
entityClass is T.Type,
"Attempted to create generic type \(cs_typeName(From<T>)) with entity class \(cs_typeName(entityClass))"
)
self.init(entityClass: entityClass, configurations: configurations)
}
// MARK: Internal
@warn_unused_result
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext, applyAffectedStores: Bool = true) -> Bool {
fetchRequest.entity = context.entityDescriptionForEntityClass(self.entityClass)
guard applyAffectedStores else {
return true
}
if self.applyAffectedStoresForFetchedRequest(fetchRequest, context: context) {
return true
}
CoreStore.log(
.Warning,
message: "Attempted to perform a fetch but could not find any persistent store for the entity \(cs_typeName(fetchRequest.entityName))"
)
return false
}
internal func applyAffectedStoresForFetchedRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) -> Bool {
let stores = self.findPersistentStores(context: context)
fetchRequest.affectedStores = stores
return stores?.isEmpty == false
}
internal func upcast() -> From<NSManagedObject> {
return From<NSManagedObject>(
entityClass: self.entityClass,
configurations: self.configurations,
findPersistentStores: self.findPersistentStores
)
}
// MARK: Private
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
private init(entityClass: AnyClass, configurations: [String?]?) {
self.entityClass = entityClass
self.configurations = configurations
if let configurations = configurations {
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return configurationsSet.contains($0.configurationName)
}
}
}
else {
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)
}
}
}
private init(entityClass: AnyClass, configurations: [String?]?, findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?) {
self.entityClass = entityClass
self.configurations = configurations
self.findPersistentStores = findPersistentStores
}
// MARK: Obsolete
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ storeURLs: [NSURL]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entity: T.Type, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entityClass: AnyClass, _ storeURL: NSURL, _ otherStoreURLs: NSURL...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ persistentStores: [NSPersistentStore]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entity: T.Type, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entityClass: AnyClass, _ persistentStore: NSPersistentStore, _ otherPersistentStores: NSPersistentStore...) {
CoreStore.abort("Use initializers that accept configuration names.")
}
/**
Obsolete. Use initializers that accept configuration names.
*/
@available(*, obsoleted=2.0.0, message="Use initializers that accept configuration names.")
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
CoreStore.abort("Use initializers that accept configuration names.")
}
}

View File

@@ -71,13 +71,13 @@ public struct GroupBy: QueryClause, Hashable {
// MARK: QueryClause
public func applyToFetchRequest<ResultType: NSFetchRequestResult>(_ fetchRequest: NSFetchRequest<ResultType>) {
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
if let keyPaths = fetchRequest.propertiesToGroupBy as? [String], keyPaths != self.keyPaths {
if let keyPaths = fetchRequest.propertiesToGroupBy as? [String] where keyPaths != self.keyPaths {
CoreStore.log(
.warning,
message: "An existing \"propertiesToGroupBy\" for the \(cs_typeName(NSFetchRequest<ResultType>.self)) was overwritten by \(cs_typeName(self)) query clause."
.Warning,
message: "An existing \"propertiesToGroupBy\" for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
)
}
@@ -85,14 +85,6 @@ public struct GroupBy: QueryClause, Hashable {
}
// MARK: Equatable
public static func == (lhs: GroupBy, rhs: GroupBy) -> Bool {
return lhs.keyPaths == rhs.keyPaths
}
// MARK: Hashable
public var hashValue: Int {
@@ -100,3 +92,12 @@ public struct GroupBy: QueryClause, Hashable {
return (self.keyPaths as NSArray).hashValue
}
}
// MARK: - GroupBy: Equatable
@warn_unused_result
public func == (lhs: GroupBy, rhs: GroupBy) -> Bool {
return lhs.keyPaths == rhs.keyPaths
}

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