forked from jessesquires/JSQCoreDataKit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathCoreDataStack.swift
More file actions
232 lines (187 loc) · 8.95 KB
/
CoreDataStack.swift
File metadata and controls
232 lines (187 loc) · 8.95 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
//
// Created by Jesse Squires
// https://www.jessesquires.com
//
//
// Documentation
// https://jessesquires.github.io/JSQCoreDataKit
//
//
// GitHub
// https://github.com/jessesquires/JSQCoreDataKit
//
//
// License
// Copyright © 2015 Jesse Squires
// Released under an MIT license: https://opensource.org/licenses/MIT
//
import CoreData
import Foundation
/**
An instance of `CoreDataStack` encapsulates the entire Core Data stack.
It manages the managed object model, the persistent store coordinator, and managed object contexts.
It is composed of a main context and a background context.
These two contexts operate on the main queue and a private background queue, respectively.
Both are connected to the persistent store coordinator and data between them is perpetually kept in sync.
Changes to a child context are propagated to its parent context and eventually the persistent store when saving.
- warning: **You cannot create a `CoreDataStack` instance directly. Instead, use a `CoreDataStackFactory` for initialization.**
*/
public final class CoreDataStack: CustomStringConvertible, Equatable {
// MARK: Properties
/// The model for the stack.
public let model: CoreDataModel
/**
The main managed object context for the stack, which operates on the main queue.
This context is a root level context that is connected to the `storeCoordinator`.
It is kept in sync with `backgroundContext`.
*/
public let mainContext: NSManagedObjectContext
/**
The background managed object context for the stack, which operates on a background queue.
This context is a root level context that is connected to the `storeCoordinator`.
It is kept in sync with `mainContext`.
*/
public let backgroundContext: NSManagedObjectContext
/**
The persistent store coordinator for the stack. Both contexts are connected to this coordinator.
*/
public let storeCoordinator: NSPersistentStoreCoordinator
// MARK: Initialization
internal init(model: CoreDataModel,
mainContext: NSManagedObjectContext,
backgroundContext: NSManagedObjectContext,
storeCoordinator: NSPersistentStoreCoordinator) {
self.model = model
self.mainContext = mainContext
self.backgroundContext = backgroundContext
self.storeCoordinator = storeCoordinator
let notificationCenter = NotificationCenter.default
notificationCenter.addObserver(self,
selector: #selector(didReceiveMainContextDidSave(notification:)),
name: .NSManagedObjectContextDidSave,
object: mainContext)
notificationCenter.addObserver(self,
selector: #selector(didReceiveBackgroundContextDidSave(notification:)),
name: .NSManagedObjectContextDidSave,
object: backgroundContext)
}
/// :nodoc:
deinit {
NotificationCenter.default.removeObserver(self)
}
// MARK: Child contexts
/**
Creates a new child context with the specified `concurrencyType` and `mergePolicyType`.
The parent context is either `mainContext` or `backgroundContext` dependending on the specified `concurrencyType`:
* `.PrivateQueueConcurrencyType` will set `backgroundContext` as the parent.
* `.MainQueueConcurrencyType` will set `mainContext` as the parent.
Saving the returned context will propagate changes through the parent context and then to the persistent store.
- parameter concurrencyType: The concurrency pattern to use. The default is `.MainQueueConcurrencyType`.
- parameter mergePolicyType: The merge policy to use. The default is `.MergeByPropertyObjectTrumpMergePolicyType`.
- returns: A new child managed object context.
*/
public func childContext(concurrencyType: NSManagedObjectContextConcurrencyType = .mainQueueConcurrencyType,
mergePolicyType: NSMergePolicyType = .mergeByPropertyObjectTrumpMergePolicyType) -> ChildContext {
let childContext = NSManagedObjectContext(concurrencyType: concurrencyType)
childContext.mergePolicy = NSMergePolicy(merge: mergePolicyType)
switch concurrencyType {
case .mainQueueConcurrencyType:
childContext.parent = mainContext
case .privateQueueConcurrencyType:
childContext.parent = backgroundContext
case .confinementConcurrencyType:
fatalError("*** Error: ConfinementConcurrencyType is not supported because it is being deprecated in iOS 9.0")
}
if let name = childContext.parent?.name {
childContext.name = name + ".child"
}
NotificationCenter.default.addObserver(self,
selector: #selector(didReceiveChildContextDidSave(notification:)),
name: .NSManagedObjectContextDidSave,
object: childContext)
return childContext
}
/**
Resets the managed object contexts in the stack on their respective threads.
Then, if the coordinator is connected to a persistent store, the store will be deleted and recreated on a background thread.
The completion closure is executed on the main thread.
- note: Removing and re-adding the persistent store is performed on a background queue.
For binary and SQLite stores, this will also remove the store from disk.
- parameter queue: A background queue on which to reset the stack.
The default is a background queue with a "user initiated" quality of service class.
- parameter completion: The closure to be called once resetting is complete. This is called on the main queue.
*/
public func reset(onQueue queue: DispatchQueue = .global(qos: .userInitiated),
completion: @escaping (StackResult) -> Void) {
mainContext.performAndWait { self.mainContext.reset() }
backgroundContext.performAndWait { self.backgroundContext.reset() }
guard let store = storeCoordinator.persistentStores.first else {
DispatchQueue.main.async {
completion(.success(self))
}
return
}
queue.async {
precondition(!Thread.isMainThread, "*** Error: cannot reset a stack on the main queue")
let storeCoordinator = self.storeCoordinator
let options = store.options
let model = self.model
storeCoordinator.performAndWait {
do {
if let modelURL = model.storeURL {
try storeCoordinator.destroyPersistentStore(at: modelURL,
ofType: model.storeType.type,
options: options)
}
try storeCoordinator.addPersistentStore(ofType: model.storeType.type,
configurationName: nil,
at: model.storeURL,
options: options)
}
catch {
DispatchQueue.main.async {
completion(.failure(error as NSError))
}
return
}
DispatchQueue.main.async {
completion(.success(self))
}
}
}
}
// MARK: CustomStringConvertible
/// :nodoc:
public var description: String {
get {
return "\(CoreDataStack.self)(model=\(model.name); mainContext=\(mainContext); backgroundContext=\(backgroundContext))"
}
}
// MARK: Private
@objc
private func didReceiveChildContextDidSave(notification: Notification) {
guard let context = notification.object as? NSManagedObjectContext else {
assertionFailure("*** Error: \(notification.name) posted from object of type "
+ String(describing: notification.object.self)
+ ". Expected \(NSManagedObjectContext.self) instead.")
return
}
guard let parentContext = context.parent else {
// have reached the root context, nothing to do
return
}
saveContext(parentContext)
}
@objc
private func didReceiveBackgroundContextDidSave(notification: Notification) {
mainContext.perform {
self.mainContext.mergeChanges(fromContextDidSave: notification)
}
}
@objc
private func didReceiveMainContextDidSave(notification: Notification) {
backgroundContext.perform {
self.backgroundContext.mergeChanges(fromContextDidSave: notification)
}
}
}