@@ -12,7 +12,7 @@ enum PersistenceConst {
12
12
enum Entity {
13
13
enum Task {
14
14
static let name = " IterableTaskManagedObject "
15
-
15
+
16
16
enum Column {
17
17
static let id = " id "
18
18
static let scheduledAt = " scheduledAt "
@@ -21,143 +21,158 @@ enum PersistenceConst {
21
21
}
22
22
}
23
23
24
- class PersistentContainer : NSPersistentContainer {
25
- static var shared : PersistentContainer ?
26
-
27
- static func initialize( ) -> PersistentContainer ? {
28
- if shared == nil {
29
- shared = create ( )
30
- }
31
- return shared
32
- }
33
-
24
+ let sharedManagedObjectModel : NSManagedObjectModel ? = {
25
+ guard let url = PersistentContainer . dataModelUrl ( fromBundles: [ Bundle . main, Bundle ( for: PersistentContainer . self) ] ) else {
26
+ ITBError ( " Could not find \( PersistenceConst . dataModelFileName) . \( PersistenceConst . dataModelExtension) in bundle " )
27
+ return nil
28
+ }
29
+ ITBInfo ( " DB Bundle url: \( url) " )
30
+ return NSManagedObjectModel ( contentsOf: url)
31
+ } ( )
32
+
33
+ final class PersistentContainer : NSPersistentContainer , @unchecked Sendable {
34
+
34
35
override func newBackgroundContext( ) -> NSManagedObjectContext {
35
36
let backgroundContext = super. newBackgroundContext ( )
36
37
backgroundContext. automaticallyMergesChangesFromParent = true
37
38
backgroundContext. mergePolicy = NSMergePolicy ( merge: NSMergePolicyType . mergeByPropertyStoreTrumpMergePolicyType)
38
39
return backgroundContext
39
40
}
40
41
41
- private static func create( ) -> PersistentContainer ? {
42
- guard let managedObjectModel = createManagedObjectModel ( ) else {
43
- ITBError ( " Could not initialize managed object model " )
44
- return nil
42
+ override init (
43
+ name: String = PersistenceConst . dataModelFileName,
44
+ managedObjectModel: NSManagedObjectModel ? = sharedManagedObjectModel
45
+ ) {
46
+ if let managedObjectModel {
47
+ super. init ( name: name, managedObjectModel: managedObjectModel)
48
+ } else {
49
+ super. init ( name: name)
45
50
}
46
- let container = PersistentContainer ( name: PersistenceConst . dataModelFileName, managedObjectModel: managedObjectModel)
47
- container. loadPersistentStores { desc, error in
48
- if let error = error {
49
- ITBError ( " Unresolved error when creating PersistentContainer: \( error) " )
50
- }
51
-
52
- ITBInfo ( " Successfully loaded persistent store at: \( desc. url? . description ?? " nil " ) " )
53
- }
54
-
55
- container. viewContext. automaticallyMergesChangesFromParent = true
56
- container. viewContext. mergePolicy = NSMergePolicy ( merge: NSMergePolicyType . mergeByPropertyStoreTrumpMergePolicyType)
57
-
58
- return container
59
- }
60
-
61
- private static func createManagedObjectModel( ) -> NSManagedObjectModel ? {
62
- guard let url = dataModelUrl ( fromBundles: [ Bundle . main, Bundle ( for: PersistentContainer . self) ] ) else {
63
- ITBError ( " Could not find \( PersistenceConst . dataModelFileName) . \( PersistenceConst . dataModelExtension) in bundle " )
64
- return nil
65
- }
66
- ITBInfo ( " DB Bundle url: \( url) " )
67
- return NSManagedObjectModel ( contentsOf: url)
51
+ viewContext. automaticallyMergesChangesFromParent = true
52
+ viewContext. mergePolicy = NSMergePolicy ( merge: NSMergePolicyType . mergeByPropertyStoreTrumpMergePolicyType)
68
53
}
69
-
70
- private static func dataModelUrl( fromBundles bundles: [ Bundle ] ) -> URL ? {
54
+
55
+ static func dataModelUrl( fromBundles bundles: [ Bundle ] ) -> URL ? {
71
56
bundles. lazy. compactMap ( dataModelUrl ( fromBundle: ) ) . first
72
57
}
73
-
58
+
74
59
private static func dataModelUrl( fromBundle bundle: Bundle ) -> URL ? {
75
- ResourceHelper . url ( forResource: PersistenceConst . dataModelFileName,
76
- withExtension: PersistenceConst . dataModelExtension,
77
- fromBundle: bundle)
60
+ ResourceHelper . url (
61
+ forResource: PersistenceConst . dataModelFileName,
62
+ withExtension: PersistenceConst . dataModelExtension,
63
+ fromBundle: bundle
64
+ )
78
65
}
79
66
}
80
67
81
- struct CoreDataPersistenceContextProvider : IterablePersistenceContextProvider {
82
- init ? ( dateProvider : DateProviderProtocol = SystemDateProvider ( ) ) {
83
- guard let persistentContainer = PersistentContainer . initialize ( ) else {
84
- return nil
85
- }
68
+ final class CoreDataPersistenceContextProvider : IterablePersistenceContextProvider {
69
+ init (
70
+ dateProvider : DateProviderProtocol = SystemDateProvider ( ) ,
71
+ persistentContainer : NSPersistentContainer = PersistentContainer ( )
72
+ ) {
86
73
self . persistentContainer = persistentContainer
87
74
self . dateProvider = dateProvider
88
75
}
89
-
76
+
90
77
func newBackgroundContext( ) -> IterablePersistenceContext {
78
+ if !isStoreLoaded {
79
+ isStoreLoaded = loadStore ( into: persistentContainer)
80
+ }
91
81
return CoreDataPersistenceContext ( managedObjectContext: persistentContainer. newBackgroundContext ( ) , dateProvider: dateProvider)
92
82
}
93
-
83
+
94
84
func mainQueueContext( ) -> IterablePersistenceContext {
85
+ if !isStoreLoaded {
86
+ isStoreLoaded = loadStore ( into: persistentContainer)
87
+ }
95
88
return CoreDataPersistenceContext ( managedObjectContext: persistentContainer. viewContext, dateProvider: dateProvider)
96
89
}
97
-
98
- private let persistentContainer : PersistentContainer
90
+
91
+ private let persistentContainer : NSPersistentContainer
99
92
private let dateProvider : DateProviderProtocol
93
+ private var isStoreLoaded = false
94
+
95
+ /// Loads the persistent container synchronously so we can easily capture loading errors.
96
+ private func loadStore( into container: NSPersistentContainer ) -> Bool {
97
+ if let descriptor = container. persistentStoreDescriptions. first {
98
+ descriptor. shouldAddStoreAsynchronously = false
99
+ }
100
+
101
+ // This closure runs synchronously because of the settings above
102
+ var loadError : ( any Error ) ?
103
+ container. loadPersistentStores { _, error in
104
+ loadError = error
105
+ }
106
+
107
+ if let error = loadError {
108
+ ITBError ( " Failed to load Iterable's store. \( error. localizedDescription) " )
109
+ return false
110
+ }
111
+ return true
112
+ }
100
113
}
101
114
102
115
struct CoreDataPersistenceContext : IterablePersistenceContext {
103
116
init ( managedObjectContext: NSManagedObjectContext , dateProvider: DateProviderProtocol ) {
104
117
self . managedObjectContext = managedObjectContext
105
118
self . dateProvider = dateProvider
106
119
}
107
-
120
+
108
121
func create( task: IterableTask ) throws -> IterableTask {
109
122
guard let taskManagedObject = createTaskManagedObject ( ) else {
110
123
throw IterableDBError . general ( " Could not create task managed object " )
111
124
}
112
-
125
+
113
126
PersistenceHelper . copy ( from: task, to: taskManagedObject)
114
127
taskManagedObject. createdAt = dateProvider. currentDate
115
128
return PersistenceHelper . task ( from: taskManagedObject)
116
129
}
117
-
130
+
118
131
func update( task: IterableTask ) throws -> IterableTask {
119
132
guard let taskManagedObject = try findTaskManagedObject ( id: task. id) else {
120
133
throw IterableDBError . general ( " Could not find task to update " )
121
134
}
122
-
135
+
123
136
PersistenceHelper . copy ( from: task, to: taskManagedObject)
124
137
taskManagedObject. modifiedAt = dateProvider. currentDate
125
138
return PersistenceHelper . task ( from: taskManagedObject)
126
139
}
127
-
140
+
128
141
func delete( task: IterableTask ) throws {
129
142
try deleteTask ( withId: task. id)
130
143
}
131
144
132
145
func nextTask( ) throws -> IterableTask ? {
133
- let taskManagedObjects : [ IterableTaskManagedObject ] = try CoreDataUtil . findSortedEntities ( context: managedObjectContext,
134
- entity: PersistenceConst . Entity. Task. name,
135
- column: PersistenceConst . Entity. Task. Column. scheduledAt,
136
- ascending: true ,
137
- limit: 1 )
146
+ let taskManagedObjects : [ IterableTaskManagedObject ] = try CoreDataUtil . findSortedEntities (
147
+ context: managedObjectContext,
148
+ entity: PersistenceConst . Entity. Task. name,
149
+ column: PersistenceConst . Entity. Task. Column. scheduledAt,
150
+ ascending: true ,
151
+ limit: 1
152
+ )
138
153
return taskManagedObjects. first. map ( PersistenceHelper . task ( from: ) )
139
154
}
140
-
155
+
141
156
func findTask( withId id: String ) throws -> IterableTask ? {
142
157
guard let taskManagedObject = try findTaskManagedObject ( id: id) else {
143
158
return nil
144
159
}
145
160
return PersistenceHelper . task ( from: taskManagedObject)
146
161
}
147
-
162
+
148
163
func deleteTask( withId id: String ) throws {
149
164
guard let taskManagedObject = try findTaskManagedObject ( id: id) else {
150
165
return
151
166
}
152
167
managedObjectContext. delete ( taskManagedObject)
153
168
}
154
-
169
+
155
170
func findAllTasks( ) throws -> [ IterableTask ] {
156
171
let taskManagedObjects : [ IterableTaskManagedObject ] = try CoreDataUtil . findAll ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name)
157
-
172
+
158
173
return taskManagedObjects. map ( PersistenceHelper . task ( from: ) )
159
174
}
160
-
175
+
161
176
func deleteAllTasks( ) throws {
162
177
let taskManagedObjects : [ IterableTaskManagedObject ] = try CoreDataUtil . findAll ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name)
163
178
taskManagedObjects. forEach {
@@ -168,34 +183,41 @@ struct CoreDataPersistenceContext: IterablePersistenceContext {
168
183
}
169
184
}
170
185
}
171
-
186
+
172
187
func countTasks( ) throws -> Int {
173
- return try CoreDataUtil . count ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name)
188
+ try CoreDataUtil . count ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name)
174
189
}
175
-
190
+
176
191
func save( ) throws {
192
+ // Guard against Objective-C exceptions which cannot be caught in Swift.
193
+ guard
194
+ let coordinator = managedObjectContext. persistentStoreCoordinator,
195
+ !coordinator. persistentStores. isEmpty
196
+ else {
197
+ throw NSError ( domain: NSCocoaErrorDomain, code: NSPersistentStoreSaveError)
198
+ }
177
199
try managedObjectContext. save ( )
178
200
}
179
-
201
+
180
202
func perform( _ block: @escaping ( ) -> Void ) {
181
203
managedObjectContext. perform ( block)
182
204
}
183
-
205
+
184
206
func performAndWait( _ block: ( ) -> Void ) {
185
207
managedObjectContext. performAndWait ( block)
186
208
}
187
-
209
+
188
210
func performAndWait< T> ( _ block: ( ) throws -> T ) throws -> T {
189
211
try managedObjectContext. performAndWait ( block)
190
212
}
191
-
213
+
192
214
private let managedObjectContext : NSManagedObjectContext
193
215
private let dateProvider : DateProviderProtocol
194
-
216
+
195
217
private func findTaskManagedObject( id: String ) throws -> IterableTaskManagedObject ? {
196
218
try CoreDataUtil . findEntitiyByColumn ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name, columnName: PersistenceConst . Entity. Task. Column. id, columnValue: id)
197
219
}
198
-
220
+
199
221
private func createTaskManagedObject( ) -> IterableTaskManagedObject ? {
200
222
CoreDataUtil . create ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name)
201
223
}
0 commit comments