Compare commits

..

7 Commits

Author SHA1 Message Date
John Estropia
7565bfae2b Merge branch 'develop' into _corespotlight-test
Conflicts:
	CoreStoreDemo/CoreStoreDemo.xcodeproj/project.pbxproj
2016-01-29 15:53:01 +09:00
John Estropia
f0ea095e80 WIP: spotlight 2016-01-21 20:16:58 +09:00
John Estropia
2837009581 Merge branch 'develop' into _corespotlight-test
Conflicts:
	CoreStore.xcodeproj/project.pbxproj
2016-01-19 15:43:30 +09:00
John Estropia
42c708e52b WIP: corespotlight 2016-01-08 13:29:05 +09:00
John Rommel Estropia
16daddbcc2 WIP corespotlight 2015-12-17 07:44:48 +09:00
John Rommel Estropia
103c715f5a corespotlight test 2015-12-17 01:10:25 +09:00
John Estropia
54a76f5d0a WIP: spotlight demo app prototype 2015-12-16 19:24:43 +09:00
218 changed files with 7963 additions and 32724 deletions

2
.gitignore vendored
View File

@@ -5,5 +5,3 @@ CoreStoreDemo/CoreStoreDemo.xcodeproj/xcuserdata
Carthage/Build
CoreStore.xcworkspace/xcuserdata
.DS_Store
DerivedData
*.orig

View File

@@ -1,31 +1,33 @@
language: objective-c
osx_image: xcode7.3
osx_image: xcode7.2
sudo: false
git:
submodules: false
notifications:
email: false
branches:
only:
- master
- develop
env:
global:
- LC_CTYPE=en_US.UTF-8
- LANG=en_US.UTF-8
- LC_CTYPE=en_US.UTF-8
- LANG=en_US.UTF-8
matrix:
- 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="OS=9.2,name=iPhone 6s" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=9.0,name=iPhone 6 Plus" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.4,name=iPhone 6" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.3,name=iPhone 5S" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.2,name=iPhone 5" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 RUN_TESTS="YES" POD_LINT="NO"
- DESTINATION="OS=8.1,name=iPhone 4S" SCHEME="CoreStore iOS" SDK=iphonesimulator9.2 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"
- DESTINATION="OS=2.1,name=Apple Watch - 42mm" SCHEME="CoreStore watchOS" SDK=watchsimulator2.1 RUN_TESTS="NO" POD_LINT="NO"
- DESTINATION="OS=9.1,name=Apple TV 1080p" SCHEME="CoreStore tvOS" SDK=appletvsimulator9.1 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.11/Carthage.pkg"
- sudo installer -pkg "Carthage.pkg" -target /
- rm "Carthage.pkg"
- brew update
- brew install carthage
before_script:
- carthage update --use-submodules
script:
@@ -36,8 +38,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 "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;
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator9.2" -destination "OS=9.2,name=iPhone 6s" -configuration Debug ONLY_ACTIVE_ARCH=NO clean test | xcpretty -c;
- xcodebuild -workspace "CoreStore.xcworkspace" -scheme "CoreStore iOS" -sdk "iphonesimulator9.2" -destination "OS=9.2,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

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

View File

@@ -1 +1 @@
github "JohnEstropia/GCDKit" "1.2.6"
github "JohnEstropia/GCDKit" "1.1.7"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

View File

@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = "CoreStore"
s.version = "2.0.4"
s.version = "1.4.4"
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"
@@ -12,12 +12,11 @@ Pod::Spec.new do |s|
s.watchos.deployment_target = "2.0"
s.tvos.deployment_target = "9.0"
s.source_files = "Sources", "Sources/**/*.{swift,h,m}"
s.public_header_files = "Sources/**/*.h"
s.source_files = "CoreStore", "CoreStore/**/*.{swift}"
s.osx.exclude_files = "CoreStore/Observing/*.{swift}", "CoreStore/Internal/FetchedResultsControllerDelegate.swift", "CoreStore/Convenience Helpers/NSFetchedResultsController+Convenience.swift"
s.frameworks = "Foundation", "CoreData"
s.requires_arc = true
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-D USE_FRAMEWORKS',
'GCC_PREPROCESSOR_DEFINITIONS' => 'USE_FRAMEWORKS=1' }
s.pod_target_xcconfig = { 'OTHER_SWIFT_FLAGS' => '-D USE_FRAMEWORKS' }
s.dependency "GCDKit", "1.2.6"
s.dependency "GCDKit", "1.1.7"
end

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -11,8 +11,7 @@
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES"
hideIssues = "NO">
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
@@ -26,8 +25,7 @@
buildForRunning = "NO"
buildForProfiling = "NO"
buildForArchiving = "NO"
buildForAnalyzing = "NO"
hideIssues = "NO">
buildForAnalyzing = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"

View File

@@ -1,104 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<Scheme
LastUpgradeVersion = "0700"
version = "1.3">
<BuildAction
parallelizeBuildables = "YES"
buildImplicitDependencies = "YES">
<BuildActionEntries>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
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>
</BuildAction>
<TestAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
shouldUseLaunchSchemeArgsEnv = "YES">
<Testables>
<TestableReference
skipped = "NO">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A53A19C5C6DA005002A5"
BuildableName = "CoreStoreTests.xctest"
BlueprintName = "CoreStoreTests iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</TestableReference>
</Testables>
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "2F03A52F19C5C6DA005002A5"
BuildableName = "CoreStore.framework"
BlueprintName = "CoreStore iOS"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</TestAction>
<LaunchAction
buildConfiguration = "Debug"
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
launchStyle = "0"
useCustomWorkingDirectory = "NO"
ignoresPersistentStateOnLaunch = "NO"
debugDocumentVersioning = "YES"
debugServiceExtension = "internal"
allowLocationSimulation = "YES">
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "B5D9E2ED1CA2C317007A9D52"
BuildableName = "CoreStore_iOS7.framework"
BlueprintName = "CoreStore iOS7"
ReferencedContainer = "container:CoreStore.xcodeproj">
</BuildableReference>
</MacroExpansion>
<AdditionalOptions>
</AdditionalOptions>
</LaunchAction>
<ProfileAction
buildConfiguration = "Release"
shouldUseLaunchSchemeArgsEnv = "YES"
savedToolIdentifier = ""
useCustomWorkingDirectory = "NO"
debugDocumentVersioning = "YES">
</ProfileAction>
<AnalyzeAction
buildConfiguration = "Release">
</AnalyzeAction>
<ArchiveAction
buildConfiguration = "Release"
revealArchiveInOrganizer = "YES">
</ArchiveAction>
</Scheme>

View File

@@ -5,13 +5,11 @@
},
"DVTSourceControlWorkspaceBlueprintWorkingCopyStatesKey" : {
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : 0,
"95438028B10BBB846574013D29F154A00556A9D1" : 0,
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : 0
},
"DVTSourceControlWorkspaceBlueprintIdentifierKey" : "EBFDEFFE-8BA0-441A-862A-1DE28AA5CD21",
"DVTSourceControlWorkspaceBlueprintWorkingCopyPathsKey" : {
"8B2E522D57154DFA93A06982C36315ECBEA4FA97" : "CoreStore\/Carthage\/Checkouts\/GCDKit\/",
"95438028B10BBB846574013D29F154A00556A9D1" : "CoreStore\/Carthage\/Checkouts\/Nimble\/",
"4B60F1BCB491FF717C56441AE7783C74F417BE48" : "CoreStore\/"
},
"DVTSourceControlWorkspaceBlueprintNameKey" : "CoreStore",
@@ -27,11 +25,6 @@
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/JohnEstropia\/GCDKit.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "8B2E522D57154DFA93A06982C36315ECBEA4FA97"
},
{
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryURLKey" : "https:\/\/github.com\/Quick\/Nimble.git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositorySystemKey" : "com.apple.dt.Xcode.sourcecontrol.Git",
"DVTSourceControlWorkspaceBlueprintRemoteRepositoryIdentifierKey" : "95438028B10BBB846574013D29F154A00556A9D1"
}
]
}

1
CoreStore/Cartfile Normal file
View File

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

View File

@@ -1,8 +1,8 @@
//
// CoreStoreFetchRequest.swift
// NSManagedObject+Convenience.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -27,16 +27,24 @@ import Foundation
import CoreData
// MARK: - CoreStoreFetchRequest
// MARK: - NSFetchedResultsController
// Bugfix for NSFetchRequest messing up memory management for `affectedStores`
// http://stackoverflow.com/questions/14396375/nsfetchedresultscontroller-crashes-in-ios-6-if-affectedstores-is-specified
internal final class CoreStoreFetchRequest: NSFetchRequest {
public extension NSFetchedResultsController {
@objc
override var affectedStores: [NSPersistentStore]? {
public convenience init<T: NSManagedObject>(dataStack: DataStack, fetchRequest: NSFetchRequest, from: From<T>? = nil, sectionBy: SectionBy? = nil, fetchClauses: [FetchClause]) {
get { return super.affectedStores }
set { super.affectedStores = newValue }
let context = dataStack.mainContext
from?.applyToFetchRequest(fetchRequest, context: context)
for clause in fetchClauses {
clause.applyToFetchRequest(fetchRequest)
}
self.init(
fetchRequest: fetchRequest,
managedObjectContext: context,
sectionNameKeyPath: sectionBy?.sectionKeyPath,
cacheName: nil
)
}
}

View File

@@ -2,7 +2,7 @@
// NSManagedObject+Convenience.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -32,12 +32,11 @@ import CoreData
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
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
*/
@warn_unused_result
public func accessValueForKVCKey(KVCKey: KeyPath) -> AnyObject? {
@@ -49,12 +48,11 @@ public extension NSManagedObject {
}
/**
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
Provides a convenience wrapper for setting `setPrimitiveValue(...)` with proper calls to `willChangeValueForKey(...)` and `didChangeValueForKey(...)`. This is useful when implementing mutator methods for transient attributes.
- parameter value: the value to set the KVC key with
- parameter KVCKey: the KVC key
*/
public func setValue(value: AnyObject?, forKVCKey KVCKey: KeyPath) {
self.willChangeValueForKey(KVCKey)
@@ -63,18 +61,16 @@ public extension NSManagedObject {
}
/**
Re-faults the object to use the latest values from the persistent store
*/
@nonobjc
Re-faults the object to use the latest values from the persistent store
*/
public func refreshAsFault() {
self.managedObjectContext?.refreshObject(self, mergeChanges: false)
}
/**
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
*/
@nonobjc
Re-faults the object to use the latest values from the persistent store and merges previously pending changes back
*/
public func refreshAndMerge() {
self.managedObjectContext?.refreshObject(self, mergeChanges: true)

View File

@@ -2,7 +2,7 @@
// NSProgress+Convenience.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -33,12 +33,12 @@ import Foundation
public extension NSProgress {
// MARK: Public
/**
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
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
*/
public func setProgressHandler(closure: ((progress: NSProgress) -> Void)?) {
self.progressObserver.progressHandler = closure
@@ -52,19 +52,18 @@ public extension NSProgress {
static var progressObserver: Void?
}
@nonobjc
private var progressObserver: ProgressObserver {
get {
let object: ProgressObserver? = cs_getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
let object: ProgressObserver? = getAssociatedObjectForKey(&PropertyKeys.progressObserver, inObject: self)
if let observer = object {
return observer
}
let observer = ProgressObserver(self)
cs_setAssociatedRetainedObject(
setAssociatedRetainedObject(
observer,
forKey: &PropertyKeys.progressObserver,
inObject: self
@@ -76,10 +75,7 @@ public extension NSProgress {
}
// MARK: - ProgressObserver
@objc
private final class ProgressObserver: NSObject {
@objc private final class ProgressObserver: NSObject {
private unowned let progress: NSProgress
private var progressHandler: ((progress: NSProgress) -> Void)? {

View File

@@ -1,8 +1,8 @@
//
// BridgingTests.h
// CoreSpotlightSearchableObject.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -23,8 +23,17 @@
// SOFTWARE.
//
#import <XCTest/XCTest.h>
import Foundation
import CoreData
@interface BridgingTests : XCTestCase
// MARK: - CoreSpotlightSearchableObject
@end
public protocol CoreSpotlightSearchableObject: class {
/**
The object's index value for CoreSpotlight
*/
var coreSpotlightIndexValue: String? { get set }
}

View File

@@ -2,7 +2,7 @@
// CoreStore.h
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -29,4 +29,3 @@
FOUNDATION_EXPORT double CoreStoreVersionNumber;
FOUNDATION_EXPORT const unsigned char CoreStoreVersionString[];
#import "CoreStoreBridge.h"

View File

@@ -2,7 +2,7 @@
// CoreStore.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -32,15 +32,17 @@ import CoreData
// MARK: - CoreStore
/**
`CoreStore` is the main entry point for all other APIs.
*/
`CoreStore` is the main entry point for all other APIs.
*/
public enum CoreStore {
// MARK: Public
/**
The default `DataStack` instance to be used. If `defaultStack` is not set before the first time accessed, a default-configured `DataStack` will be created.
- SeeAlso: `DataStack`
- Note: Changing the `defaultStack` is thread safe, but it is recommended to setup `DataStacks` on a common queue (e.g. the main queue).
*/
The default `DataStack` instance to be used. If `defaultStack` is not set before the first time accessed, a default-configured `DataStack` will be created.
Changing the `defaultStack` is thread safe, but it is recommended to setup `DataStacks` on a common queue (e.g. the main queue).
*/
public static var defaultStack: DataStack {
get {

View File

@@ -0,0 +1,395 @@
//
// BaseDataTransaction+Querying.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - DataTransaction
public extension BaseDataTransaction {
// MARK: Public
/**
Fetches the `NSManagedObject` instance in the transaction's context from a reference created from a transaction or from a different managed object context.
- parameter object: a reference to the object created/fetched outside the transaction
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
*/
@warn_unused_result
public func fetchExisting<T: NSManagedObject>(object: T) -> T? {
do {
return (try self.context.existingObjectWithID(object.objectID) as! T)
}
catch _ {
return nil
}
}
/**
Fetches the `NSManagedObject` instance in the transaction's context from an `NSManagedObjectID`.
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `NSManagedObject` instance if the object exists in the transaction, or `nil` if not found.
*/
@warn_unused_result
public func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
do {
return (try self.context.existingObjectWithID(objectID) as! T)
}
catch _ {
return nil
}
}
/**
Fetches the `NSManagedObject` instances in the transaction's context from references created from a transaction or from a different managed object context.
- parameter objects: an array of `NSManagedObject`s created/fetched outside the transaction
- returns: the `NSManagedObject` array for objects that exists in the transaction
*/
@warn_unused_result
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == T>(objects: S) -> [T] {
return objects.flatMap { (try? self.context.existingObjectWithID($0.objectID)) as? T }
}
/**
Fetches the `NSManagedObject` instances in the transaction's context from a list of `NSManagedObjectID`.
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `NSManagedObject` array for objects that exists in the transaction
*/
@warn_unused_result
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == NSManagedObjectID>(objectIDs: S) -> [T] {
return objectIDs.flatMap { (try? self.context.existingObjectWithID($0)) as? T }
}
/**
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(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchOne(from, fetchClauses)
}
/**
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchOne(from, fetchClauses)
}
/**
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchAll(from, fetchClauses)
}
/**
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchAll(from, fetchClauses)
}
/**
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchCount(from, fetchClauses)
}
/**
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchCount(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchObjectID(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchObjectID(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchObjectIDs(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to fetch from a \(typeName(self)) outside its designated queue."
)
return self.context.fetchObjectIDs(from, fetchClauses)
}
/**
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `NSManagedObject`s deleted
*/
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete from a \(typeName(self)) outside its designated queue."
)
return self.context.deleteAll(from, deleteClauses)
}
/**
Deletes all `NSManagedObject`s that satisfy the specified `DeleteClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter deleteClauses: a series of `DeleteClause` instances for the delete request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number of `NSManagedObject`s deleted
*/
public func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete from a \(typeName(self)) outside its designated queue."
)
return self.context.deleteAll(from, deleteClauses)
}
/**
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@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 \(typeName(self)) outside its designated queue."
)
return self.context.queryValue(from, selectClause, queryClauses)
}
/**
Queries aggregate values or aggregates as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@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 \(typeName(self)) outside its designated queue."
)
return self.context.queryValue(from, selectClause, queryClauses)
}
/**
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@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 \(typeName(self)) outside its designated queue."
)
return self.context.queryAttributes(from, selectClause, queryClauses)
}
/**
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@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 \(typeName(self)) outside its designated queue."
)
return self.context.queryAttributes(from, selectClause, queryClauses)
}
}

View File

@@ -0,0 +1,201 @@
//
// From.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - From
/**
A `Form` clause binds the `NSManagedObject` entity type to the generics type system.
*/
public struct From<T: NSManagedObject> {
// MARK: Public
public init(){
self.entityClass = T.self
self.findPersistentStores = { _ in nil }
}
public init(_ entity: T.Type) {
self.entityClass = entity
self.findPersistentStores = { _ in nil }
}
public init(_ entityClass: AnyClass) {
self.entityClass = entityClass
self.findPersistentStores = { _ in nil }
}
public init(_ configurations: String?...) {
self.init(entityClass: T.self, configurations: configurations)
}
public init(_ configurations: [String?]) {
self.init(entityClass: T.self, configurations: configurations)
}
public init(_ entity: T.Type, _ configurations: String?...) {
self.init(entityClass: entity, configurations: configurations)
}
public init(_ entity: T.Type, _ configurations: [String?]) {
self.init(entityClass: entity, configurations: configurations)
}
public init(_ entityClass: AnyClass, _ configurations: String?...) {
self.init(entityClass: entityClass, configurations: configurations)
}
public init(_ entityClass: AnyClass, _ configurations: [String?]) {
self.init(entityClass: entityClass, configurations: configurations)
}
public init(_ storeURLs: NSURL...) {
self.init(entityClass: T.self, storeURLs: storeURLs)
}
public init(_ storeURLs: [NSURL]) {
self.init(entityClass: T.self, storeURLs: storeURLs)
}
public init(_ entity: T.Type, _ storeURLs: NSURL...) {
self.init(entityClass: entity, storeURLs: storeURLs)
}
public init(_ entity: T.Type, _ storeURLs: [NSURL]) {
self.init(entityClass: entity, storeURLs: storeURLs)
}
public init(_ entityClass: AnyClass, _ storeURLs: NSURL...) {
self.init(entityClass: entityClass, storeURLs: storeURLs)
}
public init(_ entityClass: AnyClass, _ storeURLs: [NSURL]) {
self.init(entityClass: entityClass, storeURLs: storeURLs)
}
public init(_ persistentStores: NSPersistentStore...) {
self.init(entityClass: T.self, persistentStores: persistentStores)
}
public init(_ persistentStores: [NSPersistentStore]) {
self.init(entityClass: T.self, persistentStores: persistentStores)
}
public init(_ entity: T.Type, _ persistentStores: NSPersistentStore...) {
self.init(entityClass: entity, persistentStores: persistentStores)
}
public init(_ entity: T.Type, _ persistentStores: [NSPersistentStore]) {
self.init(entityClass: entity, persistentStores: persistentStores)
}
public init(_ entityClass: AnyClass, _ persistentStores: NSPersistentStore...) {
self.init(entityClass: entityClass, persistentStores: persistentStores)
}
public init(_ entityClass: AnyClass, _ persistentStores: [NSPersistentStore]) {
self.init(entityClass: entityClass, persistentStores: persistentStores)
}
// MARK: Internal
internal func applyToFetchRequest(fetchRequest: NSFetchRequest, context: NSManagedObjectContext) {
fetchRequest.entity = context.entityDescriptionForEntityClass(self.entityClass)
fetchRequest.affectedStores = self.findPersistentStores(context: context)
}
// MARK: Private
private let entityClass: AnyClass
private let findPersistentStores: (context: NSManagedObjectContext) -> [NSPersistentStore]?
private init(entityClass: AnyClass, configurations: [String?]) {
let configurationsSet = Set(configurations.map { $0 ?? Into.defaultConfigurationName })
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return configurationsSet.contains($0.configurationName)
}
}
}
private init(entityClass: AnyClass, storeURLs: [NSURL]) {
let storeURLsSet = Set(storeURLs)
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return $0.URL != nil && storeURLsSet.contains($0.URL!)
}
}
}
private init(entityClass: AnyClass, persistentStores: [NSPersistentStore]) {
let persistentStores = Set(persistentStores)
self.entityClass = entityClass
self.findPersistentStores = { (context: NSManagedObjectContext) -> [NSPersistentStore]? in
return context.parentStack?.persistentStoresForEntityClass(entityClass)?.filter {
return persistentStores.contains($0)
}
}
}
}

View File

@@ -2,7 +2,7 @@
// GroupBy.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,74 +30,56 @@ import CoreData
// MARK: - GroupBy
/**
The `GroupBy` clause specifies that the result of a query be grouped accoording to the specified key path.
*/
public struct GroupBy: QueryClause, Hashable {
The `GroupBy` clause specifies that the result of a query be grouped accoording to the specified key path.
*/
public struct GroupBy: QueryClause {
// MARK: Public
/**
The list of key path strings to group results with
*/
public let keyPaths: [KeyPath]
Initializes a `GroupBy` clause with a list of key path strings
- parameter keyPaths: a list of key path strings to group results with
*/
public init(_ keyPaths: [KeyPath]) {
self.keyPaths = keyPaths
}
/**
Initializes a `GroupBy` clause with an empty list of key path strings
*/
Initializes a `GroupBy` clause with an empty list of key path strings
*/
public init() {
self.init([])
}
/**
Initializes a `GroupBy` clause with a list of key path strings
- parameter keyPath: a key path string to group results with
- parameter keyPaths: a series of key path strings to group results with
*/
Initializes a `GroupBy` clause with a list of key path strings
- parameter keyPath: a key path string to group results with
- parameter keyPaths: a series of key path strings to group results with
*/
public init(_ keyPath: KeyPath, _ keyPaths: KeyPath...) {
self.init([keyPath] + keyPaths)
}
/**
Initializes a `GroupBy` clause with a list of key path strings
- parameter keyPaths: a list of key path strings to group results with
*/
public init(_ keyPaths: [KeyPath]) {
self.keyPaths = keyPaths
}
public let keyPaths: [KeyPath]
// MARK: QueryClause
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
if let keyPaths = fetchRequest.propertiesToGroupBy as? [String] where keyPaths != self.keyPaths {
if fetchRequest.propertiesToGroupBy != nil {
CoreStore.log(
.Warning,
message: "An existing \"propertiesToGroupBy\" for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
message: "An existing \"propertiesToGroupBy\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
)
}
fetchRequest.propertiesToGroupBy = self.keyPaths
}
// MARK: Hashable
public var hashValue: Int {
return (self.keyPaths as NSArray).hashValue
}
}
// MARK: - GroupBy: Equatable
@warn_unused_result
public func == (lhs: GroupBy, rhs: GroupBy) -> Bool {
return lhs.keyPaths == rhs.keyPaths
}

View File

@@ -2,7 +2,7 @@
// OrderBy.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -46,18 +46,18 @@ public typealias KeyPath = String
// MARK: - SortKey
/**
The `SortKey` is passed to the `OrderBy` clause to indicate the sort keys and their sort direction.
*/
The `SortKey` is passed to the `OrderBy` clause to indicate the sort keys and their sort direction.
*/
public enum SortKey {
/**
Indicates that the `KeyPath` should be sorted in ascending order
*/
Indicates that the `KeyPath` should be sorted in ascending order
*/
case Ascending(KeyPath)
/**
Indicates that the `KeyPath` should be sorted in descending order
*/
Indicates that the `KeyPath` should be sorted in descending order
*/
case Descending(KeyPath)
}
@@ -65,48 +65,45 @@ public enum SortKey {
// MARK: - OrderBy
/**
The `OrderBy` clause specifies the sort order for results for a fetch or a query.
*/
public struct OrderBy: FetchClause, QueryClause, DeleteClause, Hashable {
The `OrderBy` clause specifies the sort order for results for a fetch or a query.
*/
public struct OrderBy: FetchClause, QueryClause, DeleteClause {
// MARK: Public
/**
The list of sort descriptors
*/
public let sortDescriptors: [NSSortDescriptor]
Initializes a `OrderBy` clause with a list of sort descriptors
/**
Initializes a `OrderBy` clause with an empty list of sort descriptors
*/
public init() {
self.init([NSSortDescriptor]())
}
/**
Initializes a `OrderBy` clause with a single sort descriptor
- parameter sortDescriptor: a `NSSortDescriptor`
*/
public init(_ sortDescriptor: NSSortDescriptor) {
self.init([sortDescriptor])
}
/**
Initializes a `OrderBy` clause with a list of sort descriptors
- parameter sortDescriptors: a series of `NSSortDescriptor`s
*/
- parameter sortDescriptors: a series of `NSSortDescriptor`s
*/
public init(_ sortDescriptors: [NSSortDescriptor]) {
self.sortDescriptors = sortDescriptors
}
/**
Initializes a `OrderBy` clause with a series of `SortKey`s
- parameter sortKey: a series of `SortKey`s
*/
Initializes a `OrderBy` clause with an empty list of sort descriptors
*/
public init() {
self.init([NSSortDescriptor]())
}
/**
Initializes a `OrderBy` clause with a single sort descriptor
- parameter sortDescriptor: a `NSSortDescriptor`
*/
public init(_ sortDescriptor: NSSortDescriptor) {
self.init([sortDescriptor])
}
/**
Initializes a `OrderBy` clause with a series of `SortKey`s
- parameter sortKey: a series of `SortKey`s
*/
public init(_ sortKey: [SortKey]) {
self.init(
@@ -125,46 +122,31 @@ public struct OrderBy: FetchClause, QueryClause, DeleteClause, Hashable {
}
/**
Initializes a `OrderBy` clause with a series of `SortKey`s
- parameter sortKey: a single `SortKey`
- parameter sortKeys: a series of `SortKey`s
*/
Initializes a `OrderBy` clause with a series of `SortKey`s
- parameter sortKey: a single `SortKey`
- parameter sortKeys: a series of `SortKey`s
*/
public init(_ sortKey: SortKey, _ sortKeys: SortKey...) {
self.init([sortKey] + sortKeys)
}
public let sortDescriptors: [NSSortDescriptor]
// MARK: FetchClause, QueryClause, DeleteClause
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
if let sortDescriptors = fetchRequest.sortDescriptors where sortDescriptors != self.sortDescriptors {
if fetchRequest.sortDescriptors != nil {
CoreStore.log(
.Warning,
message: "Existing sortDescriptors for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
message: "Existing sortDescriptors for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
)
}
fetchRequest.sortDescriptors = self.sortDescriptors
}
// MARK: Hashable
public var hashValue: Int {
return (self.sortDescriptors as NSArray).hashValue
}
}
// MARK: - OrderBy: Equatable
@warn_unused_result
public func == (lhs: OrderBy, rhs: OrderBy) -> Bool {
return lhs.sortDescriptors == rhs.sortDescriptors
}

View File

@@ -2,7 +2,7 @@
// Select.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,20 +30,18 @@ import CoreData
// MARK: - SelectResultType
/**
The `SelectResultType` protocol is implemented by return types supported by the `Select` clause.
*/
public protocol SelectResultType {}
The `SelectResultType` protocol is implemented by return types supported by the `Select` clause.
*/
public protocol SelectResultType { }
// MARK: - SelectValueResultType
/**
The `SelectValueResultType` protocol is implemented by return types supported by the `queryValue(...)` methods.
*/
The `SelectValueResultType` protocol is implemented by return types supported by the `queryValue(...)` methods.
*/
public protocol SelectValueResultType: SelectResultType {
static var attributeType: NSAttributeType { get }
static func fromResultObject(result: AnyObject) -> Self?
}
@@ -51,8 +49,8 @@ public protocol SelectValueResultType: SelectResultType {
// MARK: - SelectAttributesResultType
/**
The `SelectValueResultType` protocol is implemented by return types supported by the `queryAttributes(...)` methods.
*/
The `SelectValueResultType` protocol is implemented by return types supported by the `queryAttributes(...)` methods.
*/
public protocol SelectAttributesResultType: SelectResultType {
static func fromResultObjects(result: [AnyObject]) -> [[NSString: AnyObject]]
@@ -62,173 +60,147 @@ public protocol SelectAttributesResultType: SelectResultType {
// MARK: - SelectTerm
/**
The `SelectTerm` is passed to the `Select` clause to indicate the attributes/aggregate keys to be queried.
*/
public enum SelectTerm: StringLiteralConvertible, Hashable {
The `SelectTerm` is passed to the `Select` clause to indicate the attributes/aggregate keys to be queried.
*/
public enum SelectTerm: StringLiteralConvertible {
// MARK: Public
/**
Provides a `SelectTerm` to a `Select` clause for querying an entity attribute. A shorter way to do the same is to assign from the string keypath directly:
```
let fullName = CoreStore.queryValue(
From(MyPersonEntity),
Select<String>(.Attribute("fullName")),
Where("employeeID", isEqualTo: 1111)
)
```
is equivalent to:
```
let fullName = CoreStore.queryValue(
From(MyPersonEntity),
Select<String>("fullName"),
Where("employeeID", isEqualTo: 1111)
)
```
- parameter keyPath: the attribute name
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
*/
Provides a `SelectTerm` to a `Select` clause for querying an entity attribute. A shorter way to do the same is to assign from the string keypath directly:
let fullName = CoreStore.queryValue(
From(MyPersonEntity),
Select<String>(.Attribute("fullName")),
Where("employeeID", isEqualTo: 1111)
)
is equivalent to:
let fullName = CoreStore.queryValue(
From(MyPersonEntity),
Select<String>("fullName"),
Where("employeeID", isEqualTo: 1111)
)
- parameter keyPath: the attribute name
- returns: a `SelectTerm` to a `Select` clause for querying an entity attribute
*/
public static func Attribute(keyPath: KeyPath) -> SelectTerm {
return ._Attribute(keyPath)
}
/**
Provides a `SelectTerm` to a `Select` clause for querying the average value of an attribute.
```
let averageAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Average("age"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
*/
Provides a `SelectTerm` to a `Select` clause for querying the average value of an attribute.
let averageAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Average("age"))
)
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "average(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the average value of an attribute
*/
public static func Average(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return ._Aggregate(
function: "average:",
keyPath: keyPath,
alias: alias ?? "average(\(keyPath))",
keyPath,
As: alias ?? "average(\(keyPath))",
nativeType: .DecimalAttributeType
)
}
/**
Provides a `SelectTerm` to a `Select` clause for a count query.
```
let numberOfEmployees = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Count("employeeID"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for a count query
*/
Provides a `SelectTerm` to a `Select` clause for a count query.
let numberOfEmployees = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Count("employeeID"))
)
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "count(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for a count query
*/
public static func Count(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return ._Aggregate(
function: "count:",
keyPath: keyPath,
alias: alias ?? "count(\(keyPath))",
keyPath,
As: alias ?? "count(\(keyPath))",
nativeType: .Integer64AttributeType
)
}
/**
Provides a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute.
```
let maximumAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Maximum("age"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
*/
Provides a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute.
let maximumAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Maximum("age"))
)
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "max(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the maximum value for an attribute
*/
public static func Maximum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return ._Aggregate(
function: "max:",
keyPath: keyPath,
alias: alias ?? "max(\(keyPath))",
keyPath,
As: alias ?? "max(\(keyPath))",
nativeType: .UndefinedAttributeType
)
}
/**
Provides a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute.
```
let minimumAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Minimum("age"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
*/
Provides a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute.
let minimumAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Minimum("age"))
)
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "min(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the minimum value for an attribute
*/
public static func Minimum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return ._Aggregate(
function: "min:",
keyPath: keyPath,
alias: alias ?? "min(\(keyPath))",
keyPath,
As: alias ?? "min(\(keyPath))",
nativeType: .UndefinedAttributeType
)
}
/**
Provides a `SelectTerm` to a `Select` clause for querying the sum value for an attribute.
```
let totalAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Sum("age"))
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
*/
Provides a `SelectTerm` to a `Select` clause for querying the sum value for an attribute.
let totalAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Sum("age"))
)
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "sum(<attributeName>)" is used
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
*/
public static func Sum(keyPath: KeyPath, As alias: KeyPath? = nil) -> SelectTerm {
return ._Aggregate(
function: "sum:",
keyPath: keyPath,
alias: alias ?? "sum(\(keyPath))",
keyPath,
As: alias ?? "sum(\(keyPath))",
nativeType: .DecimalAttributeType
)
}
/**
Provides a `SelectTerm` to a `Select` clause for querying the `NSManagedObjectID`.
```
let objectID = CoreStore.queryValue(
From(MyPersonEntity),
Select<NSManagedObjectID>(),
Where("employeeID", isEqualTo: 1111)
)
```
- parameter keyPath: the attribute name
- parameter alias: the dictionary key to use to access the result. Ignored when the query return value is not an `NSDictionary`. If `nil`, the default key "objecID" is used
- returns: a `SelectTerm` to a `Select` clause for querying the sum value for an attribute
*/
public static func ObjectID(As alias: KeyPath? = nil) -> SelectTerm {
return ._Identity(
alias: alias ?? "objectID",
nativeType: .ObjectIDAttributeType
)
}
// MARK: StringLiteralConvertible
@@ -248,157 +220,162 @@ public enum SelectTerm: StringLiteralConvertible, Hashable {
}
// MARK: Hashable
public var hashValue: Int {
switch self {
case ._Attribute(let keyPath):
return 0 ^ keyPath.hashValue
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
return 1 ^ function.hashValue ^ keyPath.hashValue ^ alias.hashValue ^ nativeType.hashValue
case ._Identity(let alias, let nativeType):
return 3 ^ alias.hashValue ^ nativeType.hashValue
}
}
// MARK: Internal
case _Attribute(KeyPath)
case _Aggregate(function: String, keyPath: KeyPath, alias: String, nativeType: NSAttributeType)
case _Identity(alias: String, nativeType: NSAttributeType)
}
// MARK: - SelectTerm: Equatable
@warn_unused_result
public func == (lhs: SelectTerm, rhs: SelectTerm) -> Bool {
switch (lhs, rhs) {
case (._Attribute(let keyPath1), ._Attribute(let keyPath2)):
return keyPath1 == keyPath2
case (._Aggregate(let function1, let keyPath1, let alias1, let nativeType1),
._Aggregate(let function2, let keyPath2, let alias2, let nativeType2)):
return function1 == function2
&& keyPath1 == keyPath2
&& alias1 == alias2
&& nativeType1 == nativeType2
case (._Identity(let alias1, let nativeType1), ._Identity(let alias2, let nativeType2)):
return alias1 == alias2 && nativeType1 == nativeType2
default:
return false
}
case _Aggregate(function: String, KeyPath, As: String, nativeType: NSAttributeType)
}
// MARK: - Select
/**
The `Select` clause indicates the attribute / aggregate value to be queried. The generic type is a `SelectResultType`, and will be used as the return type for the query.
You can bind the return type by specializing the initializer:
```
let maximumAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Maximum("age"))
)
```
or by casting the type of the return value:
```
let maximumAge: Int = CoreStore.queryValue(
From(MyPersonEntity),
Select(.Maximum("age"))
)
```
Valid return types depend on the query:
- for `queryValue(...)` methods:
- `Bool`
- `Int8`
- `Int16`
- `Int32`
- `Int64`
- `Double`
- `Float`
- `String`
- `NSNumber`
- `NSString`
- `NSDecimalNumber`
- `NSDate`
- `NSData`
- `NSManagedObjectID`
- `NSString`
- for `queryAttributes(...)` methods:
- `NSDictionary`
- parameter sortDescriptors: a series of `NSSortDescriptor`s
*/
public struct Select<T: SelectResultType>: Hashable {
The `Select` clause indicates the attribute / aggregate value to be queried. The generic type is a `SelectResultType`, and will be used as the return type for the query.
You can bind the return type by specializing the initializer:
let maximumAge = CoreStore.queryValue(
From(MyPersonEntity),
Select<Int>(.Maximum("age"))
)
or by casting the type of the return value:
let maximumAge: Int = CoreStore.queryValue(
From(MyPersonEntity),
Select(.Maximum("age"))
)
Valid return types depend on the query:
- for `queryValue(...)` methods:
- `Bool`
- `Int8`
- `Int16`
- `Int32`
- `Int64`
- `Double`
- `Float`
- `String`
- `NSNumber`
- `NSString`
- `NSDecimalNumber`
- `NSDate`
- `NSData`
- `NSManagedObjectID`
- `NSString`
- for `queryAttributes(...)` methods:
- `NSDictionary`
- parameter sortDescriptors: a series of `NSSortDescriptor`s
*/
public struct Select<T: SelectResultType> {
// MARK: Public
/**
The `SelectResultType` type for the query's return value
*/
The `SelectResultType` type for the query's return value
*/
public typealias ReturnType = T
/**
Initializes a `Select` clause with a list of `SelectTerm`s
- parameter selectTerm: a `SelectTerm`
- parameter selectTerms: a series of `SelectTerm`s
*/
Initializes a `Select` clause with a list of `SelectTerm`s
- parameter selectTerm: a `SelectTerm`
- parameter selectTerms: a series of `SelectTerm`s
*/
public init(_ selectTerm: SelectTerm, _ selectTerms: SelectTerm...) {
self.selectTerms = [selectTerm] + selectTerms
}
/**
Initializes a `Select` clause with a list of `SelectTerm`s
- parameter selectTerms: a series of `SelectTerm`s
*/
public init(_ selectTerms: [SelectTerm]) {
self.selectTerms = selectTerms
}
// MARK: Hashable
public var hashValue: Int {
return self.selectTerms.map { $0.hashValue }.reduce(0, combine: ^)
}
// MARK: Internal
internal let selectTerms: [SelectTerm]
}
public extension Select where T: NSManagedObjectID {
public init() {
internal func applyToFetchRequest(fetchRequest: NSFetchRequest) {
self.init(.ObjectID())
if fetchRequest.propertiesToFetch != nil {
CoreStore.log(
.Warning,
message: "An existing \"propertiesToFetch\" for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
)
}
fetchRequest.includesPendingChanges = false
fetchRequest.resultType = .DictionaryResultType
let entityDescription = fetchRequest.entity!
let propertiesByName = entityDescription.propertiesByName
let attributesByName = entityDescription.attributesByName
var propertiesToFetch = [AnyObject]()
for term in self.selectTerms {
switch term {
case ._Attribute(let keyPath):
if let propertyDescription = propertiesByName[keyPath] {
propertiesToFetch.append(propertyDescription)
}
else {
CoreStore.log(
.Warning,
message: "The property \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause."
)
}
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
if let attributeDescription = attributesByName[keyPath] {
let expressionDescription = NSExpressionDescription()
expressionDescription.name = alias
if nativeType == .UndefinedAttributeType {
expressionDescription.expressionResultType = attributeDescription.attributeType
}
else {
expressionDescription.expressionResultType = nativeType
}
expressionDescription.expression = NSExpression(
forFunction: function,
arguments: [NSExpression(forKeyPath: keyPath)]
)
propertiesToFetch.append(expressionDescription)
}
else {
CoreStore.log(
.Warning,
message: "The attribute \"\(keyPath)\" does not exist in entity \(typeName(entityDescription.managedObjectClassName)) and will be ignored by \(typeName(self)) query clause."
)
}
}
}
fetchRequest.propertiesToFetch = propertiesToFetch
}
}
// MARK: - Select: Equatable
@warn_unused_result
public func == <T: SelectResultType, U: SelectResultType>(lhs: Select<T>, rhs: Select<U>) -> Bool {
return lhs.selectTerms == rhs.selectTerms
internal func keyPathForFirstSelectTerm() -> KeyPath {
switch self.selectTerms.first! {
case ._Attribute(let keyPath):
return keyPath
case ._Aggregate(_, _, let alias, _):
return alias
}
}
// MARK: Private
private let selectTerms: [SelectTerm]
}
@@ -412,19 +389,8 @@ extension Bool: SelectValueResultType {
}
public static func fromResultObject(result: AnyObject) -> Bool? {
switch result {
case let decimal as NSDecimalNumber:
// iOS: NSDecimalNumber(string: "0.5").boolValue // true
// OSX: NSDecimalNumber(string: "0.5").boolValue // false
return NSNumber(double: decimal.doubleValue).boolValue
case let number as NSNumber:
return number.boolValue
default:
return nil
}
return (result as? NSNumber)?.boolValue
}
}
@@ -704,119 +670,3 @@ extension NSDictionary: SelectAttributesResultType {
return result as! [[NSString: AnyObject]]
}
}
// MARK: - Internal
internal extension CollectionType where Generator.Element == SelectTerm {
internal func applyToFetchRequest<T>(fetchRequest: NSFetchRequest, owner: T) {
fetchRequest.includesPendingChanges = false
fetchRequest.resultType = .DictionaryResultType
func attributeDescriptionForKeyPath(keyPath: String, inEntity entity: NSEntityDescription) -> NSAttributeDescription? {
let components = keyPath.componentsSeparatedByString(".")
switch components.count {
case 0:
return nil
case 1:
return entity.attributesByName[components[0]]
default:
guard let relationship = entity.relationshipsByName[components[0]] else {
return nil
}
return attributeDescriptionForKeyPath(
components.dropFirst().joinWithSeparator("."),
inEntity: relationship.entity
)
}
}
var propertiesToFetch = [AnyObject]()
for term in self {
switch term {
case ._Attribute(let keyPath):
let entityDescription = fetchRequest.entity!
if let attributeDescription = attributeDescriptionForKeyPath(keyPath, inEntity: entityDescription) {
propertiesToFetch.append(attributeDescription)
}
else {
CoreStore.log(
.Warning,
message: "The key path \"\(keyPath)\" could not be resolved in entity \(cs_typeName(entityDescription.managedObjectClassName)) as an attribute and will be ignored by \(cs_typeName(owner)) query clause."
)
}
case ._Aggregate(let function, let keyPath, let alias, let nativeType):
let entityDescription = fetchRequest.entity!
if let attributeDescription = attributeDescriptionForKeyPath(keyPath, inEntity: entityDescription) {
let expressionDescription = NSExpressionDescription()
expressionDescription.name = alias
if nativeType == .UndefinedAttributeType {
expressionDescription.expressionResultType = attributeDescription.attributeType
}
else {
expressionDescription.expressionResultType = nativeType
}
expressionDescription.expression = NSExpression(
forFunction: function,
arguments: [NSExpression(forKeyPath: keyPath)]
)
propertiesToFetch.append(expressionDescription)
}
else {
CoreStore.log(
.Warning,
message: "The key path \"\(keyPath)\" could not be resolved in entity \(cs_typeName(entityDescription.managedObjectClassName)) as an attribute and will be ignored by \(cs_typeName(owner)) query clause."
)
}
case ._Identity(let alias, let nativeType):
let expressionDescription = NSExpressionDescription()
expressionDescription.name = alias
if nativeType == .UndefinedAttributeType {
expressionDescription.expressionResultType = .ObjectIDAttributeType
}
else {
expressionDescription.expressionResultType = nativeType
}
expressionDescription.expression = NSExpression.expressionForEvaluatedObject()
propertiesToFetch.append(expressionDescription)
}
}
fetchRequest.propertiesToFetch = propertiesToFetch
}
internal func keyPathForFirstSelectTerm() -> KeyPath {
switch self.first! {
case ._Attribute(let keyPath):
return keyPath
case ._Aggregate(_, _, let alias, _):
return alias
case ._Identity(let alias, _):
return alias
}
}
}

View File

@@ -2,7 +2,7 @@
// Tweak.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,34 +30,30 @@ import CoreData
// MARK: - Tweak
/**
The `Tweak` clause allows fine-tuning the `NSFetchRequest` for a fetch or query.
Sample usage:
```
let employees = transaction.fetchAll(
From(MyPersonEntity),
Tweak { (fetchRequest) -> Void in
fetchRequest.includesPendingChanges = false
fetchRequest.fetchLimit = 5
}
)
```
*/
The `Tweak` clause allows fine-tuning the `NSFetchRequest` for a fetch or query.
Sample usage:
let employees = transaction.fetchAll(
From(MyPersonEntity),
Tweak { (fetchRequest) -> Void in
fetchRequest.includesPendingChanges = false
fetchRequest.fetchLimit = 5
}
)
*/
public struct Tweak: FetchClause, QueryClause, DeleteClause {
/**
The block to customize the `NSFetchRequest`
*/
public let closure: (fetchRequest: NSFetchRequest) -> Void
// MARK: Public
/**
Initializes a `Tweak` clause with a closure where the `NSFetchRequest` may be configured.
- Important: `Tweak`'s closure is executed only just before the fetch occurs, so make sure that any values captured by the closure is not prone to race conditions. Also, some utilities (such as `ListMonitor`s) may keep `FetchClause`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter closure: the block to customize the `NSFetchRequest`
*/
public init(_ closure: (fetchRequest: NSFetchRequest) -> Void) {
Initializes a `Tweak` clause with a closure where the `NSFetchRequest` may be configured.
- parameter customization: a list of key path strings to group results with
*/
public init(_ customization: (fetchRequest: NSFetchRequest) -> Void) {
self.closure = closure
self.customization = customization
}
@@ -65,6 +61,9 @@ public struct Tweak: FetchClause, QueryClause, DeleteClause {
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
self.closure(fetchRequest: fetchRequest)
self.customization(fetchRequest: fetchRequest)
}
private let customization: (fetchRequest: NSFetchRequest) -> Void
}

View File

@@ -2,7 +2,7 @@
// Where.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -46,61 +46,68 @@ public prefix func !(clause: Where) -> Where {
// MARK: - Where
/**
The `Where` clause specifies the conditions for a fetch or a query.
*/
public struct Where: FetchClause, QueryClause, DeleteClause, Hashable {
The `Where` clause specifies the conditions for a fetch or a query.
*/
public struct Where: FetchClause, QueryClause, DeleteClause {
// MARK: Public
/**
The `NSPredicate` for the fetch or query
*/
public let predicate: NSPredicate
Initializes a `Where` clause with an `NSPredicate`
- parameter predicate: the `NSPredicate` for the fetch or query
*/
public init(_ predicate: NSPredicate) {
self.predicate = predicate
}
/**
Initializes a `Where` clause with a predicate that always evaluates to `true`
*/
Initializes a `Where` clause with a predicate that always evaluates to `true`
*/
public init() {
self.init(true)
}
/**
Initializes a `Where` clause with a predicate that always evaluates to the specified boolean value
- parameter value: the boolean value for the predicate
*/
Initializes a `Where` clause with a predicate that always evaluates to the specified boolean value
- parameter value: the boolean value for the predicate
*/
public init(_ value: Bool) {
self.init(NSPredicate(value: value))
}
/**
Initializes a `Where` clause with a predicate using the specified string format and arguments
- parameter format: the format string for the predicate
- parameter args: the arguments for `format`
*/
Initializes a `Where` clause with a predicate using the specified string format and arguments
- parameter format: the format string for the predicate
- parameter args: the arguments for `format`
*/
public init(_ format: String, _ args: NSObject...) {
self.init(NSPredicate(format: format, argumentArray: args))
}
/**
Initializes a `Where` clause with a predicate using the specified string format and arguments
- parameter format: the format string for the predicate
- parameter argumentArray: the arguments for `format`
*/
Initializes a `Where` clause with a predicate using the specified string format and arguments
- parameter format: the format string for the predicate
- parameter argumentArray: the arguments for `format`
*/
public init(_ format: String, argumentArray: [NSObject]?) {
self.init(NSPredicate(format: format, argumentArray: argumentArray))
}
/**
Initializes a `Where` clause that compares equality
- parameter keyPath: the keyPath to compare with
- parameter value: the arguments for the `==` operator
*/
Initializes a `Where` clause that compares equality
- parameter keyPath: the keyPath to compare with
- parameter value: the arguments for the `==` operator
*/
public init(_ keyPath: KeyPath, isEqualTo value: NSObject?) {
self.init(value == nil
@@ -109,67 +116,42 @@ public struct Where: FetchClause, QueryClause, DeleteClause, Hashable {
}
/**
Initializes a `Where` clause that compares membership
- parameter keyPath: the keyPath to compare with
- parameter list: the array to check membership of
*/
public init(_ keyPath: KeyPath, isMemberOf list: [NSObject]) {
Initializes a `Where` clause that compares membership
- parameter keyPath: the keyPath to compare with
- parameter list: the array to check membership of
*/
public init(_ keyPath: KeyPath, isMemberOf list: NSArray) {
self.init(NSPredicate(format: "\(keyPath) IN %@", list))
}
/**
Initializes a `Where` clause that compares membership
- parameter keyPath: the keyPath to compare with
- parameter list: the sequence to check membership of
*/
Initializes a `Where` clause that compares membership
- parameter keyPath: the keyPath to compare with
- parameter list: the sequence to check membership of
*/
public init<S: SequenceType where S.Generator.Element: NSObject>(_ keyPath: KeyPath, isMemberOf list: S) {
self.init(NSPredicate(format: "\(keyPath) IN %@", Array(list) as NSArray))
}
/**
Initializes a `Where` clause with an `NSPredicate`
- parameter predicate: the `NSPredicate` for the fetch or query
*/
public init(_ predicate: NSPredicate) {
self.predicate = predicate
}
public let predicate: NSPredicate
// MARK: FetchClause, QueryClause, DeleteClause
public func applyToFetchRequest(fetchRequest: NSFetchRequest) {
if let predicate = fetchRequest.predicate where predicate != self.predicate {
if fetchRequest.predicate != nil {
CoreStore.log(
.Warning,
message: "An existing predicate for the \(cs_typeName(NSFetchRequest)) was overwritten by \(cs_typeName(self)) query clause."
message: "An existing predicate for the \(typeName(NSFetchRequest)) was overwritten by \(typeName(self)) query clause."
)
}
fetchRequest.predicate = self.predicate
}
// MARK: Hashable
public var hashValue: Int {
return self.predicate.hashValue
}
}
// MARK: - Where: Equatable
@warn_unused_result
public func == (lhs: Where, rhs: Where) -> Bool {
return lhs.predicate == rhs.predicate
}

View File

@@ -0,0 +1,277 @@
//
// CoreStore+Querying.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - CoreStore
public extension CoreStore {
// MARK: Public
/**
Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
- parameter object: a reference to the object created/fetched outside the `DataStack`
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
@warn_unused_result
public static func fetchExisting<T: NSManagedObject>(object: T) -> T? {
return self.defaultStack.fetchExisting(object)
}
/**
Using the `defaultStack`, fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
@warn_unused_result
public static func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
return self.defaultStack.fetchExisting(objectID)
}
/**
Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
- parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack`
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
*/
@warn_unused_result
public static func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == T>(objects: S) -> [T] {
return self.defaultStack.fetchExisting(objects)
}
/**
Using the `defaultStack`, fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
*/
@warn_unused_result
public static func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == NSManagedObjectID>(objectIDs: S) -> [T] {
return self.defaultStack.fetchExisting(objectIDs)
}
/**
Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
return self.defaultStack.fetchOne(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
return self.defaultStack.fetchOne(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
return self.defaultStack.fetchAll(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
return self.defaultStack.fetchAll(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
return self.defaultStack.fetchCount(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
return self.defaultStack.fetchCount(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
return self.defaultStack.fetchObjectID(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
return self.defaultStack.fetchObjectID(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
return self.defaultStack.fetchObjectIDs(from, fetchClauses)
}
/**
Using the `defaultStack`, fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public static func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
return self.defaultStack.fetchObjectIDs(from, fetchClauses)
}
/**
Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@warn_unused_result
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
return self.defaultStack.queryValue(from, selectClause, queryClauses)
}
/**
Using the `defaultStack`, queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@warn_unused_result
public static func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
return self.defaultStack.queryValue(from, selectClause, queryClauses)
}
/**
Using the `defaultStack`, queries a dictionary of attribtue values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@warn_unused_result
public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
return self.defaultStack.queryAttributes(from, selectClause, queryClauses)
}
/**
Using the `defaultStack`, queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@warn_unused_result
public static func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
return self.defaultStack.queryAttributes(from, selectClause, queryClauses)
}
}

View File

@@ -0,0 +1,364 @@
//
// DataStack+Querying.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - DataStack
public extension DataStack {
// MARK: Public
/**
Fetches the `NSManagedObject` instance in the `DataStack`'s context from a reference created from a transaction or from a different managed object context.
- parameter object: a reference to the object created/fetched outside the `DataStack`
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
@warn_unused_result
public func fetchExisting<T: NSManagedObject>(object: T) -> T? {
do {
return (try self.mainContext.existingObjectWithID(object.objectID) as! T)
}
catch _ {
return nil
}
}
/**
Fetches the `NSManagedObject` instance in the `DataStack`'s context from an `NSManagedObjectID`.
- parameter objectID: the `NSManagedObjectID` for the object
- returns: the `NSManagedObject` instance if the object exists in the `DataStack`, or `nil` if not found.
*/
@warn_unused_result
public func fetchExisting<T: NSManagedObject>(objectID: NSManagedObjectID) -> T? {
do {
return (try self.mainContext.existingObjectWithID(objectID) as! T)
}
catch _ {
return nil
}
}
/**
Fetches the `NSManagedObject` instances in the `DataStack`'s context from references created from a transaction or from a different managed object context.
- parameter objects: an array of `NSManagedObject`s created/fetched outside the `DataStack`
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
*/
@warn_unused_result
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == T>(objects: S) -> [T] {
return objects.flatMap { (try? self.mainContext.existingObjectWithID($0.objectID)) as? T }
}
/**
Fetches the `NSManagedObject` instances in the `DataStack`'s context from a list of `NSManagedObjectID`.
- parameter objectIDs: the `NSManagedObjectID` array for the objects
- returns: the `NSManagedObject` array for objects that exists in the `DataStack`
*/
@warn_unused_result
public func fetchExisting<T: NSManagedObject, S: SequenceType where S.Generator.Element == NSManagedObjectID>(objectIDs: S) -> [T] {
return objectIDs.flatMap { (try? self.mainContext.existingObjectWithID($0)) as? T }
}
/**
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(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchOne(from, fetchClauses)
}
/**
Fetches the first `NSManagedObject` instance that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the first `NSManagedObject` instance that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchOne(from, fetchClauses)
}
/**
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchAll(from, fetchClauses)
}
/**
Fetches all `NSManagedObject` instances that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: all `NSManagedObject` instances that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchAll(from, fetchClauses)
}
/**
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchCount(from, fetchClauses)
}
/**
Fetches the number of `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the number `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchCount(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchObjectID(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for the first `NSManagedObject` that satisfies the specified `FetchClause`s
*/
@warn_unused_result
public func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchObjectID(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchObjectIDs(from, fetchClauses)
}
/**
Fetches the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for the fetch request. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: the `NSManagedObjectID` for all `NSManagedObject`s that satisfy the specified `FetchClause`s
*/
@warn_unused_result
public func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to fetch from a \(typeName(self)) outside the main thread."
)
return self.mainContext.fetchObjectIDs(from, fetchClauses)
}
/**
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@warn_unused_result
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to query from a \(typeName(self)) outside the main thread."
)
return self.mainContext.queryValue(from, selectClause, queryClauses)
}
/**
Queries aggregate values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@warn_unused_result
public func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to query from a \(typeName(self)) outside the main thread."
)
return self.mainContext.queryValue(from, selectClause, queryClauses)
}
/**
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@warn_unused_result
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to query from a \(typeName(self)) outside the main thread."
)
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
}
/**
Queries a dictionary of attribute values as specified by the `QueryClause`s. Requires at least a `Select` clause, and optional `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
A "query" differs from a "fetch" in that it only retrieves values already stored in the persistent store. As such, values from unsaved transactions or contexts will not be incorporated in the query result.
- parameter from: a `From` clause indicating the entity type
- parameter selectClause: a `Select<U>` clause indicating the properties to fetch, and with the generic type indicating the return type.
- parameter queryClauses: a series of `QueryClause` instances for the query request. Accepts `Where`, `OrderBy`, `GroupBy`, and `Tweak` clauses.
- returns: the result of the the query. The type of the return value is specified by the generic type of the `Select<U>` parameter.
*/
@warn_unused_result
public func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to query from a \(typeName(self)) outside the main thread."
)
return self.mainContext.queryAttributes(from, selectClause, queryClauses)
}
}

View File

@@ -2,7 +2,7 @@
// ClauseTypes.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -29,9 +29,6 @@ import CoreData
// MARK: - FetchClause
/**
The `FetchClause` implement clauses used to configure `NSFetchRequest`s.
*/
public protocol FetchClause {
func applyToFetchRequest(fetchRequest: NSFetchRequest)
@@ -40,9 +37,6 @@ public protocol FetchClause {
// MARK: - QueryClause
/**
The `QueryClause` implement clauses used to configure `NSFetchRequest`s.
*/
public protocol QueryClause {
func applyToFetchRequest(fetchRequest: NSFetchRequest)
@@ -51,9 +45,6 @@ public protocol QueryClause {
// MARK: - DeleteClause
/**
The `DeleteClause` implement clauses used to configure `NSFetchRequest`s.
*/
public protocol DeleteClause {
func applyToFetchRequest(fetchRequest: NSFetchRequest)

View File

@@ -2,7 +2,7 @@
// BaseDataTransaction+Importing.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -31,24 +31,25 @@ import CoreData
public extension BaseDataTransaction {
// MARK: Public
/**
Creates an `ImportableObject` by importing from the specified import source.
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- throws: an `ErrorType` thrown from any of the `ImportableObject` methods
- returns: the created `ImportableObject` instance, or `nil` if the import was ignored
*/
Creates an `ImportableObject` by importing from the specified import source.
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- returns: the created `ImportableObject` instance, or `nil` if the import was ignored
*/
public func importObject<T where T: NSManagedObject, T: ImportableObject>(
into: Into<T>,
source: T.ImportSource) throws -> T? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
)
return try cs_autoreleasepool {
return try autoreleasepool {
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
@@ -62,50 +63,22 @@ public extension BaseDataTransaction {
}
/**
Updates an existing `ImportableObject` by importing values from the specified import source.
- parameter object: the `NSManagedObject` to update
- parameter source: the object to import values from
- throws: an `ErrorType` thrown from any of the `ImportableObject` methods
*/
public func importObject<T where T: NSManagedObject, T: ImportableObject>(
object: T,
source: T.ImportSource) throws {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(cs_typeName(object)) outside the transaction's designated queue."
)
try cs_autoreleasepool {
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {
return
}
try object.didInsertFromImportSource(source, inTransaction: self)
}
}
Creates multiple `ImportableObject`s by importing from the specified array of import sources.
/**
Creates multiple `ImportableObject`s by importing from the specified array of import sources.
- parameter into: an `Into` clause specifying the entity type
- parameter sourceArray: the array of objects to import values from
- throws: an `ErrorType` thrown from any of the `ImportableObject` methods
- returns: the array of created `ImportableObject` instances
*/
- parameter into: an `Into` clause specifying the entity type
- parameter sourceArray: the array of objects to import values from
- returns: the array of created `ImportableObject` instances
*/
public func importObjects<T, S: SequenceType where T: NSManagedObject, T: ImportableObject, S.Generator.Element == T.ImportSource>(
into: Into<T>,
sourceArray: S) throws -> [T] {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
)
return try cs_autoreleasepool {
return try autoreleasepool {
return try sourceArray.flatMap { (source) -> T? in
@@ -114,7 +87,7 @@ public extension BaseDataTransaction {
return nil
}
return try cs_autoreleasepool {
return try autoreleasepool {
let object = self.create(into)
try object.didInsertFromImportSource(source, inTransaction: self)
@@ -125,23 +98,22 @@ public extension BaseDataTransaction {
}
/**
Updates an existing `ImportableUniqueObject` or creates a new instance by importing from the specified import source.
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- throws: an `ErrorType` thrown from any of the `ImportableUniqueObject` methods
- returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored
*/
Updates an existing `ImportableUniqueObject` or creates a new instance by importing from the specified import source.
- parameter into: an `Into` clause specifying the entity type
- parameter source: the object to import values from
- returns: the created/updated `ImportableUniqueObject` instance, or `nil` if the import was ignored
*/
public func importUniqueObject<T where T: NSManagedObject, T: ImportableUniqueObject>(
into: Into<T>,
source: T.ImportSource) throws -> T? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
)
return try cs_autoreleasepool {
return try autoreleasepool {
let uniqueIDKeyPath = T.uniqueIDKeyPath
guard let uniqueIDValue = try T.uniqueIDFromImportSource(source, inTransaction: self) else {
@@ -152,7 +124,7 @@ public extension BaseDataTransaction {
if let object = self.fetchOne(From(T), Where(uniqueIDKeyPath, isEqualTo: uniqueIDValue)) {
guard T.shouldUpdateFromImportSource(source, inTransaction: self) else {
return nil
}
@@ -175,15 +147,13 @@ public extension BaseDataTransaction {
}
/**
Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources.
- Warning: While the array returned from `importUniqueObjects(...)` correctly maps to the order of `sourceArray`, the order of objects called with `ImportableUniqueObject` methods is arbitrary. Do not make assumptions that any particular object will be imported ahead or after another object.
- parameter into: an `Into` clause specifying the entity type
- parameter sourceArray: the array of objects to import values from
- parameter preProcess: a closure that lets the caller tweak the internal `UniqueIDType`-to-`ImportSource` mapping to be used for importing. Callers can remove from/add to/update `mapping` and return the updated array from the closure.
- throws: an `ErrorType` thrown from any of the `ImportableUniqueObject` methods
- returns: the array of created/updated `ImportableUniqueObject` instances
*/
Updates existing `ImportableUniqueObject`s or creates them by importing from the specified array of import sources.
- parameter into: an `Into` clause specifying the entity type
- parameter sourceArray: the array of objects to import values from
- parameter preProcess: a closure that lets the caller tweak the internal `UniqueIDType`-to-`ImportSource` mapping to be used for importing. Callers can remove from/add to/update `mapping` and return the updated array from the closure.
- returns: the array of created/updated `ImportableUniqueObject` instances
*/
public func importUniqueObjects<T, S: SequenceType where T: NSManagedObject, T: ImportableUniqueObject, S.Generator.Element == T.ImportSource>(
into: Into<T>,
sourceArray: S,
@@ -191,13 +161,13 @@ public extension BaseDataTransaction {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to import an object of type \(cs_typeName(into.entityClass)) outside the transaction's designated queue."
"Attempted to import an object of type \(typeName(into.entityClass)) outside the transaction's designated queue."
)
return try cs_autoreleasepool {
return try autoreleasepool {
var mapping = Dictionary<T.UniqueIDType, T.ImportSource>()
let sortedIDs = try cs_autoreleasepool {
let sortedIDs = try autoreleasepool {
return try sourceArray.flatMap { (source) -> T.UniqueIDType? in
@@ -211,12 +181,12 @@ public extension BaseDataTransaction {
}
}
mapping = try cs_autoreleasepool { try preProcess(mapping: mapping) }
mapping = try autoreleasepool { try preProcess(mapping: mapping) }
var objects = Dictionary<T.UniqueIDType, T>()
for object in self.fetchAll(From(T), Where(T.uniqueIDKeyPath, isMemberOf: sortedIDs)) ?? [] {
for object in self.fetchAll(From(T), Where(T.uniqueIDKeyPath, isMemberOf: mapping.keys)) ?? [] {
try cs_autoreleasepool {
try autoreleasepool {
let uniqueIDValue = object.uniqueIDValue
@@ -233,7 +203,7 @@ public extension BaseDataTransaction {
for (uniqueIDValue, source) in mapping {
try cs_autoreleasepool {
try autoreleasepool {
guard T.shouldInsertFromImportSource(source, inTransaction: self) else {

View File

@@ -2,7 +2,7 @@
// ImportableObject.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,45 +30,44 @@ import CoreData
// MARK: - ImportableObject
/**
`NSManagedObject` subclasses that conform to the `ImportableObject` protocol can be imported from a specified `ImportSource`. This allows transactions to create and insert instances this way:
```
class MyPersonEntity: NSManagedObject, ImportableObject {
typealias ImportSource = NSDictionary
// ...
}
CoreStore.beginAsynchronous { (transaction) -> Void in
let json: NSDictionary = // ...
let person = try! transaction.importObject(
Into(MyPersonEntity),
source: json
)
// ...
transaction.commit()
}
```
*/
`NSManagedObject` subclasses that conform to the `ImportableObject` protocol can be imported from a specified `ImportSource`. This allows transactions to create and insert instances this way:
class MyPersonEntity: NSManagedObject, ImportableObject {
typealias ImportSource = NSDictionary
// ...
}
CoreStore.beginAsynchronous { (transaction) -> Void in
let json: NSDictionary = // ...
let person = try! transaction.importObject(
Into(MyPersonEntity),
source: json
)
// ...
transaction.commit()
}
*/
public protocol ImportableObject: class {
/**
The data type for the import source. This is most commonly an `NSDictionary` or another external source such as an `NSUserDefaults`.
*/
associatedtype ImportSource
The data type for the import source. This is most commonly an `NSDictionary` or another external source such as an `NSUserDefaults`.
*/
typealias ImportSource
/**
Return `true` if an object should be created from `source`. Return `false` to ignore and skip `source`. The default implementation returns `true`.
- parameter source: the object to import from
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
- returns: `true` if an object should be created from `source`. Return `false` to ignore.
*/
Return `true` if an object should be created from `source`. Return `false` to ignore and skip `source`. The default implementation returns `true`.
- parameter source: the object to import from
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
- returns: `true` if an object should be created from `source`. Return `false` to ignore.
*/
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
/**
Implements the actual importing of data from `source`. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importObjects(:sourceArray:)` call to be cancelled.
- parameter source: the object to import from
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
Implements the actual importing of data from `source`. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importObjects(:sourceArray:)` call to be cancelled.
- parameter source: the object to import from
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
*/
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
}

View File

@@ -0,0 +1,136 @@
//
// ImportableUniqueObject.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - ImportableUniqueObject
/**
`NSManagedObject` subclasses that conform to the `ImportableUniqueObject` protocol can be imported from a specified `ImportSource`. This allows transactions to either update existing objects or create new instances this way:
class MyPersonEntity: NSManagedObject, ImportableUniqueObject {
typealias ImportSource = NSDictionary
typealias UniqueIDType = NSString
// ...
}
CoreStore.beginAsynchronous { (transaction) -> Void in
let json: NSDictionary = // ...
let person = try! transaction.importUniqueObject(
Into(MyPersonEntity),
source: json
)
// ...
transaction.commit()
}
*/
public protocol ImportableUniqueObject: ImportableObject {
/**
The data type for the import source. This is most commonly an `NSDictionary` or another external source such as an `NSUserDefaults`.
*/
typealias ImportSource
/**
The data type for the entity's unique ID attribute
*/
typealias UniqueIDType: NSObject
/**
The keyPath to the entity's unique ID attribute
*/
static var uniqueIDKeyPath: String { get }
/**
The object's unique ID value
*/
var uniqueIDValue: UniqueIDType { get set }
/**
Return `true` if an object should be created from `source`. Return `false` to ignore and skip `source`. The default implementation returns the value returned by the `shouldUpdateFromImportSource(:inTransaction:)` implementation.
- parameter source: the object to import from
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
- returns: `true` if an object should be created from `source`. Return `false` to ignore.
*/
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
/**
Return `true` if an object should be updated from `source`. Return `false` to ignore and skip `source`. The default implementation returns `true`.
- parameter source: the object to import from
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
- returns: `true` if an object should be updated from `source`. Return `false` to ignore.
*/
static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool
/**
Return the unique ID as extracted from `source`. This method is called before `shouldInsertFromImportSource(...)` or `shouldUpdateFromImportSource(...)`. Return `nil` to skip importing from `source`. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled.
- parameter source: the object to import from
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
- returns: the unique ID as extracted from `source`, or `nil` to skip importing from `source`.
*/
static func uniqueIDFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws -> UniqueIDType?
/**
Implements the actual importing of data from `source`. This method is called just after the object is created and assigned its unique ID as returned from `uniqueIDFromImportSource(...)`. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled. The default implementation simply calls `updateFromImportSource(...)`.
- parameter source: the object to import from
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
*/
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
/**
Implements the actual importing of data from `source`. This method is called just after the existing object is fetched using its unique ID. Implementers should pull values from `source` and assign them to the receiver's attributes. Note that throwing from this method will cause subsequent imports that are part of the same `importUniqueObjects(:sourceArray:)` call to be cancelled.
- parameter source: the object to import from
- parameter transaction: the transaction that invoked the import. Use the transaction to fetch or create related objects if needed.
*/
func updateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws
}
// MARK: - ImportableUniqueObject (Default Implementations)
public extension ImportableUniqueObject {
static func shouldInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
return self.shouldUpdateFromImportSource(source, inTransaction: transaction)
}
static func shouldUpdateFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) -> Bool {
return true
}
func didInsertFromImportSource(source: ImportSource, inTransaction transaction: BaseDataTransaction) throws {
try self.updateFromImportSource(source, inTransaction: transaction)
}
}

View File

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

View File

@@ -0,0 +1,68 @@
//
// NSObject+CoreStore.swift
// CoreStore
//
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
internal func getAssociatedObjectForKey<T: AnyObject>(key: UnsafePointer<Void>, inObject object: AnyObject) -> T? {
switch objc_getAssociatedObject(object, key) {
case let associatedObject as T:
return associatedObject
case let associatedObject as WeakObject:
return associatedObject.object as? T
default:
return nil
}
}
internal func setAssociatedRetainedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
internal func setAssociatedCopiedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_COPY_NONATOMIC)
}
internal func setAssociatedAssignedObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
objc_setAssociatedObject(object, key, associatedObject, .OBJC_ASSOCIATION_ASSIGN)
}
internal func setAssociatedWeakObject<T: AnyObject>(associatedObject: T?, forKey key: UnsafePointer<Void>, inObject object: AnyObject) {
if let associatedObject = associatedObject {
objc_setAssociatedObject(object, key, WeakObject(associatedObject), .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
else {
objc_setAssociatedObject(object, key, nil, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}

View File

@@ -2,7 +2,7 @@
// FetchedResultsControllerDelegate.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -27,10 +27,9 @@ import Foundation
import CoreData
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - FetchedResultsControllerHandler
@available(OSX, unavailable)
internal protocol FetchedResultsControllerHandler: class {
func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?)
@@ -47,17 +46,14 @@ internal protocol FetchedResultsControllerHandler: class {
// MARK: - FetchedResultsControllerDelegate
@available(OSX, unavailable)
internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResultsControllerDelegate {
// MARK: Internal
@nonobjc
internal var enabled = true
@nonobjc
internal weak var handler: FetchedResultsControllerHandler?
@nonobjc
internal weak var fetchedResultsController: NSFetchedResultsController? {
didSet {
@@ -75,8 +71,7 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
// MARK: NSFetchedResultsControllerDelegate
@objc
dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
@objc dynamic func controllerWillChangeContent(controller: NSFetchedResultsController) {
guard self.enabled else {
@@ -89,8 +84,7 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
self.handler?.controllerWillChangeContent(controller)
}
@objc
dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
@objc dynamic func controllerDidChangeContent(controller: NSFetchedResultsController) {
guard self.enabled else {
@@ -100,8 +94,7 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
self.handler?.controllerDidChangeContent(controller)
}
@objc
dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeObject anObject: AnyObject, atIndexPath indexPath: NSIndexPath?, forChangeType type: NSFetchedResultsChangeType, newIndexPath: NSIndexPath?) {
guard self.enabled else {
@@ -188,8 +181,7 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
)
}
@objc
dynamic func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
@objc dynamic func controller(controller: NSFetchedResultsController, didChangeSection sectionInfo: NSFetchedResultsSectionInfo, atIndex sectionIndex: Int, forChangeType type: NSFetchedResultsChangeType) {
guard self.enabled else {
@@ -211,8 +203,7 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
)
}
@objc
dynamic func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
@objc dynamic func controller(controller: NSFetchedResultsController, sectionIndexTitleForSectionName sectionName: String) -> String? {
return self.handler?.controller(
controller,
@@ -223,11 +214,6 @@ internal final class FetchedResultsControllerDelegate: NSObject, NSFetchedResult
// MARK: Private
@nonobjc
private var deletedSections = Set<Int>()
@nonobjc
private var insertedSections = Set<Int>()
}
#endif

View File

@@ -1,8 +1,8 @@
//
// NSPersistentStore+Setup.swift
// Functions.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -24,58 +24,58 @@
//
import Foundation
import CoreData
// MARK: - NSPersistentStore
internal extension NSPersistentStore {
internal func autoreleasepool<T>(@noescape closure: () -> T) -> T {
// MARK: Internal
@nonobjc
internal var storageInterface: StorageInterface? {
var closureValue: T!
ObjectiveC.autoreleasepool {
get {
closureValue = closure()
}
return closureValue
}
internal func autoreleasepool<T>(@noescape closure: () throws -> T) throws -> T {
var closureValue: T!
var closureError: ErrorType?
ObjectiveC.autoreleasepool {
do {
let wrapper: StorageObject? = cs_getAssociatedObjectForKey(
&PropertyKeys.storageInterface,
inObject: self
)
return wrapper?.storageInterface
closureValue = try closure()
}
set {
catch {
cs_setAssociatedRetainedObject(
StorageObject(newValue),
forKey: &PropertyKeys.storageInterface,
inObject: self
)
closureError = error
}
}
// MARK: Private
private struct PropertyKeys {
if let closureError = closureError {
static var storageInterface: Void?
throw closureError
}
return closureValue
}
// MARK: - StorageObject
private class StorageObject: NSObject {
internal func autoreleasepool(@noescape closure: () throws -> Void) throws {
// MARK: Private
@nonobjc
private let storageInterface: StorageInterface?
@nonobjc
private init(_ storage: StorageInterface?) {
var closureError: ErrorType?
ObjectiveC.autoreleasepool {
self.storageInterface = storage
do {
try closure()
}
catch {
closureError = error
}
}
if let closureError = closureError {
throw closureError
}
}

View File

@@ -2,7 +2,7 @@
// MigrationManager.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal

View File

@@ -0,0 +1,26 @@
//
// NSFileManager+Setup.swift
// CoreStore
//
// Created by John Rommel Estropia on 2015/07/19.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
//
import Foundation
// MARK: - NSFileManager
internal extension NSFileManager {
// MARK: Internal
internal func removeSQLiteStoreAtURL(fileURL: NSURL) {
_ = try? self.removeItemAtURL(fileURL)
let filePath = fileURL.path!
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-shm"))
_ = try? self.removeItemAtPath(filePath.stringByAppendingString("-wal"))
}
}

View File

@@ -0,0 +1,89 @@
//
// NSManagedObjectContext+CoreSpotlight.swift
// CoreStore
//
// Copyright (c) 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
import CoreSpotlight
import MobileCoreServices
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - NSManagedObjectContext
internal extension NSManagedObjectContext {
// MARK: Internal
internal func commitCompletionForSearchableItems() -> (() -> Void) {
guard #available(iOS 9.0, *) else {
return {}
}
guard CSSearchableIndex.isIndexingAvailable() else {
return {}
}
let searchableItems = self.insertedObjects.flatMap {
let attributeSet = CSSearchableItemAttributeSet(itemContentType: kUTTypeJSON as String)
attributeSet.title = "aa"
attributeSet.contentDescription = "bb"
let item = CSSearchableItem(
uniqueIdentifier: "aa",
domainIdentifier: "jp.eureka.sample",
attributeSet: attributeSet
)
return item
}
return {
CSSearchableIndex.defaultSearchableIndex().indexSearchableItems(
searchableItems,
completionHandler: { (error) -> Void in
//...
}
)
}
//
//
// for case (let object as CoreSpotlightSearchableObject) in self.context.insertedObjects {
//
// object.coreSpotlightIndexValue
// }
//
// public var insertedObjects: Set<NSManagedObject> { get }
// public var updatedObjects: Set<NSManagedObject> { get }
// public var deletedObjects: Set<NSManagedObject> { get }
// public var registeredObjects: Set<NSManagedObject> { get }
}
}

View File

@@ -2,7 +2,7 @@
// NSManagedObjectContext+CoreStore.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -36,12 +36,11 @@ internal extension NSManagedObjectContext {
// MARK: Internal
@nonobjc
internal var shouldCascadeSavesToParent: Bool {
get {
let number: NSNumber? = cs_getAssociatedObjectForKey(
let number: NSNumber? = getAssociatedObjectForKey(
&PropertyKeys.shouldCascadeSavesToParent,
inObject: self
)
@@ -49,7 +48,7 @@ internal extension NSManagedObjectContext {
}
set {
cs_setAssociatedCopiedObject(
setAssociatedCopiedObject(
NSNumber(bool: newValue),
forKey: &PropertyKeys.shouldCascadeSavesToParent,
inObject: self
@@ -57,13 +56,11 @@ internal extension NSManagedObjectContext {
}
}
@nonobjc
internal func entityDescriptionForEntityType(entity: NSManagedObject.Type) -> NSEntityDescription? {
return self.entityDescriptionForEntityClass(entity)
}
@nonobjc
internal func entityDescriptionForEntityClass(entity: AnyClass) -> NSEntityDescription? {
guard let entityName = self.parentStack?.entityNameForEntityClass(entity) else {
@@ -76,19 +73,9 @@ internal extension NSManagedObjectContext {
)
}
@nonobjc
internal func setupForCoreStoreWithContextName(contextName: String) {
#if USE_FRAMEWORKS
self.name = contextName
#else
if #available(iOS 8.0, *) {
self.name = contextName
}
#endif
self.name = contextName
self.observerForWillSaveNotification = NotificationObserver(
notificationName: NSManagedObjectContextWillSaveNotification,
@@ -109,8 +96,8 @@ internal extension NSManagedObjectContext {
}
catch {
CoreStore.log(
CoreStoreError(error),
CoreStore.handleError(
error as NSError,
"Failed to obtain permanent ID(s) for \(numberOfInsertedObjects) inserted object(s)."
)
}
@@ -127,19 +114,18 @@ internal extension NSManagedObjectContext {
static var shouldCascadeSavesToParent: Void?
}
@nonobjc
private var observerForWillSaveNotification: NotificationObserver? {
get {
return cs_getAssociatedObjectForKey(
return getAssociatedObjectForKey(
&PropertyKeys.observerForWillSaveNotification,
inObject: self
)
}
set {
cs_setAssociatedRetainedObject(
setAssociatedRetainedObject(
newValue,
forKey: &PropertyKeys.observerForWillSaveNotification,
inObject: self

View File

@@ -2,7 +2,7 @@
// NSManagedObjectContext+Querying.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -31,9 +31,8 @@ import CoreData
internal extension NSManagedObjectContext {
// MARK: Internal: Fetch Existing
// MARK: Internal
@nonobjc
internal func fetchExisting<T: NSManagedObject>(object: T) -> T? {
if object.objectID.temporaryID {
@@ -47,8 +46,8 @@ internal extension NSManagedObjectContext {
}
catch {
CoreStore.log(
CoreStoreError(error),
CoreStore.handleError(
error as NSError,
"Failed to obtain permanent ID for object."
)
return nil
@@ -62,45 +61,34 @@ internal extension NSManagedObjectContext {
}
catch {
CoreStore.log(
CoreStoreError(error),
"Failed to load existing \(cs_typeName(object)) in context."
CoreStore.handleError(
error as NSError,
"Failed to load existing \(typeName(object)) in context."
)
return nil
}
}
// MARK: Internal: Fetch One
@nonobjc
internal func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> T? {
return self.fetchOne(from, fetchClauses)
}
@nonobjc
internal func fetchOne<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> T? {
let fetchRequest = CoreStoreFetchRequest()
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
let fetchRequest = NSFetchRequest()
from.applyToFetchRequest(fetchRequest, context: self)
fetchRequest.fetchLimit = 1
fetchRequest.resultType = .ManagedObjectResultType
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
guard storeFound else {
for clause in fetchClauses {
return nil
clause.applyToFetchRequest(fetchRequest)
}
return self.fetchOne(fetchRequest)
}
@nonobjc
internal func fetchOne<T: NSManagedObject>(fetchRequest: NSFetchRequest) -> T? {
var fetchResults: [T]?
var fetchError: ErrorType?
var fetchError: NSError?
self.performBlockAndWait {
do {
@@ -109,13 +97,13 @@ internal extension NSManagedObjectContext {
}
catch {
fetchError = error
fetchError = error as NSError
}
}
if fetchResults == nil {
CoreStore.log(
CoreStoreError(fetchError),
CoreStore.handleError(
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request."
)
return nil
@@ -124,37 +112,26 @@ internal extension NSManagedObjectContext {
return fetchResults?.first
}
// MARK: Internal: Fetch All
@nonobjc
internal func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [T]? {
return self.fetchAll(from, fetchClauses)
}
@nonobjc
internal func fetchAll<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [T]? {
let fetchRequest = CoreStoreFetchRequest()
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
let fetchRequest = NSFetchRequest()
from.applyToFetchRequest(fetchRequest, context: self)
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectResultType
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
guard storeFound else {
for clause in fetchClauses {
return nil
clause.applyToFetchRequest(fetchRequest)
}
return self.fetchAll(fetchRequest)
}
@nonobjc
internal func fetchAll<T: NSManagedObject>(fetchRequest: NSFetchRequest) -> [T]? {
var fetchResults: [T]?
var fetchError: ErrorType?
var fetchError: NSError?
self.performBlockAndWait {
do {
@@ -163,13 +140,13 @@ internal extension NSManagedObjectContext {
}
catch {
fetchError = error
fetchError = error as NSError
}
}
if fetchResults == nil {
CoreStore.log(
CoreStoreError(fetchError),
CoreStore.handleError(
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request."
)
return nil
@@ -178,31 +155,20 @@ internal extension NSManagedObjectContext {
return fetchResults
}
// MARK: Internal: Count
@nonobjc
internal func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> Int? {
return self.fetchCount(from, fetchClauses)
}
@nonobjc
internal func fetchCount<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> Int? {
let fetchRequest = CoreStoreFetchRequest()
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
let fetchRequest = NSFetchRequest()
from.applyToFetchRequest(fetchRequest, context: self)
guard storeFound else {
for clause in fetchClauses {
return nil
clause.applyToFetchRequest(fetchRequest)
}
return self.fetchCount(fetchRequest)
}
@nonobjc
internal func fetchCount(fetchRequest: NSFetchRequest) -> Int? {
var count = 0
var error: NSError?
@@ -212,8 +178,8 @@ internal extension NSManagedObjectContext {
}
if count == NSNotFound {
CoreStore.log(
CoreStoreError(error),
CoreStore.handleError(
error ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request."
)
return nil
@@ -222,37 +188,26 @@ internal extension NSManagedObjectContext {
return count
}
// MARK: Internal: Object ID
@nonobjc
internal func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> NSManagedObjectID? {
return self.fetchObjectID(from, fetchClauses)
}
@nonobjc
internal func fetchObjectID<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> NSManagedObjectID? {
let fetchRequest = CoreStoreFetchRequest()
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
let fetchRequest = NSFetchRequest()
from.applyToFetchRequest(fetchRequest, context: self)
fetchRequest.fetchLimit = 1
fetchRequest.resultType = .ManagedObjectIDResultType
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
guard storeFound else {
for clause in fetchClauses {
return nil
clause.applyToFetchRequest(fetchRequest)
}
return self.fetchObjectID(fetchRequest)
}
@nonobjc
internal func fetchObjectID(fetchRequest: NSFetchRequest) -> NSManagedObjectID? {
var fetchResults: [NSManagedObjectID]?
var fetchError: ErrorType?
var fetchError: NSError?
self.performBlockAndWait {
do {
@@ -261,13 +216,13 @@ internal extension NSManagedObjectContext {
}
catch {
fetchError = error
fetchError = error as NSError
}
}
if fetchResults == nil {
CoreStore.log(
CoreStoreError(fetchError),
CoreStore.handleError(
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request."
)
return nil
@@ -276,37 +231,26 @@ internal extension NSManagedObjectContext {
return fetchResults?.first
}
// MARK: Internal: Object IDs
@nonobjc
internal func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> [NSManagedObjectID]? {
return self.fetchObjectIDs(from, fetchClauses)
}
@nonobjc
internal func fetchObjectIDs<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> [NSManagedObjectID]? {
let fetchRequest = CoreStoreFetchRequest()
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
let fetchRequest = NSFetchRequest()
from.applyToFetchRequest(fetchRequest, context: self)
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectIDResultType
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
guard storeFound else {
for clause in fetchClauses {
return nil
clause.applyToFetchRequest(fetchRequest)
}
return self.fetchObjectIDs(fetchRequest)
}
@nonobjc
internal func fetchObjectIDs(fetchRequest: NSFetchRequest) -> [NSManagedObjectID]? {
var fetchResults: [NSManagedObjectID]?
var fetchError: ErrorType?
var fetchError: NSError?
self.performBlockAndWait {
do {
@@ -315,13 +259,13 @@ internal extension NSManagedObjectContext {
}
catch {
fetchError = error
fetchError = error as NSError
}
}
if fetchResults == nil {
CoreStore.log(
CoreStoreError(fetchError),
CoreStore.handleError(
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request."
)
return nil
@@ -330,46 +274,35 @@ internal extension NSManagedObjectContext {
return fetchResults
}
// MARK: Internal: Delete All
@nonobjc
internal func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: DeleteClause...) -> Int? {
return self.deleteAll(from, deleteClauses)
}
@nonobjc
internal func deleteAll<T: NSManagedObject>(from: From<T>, _ deleteClauses: [DeleteClause]) -> Int? {
let fetchRequest = CoreStoreFetchRequest()
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
let fetchRequest = NSFetchRequest()
from.applyToFetchRequest(fetchRequest, context: self)
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectResultType
fetchRequest.returnsObjectsAsFaults = true
fetchRequest.includesPropertyValues = false
deleteClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
guard storeFound else {
for clause in deleteClauses {
return nil
clause.applyToFetchRequest(fetchRequest)
}
return self.deleteAll(fetchRequest)
}
@nonobjc
internal func deleteAll(fetchRequest: NSFetchRequest) -> Int? {
var numberOfDeletedObjects: Int?
var fetchError: ErrorType?
var fetchError: NSError?
self.performBlockAndWait {
cs_autoreleasepool {
autoreleasepool {
do {
let fetchResults = try self.executeFetchRequest(fetchRequest) as? [NSManagedObject] ?? []
let fetchResults = try self.executeFetchRequest(fetchRequest) as? [T] ?? []
for object in fetchResults {
self.deleteObject(object)
@@ -378,14 +311,14 @@ internal extension NSManagedObjectContext {
}
catch {
fetchError = error
fetchError = error as NSError
}
}
}
if numberOfDeletedObjects == nil {
CoreStore.log(
CoreStoreError(fetchError),
CoreStore.handleError(
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request."
)
return nil
@@ -394,39 +327,27 @@ internal extension NSManagedObjectContext {
return numberOfDeletedObjects
}
// MARK: Internal: Value
@nonobjc
internal func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: QueryClause...) -> U? {
return self.queryValue(from, selectClause, queryClauses)
}
@nonobjc
internal func queryValue<T: NSManagedObject, U: SelectValueResultType>(from: From<T>, _ selectClause: Select<U>, _ queryClauses: [QueryClause]) -> U? {
let fetchRequest = CoreStoreFetchRequest()
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
let fetchRequest = NSFetchRequest()
from.applyToFetchRequest(fetchRequest, context: self)
fetchRequest.fetchLimit = 0
let selectTerms = selectClause.selectTerms
selectTerms.applyToFetchRequest(fetchRequest, owner: selectClause)
queryClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
selectClause.applyToFetchRequest(fetchRequest)
guard storeFound else {
for clause in queryClauses {
return nil
clause.applyToFetchRequest(fetchRequest)
}
return self.queryValue(selectTerms, fetchRequest: fetchRequest)
}
@nonobjc
internal func queryValue<U: SelectValueResultType>(selectTerms: [SelectTerm], fetchRequest: NSFetchRequest) -> U? {
var fetchResults: [AnyObject]?
var fetchError: ErrorType?
var fetchError: NSError?
self.performBlockAndWait {
do {
@@ -435,91 +356,47 @@ internal extension NSManagedObjectContext {
}
catch {
fetchError = error
fetchError = error as NSError
}
}
if let fetchResults = fetchResults {
if let rawResult = fetchResults.first as? NSDictionary,
let rawObject: AnyObject = rawResult[selectTerms.keyPathForFirstSelectTerm()] {
return Select<U>.ReturnType.fromResultObject(rawObject)
let rawObject: AnyObject = rawResult[selectClause.keyPathForFirstSelectTerm()] {
return Select<U>.ReturnType.fromResultObject(rawObject)
}
return nil
}
CoreStore.log(
CoreStoreError(fetchError),
CoreStore.handleError(
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request."
)
return nil
}
@nonobjc
internal func queryValue(selectTerms: [SelectTerm], fetchRequest: NSFetchRequest) -> AnyObject? {
var fetchResults: [AnyObject]?
var fetchError: ErrorType?
self.performBlockAndWait {
do {
fetchResults = try self.executeFetchRequest(fetchRequest)
}
catch {
fetchError = error
}
}
if let fetchResults = fetchResults {
if let rawResult = fetchResults.first as? NSDictionary,
let rawObject: AnyObject = rawResult[selectTerms.keyPathForFirstSelectTerm()] {
return rawObject
}
return nil
}
CoreStore.log(
CoreStoreError(fetchError),
"Failed executing fetch request."
)
return nil
}
// MARK: Internal: Attributes
@nonobjc
internal func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: QueryClause...) -> [[NSString: AnyObject]]? {
return self.queryAttributes(from, selectClause, queryClauses)
}
@nonobjc
internal func queryAttributes<T: NSManagedObject>(from: From<T>, _ selectClause: Select<NSDictionary>, _ queryClauses: [QueryClause]) -> [[NSString: AnyObject]]? {
let fetchRequest = CoreStoreFetchRequest()
let storeFound = from.applyToFetchRequest(fetchRequest, context: self)
let fetchRequest = NSFetchRequest()
from.applyToFetchRequest(fetchRequest, context: self)
fetchRequest.fetchLimit = 0
selectClause.selectTerms.applyToFetchRequest(fetchRequest, owner: selectClause)
queryClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
selectClause.applyToFetchRequest(fetchRequest)
guard storeFound else {
for clause in queryClauses {
return nil
clause.applyToFetchRequest(fetchRequest)
}
return self.queryAttributes(fetchRequest)
}
@nonobjc
internal func queryAttributes(fetchRequest: NSFetchRequest) -> [[NSString: AnyObject]]? {
var fetchResults: [AnyObject]?
var fetchError: ErrorType?
var fetchError: NSError?
self.performBlockAndWait {
do {
@@ -528,7 +405,7 @@ internal extension NSManagedObjectContext {
}
catch {
fetchError = error
fetchError = error as NSError
}
}
if let fetchResults = fetchResults {
@@ -536,8 +413,8 @@ internal extension NSManagedObjectContext {
return Select<NSDictionary>.ReturnType.fromResultObjects(fetchResults)
}
CoreStore.log(
CoreStoreError(fetchError),
CoreStore.handleError(
fetchError ?? NSError(coreStoreErrorCode: .UnknownError),
"Failed executing fetch request."
)
return nil

View File

@@ -2,7 +2,7 @@
// NSManagedObjectContext+Setup.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -33,7 +33,6 @@ internal extension NSManagedObjectContext {
// MARK: Internal
@nonobjc
internal weak var parentStack: DataStack? {
get {
@@ -43,7 +42,7 @@ internal extension NSManagedObjectContext {
return parentContext.parentStack
}
return cs_getAssociatedObjectForKey(&PropertyKeys.parentStack, inObject: self)
return getAssociatedObjectForKey(&PropertyKeys.parentStack, inObject: self)
}
set {
@@ -52,7 +51,7 @@ internal extension NSManagedObjectContext {
return
}
cs_setAssociatedWeakObject(
setAssociatedWeakObject(
newValue,
forKey: &PropertyKeys.parentStack,
inObject: self
@@ -60,8 +59,7 @@ internal extension NSManagedObjectContext {
}
}
@nonobjc
internal static func rootSavingContextForCoordinator(coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
internal class func rootSavingContextForCoordinator(coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .PrivateQueueConcurrencyType)
context.persistentStoreCoordinator = coordinator
@@ -69,32 +67,10 @@ internal extension NSManagedObjectContext {
context.undoManager = nil
context.setupForCoreStoreWithContextName("com.corestore.rootcontext")
#if os(iOS) || os(OSX)
context.observerForDidImportUbiquitousContentChangesNotification = NotificationObserver(
notificationName: NSPersistentStoreDidImportUbiquitousContentChangesNotification,
object: coordinator,
closure: { [weak context] (note) -> Void in
context?.performBlock { () -> Void in
let updatedObjectIDs = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObjectID>) ?? []
for objectID in updatedObjectIDs {
context?.objectWithID(objectID).willAccessValueForKey(nil)
}
context?.mergeChangesFromContextDidSaveNotification(note)
}
}
)
#endif
return context
}
@nonobjc
internal static func mainContextForRootContext(rootContext: NSManagedObjectContext) -> NSManagedObjectContext {
internal class func mainContextForRootContext(rootContext: NSManagedObjectContext) -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: .MainQueueConcurrencyType)
context.parentContext = rootContext
@@ -106,31 +82,18 @@ internal extension NSManagedObjectContext {
object: rootContext,
closure: { [weak context] (note) -> Void in
guard let rootContext = note.object as? NSManagedObjectContext,
let context = context else {
return
}
let mergeChanges = { () -> Void in
context?.performBlock { () -> Void in
let updatedObjects = (note.userInfo?[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? []
for object in updatedObjects {
context.objectWithID(object.objectID).willAccessValueForKey(nil)
context?.objectWithID(object.objectID).willAccessValueForKey(nil)
}
context.mergeChangesFromContextDidSaveNotification(note)
}
if rootContext.isSavingSynchronously == true {
context.performBlockAndWait(mergeChanges)
}
else {
context.performBlock(mergeChanges)
context?.mergeChangesFromContextDidSaveNotification(note)
}
}
)
return context
}
@@ -141,46 +104,24 @@ internal extension NSManagedObjectContext {
static var parentStack: Void?
static var observerForDidSaveNotification: Void?
static var observerForDidImportUbiquitousContentChangesNotification: Void?
}
@nonobjc
private var observerForDidSaveNotification: NotificationObserver? {
get {
return cs_getAssociatedObjectForKey(
return getAssociatedObjectForKey(
&PropertyKeys.observerForDidSaveNotification,
inObject: self
)
}
set {
cs_setAssociatedRetainedObject(
setAssociatedRetainedObject(
newValue,
forKey: &PropertyKeys.observerForDidSaveNotification,
inObject: self
)
}
}
@nonobjc
private var observerForDidImportUbiquitousContentChangesNotification: NotificationObserver? {
get {
return cs_getAssociatedObjectForKey(
&PropertyKeys.observerForDidImportUbiquitousContentChangesNotification,
inObject: self
)
}
set {
cs_setAssociatedRetainedObject(
newValue,
forKey: &PropertyKeys.observerForDidImportUbiquitousContentChangesNotification,
inObject: self
)
}
}
}

View File

@@ -2,7 +2,7 @@
// NSManagedObjectContext+Transaction.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -36,19 +36,18 @@ internal extension NSManagedObjectContext {
// MARK: Internal
@nonobjc
internal weak var parentTransaction: BaseDataTransaction? {
get {
return cs_getAssociatedObjectForKey(
return getAssociatedObjectForKey(
&PropertyKeys.parentTransaction,
inObject: self
)
}
set {
cs_setAssociatedWeakObject(
setAssociatedWeakObject(
newValue,
forKey: &PropertyKeys.parentTransaction,
inObject: self
@@ -56,28 +55,6 @@ internal extension NSManagedObjectContext {
}
}
@nonobjc
internal var isSavingSynchronously: Bool? {
get {
let value: NSNumber? = cs_getAssociatedObjectForKey(
&PropertyKeys.isSavingSynchronously,
inObject: self
)
return value?.boolValue
}
set {
cs_setAssociatedWeakObject(
newValue.flatMap { NSNumber(bool: $0) },
forKey: &PropertyKeys.isSavingSynchronously,
inObject: self
)
}
}
@nonobjc
internal func isRunningInAllowedQueue() -> Bool {
guard let parentTransaction = self.parentTransaction else {
@@ -87,7 +64,6 @@ internal extension NSManagedObjectContext {
return parentTransaction.isRunningInAllowedQueue()
}
@nonobjc
internal func temporaryContextInTransactionWithConcurrencyType(concurrencyType: NSManagedObjectContextConcurrencyType) -> NSManagedObjectContext {
let context = NSManagedObjectContext(concurrencyType: concurrencyType)
@@ -100,12 +76,11 @@ internal extension NSManagedObjectContext {
return context
}
@nonobjc
internal func saveSynchronously() -> SaveResult {
var result = SaveResult(hasChanges: false)
self.performBlockAndWait {
self.performBlockAndWait { [unowned self] () -> Void in
guard self.hasChanges else {
@@ -114,16 +89,14 @@ internal extension NSManagedObjectContext {
do {
self.isSavingSynchronously = true
try self.save()
self.isSavingSynchronously = nil
}
catch {
let saveError = CoreStoreError(error)
CoreStore.log(
let saveError = error as NSError
CoreStore.handleError(
saveError,
"Failed to save \(cs_typeName(NSManagedObjectContext))."
"Failed to save \(typeName(NSManagedObjectContext))."
)
result = SaveResult(saveError)
return
@@ -149,10 +122,9 @@ internal extension NSManagedObjectContext {
return result
}
@nonobjc
internal func saveAsynchronouslyWithCompletion(completion: ((result: SaveResult) -> Void) = { _ in }) {
self.performBlock {
self.performBlock { () -> Void in
guard self.hasChanges else {
@@ -165,16 +137,14 @@ internal extension NSManagedObjectContext {
do {
self.isSavingSynchronously = false
try self.save()
self.isSavingSynchronously = nil
}
catch {
let saveError = CoreStoreError(error)
CoreStore.log(
let saveError = error as NSError
CoreStore.handleError(
saveError,
"Failed to save \(cs_typeName(NSManagedObjectContext))."
"Failed to save \(typeName(NSManagedObjectContext))."
)
GCDQueue.Main.async {
@@ -197,16 +167,15 @@ internal extension NSManagedObjectContext {
}
}
@nonobjc
internal func refreshAndMergeAllObjects() {
internal func refreshAllObjectsAsFaults() {
if #available(iOS 8.3, OSX 10.11, *) {
if #available(iOS 8.3, *) {
self.refreshAllObjects()
}
else {
self.registeredObjects.forEach { self.refreshObject($0, mergeChanges: true) }
self.registeredObjects.forEach { self.refreshObject($0, mergeChanges: false) }
}
}
@@ -216,6 +185,5 @@ internal extension NSManagedObjectContext {
private struct PropertyKeys {
static var parentTransaction: Void?
static var isSavingSynchronously: Void?
}
}

View File

@@ -2,7 +2,7 @@
// NSManagedObjectModel+Setup.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -33,12 +33,11 @@ internal extension NSManagedObjectModel {
// MARK: Internal
@nonobjc
internal static func fromBundle(bundle: NSBundle, modelName: String, modelVersionHints: Set<String> = []) -> NSManagedObjectModel {
@nonobjc internal class func fromBundle(bundle: NSBundle, modelName: String, modelVersionHints: Set<String> = []) -> NSManagedObjectModel {
guard let modelFilePath = bundle.pathForResource(modelName, ofType: "momd") else {
CoreStore.abort("Could not find \"\(modelName).momd\" from the bundle. \(bundle)")
fatalError("Could not find \"\(modelName).momd\" from the bundle. \(bundle)")
}
let modelFileURL = NSURL(fileURLWithPath: modelFilePath)
@@ -47,7 +46,7 @@ internal extension NSManagedObjectModel {
guard let versionInfo = NSDictionary(contentsOfURL: versionInfoPlistURL),
let versionHashes = versionInfo["NSManagedObjectModel_VersionHashes"] as? [String: AnyObject] else {
CoreStore.abort("Could not load \(cs_typeName(NSManagedObjectModel)) metadata from path \"\(versionInfoPlistURL)\".")
fatalError("Could not load \(typeName(NSManagedObjectModel)) metadata from path \"\(versionInfoPlistURL)\".")
}
let modelVersions = Set(versionHashes.keys)
@@ -77,7 +76,7 @@ internal extension NSManagedObjectModel {
}
else {
CoreStore.abort("No model files were found in URL \"\(modelFileURL)\".")
fatalError("No model files were found in URL \"\(modelFileURL)\".")
}
var modelVersionFileURL: NSURL?
@@ -106,15 +105,14 @@ internal extension NSManagedObjectModel {
return rootModel
}
CoreStore.abort("Could not create an \(cs_typeName(NSManagedObjectModel)) from the model at URL \"\(modelFileURL)\".")
fatalError("Could not create an \(typeName(NSManagedObjectModel)) from the model at URL \"\(modelFileURL)\".")
}
@nonobjc
internal private(set) var currentModelVersion: String? {
@nonobjc private(set) internal var currentModelVersion: String? {
get {
let value: NSString? = cs_getAssociatedObjectForKey(
let value: NSString? = getAssociatedObjectForKey(
&PropertyKeys.currentModelVersion,
inObject: self
)
@@ -122,7 +120,7 @@ internal extension NSManagedObjectModel {
}
set {
cs_setAssociatedCopiedObject(
setAssociatedCopiedObject(
newValue == nil ? nil : (newValue! as NSString),
forKey: &PropertyKeys.currentModelVersion,
inObject: self
@@ -130,12 +128,11 @@ internal extension NSManagedObjectModel {
}
}
@nonobjc
internal private(set) var modelVersions: Set<String>? {
@nonobjc private(set) internal var modelVersions: Set<String>? {
get {
let value: NSSet? = cs_getAssociatedObjectForKey(
let value: NSSet? = getAssociatedObjectForKey(
&PropertyKeys.modelVersions,
inObject: self
)
@@ -143,7 +140,7 @@ internal extension NSManagedObjectModel {
}
set {
cs_setAssociatedCopiedObject(
setAssociatedCopiedObject(
newValue == nil ? nil : (newValue! as NSSet),
forKey: &PropertyKeys.modelVersions,
inObject: self
@@ -151,31 +148,26 @@ internal extension NSManagedObjectModel {
}
}
@nonobjc
internal func entityNameForClass(entityClass: AnyClass) -> String {
@nonobjc internal func entityNameForClass(entityClass: AnyClass) -> String {
return self.entityNameMapping[NSStringFromClass(entityClass)]!
}
@nonobjc
internal func entityTypesMapping() -> [String: NSManagedObject.Type] {
var mapping = [String: NSManagedObject.Type]()
self.entityNameMapping.forEach { (className, entityName) in
@nonobjc internal func entityTypesMapping() -> [String: NSManagedObject.Type] {
return self.entityNameMapping.reduce([:]) { (var mapping, pair) in
mapping[entityName] = (NSClassFromString(className)! as! NSManagedObject.Type)
mapping[pair.1] = (NSClassFromString(pair.0)! as! NSManagedObject.Type)
return mapping
}
return mapping
}
@nonobjc
internal func mergedModels() -> [NSManagedObjectModel] {
@nonobjc internal func mergedModels() -> [NSManagedObjectModel] {
return self.modelVersions?.map { self[$0] }.flatMap { $0 == nil ? [] : [$0!] } ?? [self]
}
@nonobjc
internal subscript(modelVersion: String) -> NSManagedObjectModel? {
@nonobjc internal subscript(modelVersion: String) -> NSManagedObjectModel? {
if modelVersion == self.currentModelVersion {
@@ -201,8 +193,7 @@ internal extension NSManagedObjectModel {
return model
}
@nonobjc
internal subscript(metadata: [String: AnyObject]) -> NSManagedObjectModel? {
@nonobjc internal subscript(metadata: [String: AnyObject]) -> NSManagedObjectModel? {
guard let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : NSData] else {
@@ -221,7 +212,6 @@ internal extension NSManagedObjectModel {
// MARK: Private
@nonobjc
private var modelFileURL: NSURL? {
get {
@@ -230,12 +220,11 @@ internal extension NSManagedObjectModel {
}
}
@nonobjc
private var modelVersionFileURL: NSURL? {
get {
let value: NSURL? = cs_getAssociatedObjectForKey(
let value: NSURL? = getAssociatedObjectForKey(
&PropertyKeys.modelVersionFileURL,
inObject: self
)
@@ -243,7 +232,7 @@ internal extension NSManagedObjectModel {
}
set {
cs_setAssociatedCopiedObject(
setAssociatedCopiedObject(
newValue,
forKey: &PropertyKeys.modelVersionFileURL,
inObject: self
@@ -251,28 +240,26 @@ internal extension NSManagedObjectModel {
}
}
@nonobjc
private var entityNameMapping: [String: String] {
get {
if let mapping: NSDictionary = cs_getAssociatedObjectForKey(&PropertyKeys.entityNameMapping, inObject: self) {
if let mapping: NSDictionary = getAssociatedObjectForKey(&PropertyKeys.entityNameMapping, inObject: self) {
return mapping as! [String: String]
}
var mapping = [String: String]()
self.entities.forEach {
let mapping = self.entities.reduce([String: String]()) {
(var mapping, entityDescription) -> [String: String] in
guard let entityName = $0.name else {
if let entityName = entityDescription.name {
return
let className = entityDescription.managedObjectClassName
mapping[className] = entityName
}
let className = $0.managedObjectClassName
mapping[className] = entityName
return mapping
}
cs_setAssociatedCopiedObject(
setAssociatedCopiedObject(
mapping as NSDictionary,
forKey: &PropertyKeys.entityNameMapping,
inObject: self

View File

@@ -2,7 +2,7 @@
// NotificationObserver.swift
// CoreStore
//
// Copyright © 2014 John Rommel Estropia
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -32,20 +32,28 @@ internal final class NotificationObserver {
// MARK: Public
let notificationName: String
let object: AnyObject?
let observer: NSObjectProtocol
init(notificationName: String, object: AnyObject?, queue: NSOperationQueue? = nil, closure: (note: NSNotification) -> Void) {
init(notificationName: String, object: AnyObject?, closure: (note: NSNotification) -> Void) {
self.notificationName = notificationName
self.object = object
self.observer = NSNotificationCenter.defaultCenter().addObserverForName(
notificationName,
object: object,
queue: queue,
queue: nil,
usingBlock: closure
)
}
deinit {
NSNotificationCenter.defaultCenter().removeObserver(self.observer)
NSNotificationCenter.defaultCenter().removeObserver(
self.observer,
name: self.notificationName,
object: self.object
)
}
}

View File

@@ -2,7 +2,7 @@
// WeakObject.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -23,6 +23,8 @@
// SOFTWARE.
//
import Foundation
// MARK: - WeakObject
@@ -35,5 +37,8 @@ internal final class WeakObject {
self.object = object
}
internal private(set) weak var object: AnyObject?
// MARK: Private
private(set) weak var object: AnyObject?
}

View File

@@ -2,7 +2,7 @@
// CoreStore+Logging.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,6 +30,8 @@ import Foundation
public extension CoreStore {
// MARK: Public
/**
The `CoreStoreLogger` instance to be used. The default logger is an instance of a `DefaultLogger`.
*/
@@ -38,7 +40,7 @@ public extension CoreStore {
// MARK: Internal
internal static func log(level: LogLevel, message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
internal static func log(level: LogLevel, message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) {
self.logger.log(
level: level,
@@ -49,9 +51,9 @@ public extension CoreStore {
)
}
internal static func log(error: CoreStoreError, _ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
internal static func handleError(error: NSError, _ message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) {
self.logger.log(
self.logger.handleError(
error: error,
message: message,
fileName: fileName,
@@ -60,7 +62,7 @@ public extension CoreStore {
)
}
internal static func assert(@autoclosure condition: () -> Bool, _ message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
internal static func assert(@autoclosure condition: () -> Bool, _ message: String, fileName: StaticString = __FILE__, lineNumber: Int = __LINE__, functionName: StaticString = __FUNCTION__) {
self.logger.assert(
condition,
@@ -70,16 +72,4 @@ public extension CoreStore {
functionName: functionName
)
}
@noreturn
internal static func abort(message: String, fileName: StaticString = #file, lineNumber: Int = #line, functionName: StaticString = #function) {
self.logger.abort(
message,
fileName: fileName,
lineNumber: lineNumber,
functionName: functionName
)
Swift.fatalError(message, file: fileName, line: UInt(lineNumber))
}
}

View File

@@ -0,0 +1,106 @@
//
// CoreStoreLogger.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - LogLevel
/**
The `LogLevel` indicates the severity of a log message.
*/
public enum LogLevel {
case Trace
case Notice
case Warning
case Fatal
}
// MARK: - CoreStoreLogger
/**
Custom loggers should implement the `CoreStoreLogger` protocol and pass its instance to `CoreStore.logger`. Calls to `log(...)`, `handleError(...)`, and `assert(...)` are not tied to a specific queue/thread, so it is the implementer's job to handle thread-safety.
*/
public protocol CoreStoreLogger {
/**
Handles log messages sent by the `CoreStore` framework.
:level: the severity of the log message
:message: the log message
:fileName: the source file name
:lineNumber: the source line number
:functionName: the source function name
*/
func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
/**
Handles errors sent by the `CoreStore` framework.
:error: the error
:message: the error message
:fileName: the source file name
:lineNumber: the source line number
:functionName: the source function name
*/
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
/**
Handles assertions made throughout the `CoreStore` framework.
:condition: the assertion condition
:message: the assertion message
:fileName: the source file name
:lineNumber: the source line number
:functionName: the source function name
*/
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString)
}
// MARK: - Utilities
internal func typeName<T>(value: T) -> String {
return "'\(_stdlib_getDemangledTypeName(value))'"
}
internal func typeName<T>(value: T.Type) -> String {
return "'\(value)'"
}
internal func typeName(value: AnyClass) -> String {
return "'\(value)'"
}
internal func typeName(name: String?) -> String {
let typeName = name ?? "unknown"
return "<\(typeName)>"
}

View File

@@ -0,0 +1,87 @@
//
// DefaultLogger.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - DefaultLogger
/**
The `DefaultLogger` is a basic implementation of the `CoreStoreLogger` protocol.
- The `log(...)` method calls `print(...)` to print the level, source file name, line number, function name, and the log message.
- The `handleError(...)` method calls `print(...)` to print the source file name, line number, function name, and the error message.
- The `assert(...)` method calls `assert(...)` on the arguments.
*/
public final class DefaultLogger: CoreStoreLogger {
public init() { }
public func log(level level: LogLevel, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
#if DEBUG
let icon: String
let levelString: String
switch level {
case .Trace:
icon = "🔹"
levelString = "Trace"
case .Notice:
icon = "🔸"
levelString = "Notice"
case .Warning:
icon = "⚠️"
levelString = "Warning"
case .Fatal:
icon = ""
levelString = "Fatal"
}
Swift.print("\(icon) [CoreStore: \(levelString)] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
#endif
}
public func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
#if DEBUG
Swift.print("⚠️ [CoreStore: Error] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n \(error)\n")
#endif
}
public func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
#if DEBUG
if condition() {
return
}
Swift.print("❗ [CoreStore: Assertion Failure] \((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ \(message)\n")
Swift.fatalError()
#endif
}
}

View File

@@ -0,0 +1,152 @@
//
// CoreStore+Migration.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - CoreStore
public extension CoreStore {
/**
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
completion: completion
)
}
/**
Asynchronously adds to the `defaultStack` an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.addSQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
completion: completion
)
}
/**
Using the `defaultStack`, migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Using the `defaultStack`, migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public static func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.defaultStack.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Using the `defaultStack`, checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public static func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForSQLiteStore(
fileName: fileName,
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
/**
Using the `defaultStack`, checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
- parameter fileURL: the local file URL for the SQLite persistent store.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public static func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try self.defaultStack.requiredMigrationsForSQLiteStore(
fileURL: fileURL,
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
}

View File

@@ -0,0 +1,635 @@
//
// DataStack+Migration.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - DataStack
public extension DataStack {
/**
Asynchronously adds an in-memory store to the stack.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
*/
public func addInMemoryStore(configuration configuration: String? = nil, completion: (PersistentStoreResult) -> Void) {
self.coordinator.performBlock {
do {
let store = try self.coordinator.addPersistentStoreWithType(
NSInMemoryStoreType,
configuration: configuration,
URL: nil,
options: nil
)
self.updateMetadataForPersistentStore(store)
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
}
catch {
let storeError = error as NSError
CoreStore.handleError(
storeError,
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
)
GCDQueue.Main.async {
completion(PersistentStoreResult(storeError))
}
}
}
}
/**
Asynchronously adds to the stack an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func addSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
return try self.addSQLiteStore(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles,
resetStoreOnModelMismatch: resetStoreOnModelMismatch,
completion: completion
)
}
/**
Asynchronously adds to the stack an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to report failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result. This closure is NOT executed if an error is thrown, but will be executed with a `.Failure` result if an error occurs asynchronously.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = NSBundle.allBundles(), resetStoreOnModelMismatch: Bool = false, completion: (PersistentStoreResult) -> Void) throws -> NSProgress? {
CoreStore.assert(
fileURL.fileURL,
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
)
let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) {
guard store.type == NSSQLiteStoreType
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError(
error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
)
throw error
}
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
return nil
}
let fileManager = NSFileManager.defaultManager()
_ = try? fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
do {
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL,
options: self.optionsForSQLiteStore()
)
return self.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
metadata: metadata,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: { (result) -> Void in
if case .Failure(let error) = result {
if resetStoreOnModelMismatch && error.isCoreDataMigrationError {
fileManager.removeSQLiteStoreAtURL(fileURL)
do {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
}
catch {
completion(PersistentStoreResult(error as NSError))
}
return
}
completion(PersistentStoreResult(error))
return
}
do {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
completion(PersistentStoreResult(store))
}
catch {
completion(PersistentStoreResult(error as NSError))
}
}
)
}
catch let error as NSError
where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain {
let store = try self.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: false
)
GCDQueue.Main.async {
completion(PersistentStoreResult(store))
}
return nil
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
}
}
/**
Migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version WITHOUT adding the migrated store to the data stack.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func upgradeSQLiteStoreIfNeeded(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
return try self.upgradeSQLiteStoreIfNeeded(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
- returns: an `NSProgress` instance if a migration has started, or `nil` is no migrations are required
*/
public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) throws -> NSProgress? {
let metadata: [String: AnyObject]
do {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL,
options: self.optionsForSQLiteStore()
)
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
}
return self.upgradeSQLiteStoreIfNeeded(
fileURL: fileURL,
metadata: metadata,
configuration: configuration,
mappingModelBundles: mappingModelBundles,
completion: completion
)
}
/**
Checks for the required migrations needed for the store with the specified filename and configuration to be migrated to the `DataStack`'s managed object model version. This method throws an error if the store does not exist, if inspection of the store failed, or no mapping model was found/inferred.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: an array of `MigrationType`s indicating the chain of migrations required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public func requiredMigrationsForSQLiteStore(fileName fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
return try requiredMigrationsForSQLiteStore(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
mappingModelBundles: mappingModelBundles
)
}
/**
Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
- parameter fileURL: the local file URL for the SQLite persistent store.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
*/
@warn_unused_result
public func requiredMigrationsForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) throws -> [MigrationType] {
let metadata: [String : AnyObject]
do {
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
NSSQLiteStoreType,
URL: fileURL,
options: self.optionsForSQLiteStore()
)
}
catch {
CoreStore.handleError(
error as NSError,
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata."
)
throw error
}
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else {
let error = NSError(coreStoreErrorCode: .MappingModelNotFound)
CoreStore.handleError(
error,
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(self.modelVersion)\"."
)
throw error
}
return migrationSteps.map { $0.migrationType }
}
// MARK: Private
private func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL, metadata: [String: AnyObject], configuration: String?, mappingModelBundles: [NSBundle]?, completion: (MigrationResult) -> Void) -> NSProgress? {
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, mappingModelBundles: mappingModelBundles) else {
CoreStore.handleError(
NSError(coreStoreErrorCode: .MappingModelNotFound),
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(model)\"."
)
GCDQueue.Main.async {
completion(MigrationResult(.MappingModelNotFound))
}
return nil
}
let numberOfMigrations: Int64 = Int64(migrationSteps.count)
if numberOfMigrations == 0 {
GCDQueue.Main.async {
completion(MigrationResult([]))
return
}
return nil
}
let migrationTypes = migrationSteps.map { $0.migrationType }
var migrationResult: MigrationResult?
var operations = [NSOperation]()
var cancelled = false
let progress = NSProgress(parent: nil, userInfo: nil)
progress.totalUnitCount = numberOfMigrations
// todo nsprogress crashing sometimes
for (sourceModel, destinationModel, mappingModel, _) in migrationSteps {
progress.becomeCurrentWithPendingUnitCount(1)
let childProgress = NSProgress(parent: progress, userInfo: nil)
childProgress.totalUnitCount = 100
operations.append(
NSBlockOperation { [weak self] in
guard let strongSelf = self where !cancelled else {
return
}
autoreleasepool {
do {
try strongSelf.startMigrationForSQLiteStore(
fileURL: fileURL,
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
progress: childProgress
)
}
catch {
migrationResult = MigrationResult(error as NSError)
cancelled = true
}
}
GCDQueue.Main.async {
withExtendedLifetime(childProgress) { (_: NSProgress) -> Void in }
return
}
}
)
progress.resignCurrent()
}
let migrationOperation = NSBlockOperation()
migrationOperation.qualityOfService = .Utility
operations.forEach { migrationOperation.addDependency($0) }
migrationOperation.addExecutionBlock { () -> Void in
GCDQueue.Main.async {
progress.setProgressHandler(nil)
completion(migrationResult ?? MigrationResult(migrationTypes))
return
}
}
operations.append(migrationOperation)
self.migrationQueue.addOperations(operations, waitUntilFinished: false)
return progress
}
private func computeMigrationFromStoreMetadata(metadata: [String: AnyObject], configuration: String? = nil, mappingModelBundles: [NSBundle]? = nil) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? {
let model = self.model
if model.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) {
return []
}
guard let initialModel = model[metadata],
var currentVersion = initialModel.currentModelVersion else {
return nil
}
let migrationChain: MigrationChain = self.migrationChain.empty
? [currentVersion: model.currentModelVersion!]
: self.migrationChain
var migrationSteps = [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]()
while let nextVersion = migrationChain.nextVersionFrom(currentVersion),
let sourceModel = model[currentVersion],
let destinationModel = model[nextVersion] where sourceModel != model {
if let mappingModel = NSMappingModel(
fromBundles: mappingModelBundles,
forSourceModel: sourceModel,
destinationModel: destinationModel) {
migrationSteps.append(
(
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
migrationType: .Heavyweight(
sourceVersion: currentVersion,
destinationVersion: nextVersion
)
)
)
}
else {
do {
let mappingModel = try NSMappingModel.inferredMappingModelForSourceModel(
sourceModel,
destinationModel: destinationModel
)
migrationSteps.append(
(
sourceModel: sourceModel,
destinationModel: destinationModel,
mappingModel: mappingModel,
migrationType: .Lightweight(
sourceVersion: currentVersion,
destinationVersion: nextVersion
)
)
)
}
catch {
return nil
}
}
currentVersion = nextVersion
}
if migrationSteps.last?.destinationModel == model {
return migrationSteps
}
return nil
}
private func startMigrationForSQLiteStore(fileURL fileURL: NSURL, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, progress: NSProgress) throws {
autoreleasepool {
let journalUpdatingCoordinator = NSPersistentStoreCoordinator(managedObjectModel: sourceModel)
let store = try! journalUpdatingCoordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: nil,
URL: fileURL,
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
)
try! journalUpdatingCoordinator.removePersistentStore(store)
}
let migrationManager = MigrationManager(
sourceModel: sourceModel,
destinationModel: destinationModel,
progress: progress
)
let temporaryDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString)
let fileManager = NSFileManager.defaultManager()
try! fileManager.createDirectoryAtURL(
temporaryDirectoryURL,
withIntermediateDirectories: true,
attributes: nil
)
let temporaryFileURL = temporaryDirectoryURL.URLByAppendingPathComponent(fileURL.lastPathComponent!, isDirectory: false)
do {
try migrationManager.migrateStoreFromURL(
fileURL,
type: NSSQLiteStoreType,
options: nil,
withMappingModel: mappingModel,
toDestinationURL: temporaryFileURL,
destinationType: NSSQLiteStoreType,
destinationOptions: nil
)
}
catch {
do {
try fileManager.removeItemAtURL(temporaryDirectoryURL)
}
catch _ { }
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
CoreStore.handleError(
error as NSError,
"Failed to migrate from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
)
throw error
}
do {
try fileManager.replaceItemAtURL(
fileURL,
withItemAtURL: temporaryFileURL,
backupItemName: nil,
options: [],
resultingItemURL: nil
)
progress.completedUnitCount = progress.totalUnitCount
do {
try fileManager.removeItemAtPath(fileURL.path! + "-shm")
}
catch _ { }
}
catch {
do {
try fileManager.removeItemAtURL(temporaryDirectoryURL)
}
catch _ { }
let sourceVersion = migrationManager.sourceModel.currentModelVersion ?? "???"
let destinationVersion = migrationManager.destinationModel.currentModelVersion ?? "???"
CoreStore.handleError(
error as NSError,
"Failed to save store after migrating from version model \"\(sourceVersion)\" to version model \"\(destinationVersion)\"."
)
throw error
}
}
}

View File

@@ -2,7 +2,7 @@
// MigrationChain.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,42 +30,41 @@ import CoreData
// MARK: - MigrationChain
/**
A `MigrationChain` indicates the sequence of model versions to be used as the order for progressive migrations. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants.
Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to use the .xcdatamodel's current version as the final version, and to disable progressive migrations:
```
let dataStack = DataStack(migrationChain: nil)
```
This means that the mapping model will be computed from the store's version straight to the `DataStack`'s model version.
To support progressive migrations, specify the linear order of versions:
```
let dataStack = DataStack(migrationChain:
["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"])
```
or for more complex migration paths, a version tree that maps the key-values to the source-destination versions:
```
let dataStack = DataStack(migrationChain: [
"MyAppModel": "MyAppModelV3",
"MyAppModelV2": "MyAppModelV4",
"MyAppModelV3": "MyAppModelV4"
])
```
This allows for different migration paths depending on the starting version. The example above resolves to the following paths:
- MyAppModel-MyAppModelV3-MyAppModelV4
- MyAppModelV2-MyAppModelV4
- MyAppModelV3-MyAppModelV4
The `MigrationChain` is validated when passed to the `DataStack` and unless it is empty, will raise an assertion if any of the following conditions are met:
- a version appears twice in an array
- a version appears twice as a key in a dictionary literal
- a loop is found in any of the paths
*/
public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, DictionaryLiteralConvertible, ArrayLiteralConvertible, Equatable {
A `MigrationChain` indicates the sequence of model versions to be used as the order for incremental migration. This is typically passed to the `DataStack` initializer and will be applied to all stores added to the `DataStack` with `addSQLiteStore(...)` and its variants.
Initializing with empty values (either `nil`, `[]`, or `[:]`) instructs the `DataStack` to use the .xcdatamodel's current version as the final version, and to disable incremental migrations:
let dataStack = DataStack(migrationChain: nil)
This means that the mapping model will be computed from the store's version straight to the `DataStack`'s model version.
To support incremental migrations, specify the linear order of versions:
let dataStack = DataStack(migrationChain:
["MyAppModel", "MyAppModelV2", "MyAppModelV3", "MyAppModelV4"])
or for more complex migration paths, a version tree that maps the key-values to the source-destination versions:
let dataStack = DataStack(migrationChain: [
"MyAppModel": "MyAppModelV3",
"MyAppModelV2": "MyAppModelV4",
"MyAppModelV3": "MyAppModelV4"
])
This allows for different migration paths depending on the starting version. The example above resolves to the following paths:
- MyAppModel-MyAppModelV3-MyAppModelV4
- MyAppModelV2-MyAppModelV4
- MyAppModelV3-MyAppModelV4
The `MigrationChain` is validated when passed to the `DataStack` and unless it is empty, will raise an assertion if any of the following conditions are met:
- a version appears twice in an array
- a version appears twice as a key in a dictionary literal
- a loop is found in any of the paths
*/
public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, DictionaryLiteralConvertible, ArrayLiteralConvertible {
/**
Initializes the `MigrationChain` with empty values, which instructs the `DataStack` to use the .xcdatamodel's current version as the final version, and to disable progressive migrations.
*/
public init() {
// MARK: NilLiteralConvertible
public init(nilLiteral: ()) {
self.versionTree = [:]
self.rootVersions = []
@@ -73,10 +72,10 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D
self.valid = true
}
/**
Initializes the `MigrationChain` with a single model version, which instructs the `DataStack` to use the the specified version as the final version, and to disable progressive migrations.
*/
public init(_ value: String) {
// MARK: StringLiteralConvertible
public init(stringLiteral value: String) {
self.versionTree = [:]
self.rootVersions = [value]
@@ -84,54 +83,50 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D
self.valid = true
}
/**
Initializes the `MigrationChain` with a linear order of versions, which becomes the order of the `DataStack`'s progressive migrations.
*/
public init<T: CollectionType where T.Generator.Element == String, T.Index: BidirectionalIndexType>(_ elements: T) {
// MARK: ExtendedGraphemeClusterLiteralConvertible
public init(extendedGraphemeClusterLiteral value: String) {
CoreStore.assert(Set(elements).count == Array(elements).count, "\(cs_typeName(MigrationChain))'s migration chain could not be created due to duplicate version strings.")
self.versionTree = [:]
self.rootVersions = [value]
self.leafVersions = [value]
self.valid = true
}
// MARK: UnicodeScalarLiteralConvertible
public init(unicodeScalarLiteral value: String) {
self.versionTree = [:]
self.rootVersions = [value]
self.leafVersions = [value]
self.valid = true
}
// MARK: DictionaryLiteralConvertible
public init(dictionaryLiteral elements: (String, String)...) {
var lastVersion: String?
var versionTree = [String: String]()
var valid = true
for version in elements {
let versionTree = elements.reduce([String: String]()) { (var versionTree, tuple: (String, String)) -> [String: String] in
if let lastVersion = lastVersion,
let _ = versionTree.updateValue(version, forKey: lastVersion) {
if let _ = versionTree.updateValue(tuple.1, forKey: tuple.0) {
CoreStore.assert(false, "\(typeName(MigrationChain))'s migration chain could not be created due to ambiguous version paths.")
valid = false
}
lastVersion = version
}
self.versionTree = versionTree
self.rootVersions = Set([elements.first].flatMap { $0 == nil ? [] : [$0!] })
self.leafVersions = Set([elements.last].flatMap { $0 == nil ? [] : [$0!] })
self.valid = valid
}
/**
Initializes the `MigrationChain` with a version tree, which becomes the order of the `DataStack`'s progressive migrations.
*/
public init(_ elements: [(String, String)]) {
var valid = true
var versionTree = [String: String]()
elements.forEach { (sourceVersion, destinationVersion) in
guard let _ = versionTree.updateValue(destinationVersion, forKey: sourceVersion) else {
return
}
CoreStore.assert(false, "\(cs_typeName(MigrationChain))'s migration chain could not be created due to ambiguous version paths.")
valid = false
return versionTree
}
let leafVersions = Set(
elements
.filter { versionTree[$1] == nil }
.map { $1 }
elements.filter { (tuple: (String, String)) -> Bool in
return versionTree[tuple.1] == nil
}.map { $1 }
)
let isVersionAmbiguous = { (start: String) -> Bool in
@@ -142,7 +137,7 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D
if checklist.contains(nextVersion) {
CoreStore.assert(false, "\(cs_typeName(MigrationChain))'s migration chain could not be created due to looping version paths.")
CoreStore.assert(false, "\(typeName(MigrationChain))'s migration chain could not be created due to looping version paths.")
return true
}
@@ -159,60 +154,30 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D
self.valid = valid && Set(versionTree.keys).union(versionTree.values).filter { isVersionAmbiguous($0) }.count <= 0
}
/**
Initializes the `MigrationChain` with a version tree, which becomes the order of the `DataStack`'s progressive migrations.
*/
public init(_ dictionary: [String: String]) {
self.init(dictionary.map { $0 })
}
// MARK: NilLiteralConvertible
public init(nilLiteral: ()) {
self.init()
}
// MARK: StringLiteralConvertible
public init(stringLiteral value: String) {
self.init(value)
}
// MARK: ExtendedGraphemeClusterLiteralConvertible
public init(extendedGraphemeClusterLiteral value: String) {
self.init(value)
}
// MARK: UnicodeScalarLiteralConvertible
public init(unicodeScalarLiteral value: String) {
self.init(value)
}
// MARK: DictionaryLiteralConvertible
public init(dictionaryLiteral elements: (String, String)...) {
self.init(elements)
}
// MARK: ArrayLiteralConvertible
public init(arrayLiteral elements: String...) {
self.init(elements)
CoreStore.assert(Set(elements).count == elements.count, "\(typeName(MigrationChain))'s migration chain could not be created due to duplicate version strings.")
var lastVersion: String?
var versionTree = [String: String]()
var valid = true
for version in elements {
if let lastVersion = lastVersion,
let _ = versionTree.updateValue(version, forKey: lastVersion) {
valid = false
}
lastVersion = version
}
self.versionTree = versionTree
self.rootVersions = Set([elements.first].flatMap { $0 == nil ? [] : [$0!] })
self.leafVersions = Set([elements.last].flatMap { $0 == nil ? [] : [$0!] })
self.valid = valid
}
@@ -250,14 +215,29 @@ public struct MigrationChain: NilLiteralConvertible, StringLiteralConvertible, D
}
// MARK: - MigrationChain: Equatable
// MARK: - MigrationChain: CustomDebugStringConvertible
@warn_unused_result
public func == (lhs: MigrationChain, rhs: MigrationChain) -> Bool {
extension MigrationChain: CustomDebugStringConvertible {
return lhs.versionTree == rhs.versionTree
&& lhs.rootVersions == rhs.rootVersions
&& lhs.leafVersions == rhs.leafVersions
&& lhs.valid == rhs.valid
public var debugDescription: String {
guard self.valid else {
return "<invalid migration chain>"
}
var paths = [String]()
for var version in self.rootVersions {
var steps = [version]
while let nextVersion = self.nextVersionFrom(version) {
steps.append(nextVersion)
version = nextVersion
}
paths.append(steps.joinWithSeparator(""))
}
return "[" + paths.joinWithSeparator("], [") + "]"
}
}

View File

@@ -0,0 +1,110 @@
//
// MigrationResult.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - MigrationResult
/**
The `MigrationResult` indicates the result of a migration.
The `MigrationResult` can be treated as a boolean:
CoreStore.upgradeSQLiteStoreIfNeeded { transaction in
// ...
let result = transaction.commit()
if result {
// succeeded
}
else {
// failed
}
}
or as an `enum`, where the resulting associated object can also be inspected:
CoreStore.beginAsynchronous { transaction in
// ...
let result = transaction.commit()
switch result {
case .Success(let hasChanges):
// hasChanges indicates if there were changes or not
case .Failure(let error):
// error is the NSError instance for the failure
}
}
```
*/
public enum MigrationResult {
// MARK: Public
/**
`MigrationResult.Success` indicates either the migration succeeded, or there were no migrations needed. The associated value is an array of `MigrationType`s reflecting the migration steps completed.
*/
case Success([MigrationType])
/**
`SaveResult.Failure` indicates that the migration failed. The associated object for this value is the related `NSError` instance.
*/
case Failure(NSError)
// MARK: Internal
internal init(_ migrationTypes: [MigrationType]) {
self = .Success(migrationTypes)
}
internal init(_ error: NSError) {
self = .Failure(error)
}
internal init(_ errorCode: CoreStoreErrorCode) {
self.init(errorCode, userInfo: nil)
}
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
}
}
// MARK: - MigrationResult: BooleanType
extension MigrationResult: BooleanType {
public var boolValue: Bool {
switch self {
case .Success: return true
case .Failure: return false
}
}
}

View File

@@ -2,7 +2,7 @@
// MigrationType.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -29,28 +29,30 @@ import Foundation
// MARK: - MigrationType
/**
The `MigrationType` specifies the type of migration required for a store.
*/
public enum MigrationType: BooleanType, Hashable {
The `MigrationType` specifies the type of migration required for a store.
*/
public enum MigrationType: BooleanType {
// MARK: Public
/**
Indicates that the persistent store matches the latest model version and no migration is needed
*/
Indicates that the persistent store matches the latest model version and no migration is needed
*/
case None(version: String)
/**
Indicates that the persistent store does not match the latest model version but Core Data can infer the mapping model, so a lightweight migration is needed
*/
Indicates that the persistent store does not match the latest model version but Core Data can infer the mapping model, so a lightweight migration is needed
*/
case Lightweight(sourceVersion: String, destinationVersion: String)
/**
Indicates that the persistent store does not match the latest model version and Core Data could not infer a mapping model, so a custom migration is needed
*/
Indicates that the persistent store does not match the latest model version and Core Data could not infer a mapping model, so a custom migration is needed
*/
case Heavyweight(sourceVersion: String, destinationVersion: String)
/**
Returns the source model version for the migration type. If no migration is required, `sourceVersion` will be equal to the `destinationVersion`.
*/
Returns the source model version for the migration type. If no migration is required, `sourceVersion` will be equal to the `destinationVersion`.
*/
public var sourceVersion: String {
switch self {
@@ -67,8 +69,8 @@ public enum MigrationType: BooleanType, Hashable {
}
/**
Returns the destination model version for the migration type. If no migration is required, `destinationVersion` will be equal to the `sourceVersion`.
*/
Returns the destination model version for the migration type. If no migration is required, `destinationVersion` will be equal to the `sourceVersion`.
*/
public var destinationVersion: String {
switch self {
@@ -85,8 +87,8 @@ public enum MigrationType: BooleanType, Hashable {
}
/**
Returns `true` if the `MigrationType` is a lightweight migration. Used as syntactic sugar.
*/
Returns `true` if the `MigrationType` is a lightweight migration. Used as syntactic sugar.
*/
public var isLightweightMigration: Bool {
if case .Lightweight = self {
@@ -97,8 +99,8 @@ public enum MigrationType: BooleanType, Hashable {
}
/**
Returns `true` if the `MigrationType` is a heavyweight migration. Used as syntactic sugar.
*/
Returns `true` if the `MigrationType` is a heavyweight migration. Used as syntactic sugar.
*/
public var isHeavyweightMigration: Bool {
if case .Heavyweight = self {
@@ -120,45 +122,4 @@ public enum MigrationType: BooleanType, Hashable {
case .Heavyweight: return true
}
}
// MARK: Hashable
public var hashValue: Int {
let preHash = self.boolValue.hashValue ^ self.isHeavyweightMigration.hashValue
switch self {
case .None(let version):
return preHash ^ version.hashValue
case .Lightweight(let sourceVersion, let destinationVersion):
return preHash ^ sourceVersion.hashValue ^ destinationVersion.hashValue
case .Heavyweight(let sourceVersion, let destinationVersion):
return preHash ^ sourceVersion.hashValue ^ destinationVersion.hashValue
}
}
}
// MARK: - MigrationType: Equatable
@warn_unused_result
public func == (lhs: MigrationType, rhs: MigrationType) -> Bool {
switch (lhs, rhs) {
case (.None(let version1), .None(let version2)):
return version1 == version2
case (.Lightweight(let source1, let destination1), .Lightweight(let source2, let destination2)):
return source1 == source2 && destination1 == destination2
case (.Heavyweight(let source1, let destination1), .Heavyweight(let source2, let destination2)):
return source1 == source2 && destination1 == destination2
default:
return false
}
}

View File

@@ -0,0 +1,99 @@
//
// NSError+CoreStore.swift
// CoreStore
//
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
/**
The `NSError` error domain for `CoreStore`.
*/
public let CoreStoreErrorDomain = "com.corestore.error"
/**
The `NSError` error codes for `CoreStoreErrorDomain`.
*/
public enum CoreStoreErrorCode: Int {
/**
A failure occured because of an unknown error.
*/
case UnknownError
/**
The `NSPersistentStore` could note be initialized because another store existed at the specified `NSURL`.
*/
case DifferentPersistentStoreExistsAtURL
/**
The `NSPersistentStore` specified could not be found.
*/
case PersistentStoreNotFound
/**
An `NSMappingModel` could not be found for a specific source and destination model versions.
*/
case MappingModelNotFound
}
// MARK: - NSError+CoreStore
public extension NSError {
/**
If the error's domain is equal to `CoreStoreErrorDomain`, returns the associated `CoreStoreErrorCode`. For other domains, returns `nil`.
*/
public var coreStoreErrorCode: CoreStoreErrorCode? {
return (self.domain == CoreStoreErrorDomain
? CoreStoreErrorCode(rawValue: self.code)
: nil)
}
// MARK: Internal
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode) {
self.init(coreStoreErrorCode: coreStoreErrorCode, userInfo: nil)
}
internal convenience init(coreStoreErrorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(
domain: CoreStoreErrorDomain,
code: coreStoreErrorCode.rawValue,
userInfo: userInfo)
}
internal var isCoreDataMigrationError: Bool {
let code = self.code
return (code == NSPersistentStoreIncompatibleVersionHashError
|| code == NSMigrationMissingSourceModelError
|| code == NSMigrationError)
&& self.domain == NSCocoaErrorDomain
}
}

View File

@@ -2,7 +2,7 @@
// CoreStore+Observing.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -27,18 +27,19 @@ import Foundation
import CoreData
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - CoreStore
@available(OSX, unavailable)
public extension CoreStore {
// MARK: Public
/**
Using the `defaultStack`, creates an `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
Using the `defaultStack`, creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
@warn_unused_result
public static func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
@@ -46,29 +47,29 @@ public extension CoreStore {
}
/**
Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public static func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: FetchClause...) -> ListMonitor<T> {
return self.defaultStack.monitorList(from, fetchClauses)
return self.defaultStack.monitorList(from, queryClauses)
}
/**
Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
Using the `defaultStack`, creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public static func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
public static func monitorList<T: NSManagedObject>(from: From<T>, _ queryClauses: [FetchClause]) -> ListMonitor<T> {
return self.defaultStack.monitorList(from, fetchClauses)
return self.defaultStack.monitorList(from, queryClauses)
}
/**
@@ -96,13 +97,13 @@ public extension CoreStore {
}
/**
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public static func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
@@ -110,13 +111,13 @@ public extension CoreStore {
}
/**
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
Using the `defaultStack`, creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public static func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
@@ -149,5 +150,3 @@ public extension CoreStore {
self.defaultStack.monitorSectionedList(createAsynchronously: createAsynchronously, from, sectionBy, fetchClauses)
}
}
#endif

View File

@@ -1,8 +1,8 @@
//
// UnsafeDataTransaction+Observing.swift
// DataStack+Observing.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,33 +30,39 @@ import CoreData
#endif
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - DataStack
// MARK: - UnsafeDataTransaction
public extension UnsafeDataTransaction {
@available(OSX, unavailable)
public extension DataStack {
// MARK: Public
/**
Creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
Creates a `ObjectMonitor` for the specified `NSManagedObject`. Multiple `ObjectObserver`s may then register themselves to be notified when changes are made to the `NSManagedObject`.
- parameter object: the `NSManagedObject` to observe changes from
- returns: a `ObjectMonitor` that monitors changes to `object`
*/
@warn_unused_result
public func monitorObject<T: NSManagedObject>(object: T) -> ObjectMonitor<T> {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to observe objects from \(typeName(self)) outside the main thread."
)
return ObjectMonitor(
unsafeTransaction: self,
dataStack: self,
object: object
)
}
/**
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
@@ -65,28 +71,29 @@ public extension UnsafeDataTransaction {
}
/**
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
Creates a `ListMonitor` for a list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public func monitorList<T: NSManagedObject>(from: From<T>, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to observe objects from \(typeName(self)) outside the main thread."
)
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
return ListMonitor(
unsafeTransaction: self,
dataStack: self,
from: from,
sectionBy: nil,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
}
fetchClauses: fetchClauses
)
}
@@ -111,61 +118,63 @@ public extension UnsafeDataTransaction {
*/
public func monitorList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ fetchClauses: [FetchClause]) {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to observe objects from \(typeName(self)) outside the main thread."
)
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
_ = ListMonitor(
unsafeTransaction: self,
dataStack: self,
from: from,
sectionBy: nil,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
},
fetchClauses: fetchClauses,
createAsynchronously: createAsynchronously
)
}
/**
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: FetchClause...) -> ListMonitor<T> {
return self.monitorSectionedList(from, sectionBy, fetchClauses)
}
/**
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
Creates a `ListMonitor` for a sectioned list of `NSManagedObject`s that satisfy the specified fetch clauses. Multiple `ListObserver`s may then register themselves to be notified when changes are made to the list.
- parameter from: a `From` clause indicating the entity type
- parameter sectionBy: a `SectionBy` clause indicating the keyPath for the attribute to use when sorting the list into sections.
- parameter fetchClauses: a series of `FetchClause` instances for fetching the object list. Accepts `Where`, `OrderBy`, and `Tweak` clauses.
- returns: a `ListMonitor` instance that monitors changes to the list
*/
@warn_unused_result
public func monitorSectionedList<T: NSManagedObject>(from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) -> ListMonitor<T> {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to observe objects from \(typeName(self)) outside the main thread."
)
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
return ListMonitor(
unsafeTransaction: self,
dataStack: self,
from: from,
sectionBy: sectionBy,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
}
fetchClauses: fetchClauses
)
}
@@ -192,22 +201,21 @@ public extension UnsafeDataTransaction {
*/
public func monitorSectionedList<T: NSManagedObject>(createAsynchronously createAsynchronously: (ListMonitor<T>) -> Void, _ from: From<T>, _ sectionBy: SectionBy, _ fetchClauses: [FetchClause]) {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to observe objects from \(typeName(self)) outside the main thread."
)
CoreStore.assert(
fetchClauses.filter { $0 is OrderBy }.count > 0,
"A ListMonitor requires an OrderBy clause."
)
_ = ListMonitor(
unsafeTransaction: self,
dataStack: self,
from: from,
sectionBy: sectionBy,
applyFetchClauses: { fetchRequest in
fetchClauses.forEach { $0.applyToFetchRequest(fetchRequest) }
},
fetchClauses: fetchClauses,
createAsynchronously: createAsynchronously
)
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,235 @@
//
// ListObserver.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - ListObserver
/**
Implement the `ListObserver` protocol to observe changes to a list of `NSManagedObject`s. `ListObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method:
let monitor = CoreStore.monitorList(
From(MyPersonEntity),
OrderBy(.Ascending("lastName"))
)
monitor.addObserver(self)
*/
@available(OSX, unavailable)
public protocol ListObserver: class {
/**
The `NSManagedObject` type for the observed list
*/
typealias ListEntityType: NSManagedObject
/**
Handles processing just before a change to the observed list occurs
- parameter monitor: the `ListMonitor` monitoring the list being observed
*/
func listMonitorWillChange(monitor: ListMonitor<ListEntityType>)
/**
Handles processing right after a change to the observed list occurs
- parameter monitor: the `ListMonitor` monitoring the object being observed
*/
func listMonitorDidChange(monitor: ListMonitor<ListEntityType>)
/**
This method is broadcast from within the `ListMonitor`'s `refetch(...)` method to let observers prepare for the internal `NSFetchedResultsController`'s pending change to its predicate, sort descriptors, etc. Note that the actual refetch will happen after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
- parameter monitor: the `ListMonitor` monitoring the object being observed
*/
func listMonitorWillRefetch(monitor: ListMonitor<ListEntityType>)
/**
After the `ListMonitor`'s `refetch(...)` method is called, this method is broadcast after the `NSFetchedResultsController`'s last `controllerDidChangeContent(_:)` notification completes.
- parameter monitor: the `ListMonitor` monitoring the object being observed
*/
func listMonitorDidRefetch(monitor: ListMonitor<ListEntityType>)
}
// MARK: - ListObserver (Default Implementations)
@available(OSX, unavailable)
public extension ListObserver {
/**
The default implementation does nothing.
*/
func listMonitorWillChange(monitor: ListMonitor<ListEntityType>) { }
/**
The default implementation does nothing.
*/
func listMonitorDidChange(monitor: ListMonitor<ListEntityType>) { }
/**
The default implementation does nothing.
*/
func listMonitorWillRefetch(monitor: ListMonitor<ListEntityType>) { }
/**
The default implementation does nothing.
*/
func listMonitorDidRefetch(monitor: ListMonitor<ListEntityType>) { }
}
// MARK: - ListObjectObserver
/**
Implement the `ListObjectObserver` protocol to observe detailed changes to a list's object. `ListObjectObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method:
let monitor = CoreStore.monitorList(
From(MyPersonEntity),
OrderBy(.Ascending("lastName"))
)
monitor.addObserver(self)
*/
@available(OSX, unavailable)
public protocol ListObjectObserver: ListObserver {
/**
Notifies that an object was inserted to the specified `NSIndexPath` in the list
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter object: the entity type for the inserted object
- parameter indexPath: the new `NSIndexPath` for the inserted object
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: NSIndexPath)
/**
Notifies that an object was deleted from the specified `NSIndexPath` in the list
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter object: the entity type for the deleted object
- parameter indexPath: the `NSIndexPath` for the deleted object
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: NSIndexPath)
/**
Notifies that an object at the specified `NSIndexPath` was updated
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter object: the entity type for the updated object
- parameter indexPath: the `NSIndexPath` for the updated object
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: NSIndexPath)
/**
Notifies that an object's index changed
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter object: the entity type for the moved object
- parameter fromIndexPath: the previous `NSIndexPath` for the moved object
- parameter toIndexPath: the new `NSIndexPath` for the moved object
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath)
}
// MARK: - ListObjectObserver (Default Implementations)
@available(OSX, unavailable)
public extension ListObjectObserver {
/**
The default implementation does nothing.
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertObject object: ListEntityType, toIndexPath indexPath: NSIndexPath) { }
/**
The default implementation does nothing.
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteObject object: ListEntityType, fromIndexPath indexPath: NSIndexPath) { }
/**
The default implementation does nothing.
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didUpdateObject object: ListEntityType, atIndexPath indexPath: NSIndexPath) { }
/**
The default implementation does nothing.
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didMoveObject object: ListEntityType, fromIndexPath: NSIndexPath, toIndexPath: NSIndexPath) { }
}
// MARK: - ListSectionObserver
/**
Implement the `ListSectionObserver` protocol to observe changes to a list's section info. `ListSectionObserver`s may register themselves to a `ListMonitor`'s `addObserver(_:)` method:
let monitor = CoreStore.monitorSectionedList(
From(MyPersonEntity),
SectionBy("age") { "Age \($0)" },
OrderBy(.Ascending("lastName"))
)
monitor.addObserver(self)
*/
@available(OSX, unavailable)
public protocol ListSectionObserver: ListObjectObserver {
/**
Notifies that a section was inserted at the specified index
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the inserted section
- parameter sectionIndex: the new section index for the new section
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int)
/**
Notifies that a section was inserted at the specified index
- parameter monitor: the `ListMonitor` monitoring the list being observed
- parameter sectionInfo: the `NSFetchedResultsSectionInfo` for the deleted section
- parameter sectionIndex: the previous section index for the deleted section
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int)
}
// MARK: - ListSectionObserver (Default Implementations)
@available(OSX, unavailable)
public extension ListSectionObserver {
/**
The default implementation does nothing.
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didInsertSection sectionInfo: NSFetchedResultsSectionInfo, toSectionIndex sectionIndex: Int) { }
/**
The default implementation does nothing.
*/
func listMonitor(monitor: ListMonitor<ListEntityType>, didDeleteSection sectionInfo: NSFetchedResultsSectionInfo, fromSectionIndex sectionIndex: Int) { }
}

View File

@@ -2,7 +2,7 @@
// ObjectMonitor.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,108 +30,59 @@ import CoreData
#endif
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - ObjectMonitor
/**
The `ObjectMonitor` monitors changes to a single `NSManagedObject` instance. Observers that implement the `ObjectObserver` protocol may then register themselves to the `ObjectMonitor`'s `addObserver(_:)` method:
```
let monitor = CoreStore.monitorObject(object)
monitor.addObserver(self)
```
The created `ObjectMonitor` instance needs to be held on (retained) for as long as the object needs to be observed.
Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
*/
The `ObjectMonitor` monitors changes to a single `NSManagedObject` instance. Observers that implement the `ObjectObserver` protocol may then register themselves to the `ObjectMonitor`'s `addObserver(_:)` method:
let monitor = CoreStore.monitorObject(object)
monitor.addObserver(self)
The created `ObjectMonitor` instance needs to be held on (retained) for as long as the object needs to be observed.
Observers registered via `addObserver(_:)` are not retained. `ObjectMonitor` only keeps a `weak` reference to all observers, thus keeping itself free from retain-cycles.
*/
@available(OSX, unavailable)
public final class ObjectMonitor<T: NSManagedObject> {
// MARK: Public
/**
Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted.
*/
Returns the `NSManagedObject` instance being observed, or `nil` if the object was already deleted.
*/
public var object: T? {
return self.fetchedResultsController.fetchedObjects?.first as? T
}
/**
Returns `true` if the `NSManagedObject` instance being observed still exists, or `false` if the object was already deleted.
*/
Returns `true` if the `NSManagedObject` instance being observed still exists, or `false` if the object was already deleted.
*/
public var isObjectDeleted: Bool {
return self.object?.managedObjectContext == nil
}
/**
Registers an `ObjectObserver` to be notified when changes to the receiver's `object` are made.
To prevent retain-cycles, `ObjectMonitor` only keeps `weak` references to its observers.
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
Calling `addObserver(_:)` multiple times on the same observer is safe, as `ObjectMonitor` unregisters previous notifications to the observer before re-registering them.
- parameter observer: an `ObjectObserver` to send change notifications to
*/
Registers an `ObjectObserver` to be notified when changes to the receiver's `object` are made.
To prevent retain-cycles, `ObjectMonitor` only keeps `weak` references to its observers.
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
Calling `addObserver(_:)` multiple times on the same observer is safe, as `ObjectMonitor` unregisters previous notifications to the observer before re-registering them.
- parameter observer: an `ObjectObserver` to send change notifications to
*/
public func addObserver<U: ObjectObserver where U.ObjectEntityType == T>(observer: U) {
self.unregisterObserver(observer)
self.registerObserver(
observer,
willChangeObject: { (observer, monitor, object) in
observer.objectMonitor(monitor, willUpdateObject: object)
},
didDeleteObject: { (observer, monitor, object) in
observer.objectMonitor(monitor, didDeleteObject: object)
},
didUpdateObject: { (observer, monitor, object, changedPersistentKeys) in
observer.objectMonitor(monitor, didUpdateObject: object, changedPersistentKeys: changedPersistentKeys)
}
)
}
/**
Unregisters an `ObjectObserver` from receiving notifications for changes to the receiver's `object`.
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
- parameter observer: an `ObjectObserver` to unregister notifications to
*/
public func removeObserver<U: ObjectObserver where U.ObjectEntityType == T>(observer: U) {
self.unregisterObserver(observer)
}
// MARK: Hashable
public var hashValue: Int {
return ObjectIdentifier(self).hashValue
}
// MARK: Internal
internal convenience init(dataStack: DataStack, object: T) {
self.init(context: dataStack.mainContext, object: object)
}
internal convenience init(unsafeTransaction: UnsafeDataTransaction, object: T) {
self.init(context: unsafeTransaction.context, object: object)
}
internal func registerObserver<U: AnyObject>(observer: U, willChangeObject: (observer: U, monitor: ObjectMonitor<T>, object: T) -> Void, didDeleteObject: (observer: U, monitor: ObjectMonitor<T>, object: T) -> Void, didUpdateObject: (observer: U, monitor: ObjectMonitor<T>, object: T, changedPersistentKeys: Set<String>) -> Void) {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to add an observer of type \(cs_typeName(observer)) outside the main thread."
"Attempted to add an observer of type \(typeName(observer)) outside the main thread."
)
self.removeObserver(observer)
self.registerChangeNotification(
&self.willChangeObjectKey,
name: ObjectMonitorWillChangeObjectNotification,
@@ -142,7 +93,7 @@ public final class ObjectMonitor<T: NSManagedObject> {
return
}
willChangeObject(observer: observer, monitor: monitor, object: object)
observer.objectMonitor(monitor, willUpdateObject: object)
}
)
self.registerObjectNotification(
@@ -155,7 +106,7 @@ public final class ObjectMonitor<T: NSManagedObject> {
return
}
didDeleteObject(observer: observer, monitor: monitor, object: object)
observer.objectMonitor(monitor, didDeleteObject: object)
}
)
self.registerObjectNotification(
@@ -164,12 +115,12 @@ public final class ObjectMonitor<T: NSManagedObject> {
toObserver: observer,
callback: { [weak self, weak observer] (monitor, object) -> Void in
guard let `self` = self, let observer = observer else {
guard let strongSelf = self, let observer = observer else {
return
}
let previousCommitedAttributes = self.lastCommittedAttributes
let previousCommitedAttributes = strongSelf.lastCommittedAttributes
let currentCommitedAttributes = object.committedValuesForKeys(nil) as! [String: NSObject]
var changedKeys = Set<String>()
@@ -181,33 +132,66 @@ public final class ObjectMonitor<T: NSManagedObject> {
}
}
self.lastCommittedAttributes = currentCommitedAttributes
didUpdateObject(
observer: observer,
monitor: monitor,
object: object,
strongSelf.lastCommittedAttributes = currentCommitedAttributes
observer.objectMonitor(
monitor,
didUpdateObject: object,
changedPersistentKeys: changedKeys
)
}
)
}
internal func unregisterObserver(observer: AnyObject) {
/**
Unregisters an `ObjectObserver` from receiving notifications for changes to the receiver's `object`.
For thread safety, this method needs to be called from the main thread. An assertion failure will occur (on debug builds only) if called from any thread other than the main thread.
- parameter observer: an `ObjectObserver` to unregister notifications to
*/
public func removeObserver<U: ObjectObserver where U.ObjectEntityType == T>(observer: U) {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to remove an observer of type \(cs_typeName(observer)) outside the main thread."
"Attempted to remove an observer of type \(typeName(observer)) outside the main thread."
)
let nilValue: AnyObject? = nil
cs_setAssociatedRetainedObject(nilValue, forKey: &self.willChangeObjectKey, inObject: observer)
cs_setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteObjectKey, inObject: observer)
cs_setAssociatedRetainedObject(nilValue, forKey: &self.didUpdateObjectKey, inObject: observer)
setAssociatedRetainedObject(nilValue, forKey: &self.willChangeObjectKey, inObject: observer)
setAssociatedRetainedObject(nilValue, forKey: &self.didDeleteObjectKey, inObject: observer)
setAssociatedRetainedObject(nilValue, forKey: &self.didUpdateObjectKey, inObject: observer)
}
internal func upcast() -> ObjectMonitor<NSManagedObject> {
// MARK: Internal
internal init(dataStack: DataStack, object: T) {
return unsafeBitCast(self, ObjectMonitor<NSManagedObject>.self)
let fetchRequest = NSFetchRequest()
fetchRequest.entity = object.entity
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectResultType
fetchRequest.sortDescriptors = []
fetchRequest.includesPendingChanges = false
fetchRequest.shouldRefreshRefetchedObjects = true
let fetchedResultsController = NSFetchedResultsController(
dataStack: dataStack,
fetchRequest: fetchRequest,
fetchClauses: [Where("SELF", isEqualTo: object.objectID)]
)
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
self.fetchedResultsController = fetchedResultsController
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
self.parentStack = dataStack
fetchedResultsControllerDelegate.handler = self
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
try! fetchedResultsController.performFetch()
self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [String: NSObject]) ?? [:]
}
deinit {
@@ -218,56 +202,28 @@ public final class ObjectMonitor<T: NSManagedObject> {
// MARK: Private
private let fetchedResultsController: CoreStoreFetchedResultsController
private let fetchedResultsController: NSFetchedResultsController
private let fetchedResultsControllerDelegate: FetchedResultsControllerDelegate
private var lastCommittedAttributes = [String: NSObject]()
private weak var parentStack: DataStack?
private var willChangeObjectKey: Void?
private var didDeleteObjectKey: Void?
private var didUpdateObjectKey: Void?
private init(context: NSManagedObjectContext, object: T) {
let fetchRequest = CoreStoreFetchRequest()
fetchRequest.entity = object.entity
fetchRequest.fetchLimit = 0
fetchRequest.resultType = .ManagedObjectResultType
fetchRequest.sortDescriptors = []
fetchRequest.includesPendingChanges = false
fetchRequest.shouldRefreshRefetchedObjects = true
let objectID = object.objectID
let fetchedResultsController = CoreStoreFetchedResultsController(
context: context,
fetchRequest: fetchRequest,
applyFetchClauses: Where("SELF", isEqualTo: objectID).applyToFetchRequest
)
let fetchedResultsControllerDelegate = FetchedResultsControllerDelegate()
self.fetchedResultsController = fetchedResultsController
self.fetchedResultsControllerDelegate = fetchedResultsControllerDelegate
fetchedResultsControllerDelegate.handler = self
fetchedResultsControllerDelegate.fetchedResultsController = fetchedResultsController
try! fetchedResultsController.performFetchFromSpecifiedStores()
self.lastCommittedAttributes = (self.object?.committedValuesForKeys(nil) as? [String: NSObject]) ?? [:]
}
private func registerChangeNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor<T>) -> Void) {
cs_setAssociatedRetainedObject(
setAssociatedRetainedObject(
NotificationObserver(
notificationName: name,
object: self,
closure: { [weak self] (note) -> Void in
guard let `self` = self else {
guard let strongSelf = self else {
return
}
callback(monitor: self)
callback(monitor: strongSelf)
}
),
forKey: notificationKey,
@@ -277,19 +233,19 @@ public final class ObjectMonitor<T: NSManagedObject> {
private func registerObjectNotification(notificationKey: UnsafePointer<Void>, name: String, toObserver observer: AnyObject, callback: (monitor: ObjectMonitor<T>, object: T) -> Void) {
cs_setAssociatedRetainedObject(
setAssociatedRetainedObject(
NotificationObserver(
notificationName: name,
object: self,
closure: { [weak self] (note) -> Void in
guard let `self` = self,
guard let strongSelf = self,
let userInfo = note.userInfo,
let object = userInfo[UserInfoKeyObject] as? T else {
return
}
callback(monitor: self, object: object)
callback(monitor: strongSelf, object: object)
}
),
forKey: notificationKey,
@@ -301,21 +257,19 @@ public final class ObjectMonitor<T: NSManagedObject> {
// MARK: - ObjectMonitor: Equatable
public func == <T: NSManagedObject>(lhs: ObjectMonitor<T>, rhs: ObjectMonitor<T>) -> Bool {
return lhs === rhs
}
public func ~= <T: NSManagedObject>(lhs: ObjectMonitor<T>, rhs: ObjectMonitor<T>) -> Bool {
@available(OSX, unavailable)
public func ==<T: NSManagedObject>(lhs: ObjectMonitor<T>, rhs: ObjectMonitor<T>) -> Bool {
return lhs === rhs
}
@available(OSX, unavailable)
extension ObjectMonitor: Equatable { }
// MARK: - ObjectMonitor: FetchedResultsControllerHandler
@available(OSX, unavailable)
extension ObjectMonitor: FetchedResultsControllerHandler {
// MARK: FetchedResultsControllerHandler
@@ -368,4 +322,3 @@ private let ObjectMonitorDidUpdateObjectNotification = "ObjectMonitorDidUpdateOb
private let UserInfoKeyObject = "UserInfoKeyObject"
#endif

View File

@@ -2,7 +2,7 @@
// ObjectObserver.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -27,69 +27,66 @@ import Foundation
import CoreData
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - ObjectObserver
/**
Implement the `ObjectObserver` protocol to observe changes to a single `NSManagedObject` instance. `ObjectObserver`s may register themselves to a `ObjectMonitor`'s `addObserver(_:)` method:
```
let monitor = CoreStore.monitorObject(object)
monitor.addObserver(self)
```
*/
Implement the `ObjectObserver` protocol to observe changes to a single `NSManagedObject` instance. `ObjectObserver`s may register themselves to a `ObjectMonitor`'s `addObserver(_:)` method:
let monitor = CoreStore.monitorObject(object)
monitor.addObserver(self)
*/
@available(OSX, unavailable)
public protocol ObjectObserver: class {
/**
The `NSManagedObject` type for the observed object
*/
associatedtype ObjectEntityType: NSManagedObject
The `NSManagedObject` type for the observed object
*/
typealias ObjectEntityType: NSManagedObject
/**
Handles processing just before a change to the observed `object` occurs
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
- parameter object: the `NSManagedObject` instance being observed
*/
Handles processing just before a change to the observed `object` occurs
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
- parameter object: the `NSManagedObject` instance being observed
*/
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType)
/**
Handles processing right after a change to the observed `object` occurs
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
- parameter object: the `NSManagedObject` instance being observed
- parameter changedPersistentKeys: a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported.
*/
Handles processing right after a change to the observed `object` occurs
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
- parameter object: the `NSManagedObject` instance being observed
- parameter changedPersistentKeys: a `Set` of key paths for the attributes that were changed. Note that `changedPersistentKeys` only contains keys for attributes/relationships present in the persistent store, thus transient properties will not be reported.
*/
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set<KeyPath>)
/**
Handles processing right after `object` is deleted
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
- parameter object: the `NSManagedObject` instance being observed
*/
Handles processing right after `object` is deleted
- parameter monitor: the `ObjectMonitor` monitoring the object being observed
- parameter object: the `NSManagedObject` instance being observed
*/
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, didDeleteObject object: ObjectEntityType)
}
// MARK: - ObjectObserver (Default Implementations)
@available(OSX, unavailable)
public extension ObjectObserver {
/**
The default implementation does nothing.
*/
The default implementation does nothing.
*/
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, willUpdateObject object: ObjectEntityType) { }
/**
The default implementation does nothing.
*/
The default implementation does nothing.
*/
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, didUpdateObject object: ObjectEntityType, changedPersistentKeys: Set<KeyPath>) { }
/**
The default implementation does nothing.
*/
The default implementation does nothing.
*/
func objectMonitor(monitor: ObjectMonitor<ObjectEntityType>, didDeleteObject object: ObjectEntityType) { }
}
#endif

View File

@@ -2,7 +2,7 @@
// SectionBy.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -27,39 +27,38 @@ import Foundation
import CoreData
#if os(iOS) || os(watchOS) || os(tvOS)
// MARK: - SectionBy
/**
The `SectionBy` clause indicates the key path to use to group the `ListMonitor` objects into sections. An optional closure can also be provided to transform the value into an appropriate section name:
```
let monitor = CoreStore.monitorSectionedList(
From(MyPersonEntity),
SectionBy("age") { "Age \($0)" },
OrderBy(.Ascending("lastName"))
)
```
*/
The `SectionBy` clause indicates the key path to use to group the `ListMonitor` objects into sections. An optional closure can also be provided to transform the value into an appropriate section name:
let monitor = CoreStore.monitorSectionedList(
From(MyPersonEntity),
SectionBy("age") { "Age \($0)" },
OrderBy(.Ascending("lastName"))
)
*/
@available(OSX, unavailable)
public struct SectionBy {
// MARK: Public
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections
- parameter sectionKeyPath: the key path to use to group the objects into sections
*/
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections
- parameter sectionKeyPath: the key path to use to group the objects into sections
*/
public init(_ sectionKeyPath: KeyPath) {
self.init(sectionKeyPath, { $0 })
}
/**
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
- Important: Some utilities (such as `ListMonitor`s) may keep `SectionBy`s in memory and may thus introduce retain cycles if reference captures are not handled properly.
- parameter sectionKeyPath: the key path to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
*/
Initializes a `SectionBy` clause with the key path to use to group `ListMonitor` objects into sections, and a closure to transform the value for the key path to an appropriate section name
- parameter sectionKeyPath: the key path to use to group the objects into sections
- parameter sectionIndexTransformer: a closure to transform the value for the key path to an appropriate section name
*/
public init(_ sectionKeyPath: KeyPath, _ sectionIndexTransformer: (sectionName: String?) -> String?) {
self.sectionKeyPath = sectionKeyPath
@@ -72,5 +71,3 @@ public struct SectionBy {
internal let sectionKeyPath: KeyPath
internal let sectionIndexTransformer: (sectionName: KeyPath?) -> String?
}
#endif

View File

@@ -2,7 +2,7 @@
// AsynchronousDataTransaction.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -25,6 +25,8 @@
import Foundation
import CoreData
import CoreSpotlight
import MobileCoreServices
#if USE_FRAMEWORKS
import GCDKit
#endif
@@ -33,32 +35,42 @@ import CoreData
// MARK: - AsynchronousDataTransaction
/**
The `AsynchronousDataTransaction` provides an interface for `NSManagedObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.beginAsynchronous(_:)`, or from `CoreStore.beginAsynchronous(_:)`.
*/
The `AsynchronousDataTransaction` provides an interface for `NSManagedObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.beginAsynchronous(_:)`, or from `CoreStore.beginAsynchronous(_:)`.
*/
public final class AsynchronousDataTransaction: BaseDataTransaction {
// MARK: Public
/**
Saves the transaction changes. This method should not be used after the `commit()` method was already called once.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
*/
Saves the transaction changes. This method should not be used after the `commit()` method was already called once.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
*/
public func commit(completion: (result: SaveResult) -> Void = { _ in }) {
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
"Attempted to commit a \(typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
"Attempted to commit a \(typeName(self)) more than once."
)
self.isCommitted = true
let commitCompletionForSearchableItems = self.context.commitCompletionForSearchableItems()
let group = GCDGroup()
group.enter()
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
self.result = result
if result {
commitCompletionForSearchableItems()
}
completion(result: result)
group.leave()
}
@@ -66,20 +78,20 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
}
/**
Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
Begins a child transaction synchronously where NSManagedObject creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
"Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue."
"Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to begin a child transaction from an already committed \(cs_typeName(self))."
"Attempted to begin a child transaction from an already committed \(typeName(self))."
)
return SynchronousDataTransaction(
@@ -92,103 +104,121 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
// MARK: BaseDataTransaction
/**
Creates a new `NSManagedObject` with the specified entity type.
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
- returns: a new `NSManagedObject` instance of the specified entity type.
*/
Creates a new `NSManagedObject` with the specified entity type.
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
- returns: a new `NSManagedObject` instance of the specified entity type.
*/
public override func create<T: NSManagedObject>(into: Into<T>) -> T {
CoreStore.assert(
!self.isCommitted,
"Attempted to create an entity of type \(cs_typeName(T)) from an already committed \(cs_typeName(self))."
"Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))."
)
return super.create(into)
}
/**
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
- parameter object: the `NSManagedObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
- parameter object: the `NSManagedObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
@warn_unused_result
public override func edit<T: NSManagedObject>(object: T?) -> T? {
CoreStore.assert(
!self.isCommitted,
"Attempted to update an entity of type \(cs_typeName(object)) from an already committed \(cs_typeName(self))."
"Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self))."
)
return super.edit(object)
}
/**
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
- parameter into: an `Into` clause specifying the entity type
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
- parameter into: an `Into` clause specifying the entity type
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
@warn_unused_result
public override func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
CoreStore.assert(
!self.isCommitted,
"Attempted to update an entity of type \(cs_typeName(T)) from an already committed \(cs_typeName(self))."
"Attempted to update an entity of type \(typeName(T)) from an already committed \(typeName(self))."
)
return super.edit(into, objectID)
}
/**
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
- parameter object: the `NSManagedObject` type to be deleted
*/
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
- parameter object: the `NSManagedObject` type to be deleted
*/
public override func delete(object: NSManagedObject?) {
CoreStore.assert(
!self.isCommitted,
"Attempted to delete an entity of type \(cs_typeName(object)) from an already committed \(cs_typeName(self))."
"Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self))."
)
super.delete(object)
}
/**
Deletes the specified `NSManagedObject`s.
- parameter object1: the `NSManagedObject` type to be deleted
- parameter object2: another `NSManagedObject` type to be deleted
- parameter objects: other `NSManagedObject`s type to be deleted
*/
Deletes the specified `NSManagedObject`s.
- parameter object1: the `NSManagedObject` type to be deleted
- parameter object2: another `NSManagedObject` type to be deleted
- parameter objects: other `NSManagedObject`s type to be deleted
*/
public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
CoreStore.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(cs_typeName(self))."
"Attempted to delete an entities from an already committed \(typeName(self))."
)
super.delete(([object1, object2] + objects).flatMap { $0 })
}
/**
Deletes the specified `NSManagedObject`s.
- parameter objects: the `NSManagedObject`s type to be deleted
*/
Deletes the specified `NSManagedObject`s.
- parameter objects: the `NSManagedObject`s type to be deleted
*/
public override func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
CoreStore.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(cs_typeName(self))."
"Attempted to delete an entities from an already committed \(typeName(self))."
)
super.delete(objects)
}
/**
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once.
*/
@available(*, deprecated=1.3.4, message="Resetting the context is inherently unsafe. This method will be removed in the near future. Use `beginUnsafe()` to create transactions with `undo` support.")
public func rollback() {
CoreStore.assert(
!self.isCommitted,
"Attempted to rollback an already committed \(typeName(self))."
)
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
"Attempted to rollback a \(typeName(self)) outside its designated queue."
)
self.context.reset()
}
// MARK: Internal
@@ -208,7 +238,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
CoreStore.log(
.Warning,
message: "The closure for the \(cs_typeName(self)) completed without being committed. All changes made within the transaction were discarded."
message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
@@ -224,7 +254,7 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
CoreStore.log(
.Warning,
message: "The closure for the \(cs_typeName(self)) completed without being committed. All changes made within the transaction were discarded."
message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
@@ -235,22 +265,4 @@ public final class AsynchronousDataTransaction: BaseDataTransaction {
// MARK: Private
private let closure: (transaction: AsynchronousDataTransaction) -> Void
// MARK: Deprecated
@available(*, deprecated=1.3.4, obsoleted=2.0.0, message="Resetting the context is inherently unsafe. This method will be removed in the near future. Use `beginUnsafe()` to create transactions with `undo` support.")
public func rollback() {
CoreStore.assert(
!self.isCommitted,
"Attempted to rollback an already committed \(cs_typeName(self))."
)
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
"Attempted to rollback a \(cs_typeName(self)) outside its designated queue."
)
self.context.reset()
}
}

View File

@@ -0,0 +1,244 @@
//
// BaseDataTransaction.swift
// CoreStore
//
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - BaseDataTransaction
/**
The `BaseDataTransaction` is an abstract interface for `NSManagedObject` creates, updates, and deletes. All `BaseDataTransaction` subclasses manage a private `NSManagedObjectContext` which are direct children of the `NSPersistentStoreCoordinator`'s root `NSManagedObjectContext`. This means that all updates are saved first to the persistent store, and then propagated up to the read-only `NSManagedObjectContext`.
*/
public /*abstract*/ class BaseDataTransaction {
// MARK: Object management
/**
Indicates if the transaction has pending changes
*/
public var hasChanges: Bool {
return self.context.hasChanges
}
/**
Creates a new `NSManagedObject` with the specified entity type.
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
- returns: a new `NSManagedObject` instance of the specified entity type.
*/
public func create<T: NSManagedObject>(into: Into<T>) -> T {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to create an entity of type \(typeName(T)) outside its designated queue."
)
let context = self.context
let entityClass = (into.entityClass as! NSManagedObject.Type)
if into.inferStoreIfPossible {
switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: nil, inferStoreIfPossible: true) {
case (let persistentStore?, _):
let object = entityClass.createInContext(context) as! T
context.assignObject(object, toPersistentStore: persistentStore)
return object
case (.None, true):
fatalError("Attempted to create an entity of type \(typeName(entityClass)) with ambiguous destination persistent store, but the configuration name was not specified.")
default:
fatalError("Attempted to create an entity of type \(typeName(entityClass)), but a destination persistent store containing the entity type could not be found.")
}
}
else {
switch context.parentStack!.persistentStoreForEntityClass(entityClass, configuration: into.configuration, inferStoreIfPossible: false) {
case (let persistentStore?, _):
let object = entityClass.createInContext(context) as! T
context.assignObject(object, toPersistentStore: persistentStore)
return object
default:
if let configuration = into.configuration {
fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the configuration \"\(configuration)\", which it doesn't belong to.")
}
else {
fatalError("Attempted to create an entity of type \(typeName(entityClass)) into the default configuration, which it doesn't belong to.")
}
}
}
}
/**
Returns an editable proxy of a specified `NSManagedObject`.
- parameter object: the `NSManagedObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
@warn_unused_result
public func edit<T: NSManagedObject>(object: T?) -> T? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to update an entity of type \(typeName(object)) outside its designated queue."
)
guard let object = object else {
return nil
}
return self.context.fetchExisting(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`.
*/
@warn_unused_result
public func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to update an entity of type \(typeName(T)) outside its designated queue."
)
CoreStore.assert(
into.inferStoreIfPossible
|| (into.configuration ?? Into.defaultConfigurationName) == objectID.persistentStore?.configurationName,
"Attempted to update an entity of type \(typeName(T)) but the specified persistent store do not match the `NSManagedObjectID`."
)
return self.fetchExisting(objectID) as? T
}
/**
Deletes a specified `NSManagedObject`.
- parameter object: the `NSManagedObject` to be deleted
*/
public func delete(object: NSManagedObject?) {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete an entity outside its designated queue."
)
guard let object = object else {
return
}
self.context.fetchExisting(object)?.deleteFromContext()
}
/**
Deletes the specified `NSManagedObject`s.
- parameter object1: the `NSManagedObject` to be deleted
- parameter object2: another `NSManagedObject` to be deleted
- parameter objects: other `NSManagedObject`s to be deleted
*/
public func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
self.delete(([object1, object2] + objects).flatMap { $0 })
}
/**
Deletes the specified `NSManagedObject`s.
- parameter objects: the `NSManagedObject`s to be deleted
*/
public func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to delete entities outside their designated queue."
)
let context = self.context
objects.forEach { context.fetchExisting($0)?.deleteFromContext() }
}
/**
Refreshes all registered objects `NSManagedObject`s in the transaction.
*/
public func refreshAllObjectsAsFaults() {
CoreStore.assert(
self.isRunningInAllowedQueue(),
"Attempted to refresh entities outside their designated queue."
)
self.context.refreshAllObjectsAsFaults()
}
// MARK: Internal
internal let context: NSManagedObjectContext
internal let transactionQueue: GCDQueue
internal let childTransactionQueue: GCDQueue = .createSerial("com.corestore.datastack.childtransactionqueue")
internal let supportsUndo: Bool
internal let bypassesQueueing: Bool
internal var isCommitted = false
internal var result: SaveResult?
internal init(mainContext: NSManagedObjectContext, queue: GCDQueue, supportsUndo: Bool, bypassesQueueing: Bool) {
let context = mainContext.temporaryContextInTransactionWithConcurrencyType(
queue == .Main
? .MainQueueConcurrencyType
: .PrivateQueueConcurrencyType
)
self.transactionQueue = queue
self.context = context
self.supportsUndo = supportsUndo
self.bypassesQueueing = bypassesQueueing
context.parentTransaction = self
if !supportsUndo {
context.undoManager = nil
}
else if context.undoManager == nil {
context.undoManager = NSUndoManager()
}
}
internal func isRunningInAllowedQueue() -> Bool {
return self.bypassesQueueing || self.transactionQueue.isCurrentExecutionContext()
}
}

View File

@@ -2,7 +2,7 @@
// CoreStore+Transaction.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,11 +30,13 @@ import Foundation
public extension CoreStore {
// MARK: Public
/**
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
*/
Using the `defaultStack`, begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
*/
public static func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) {
self.defaultStack.beginAsynchronous(closure)
@@ -64,17 +66,14 @@ public extension CoreStore {
}
/**
Refreshes all registered objects `NSManagedObject`s in the `defaultStack`.
Refreshes all registered objects `NSManagedObject`s in the `DataStack`.
*/
public static func refreshAndMergeAllObjects() {
public static func refreshAllObjectsAsFaults() {
self.defaultStack.refreshAndMergeAllObjects()
self.defaultStack.refreshAllObjectsAsFaults()
}
// MARK: Deprecated
@available(*, deprecated=1.3.1, obsoleted=2.0.0, renamed="beginUnsafe")
@available(*, deprecated=1.3.1, renamed="beginUnsafe")
@warn_unused_result
public static func beginDetached() -> UnsafeDataTransaction {

View File

@@ -2,7 +2,7 @@
// DataStack+Transaction.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -34,11 +34,13 @@ import CoreData
public extension DataStack {
// MARK: Public
/**
Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
*/
Begins a transaction asynchronously where `NSManagedObject` creates, updates, and deletes can be made.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
*/
public func beginAsynchronous(closure: (transaction: AsynchronousDataTransaction) -> Void) {
AsynchronousDataTransaction(
@@ -83,20 +85,17 @@ public extension DataStack {
/**
Refreshes all registered objects `NSManagedObject`s in the `DataStack`.
*/
public func refreshAndMergeAllObjects() {
public func refreshAllObjectsAsFaults() {
CoreStore.assert(
NSThread.isMainThread(),
"Attempted to refresh entities outside their designated queue."
)
self.mainContext.refreshAndMergeAllObjects()
self.mainContext.refreshAllObjectsAsFaults()
}
// MARK: Deprecated
@available(*, deprecated=1.3.1, obsoleted=2.0.0, renamed="beginUnsafe")
@available(*, deprecated=1.3.1, renamed="beginUnsafe")
@warn_unused_result
public func beginDetached() -> UnsafeDataTransaction {

View File

@@ -0,0 +1,145 @@
//
// Into.swift
// CoreStore
//
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - Into
/**
A `Into` clause contains the destination entity and destination persistent store for a `create(...)` method. A common usage is to just indicate the entity:
let person = transaction.create(Into(MyPersonEntity))
For cases where multiple `NSPersistentStore`s contain the same entity, the destination configuration's name needs to be specified as well:
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
This helps the `NSManagedObjectContext` to determine which
*/
public struct Into<T: NSManagedObject> {
// MARK: Public
/**
Initializes an `Into` clause.
Sample Usage:
let person = transaction.create(Into<MyPersonEntity>())
*/
public init(){
self.configuration = nil
self.inferStoreIfPossible = true
self.entityClass = T.self
}
/**
Initializes an `Into` clause with the specified entity type.
Sample Usage:
let person = transaction.create(Into(MyPersonEntity))
- parameter entity: the `NSManagedObject` type to be created
*/
public init(_ entity: T.Type) {
self.configuration = nil
self.inferStoreIfPossible = true
self.entityClass = entity
}
/**
Initializes an `Into` clause with the specified entity class.
- parameter entityClass: the `NSManagedObject` class type to be created
*/
public init(_ entityClass: AnyClass) {
self.configuration = nil
self.inferStoreIfPossible = true
self.entityClass = entityClass
}
/**
Initializes an `Into` clause with the specified configuration.
Sample Usage:
let person = transaction.create(Into<MyPersonEntity>("Configuration1"))
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ configuration: String?) {
self.configuration = configuration
self.inferStoreIfPossible = false
self.entityClass = T.self
}
/**
Initializes an `Into` clause with the specified entity type and configuration.
Sample Usage:
let person = transaction.create(Into(MyPersonEntity.self, "Configuration1"))
- parameter entity: the `NSManagedObject` type to be created
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ entity: T.Type, _ configuration: String?) {
self.configuration = configuration
self.inferStoreIfPossible = false
self.entityClass = entity
}
/**
Initializes an `Into` clause with the specified entity class and configuration.
Sample Usage:
let person = transaction.create(Into(MyPersonEntity.self, "Configuration1"))
- parameter entityClass: the `NSManagedObject` class type to be created
- parameter configuration: the `NSPersistentStore` configuration name to associate the object to. This parameter is required if multiple configurations contain the created `NSManagedObject`'s entity type. Set to `nil` to use the default configuration.
*/
public init(_ entityClass: AnyClass, _ configuration: String?) {
self.configuration = configuration
self.inferStoreIfPossible = false
self.entityClass = entityClass
}
// MARK: Internal
internal static var defaultConfigurationName: String {
return "PF_DEFAULT_CONFIGURATION_NAME"
}
internal let entityClass: AnyClass
internal let configuration: String?
internal let inferStoreIfPossible: Bool
}

View File

@@ -2,7 +2,7 @@
// NSManagedObject+Transaction.swift
// CoreStore
//
// Copyright © 2016 John Rommel Estropia
// Copyright (c) 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
@@ -26,19 +26,19 @@
import Foundation
import CoreData
// MARK: - NSManagedObject
public extension NSManagedObject {
// MARK: Public
/**
Returns this object's parent `UnsafeDataTransaction` instance if it was created from one. Returns `nil` if the parent transaction is either an `AsynchronousDataTransaction` or a `SynchronousDataTransaction`, or if the object is not managed by CoreStore.
When using an `UnsafeDataTransaction` and passing around a temporary object, you can use this property to execute fetches and updates to the transaction without having to pass around both the object and the transaction instances.
- Important: The internal reference to the transaction is `weak`, and it is still the developer's responsibility to retain a strong reference to the `UnsafeDataTransaction`.
Note that the internal reference to the transaction is `weak`, and it is still the developer's responsibility to retain a strong reference to the `UnsafeDataTransaction`.
*/
@nonobjc
public var unsafeDataTransaction: UnsafeDataTransaction? {
return self.managedObjectContext?.parentTransaction as? UnsafeDataTransaction
@@ -47,8 +47,7 @@ public extension NSManagedObject {
// MARK: Internal
@nonobjc
internal class func createInContext(context: NSManagedObjectContext) -> Self {
internal dynamic class func createInContext(context: NSManagedObjectContext) -> Self {
return self.init(
entity: context.entityDescriptionForEntityType(self)!,
@@ -56,7 +55,6 @@ public extension NSManagedObject {
)
}
@nonobjc
internal func deleteFromContext() {
self.managedObjectContext?.deleteObject(self)

View File

@@ -0,0 +1,110 @@
//
// SaveResult.swift
// CoreStore
//
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
// MARK: - SaveResult
/**
The `SaveResult` indicates the result of a `commit(...)` for a transaction.
The `SaveResult` can be treated as a boolean:
CoreStore.beginAsynchronous { transaction in
// ...
let result = transaction.commit()
if result {
// succeeded
}
else {
// failed
}
}
or as an `enum`, where the resulting associated object can also be inspected:
CoreStore.beginAsynchronous { transaction in
// ...
let result = transaction.commit()
switch result {
case .Success(let hasChanges):
// hasChanges indicates if there were changes or not
case .Failure(let error):
// error is the NSError instance for the failure
}
}
```
*/
public enum SaveResult {
// MARK: Public
/**
`SaveResult.Success` indicates that the `commit()` for the transaction succeeded, either because the save succeeded or because there were no changes to save. The associated value `hasChanges` indicates if there were saved changes or not.
*/
case Success(hasChanges: Bool)
/**
`SaveResult.Failure` indicates that the `commit()` for the transaction failed. The associated object for this value is the related `NSError` instance.
*/
case Failure(NSError)
// MARK: Internal
internal init(hasChanges: Bool) {
self = .Success(hasChanges: hasChanges)
}
internal init(_ error: NSError) {
self = .Failure(error)
}
internal init(_ errorCode: CoreStoreErrorCode) {
self.init(errorCode, userInfo: nil)
}
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(NSError(coreStoreErrorCode: errorCode, userInfo: userInfo))
}
}
// MARK: - SaveResult: BooleanType
extension SaveResult: BooleanType {
public var boolValue: Bool {
switch self {
case .Success: return true
case .Failure: return false
}
}
}

View File

@@ -2,7 +2,7 @@
// SynchronousDataTransaction.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -33,48 +33,45 @@ import CoreData
// MARK: - SynchronousDataTransaction
/**
The `SynchronousDataTransaction` provides an interface for `NSManagedObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.beginSynchronous(_:)`, or from `CoreStore.beginSynchronous(_:)`.
*/
The `SynchronousDataTransaction` provides an interface for `NSManagedObject` creates, updates, and deletes. A transaction object should typically be only used from within a transaction block initiated from `DataStack.beginSynchronous(_:)`, or from `CoreStore.beginSynchronous(_:)`.
*/
public final class SynchronousDataTransaction: BaseDataTransaction {
// MARK: Public
/**
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` method was already called once.
- returns: a `SaveResult` containing the success or failure information
*/
public func commitAndWait() -> SaveResult {
Saves the transaction changes and waits for completion synchronously. This method should not be used after the `commit()` method was already called once.
*/
public func commit() {
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
"Attempted to commit a \(cs_typeName(self)) outside its designated queue."
"Attempted to commit a \(typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to commit a \(cs_typeName(self)) more than once."
"Attempted to commit a \(typeName(self)) more than once."
)
self.isCommitted = true
let result = self.context.saveSynchronously()
self.result = result
return result
self.result = self.context.saveSynchronously()
}
/**
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
Begins a child transaction synchronously where `NSManagedObject` creates, updates, and deletes can be made. This method should not be used after the `commit()` method was already called once.
- parameter closure: the block where creates, updates, and deletes can be made to the transaction. Transaction blocks are executed serially in a background queue, and all changes are made from a concurrent `NSManagedObjectContext`.
- returns: a `SaveResult` value indicating success or failure, or `nil` if the transaction was not comitted synchronously
*/
public func beginSynchronous(closure: (transaction: SynchronousDataTransaction) -> Void) -> SaveResult? {
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
"Attempted to begin a child transaction from a \(cs_typeName(self)) outside its designated queue."
"Attempted to begin a child transaction from a \(typeName(self)) outside its designated queue."
)
CoreStore.assert(
!self.isCommitted,
"Attempted to begin a child transaction from an already committed \(cs_typeName(self))."
"Attempted to begin a child transaction from an already committed \(typeName(self))."
)
return SynchronousDataTransaction(
@@ -87,103 +84,121 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
// MARK: BaseDataTransaction
/**
Creates a new `NSManagedObject` with the specified entity type.
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
- returns: a new `NSManagedObject` instance of the specified entity type.
*/
Creates a new `NSManagedObject` with the specified entity type.
- parameter into: the `Into` clause indicating the destination `NSManagedObject` entity type and the destination configuration
- returns: a new `NSManagedObject` instance of the specified entity type.
*/
public override func create<T: NSManagedObject>(into: Into<T>) -> T {
CoreStore.assert(
!self.isCommitted,
"Attempted to create an entity of type \(cs_typeName(T)) from an already committed \(cs_typeName(self))."
"Attempted to create an entity of type \(typeName(T)) from an already committed \(typeName(self))."
)
return super.create(into)
}
/**
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
- parameter object: the `NSManagedObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
Returns an editable proxy of a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
- parameter object: the `NSManagedObject` type to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
@warn_unused_result
public override func edit<T: NSManagedObject>(object: T?) -> T? {
CoreStore.assert(
!self.isCommitted,
"Attempted to update an entity of type \(cs_typeName(object)) from an already committed \(cs_typeName(self))."
"Attempted to update an entity of type \(typeName(object)) from an already committed \(typeName(self))."
)
return super.edit(object)
}
/**
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
- parameter into: an `Into` clause specifying the entity type
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
Returns an editable proxy of the object with the specified `NSManagedObjectID`. This method should not be used after the `commit()` method was already called once.
- parameter into: an `Into` clause specifying the entity type
- parameter objectID: the `NSManagedObjectID` for the object to be edited
- returns: an editable proxy for the specified `NSManagedObject`.
*/
@warn_unused_result
public override func edit<T: NSManagedObject>(into: Into<T>, _ objectID: NSManagedObjectID) -> T? {
CoreStore.assert(
!self.isCommitted,
"Attempted to update an entity of type \(cs_typeName(T)) from an already committed \(cs_typeName(self))."
"Attempted to update an entity of type \(typeName(T)) from an already committed \(typeName(self))."
)
return super.edit(into, objectID)
}
/**
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
- parameter object: the `NSManagedObject` type to be deleted
*/
Deletes a specified `NSManagedObject`. This method should not be used after the `commit()` method was already called once.
- parameter object: the `NSManagedObject` type to be deleted
*/
public override func delete(object: NSManagedObject?) {
CoreStore.assert(
!self.isCommitted,
"Attempted to delete an entity of type \(cs_typeName(object)) from an already committed \(cs_typeName(self))."
"Attempted to delete an entity of type \(typeName(object)) from an already committed \(typeName(self))."
)
super.delete(object)
}
/**
Deletes the specified `NSManagedObject`s.
- parameter object1: the `NSManagedObject` to be deleted
- parameter object2: another `NSManagedObject` to be deleted
- parameter objects: other `NSManagedObject`s to be deleted
*/
Deletes the specified `NSManagedObject`s.
- parameter object1: the `NSManagedObject` to be deleted
- parameter object2: another `NSManagedObject` to be deleted
- parameter objects: other `NSManagedObject`s to be deleted
*/
public override func delete(object1: NSManagedObject?, _ object2: NSManagedObject?, _ objects: NSManagedObject?...) {
CoreStore.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(cs_typeName(self))."
"Attempted to delete an entities from an already committed \(typeName(self))."
)
super.delete(([object1, object2] + objects).flatMap { $0 })
}
/**
Deletes the specified `NSManagedObject`s.
- parameter objects: the `NSManagedObject`s to be deleted
*/
Deletes the specified `NSManagedObject`s.
- parameter objects: the `NSManagedObject`s to be deleted
*/
public override func delete<S: SequenceType where S.Generator.Element: NSManagedObject>(objects: S) {
CoreStore.assert(
!self.isCommitted,
"Attempted to delete an entities from an already committed \(cs_typeName(self))."
"Attempted to delete an entities from an already committed \(typeName(self))."
)
super.delete(objects)
}
/**
Rolls back the transaction by resetting the `NSManagedObjectContext`. After calling this method, all `NSManagedObjects` fetched within the transaction will become invalid. This method should not be used after the `commit()` method was already called once.
*/
@available(*, deprecated=1.3.4, message="Resetting the context is inherently unsafe. This method will be removed in the near future. Use `beginUnsafe()` to create transactions with `undo` support.")
public func rollback() {
CoreStore.assert(
!self.isCommitted,
"Attempted to rollback an already committed \(typeName(self))."
)
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
"Attempted to rollback a \(typeName(self)) outside its designated queue."
)
self.context.reset()
}
// MARK: Internal
@@ -204,7 +219,7 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
CoreStore.log(
.Warning,
message: "The closure for the \(cs_typeName(self)) completed without being committed. All changes made within the transaction were discarded."
message: "The closure for the \(typeName(self)) completed without being committed. All changes made within the transaction were discarded."
)
}
}
@@ -215,28 +230,4 @@ public final class SynchronousDataTransaction: BaseDataTransaction {
// MARK: Private
private let closure: (transaction: SynchronousDataTransaction) -> Void
// MARK: Deprecated
@available(*, deprecated=1.3.4, obsoleted=2.0.0, message="Resetting the context is inherently unsafe. This method will be removed in the near future. Use `beginUnsafe()` to create transactions with `undo` support.")
public func rollback() {
CoreStore.assert(
!self.isCommitted,
"Attempted to rollback an already committed \(cs_typeName(self))."
)
CoreStore.assert(
self.transactionQueue.isCurrentExecutionContext(),
"Attempted to rollback a \(cs_typeName(self)) outside its designated queue."
)
self.context.reset()
}
@available(*, deprecated=1.5.2, obsoleted=2.0.0, renamed="commitAndWait")
public func commit() {
self.commitAndWait()
}
}

View File

@@ -2,7 +2,7 @@
// UnsafeDataTransaction.swift
// CoreStore
//
// Copyright © 2015 John Rommel Estropia
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
@@ -30,18 +30,24 @@ import CoreData
#endif
@available(*, deprecated=1.3.1, renamed="UnsafeDataTransaction")
public typealias DetachedDataTransaction = UnsafeDataTransaction
// MARK: - UnsafeDataTransaction
/**
The `UnsafeDataTransaction` provides an interface for non-contiguous `NSManagedObject` creates, updates, and deletes. This is useful for making temporary changes, such as partially filled forms. An unsafe transaction object should typically be only used from the main queue.
*/
The `UnsafeDataTransaction` provides an interface for non-contiguous `NSManagedObject` creates, updates, and deletes. This is useful for making temporary changes, such as partially filled forms. An unsafe transaction object should typically be only used from the main queue.
*/
public final class UnsafeDataTransaction: BaseDataTransaction {
// MARK: Public
/**
Saves the transaction changes asynchronously. For an `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
*/
Saves the transaction changes asynchronously. For a `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
- parameter completion: the block executed after the save completes. Success or failure is reported by the `SaveResult` argument of the block.
*/
public func commit(completion: (result: SaveResult) -> Void) {
self.context.saveAsynchronouslyWithCompletion { (result) -> Void in
@@ -51,18 +57,6 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
}
}
/**
Saves the transaction changes and waits for completion synchronously. For an `UnsafeDataTransaction`, multiple commits are allowed, although it is the developer's responsibility to ensure a reasonable leeway to prevent blocking the main thread.
- returns: a `SaveResult` containing the success or failure information
*/
public func commitAndWait() -> SaveResult {
let result = self.context.saveSynchronously()
self.result = result
return result
}
/**
Rolls back the transaction.
*/
@@ -70,7 +64,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
CoreStore.assert(
self.supportsUndo,
"Attempted to rollback a \(cs_typeName(self)) with Undo support disabled."
"Attempted to rollback a \(typeName(self)) with Undo support disabled."
)
self.context.rollback()
}
@@ -82,35 +76,11 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
CoreStore.assert(
self.supportsUndo,
"Attempted to undo a \(cs_typeName(self)) with Undo support disabled."
"Attempted to undo a \(typeName(self)) with Undo support disabled."
)
self.context.undo()
}
/**
Immediately flushes all pending changes to the transaction's observers. This is useful in conjunction with `ListMonitor`s and `ObjectMonitor`s created from `UnsafeDataTransaction`s used to manage temporary "scratch" data.
- Important: Note that unlike `commit()`, `flush()` does not propagate/save updates to the `DataStack` and the persistent store. However, the flushed changes will be seen by children transactions created further from the current transaction (i.e. through `transaction.beginUnsafe()`)
- throws: an error thrown from `closure`, or an error thrown by Core Data (usually validation errors or conflict errors)
*/
public func flush() {
self.context.processPendingChanges()
}
/**
Flushes all pending changes to the transaction's observers at the end of the `closure`'s execution. This is useful in conjunction with `ListMonitor`s and `ObjectMonitor`s created from `UnsafeDataTransaction`s used to manage temporary "scratch" data.
- Important: Note that unlike `commit()`, `flush()` does not propagate/save updates to the `DataStack` and the persistent store. However, the flushed changes will be seen by children transactions created further from the current transaction (i.e. through `transaction.beginUnsafe()`)
- parameter closure: the closure where changes can be made prior to the flush
- throws: an error thrown from `closure`, or an error thrown by Core Data (usually validation errors or conflict errors)
*/
public func flush(@noescape closure: () throws -> Void) rethrows {
try closure()
self.context.processPendingChanges()
}
/**
Redo's the last undone change to the transaction.
*/
@@ -118,7 +88,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
CoreStore.assert(
self.supportsUndo,
"Attempted to redo a \(cs_typeName(self)) with Undo support disabled."
"Attempted to redo a \(typeName(self)) with Undo support disabled."
)
self.context.redo()
}
@@ -127,7 +97,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
Begins a child transaction where `NSManagedObject` creates, updates, and deletes can be made. This is useful for making temporary changes, such as partially filled forms.
- 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: an `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
- returns: a `UnsafeDataTransaction` instance where creates, updates, and deletes can be made.
*/
@warn_unused_result
public func beginUnsafe(supportsUndo supportsUndo: Bool = false) -> UnsafeDataTransaction {
@@ -142,7 +112,7 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
/**
Returns the `NSManagedObjectContext` for this unsafe transaction. Use only for cases where external frameworks need an `NSManagedObjectContext` instance to work with.
- Important: It is the developer's responsibility to ensure the following:
Note that it is the developer's responsibility to ensure the following:
- that the `UnsafeDataTransaction` that owns this context should be strongly referenced and prevented from being deallocated during the context's lifetime
- that all saves will be done either through the `UnsafeDataTransaction`'s `commit(...)` method, or by calling `save()` manually on the context, its parent, and all other ancestor contexts if there are any.
*/
@@ -151,6 +121,13 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
return self.context
}
@available(*, deprecated=1.3.1, renamed="beginUnsafe")
@warn_unused_result
public func beginDetached() -> UnsafeDataTransaction {
return self.beginUnsafe()
}
// MARK: Internal
@@ -158,20 +135,4 @@ public final class UnsafeDataTransaction: BaseDataTransaction {
super.init(mainContext: mainContext, queue: queue, supportsUndo: supportsUndo, bypassesQueueing: true)
}
// MARK: Deprecated
@available(*, deprecated=1.3.1, obsoleted=2.0.0, renamed="beginUnsafe")
@warn_unused_result
public func beginDetached() -> UnsafeDataTransaction {
return self.beginUnsafe()
}
}
// MARK: Deprecated
@available(*, deprecated=1.3.1, obsoleted=2.0.0, renamed="UnsafeDataTransaction")
public typealias DetachedDataTransaction = UnsafeDataTransaction

View File

@@ -0,0 +1,105 @@
//
// CoreStore+Setup.swift
// CoreStore
//
// Copyright (c) 2015 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
// MARK: - CoreStore
public extension CoreStore {
/**
Returns the `defaultStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
*/
public static var modelVersion: String {
return self.defaultStack.modelVersion
}
/**
Returns the entity name-to-class type mapping from the `defaultStack`'s model.
*/
public static var entityTypesByName: [String: NSManagedObject.Type] {
return self.defaultStack.entityTypesByName
}
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass from `defaultStack`'s model.
*/
public static func entityDescriptionForType(type: NSManagedObject.Type) -> NSEntityDescription? {
return self.defaultStack.entityDescriptionForType(type)
}
/**
Adds an in-memory store to the `defaultStack`.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
- returns: the `NSPersistentStore` added to the stack.
*/
public static func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
return try self.defaultStack.addInMemoryStoreAndWait(configuration: configuration)
}
/**
Adds to the `defaultStack` an SQLite store from the given SQLite file name.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
- returns: the `NSPersistentStore` added to the stack.
*/
public static func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
return try self.defaultStack.addSQLiteStoreAndWait(
fileName: fileName,
configuration: configuration,
resetStoreOnModelMismatch: resetStoreOnModelMismatch
)
}
/**
Adds to the `defaultStack` an SQLite store from the given SQLite file URL.
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS).
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to nil.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- returns: the `NSPersistentStore` added to the stack.
*/
public static func addSQLiteStoreAndWait(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
return try self.defaultStack.addSQLiteStoreAndWait(
fileURL: fileURL,
configuration: configuration,
resetStoreOnModelMismatch: resetStoreOnModelMismatch
)
}
}

View File

@@ -0,0 +1,393 @@
//
// DataStack.swift
// CoreStore
//
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
#if USE_FRAMEWORKS
import GCDKit
#endif
#if os(tvOS)
internal let deviceDirectorySearchPath = NSSearchPathDirectory.CachesDirectory
#else
internal let deviceDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory
#endif
internal let defaultDirectory = NSFileManager.defaultManager().URLsForDirectory(deviceDirectorySearchPath, inDomains: .UserDomainMask).first!
internal let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData"
internal let defaultSQLiteStoreURL = defaultDirectory.URLByAppendingPathComponent(applicationName, isDirectory: false).URLByAppendingPathExtension("sqlite")
// MARK: - DataStack
/**
The `DataStack` encapsulates the data model for the Core Data stack. Each `DataStack` can have multiple data stores, usually specified as a "Configuration" in the model editor. Behind the scenes, the DataStack manages its own `NSPersistentStoreCoordinator`, a root `NSManagedObjectContext` for disk saves, and a shared `NSManagedObjectContext` designed as a read-only model interface for `NSManagedObjects`.
*/
public final class DataStack {
// MARK: Public
/**
Initializes a `DataStack` from an `NSManagedObjectModel`.
- parameter modelName: the name of the (.xcdatamodeld) model file. If not specified, the application name will be used.
- parameter bundle: an optional bundle to load models from. If not specified, the main bundle will be used.
- parameter migrationChain: the `MigrationChain` that indicates the sequence of model versions to be used as the order for incremental migration. If not specified, will default to a non-migrating data stack.
*/
public required init(modelName: String = applicationName, bundle: NSBundle = NSBundle.mainBundle(), migrationChain: MigrationChain = nil) {
CoreStore.assert(
migrationChain.valid,
"Invalid migration chain passed to the \(typeName(DataStack)). Check that the model versions' order is correct and that no repetitions or ambiguities exist."
)
let model = NSManagedObjectModel.fromBundle(
bundle,
modelName: modelName,
modelVersionHints: migrationChain.leafVersions
)
self.coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
self.rootSavingContext = NSManagedObjectContext.rootSavingContextForCoordinator(self.coordinator)
self.mainContext = NSManagedObjectContext.mainContextForRootContext(self.rootSavingContext)
self.model = model
self.migrationChain = migrationChain
self.rootSavingContext.parentStack = self
}
/**
Returns the `DataStack`'s model version. The version string is the same as the name of the version-specific .xcdatamodeld file.
*/
public var modelVersion: String {
return self.model.currentModelVersion!
}
/**
Returns the entity name-to-class type mapping from the `DataStack`'s model.
*/
public var entityTypesByName: [String: NSManagedObject.Type] {
return self.model.entityTypesMapping()
}
/**
Returns the `NSEntityDescription` for the specified `NSManagedObject` subclass.
*/
public func entityDescriptionForType(type: NSManagedObject.Type) -> NSEntityDescription? {
return NSEntityDescription.entityForName(
self.model.entityNameForClass(type),
inManagedObjectContext: self.mainContext
)
}
/**
Returns the `NSManagedObjectID` for the specified object URI if it exists in the persistent store.
*/
public func objectIDForURIRepresentation(url: NSURL) -> NSManagedObjectID? {
return self.coordinator.managedObjectIDForURIRepresentation(url)
}
/**
Adds an in-memory store to the stack.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`.
- returns: the `NSPersistentStore` added to the stack.
*/
public func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore {
let coordinator = self.coordinator;
var store: NSPersistentStore?
var storeError: NSError?
coordinator.performBlockAndWait {
do {
store = try coordinator.addPersistentStoreWithType(
NSInMemoryStoreType,
configuration: configuration,
URL: nil,
options: nil
)
}
catch {
storeError = error as NSError
}
}
if let store = store {
self.updateMetadataForPersistentStore(store)
return store
}
let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
CoreStore.handleError(
error,
"Failed to add in-memory \(typeName(NSPersistentStore)) to the stack."
)
throw error
}
/**
Adds to the stack an SQLite store from the given SQLite file name.
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory (or the "Caches" directory on tvOS). A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false
- returns: the `NSPersistentStore` added to the stack.
*/
public func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
return try self.addSQLiteStoreAndWait(
fileURL: defaultDirectory.URLByAppendingPathComponent(
fileName,
isDirectory: false
),
configuration: configuration,
resetStoreOnModelMismatch: resetStoreOnModelMismatch
)
}
/**
Adds to the stack an SQLite store from the given SQLite file URL.
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory (or the "Caches" directory on tvOS). Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
- parameter resetStoreOnModelMismatch: Set to true to delete the store on model mismatch; or set to false to throw exceptions on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false.
- returns: the `NSPersistentStore` added to the stack.
*/
public func addSQLiteStoreAndWait(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore {
CoreStore.assert(
fileURL.fileURL,
"The specified file URL for the SQLite store is invalid: \"\(fileURL)\""
)
let coordinator = self.coordinator;
if let store = coordinator.persistentStoreForURL(fileURL) {
guard store.type == NSSQLiteStoreType
&& store.configurationName == (configuration ?? Into.defaultConfigurationName) else {
let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL)
CoreStore.handleError(
error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
)
throw error
}
return store
}
let fileManager = NSFileManager.defaultManager()
_ = try? fileManager.createDirectoryAtURL(
fileURL.URLByDeletingLastPathComponent!,
withIntermediateDirectories: true,
attributes: nil
)
var store: NSPersistentStore?
var storeError: NSError?
let options = self.optionsForSQLiteStore()
coordinator.performBlockAndWait {
do {
store = try coordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: configuration,
URL: fileURL,
options: options
)
}
catch {
storeError = error as NSError
}
}
if let store = store {
self.updateMetadataForPersistentStore(store)
return store
}
if let error = storeError
where (resetStoreOnModelMismatch && error.isCoreDataMigrationError) {
fileManager.removeSQLiteStoreAtURL(fileURL)
var store: NSPersistentStore?
coordinator.performBlockAndWait {
do {
store = try coordinator.addPersistentStoreWithType(
NSSQLiteStoreType,
configuration: configuration,
URL: fileURL,
options: [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
)
}
catch {
storeError = error as NSError
}
}
if let store = store {
self.updateMetadataForPersistentStore(store)
return store
}
}
let error = storeError ?? NSError(coreStoreErrorCode: .UnknownError)
CoreStore.handleError(
error,
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\"."
)
throw error
}
// MARK: Internal
internal let coordinator: NSPersistentStoreCoordinator
internal let rootSavingContext: NSManagedObjectContext
internal let mainContext: NSManagedObjectContext
internal let model: NSManagedObjectModel
internal let migrationChain: MigrationChain
internal let childTransactionQueue: GCDQueue = .createSerial("com.coreStore.dataStack.childTransactionQueue")
internal let migrationQueue: NSOperationQueue = {
let migrationQueue = NSOperationQueue()
migrationQueue.maxConcurrentOperationCount = 1
migrationQueue.name = "com.coreStore.migrationOperationQueue"
migrationQueue.qualityOfService = .Utility
migrationQueue.underlyingQueue = dispatch_queue_create("com.coreStore.migrationQueue", DISPATCH_QUEUE_SERIAL)
return migrationQueue
}()
internal func optionsForSQLiteStore() -> [String: AnyObject] {
return [NSSQLitePragmasOption: ["journal_mode": "WAL"]]
}
internal func entityNameForEntityClass(entityClass: AnyClass) -> String? {
return self.model.entityNameForClass(entityClass)
}
internal func persistentStoresForEntityClass(entityClass: AnyClass) -> [NSPersistentStore]? {
var returnValue: [NSPersistentStore]? = nil
self.storeMetadataUpdateQueue.barrierSync {
returnValue = self.entityConfigurationsMapping[NSStringFromClass(entityClass)]?.map {
return self.configurationStoreMapping[$0]!
} ?? []
}
return returnValue
}
internal func persistentStoreForEntityClass(entityClass: AnyClass, configuration: String?, inferStoreIfPossible: Bool) -> (store: NSPersistentStore?, isAmbiguous: Bool) {
var returnValue: (store: NSPersistentStore?, isAmbiguous: Bool) = (store: nil, isAmbiguous: false)
self.storeMetadataUpdateQueue.barrierSync {
let configurationsForEntity = self.entityConfigurationsMapping[NSStringFromClass(entityClass)] ?? []
if let configuration = configuration {
if configurationsForEntity.contains(configuration) {
returnValue = (store: self.configurationStoreMapping[configuration], isAmbiguous: false)
return
}
else if !inferStoreIfPossible {
return
}
}
switch configurationsForEntity.count {
case 0:
return
case 1 where inferStoreIfPossible:
returnValue = (store: self.configurationStoreMapping[configurationsForEntity.first!], isAmbiguous: false)
default:
returnValue = (store: nil, isAmbiguous: true)
}
}
return returnValue
}
internal func updateMetadataForPersistentStore(persistentStore: NSPersistentStore) {
self.storeMetadataUpdateQueue.barrierAsync {
let configurationName = persistentStore.configurationName
self.configurationStoreMapping[configurationName] = persistentStore
for entityDescription in (self.coordinator.managedObjectModel.entitiesForConfiguration(configurationName) ?? []) {
if self.entityConfigurationsMapping[entityDescription.managedObjectClassName] == nil {
self.entityConfigurationsMapping[entityDescription.managedObjectClassName] = []
}
self.entityConfigurationsMapping[entityDescription.managedObjectClassName]?.insert(configurationName)
}
}
}
// MARK: Private
private let storeMetadataUpdateQueue = GCDQueue.createConcurrent("com.coreStore.persistentStoreBarrierQueue")
private var configurationStoreMapping = [String: NSPersistentStore]()
private var entityConfigurationsMapping = [String: Set<String>]()
deinit {
for store in self.coordinator.persistentStores {
_ = try? self.coordinator.removePersistentStore(store)
}
}
}

View File

@@ -0,0 +1,112 @@
//
// PersistentStoreResult.swift
// CoreStore
//
// Copyright (c) 2014 John Rommel Estropia
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.
//
import Foundation
import CoreData
// MARK: - PersistentStoreResult
/**
The `PersistentStoreResult` indicates the result of an asynchronous initialization of a persistent store.
The `PersistentStoreResult` can be treated as a boolean:
try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
if result {
// succeeded
}
else {
// failed
}
})
or as an `enum`, where the resulting associated object can also be inspected:
try! CoreStore.addSQLiteStore(completion: { (result: PersistentStoreResult) -> Void in
switch result {
case .Success(let persistentStore):
// persistentStore is the related NSPersistentStore instance
case .Failure(let error):
// error is the NSError instance for the failure
}
})
```
*/
public enum PersistentStoreResult {
// MARK: Public
/**
`PersistentStoreResult.Success` indicates that the persistent store process succeeded. The associated object for this `enum` value is the related `NSPersistentStore` instance.
*/
case Success(NSPersistentStore)
/**
`PersistentStoreResult.Failure` indicates that the persistent store process failed. The associated object for this value is the related `NSError` instance.
*/
case Failure(NSError)
// MARK: Internal
internal init(_ store: NSPersistentStore) {
self = .Success(store)
}
internal init(_ error: NSError) {
self = .Failure(error)
}
internal init(_ errorCode: CoreStoreErrorCode) {
self.init(errorCode, userInfo: nil)
}
internal init(_ errorCode: CoreStoreErrorCode, userInfo: [NSObject: AnyObject]?) {
self.init(NSError(
coreStoreErrorCode: errorCode,
userInfo: userInfo))
}
}
// MARK: - PersistentStoreResult: BooleanType
extension PersistentStoreResult: BooleanType {
// MARK: Public
public var boolValue: Bool {
switch self {
case .Success: return true
case .Failure: return false
}
}
}

View File

@@ -35,6 +35,9 @@
B569651A1B30888A0075EE4A /* FetchingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B56965191B30888A0075EE4A /* FetchingResultsViewController.swift */; };
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B569651B1B30889A0075EE4A /* QueryingResultsViewController.swift */; };
B56965291B3582D30075EE4A /* MigrationDemo.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B56965271B3582D30075EE4A /* MigrationDemo.xcdatamodeld */; };
B5A93A141C214E5900E47273 /* SpotlightDemoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5A93A131C214E5900E47273 /* SpotlightDemoViewController.swift */; };
B5C12CE71C21B2F70098E05F /* CoreSpotlight.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5C12CE61C21B2F70098E05F /* CoreSpotlight.framework */; };
B5C12CE91C21B2FD0098E05F /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5C12CE81C21B2FD0098E05F /* MobileCoreServices.framework */; };
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, ); }; };
@@ -95,6 +98,9 @@
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; };
B5A93A131C214E5900E47273 /* SpotlightDemoViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SpotlightDemoViewController.swift; sourceTree = "<group>"; };
B5C12CE61C21B2F70098E05F /* CoreSpotlight.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreSpotlight.framework; path = System/Library/Frameworks/CoreSpotlight.framework; sourceTree = SDKROOT; };
B5C12CE81C21B2FD0098E05F /* MobileCoreServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MobileCoreServices.framework; path = System/Library/Frameworks/MobileCoreServices.framework; sourceTree = SDKROOT; };
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>"; };
@@ -112,6 +118,8 @@
files = (
B5E89ACD1C52929C003B04A9 /* GCDKit.framework in Frameworks */,
B5E89AD01C5292A2003B04A9 /* CoreStore.framework in Frameworks */,
B5C12CE91C21B2FD0098E05F /* MobileCoreServices.framework in Frameworks */,
B5C12CE71C21B2F70098E05F /* CoreSpotlight.framework in Frameworks */,
B52977E11B120F8A003D50A5 /* CoreLocation.framework in Frameworks */,
B52977DF1B120F83003D50A5 /* MapKit.framework in Frameworks */,
);
@@ -144,6 +152,8 @@
B52977E21B120F90003D50A5 /* Frameworks */ = {
isa = PBXGroup;
children = (
B5C12CE81C21B2FD0098E05F /* MobileCoreServices.framework */,
B5C12CE61C21B2F70098E05F /* CoreSpotlight.framework */,
B52977E01B120F8A003D50A5 /* CoreLocation.framework */,
B5BDC9211C202429008147CD /* CoreStore.framework */,
B5BDC9241C202429008147CD /* GCDKit.framework */,
@@ -173,6 +183,7 @@
isa = PBXGroup;
children = (
B54AAD4E1AF4D26E00848AE0 /* AppDelegate.swift */,
B5A93A121C214D9D00E47273 /* Spotlight Demo */,
B566E3271B117AE700F4F0C6 /* Stack Setup Demo */,
B503FADA1AFDC71700F90881 /* List and Object Observers Demo */,
B52977DB1B120F2C003D50A5 /* Transactions Demo */,
@@ -244,6 +255,14 @@
path = "Migrations Demo";
sourceTree = "<group>";
};
B5A93A121C214D9D00E47273 /* Spotlight Demo */ = {
isa = PBXGroup;
children = (
B5A93A131C214E5900E47273 /* SpotlightDemoViewController.swift */,
);
path = "Spotlight Demo";
sourceTree = "<group>";
};
/* End PBXGroup section */
/* Begin PBXNativeTarget section */
@@ -342,6 +361,7 @@
B56964D71B231AE90075EE4A /* StackSetupDemo.xcdatamodeld in Sources */,
B56964DC1B231BCB0075EE4A /* FemaleAccount.swift in Sources */,
B5EE259E1B3EC1B20000406B /* OrganismProtocol.swift in Sources */,
B5A93A141C214E5900E47273 /* SpotlightDemoViewController.swift in Sources */,
B5EE258C1B36E40D0000406B /* MigrationsDemoViewController.swift in Sources */,
B569651C1B30889A0075EE4A /* QueryingResultsViewController.swift in Sources */,
B5125C121B521B78003A42C7 /* OrganismV2ToV3.xcmappingmodel in Sources */,
@@ -459,7 +479,7 @@
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Debug;
@@ -471,7 +491,7 @@
INFOPLIST_FILE = CoreStoreDemo/Info.plist;
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks";
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.corestore.demo;
PRODUCT_BUNDLE_IDENTIFIER = com.johnestropia.CoreStoreDemo;
PRODUCT_NAME = "$(TARGET_NAME)";
};
name = Release;

View File

@@ -3,10 +3,11 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/05/02.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit
import CoreSpotlight
// MARK: - AppDelegate
@@ -21,6 +22,25 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
application.statusBarStyle = .LightContent
return true
}
func application(application: UIApplication, continueUserActivity userActivity: NSUserActivity, restorationHandler: ([AnyObject]?) -> Void) -> Bool {
guard #available(iOS 9.0, *) else {
return false
}
guard userActivity.activityType == CSSearchableItemActionType,
let identifier = userActivity.userInfo?[CSSearchableItemActivityIdentifier] as? String else {
return false
}
let alert = UIAlertController(title: identifier, message: "You have tapped \(identifier)", preferredStyle: .Alert)
alert.addAction(UIAlertAction(title: "OK", style: .Default, handler: nil))
self.window?.rootViewController?.presentViewController(alert, animated: true, completion: nil)
return true
}

View File

@@ -1,8 +1,7 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="10112" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7706" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10083"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7703"/>
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
@@ -12,17 +11,14 @@
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<imageView userInteractionEnabled="NO" contentMode="center" horizontalHuggingPriority="251" verticalHuggingPriority="251" image="CoreStoreIcon" translatesAutoresizingMaskIntoConstraints="NO" id="q8C-V6-gXH">
<rect key="frame" x="155" y="83" width="170" height="170"/>
</imageView>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="273" width="440" height="57.5"/>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" misplaced="YES" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="140" width="441" height="43"/>
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
@@ -30,21 +26,16 @@
</subviews>
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
<constraint firstItem="q8C-V6-gXH" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" multiplier="0.7" id="QW6-8Y-w15"/>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
<constraint firstItem="q8C-V6-gXH" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="fRb-1V-9iD"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="top" secondItem="q8C-V6-gXH" secondAttribute="bottom" constant="20" id="s63-MP-ush"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
<resources>
<image name="CoreStoreIcon" width="170" height="170"/>
</resources>
</document>

View File

@@ -1,53 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<<<<<<< Updated upstream
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
=======
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="9532" systemVersion="15D21" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9530"/>
>>>>>>> Stashed changes
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<<<<<<< Updated upstream
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright © 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21"/>
=======
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2015 John Rommel Estropia. All rights reserved." textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439.5" width="441" height="20.5"/>
>>>>>>> Stashed changes
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="CoreStore" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="133" width="441" height="57.5"/>
<fontDescription key="fontDescription" name="HelveticaNeue-UltraLight" family="Helvetica Neue" pointSize="50"/>
<color key="textColor" red="0.92549019607843142" green="0.94117647058823528" blue="0.94509803921568625" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="0.20392156862745098" green="0.28627450980392155" blue="0.36862745098039218" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
</constraints>
<nil key="simulatedStatusBarMetrics"/>
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
<point key="canvasLocation" x="548" y="455"/>
</view>
</objects>
</document>

View File

@@ -1,8 +1,8 @@
<?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">
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="9531" systemVersion="15C50" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" initialViewController="Ni8-QF-XHB">
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="10083"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="9529"/>
<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"/>
@@ -16,6 +16,7 @@
<string>HelveticaNeue-Bold</string>
<string>HelveticaNeue</string>
<string>HelveticaNeue</string>
<string>HelveticaNeue</string>
</mutableArray>
<mutableArray key="HelveticaNeueLights.ttc">
<string>HelveticaNeue-Light</string>
@@ -33,6 +34,7 @@
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
<string>HelveticaNeue-Light</string>
</mutableArray>
</customFonts>
<scenes>
@@ -444,6 +446,33 @@
<segue destination="iVv-Vc-nCL" kind="show" id="HbO-rU-Qfj"/>
</connections>
</tableViewCell>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" textLabel="Kbo-Ue-PVK" detailTextLabel="grJ-ZB-NmV" style="IBUITableViewCellStyleSubtitle" id="Yc3-hN-kGF">
<rect key="frame" x="0.0" y="399" width="600" height="50"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="Yc3-hN-kGF" id="eQ3-C9-8sy">
<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="Spotlight Search" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="Kbo-Ue-PVK">
<rect key="frame" x="15" y="6" width="142.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.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="Indexing objects for CoreSpotlight search" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="grJ-ZB-NmV">
<rect key="frame" x="15" y="30" width="218" height="13.5"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="11"/>
<color key="textColor" red="0.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
<connections>
<segue destination="rvO-rT-QbP" kind="show" id="A6e-J5-Bgv"/>
</connections>
</tableViewCell>
</cells>
</tableViewSection>
</sections>
@@ -1093,11 +1122,75 @@
</objects>
<point key="canvasLocation" x="4404" y="3055"/>
</scene>
<!--Spotlight Items-->
<scene sceneID="2O0-uM-CKG">
<objects>
<tableViewController id="rvO-rT-QbP" customClass="SpotlightDemoViewController" customModule="CoreStoreDemo" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" showsSelectionImmediatelyOnTouchBegin="NO" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="TLr-Zy-NC6">
<rect key="frame" x="0.0" y="0.0" width="600" height="556"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<prototypes>
<tableViewCell contentMode="scaleToFill" selectionStyle="default" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="PaletteTableViewCell" id="2KP-BI-oxb" customClass="PaletteTableViewCell" customModule="CoreStoreDemo" customModuleProvider="target">
<rect key="frame" x="0.0" y="86" width="600" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" tableViewCell="2KP-BI-oxb" id="kPu-7L-49q">
<rect key="frame" x="0.0" y="0.0" width="567" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<view contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hTX-Dn-pv2">
<rect key="frame" x="8" y="8" width="27" height="27"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<constraints>
<constraint firstAttribute="width" secondItem="hTX-Dn-pv2" secondAttribute="height" multiplier="1:1" id="073-rz-szO"/>
</constraints>
</view>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="O5e-aP-R0t">
<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.17254901959999999" green="0.24313725489999999" blue="0.31372549020000001" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<constraints>
<constraint firstItem="O5e-aP-R0t" firstAttribute="height" secondItem="hTX-Dn-pv2" secondAttribute="height" id="4Lt-tT-SDF"/>
<constraint firstItem="hTX-Dn-pv2" firstAttribute="leading" secondItem="kPu-7L-49q" secondAttribute="leadingMargin" id="imZ-w9-r9g"/>
<constraint firstAttribute="bottomMargin" secondItem="hTX-Dn-pv2" secondAttribute="bottom" id="k3I-MU-Z4p"/>
<constraint firstItem="O5e-aP-R0t" firstAttribute="leading" secondItem="hTX-Dn-pv2" secondAttribute="trailing" constant="10" id="sNZ-Ym-Pwm"/>
<constraint firstAttribute="centerY" secondItem="O5e-aP-R0t" secondAttribute="centerY" id="wvz-LF-25u"/>
<constraint firstItem="hTX-Dn-pv2" firstAttribute="top" secondItem="kPu-7L-49q" secondAttribute="topMargin" id="xNz-bS-ajl"/>
</constraints>
</tableViewCellContentView>
<connections>
<outlet property="colorView" destination="hTX-Dn-pv2" id="AgP-lI-5ea"/>
<outlet property="label" destination="O5e-aP-R0t" id="IYz-nE-xVN"/>
</connections>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="rvO-rT-QbP" id="7kz-6g-mJK"/>
<outlet property="delegate" destination="rvO-rT-QbP" id="iOM-iJ-jYn"/>
</connections>
</tableView>
<extendedEdge key="edgesForExtendedLayout" top="YES"/>
<toolbarItems/>
<navigationItem key="navigationItem" title="Spotlight Items" id="Xbw-P1-gif">
<barButtonItem key="rightBarButtonItem" style="plain" systemItem="add" id="PxN-1K-Npj">
<connections>
<action selector="addBarButtonTapped:" destination="rvO-rT-QbP" id="z4I-GZ-AfS"/>
</connections>
</barButtonItem>
</navigationItem>
<simulatedToolbarMetrics key="simulatedBottomBarMetrics"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="KMF-5b-6U9" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="3694" y="4816"/>
</scene>
</scenes>
<resources>
<image name="second" width="30" height="30"/>
</resources>
<inferredMetricsTieBreakers>
<segue reference="fIB-GS-Ppk"/>
<segue reference="hyN-De-zte"/>
</inferredMetricsTieBreakers>
</document>

View File

@@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="10169.1" systemVersion="15D21" minimumToolsVersion="Automatic">
<model userDefinedModelVersionIdentifier="" type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="9525" systemVersion="15C50" 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"/>
@@ -26,6 +26,9 @@
<configuration name="ObservingDemo">
<memberEntity name="Palette"/>
</configuration>
<configuration name="SpotlightDemo">
<memberEntity name="Palette"/>
</configuration>
<configuration name="TransactionsDemo">
<memberEntity name="Place"/>
</configuration>

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/06/12.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit
@@ -15,12 +15,10 @@ private struct Static {
static let timeZonesStack: DataStack = {
let dataStack = DataStack()
try! dataStack.addStorageAndWait(
SQLiteStore(
fileName: "TimeZoneDemo.sqlite",
configuration: "FetchingAndQueryingDemo",
localStorageOptions: .RecreateStoreOnModelMismatch
)
try! dataStack.addSQLiteStoreAndWait(
fileName: "TimeZoneDemo.sqlite",
configuration: "FetchingAndQueryingDemo",
resetStoreOnModelMismatch: true
)
dataStack.beginSynchronous { (transaction) -> Void in
@@ -39,7 +37,7 @@ private struct Static {
cachedTimeZone.daylightSavingTimeOffset = rawTimeZone.daylightSavingTimeOffset
}
transaction.commitAndWait()
transaction.commit()
}
return dataStack
@@ -99,13 +97,12 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
// MARK: UITableViewDataSource
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
switch self.segmentedControl?.selectedSegmentIndex {
case Section.Fetching.rawValue?:
case .Some(Section.Fetching.rawValue):
return self.fetchingItems.count
case Section.Querying.rawValue?:
case .Some(Section.Querying.rawValue):
return self.queryingItems.count
default:
@@ -119,10 +116,10 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
switch self.segmentedControl?.selectedSegmentIndex {
case Section.Fetching.rawValue?:
case .Some(Section.Fetching.rawValue):
cell.textLabel?.text = self.fetchingItems[indexPath.row].title
case Section.Querying.rawValue?:
case .Some(Section.Querying.rawValue):
cell.textLabel?.text = self.queryingItems[indexPath.row].title
default:
@@ -141,10 +138,10 @@ class FetchingAndQueryingDemoViewController: UIViewController, UITableViewDataSo
switch self.segmentedControl?.selectedSegmentIndex {
case Section.Fetching.rawValue?:
case .Some(Section.Fetching.rawValue):
self.performSegueWithIdentifier("FetchingResultsViewController", sender: indexPath)
case Section.Querying.rawValue?:
case .Some(Section.Querying.rawValue):
self.performSegueWithIdentifier("QueryingResultsViewController", sender: indexPath)
default:

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/06/17.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/06/17.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit
@@ -16,19 +16,19 @@ class QueryingResultsViewController: UITableViewController {
switch value {
case (let array as [AnyObject])?:
case .Some(let array as [AnyObject]):
self.values = array.map { (item: AnyObject) -> (title: String, detail: String) in
(
title: item.description,
detail: String(reflecting: item.dynamicType)
detail: _stdlib_getDemangledTypeName(item)
)
}
case let item?:
case .Some(let item):
self.values = [
(
title: item.description,
detail: String(reflecting: item.dynamicType)
detail: _stdlib_getDemangledTypeName(item)
)
]

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/06/15.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import Foundation

View File

@@ -21,33 +21,13 @@
"scale" : "3x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@2x.png",
"size" : "60x60",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "iphone",
"filename" : "Icon-60@3x-1.png",
"scale" : "3x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76.png",
"scale" : "1x"
},
{
"size" : "76x76",
"idiom" : "ipad",
"filename" : "Icon-76@2x.png",
"scale" : "2x"
},
{
"size" : "60x60",
"idiom" : "car",
"filename" : "Icon-60@3x.png",
"scale" : "3x"
}
],

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

View File

@@ -1,6 +0,0 @@
{
"info" : {
"version" : 1,
"author" : "xcode"
}
}

View File

@@ -1,21 +0,0 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "CoreStoreIcon@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

View File

@@ -4,8 +4,6 @@
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleDisplayName</key>
<string>CoreStore</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/05/02.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit
@@ -49,12 +49,10 @@ private struct Static {
static let palettes: ListMonitor<Palette> = {
try! CoreStore.addStorageAndWait(
SQLiteStore(
fileName: "ColorsDemo.sqlite",
configuration: "ObservingDemo",
localStorageOptions: .RecreateStoreOnModelMismatch
)
try! CoreStore.addSQLiteStoreAndWait(
fileName: "ColorsDemo.sqlite",
configuration: "ObservingDemo",
resetStoreOnModelMismatch: true
)
return CoreStore.monitorSectionedList(
@@ -90,7 +88,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
UIBarButtonItem(
barButtonSystemItem: .Trash,
target: self,
action: #selector(self.resetBarButtonItemTouched(_:))
action: "resetBarButtonItemTouched:"
)
]
@@ -98,13 +96,13 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
title: Static.filter.rawValue,
style: .Plain,
target: self,
action: #selector(self.filterBarButtonItemTouched(_:))
action: "filterBarButtonItemTouched:"
)
navigationItem.rightBarButtonItems = [
UIBarButtonItem(
barButtonSystemItem: .Add,
target: self,
action: #selector(self.addBarButtonItemTouched(_:))
action: "addBarButtonItemTouched:"
),
filterBarButton
]
@@ -121,7 +119,7 @@ class ListObserverDemoViewController: UITableViewController, ListSectionObserver
switch (segue.identifier, segue.destinationViewController, sender) {
case ("ObjectObserverDemoViewController"?, let destinationViewController as ObjectObserverDemoViewController, let palette as Palette):
case (.Some("ObjectObserverDemoViewController"), let destinationViewController as ObjectObserverDemoViewController, let palette as Palette):
destinationViewController.palette = palette
default:

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/05/06.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit
@@ -61,7 +61,7 @@ class ObjectObserverDemoViewController: UIViewController, ObjectObserver {
let palette = transaction.create(Into(Palette))
palette.setInitialValues()
transaction.commitAndWait()
transaction.commit()
}
let palette = CoreStore.fetchOne(From(Palette), OrderBy(.Ascending("hue")))!

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/05/24.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/05/05.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import Foundation

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/05/05.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit

View File

@@ -3,7 +3,7 @@
// CoreStoreDemo
//
// Created by John Rommel Estropia on 2015/06/05.
// Copyright © 2015 John Rommel Estropia. All rights reserved.
// Copyright (c) 2015 John Rommel Estropia. All rights reserved.
//
import UIKit
@@ -30,7 +30,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
super.viewDidLoad()
try! self.dataStack.addStorageAndWait(SQLiteStore(fileName: "emptyStore.sqlite"))
try! self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite")
CoreStore.logger = self
}
@@ -66,7 +66,7 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
}
}
func log(error error: CoreStoreError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func handleError(error error: NSError, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
GCDQueue.Main.async { [weak self] in
@@ -74,20 +74,24 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
}
}
func assert(@autoclosure condition: () -> Bool, @autoclosure message: () -> String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
func assert(@autoclosure condition: () -> Bool, message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
if condition() {
return
}
let messageString = message()
GCDQueue.Main.async { [weak self] in
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(messageString)\n\n")
self?.textView?.insertText("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Assert] \(message)\n\n")
}
}
@noreturn func fatalError(message: String, fileName: StaticString, lineNumber: Int, functionName: StaticString) {
Swift.fatalError("\((fileName.stringValue as NSString).lastPathComponent):\(lineNumber) \(functionName)\n ↪︎ [Abort] \(message)")
}
// MARK: Private
@@ -98,21 +102,20 @@ class CustomLoggerViewController: UIViewController, CoreStoreLogger {
switch self.segmentedControl?.selectedSegmentIndex {
case 0?:
case .Some(0):
self.dataStack.beginAsynchronous { (transaction) -> Void in
transaction.create(Into(Palette))
}
case 1?:
_ = try? dataStack.addStorageAndWait(
SQLiteStore(
fileName: "emptyStore.sqlite",
configuration: "invalidStore"
)
)
case .Some(1):
do {
try self.dataStack.addSQLiteStoreAndWait(fileName: "emptyStore.sqlite", configuration: "invalidStore")
}
catch _ { }
case 2?:
case .Some(2):
self.dataStack.beginAsynchronous { (transaction) -> Void in
transaction.commit()

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