-
Notifications
You must be signed in to change notification settings - Fork 11
/
Copy pathPersistenceController.swift
176 lines (152 loc) · 7.39 KB
/
PersistenceController.swift
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
/*
<samplecode>
<abstract>
A class that sets up the Core Data stack.
</abstract>
</samplecode>
*/
import Foundation
import CoreData
import CloudKit
import SwiftUI
let gCloudKitContainerIdentifier = "iCloud.com.example.ziqiao-samplecode.CoreDataCloudKitShare"
/**
This app doesn't necessarily post notifications from the main queue.
*/
extension Notification.Name {
static let cdcksStoreDidChange = Notification.Name("cdcksStoreDidChange")
}
struct UserInfoKey {
static let storeUUID = "storeUUID"
static let transactions = "transactions"
}
struct TransactionAuthor {
static let app = "app"
}
class PersistenceController: NSObject, ObservableObject {
static let shared = PersistenceController()
lazy var persistentContainer: NSPersistentCloudKitContainer = {
/**
Prepare the parent folder for the Core Data stores.
A Core Data store has companion files, so it is a good practice to put a store under a folder.
*/
let baseURL = NSPersistentContainer.defaultDirectoryURL()
let storeFolderURL = baseURL.appendingPathComponent("CoreDataStores")
let privateStoreFolderURL = storeFolderURL.appendingPathComponent("Private")
let sharedStoreFolderURL = storeFolderURL.appendingPathComponent("Shared")
let fileManager = FileManager.default
for folderURL in [privateStoreFolderURL, sharedStoreFolderURL] where !fileManager.fileExists(atPath: folderURL.path) {
do {
try fileManager.createDirectory(at: folderURL, withIntermediateDirectories: true, attributes: nil)
} catch {
fatalError("#\(#function): Failed to create the store folder: \(error)")
}
}
let container = NSPersistentCloudKitContainer(name: "CoreDataCloudKitShare")
/**
Grab the default (first) store and associate it with the CloudKit private database.
Set up the store description by:
- Specifying a file name for the store.
- Enabling history tracking and remote notifications.
- Specifying the iCloud container and database scope.
*/
guard let privateStoreDescription = container.persistentStoreDescriptions.first else {
fatalError("#\(#function): Failed to retrieve a persistent store description.")
}
privateStoreDescription.url = privateStoreFolderURL.appendingPathComponent("private.sqlite")
//#-code-listing(setOption)
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
privateStoreDescription.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey)
//#-end-code-listing
//#-code-listing(NSPersistentCloudKitContainerOptions)
let cloudKitContainerOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: gCloudKitContainerIdentifier)
//#-end-code-listing
cloudKitContainerOptions.databaseScope = .private
privateStoreDescription.cloudKitContainerOptions = cloudKitContainerOptions
/**
Similarly, add a second store and associate it with the CloudKit shared database.
*/
guard let sharedStoreDescription = privateStoreDescription.copy() as? NSPersistentStoreDescription else {
fatalError("#\(#function): Copying the private store description returned an unexpected value.")
}
sharedStoreDescription.url = sharedStoreFolderURL.appendingPathComponent("shared.sqlite")
let sharedStoreOptions = NSPersistentCloudKitContainerOptions(containerIdentifier: gCloudKitContainerIdentifier)
sharedStoreOptions.databaseScope = .shared
sharedStoreDescription.cloudKitContainerOptions = sharedStoreOptions
/**
Load the persistent stores
*/
container.persistentStoreDescriptions.append(sharedStoreDescription)
container.loadPersistentStores(completionHandler: { (loadedStoreDescription, error) in
guard error == nil else {
fatalError("#\(#function): Failed to load persistent stores:\(error!)")
}
guard let cloudKitContainerOptions = loadedStoreDescription.cloudKitContainerOptions else {
return
}
if cloudKitContainerOptions.databaseScope == .private {
self._privatePersistentStore = container.persistentStoreCoordinator.persistentStore(for: loadedStoreDescription.url!)
} else if cloudKitContainerOptions.databaseScope == .shared {
self._sharedPersistentStore = container.persistentStoreCoordinator.persistentStore(for: loadedStoreDescription.url!)
}
})
/**
Run initializeCloudKitSchema() once to update the CloudKit schema every time you change the Core Data model.
Do not call this code in the production environment.
*/
#if InitializeCloudKitSchema
do {
try container.initializeCloudKitSchema()
} catch {
print("\(#function): initializeCloudKitSchema: \(error)")
}
#else
container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
container.viewContext.transactionAuthor = TransactionAuthor.app
/**
Automatically merge the changes from other contexts.
*/
container.viewContext.automaticallyMergesChangesFromParent = true
/**
Pin the viewContext to the current generation token and set it to keep itself up to date with local changes.
*/
do {
try container.viewContext.setQueryGenerationFrom(.current)
} catch {
fatalError("#\(#function): Failed to pin viewContext to the current generation:\(error)")
}
/**
Observe the following notifications:
- The remote change notifications from container.persistentStoreCoordinator
- The .NSManagedObjectContextDidSave notifications from any context.
- The event change notifications from the container.
*/
NotificationCenter.default.addObserver(self, selector: #selector(storeRemoteChange(_:)),
name: .NSPersistentStoreRemoteChange,
object: container.persistentStoreCoordinator)
NotificationCenter.default.addObserver(self, selector: #selector(containerEventChanged(_:)),
name: NSPersistentCloudKitContainer.eventChangedNotification,
object: container)
#endif
return container
}()
private var _privatePersistentStore: NSPersistentStore?
var privatePersistentStore: NSPersistentStore {
return _privatePersistentStore!
}
private var _sharedPersistentStore: NSPersistentStore?
var sharedPersistentStore: NSPersistentStore {
return _sharedPersistentStore!
}
lazy var cloudKitContainer: CKContainer = {
return CKContainer(identifier: gCloudKitContainerIdentifier)
}()
/**
An operation queue for handling history processing tasks: watching changes, deduplicating tags, and triggering UI updates if needed.
*/
lazy var historyQueue: OperationQueue = {
let queue = OperationQueue()
queue.maxConcurrentOperationCount = 1
return queue
}()
}