Core Data: how to just delete and rebuild the data store?
Asked Answered
A

4

11

I'm using Core Data in an iOS 7+ app that does not need to save user's data, all data the app needs is requested to services and it can be recovered at any time. So, if I change my data model in a next app update, I have no problem in deleting all the previous data and requesting it all again. But I don't know how to simply replace the previous data model with the new one, without performing a migration since it looks that I don't need to do that...

Thanks in advance

Acciaccatura answered 13/3, 2015 at 7:54 Comment(3)
have you tried in-memory storage ?Wesle
@Wesle I was told to use the SQLite storage, but... where can I find documentation about the in-memory option?Acciaccatura
in addPersistentStoreWithType instead of NSSQLiteStoreType just use NSInMemoryStoreType and work with Core Data as usualWesle
K
13

Working Swift solution if you target iOS 9 or higher

The shared CoreData manager:

class CoreDataContext {
    static let datamodelName = "CoreDataTests"
    static let storeType = "sqlite"

    static let persistentContainer = NSPersistentContainer(name: datamodelName)
    private static let url: URL = {
        let url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].appendingPathComponent("\(datamodelName).\(storeType)")

        assert(FileManager.default.fileExists(atPath: url.path))

        return url
    }()

    static func loadStores() {
        persistentContainer.loadPersistentStores(completionHandler: { (nsPersistentStoreDescription, error) in
            guard let error = error else {
                return
            }
            fatalError(error.localizedDescription)
        })
    }

    static func deleteAndRebuild() {
        try! persistentContainer.persistentStoreCoordinator.destroyPersistentStore(at: url, ofType: storeType, options: nil)

        loadStores()
    }
}

call loadStores only once in the appDelegate and deleteAndRebuild when you want to delete and rebuild the database :)

Kinsley answered 22/6, 2019 at 10:37 Comment(2)
Did you actually run this code? you have asset(FileManager... which will crash the app if the file doesn't exist!! i.e. if its a first run.Tetrarch
@TommP You should read the answer first before commenting. Of course I did run the code and it works for many people, seeing the upvotes. In the end of the answer I stated already, call deleteAndRebuild if you want to delete the database. Why do you want to delete a database on the first run when there is no database? Of course that will fail. It makes no sense calling it unconditionally when the app starts, only call when a migration fails or so.Kinsley
L
11

Case 1: You're using a SQLite store

This applies if your store type is NSSQLiteStoreType. Even if you plan to delete your data from time to time, it's not a bad idea to stick to SQLite, as it gives you the flexibility to keep your cached data on disk as long as you wish, and only delete it when you change your model and you don't want to apply any migrations.

Quick solution? Delete your NSPersistentStoreCoordinator's store at startup, when you're initializing Core Data. For instance, if you're using the default SQLite store, provided by Apple's boilerplate code:

NSURL *storeURL = [[self applicationDocumentsDirectory] URLByAppendingPathComponent:@"cd.sqlite"]

you can simply delete the file:

[[NSFileManager defaultManager] removeItemAtURL:storeURL error:nil];

then use storeURL to add a new persistent store as usual.
Your NSPersistentStoreCoordinator won't complain if you have a new model and you won't need any migrations, but your data will be lost, of course.

It goes without saying that you can decide to use this solution when you detect a change in the data model, leaving the store alone if there's no change, so that you can preserve your cached data as long as necessary.

UPDATE:

As suggested by TomHerrington in the comments, to be sure you've removed the old store completely, you should also delete journal files, which might come back and haunt you in the future, if you don't take care of them.
If your store file is named cd.sqlite, like in the example, the additional files to be removed are cd.sqlite-shm and cd.sqlite-wal.

WAL journaling mode for Core Data was introduced as the default in iOS 7 and OSX Mavericks, as reported by Apple in QA1809.

Case 2: Use an in-memory store

As suggested, you could switch to an in-memory store, using NSInMemoryStoreType instead of NSSQLiteStoreType. Erasing the store is much easier in this case: all your data resides in memory, all of it will disappear when your app stops running, nothing will remain on disk for you to clean. Next time, you could potentially load a completely different model, without any migrations, as there's no data to migrate.
However, this solution, implemented as it is, wouldn't let you cache data between sessions, which looks like something you'd like to do between app updates (i.e., the store must be erased only when the app is updated and the model changes, while it could be useful to keep it on disk otherwise).

Note:

Both approaches are viable, with their pros and cons, and I'm sure there could be other strategies as well. In the end, you should have all the elements to decide what the best approach would be in your specific case.

Lighterman answered 13/3, 2015 at 8:15 Comment(7)
I'd just suggest not to delete persistent store file while you're in the middle of the app i.e. after persistent store has been added.Mithras
You're absolutely right. I took it for granted that, since we're talking about changing data model in an app update, this would be done at startup, when initializing Core Data. But thanks for the suggestion, I'll add a note :)Lighterman
You also need to delete the journal files, because some (maybe all) of the data in the persistent store is actually in those files.Darreldarrell
I never had any issues with the journal files. @TomHarrington Did you experience any inconsistencies after not deleting them?Quadrature
I haven't had need to delete the persistent store file, but I've seen others describe situations where they removed the persistent store file while leaving the journal files in place, and later found that old "deleted" data was still present because it had been in the journal.Darreldarrell
Thanks for the comment @TomHarrington . I haven't had any problems with journal files yet, but I see what you mean. My luck is probably due to my limited experience. Let me update my answer and mention additional files (both -wal and -shm) you'd better delete to be completely sure you've removed old data. Thanks!Lighterman
I wouldn't count NSInMemoryStoreType as a "persistent store" per se for the matter OP's question, although having used it many times - I really like it. However removing the persistent store files/bundle from under the feet of the PSCoordinator doesn't seem to be good practice for me. The ritual must first detach it from the actual store. How do you do that?Retainer
Q
7

I think destroyPersistentStoreAtURL method of NSPersistentStoreCoordinator is what you want.

It will remove the data store and journal files and all other things that need to be removed.

Look at Apple documentation.

Qualmish answered 15/2, 2018 at 10:38 Comment(1)
Thanks a lot for the answer. I believe it to be the correct one. However - considering the hierarchy and dependency of CoreData objects (Context, entities, PSCoordinator, PSStore etc.) is it safe to call "destroyPersistentStoreAtURL" any time? shouldn't one do some "closing" ritual before destroying? what is your experience in this regard?Retainer
T
0

OK So I needed to do this in my app, ended up writing the following function.

Note: Call this BEFORE you load your CoreData store (I added it to the init() of the App (it's a swiftUI app), which seemed to work.

Also note that the above answers referring to

   destroyPersistentStore(at: url, ofType: storeType, options: nil)

did not work for me because it seemed to not delete the .sqlite-wal file or the sqlite.shm file.

class CoreDataContext {
    static let datamodelName = "cedarca"
    static let storeType = "sqlite"

    static let persistentContainer = NSPersistentContainer(name: datamodelName)



    class func deleteAndRebuild() {
    
        let url = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].appendingPathComponent("\(datamodelName).\(storeType)")
        let url1 = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].appendingPathComponent("\(datamodelName).sqlite-shm")
        let url2 = FileManager.default.urls(for: .applicationSupportDirectory, in: .userDomainMask)[0].appendingPathComponent("\(datamodelName).sqlite-wal")
    
    
        if FileManager.default.fileExists(atPath: url.path) {
            do {
                try FileManager.default.removeItem(at: url)
                try FileManager.default.removeItem(at: url1)
                try FileManager.default.removeItem(at: url2)

            } catch {
                print(error)
            }
        
        }
    
    }
}
Tetrarch answered 27/3 at 9:26 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.