@@ -12,7 +12,7 @@ enum PersistenceConst {
1212 enum Entity {
1313 enum Task {
1414 static let name = " IterableTaskManagedObject "
15-
15+
1616 enum Column {
1717 static let id = " id "
1818 static let scheduledAt = " scheduledAt "
@@ -21,143 +21,152 @@ enum PersistenceConst {
2121 }
2222}
2323
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
3236 }
33-
37+ ITBInfo ( " DB Bundle url: \( url) " )
38+ return NSManagedObjectModel ( contentsOf: url)
39+ } ( )
40+
41+ final class PersistentContainer : NSPersistentContainer , @unchecked Sendable {
42+
3443 override func newBackgroundContext( ) -> NSManagedObjectContext {
3544 let backgroundContext = super. newBackgroundContext ( )
3645 backgroundContext. automaticallyMergesChangesFromParent = true
3746 backgroundContext. mergePolicy = NSMergePolicy ( merge: NSMergePolicyType . mergeByPropertyStoreTrumpMergePolicyType)
3847 return backgroundContext
3948 }
4049
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)
4556 }
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)
7859 }
7960}
8061
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+ ) {
8667 self . persistentContainer = persistentContainer
8768 self . dateProvider = dateProvider
8869 }
89-
70+
9071 func newBackgroundContext( ) -> IterablePersistenceContext {
72+ if !isStoreLoaded {
73+ isStoreLoaded = loadStore ( into: persistentContainer)
74+ }
9175 return CoreDataPersistenceContext ( managedObjectContext: persistentContainer. newBackgroundContext ( ) , dateProvider: dateProvider)
9276 }
93-
77+
9478 func mainQueueContext( ) -> IterablePersistenceContext {
79+ if !isStoreLoaded {
80+ isStoreLoaded = loadStore ( into: persistentContainer)
81+ }
9582 return CoreDataPersistenceContext ( managedObjectContext: persistentContainer. viewContext, dateProvider: dateProvider)
9683 }
97-
98- private let persistentContainer : PersistentContainer
84+
85+ private let persistentContainer : NSPersistentContainer
9986 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+ }
100107}
101108
102109struct CoreDataPersistenceContext : IterablePersistenceContext {
103110 init ( managedObjectContext: NSManagedObjectContext , dateProvider: DateProviderProtocol ) {
104111 self . managedObjectContext = managedObjectContext
105112 self . dateProvider = dateProvider
106113 }
107-
114+
108115 func create( task: IterableTask ) throws -> IterableTask {
109116 guard let taskManagedObject = createTaskManagedObject ( ) else {
110117 throw IterableDBError . general ( " Could not create task managed object " )
111118 }
112-
119+
113120 PersistenceHelper . copy ( from: task, to: taskManagedObject)
114121 taskManagedObject. createdAt = dateProvider. currentDate
115122 return PersistenceHelper . task ( from: taskManagedObject)
116123 }
117-
124+
118125 func update( task: IterableTask ) throws -> IterableTask {
119126 guard let taskManagedObject = try findTaskManagedObject ( id: task. id) else {
120127 throw IterableDBError . general ( " Could not find task to update " )
121128 }
122-
129+
123130 PersistenceHelper . copy ( from: task, to: taskManagedObject)
124131 taskManagedObject. modifiedAt = dateProvider. currentDate
125132 return PersistenceHelper . task ( from: taskManagedObject)
126133 }
127-
134+
128135 func delete( task: IterableTask ) throws {
129136 try deleteTask ( withId: task. id)
130137 }
131138
132139 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+ )
138147 return taskManagedObjects. first. map ( PersistenceHelper . task ( from: ) )
139148 }
140-
149+
141150 func findTask( withId id: String ) throws -> IterableTask ? {
142151 guard let taskManagedObject = try findTaskManagedObject ( id: id) else {
143152 return nil
144153 }
145154 return PersistenceHelper . task ( from: taskManagedObject)
146155 }
147-
156+
148157 func deleteTask( withId id: String ) throws {
149158 guard let taskManagedObject = try findTaskManagedObject ( id: id) else {
150159 return
151160 }
152161 managedObjectContext. delete ( taskManagedObject)
153162 }
154-
163+
155164 func findAllTasks( ) throws -> [ IterableTask ] {
156165 let taskManagedObjects : [ IterableTaskManagedObject ] = try CoreDataUtil . findAll ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name)
157-
166+
158167 return taskManagedObjects. map ( PersistenceHelper . task ( from: ) )
159168 }
160-
169+
161170 func deleteAllTasks( ) throws {
162171 let taskManagedObjects : [ IterableTaskManagedObject ] = try CoreDataUtil . findAll ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name)
163172 taskManagedObjects. forEach {
@@ -168,34 +177,41 @@ struct CoreDataPersistenceContext: IterablePersistenceContext {
168177 }
169178 }
170179 }
171-
180+
172181 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)
174183 }
175-
184+
176185 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+ }
177193 try managedObjectContext. save ( )
178194 }
179-
195+
180196 func perform( _ block: @escaping ( ) -> Void ) {
181197 managedObjectContext. perform ( block)
182198 }
183-
199+
184200 func performAndWait( _ block: ( ) -> Void ) {
185201 managedObjectContext. performAndWait ( block)
186202 }
187-
203+
188204 func performAndWait< T> ( _ block: ( ) throws -> T ) throws -> T {
189205 try managedObjectContext. performAndWait ( block)
190206 }
191-
207+
192208 private let managedObjectContext : NSManagedObjectContext
193209 private let dateProvider : DateProviderProtocol
194-
210+
195211 private func findTaskManagedObject( id: String ) throws -> IterableTaskManagedObject ? {
196212 try CoreDataUtil . findEntitiyByColumn ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name, columnName: PersistenceConst . Entity. Task. Column. id, columnValue: id)
197213 }
198-
214+
199215 private func createTaskManagedObject( ) -> IterableTaskManagedObject ? {
200216 CoreDataUtil . create ( context: managedObjectContext, entity: PersistenceConst . Entity. Task. name)
201217 }
0 commit comments