@@ -78,34 +78,50 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift {
7878 }
7979
8080 @objc ( performAndSaveUsingBlock: completion: onQueue: )
81- public func performAndSave( _ block: @escaping ( NSManagedObjectContext ) -> Void , completion: ( ( ) -> Void ) ? , on queue: DispatchQueue ) {
81+ public func performAndSave(
82+ _ block: @escaping ( NSManagedObjectContext ) -> Void ,
83+ completion: ( ( ) -> Void ) ? ,
84+ on queue: DispatchQueue
85+ ) {
8286 let context = newDerivedContext ( )
83- self . writerQueue. addOperation ( AsyncBlockOperation { done in
84- context. perform {
85- block ( context)
87+ self . writerQueue. addOperation (
88+ AsyncBlockOperation { done in
89+ context. perform {
90+ block ( context)
8691
87- self . save ( context, . alreadyInContextQueue)
88- queue. async { completion ? ( ) }
89- done ( )
92+ self . save ( context, . alreadyInContextQueue)
93+ queue. async { completion ? ( ) }
94+ done ( )
95+ }
9096 }
91- } )
97+ )
9298 }
9399
94- public func performAndSave< T> ( _ block: @escaping ( NSManagedObjectContext ) throws -> T , completion: ( ( Result < T , Error > ) -> Void ) ? , on queue: DispatchQueue ) {
100+ public func performAndSave< T> (
101+ _ block: @escaping ( NSManagedObjectContext ) throws -> T ,
102+ completion: ( ( Result < T , Error > ) -> Void ) ? ,
103+ on queue: DispatchQueue
104+ ) {
95105 let context = newDerivedContext ( )
96- self . writerQueue. addOperation ( AsyncBlockOperation { done in
97- context. perform {
98- let result = Result ( catching: { try block ( context) } )
99- if case . success = result {
100- self . save ( context, . alreadyInContextQueue)
106+ self . writerQueue. addOperation (
107+ AsyncBlockOperation { done in
108+ context. perform {
109+ let result = Result ( catching: { try block ( context) } )
110+ if case . success = result {
111+ self . save ( context, . alreadyInContextQueue)
112+ }
113+ queue. async { completion ? ( result) }
114+ done ( )
101115 }
102- queue. async { completion ? ( result) }
103- done ( )
104116 }
105- } )
117+ )
106118 }
107119
108- public func performAndSave< T> ( _ block: @escaping ( NSManagedObjectContext ) -> T , completion: ( ( T ) -> Void ) ? , on queue: DispatchQueue ) {
120+ public func performAndSave< T> (
121+ _ block: @escaping ( NSManagedObjectContext ) -> T ,
122+ completion: ( ( T ) -> Void ) ? ,
123+ on queue: DispatchQueue
124+ ) {
109125 performAndSave (
110126 block,
111127 completion: { ( result: Result < T , Error > ) in
@@ -147,7 +163,11 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift {
147163 return
148164 }
149165
150- guard let metadata = try ? NSPersistentStoreCoordinator . metadataForPersistentStore ( ofType: NSSQLiteStoreType, at: storeURL) ,
166+ guard
167+ let metadata = try ? NSPersistentStoreCoordinator . metadataForPersistentStore (
168+ ofType: NSSQLiteStoreType,
169+ at: storeURL
170+ ) ,
151171 !objectModel. isConfiguration ( withName: nil , compatibleWithStoreMetadata: metadata)
152172 else {
153173 return
@@ -159,7 +179,8 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift {
159179 fatalError ( " Can't find WordPress.momd " )
160180 }
161181
162- guard let versionInfo = NSDictionary ( contentsOf: modelFileURL. appendingPathComponent ( " VersionInfo.plist " ) ) else {
182+ guard let versionInfo = NSDictionary ( contentsOf: modelFileURL. appendingPathComponent ( " VersionInfo.plist " ) )
183+ else {
163184 fatalError ( " Can't get the object model's version info " )
164185 }
165186
@@ -181,7 +202,14 @@ public class ContextManager: NSObject, CoreDataStack, CoreDataStackSwift {
181202
182203private extension ContextManager {
183204 static var localDatabasePath : URL {
184- guard let url = try ? FileManager . default. url ( for: . documentDirectory, in: . userDomainMask, appropriateFor: nil , create: true ) else {
205+ guard
206+ let url = try ? FileManager . default. url (
207+ for: . documentDirectory,
208+ in: . userDomainMask,
209+ appropriateFor: nil ,
210+ create: true
211+ )
212+ else {
185213 fatalError ( " Failed to find the document directory " )
186214 }
187215
@@ -202,7 +230,10 @@ private extension ContextManager {
202230 }
203231
204232 // Ensure that the `context`'s concurrency type is not `confinementConcurrencyType`, since it will crash if `perform` or `performAndWait` is called.
205- guard context. concurrencyType == . mainQueueConcurrencyType || context. concurrencyType == . privateQueueConcurrencyType else {
233+ guard
234+ context. concurrencyType == . mainQueueConcurrencyType
235+ || context. concurrencyType == . privateQueueConcurrencyType
236+ else {
206237 block ( )
207238 return
208239 }
@@ -221,19 +252,45 @@ private extension ContextManager {
221252// MARK: - Initialise Core Data stack
222253
223254private extension ContextManager {
224- static func createPersistentContainer( storeURL: URL , modelName: String ) -> NSPersistentContainer {
225- guard var modelFileURL = Bundle . wordPressData. url ( forResource: " WordPress " , withExtension: " momd " ) else {
255+ /// The current version of the data model, loaded once per process.
256+ ///
257+ /// Every loaded `NSManagedObjectModel` registers its entities in Core Data's global
258+ /// class-to-entity table. Loading the model file more than once (which happens in unit
259+ /// tests, where each test creates its own `ContextManager` instance) leaves multiple
260+ /// entity descriptions claiming the same `NSManagedObject` subclasses, and
261+ /// `+[NSManagedObject entity]` fails to resolve them.
262+ static let currentObjectModel : NSManagedObjectModel = {
263+ guard let modelFileURL = Bundle . wordPressData. url ( forResource: " WordPress " , withExtension: " momd " ) else {
226264 fatalError ( " Can't find WordPress.momd " )
227265 }
228266
229- if modelName != ContextManagerModelNameCurrent {
230- modelFileURL = modelFileURL . appendingPathComponent ( modelName ) . appendingPathExtension ( " mom " )
267+ guard let objectModel = NSManagedObjectModel ( contentsOf : modelFileURL ) else {
268+ fatalError ( " Can't create object model at \( modelFileURL ) " )
231269 }
232270
233- guard let objectModel = NSManagedObjectModel ( contentsOf: modelFileURL) else {
234- fatalError ( " Can't create object model named \( modelName) at \( modelFileURL) " )
271+ return objectModel
272+ } ( )
273+
274+ static func objectModel( named modelName: String ) -> NSManagedObjectModel {
275+ guard modelName != ContextManagerModelNameCurrent else {
276+ return currentObjectModel
277+ }
278+
279+ guard let modelFileURL = Bundle . wordPressData. url ( forResource: " WordPress " , withExtension: " momd " ) else {
280+ fatalError ( " Can't find WordPress.momd " )
235281 }
236282
283+ let versionedModelURL = modelFileURL. appendingPathComponent ( modelName) . appendingPathExtension ( " mom " )
284+ guard let objectModel = NSManagedObjectModel ( contentsOf: versionedModelURL) else {
285+ fatalError ( " Can't create object model named \( modelName) at \( versionedModelURL) " )
286+ }
287+
288+ return objectModel
289+ }
290+
291+ static func createPersistentContainer( storeURL: URL , modelName: String ) -> NSPersistentContainer {
292+ let objectModel = objectModel ( named: modelName)
293+
237294 // FIXME: Import the Sentry stuff, too — But it accesses the app delegate!
238295 // let startupEvent = SentryStartupEvent()
239296
@@ -292,7 +349,7 @@ extension ContextManager {
292349 }
293350
294351 public static var shared : ContextManager {
295- return sharedInstance ( )
352+ sharedInstance ( )
296353 }
297354}
298355
0 commit comments