My app starts by presenting a tableview whose datasource is a Core Data SQLite store. When the app starts, a secondary thread with its own store controller and context is created to obtain updates from the web for data in the store. However, any resulting changes to the store are not notified to the fetchedresults controller (I presume because it has its own coordinator) and consequently the table is not updated with store changes. What would be the most efficient way to refresh the context on the main thread? I am considering tracking the objectIDs of any objects changed on the secondary thread, sending those to the main thread when the secondary thread completes and invoking "[context refreshObject:....] Any help would be greatly appreciated.
In your NSFetchedResultsController handling the table, register in viewDidLoad or loadView for a notification:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(contextDidSave:) name:#"ContextDidSave" object:nil];
When the secondary thread is ready with the new data, simply save the context as usual, and then post the notification:
[[NSNotificationCenter defaultCenter] postNotificationName:#"ContextDidSave" object:managedObjectContext];
The notification will be handled in your NSFetchedResultsController using the following method:
EDIT: modified the method below taking correctly into account multi-threading, after an insightful discussion with bbum.
- (void)contextDidSave:(NSNotification *)notification
{
SEL selector = #selector(mergeChangesFromContextDidSaveNotification:);
[[[[UIApplication sharedApplication] delegate] managedObjectContext] performSelectorOnMainThread:selector withObject:notification waitUntilDone:YES];
}
For UI update, it can be done automatically using the NSFetchedResultsController delegate methods.
Finally, remember to add in the dealloc method of the NSFetchedResultsController the following:
[[NSNotificationCenter defaultCenter] removeObserver:self];
Unforgiven's answer doesn't handle threading correctly. In particular, the documentation states (emphasis mine):
If your application has a graphical
user interface, it is recommended that
you receive user-related events and
initiate interface updates from your
application’s main thread. This
approach helps avoid synchronization
issues associated with handling user
events and drawing window content.
Some frameworks, such as Cocoa,
generally require this behavior, but
even for those that do not, keeping
this behavior on the main thread has
the advantage of simplifying the logic
for managing your user interface.
A notification observer will be fired on whatever thread the notification was posted upon in the first place. Thus, you can't call NSTableView's reloadData directly from the notification posted by a background thread.
There is no need to use notifications at all. In your background thread, when ready to update the user interface, use any of a number of mechanisms to reload the data in the main thread -- in the thread that manages the main event loop & user interface.
[tableView performSelectorOnMainThread: #selector(reloadData)
withObject: nil waitUntilDone: YES];
You can also use Grand Central Dispatch or NSOperation to do something similar.
Related
I have a ViewController which is pushed onto the NavigationController stack. As soon as it's pushed onto the stack it starts to download some images, by means of a downloader object, which is responsible for downlading the images in a background thread. The images can take several seconds, even over WiFi to download. When an image has finished being downloaded the downloader object instructs the ViewController to layout its images, putting the newly downloaded image(s) to the back of a paged UIScrollView. However during this time the user could have pressed the Back button and the ViewController could now have been released / deallocated and so the downloader object will cause a SIGABRT error message and the app will crash.
How should I deal with this situation? Is there some way to check for released / deallocated instances? Or some way to catch the error and log, then ignore, it?
Your best bet is to use a zeroing weak reference. Of course, with iOS 5.0, this is simply a "weak" reference. But, if you are targeting below iOS 5.0 though, then you need a custom solution for it. There is a nice one described by Mike Ash in this article.
Maybe you could avoid the problem by using notifications? Instead of your downloader object referencing the viewController, it posts a notification, which the viewController (if it's loaded) responds to.
in your data loading object:
[[NSNotificationCenter defaultCenter] postNotificationName:#"allDataLoaded" object:nil];
in your view controller viewDidLoad:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(refreshMyLayout) name:#"allDataLoaded" object:nil];
in your view controller dealloc:
[[NSNotificationCenter defaultCenter] removeObserver:self];
You could even put a reverse notification from your viewController in it's viewDidUnload to tell the data downloader to cancel the downloads (if you want).
The best would be to cancel the download once the result is no longer needed. This prevents unnecessary data traffic and memory consumption.
If you can't do that, the downloader object should have some sort of delegate (your view controller probably) that you can set to nil when you're no longer interested in the results (e.g. in your view controller's dealloc method). Zeroing weak references are also an option, if you target iOS 5, but again, it would be much better to cancel the downloading NSURLConnection.
I have a UITableViewController that I'd like to notify once the data of the corresponding model is ready to be displayed. The problem is that this data is fetched from a web service and the request can take up to several seconds. Currently, I'm fetching the data synchronously on the main thread which, of course, causes my main thread to block. Now, I don't want my controller to know anything about downloading data from the internet. How can I accomplish this. Currently, I'm thinking about utilizing GCD and implementing a method like -loadDataWithCallback: and provide a callback that triggers a [tableView reloadData] on success. Is this a good approach? Are there other possibilities to notify a controller that the model is ready? An other idea I had was to use a delegate mechanism and setting the controller as a delegate of my model?
To summarize, what's better: GCD with callbacks or implementing your own delegate mechanism?
Are there other possibilities?
Update: June, 24th 2011 13:15 CET
After reading all your replies, I come to the conclusion that there are 3 possible solutions to my problem:
Make use of NSNotifications and use NSURLConnection to implement async. download
Implement a custom protocol and use a delegation mechanism. Again, use NSURLConnection to implement async. download.
Use synchronous download in a separate GCD queue and use callbacks.
Since nobody favors the last solution, I want to discuss this approach a little in depth. After seeing all the code that is involved in notification handling, I think that GCD is a better approach. Instead of agreeing on a certain notification which has to be somehow documented, so that every developer knows about it, I can simply use a callback. On the one hand, it gives me a clear interface like one I would have when I would use a delegate, on the other hand, it give me total flexibility. Do you really think that GCD is to complicated for this? Here is my code:
- (void)loadRestaurantsWithCallback:(void (^)())callback
{
dispatch_queue_t current_queue = dispatch_get_current_queue();
dispatch_queue_t download_queue = dispatch_queue_create("Download queue", NULL);
dispatch_async(download_queue, ^{
self.restaurants = [self loadRestaurants];
dispatch_async(current_queue, ^{ callback(); });
});
dispatch_release(download_queue);
}
Btw., my application simply displays the menus of the different canteens at my university.
In my controller, I simply do the following:
if (![self.canteen hasRestaurants]) {
[self.canteen loadRestaurantsWithCallback:^{
[self.tableView reloadData];
}];
}
It works like a charm. What do you think about this solution?
Update: June, 24th 2011 16:30 CET
There is a fourth solution to this problem and it's probably the way to go even if it involves more code than the GCD approach. Here is what I came up with:
Use NSURLConnection to do asynchronous downloading.
Have your model respond to the callbacks sent by the NSURLConnection instance.
Use Key-Value Coding and Key-Value Observing.
In your controller, you simply have to do the following:
[self.model addObserver:self forKeyPath:#"method-name" options:0 context:NULL];
and
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
[self.tableView reloadData];
}
The best way to use the NSNotificationCenter and create a local notification type and post it once you get the data.
First register for the notification type DataUpdateNotification .
- (void)viewWillAppear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(receiveDataNotification:)
name:#"DataUpdateNotification"
object:nil];
...............
}
Implement receiveDataNotification: to handle DataUpdateNotification type notification.
- (void) receiveDataNotification:(NSNotification *) notification
{
if ([[notification name] isEqualToString:#"DataUpdateNotification"])
{
NSLog (#"Successfully received the Data Update notification!");
}
}
Remove the notification from your object instance when your controller is disappeared.
- (void)viewWillDisappear:(BOOL)animated
{
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
Now Post the notification from any part of your application ..
- (void) DataUpdated
{
[[NSNotificationCenter defaultCenter] postNotificationName:#"DataUpdateNotification" object:self];
}
My suggestion is using asynchronous communication instead of multi threading (GCD). It is much easier to deal with and it does what you need.
The idea is:
you execute the web request async, so you don't block;
when issuing the request asynchronously, you register a "callback" with it;
when the data is there, the underlying communication infrastructure calls the "callback" so that you know data is there;
the callback is typically a delegate of some of your classes (even you controller will do), and its main duty is updating your model, and issuing a reload of the table view.
It is a very simple and clean paradigm to use.
So, you can investigate the async possibilities offered by NSURLConnection/NSURLRequest, or (strongly encouraged) have a look at ASIHTTRequest, which will make your like much easier.
How about implementing a delegate for your processing class and using the delegate method to inform the caller.
Its pretty simple and easy to get hold off and works perfectly in such async scenarios.
Define delegate protocol and create a object for the class. Implement the delegate methods in the calling object class and when action is complete call the delegate.
I'd be happy to write some code here if you need.
if you are using threads.. you can use
[self performSelectorOnMainThread: #selector ( // ) waitUntilDone:YES]
This will make sure that your process waits until another process gets Done.
I have a similar situation and I use NSFetchedResultsController to solve it. The table is updated with the fetched results controller, and a client runs asynchronously. When data is received I create a model and write it using CoreData, at which time it is automatically detected by the fetched results controller for an insert, update, or delete into the table.
I have an NSAutoreleasePool thread that is designed to pull information down from a web service, i have the web service code working nicely and i can trigger the thread to start in a view controller without any trouble, in fact its working quite nicely.
I want to:
move the thread instantiation to the appDelegate - easy!
have it run periodically and somehow tell the viewcontrollers under it (5 - 10) if new information is downloaded
have the capacity to manually execute the thread outside of the scheduler
I can fire up a method on the appdelegate using performSelectorOnMainThread but how i can get my child view controllers to "subscribe" to a method on the appdelegate?
Using NSNotificationCenter you can post well, notifications :D
That way without the appDelegate nowing the other classes the other classes can "subscribe" to the notifications they need.
Also, i would keep the thread alive, spawning a new thread everytime is costly, ofc only if it is spawned often. I would recommend using GCD ( iOS 4+ )
Here's what you do:
From the class sending the message, post a notification like :
[[NSNotificationCenter defaultCenter] postNotificationName: #"YOUR_NOTIFICATION_NAME" object: anyobjectyouwanttosendalong(can be nil)];
In the view controllers where you want to be notified of the notification when posted:
In the viewDidLoad do:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(METHOD_YOU_WANT_TO_INVOKE_ON_NOTIFICATION_RECEIVED) name:#"YOUR_NOTIFICATION_NAME" object:sameasbefore/nil];
Important! Don't forget this in your viewDidUnload():
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"YOUR_NOTIFICATION_NAME" object:sameasbefore/nil];
I'm not very sure about the object associated with notifications but you can look that up here
NOTE: When it's only one object notifying another one, you're better off using protocols :) But in this case since there are multiple view controllers listening, use notifications
Use NSNotificationCenter to send events that your view controllers are observing?
I've got an app design question that I'm hoping someone can help with.
Let's take a very simple setup: Core Data app for displaying news items from a server.
Main thread / UI has a managed object context that's used by all the view controllers to display the data.
An NSOperation runs in the background checking the server, with it's own context, on the same persistent store.
I want to merge the changes in the background context so I use NSManagedObjectContextObjectsDidChangeNotification.
According to the Apple docs:
Several system frameworks use Core Data internally. If you register to receive these notifications from all contexts (by passing nil as the object parameter to an addObserver… method), then you may receive unexpected notifications that are difficult to handle.
So, I want to filter my notifications merged in the main thread MOC to just those changes coming from the background operation MOC.
What's the cleanest way to get/maintain a reference to the background operation MOC so that I have something to plug into the addObserver method and the notifications are properly filtered? I can think of a lot of ways that involve a lot of coupling but they all seem like a hack.
Any suggestions or ideas? How are others handling this?
Here's how it works in my app:
// should be executed on a background thread
- (void)saveWorkerContext {
if ([_workerContext hasChanges]) {
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(workerContextDidSave:)
name:NSManagedObjectContextDidSaveNotification object:_workerContext];
NSError *error;
if (![_workerContext save:&error]) {
NSAssert(NO, #"Error on save: %#", error);
}
[nc removeObserver:self name:NSManagedObjectContextDidSaveNotification object:_workerContext];
}
}
- (void)workerContextDidSave:(NSNotification *)notification {
if (_mainContext) {
[_mainContext performSelectorOnMainThread:#selector(mergeChangesFromContextDidSaveNotification:)
withObject:notification waitUntilDone:NO];
}
}
--- Revised Answer ---
Using NSFectchedResultsController appears to be your best bet. It will inform it's delegate when changes to it's MOC effect it's results. This negates the need for your view controller to know about or directly observe events from your background MOC.
Here's the pattern I'd use with NSOperation worker processes.
Store the background MOC in a NSOperationQueue subclass with a maxConcurrentOperationCount of 1. This ensures the operations will occur serially.
Subclass NSOperationQueue
Add a property for the background MOC
Implement the NSOperationQueue's MOC getter to lazily create the background MOC from the Persistent store and register the class responsible for the merging the context with the background MOC did save notification (Typically your AppDelegate or singleton)
Unregister observations for the class and clean up the background MOC in dealoc
Create your NSOperationQueue subclass.
Before adding operations, provision them with the background MOC from the queue's background MOC property. When scheduled, your operation will perform work with the background MOC and save.
Perform the merge when the did save notification comes in on the observing class. After the merge, each fetched results controller using the foreground MOC will notify it's delegate when any changes had an impact on it results. This includes additions or deletions from the background MOC merge.
I am not sure I fully understand your question: if you have just one background thread associated to one particular MOC you want to track, then there is nothing special to do:use a property to maintain a reference to the MOC. You handle this as usual, as shown in the following code snippet.
// create a new MOC
self.backgroundMOC = ...;
// register to receive notifications
[[NSNotificationCenter defaultCenter] addObserver:self
selselector:#selector(contextDidSave:)
name:NSManagedObjectContextDidSaveNotification
object:self.backgroundMOC];
// pass backgroundMOC to your background thread
// and handle notifications here
- (void)contextDidSave:(NSNotification *)notification
{
NSManagedObjectContext *MOC = (NSManagedObjectContext *) [notification object];
if([MOC isEqual:self.backgroundMOC])
[managedObjectContext mergeChangesFromContextDidSaveNotification:notification];
}
If you don't want to have any communication between the threads/operations that hold the context, then the only way to identify if a context generating a notification belongs to you would be to check its persistent store url.
Only your context would have your store URLs. The API URLs will have system stores or in-memory stores.
Normally, of course, you do communicate between processes and can just pass the object references for identification purposes.
Are events posted by NSNotificationCenter postNotificationName processed before UI updating events?
I need to know because otherwise my current program will crash in some rare cases.
Model code:
- (void)searchFinishedWithResults:(Results *)results {
self.results = results;
// If some table cells are loaded NOW, before notication is processed, we might crash!
[[NSNotificationCenter defaultCenter]
postNotificationName:SearchResultArrived object:nil];
}
When processing the notication, I will run UITableView reloadData.
However, consider if before processing the notication, UI has to be updated. In this case -tableView:cellForRowAtIndexPath:indexPath will be called, but results object has changed, it will fetch old data.
The notifications are dispatched exactly when you call postNotification: or postNotificationName:object:, in a synchronous fashion, one observer after the other (in no particular order). In the case you show, they would be sent exactly after you assign the variable "results" and before the method ends.
Directly from Apple's documentation on NSNotificationCenter:
A notification center delivers
notifications to observers
synchronously. In other words, the
postNotification: methods do not
return until all observers have
received and processed the
notification.
To send notifications asynchronously
use NSNotificationQueue.
As an aside, I think you need to rethink your design. It sounds like you don't have sufficient separation between the view and the model.
Your data model should know what is and is not old data and should only return current data to the tableViewController. The data model should have complete control over the integrity of the data and it shouldn't be possible to force it to return the wrong data. It definitely should be impossible that the app will crash owing to such forcing.