Reputation: 1297
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
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:
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.
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
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