Core Data In Memory Store
source link: https://useyourloaf.com/blog/core-data-in-memory-store/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
Speed up your tests and SwiftUI previews by creating your Core Data stack with an in-memory store.
The Core Data stack uses an on-disk SQLite store by default. That causes some extra work when running tests as you need to reset the store to a known state before each test. It’s also slow which is a pain if you’re working with SwiftUI previews.
In Memory Store
The old way of creating an in-memory store was to change the store type in the persistent store descriptor before loading the store. The default is NSSQLiteStoreType
but we can switch to NSInMemoryStoreType
:
storeDescription.type = NSInMemoryStoreType
There’s nothing I can find in the documentation but Apple showed a different way during WWDC 2018. I was only reminded of it when looking at the project templates after creating a new Xcode Core Data project. The trick is to set the URL
of the persistent store description to /dev/null
before loading the store:
storeDescription.url = URL(fileURLWithPath: "/dev/null")
This still uses an SQLite store but we keep it in memory instead of writing it to disk. As well as being faster this also gives us a clean store each time.
NSPersistentContainer
I’m using a subclass of NSPersistentContainer
to load and configure my Core Data store. That’s a convenient place to create a custom initializer that has an option to use an in memory store:
public final class CoreDataContainer: NSPersistentContainer {
public init(name: String, bundle: Bundle = .main, inMemory: Bool = false) {
guard let mom = NSManagedObjectModel.mergedModel(from: [bundle]) else {
fatalError("Failed to create mom")
}
super.init(name: name, managedObjectModel: mom)
configureDefaults(inMemory)
}
}
Then when I configure the store I change the url
of the store description:
private func configureDefaults(_ inMemory: Bool = false) {
if let storeDescription = persistentStoreDescriptions.first {
storeDescription.shouldAddStoreAsynchronously = true
if inMemory {
storeDescription.url = URL(fileURLWithPath: "/dev/null")
storeDescription.shouldAddStoreAsynchronously = false
}
}
}
Note that I default to loading my store asynchronously to allow for slow store migrations. I don’t need to worry about that with an in-memory store. Since the store is always empty I can switch back to adding the store synchronously which keeps thing simple.
Unit Testing
In my unit tests I can now create the core data container with an in-memory store:
final class CoreDataContainerTests: XCTestCase {
private var container: CoreDataContainer!
override func setUpWithError() throws {
let container = CoreDataContainer(name: "Model", inMemory: true)
container.loadPersistentStores { description, error in
XCTAssertNil(error)
}
}
override func tearDownWithError() throws {
container = nil
}
func testPerformRequest() throws {
let context = container.viewContext
...
}
}
Note: I still keep some tests that use an on-disk store to reproduce the app environment.
SwiftUI Previews
This approach also works great for SwiftUI previews. I’ve been experimenting with keeping my Core Data objects contained in a store that publishes updates with Combine:
public final class WorldStore: ObservableObject {
@Published public private(set) var countries = [Country]()
@Published public private(set) var error: Error?
I’m creating the core data container when I initialize the store so I can again include an option for an in-memory store:
private let dataContainer: CoreDataContainer
public init(inMemory: Bool = false) {
dataContainer = CoreDataContainer(name: "World", inMemory: inMemory)
dataContainer.loadPersistentStores { ... }
}
}
I add my SwiftUI preview data as development assets:
extension Country {
static var previewData = [...]
}
I follow a similar approach to create a preview version of my store that is pre-populated with sample data:
extension WorldStore {
static var preview: WorldStore = {
let store = WorldStore(inMemory: true)
store.update(Country.previewData)
return store
}()
}
I can then use this in my SwiftUI previews:
struct WorldView_Previews: PreviewProvider {
static var previews: some View {
WorldView()
.environmentObject(WorldStore.preview)
}
}
Further Details
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK