iOS NSStreamDelegates, Grand Central Dispatch, & NSNotificationCenter

One of the more difficult design patterns I’ve managed to get working in Objective-C while developing an iOS app is one that requires NSStreamDelegates, Grand Central Dispatch (GCD) and NSNotificationCenter.  This design pattern comes into play in the following situation:

  • Application launches
  • Application does some communication with a server in the background
  • When the server communication in the background is complete, the View Controller is updated to display information from the server.

Its a pretty common scenario and I’m sure one that has many answers.  In my case, I needed to use CFReadStreams and CFWriteStreams due to the way the client server communication works.  So if there was a simpler way to do it, I’d love to know.

The design pattern that I used at a high level to accomplish this is as follows:

 

  • Application launches,  displays its first View Controller
  • This first View Controller in viewDidLoad registers an observer with NSNotificationCenter and then kicks off a background request via GCD to do all the client/server communication
  • The background request that launches is an NSStreamDelegate object.  It sends info to the server.
  • Once the background request receives information from the server, it sends a notification to NSNotificationCenter that the client/server communication is complete
  • Finally, the first View Controller gets this notification, and then updates its View with that info.

That’s the high level.  So now lets go through and flesh out the details.

Application Launch and First View Controller

This is a simple example, but its just to get you the basic idea.  This part is standard in most iOS apps.  The thing to note about this is that my FirstViewController is not an NSStreamDelegate.  It does however contain an object that is an NSStreamDelegate.  This is called my Communicator class.

...
#import "Communicator.h"
@interface FirstViewController : UIViewController  {
    UILabel *info
    Communicator *myComm;
    UIActivityIndicatorView *spinner;
}
...
// @property stuff here...

First View Controller viewDidLoad

Here’s where we start to get cool.  viewDidLoad looks something like this:

- (void)viewDidLoad
{
    // Let user know that something is happening.. spin the spinner..
    [spinner startAnimating];
    // register for notification of updates.
    [[NSNotificationCenter  defaultCenter] addObserver:self selector:@selector(updateView) name:@"updateView" object:nil];
    // create a thread and get the server updates.
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        myConnectionInfo = [[Connection alloc] initWithStuff:@"Stuff"
        myComm = [[Communicator alloc] initWithConnection:myConnection];
        [myComm communicate:@"Send this message"]
    });
    [super viewDidLoad];
}

A few things to note here:

1.  We add an observerer to NSNotificationCenter.  This basically says:  If someone sends the updateView message, then I’ll respond to it with my updateView method.  (We’ll show this later)

2.  We dispatch an asynchronous task to GCD.  That’s what this whole dispatch_async business is all about.  This way, the view controller will just display while this task runs in the background.

3.  We instantiate myComm and initiate it with some connection info.  This info might be something you got from the app delegate, some other view controller, or a plist.  That part is left to you to decide.

NSStreamDelegate actions

Our class Communicator is a NSStreamDelegate.  Here’s some of the relevant header info:

...
@interface Communicator : NSObject <NSStreamDelegate> {
    NSInputStream *inputStream;
    NSOutputStream *outputStream;
    NSString *theResult;
    NSString *cmd;
    Connection *myConn;
}
//... @property stuff here... and functions

Nothing big here, just the NSInputStream (the stream we get info from the server) and the NSOutputStream (The stream we write to the server)

As part of the initialization of the Communicator, we have a function called startConnection. This can be called in the init methods. (provided you have all the server communication info: (e.g: user, password, server, port, etc).

Our’s has this:

    self.inputStream = (NSInputStream *)readStream;
    self.outputStream = (NSOutputStream *)writeStream;
    [inputStream setDelegate:self];
    [outputStream setDelegate:self];

    // schedule on the main run loop
    dispatch_async(dispatch_get_main_queue(), ^ {

        [inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
        [outputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

        [inputStream open];
        [outputStream open];
     });

The important part here is what you do before you schedule your input and output streams on the RunLoop.  The RunLoop is the preferred way to work with NSStreamDelegates.  You have to make sure that you put it running back on the main thread’s runloop.  That’s what this whole dispatch_async(dispatch_get_main_queue() bit is about.  If you leave this out, then you’ll never get any connections going on.

Once you open the connections and you do your reading and writing in the handleEvent function:

-(void)stream:(NSStream *)aStream handleEvent:(NSStreamEvent)eventCode {
// clever code here.
}

Then once you finally get the data from the input stream you send a notification.  This is done for us in the

case NSStreamEventEndEncountered:

we run:

[[NSNotificationCenter defaultCenter] postNotificationName:@"updateView" object:nil];

Before we do that, however, we set our theResult NSString to the value we received.

Updating View in First View Controller

Back in the FirstViewController, we have a function updateView:

- (void)updateView {
    //NSLog(@"Got the update");
    dispatch_async(dispatch_get_main_queue(), ^{
        info.text = myComm.theResult;
        [spinner stopAnimating];
        spinner.hidden = TRUE;
    });
}

Notice here that once again we need to make sure it goes back on the main thread before we update the view. Then, since our Communicator belongs to our ViewController, we can get the result of the client/server communication through the getter and set the UILabel to that result.

Summary

As mentioned before, there are probably lots of ways to do this and some are probably better than what I’ve mentioned here.  However, this was pretty tricky to me to figure out and once I did, I thought it was worth sharing as I couldn’t find any holistic info on it via any search engines.

I’d be curious to know if this helps anybody.  Thanks for reading.

  • http://twitter.com/KevBahr Kevin Bahr

    Thanks for the post. Trying to get something like this working, but with a sqlite db that requests are being made to.

  • SnowDev

    Thanks! Saved my day :)

  • http://www.facebook.com/kalyfabdalla Kalyf Abdalla

    how do i remove the runloop when server is closing?

  • zhouxiang

    thanks. but i just new to objective c.can you send me this code example?my email is:344819350@qq.comnthanks again.

  • zhouxiang

    hi,i follow your way,but why the stream still block the ui ?is it because dispatch_async(dispatch_get_main_queue() to run loop?thank you

  • Pingback: chanel リボン 財布