Home / iPhone Tips and Tutorials

Application Errors

This section discusses errors generated by the next layer in the network protocol stack: the application layer. Application errors differ from OS or HTTP errors because there is no standard set of values or causes for these errors. These errors are caused by the business logic and application running in the service tier. In some situations the errors may be code failures, such as exceptions, but in others the errors may be semantic errors, such as an invalid account number supplied to the service. In the former situation it is advisable to generate an HTTP 500-level error, but the latter scenario should return an error code in the application payload.

For example, an application error would be reported in a mobile banking application if the user tried to transfer more funds from an account than were available for transfer. If such a request were made, the OS would report that the request was successfully sent and a response received. The HTTP server would report that the request was received and a response sent, but the application layer must report that the transaction failed.

The best practice for reporting application errors is to wrap all application payload data in a standard envelope that contains a consistent location for application errors. In the funds transfer example, the business payload of a successful transfer response may look like:

{ "transferResponse":{
    "fromAccount":1,
    "toAccount":5,
    "amount":500.00,
    "confirmation":232348844
   }
}

The response contains the source and destination accounts, the amount transferred, and a confirmation number. Including any error codes and messages directly into the transferResponse object would make locating the error code and message difficult. If each action includes error reporting in its own response object, any error reporting logic could not be reused across the application. Using a packet structure like the one in the following code sample allows the application to quickly determine if an error occurred by checking for the existence of the "error" object in the response JSON payload:

{"error":{
	"code":900005,
	"messages":"Insufficient Funds to Complete Transfer"
    },
    "data":{
	"fromAccount":1,
	"toAccount":5,
	"amount":500.00
    }
}

Any UI code to report errors can easily be reused because the error information is always in the error attribute of the response payload. Additionally, handling of the actual transaction payload is simplifi ed because it is always under the same attribute name.

Regardless of the cause of a failed request, either OS, HTTP layer, or application, your application must know how to respond. You should spend time early in development considering all the failure modes of the application and design a consistent method to detect and respond to them.

Rules of Thumb for Handling Errors

Errors can be caused by a multitude of conditions, and the best way to handle them can vary as greatly as the apps you write. Despite the complexity, some rules of thumb can help you cope with the uncontrolled nature of error conditions.

Include Error Handling In the Interface Contract

When designing a service interface, it is a mistake to specify only the input, outputs, and operations of the service. The interface contract should also specify how errors are communicated to the client. A service interface should leverage industry-standard means to communicate errors wherever possible. For example, the server should not define a new HTTP status value for a server side failure; instead it should use the appropriate 500-level status. If standard values are used, both the clientside and the server-side developers will have the same understanding of how errors will be communicated. Applications should never depend on accidental indications or overloaded property values to determine the presence of an error.

Application developers should also not depend on the behavior of the current server software stack to determine how to handle errors. When an iOS app is deployed, the server software stack may change behavior due to a future upgrade or replacement.

Error Statuses Lie

Mobile networking has one nonobvious behavior that differs dramatically from errors in traditional web applications: ambiguous error reporting. There are three possible outcomes of any network request from a mobile device to a server:

  • The device has absolute positive confirmation that the operation succeeded. For example, both the NSError and HTTP status values indicate success, and the returned payload contains syntactically and semantically correct information.
  • The device has absolute negative confirmation that the operation failed. For example, the returned application payload contained a failure indicator from the server that is specific to the operation attempted.
  • The device has ambiguous negative confirmation that the operation failed. For example, the mobile app sends an HTTP request to transfer funds between two accounts. The request is received and successfully processed by the bank systems; however, the reply gets lost due to a network failure, and the NSURLConnection reports a timeout. The timeout did occur, but only after the transfer request was successful. If the transfer is retried, it results in a duplicate transfer and possibly overdrawn accounts.

This third scenario is the scenario that can cause unexpected and undetected misbehavior of the app. If the app developers do not know that the third scenario exists, they may make bad assumptions about a failure and inadvertently retry an operation that already succeeded. It is not just enough to know that the full request failed; rather, the developers must consider what could cause the request to fail and whether it is appropriate to automatically retry every failed request.

Validate the Payload

App developers should not impute that the payload is valid based on the fact that no OS or HTTP error was reported. Many scenarios can occur in which the request appears to have succeeded, but the payload is invalid. Any payload transferred between client and server should have a mechanism for validation. JSON and XML are examples of payload formats that have validation mechanisms, but neither comma separated value (CSV) fi les nor HTML do.

Separate Errors from Normal Business Conditions

The service contract should not report normal business conditions as errors. For example, if you have a user whose account is locked due to potential fraud, the lock status should be reported in the data payload rather than as an error condition. Separating errors from normal business conditions enables your code to maintain proper separation of concerns. Errors should be reserved for situations when things are broken.

Always Check HTTP Status

Always check the HTTP status on HTTP responses, and be explicit about the status values that are successful. This is the case even when making repeated calls to the same service. The status of the server can change at any time, even between juxtaposed calls.

Always Check NSError

Your app code should always check the returned NSError value to make sure nothing broke at the OS level. This is true even if you know that the app always runs on a well-run and tightly controlled Wi-Fi network. Things do not always work correctly, and your code needs to be defensive when dealing with the network.

Develop a Consistent Method for Handling Errors

The causes of networking errors are too numerous to enumerate, and the variety and scope of their impact can be overwhelming. When you design your app, don't just focus on consistent user interface patterns or a consistent naming scheme. You should also design a consistent pattern for dealing with network errors. This pattern should consider all the types of errors your app may encounter. Your app cannot consistently communicate errors to the user if it is not handling those errors in a consistent manner internally.

Always Set a Timeout

The default request timeout interval for an HTTP request in iOS is 4 minutes, which is a long time for a mobile application, and most users do not spend 4 consecutive minutes in any application. Developers need to choose a reasonable timeout value by evaluating the probable response times for any network requests and then factor in network delays for the worst-case network scenario. The following example demonstrates creating a request with a 20-second timeout.

- (NSMutableURLRequest *) createRequestObject:(NSURL *)url {
    NSMutableURLRequest *request = [[[NSMutableURLRequest alloc]
					initWithURL:url
					cachePolicy:NSURLCacheStorageAllowed
				timeoutInterval:20
			    autorelease];
    return request;
}
[Previous] [Contents] [Next]