I'm adding buttons to a UITableViewHeader, but getting console messages that observers were not removed before deallocation:
An instance 0x4b4750 of class UIButton was deallocated while key value
observers were still registered with it
That's understandable, so I'm trying to remove them but unsure of the best way to go about this. The only thing which springs to mind is to add them all to an array, then in dealloc, loop through them all and remove the class which created them as an observer. I'm not entirely sure which parameters to pass into [[NSNotificationCenter defaultCenter] removeObserver though. There are are three different buttons in each header view, each firing a different callback. Does this mean I'd need three arrays, on for each type of action called, then use removeTarget?
From the docs:
Important The notification center does not retain its observers,
therefore, you must ensure that you unregister observers (using
removeObserver: or removeObserver:name:object:) before they are
deallocated. (If you don't, you will generate a runtime error if the
center sends a message to a freed object.)
If you have subclassed the buttons, then you could post a message to all observers that the observable UIButton is about to go kaput.
[[NSNotificationCenter defaultCenter] postNotificationName:#"UIButton_dealloc" object:self];
Or, in the class that allocates the buttons, once the buttons will be removed you can:
[[NSNotificationCenter defaultCenter] postNotificationName:#"UIButton_dealloc" object:theButton];
And the observer objects, in both cases, will do this:
// The special event
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(doSomething:) name:#"UIButton_event" object:theButton];
// The dealloc
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(stopObserving:) name:#"UIButton_dealloc" object:theButton];
....
-(void) stopObserving:(NSNotification*)notif {
if ([name isEqualToString:#"UIButton_dealloc"]) {
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"UIButton_event" object:object];
}
}
However, this is a somewhat convoluted example in the case of a UIButton, but can be useful for other cases.
Related
A weird issue occurs after a delegate method of my static lib is fired. first of all, the project has a sub project which is a static library (xcode 4.6 ios 6.x). The static lib fires its own delegates according to the event.
the App implements the delegate method of the static lib. in the implementation i use the following to access the UI elements and trigger other events. Didgetnotified is the delegate method of the lib.
- (void)didGetNotified
{
dispatch_async(dispatch_get_main_queue(), ^{
[self parseData];
NSNotificationCenter *notifyCenter = [NSNotificationCenter defaultCenter];
[notifyCenter addObserver:self
selector:#selector(updateUI)
name:#"updateUIN"
object:nil];
});
}
-(void) parseData {
//parse data and its ready now and send notification
[[NSNotificationCenter defaultCenter] postNotificationName:#"updateUIN" object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"updateUIN" object:nil];
}
-(void) updateUI {
//this method gets fired twice mostly
}
the problem is that the updateUI gets called twice. i cant see what i'm doing wrong. is it something with the threading? the static lib delegate is not on the main thread. but i use the dispatch on the main thread. can some one please explain?
thank in advance.
after intensive debugging i have found that the adding oberserver was actually happened twice. the solution was to remove the oberserver before adding it in case of WIFI disconnects and the date stream goes thru 3G and that case my delegate was fired twice and registered oberver 2 times.
[[NSNotificationCenter defaultCenter] removeObserver:self name:#"updateUIN" object:self];
NSNotificationCenter *notifyCenter = [NSNotificationCenter defaultCenter];
[notifyCenter addObserver:self
selector:#selector(updateUI)
name:#"updateUIN"
object:self];
I want to cancel url connection when applicationDidEnterBackground is called. But I don't know how to save a connection in a whole scope of application. I created a few url connections in other view controllers, but I'd like to cancel them in AppDelegate. How can I do this?
You may add observer to UIApplicationDidEnterBackgroundNotification in your class which create the connection. Try this:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(enterBackground) name:UIApplicationDidEnterBackgroundNotification object:nil];
remember call this
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidEnterBackgroundNotification object:nil];
in your class's dealloc
I believe your NSURLConnections will be killed when your app enters the background.
If you want to keep track of all NSURLConnections, you need to keep track of them yourself by adding them to a NSSet, NSArray, or some other datastructure, and looping through them closing them. You could also have a class that inherits or composes a NSURLConnection. This datastructure would handle adding itself to a queue, and you could clean up when the app exits.
It is better to stop the connections before app becomes inactive instead of applicationDidEnterBackground.
Answer is use notifications:
-(void)init
{
//
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(closeConnection) name:UIApplicationWillResignActiveNotification object:nil];
//
}
-(void)closeConnections
{
// [urlConnection cancel];
}
In my Class X I post a Notification like this:
[[NSNotificationCenter defaultCenter] addObserver:viewController
selector:#selector(doThis:)
name:#"myNotification"
object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"myNotification" object:nil];
In my Class Y I recieve it like this:
- (void) doThis: (NSNotification *) notification {
NSLog(#"It works.");
[uiTextView resignFirstResponder]; }
The console shows the NSLog message but my UITextView does not hide its keyboard.
(In e.g. viewDidLoad the resignFirstResponder/becomeFirstResponder works.)
Is there any special thing I need to do?
FWIW, in most, but not all, cases, observers should be added and removed by the observer itself, not by a separate object. (What happens if the observer goes away before the separate object, and fails to have the observer properly removed? Or vice-versa? It makes it all too easy to either leak observers or crash on notifications to deallocated objects.)
Anyhow, first thing's first: have you verified that uiTextView is not nil and points at the first responder? I rather suspect that uiTextView is not what you think it is.
As Conrad says, observers should be added and removed by themselves...
Use the best practice to define the name of the notifications as static constants like follows:
static NSString *const kMyNotification = #"myNotification";
Why? because there is a risk that both #"myNotification" might be two different objects and then the notificationName is different and you won't receive the notification. Since I always declare them as static constants I have never had issues with NSNotifications.
Then use it like this:
To register the observer
[[NSNotificationCenter defaultCenter] addObserver: self
selector: #selector(doThis:)
name: kMyNotification
object: nil];
To post the notification
[[NSNotificationCenter defaultCenter] postNotificationName: kMyNotification
object: nil];
To remove the observer:
[[NSNotificationCenter defaultCenter] removeObserver: self];
I'm trying to get my head around NSNotificationCenter. If I have something like this in my App Delegate:
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(something:)
name:#"something"
object:nil];
-----
-(void)something:(NSNotification *) notification
{
// do something
}
Can I somehow watch this in another view controller? In my case, I'd like to watch it in a view controller with a table, and then reload the table when a notification is received. Is this possible?
Yes you can do it like this:
In class A : post the notification
[[NSNotificationCenter defaultCenter] postNotficationName:#"DataUpdated "object:self];
In class B : register first for the notification, and write a method to handle it.
You give the corresponding selector to the method.
//view did load
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(handleUpdatedData:) name:#"DataUpdated" object:nil];
-(void)handleUpdatedData:(NSNotification *)notification {
NSLog(#"recieved");
}
Yes you can that is the whole purpose of NSNotification, you just have to add the View Controller you want as an observer exactly the same way you did on your App Delegate, and it will receive the notification.
You can find more information here: Notification Programming
Of course it's possible, that's the whole point of notifications. Using addObserver:selector:name:object: is how you register to receive notifications (you should do this in your table view controller), and you can use postNotificationName:object:userInfo: to post a notification from any class.
Read Notofication Programming Topics for more info.
You can register to observe notifications in as many classes as you like. You simply need to "rinse and repeat". Include the code to register as an observer in your view controller (perhaps in viewWillAppear:) and then reload the tableView from your method:
- (void)viewWillAppear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(something:) name:#"something" object:nil];
}
-(void)something:(NSNotification *) notification
{
[self.tableView reloadData];
}
It's also a good idea to de-register the view controller once you no longer need the notifications:
- (void)viewWillDisappear:(BOOL)animated {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
You should just add that as an Observer and give a different selector method if you want that viewController to behave differently when that notification is posted.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:#selector(somethingOtherThing:)
name:#"something"
object:nil];
-(void)somethingOtherThing:(NSNotification *) notification
{
// do something
}
I'm trying to get one instance of using NSNotificationCenter with addObserver and postNotificationName but I can't work out why it won't work.
I have 2 lines to code to add the observer and send the message in 2 different classes
[[NSNotificationCenter defaultCenter]addObserver:self selector:#selector(newEventLoaded:) name:#"Event" object:nil];
and
[[NSNotificationCenter defaultCenter]postNotificationName:#"Event" object:self];
If I set the name to nil it works fine becuase it's just a broadcast, when i try and define a notification name the messages never get through.
All my code makes use of NSNotifications like so:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateView) name:#"ScanCompleted" object:nil];
[[NSNotificationCenter defaultCenter] postNotificationName:#"ScanCompleted" object:nil];
The first one is registering the notification and the second posting of the notification.
Basically it's all to do with the order of execution. If you've executed postNotificationName before addObserver, then this is an easy problem to have. Use breakpoints and step through the code :)
Your first breakpoint should stop here:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(updateView:) name:#"ScanCompleted" object:nil];
Then here:
[[NSNotificationCenter defaultCenter]postNotificationName:#"ScanCompleted" object:self];
Also, make sure the selector has a colon on. Because it's method signature will be:
- (void)updateView:(NSNotification *)notification;
I had the same problem.
The reason is that I called removeObserver method at
- (void)viewDidDisappear:(BOOL)animated{
NSNotificationCenter *notificationCenter = [NSNotificationCenter defaultCenter];
[notificationCenter removeObserver:self];
}
So check whether if you had called removeObserver before postNotification.
Tips: You can search the keyword "removeObserver" to find if you had called this function.
Change this:
[[NSNotificationCenter defaultCenter]postNotificationName:#"Event" object:self];
to this:
[[NSNotificationCenter defaultCenter]postNotificationName:#"Event" object:nil];
If your first notification is registered properly, newEventLoaded should be called.
I had a similar issue and my problem was due to the notification being called on another thread. This solved my problem.
dispatch_async(dispatch_get_main_queue(),^{
[[NSNotificationCenter defaultCenter]postNotificationName:#"Event" object:self];
});
Have you tried any other names but #"Event" and nil? Just to be sure, you could define your event names in one file and include that into both notification registration and sending. For example:
Header file:
extern NSString * const NOTE_myEventName;
Source file:
NSString * const NOTE_myEventName = #"MyEventName";
Registration:
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:#selector(handleMyEvent:)
name:NOTE_myEventName
object:nil];
Notification sending:
[[NSNotificationCenter defaultCenter]
postNotificationName:NOTE_myEventName object:nil];
I successfully fixed my "NSNotification not being sent when postNotificationName: called" crash.
I found the real bug is in notification message handler.
The postNotificationName and addObserver are all right as the first post of this thread.