Does the NSNotification retain the object? - iphone

My question is in regards the object that gets added to a -postNotificationName:object: userInfo: method.
Does the NSNotification retain the object ?
(in a similar fashion to NSMutableDictionary or Array) ... meaning I can release the object after posting the notification
Below is a code snippet to help describe my question ... is it valid to release the object. A link to Apple documentation could be really helpful.
NSMutableDictionary *teamDictCopy = [self.teamDict mutableCopy];
[teamDictCopy setObject:[NSNumber numberWithInt:self.scrollViewIndex] forKey:#"imageIndex"];
if([self.statusButton.title isEqualToString:#"Completed"]){
[[NSNotificationCenter defaultCenter] postNotificationName:#"UnComplete" object:teamDictCopy userInfo:nil];
}
[teamDictCopy release];

"Does the NSNotification retain the
object ? (in a similar fashion to
NSMutableDictionary or Array) ...
meaning I can release the object after
posting the notification"
I'm not sure if the object and userInfo parameters are retained by that method or not, but in practice, it shouldn't really matter.
I think you may be envisioning that NSNotificationCenter is creating these notifications and broadcasting them in an asynchronous manner, but that isn't the case. As stated in the documentation for NSNotificationCenter (see NSNotificationCenter Class Reference), notifications are posted synchronously:
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. In a
multithreaded application,
notifications are always delivered in
the thread in which the notification
was posted, which may not be the same
thread in which an observer registered
itself.
So, in your code, the notification center creates the notification and then broadcasts it through the default center. Any objects which have registered for this combination of notification name and object will receive the notification and then perform the selector they specified when they registered for that notification. Afterwards, the control returns to the class that posted the notification.
In other words, by the time your code gets to the [teamDictCopy release] line, the teamDictCopy will already have been "used" by all of the interested parties. So, there shouldn't be any danger in releasing it.
Just a note on conventions. Generally, the object: parameter is meant to be the object that is posting the notification, and the userInfo: parameter is meant for an NSDictionary of extra information. So, normally, you would handle the notification like follows:
NSMutableDictionary *teamDictCopy = [self.teamDict mutableCopy];
[teamDictCopy setObject:
[NSNumber numberWithInt:self.scrollViewIndex] forKey:#"imageIndex"];
if([self.statusButton.title isEqualToString:#"Completed"]){
[[NSNotificationCenter defaultCenter] postNotificationName:#"UnComplete"
object:self userInfo:teamDictCopy];
}
[teamDictCopy release];

yes - you can release the object once it's been set as the notification's object.
you can also subclass.
as far as a specific document/statement: i don't remember one, specifically.
this is however the basis of objects, their instance variables, and distributed communications and signaling when types are identified as being an object.
i've written a test for you, so you can be assured of this. the use cases of notifications would be few if the object were not retained. just add a breakpoint where instructed, then run with breakpoints enabled. enjoy!
#import <Foundation/Foundation.h>
#interface MONObject : NSObject
#end
#implementation MONObject
- (id)retain {
return self; /* << add breakpoint here */
}
/* needed to counter retain override
(although all MONObjects will leak in this example)
*/
- (void)release {
}
#end
int main(int argc, const char* argv[]) {
NSAutoreleasePool * pool = [NSAutoreleasePool new];
NSString * name = #"UnComplete";
MONObject * obj = [MONObject new];
[[NSNotificationCenter defaultCenter] postNotificationName:name object:obj userInfo:nil];
[obj release], obj = 0;
[pool drain];
return 0;
}

Related

iPhone - sending a message "in the air" for any listener object

Is there a way onto the iPhone for an object to send a message without a specific receiver object, and into another object, listen to such messages, that could come with objects (parameters), and do what is needed ?
I searched around NSNotification but I don't see what I should do.
Objects that want to be notified need to register to receive notifications with the notification center. Thereafter, when a notification is posted to the notification center, the notification center will check it against all the registered filters, and the corresponding action will be taken for each matching filter.
A "filter" in this case is the pair of (notification name, notification object). A nil object in the filter is equivalent to any object (the notification object is ignored in matching). The name is required.
Example:
/* Subscribe to be sent -noteThis:
* whenever a notification named #"NotificationName" is posted to the center
* with any (or no) object. */
NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
[nc addObserver:self selector:#selector(noteThis:)
name:#"NotificationName"
object:nil];
/* Post a notification. */
[nc postNotificationName:#"NotificationName" object:self userInfo:someDict];
/* Handle a notification. */
- (void)noteThis:(NSNotification *)note
{
id object = [note object];
NSDictionary *userInfo = [note userInfo];
/* take some action */
}
There is a more modern API using queues and blocks, but I find the old API easier to illustrate and explain.
Basically, you post a notification (NSNotification) to the shared class, NSNotificationCenter. Here's an example:
#define kNotificationCenter [NSNotificationCenter defaultCenter]
#define kNotificationToSend #"a notification name as a string"
//... Post the notification
[kDefaultCenter postNotificationNamed:knotificationToSend withObject:nil];
Any class that wants to listen, adds itself as an observer to the notifcation center. You must remove the observer as well.
[kNotificationCenter addObserver:self selector:#selector(methodToHandleNotification) object:nil];
//... Usually in the dealloc or willDisappear method:
[kNotificationCenter removeObserver:self];
You can do more with the notification center. See the NSNotificationCenter documentation fr complete reference.
I think NSNotification is the message object itself, to send to listen to what is sent try NSNotificationCenter. It has a singleton object, so to send the message:
NSNotification *notificationObj;
NSNotificationCenter *center = [NSNotificationCenter defaultCenter];
[center postNotification:notificationObj];
And the other class listen to with:
[[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(method:) object:nil];
Make sure that class has method: method. You can have a single parameter, which is an NSNotification object that is sent earlier. The NSNotification object has [notificationObj object which you can get as a piece of data sent by the sender class. Alternatively, you might use [notificationObj userInfo] if you want it to be more structured.
you can initialise notificationObj and tailor it with the message that you'd want. More information on NSNotificationCenter, you can find it
http://developer.apple.com/library/ios/#documentation/Cocoa/Reference/Foundation/Classes/NSNotificationCenter_Class/Reference/Reference.html#//apple_ref/occ/cl/NSNotificationCenter
or for more information about NSNotification itself
http://developer.apple.com/library/mac/#documentation/Cocoa/Reference/Foundation/Classes/NSNotification_Class/Reference/Reference.html

Simulate asynchronous function call

I have an asset manager that needs to notify the owner it's assets are ready. I'm sending a token back for the consumer to listen to listen for a notification to avoid tighter coupling. The issue is when the assets are already loaded I need to call the loadComplete after a delay. What's the best way to do this in objective-c?
Asset Manager
-(tokenString*) loadAssetPath:(NSString*) asset {
//start asynchronous load
//or if assets ready send complete <-- issue
return nonceToken;
}
-(void)loadComplete {
[[NSNotificationCenter defaultCenter]
postNotificationName:tokenString object:self];
}
Consumer
NSString* token;
-(void) loadSomething {
if(token)
[self removeListener];
token = [[AssetManager sharedManager]
loadAssetPath:#"http://server.dev/myLargeImage.png"];
[[NSNotificationCenter defaultCenter]
addObserver:[AssetManager sharedManager]
selector:#selector(assetLoaded:) name:token];
}
-(void)assetLoader:(NSNotifcation*)aNotification {
[self removeListener];
//continue on with stuffing stuff
}
Use NSObject's performSelector function which allows it to be called after a delay.
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay
You can even use a form of this function to run it on another thread, which is useful to not blocking the main thread when doing lengthy operations (just don't muck with the UI objects in this thread).
#DavidNeiss is correct about performSelector:withObject:afterDelay:, but you almost certainly don't want an actual time delay here. At most you want to perform your selector on the next event loop, just so things are consistent for the listener. So you should make the delay 0. This differs from the normal performSelect:withObject: which will immediately perform the selector synchronously.
-(tokenString*) loadAssetPath:(NSString*) asset {
//start asynchronous load
if (<load is actually complete>) {
// -loadComplete will execute on the next event loop
[self performSelector:#selector(loadComplete) withObject:nil afterDelay:0];
}
return nonceToken;
}

Removing observers for an NSOperation

I have a view which loads data via an NSOperation within an NSOperationQueue. I want to allow users to leave this view before the operation has completed. My problem is that I can't seem to consistently do this without crashing. Here is my code to start the operation:
NSOperationQueue* tmpQueue = [[NSOperationQueue alloc] init];
self.queue = tmpQueue;
[tmpQueue release];
SportsLoadOperation* loadOperation = [[SportsLoadOperation alloc] init];
[loadOperation addObserver:self forKeyPath:#"isFinished" options:0 context:NULL];
[self.queue addOperation:loadOperation];
[loadOperation release];
If I leave the view while the operation is still executing, I often get this error:
[SportsViewController retain]: message sent to deallocated instance 0x38b5a0
If I try to remove the observers so that this doesn't occur, like this:
-(void)viewWillDisappear:(BOOL)animated {
if (self.isLoadingData) {
for (NSOperation *operation in [self.queue operations]) {
if([operation isExecuting]) {
[operation cancel];
[operation removeObserver:self forKeyPath:#"isFinished"];
}
}
}
[super viewWillDisappear:animated];
}
Then I sometimes get this error:
Terminating app due to uncaught exception 'NSRangeException', reason:
'Cannot remove an observer <SportsViewController 0x661c730> for the key path "isFinished" from <SportsLoadOperation 0x66201a0> because it is not registered as an observer.'
How can I avoid these problems?
The 2nd error message says it all.
Have you tried to not removeObserver after [operation cancel] and see what happens then?
Have you tried to first removeObserver and only then cancel the operation?
These might help to narrow down the conditions that trigger the error. Also, you might want to add log output to the code to see when it actually executes.
And, like freespace's answer says, adding & removing observers is best done in the construction / destruction methods of the observed instances. This generally yields more stable code.
Looks like you have an instance of SportsLoadOperation that doesn't have SportsViewController as an observer. Are you inserting SportsLoadOperation anywhere else in your code? If this is the case, consider writing an initWithObserver method for SportsLoadOperaion that will do the observing automatically. This avoids errors caused by forgetting to set the observer on isFinished.
Also, it is probably better to do the removal of observer in dealloc then in viewWillDisappear because viewWillDisappear is called in many circumstances, e.g. when displaying a modal view controller. Thus you might be prematurely stopping your operations.
Edit
Instead of checking against [operation isExecuting] check against [operation isCancelled]. This is because [operation cancel] doesn't stop an operation - it can and will continue executing if you don't actually check for isCancelled in your main method. This means that if viewWillDisappear is called twice, you could end up attempting to call removeObserver twice on the same instance of SportsLoadOperation, with the second attempt failing.
Add some debugging output statements in the following places:
when you create a SportsLoadOperation instance and insert it into the queueu
when you are cancelling a SportsLoadOperation instance and removing from it observers.
I ended up solving this by overriding the observed operation's addObserver and removeObserver methods, to keep track of observers so I could remove them in the [cancel] method.
All I have to do now is call the operation queue to cancel all operations before I dismiss the controller that was observing the operations.
Below, _observers is an NSMutableDictionary.
- (void)addObserver:(NSObject*)observer
forKeyPath:(NSString*)keyPath
options:(NSKeyValueObservingOptions)options context:(void*)context
{
[super addObserver:observer forKeyPath:keyPath options:options context:context];
[_observers setValue:observer forKey:keyPath];
}
- (void)removeObserver:(NSObject*)observer forKeyPath:(NSString*)keyPath
{
[super removeObserver:observer forKeyPath:keyPath];
[_observers removeObjectForKey:keyPath];
}
- (void)cancel
{
[super cancel];
for(id key in _observers)
{
id object = [_observers valueForKey:key];
[super removeObserver:object forKeyPath:key];
}
[_observers removeAllObjects];
}

Delegate functions not being called

Long time lurker, first time poster.
I'm making a ServerConnection module to make it a whole lot modular and easier but am having trouble getting the delegate called. I've seen a few more questions like this but none of the answers fixed my problem.
ServerConnection is set up as a protocol. So a ServerConnection object is created in Login.m which makes the call to the server and then add delegate methods in Login to handle if there's an error or if it's done, these are called by ServerConnection like below.
- (void)connectionDidFinishLoading:(NSURLConnection *)connection {
if( [self.delegate respondsToSelector:#selector(connectionDidFinish:)]) {
NSLog(#"DOES RESPOND");
[self.delegate connectionDidFinish:self];
} else {
NSLog(#"DOES NOT RESPOND");
}
self.connection = nil;
self.receivedData = nil;
}
It always "does not respond". I've tried the CFRunLoop trick (below) but it still doesn't work.
- (IBAction)processLogin:(id)sender {
// Hide the keyboard
[sender resignFirstResponder];
// Start new thread
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
// Acutally call the server
[self authenticate];
// Prevent the thread from exploding before we've got the data
CFRunLoopRun();
// End thread
[pool release];
}
I copied the Apple URLCache demo pretty heavily and have compared them both many times but can't find any discrepancies.
Any help would be greatly appreciated.
Here are the questions to ask:
Does your delegate respond to connectionDidFinishLoading:?
Does the signature match, i.e. it takes another object?
Is the delegate set at all or is it nil? (Check this in that very method)
If any of those are "NO", you will see "doesn't respond"... and all equally likely to happen in your application, but all are easy to figure out.

iPhone sdk pass messages between view controllers

I was wondering what is the best practice for an application flow in iPhone development.
How do you pass messages between ViewControllers?
Do you use singletons? pass it between views or do you have a main controller for the application that manage the flow?
Thanks.
I use NSNotificationCenter, which is fantastic for this kind of work. Think of it as an easy way to broadcast messages.
Each ViewController you want to receive the message informs the default NSNotificationCenter that it wants to listen for your message, and when you send it, the delegate in every attached listener is run. For example,
ViewController.m
NSNotificationCenter *note = [NSNotificationCenter defaultCenter];
[note addObserver:self selector:#selector(eventDidFire:) name:#"ILikeTurtlesEvent" object:nil];
/* ... */
- (void) eventDidFire:(NSNotification *)note {
id obj = [note object];
NSLog(#"First one got %#", obj);
}
ViewControllerB.m
NSNotificationCenter *note = [NSNotificationCenter defaultCenter];
[note addObserver:self selector:#selector(awesomeSauce:) name:#"ILikeTurtlesEvent" object:nil];
[note postNotificationName:#"ILikeTurtlesEvent" object:#"StackOverflow"];
/* ... */
- (void) awesomeSauce:(NSNotification *)note {
id obj = [note object];
NSLog(#"Second one got %#", obj);
}
Would produce (in either order depending on which ViewController registers first):
First one got StackOverflow
Second one got StackOverflow
The NSNotification class is a bit heavyweight, but fits the usage you describe. The way it works is that your various NSViewControllers register with an NSNotificationCenter to receive the events they're interested in
Cocoa Touch handles the routing, including providing a singleton-like "default notification center" for you. See Apple's notification guide with more info.