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