If you write an iPhone application that uses the network, then you must check if the network is both operating and that your server is reachable. Apple has and will continue to reject applications that do not test reachability. To this end, Apple provides iPhone developers a sample application and class, Reachability, that can easily be used to perform these tests. While the 2.0 version of this code is much better than v1.5, it is still quite raw. (For example, there is a bare [super init]; in a class method [line 175 in Reachability.m]. It does nothing. It also has two routines with misspellings in their names: -startNotifer/-stopNotifer should be -startNotifier/-stopNotifier) To me, these are signs of raw code. To remedy this, I have significantly reengineered this class.
I had three goals when starting this process. First, I wanted to understand how to use this code. It isn’t at all apparent how these routines should be used. What order should they be called in and what do the various tests and values mean? Second, my scan of the code convinced me that it was written by a low level network engineer. They had made implementation decisions that were not terribly friendly to using it in a Cocoa Touch program. I wanted a class that fit in with my coding style. Third, because the core network testing routine -networkStatusForFlags: was not, in my opinion, clear, I did not feel comfortable shipping it in my app, weLost™. I wanted to have confidence in this code.
This class has two important features: network status transition notifications and easy reachability testing for each server your app touches. Because radios lose connections all of the time, these dynamic notifications help your user understand why they cannot use the networking features of your app.
When I start my app, the first Reachability method I use creates a Reachability instance for the network itself, +(Reachability *) reachabilityForInternetConnection;. This tests the hardware and can immediately provide you with the current network status. Because this reachability test doesn’t leave the phone, there is no network latency. Because hostname reachability instances must resolve your host’s name into an IP address, they are asynchronous. The immediately returned status is that your host is not reachable. (Bear in mind that the subsystem underlying Reachability, SCNetworkReachability, does not actually try to send a packet to your host. It only really knows the state of the radios and whether IP addresses have been allocated for the phone and that your host’s name has resolved into an IP address.)
These Reachability instances do not start notifying you of network transitions upon instantiation. You must turn this on. Before turning on the notifications, I like to register for them. The notification name in the default notification center is: kReachabilityChangedNotification. When your registered method is called, the notification’s object will be the Reachability instance with the changed status. For example, you might register this method:
-(void) networkReachabilityEvent: (NSNotification *) notification;
with the notification center. When it is called for a status change, the Reachability instance is recovered with this line of code:
Reachability *r = [notification object];
A Reachability instance’s notifications are initiated, unsurprisingly, with the -startNotifier method.
Now that you can track gross network transitions, you need to create Reachability instances for each host. In weLost’s case, I track 4 different servers. I use:
+(Reachability *) reachabilityWithHostName: (NSString*) hostName;
to create these instances. These host names must be resolved into addresses before the instance is reachable. (A notification, if you’ve turned them on for this instance, is issued when the resolution is finished.)
This class can be used both synchronously and asynchronously. Typically, you will make synchronous queries of the Reachability instance passed to you after a network status transition. You may also want to do a synchronous test before opening a new view which connects to your host. (One advantage of creating all of your Reachability instances at startup, i.e. before you need them, is that their name resolution does not delay your later network accesses.) My version of Reachability has more methods for synchronously testing network status than Apple’s. Their version also has mixed some tests, such as whether user intervention is required, into the network status method which I think should not be there. I have cracked them out into separate methods. Here are the kinds of network tests you can perform:
- Test whether this Reachability instance is of interest.
- If so, then test what kind of network transition it is:
- Is the host reachable? -(BOOL) isReachable;
- Is the connection up? -(BOOL) isConnectionRequired;
- Will it come up automatically? -(BOOL) isConnectionOnDemand;
- Will the user have to enter a password? -(BOOL) isInterventionRequired;
- Is the connection slow? -(BOOL) isReachableViaWWAN;
- Is the connection fast? -(BOOL) isReachableViaWiFi;
How do you determine if a Reachability instance is of interest? You check its key — a hostname or a dotted quad IP address or one of two constants: kInternetConnection or kLocalWiFiConnection. This is a big change from Apple’s code. I keep a key, an NSString, along with the SCNetworkReachabilityRef. Apple just keeps a boolean and the SCNetworkReachabilityRef. In other words, I make it easy to use Reachability instances in complex Cocoa data structures. I’m sure Apple’s engineer would argue that pointer comparisons are sufficient to determine one’s interest in a notification. That doesn’t mean it is a particularly useful mechanism in a Cocoa program. Also, Apple’s boolean is used just to differentiate one instance type, kLocalWiFiConnection, from all of the other types. While this is useful to the internal functioning of Apple’s code, it isn’t particularly useful to a client of the class. I provide a convenience method, -isEqual:, to compare Reachability instances. To a Cocoa Touch programmer, a key is just more flexible.
Your decision tree to interpret network status transitions is, of course, idiosyncratic to your application. However, there are issues to consider based upon your connection. After you have determined whether your host is reachable, is the network active and connected? For example, if you are connected via the EDGE/3G wireless wide area network (WWAN), your network is always reachable but it may not be active. You start the radios by trying to connect to your host. I have seen this process take tens of seconds. Does your user need to know this? If you are connected via WiFi and if a user must enter a password, you may choose not to connect to the network. If you’ve transitioned from WiFi to WWAN, does the user need to know that things are much slower or they may have lower resolution media? And the converse? Do you enable more features when the network transitions to WiFi from WWAN? And what, exactly, do you do when there is a repeated network flap between WiFi and WWAN? What do you do when your app is brought out of sleep in a new venue? What do you tell your user? You will get a notification for each of these situations.
There are two Reachability instances I have neglected — -reachabilityWithAddress: and -reachabilityForLocalWiFi. -reachabilityWithAddress: takes an internet IP/socket address and can be queried immediately for its status. Its key is a dotted quad IP address, such as @"127.0.0.1". The -reachabilityForLocalWiFi instance is a bit more subtle. It tests for a WiFi connection using an automatic private IP address, 169.254.0.0. If this network is reachable, then your network is active but not routable. Some services, such as Bonjour, will still function fine in this case but not much else. I have not found any use for this instance type. Yet I have preserved Apple’s test criteria for this instance in my code. Perhaps it is of use to you.
Before using my extensions you should probably ask: how compatible is this stack with Apple’s? My answer is: very. My code is a superset of Apple’s API. Other than Apple’s misspelled method names, my code will be a drop in replacement for Apple’s class. It has, as far as I’ve been able to test, identical behavior. My network status test is different. It is supported by empirical observations of the status flags. Because it does not conflate a test for whether a user’s intervention is required with the basic reachability, my network status test is simpler. Nonetheless, if you do not wish to use my extensions, I provide a #define DDG_EXTENSIONS to enable easy comparisons between the two network status policies in your application. Both classes employ NSAssert and NSCAssert. This is aligned with the Cocoa policy to throw exceptions during development to catch programmer errors. These exceptions can be turned off by defining NS_BLOCK_ASSERTIONS during your release builds. Finally, I support a conditionally compiled logging system. To turn it on you need to #define CLASS_DEBUG in the class header and #define DEBUG for the debug builds of your app. Then both the -description method and synchronous logging are turned on. My log entries also list the method name and line number of the source code. Class logging is turned on by default.
This class is packaged in a .zip file here: Reachability 2.0.4ddg.zip. It has two directories: Reachability itself and my version of Apple’s Reachability project. These extensions to Apple’s class are covered by the new BSD license. Hence, if you use my version, I require a public acknowledgement of my contributions in your application.
I hope you find this version of Reachability useful. May your code compile without errors and run like the wind.
NOTE: Allen Brunson found a memory leak in Reachability. This has been corrected and a new version generated, 2.0.2ddg, and placed at the link above.
NOTE: Ling Wang suggested an additional test for a WiFi+VPN condition. This has been added and a new version generated, 2.0.3ddg, and placed at the link above.
NOTE: After complaints about my added NSAssert in the -networkStatusForFlags: method, I have now surrounded the assert with a conditional compile macro. This change has been added and a new version generated, 2.0.4ddg, and placed at the link above.