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?
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 want to pause a timer on my game screen when the iPhone is locked etc. My question is what is the best method to notify the current UIView, which the AppDelegate has no direct access to?
1) Your timer should probably not be managed by the view but by the view's controller. The timer itself is not an inherent part of your UI, only the timer's display is. (What happens if you want to have the timer continue after a view is removed, for example?)
2) Any object (view or controller included) can independently listen for the appropriate notification. For example, in your view controller (or view code, if you choose to go that route):
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(pauseTheTimer:)
name:UIApplicationWillResignActiveNotification
object:nil];
Then implement a pauseTheTimer: method that will handle the notification. (Since there is only one UIApplication object, you can use nil for the object, as shown.)
This approach nicely decouples your app delegate from the views and view controllers.
(Oh, don't forget to stop observing when your view is unloaded or deallocated. Failure to do so can and will lead to crashes.)
here i am again:
what i want to do is:
if i press a button, then post a notification. This notification should be cached by 2 instances of the same class.
the problem:
the notification is posted, but it is cached just by one instance.
some code and explanation
i have 1 tab bar controller
i have 3 tabs ( 3 different views -xib files-)
2 views references the same (view controller) class (so, there are 2 instances of the same class, let's say class A)
the other tab/view references another class (class B)
if i press a button of one view, a method of class B is fired and, at some point it does this:
[[NSNotificationCenter defaultCenter] postNotificationName:#"update" object:nil ];
in the viewDidLoad method of class A I have this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateAll:) name:#"update" object:nil];
also, i have defined the updateAll function as:
- (void) updateAll: (NSNotification *) notification {
NSLog(#"called");
}
As i said before, just one time the updateAll method is fired.
questions
why?
how to fix it?
thanks for reading!
It is possible that your view is not loaded yet, because you are using tab bar controller. The view that is not yet visible is not loaded, so it is likely that your viewDidLoad will get called only for one instance. I recommend you debug it and make sure your addObserver call is really get executed twice, not once.
This won't work at all. You're posting a notification with a name #"updated" but you've attached observers for name #"update". You should be getting no notifications at all.
The way of posting notification is synchronous. I think another object doesn't register as an observer yet, so it cannot receive the notification posted.
And, if the notification is posted on another thread, it will be obtained by the observer on the same thread.
This isn't a question so much as a warning to others to save them some time.
NSNotificationCenter on iOS 3/iPhone OS 3 (I'm assuming also Mac OS X and iOS 4) has the following behavior:
If you register yourself multiple times for the exact specific notification, NSNotificationCenter will NOT recognize the redundancy and instead will fire off as many notifications to you as you've registered an observation for.
This is almost never the behavior you want to see and is almost always accidental.
Example:
I want my view controller to receive notifications from a singleton network object when new data comes in:
- (void) viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newDataArrived:)
name:NewDataArrivedNotification
object:[NetworkListener sharedNetworkListener]];
}
but earlier I'd already put the same thing in viewWillAppear:
- (void) viewWillAppear
{
[super viewWillAppear];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newDataArrived:)
name:NewDataArrivedNotification
object:[NetworkListener sharedNetworkListener]];
}
Note that it's exactly the same notification, resolving to the same observer, sender and notification name.
In this case, if I don't remove one of those addObserver calls, I'll get duplicate notifications to my view controller.
In a multi-threaded environment, this is a world of hurt. Trust me.
Just putting this out there in case there are others who run into something like this.
NSNotificationCenter on iOS 3/iPhone OS 3 (I'm assuming also Mac OS X and iOS 4) has the following behavior:
If you register yourself multiple times for the exact specific notification, NSNotificationCenter will NOT recognize the redundancy and instead will fire off as many notifications to you as you've registered an observation for.
This is almost never the behavior you want to see and is almost always accidental.
Example:
I want my view controller to receive notifications from a singleton network object when new data comes in:
- (void) viewDidLoad
{
[super viewDidLoad];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newDataArrived:)
name:NewDataArrivedNotification
object:[NetworkListener sharedNetworkListener]];
}
but earlier I'd already put the same thing in viewWillAppear:
- (void) viewWillAppear
{
[super viewWillAppear];
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(newDataArrived:)
name:NewDataArrivedNotification
object:[NetworkListener sharedNetworkListener]];
}
Note that it's exactly the same notification, resolving to the same observer, sender and notification name.
In this case, if I don't remove one of those addObserver calls, I'll get duplicate notifications to my view controller.
In a multi-threaded environment, this is a world of hurt. Trust me.
Just putting this out there in case there are others who run into something like this.
You should and always clean up your observers.
The easiest way to do it is : [[NSNotificationCenter defaultCenter] removeObserver:self]
viewDidLoad is not a good place to add observers, because this functions may get called multiple times, this happens when viewDidUnload is triggered.
A good place to put your addObservers in viewWillAppear, and removeObservers in viewWillDisappear.
As you said yourself, NSNotificationCenter makes no check for duplicates, which may be annoying for some, but makes sense when concidering the full system behind it.
The same logic applies to adding targets to certain objects, but there is often a key recognition on those.
Thank you for the insight, and for a good, SEO-friendly warning :)
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.