Friday, December 9, 2011

NSNotificationCenter Thread Issue

I'm working on a app that uses the UITableView:

I will fetch data from a web server, parse it, and display information in the table.

Code looks like the following (before):

-(void)fetchAndParse
{
    // Fetch and Parse
    
    [[NSNotificationCenter defaultCenter] postNotificationName:kDataRetrieved object:nil userInfo:userInfo];
}

- (void)dataRetrivalSucceeded:(NSNotification *)notification
{
    // Save data

    [myTable reloadData];
}

It was working fine until I decided to put parsing into a separate thread (using NSOperation), data sometimes shows up and sometimes doesn't. The UITableView will load data as usual, or remain blank until I drag it.

Code after:

-(void)fetch
{
    // Fetch

    queue = [NSOperationQueue new];

    NSInvocationOperation *summaryOperation = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(parseXml:) object:data];

    [queue addOperation:parseOperation];

    [summaryOperation release];
}

-(void)parseXml
{
    // Parse

    [[NSNotificationCenter defaultCenter] postNotificationName:kDataRetrieved object:nil userInfo:userInfo];
}

- (void)dataRetrivalSucceeded:(NSNotification *)notification
{
    // Save data

    [myTable reloadData];
}

And that's when I found out the notification is delivered in the thread in which the notification was posted (Link).
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 what I did to make it work is the following:

-(void)fetch
{
    // Fetch

    queue = [NSOperationQueue new];
    NSInvocationOperation *summaryOperation = [[NSInvocationOperation allocinitWithTarget:self selector:@selector(parseXml:) object:data];

    [queue addOperation:parseOperation];

    [summaryOperation release];
}

-(void)parseXml
{
    // Parse

    [[NSNotificationCenter defaultCenter] postNotificationName:kDataRetrieved object:nil userInfo:userInfo];
}

- (void)dataRetrivalSucceeded:(NSNotification *)notification
{
    [self performSelectorOnMainThread:@selector(updateTable:) withObject:notification waitUntilDone:NO];
}

- (void)updateTable:(NSNotification *)notification
{
    // Save data

    [myTable reloadData];
}

The reason data shows after I dragged it is because it calls [myTable reloadData]; on main thread, while I wasn't.