Operating System Errors
Operating system (OS) errors are errors caused by a data packet not reaching its intended target. That data packet may be part of establishing a connection or occur somewhere in the middle of the connection. OS errors can be caused by several conditions:
- Lack of a Network -
If the device doesn't have a data network connection, the connection attempt is rejected quickly or fails midstream. These types of errors can be anticipated using the Reachability framework provided by Apple, which is covered later in this section. - Inability to Route to the Intended Host -
The device may have a network connection, but the intended target of the connection may be on a segregated network or offline. These errors can sometimes be detected quickly by the operating system but could potentially result in a connection timeout. - No application listening on the target Port -
After the request arrives at the target host, the packet is delivered to the port number specifi ed in the request. If no server is listening on that port or if too many connection requests are queued, then the connection request will be rejected. - Inability to resolve the Target Hostname -
If the name of the target host cannot be resolved, then the URL loading system returns an error. Often these errors can be due to configuration mistakes or an attempt to access a host on a segregated network with no external name resolution.
In the URL loading system of iOS, operating system errors are reported to the application in an NSError object. iOS uses NSError to communicate errors between software components. The key benefit of NSError when compared to a simple error code is that NSError objects contain an error domain property.
The use of NSError objects is not limited to the operating system though. Your app can create its own NSError objects and use them to propagate error messages around the app. The following snippet illustrates an application method that uses NSError to communicate a failure back to the calling view controller.
- (id)fetchMyStuff:(NSURL*)url error:(NSError**)error { BOOL errorOccurred = NO; // some code that makes a call and may fail if (errorOccurred) //some kind of error { NSMutableDictionary *errorDict = [NSMutableDictionary dictionary]; [errorDict setValue:@"Failed to fetch my stuff" forKey:NSLocalizedDescriptionKey]; *error = [NSError errorWithDomain:@"myDomain" code:kSomeErrorCode userInfo:errorDict]; return nil; } else { return stuff } }
The domain property segregates error numbers based on the library or framework that produced them. Using domains, framework developers do not need to worry about overlapping error codes because the domain property defi nes which framework generated the error. For example, frameworks A and B can both have an error code 1, but the two are distinguished by the unique domain values provided by each framework. Consequently, if your code needs to distinguish between unique NSError values, it must compare both the code and domain properties of the NSError object.
An NSError object has three primary properties:
- Code - An NSInteger value that indicates which error occurred. This number is unique to the error domain that instantiates the error.
- Domain - An NSString pointer that specifi es the domain of the error. Example domains include NSPOSIXErrorDomain, NSOSStatusErrorDomain, and NSMachErrorDomain.
- User Info - An NSDictionary pointer containing values specifi c to the error that occurred.
Many of the errors that occur within the URL loading system come from the NSURLErrorDomain domain, and the code values are frequently drawn from the error codes defi ned in CFNetworkErrors.h. As with any constant value provided by iOS, your code should rely on the defi ned constant name for the error, not the actual error code value. For example, the error code if the client cannot connect to the host is -1004 with a defined constant of kCFURLErrorCannotConnectToHost. Your code should never directly reference -1004 because this value may change in future revisions of the OS; instead it should use the kCFURLError provided enumeration name.
The following code example illustrates making an HTTP request using the URL loading system.
NSHTTPURLResponse *response=nil; NSError *error=nil; NSData *myData=[NSURLConnection sendSynchronousRequest:request returningResponse:&response error:&error]; if (!error) { // No OS Errors, keep going in the process ... } else { // Something low level broke }
Notice that the NSError object is declared as a pointer to nil. The NSURLConnection object instantiates only the NSError object if an error occurs. The URL loading system owns the NSError object; you should retain the object if your code will need it later. If the NSError pointer still points to nil after the synchronous request completes, then no low-level OS error occurred. At this point your code knows that no OS level has occurred, but an error may have occurred at a higher layer in the protocol stack.
If your application makes an asynchronous request, the NSError object is returned to the delegate class on the method with the following signature:
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error
This message is the fi nal message delivered to the delegate for the request, and the delegate must discern the cause of the error and react appropriately. In the following example, the delegate displays a UIAlertView to the user:
- (void) connection:conn didFailWithError:error { UIAlertView *alert = [UIAlertView alloc] initWithTitle:@"Network Error" message:[error description] delegate:self cancelButtonTitle:@"Oh Well" otherButtonTitles:nil]; [alert show]; [alert release]; }
This code reports errors to the user but in an abrupt and unfriendly way. In the iOS Human Interface Guidelines (HiG), Apple recommends against overuse of UIAlertViews because it breaks the illusion of a magical device. The Gracefully Handling Network Errors section reviews a pattern for handling errors cleanly and consistently with a pleasing user interface.
Another major cause of communication errors from iOS devices is the inability of the device to reach its target server because it lacks a network connection. You can avoid many OS errors by first checking the network status before attempting a network request. Keep in mind that these devices can rapidly move on and off the network; therefore, it is reasonable to check the network reachability before each call.
iOS provides many ways to determine the status of a device's network connection as part of the SystemConfiguration framework. You can find details on the low-level API in SCNetworkReachability reference documentation. The API is powerful but also somewhat cryptic. Thankfully, Apple provides an example program called Reachability that implements a simplified, high-level wrapper around SCNetworkReachability. Reachability is available in the iOS Developer Library.
The Reachability wrapper provides four major pieces of functionality:
- An indication of whether or not the device has a functional network connection
- An indication of whether a specific host can be reached with the current network connections
- An indication about which networking technology is being used: Wi-Fi, WWAN, or none
- Notifications of any changes in the network state
To use the Reachability API, download the example program from the iOS developer library at http://developer.apple.com/library/ios/#samplecode/Reachability/Introduction/ Intro.html, and add Reachability.h and Reachability.m to your app's XCode project. In addition, you need to add the SystemConfiguration framework to your XCode project. Adding the SystemConfiguration framework to your XCode project entails editing your project configuration. The steps required to add the SystemConfiguration framework to your XCode project.
After selecting the project target, scroll-down the settings to the Linked Frameworks and Libraries section and press the plus sign to add a framework. The framework selector then appears. Select the SystemConfiguration framework and press the add button to add it to your project.
The following code snippet checks to see if a network connection is available. It does not guarantee that any particular host or IP address is reachable; it just indicates that a network connection exists.
#import "Reachability.h" ... if ([[Reachability reachabilityForInternetConnection] currentReachabilityStatus] == NotReachable) { // handle the lack of a network }
In some situations you may want to change certain actions, disable UI elements, or change timeout values if the device is on a limited network. If your application needs to know the connection type it is currently using, use the following code:
#import "Reachability.h" ... NetworkStatus reach = [[Reachability reachabilityForInternetConnection] currentReachabilityStatus]; if (reach == ReachableViaWWAN) { // Network Is reachable via WWAN (aka. carrier network) } else if (reach == ReachableViaWiFi) { // Network is reachable via WiFi }
It is also useful to know if the reachability status of the device changes so that you can modify application behavior proactively. The following code snippet initiates monitoring of network status:
#import "Reachability.h" ... [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(networkChanged:) name:kReachabilityChangedNotification object:nil]; Reachability *reachability; reachability = [[Reachability reachabilityForInternetConnection] retain]; [reachability startNotifier];
This code registers the current object as an observer for notifi cations with the name kReachabilityChangedNotification. NSNotificationCenter calls the method named network- Changed: on the current object. It passes into that object an NSNotification with the new reachability status when it changes. The following example demonstrates the notification listener:
- (void) networkChanged: (NSNotification* )notification { Reachability* reachability = [notification object]; if (reachability == ReachableViaWWAN) { // Network Is reachable via WWAN (a.k.a. carrier network) } else if (reachability == ReachableViaWiFi) { // Network is reachable via WiFi } else if (reachability == NotReachable) { // No Network available } }
Reachability can also determine if a specific host is reachable on the current network. You can use this feature to alter the behavior of an enterprise app based on whether the app is on an internal segregated network or on the open Internet. The following code sample illustrates this feature:
Reachability *reach = [Reachability reachabilityWithHostName:@"www.example.com"]; if (reachability == NotReachable) { // The target host is not reachable available }
Keep in mind that this feature requires a round trip to the target host. If used for each request, it can add signifi cant network overhead and latency to the application. Apple recommends that host reachability detection not be performed on the main thread. There is a possibility that the attempt to reach the host may block the main thread, which will cause the UI to freeze.
OS Errors are your first indication that something has failed in your request. App developers sometimes ignore them, but if you ignore them it is at the peril of your app. Because HTTP leverages layered networking, there is another layer of potential failures that may occur at the HTTP layer or at the application layer.