magicalRecord setup with icloud wont reload persisted data - ios

The following line fails to persist any data, although there is a sqlite file at library/Application Support/Appname/Appname.sqlite that has header information as set from the model.
[MagicalRecord setupCoreDataStackWithiCloudContainer:#"128DBLAH422.com.company.appname.ubiquitycoredata" localStoreNamed:#"Appname.sqlite"];
ICloud is also started, as the kMagicalRecordPSCDidCompleteiCloudSetupNotification notification is fired.
I loose all data on the next run, although I am able to save correctly throughout run-time ('Finished saving' on main thread) and have all (as far as I've seen) data available in-memory.
The following saves and persists properly w/ an sqlite file at the same directory path:
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:#"Appname.sqlite"];
I wonder why when iCloud is enabled that I'm seeing the DB in "library/Application Support/Appname/". Is that correct?
On one device w/ 5.0.1, I get the following error on runs after first run:
Error: Can't resolve how to assign objects to stores; Coordinator does not have any stores
on iPad and simulator, no error, but no data also.
Also... After first run with setup core data with icloud (1st line in did finish launching):
[MagicalRecord setupCoreDataStackWithiCloudContainer:#"128DBLAH422.com.company.appname.ubiquitycoredata" localStoreNamed:#"Appname.sqlite"];
any future run using
[MagicalRecord setupCoreDataStackWithAutoMigratingSqliteStoreNamed:#"Appname.sqlite"];
instead of icloud setup will recover the data that wouldn't have appeared on next run otherwise. If I add data on first run with icloud, re-run with icloud and add no data, then run without icloud, the data is there.
Is there a method of loading the store on subsequent runs that is unique from when creating it for the first time?

Related

Cannot get the latest CoreData DB file in Swift

I have built a app using CoreData for persisting contents. Everything works fine. Now I would like to export the SQLite DB file of my stored data.
I checked the DB file's path by downloading container from my device. It seems to be "~\Library\Application Support\MainData.sqlite".
Screenshot of filepath:
I called shareDatabase() to share DB file using AirDrop to my Mac, and it works normally:
func shareDatabase() {
try? (UIApplication.shared.delegate as! AppDelegate).persistentContainer.viewContext.save()
let fileName = "MainData.sqlite"
let filePath = URL(fileURLWithPath: NSHomeDirectory()).appendingPathComponent("Library").appendingPathComponent("Application Support").appendingPathComponent(fileName)
let activityVC = UIActivityViewController(activityItems: [filePath], applicationActivities: nil)
present(activityVC, animated: true, completion: nil)
}
Strangely, the transferred MainData.sqlite is not the latest one, but the one with modified date of yesterday. I also used DB Browser opened the file. It does not contain the latest data.
Screenshot: info of AirDrop-shared file:
However, the MainData.sqlite from downloaded container is latest updated. The latest data are in this file, checking with DB Browser.
Screenshot: info of file in container:
Please help point out what is wrong with my codes.
Added contents:
Thank #user2782993 for referring the Apple answer on the topic: https://developer.apple.com/library/content/qa/qa1809/_index.html
I also read about this technical Q&A from apple library. But it is not very clear how to implement the mode changing option in XCode generated lazy var persistentContainer.
Also, this thread mentioned,
For sanity sake you should ensure you only have one connection to the
persistent store when you do this, i.e. only one persistent store
instance in a single persistent store coordinator.
I'm not sure how to close the existing connection. Any ideas? (better in Swift)
Try to use this:
https://stackoverflow.com/a/18870738/2782993
or this:
https://stackoverflow.com/a/20252663/2782993
Check this out:
http://pinkstone.co.uk/how-to-remove-wal-files-in-core-data/
Excerpt:
"Since iOS 7 and OS X 10.9 the default journalling mode in SQLite Stores is WAL. In addition to the main store file you’ll find a WAL file with the same (or larger) size as the store file, and a less important SHM file.
Prior to this implementation it was easy to save the context, extract the store file and ship it with an app as a pre-made data store. That’s no longer possible, because by default all data changes are written to the WAL file and do not sync with the main store file.
This is not a problem if you’re not shipping a pre-made store file with your app, but if you do, then this “improvement” has just ruined your way of delivering prewritten data stores.
Lucky for us we can switch this entire WAL business off by passing an option when creating our NSPersistentStoreCoordinator."
Here is the Apple answer on the topic:
https://developer.apple.com/library/content/qa/qa1809/_index.html
Please notice, synching the WAL data is called a checkpoint operation.
The important parts are in bold
Excerpt:
"A: The failure occurs because the default journaling mode for Core Data SQLite stores was changed to Write-Ahead Logging (WAL) in iOS 7 and OS X Mavericks. With the WAL mode, Core Data keeps the main store file untouched and appends transactions to a -wal file in the same location. After the Core Data context is saved, the -wal file is not deleted, and the data in that file is not merged to the store file either. Therefore, simply making copies of the store file will likely cause data loss and inconsistency.
...
To safely back up and restore a Core Data SQLite store, you can do the following:
Use the following method of NSPersistentStoreCoordinator class, rather than file system APIs, to back up and restore the Core Data store:
- (NSPersistentStore *)migratePersistentStore:(NSPersistentStore *)store toURL:(NSURL *)URL options:(NSDictionary *)options withType:(NSString *)storeType error:(NSError **)error
Note that this is the option we recommend.
Change to rollback journaling mode when adding the store to a persistent store coordinator if you have to copy the store file. Listing 1 is the code showing how to do this:
Listing 1 Use rollback journaling mode when adding a persistent store
NSDictionary *options = #{NSSQLitePragmasOption:#{#"journal_mode":#"DELETE"}};
if (! [persistentStoreCoordinator addPersistentStoreWithType:NSSQLiteStoreType
configuration:nil
URL:storeURL
options:options
error:&error]) {
// error handling.
}
For a store that was loaded with the WAL mode, if both the main store file and the corresponding -wal file exist, using rollback journaling mode to add the store to a persistent store coordinator will force Core Data to perform a checkpoint operation, which merges the data in the -wal file to the store file. This is actually the Core Data way to perform a checkpoint operation. On the other hand, if the -wal file is not present, using this approach to add the store won't cause any exceptions, but the transactions recorded in the missing -wal file will be lost.
Bundle the main store file and the -wal file into a document package and manipulate them as a single item.
For more information about the default journaling mode change, please see WWDC 2013 session 207 What's New in Core Data and iCloud.
NOTE: In iOS 6.x and Mountain Lion, the default is rollback journaling mode, in which Core Data creates a -journal file to temporarily store transactions, updates the main store file in place and deletes the -journal file after saving the context. The store file therefore contains the up-to-date database."

iOS 10+ using NSPersistentContainer - What's the best method to "clear all"

We want to have a settings bundle property that can be set to delete all the information stored using core data. I see some examples that loop over all the entities and calls delete. I see some that manipulate the files storing the stores. This last way sounds better but I'm not sure how that fits into the magic of NSPersistentContainer.
On iOS 10, what's the best way to delete all the content in Core Data and continue on in the app saving new fetched data to Core Data?
I haven't tried it, but here's what I think I'd do:
Create a new directory where you'll keep your persistent store and anything else that NSPersistentContainer wants to save. Make sure to create this directory when the app launches, using FileManager. You'll use this with Core Data by...
Subclassing NSPersistentContainer and overriding defaultDirectoryURL(). Use the directory from step 1.
Now you'll be sure that everything the persistent container writes goes in a known location that doesn't contain anything else.
When you want to delete everything, just delete every file in the directory that you're using (again, using FileManager). Make sure to do this before loading any data.

simperium: sqlite database restored to Xcode Simulator (Xcode 7.1.1). new records sync. old records don't

Question for Simperium:
A badly-written XC unit test wiped most data from a simperium user account (mine.) No problem. I moved a day-old back-up of the sqlite file into an iPhone 5 (iOS 9.1) simulator, and deleted the now-useless sqlite file from the same Documents directory.
I performed a clean (SHIFT-COMMAND-K), and started the simulator. All my missing data now appeared in the app simulator. Great.
BUT restored Core Data records did not then get synced to corresponding Simperium buckets. Puzzling as Simperium DID respond successfully to syncing new records on create, update, and delete actions.
I then deleted the entire data set on Simperium.com. Same result. New records syncing no problem, old records no.
Is there important meta-data that I deleted when deleting the old sqlite file. If so, (how) can I get old records to re-sync?
What's going on is: the method that should pick up those 'new old entities' is getting bypassed, because all of them already have both, a Key + GhostData.
This scenario should normally recover automatically, granted that those entities get updated somehow (that way the lib would pick them up and sync them with the backend).
The easiest way to force a re-upload would be:
In your app, add a (temporary + helper method) that gets executed right after Simperium is initialized (and thus, the Core Data stack is wired), but before the authenticate method is called.
Loop through all of the entities in that bucket and set to nil both, the simperiumKey and ghostData value.
Run your app just once
Kill this helper method (otherwise it'd probably cause duplicate entries!).
(Untested), but that should do the trick!

Core Data SQLite store becomes readonly after update

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.

Backing up sqlite DB on iOS

I would like to make backup copies of my app's main sqlite DB while my app is running.
1) I have read that it is safe to just copy the sqlite file if the DB has been checkpointed (at that point the wal file contains no important data). Does [managedContext save:] do that checkpointing, or is there something else I have to do? (ref -shm and -wal files in SQLite DB)
2) Is there any way, short of tearing down the whole core data stack, to be sure that core data doesn't try to write to the sqlite file while I'm copying it? My app does save frequently after any user input, and it would be nice if there was some way to force that to block for a second.
I have uploaded a sample app that provides backup and restore capabilities a number of different ways, including local backups, copy backups to and from iCloud, email backups, import from email, and file copy via iTunes. See link below for video demonstrating these capabilities and you can download the sample apps from the site.
http://ossh.com.au/design-and-technology/software-development/sample-library-style-ios-core-data-app-with-icloud-integration/sample-apps-explanations/backup-files/
EDIT
It should be safe to create a new persistentStoreCoordinator with the same fileURL and to then use migratePersistentStore API without closing the App, save the main MOC first though. I always use JOURNAL=DELETE mode to ensure I just have to use a single file to deal with. If you are using WAL mode then you would need to backup all three files used by sqlite.
Regarding 2)
When I make backup, I close all mom's, moc's and persistent store. Then it's safe to make backup or restore. Also all views are waiting for events to release all coredata resources and bring them back when database is available again. It's good to have a singleton to manage coredata.

Resources