I have had reports of users being unable to use an iOS app after updating via the App Store because the SQLite database in use by Core Data apparently becomes readonly. This occurs with a read/write persistent store that is kept in the Documents
folder of the app bundle.
The persistent store is created from scratch for each user the first time they log into the app. Its contents are maintained with a custom progressive managed object model migration. As it happens, there was no migration to perform for the most recent release, and thus the persistent store should be ready to open as soon as the app is launched.
The error that we received from the users is the same, and it happens early in the process launch after the upgrade completes. The userInfo
of the NSError
object is what we captured in this case:
NSSQLiteErrorDomain = 264;
NSUnderlyingException = "error during prepareSQL for SQL string 'SELECT Z_VERSION, Z_UUID, Z_PLIST FROM Z_METADATA' : attempt to write a readonly database";
Due to some shortcomings in what we are logging and how we are managing the log files, I do not know precisely when this happens in the process of opening the persistent store. We are using UIManagedDocument
but not sharing any documents via iCloud. I assume that the error occurs while opening an existing document package, but even that is a guess. (Because of this, I made improvements to the logging for future releases.)
The closest I have come to reproducing the error is to use the file permissions 0444 on the persistentStore-shm
file in the document package of an iOS simulator app installation and then try the SELECT
statement using the sqlite3
command line interface. The error in that case is SQLITE_CANTOPEN
rather than SQLITE_READONLY
, so I am probably not on the right track with a theory about file permissions. From what I can tell, UIManagedDocument
automatically fixes file permissions anyway.
What I am wondering is if anyone has experienced behavior similar to this? If so, is there any way to recover so that users do not have to go through the process of recreating the local data store?
Add your own descriptor to the options to (re)enable history tracking...
Do this before loading the store as part of the container set up:
let container = NSPersistentContainer(name: "YourDataStoreIdentifier" )
let description = container.persistentStoreDescriptions.first
// This allows a 'non-iCloud' sycning
// container to keep track of changes
// as if it was an iCloud syncing container
description?.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
container.loadPersistentStores(...
I ran in to this and it was rather puzzling. It turned out that the -shm file, the write-ahead log's index, had been cleared and needed to be rebuilt. This appears to happen automatically when loading the persistent store into an NSPersistentStoreCoordinator
.
If you call +[NSPersistentStore metadataForPersistentStoreWithURL:error:]
before doing that, which I was doing, you'll get the error you reported and the return value will be nil
.
So the solution that worked for me is to attempt to retrieve the metadata and, if that fails with an error, to load the persistent store into an NSPersistentStoreCoordinator
to rebuild the -shm. After doing that, calling metadataForPersistentStoreWithURL:error:
will return the metadata without hitting the readonly error.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With