NSNotification usage

From Wikichris
Jump to: navigation, search

Usage

after using NSNotification for some asynchronous HTTP requests. I could notice that it's really easy to make some mistakes which lead to a crash.

Sending a Notification

let's suppose you are using a Class MyHttpFetch. To identify the Notification you will have to define a constant, by convention the name is ObjectName.EvenDescription.Notification

//in MyHttpFetch.h
extern NSString *const MyHttpFetchDataFetchedNotification;
extern NSString *const MyHttpFetchDataErrorNotification;
@interface MyHttpFetch : NSObject {
[...]
//in MyHttpFetch.m
#import "MyHttpFetch.h"

NSString *const MyHttpFetchDataFetchedNotification=@"MyHttpFetchDataFetchedNotification";
NSString *const MyHttpFetchDataErrorNotification=@"MyHttpFetchDataErrorNotification";

@implementation MyHttpFetch
[...]

Once an even must be sent to NSNotification, add this line in your code

[[NSNotificationCenter defaultCenter] postNotificationName:MyHttpFetchDataFetchedNotification object:self];

Receiving a Notification

In a view called MyViewController, I want to run the method dataChanged: when the data is available at last. In ViewDidLoad I had the following line

	[[NSNotificationCenter defaultCenter]
	 addObserver:self selector:@selector(dataChanged:)
	 name:MyHttpFetchDataFetchedNotification object:nil];

NB: MyHttpFetchDataFetchedNotification is know since you imported MyHttpFetch.h

#import "MyHttpFetch.h"

You must then create the method dataChanged:

- (void)dataChanged:(NSNotification *)notification
{
	  //first thing, release the Notification from your observer
	  //if you forget, the next use of it will crash your app
	[[NSNotificationCenter defaultCenter]
	 removeObserver:self
	 name:MyHttpFetchDataFetchedNotification object:nil];
	 //here all your data treatment
}

If the Notification is never received because the user closed the view before, the app will crash so you must remove all the observer in your dealloc() method

- (void)dealloc
{
    [[NSNotificationCenter defaultCenter] removeObserver:self];
    [super dealloc];
}

The Possible Crashes

  1. When a view is closed before the end of the request, it crashes few seconds later when the request is completed
  2. When multiple Notification are expected (DataReceived and DataError) if we forget to remove both the observers it can crash later
  3. When a action is launched a second time, the same inscription as observer crashes

To avoid these issues, follow the advices in the next bloc.

Advice

Generally

It is possible to reduce the probability of crashes by avoiding object:nil. So instead of trying to catch every futur objects' notification, you will be an observer only for the object you certainly created just before.

	fetchedHttp = [[MyHttpFetch alloc]
				   initWithString:@"http://company.com/json.php"];

	[[NSNotificationCenter defaultCenter] 
	 addObserver:self 
	 selector:@selector(dataChanged:) 
	 name: MyHttpFetchDataFetchedNotification object:fetchedHttp];

NB: by using object:nil you catch every notification with this name, so if you run again the same process you could have a crash when the notification center doesn't find the observer

View reloaded a second time

Everything works properly when the View is shown the first time, but a crash occur at the second time.

The 2 different solutions to this problem are the 2 following bloc.

View closed before the end

If you added an observer but never received any notification, you cannot selectively remove it. So you must remove any observer linked to this view when you close it.

Or a notification arriving when the view is gone will crash the application, or the next time you load the view, the addObserver will crash.

The easiest way is to add the following line in -dealloc

- (void)dealloc
{
   [[NSNotificationCenter defaultCenter] removeObserver:self];
   [super dealloc];
}

If you have an observer for Error also

You must remember to unregister both the Observer when one or the other expected Notifications arrives. Otherwise, when you need them again later a crash occurs.

If your Observers were added to call the selectors dataChanged: and dataError: like this

	[[NSNotificationCenter defaultCenter]
	 addObserver:self selector:@selector(dataChanged:)
	 name:ObjectDataChangedNotification object:nil];

	[[NSNotificationCenter defaultCenter]
	 addObserver:self selector:@selector(dataError:)
	 name:ObjectDataErrorNotification object:nil];

in both the methods you must remove both the Observers

- (void)dataChanged:(NSNotification *)notification
{
	[[NSNotificationCenter defaultCenter]
	 removeObserver:self
	 name:ObjectDataChangedNotification object:nil];

	[[NSNotificationCenter defaultCenter]
	 removeObserver:self
	 name:ObjectDataErrorNotification object:nil];

	 //[...]
}

- (void)dataError:(NSNotification *)notification
{
	[[NSNotificationCenter defaultCenter]
	 removeObserver:self
	 name:ObjectDataChangedNotification object:nil];

	[[NSNotificationCenter defaultCenter]
	 removeObserver:self
	 name:ObjectDataErrorNotification object:nil];

	 //[...]
}