Search
Follow
Recent Comments

Entries in networking (5)

Tuesday
Feb072012

Remote Packet Capture for iOS devices

I previously posted about using the Network Link Conditioner to create realistic and “challenging” network conditions when testing iOS apps. In this post I want to highlight another useful network debugging tool which allows you capture network traffic from an iOS device.

Remote Virtual Interfaces

As with the Network Link Conditioner you need to use a host Mac computer to perform remote packet capture of an iOS device. The only other requirement is that the device be connected to the host computer via USB. No jailbreaking or hacking of your device is required to get this to work.

The basic technique is to create an OS X remote virtual network interface that represents the remote network stack of the iOS device. Once you have the virtual interface you can use your favourite network debugging tool such as tcpdump or wireshark to view the network traffic.

The steps to get the virtual network interface up and running are as follows:

1. Plug your iOS device into the USB port of your Mac.

2. Use the Xcode organizer to obtain the UDID of the device (the value you want is named Identifier):

3. The remote virtual interface is created using the rvictl command, using the UDID you obtained in the previous step. The following command needs to be entered in the terminal window:

rvictl -s <UDID>

If you want to capture packets from more devices you can repeat this process with the UDID for each device. You can also use the rvictl command to list the active devices:

rvictl -l

The virtual interfaces are named rvi0, rvi1, rvi2, etc. and like all network interfaces are viewable using the ifconfig command:

ifconfig rvi0
rvi0: flags=3005<UP,DEBUG,LINK0,LINK1> mtu 0

Finally when you are finished you can remove the virtual interface:

rvictl -x <UDID>

Using tcpdump

The easiest way to capture and dump the network traffic is to use the tcpdump command which is included with OS X. The man page for tcpdump has lots of options but if you just want to see the live traffic the following will get you started:

tcpdump -n -i rvi0

To better illustrate the results I will use the Twitter Search app I showed in an earlier post to generate a simple http request and response.

tcpdump -n -t -i rvi0 -q tcp
tcpdump: WARNING: rvi0: That device doesn't support promiscuous mode
(BIOCPROMISC: Operation not supported on socket)
tcpdump: WARNING: rvi0: no IPv4 address assigned
tcpdump: verbose output suppressed, use -v or -vv for full protocol decode
listening on rvi0, link-type RAW (Raw IP), capture size 65535 bytes
IP 192.168.1.66.55101 > 192.168.1.64.51712: tcp 117
IP 192.168.1.64.51712 > 192.168.1.66.55101: tcp 0
IP 192.168.1.64.51712 > 192.168.1.66.55101: tcp 298
IP 192.168.1.66.55101 > 192.168.1.64.51712: tcp 0
IP 192.168.1.66.55324 > 199.59.148.201.80: tcp 0
IP 199.59.148.201.80 > 192.168.1.66.55324: tcp 0
IP 192.168.1.66.55324 > 199.59.148.201.80: tcp 0
IP 192.168.1.66.55324 > 199.59.148.201.80: tcp 269
IP 199.59.148.201.80 > 192.168.1.66.55324: tcp 0
IP 199.59.148.201.80 > 192.168.1.66.55324: tcp 1428
IP 199.59.148.201.80 > 192.168.1.66.55324: tcp 1428
IP 199.59.148.201.80 > 192.168.1.66.55324: tcp 1428 

Note the tcpdump options I am using to cut down some of the noise. The -t option gets rid of the timestamp on each line, -q removes some of the packet header information which is not interesting and finally we specify that we are only interested in TCP/IP packets.

My local IP address is 192.168.1.66 and the IP of the remote Twitter server in this case is 199.59.148.201. The http request starts on line 5 where you can see an outgoing connection to port 80:

IP 192.168.1.66.55324 > 199.59.148.201.80: tcp 0

The following lines show the search results coming back. Of course, this trace is not very interesting as we cannot see the contents. You can add -x to the tcpdump command to see the actual packet contents but even that is not always that informative as you need to know how to decode and interpret the packet data. A quick and dirty way if you know you are dealing with http traffic is to add the -A option to get tcpdump to print the packet data in ASCII:

tcpdump -n -t -i rvi0 -q -A tcp
...
GET /search.json?rpp=100&q=apple HTTP/1.1
Host: search.twitter.com
User-Agent: TwitterSearch/1.0 CFNetwork/548.0.4 Darwin/11.0.0
Accept: */*
Accept-Language: en-us
Accept-Encoding: gzip, deflate
Cookie: k=86.168.77.194.5802087337bc706b
Connection: keep-alive
...
HTTP/1.1 200 OK
Cache-Control: max-age=15, must-revalidate, max-age=300
Expires: Tue, 07 Feb 2012 22:18:05 GMT
Content-Type: application/json;charset=utf-8
Vary: Accept-Encoding
Date: Tue, 07 Feb 2012 22:13:06 GMT
X-Varnish: 682230572
Age: 0
Via: 1.1 varnish
Server: tfe
Content-Encoding: gzip
Content-Length: 12715 

This is a minor improvement in that we can now see the HTTP GET request with the query we are using and see the HTTP response but we still cannot easily drop down into the JSON in the result to see what Twitter is sending back. For that we need to use a more sophisticated tool than tcpdump.

Using Wireshark

Whilst tcpdump is a quick and easy way to see and capture traffic it is not exactly an easy tool to use when you want to figure out what is going on. Wireshark is a much easier tool if you want perform deeper packet inspection or if you just prefer your network debugging tools to have a user interface. Luckily Mac OS X ports are readily available, if you are following along I downloaded and installed version 1.6.5 for OS X 10.6 (Snow Leopard) Intel 64-bit from here.

Once you have Wireshark installed and running you should see a list of available interfaces that it can capture. The one we are interested in is of course our virtual interface rvi0:

Selecting rvi0 switches us to a live capture of the packet data with a lot more information to help us decode and understand what is going on. This can be interesting to watch and see all of the things your iOS device is doing. For the purposes of this example it is useful to apply some filters so we can focus in on the HTTP request traffic. The easiest way to do that is to apply a display filter (Analyze -> Display Filters…). There are a number of pre-defined filter expressions including being able to limit the display to HTTP traffic:

Now if create our request it is immediately obvious what is going on as we can clearly see the HTTP GET request and the JSON response:

The central pane of wireshark allows you to drill down into the contents of each packet allowing us to see the JSON details:

The full packet decode is also available in the lower pane if you need to see the whole packet.

Wrapping Up

I should say that this post is not an attempt to explain everything involved in debugging network communications using tcpdump or wireshark. That is a huge topic and requires some knowledge of the underlying protocols. What I did want to make clear is that the tools you need to capture and analyse live traffic off your iOS device are readily available and take just a few minutes to get setup. It is not something you will (hopefully) need to use every day but it is well worth having it in your toolbox for those occasions when you need to debug network communications.

Monday
Jan302012

Network Link Conditioner

The Network Link Conditioner (NLC) tool is an extremely useful tool introduced with Lion that allows you to test how well an application behaves when subject to various network conditions. Whilst it is an OS X tool that runs on a Mac it can still be used test iOS apps that are running in the iOS Simulator. In this post I will show some examples of how to use the tool to test an iOS app and verify how it well it handles various network error conditions.

Getting Up and Running

With Xcode installed on Lion you should find the NLC tool installed in /Developer/Applications/Utilities/Network Link Conditioner/. It is actually implemented as a System Preferences Pane which you can install with a double-click. The Xcode 4.2 release notes mention a limitation that you need to reboot before the NLC daemon will run or you can manually reload with the following command:

sudo launchctl load /system/library/launchdaemons/com.apple.networklinkconditioner.plist

Once installed you should find the Network Link Conditioner icon in the “Other” section of the System Preferences pane:

The User Interface is pretty simple, consisting of a master on/off switch, a list of pre-defined profiles and a manage profiles button to create your own profiles. Note that the NLC tool applies to the network interface of your Mac so it will impact ALL traffic on your computer when you turn it on. So you probably want to avoid doing anything else that is relying on a network connection whilst you are testing.

Creating Test Profiles

The NLC can be used limit bandwidth, add delays both to normal traffic and to DNS queries and create packet loss. It comes pre-configured with a number of typical network profiles to allow you to simulate common 3G cellular, cable modem, DSL and WiFi connections. For testing purposes I created three rather extreme network conditions as follows:

Slow Net - Very Limited Bandwidth

This profile severely limits the bandwidth by setting both the Downlink and Uplink Bandwidth to 32kbps. A 100ms delay is also added to packets in each direction and for good measure there is also a 200ms delay to DNS queries.

This configuration is useful for slowing down all network communication so that you can really see what your app is doing. This helps flush out any points in your code where you have network code blocking the main thread. It can also help you see any progress bars or temporary spinning activity indicators that might otherwise only be displayed for a split second when testing on a fast network.

Nasty Net - High Packet Loss

This network profile increases the pain levels even further by dropping 90% of all packets in each direction:

Black Hole - 100% Downlink Packet Loss

Finally I have a network profile which simply drops ALL downlink packets. This is useful for testing network timeout conditions. Try starting with Slow Net and switching to Black Hole once your network traffic gets underway if you really want to be mean….

Putting an App to the Test

So to try things out I will run the Twitter Search app that I used to explore NSURLConnection in the iOS Simulator. If you are interested in the code under test refer back to that earlier post for details. The app takes a single string and then uses NSURLConnection to query the Twitter search API and display the results in a table view. Normally when run in the iOS Simulator the results are displayed in under a second which makes it hard to see what is happening. If I turn on the Slow Net profile though the query takes 5-10 seconds making it a lot easier to verify how the app behaves.

Since we are using an NSURLConnection the actual network connection happens asynchronously, the user interface is not blocked and our table view, displaying a Loading… message responds to touches. In addition we can see that the network activity spinner has been activated (more on this in a moment) to inform the user that we accessing the network.

To further test what happens when our network connection fails we will switch to the Black Hole profile which will prevent all responses from reaching our app. Now if we wait long enough the network connection should time out and we can test how well we cope with that situation. Our application implements the connection:didFailWithError: delegate method of NSURLConnection to display the error message and we should also see our status message in the first row of the table update to indicate that the service is not available. Sure enough after about a minute the connection times out and the user interface responds correctly:

This is all very good but whilst playing around with this app I actually spotted some unintended behaviour (ok that is another way of saying a bug…). With either the Slow Net or the Nasty Net profile enabled it is very easy to reproduce. As I mentioned previously whilst we are waiting for the search query to complete so we can display the results the user interface is not blocked. This means that the user can hit the Search button in the Navigation bar to return to the initial screen. When they do that the NSURLConnection is still running and the network activity indicator is still active. Now it will eventually time out and clean itself up but there is nothing to stop the user from initiating another search before the old one has timed out. That is certainly not what I intended when I wrote the app but I never really considered this situation. The fix is easy if we implement the viewWillDisappear method in our view controller and use that to cancel the connection:

- (void)viewWillDisappear:(BOOL)animated

{

   [self cancelConnection];

}

The cancelConnection method takes care of cleaning things up for us:

- (void)cancelConnection

{

   if (self.connection != nil)

  {

    [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;

    [self.connection cancel];

    self.connection = nil;

    self.buffer = nil;

  }

}

Now when we exit the search results table view controller any running connection is cancelled, the network activity spinner is stopped and the temporary buffer is cleaned up.

Wrapping Up

When I started out to write this post I was already sure that testing applications under challenging network conditions was a good idea. However I guess the fact that I actually also found an error in some previously posted code further demonstrates the point. You can find the updated code in my github CodeExamples repository.

One final note, don’t forget to turn NLC off when your done or you may find your Github commits take a bit longer than expected….

Thursday
Jun162011

Searching Twitter with iOS

I have in the past recommended using the excellent ASIHTTPRrequest by Ben Copsey when you need to interact with a web service. I am not changing that recommendation but I thought I would show an example of using NSURLRequest to perform a simple web service query. In this case searching on Twitter.

The Twitter Search API

The documentation for the Twitter Search API provides the basic details on what we need to do to query recent Tweets. There are some more details and examples on using the Twitter Search API which are also very useful if you want perform more complex queries. The basic URL we need to perform an HTTP GET looks as below. In this case we are searching for the text “Apple”:

http://search.twitter.com/search.json?q=Apple

The search results are returned in JSON format, so the result that you get back looks something like this:

{
"results": [
    {
      "from_user_id_str": "12345678",
      "profile_image_url": "http://a1.twimg.com/profile_images/...",
      "created_at": "Wed, 15 Jun 2011 20:58:01 +0000",
      "from_user": "SomeUser",
      "id_str": "81103188895334400",
      "metadata": {
        "result_type": "recent"
      },
      "to_user_id": null,
      "text": "The actual text from the tweet is here",
      "id": 81103188895334400,
      "from_user_id": 63215515,
      "geo": null,
      "iso_language_code": "en",
      "to_user_id_str": null,
      "source": "&lt;a href=&quot;http://twitter.com/&quot;&gt;web&lt;/a&gt;"
    },
{
... next result... 
}
],
"max_id": 81103188895334400,
  "since_id": 0,
  "refresh_url": "?since_id=81103188895334400&q=apple",
  "next_page": "?page=2&max_id=81103188895334400&q=apple",
  "results_per_page": 15,
  "page": 1,
  "completed_in": 0.052526,
  "since_id_str": "0",
  "max_id_str": "81103188895334400",
  "query": "apple"
}  

The search results consists of an array of tweets named “results” along with some additional data to make it easier to page through the results. By default the search query returns 15 tweets but you can use the rpp parameter in the query to increase this up to a maximum of 100 results. There are a number of parameters you can use to search for specific languages, location, etc., but we will keep it simple. So the actual query URL we will use in our example app will be as follows (where query is replaced by the actual string we want to search for):

http://search.twitter.com/search.json?rpp=100&q=query

You can experiment with this from the command line using curl if you want to get a feel for it.

TwitterSearch - the example app

To demonstrate how you can access the Twitter search API from within an iOS app I have created a simple example app that allows you to enter a search term. The results of searching Twitter for that search term are then presented in a table view. The two view controllers are illustrated below

 

Adding JSON support

Since the results are returned in JSON format we need to add a JSON Framework. There are several possibilities, the one I am using is SBJson. At the time of writing this the latest version is 3.0 beta 3. Once you have downloaded the zip file find the Classes folder and copy the contents into your Xcode project (ensure you select the Copy items into destination group’s folder option). You then just need to import the SBJSON.h header file.

The SearchViewController

The SearchViewController is responsible for performing the query and displaying the results. I will use the NSURLConnection class to perform the actual network connection and return the results which we will then to process and feed a standard UITableView. I won’t show the NIB file as it is just a standard table view, the interface definition is as follows:

@interface SearchViewController : UITableViewController {

 

}

 

@property (nonatomic, copy) NSString *query;

@property (nonatomic, retain) NSURLConnection *connection;

@property (nonatomic, retain) NSMutableData *buffer;

@property (nonatomic, retain) NSMutableArray *results;

 

@end

 

The query property is set when the view controller is initialised and should be the text string that we will use to search Twitter. The other properties will become clear as we proceed but in brief the connection property holds a reference to our NSURLConnection object. The buffer is an NSMutableData object that is used to temporarily store the data that is being returned by the network connection and finally the results NSMutableArray is used to store the decoded tweets that the query returns.

Initiating an NSURLConnection

Before looking at the standard viewDidLoad and UITableViewDataSource delegate methods we will dive into the methods used to drive the network connection and processing of the results. The first method is used to setup a network connection to perform the query. It uses a URL constructed with the query string that should be passed to the view controller when it is first initialised.

- (void)loadQuery {

 

   NSString *path = [NSString stringWithFormat:@"http://search.twitter.com/search.json?rpp=%d&q=%@",
                             RESULTS_PERPAGE,self.query];
  path = [path stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
  NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:path]];
  self.connection = [[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease]; 
  [UIApplication sharedApplication].networkActivityIndicatorVisible = YES;

}

 

The first thing we do is build an NSString which represents the URL we want to use for the request. This is constructed from the URL for the Twitter search API we saw earlier with two parameters for the results for page and the actual query term. The results per page is #defined as the value 100. So we should get back up to 100 results. One other thing to note about the URL is that we percent escape any special characters in the query. So for example if we were searching for the hashtag #WWDC the unencoded URL would be as follows:

http://search.twitter.com/search.json?rpp=100&q=#WWDC

The stringByAddingPercentEscapesUsingEncoding: method will percent escape the # symbol so that the resulting string would be as follows:

http://search.twitter.com/search.json?rpp=100&q=%23WWDC

Once we have the string in the correct format we use it to first construct an NSURL object and then use that to create an NSURLRequest. Finally we allocate and initialise an NSURLConnection passing it the NSURLRequest. The NSURLConnection object is what actually performs the network request and has a number of delegate methods which we need to implement to handle the results. Note that when we initialise the NSURLConnection object that we set ourselves (the SearchViewController) as the delegate. We also retain a reference to the object using the connection property so that we have the possibility to cancel the request if needed.

Finally once we have initiated the network connection we set the network activity indicator to be visible to inform the user that there is some network activity happening.

NSURLConnection delegate methods

The first delegate method we will implement is connection:didReceiveResponse which is called when the initial URL response is received. We use this method to initialise our data buffer which will be used to collect the incoming data. It is possible that this method will be called multiple times but it does not really matter as we simply initialise the buffer each time discarding any previously received data:

- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response {

 

   self.buffer = [NSMutableData data];

}

 

The next method is connection:didReceiveData which is called when some data has been delivered. The important thing to understand about this method is that it gets called multiple times as the data is received. So we need to concatenate the data in our buffer as each block is received:

- (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data {

 

  [self.buffer appendData:data];

}

 

There are two ways we can be informed that the connection has finished. If things go well the method connectionDidFinishLoading: is called. If something goes wrong we get a call to the method connection:didFailWithError: The first of these methods needs to take the data that has built up in our buffer, decode it, store it in our results ivar and then signal to the table view to reload itself. The code is as follows:

- (void)connectionDidFinishLoading:(NSURLConnection *)connection {

 

  [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  self.connection = nil;

 

  NSString *jsonString = [[NSString alloc] initWithData:self.buffer encoding:NSUTF8StringEncoding];
  NSDictionary *jsonResults = [jsonString JSONValue];
  self.results = [jsonResults objectForKey:@"results"];

 

  [jsonString release];
  self.buffer = nil;
  [self.tableView reloadData];
  [self.tableView flashScrollIndicators];

}

 

The first thing we do is turn off the network activity indicator and zero (which also releases) our reference to the NSURConnection object. Next we convert our received data into an NSString which gives us the JSON response that we can decode using the JSON framework we added at the start of the project. Using the JSONValue method on the NSString we get back a dictionary whose keys and values correspond to the JSON name/value pairs.

Since we only want the tweet data we just need to get the object that corresponds to the “results” key in the JSON response. That should give us an array of NSDictionary objects, one for each tweet, that we can use to feed our table view. The last thing we do in this method is to trigger our table view to reload and flash the scroll bars on the table view.

Before looking at the table view delegate methods we should just cover the failure situation for completeness. We do not do anything special to handle the error other than clearing up and displaying an error to the user:

- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error {

 

  [UIApplication sharedApplication].networkActivityIndicatorVisible = NO;
  self.connection = nil;
  self.buffer = nil;

 

  [self handleError:error];
  [self.tableView reloadData];

}

 

I will not show the handleError method here as it is simply shows a UIAlertView to the end user.

UITableViewDataSource Delgates

To display the data in the table view we need to implement the usual table view delegate methods. I will omit showing numberOfSectionsInTableView as it simply returns 1, but the numberOfRowsInSection: method is more interesting:

- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section

{

  NSUInteger count = [self.results count];
  return count > 0 ? count : 1;

}

 

If we have successfully retrieved some search results then we can use the count of objects in the results array to determine the number of rows in our table. If we do not have any results we return a value of 1 so that we display a text message to the user. The tableView:cellForRowAtIndexPath method uses the results array to display the resulting tweets:

- (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath

{

  static NSString *ResultCellIdentifier = @"ResultCell";
  static NSString *LoadCellIdentifier = @"LoadingCell";

 

  NSUInteger count = [self.results count];
  if ((count == 0) && (indexPath.row == 0)) {
    UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:LoadCellIdentifier];
    if (cell == nil) {
      cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                     reuseIdentifier:LoadCellIdentifier] autorelease];
      cell.textLabel.textAlignment = UITextAlignmentCenter;
    }

 

    if (self.connection) {
      cell.textLabel.text = @"Loading...";
    } else {
      cell.textLabel.text = @"Not available";
    }
    return cell;
  }

 

  UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:ResultCellIdentifier];
  if (cell == nil) {
    cell = [[[UITableViewCell alloc] initWithStyle:UITableViewCellStyleDefault 
                                   reuseIdentifier:ResultCellIdentifier] autorelease];
    cell.selectionStyle = UITableViewCellSelectionStyleNone;
    cell.textLabel.numberOfLines = 0;
    cell.textLabel.font = [UIFont systemFontOfSize:14.0];
  }

 

  NSDictionary *tweet = [self.results objectAtIndex:indexPath.row];
  cell.textLabel.text = [NSString stringWithFormat:@"%@: %@", [tweet objectForKey:@"from_user"],

[tweet objectForKey:@”text”]];

  return cell;

}

 

There are two types of cell that we need to create. The first is used when we do not have any results to display. Rather than just leaving the user to look at a blank screen we show a text message in the first row. The text message is either set to “Loading…” if we have a request in progress (which we can tell by checking if we have an NSURLConnection object allocated) or “Not available” otherwise. If we have results to display we retrieve the NSDictionary object for the current row in our array of tweet results and then format a simple string using the “from_user” and “text” keys.

I have added one other tiny cosmetic effect which is to set the background color of alternate rows in the table. The way to do this is to implement the tableView:willDisplayCell:forRowAtIndexPath: method and set the cell background color:

- (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {

 

  if (indexPath.row & 1) {
    cell.backgroundColor = [UIColor colorWithWhite:0.9 alpha:1.0];
  } else {
    cell.backgroundColor = [UIColor whiteColor];
  }

}

Loading and Unloading the View

The final piece of the search view controller is to implement the viewDidLoad, viewDidUnload and dealloc methods. The viewDidLoad method simply sets the title and calls the loadQuery method we saw earlier to initiate the request:

- (void)viewDidLoad

{

  [super viewDidLoad];

 

  self.title = self.query;
  [self loadQuery];

}

 

The viewDidUnload and dealloc methods do what you would expect with the addition that they cancel any NSURLConnection that might be in progress when we unload or dealloc the view controller:

- (void)viewDidUnload

{

  [super viewDidUnload];

 

  [self.connection cancel];

 

  self.connection = nil;
  self.buffer = nil;
  self.results = nil;

}

 

- (void)dealloc

{

  [self.connection cancel];
  [_connection release];
  [_buffer release];
  [_results release];
  [_query release];
  [super dealloc];

}

Creating the Query

Now we have the SearchViewController we just need to create a root view controller that prompts the user for a search term. I will skip some of the details on this as the code is minimal. Our user interface is very simple consisting of a single UITextField. To retrieve the text when the user hits the return key we implement the textFieldShouldReturn delegate:

- (BOOL)textFieldShouldReturn:(UITextField *)textField {

 

  if (textField.text) {

     SearchViewController *viewController = [[SearchViewController alloc]

                            initWithNibName:@”SearchViewController” bundle:nil];

     viewController.query = [NSString stringWithFormat:@”%@”, textField.text];

     [[self navigationController] pushViewController:viewController animated:YES];

     [viewController release];

  }

  [textField resignFirstResponder];

  return YES;

}

 

There is nothing special here. After allocating out SearchViewController, we set the query and then push the view controller onto the navigation controller stack.

Wrapping Up

I hope that I managed to show how easy it is to access a simple web API using NSURLConnection and a bit of JSON. You can find the full Xcode project for download here or also on my github page. I have only just recently started putting the example projects from this blog on Github but hopefully it makes it easier for you to browse the source code.

Monday
Aug092010

Reachability Updates

I mentioned in passing yesterday that Apple recently updated the Reachability class that provides a way to monitor the network connectivity of an iOS device. What I did not get a chance to mention is an alternative implementation created by Andrew Donoho which you may want to take a look at. You can find some more details on his site, I can’t claim to have used it yet, but it looks like a simpler and cleaner implementation to use.

The recent updates to the reachability class (now at version 2.2) by Apple have removed some of the rough edges that Andrew highlights. In particular the spelling mistakes in the method names -startNotifier and -stopNotifier (previously -startNotifer/-stopNotifer) and a redundant call to [super init]. If you are updating an app that uses an earlier version of the reachability class this will of course require you to fix your code anywhere you have used the misspelt methods.

I should mention that I came across this alternative implementation via the excellent ASIHTTPRequest library created by Ben Copsey which has adopted it in its latest release. If you need to do anything which involves interacting with a web server it is well worth checking out ASIHTTPRequest.

Sunday
Aug082010

Checking network connectivity when displaying iPhone iAds

There was a comment to my post about adding iAds to an application that got me thinking about an improvement to the way I handle the iAd BannerView. The comment was about moving the app to the background, entering airplane mode and then moving the app back to the foreground. That is just one example of the more general problem of changing network connectivity. The problem comes after you have successfully requested and received an Ad and shown the banner view. If at that point network connectivity is lost the banner view is left on screen. If the user clicks on the Ad banner they get a network connection error which is not exactly a great user experience.

You could reason that losing network connectivity after you have displayed the ad banner is not very likely and just live with the problem - after all the app still works, the user just gets an error message. Note that you need to have network connectivity to see the add in the first place as it is only moved onscreen when the request for an ad completes with success. However with iOS 4 the chances of network connectivity changing whilst your app is running is much more likely. An app can spend days suspended in the background before being brought back to the foreground. In that time the user can be in a completely different location with or without network connectivity. Leaving the ad banner onscreen when there is no network connection wastes some screen space than can be put to better use and as I said earlier hardly represents the best user experience we can provide.

Using Reachability to Check Network Connectivity

To summarise the problem I want to be able to control when an iAd banner is displayed or hidden based on whether the device has network connectivity. Thus if the device has no network connectivity I do not bother to request an Ad since it will return an error anyway. Likewise if I am showing the banner and lose connectivity I remove it.

The network state of an iPhone can be be monitored using the SCNetworkReachability interface of the System Configuration framework. Apple provides the Reachability sample application as an example on how to implement the framework which makes it trivial to add to an existing app. The example app was recently updated for iOS 4.0 so if you already have a copy it may be worth checking you are using the latest version (v2.2).

To get started add the reachability code and SystemConfiguration.Framework to the example app I previously used to demonstrate implementing iAds. Copy the Reachability.h and Reachability.m files from the Apple Reachability sample app and use the Add -> Existing Files and Add->Existing Frameworks options in Xcode to add the files and framework to the project:

Reachability Class Setup

The Reachability class provides a convenient wrapper to the SCNetworkReachability interface. To start using it we just need to add an ivar to the application delegate to hold a reachability object and then start the notifier when the application first launches (existing code not shown):

//  tabnavAppDelegate.h

@class Reachability;

@interface TabnavAppDelegate : NSObject <UIApplicationDelegate, UITabBarControllerDelegate> {
    ...
    ...
    Reachability *netReach;
}
...
...
@property (nonatomic, retain) Reachability *netReach;

 

//  tabnavAppDelegate.m
#import "Reachability.h"

@implementation TabnavAppDelegate

@synthesize netReach;

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:
                                                  (NSDictionary *)launchOptions { 

    self.netReach = [[Reachability reachabilityWithHostName: @"apple.com"] retain];
    [netReach startNotifier];
    ...
    ...
}

- (void)dealloc {
    [netReach release];
    ...
    ...
}

 

The reachabilityWithHostName class method takes the name of a host to check - apple.com in this case - and returns a reachability object that we can use to check the network status. The reachability class can also send notifications when the reachability status changes. The startNotifier method instructs the reachability class to start sending reachability changed notifications.

Listening for Reachability Changed Notifications

With the reachability class initialised we can use it in our view controller to check the reachability status and control when to show and hide the iAd BannerView. First I will add a helper method to query the Reachability object to determine if we currently have network connectivity:

- (BOOL)networkReachable {
	
    TabnavAppDelegate *delegate = (TabnavAppDelegate *)[[UIApplication sharedApplication]
                                                        delegate];
    Reachability *netReach = [delegate netReach];

    NetworkStatus netStatus = [netReach currentReachabilityStatus];
    BOOL connectionRequired = [netReach connectionRequired];
	
    if ((netStatus == ReachableViaWiFi) ||
        ((netStatus == ReachableViaWWAN) && !connectionRequired)) {

        return YES;
    }

    return NO;
}

 

This method first retrieves the reachability object we setup in the application delegate and then uses the currentReachabilityStatus and connectionRequired methods to determine network connectivity. There are several possible situations since the device may be connected via WiFi or via the cellular network. Also the cellular network may be available but not actually connected.

We can now use this method to determine the network status when our view controller first loads. If we already have network connectivity we can go ahead and create the iAd BannerView immediately. In addition we will initialise an observer to listen for reachability changed notifications generated by the Reachability class. Each time the network connectivity of the device changes we will get a call to the method reachabilityChanged that we specified when creating the observer:

- (void)viewDidLoad {

    // If the network is reachable create the iAd banner, otherwise we
    // wait until the network becomes reachable
    BOOL reachable = [self networkReachable];
	
    if (reachable) {
        [self createBannerView];
    }

    // Listen for reachability changes
    [[NSNotificationCenter defaultCenter] addObserver:self
                                          selector:@selector(reachabilityChanged:) 
                                          name:kReachabilityChangedNotification
                                          object:nil];
}

 

Note that whenever you add an observer for a notification you need to ensure to remove the observer when your view controller is deallocated:

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

 

To finish up we just need to implement the reachabilityChanged method that is called whenever a reachability notification is received:

- (void)reachabilityChanged:(NSNotification *)note {

    BOOL reachable = [self networkReachable];
	
    if (reachable && (self.bannerView == nil)) {
        [self createBannerView];
    }

    if (!reachable && self.bannerView) {
       [self hideBanner];
       [self releaseBanner];
    }
}

 

This method determines the current reachability status using the helper method we created earlier. Then if the network has just become reachable and we do not currently have a bannerView it is created. Alternatively if network connectivity has just been lost and we currently have a bannerView it is hidden and released. This means that during the life of the application we will add and remove the bannerView as the network connectivity changes.

There are several different strategies we could have used here that would achieve the same result from the users perspective. For example, we could always create the bannerView in viewDidLoad and then call hideBanner and showBanner in reachabilityChanged instead of creating and releasing each time.

To test that everything is working you can try it on a network connected device. Once the test iAd banner appears you can disconnect the network connection using the settings application (or use airplane mode) and you should see the Ad disappear. Likewise when you restore connectivity you should eventually see a new ad banner.

Handling Background App Switching

One other enhancement I considered adding was to release the BannerView when the app is switched to the background and recreate it when moving back to the foreground. This is easy enough to implement by having our view controller listen for UIApplicationDidEnterBackgroundNotification and UIApplicationDidBecomeActiveNotification. However when I checked the background memory usage after releasing the bannerView I found it made very little difference so I have left this out of the sample app.

All Done

That’s all for now you can find the updated sample app here. Let me know what you think.