protasm
protasm

Reputation: 1297

Background Seeding/Loading iOS Core Data Store

My iOS 8.0 + app is essentially a dictionary app, presenting a read-only data set to the user in an indexed, easily navigable format. I have explored several strategies for loading the static data, and I have decided to ship the app with several JSON data files that are serialized and loaded into a Core Data store once when the app is first opened. The call to managedObjectContext.save(), therefore, will happen only once in the lifetime of the app, on first use.

From reading Apple's Core Data Programming Guide in the Mac Developer Library (updated Sept. 2015), I understand that Apple's recommended practice is to

1) separate the Core Data stack from the AppDelegate into a dedicated DataController object (which makes it seem odd that even in Xcode 7.2 the Core Data stack is still put in the AppDelegate by default, but anyway...); and
2) open (and, I assume, seed/load) the persistent store in a background thread with a dispatch_async block, like so :

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0)) {
    //(get URL to persistent store here)

    do {
        try psc.addPersistentStoreWithType(NSSQLiteStoreType, configuration: nil, URL: storeURL, options: nil)

        //presumably load the store from serialized JSON files here?
    } catch { fatalError("Error migrating store: \(error)") }
}

I'm just getting started learning about concurrency and GCD, so my questions are basic:

1) If the data set is being loaded in a background thread, which could take some non-trivial time to complete, how does the initial view controller know when the data is finished loading so that it can fetch data from the ManagedObjectContext to display in a UITableView ?

2) Along similar lines, if I would like to test the completely loaded data set by running some fetches and printing debug text to the console, how will I know when the background process is finished and it's safe to start querying?

Thanks!

p.s. I am developing in swift, so any swift-specific tips would be tremendous.

Upvotes: 1

Views: 620

Answers (2)

bjrne
bjrne

Reputation: 375

Leaving aside whether loading a JSON at the first startup is the best option and that this question is four years old, the solution to your two questions is probably using notifications. They work from all threads and every listening class instance will be notified. Plus, you only need to add two lines:

  1. The listener (your view controller or test class for question 2) needs to listen for notifications of a specific notification name:

    NotificationCenter.default.addObserver(self, selector: #selector(ViewController.handleMySeedNotification(_:)), name: "com.yourwebsite.MyCustomSeedNotificationName", object: nil)

where @objc func handleMySeedNotification(_ notification: Notification) is the function where you are going to implement whatever should happen when a notification is received.

  1. The caller (your database logic) the sends the notification on successful data import. This looks like this:

    NotificationCenter.default.post(name: "com.yourwebsite.MyCustomSeedNotificationName", object: nil)

This is enough. I personally like to use an extension to Notification.Name in order to access the names faster and to prevent typos. This is optional, but works like this:

extension Notification.Name {
    static let MyCustomName1 = Notification.Name("com.yourwebsite.MyCustomSeedNotificationName1")
    static let MyCustomName2 = Notification.Name("CustomNotificationName2")
}

Using them now becomes as easy as this: NotificationCenter.default.post(name: .MyCustomSeedNotificationName1, object: nil) and even has code-completion after typing the dot!

Upvotes: 0

user4151918
user4151918

Reputation:

Instead of trying to make your app import the read-only data on first launch (forcing the user to wait while the data is imported), you can import the data yourself, then add the read-only .sqlite file and data model to your app target, to be copied to the app bundle.

For the import, specify that the persistent store should use the rollback journaling option, since write-ahead logging is not recommended for read-only stores:

let importStoreOptions: [NSObject: AnyObject] = [
    NSSQLitePragmasOption: ["journal_mode": "DELETE"],]

In the actual app, also specify that the bundled persistent store should use the read-only option:

let readOnlyStoreOptions: [NSObject: AnyObject] = [
    NSReadOnlyPersistentStoreOption: true,
    NSSQLitePragmasOption: ["journal_mode": "DELETE"],]

Since the bundled persistent store is read-only, it can be accessed directly from the app bundle, and would not even need to be copied from the bundle to a user directory.

Upvotes: 1

Related Questions