Thursday, April 2, 2015

Asynchronous JSON requests in Objective-C

One of the most common patterns in iOS development is the display of data which originates from a Web Service and is transmitted via JSON. This post discusses synchronous and asynchronous fetching of data, and other related design patterns.

What is the difference between synchronous and asynchronous http requests? In a synchronous http request, the currently executing code "blocks" (stops and waits) until the request finishes. If this code is on the main thread, then the app will appear frozen and broken while the network call is made. This is a bad idea, is against best practices, and should always be avoided.

In contrast, an asynchronous http request allows the currently executing code to continue while the request is initiated and while the request is running. Once the request completes, the code "reports back" to a listener. In the case of  NSURLConnection sendAsynchronousRequest:queue:completionHandler: , the listener is the caller of the method who passes in a block as a method argument. In the case of network requests being made from the main thread, the User Interface does not lock, the application continues to respond to user gestures, and is the accepted best practice.

In a nutshell, that's all you need to know: use the asynchronous method. But that begs the question: why is the synchronous method even available if it should never be used? The answer is that there are many scenarios when making a synchronous request makes sense. Detailing some of these scenarios comprises the remainder of this post.

Model-View-Controller (MVC)

It is no secret that MVC is the most prevalent design pattern seen in iOS development. The View Controller does a lot of heavy lifting. But that doesn't mean that it should do all the work. Views are generally responsible for how they render themselves; view controllers may give views general directions such as change position or change state. A common mistake in beginning iOS developers is to put all the model code inside the View Controller. Experienced developers see the benefits of having separate model classes, and sometimes even separate classes that manage these models.

Since many iOS applications fetch data from a Web Service in JSON format, it makes sense to have Model classes that mirror the JSON objects. But what about the fetching of the JSON data, where should this code go: in the View Controller or in the Model? An argument could be made for either, however a better solution is to put the data fetching in yet another class. Following good Object Oriented Programming (OOP) means that each class has a single duty that is easily explained.

Service Layer

I learned a really valuable lesson from a co-worker while doing full-stack ASP.Net C# development. We had a 3-tiered architecture: a Database layer, a Web Server layer, and a web browser layer. The Web Server fetched data from the Database and then served this data to the web browser in JSON. However, the web service did not just embed raw SQL scripts in the same classes which were serving the JSON. This would not have followed the clean separation of duties referred to above, and would have resulted in duplicate code that was very hard to debug and maintain. Instead, we had several classes in the web server that looked after getting data from the database:

  • model classes. These often mirrored the entities in the Database
  • service layer. These classes called SQL stored procedures to get database objects, and converted the database objects to the model classes (C# objects)
  • web server API classes. These classes handled API requests for JSON data, and called upon the service layer for data as needed to respond to the the API requests with data.
One of the benefits to this approach was that if we changed the database server location, language, or stored procedures, the change was needed only in the service layer. The model classes and the web server API classes were un-affected.

I took the lessons learned from building full-stack web applications, and applied them to building iOS applications. Now the service layer fits as follows:
  • model classes. These often mirror the JSON objects
  • service layer. These classes know about the API and make network calls to fetch JSON. When the network calls complete, JSON is converted to native model classes (Objective-C objects).
  • View controller. These classes handle user interaction and ask for data from the service layer as needed.
The astute reader may have just had an "ah-ha" moment when they read about making network calls and doing something when complete. Because the service layer is not a view controller, it need not be running on the main thread, and therefore could make use of synchronous http requests.

Now that we have a good understanding of how to architect an iOS application to fetch and convert JSON data, all that is left is to discuss the interaction between the View Controller and the application data.

Blocks

It is sometimes the case that an iOS application is not very concerned with the staleness of data; the iOS application only fetches data once when it first starts and does not make any subsequent API calls to find out if there is newer data available. In this case, the application only has two states:

  1. no data (after first starting and before network call completion)
  2. there is data (after a successful network call)

We're not always lucky to have such a simple scenario, but if so, an appropriate solution is to use blocks. A block is a piece of code that we can pass as a method parameter, that may get called at some point in the future. A method which belongs to a service class, and which takes a block as a parameter, may look something like this:

typedef void (^JGCResponseBlock) (NSArray* arrayOfObjects, NSError * error);

- (void) fetchDataFromAPIWithResponseBlock:(JGCResponseBlock)responseBlock;

The caller (a View Controller), can call ask the service class to fetch some data for the View Controller. When the data has been fetched (or the fetch fails), the service class uses the block to inform the View Controller. The View Controller may implement the block as follows:

[serviceLayer fetchDataFromAPIWithResponseBlock:^(NSArray * arrayOfObjects, NSError *error) {
        
        if (nil != error) {
            // deal with error appropriately
        }
        else if (nil != arrayOfObjects) {
            // update the user interface
        }

    }];

One of the benefits to using blocks is that the code which deals with the API results is declared inline with the method call which asks for data from the API. But remember that blocks are only the best solution for gluing together the model and the View Controller when the data is not expected to change. If the model objects change, we need another solution.

Delegates and Observers

It is a very common scenario in iOS development that views need to know when the model objects whose information the views are presenting changes. When one object changes state, other objects may be notified either by using a delegate or by using an observer pattern.

Delegates

Delegates are classes which implement a protocol, a protocol which defines methods that an object may use to inform the delegate that a change in state is about to occur or has occurred. Delegate methods which report a change in state should contain with the verbs “will” or “did”, such as

tableView:willDisplayCell:forRowAtIndexPath
tableView:didEndDisplayingCell:forRowAtIndexPath

The class which “calls back” the delegate, must first check to see that the delegate responds to selector before calling the callback method. This may not be necessary if the protocol method is required, but must be done when the protocol method is optional.

Classes which have a delegate should declare the delegate as a weak property. The rationale for specifying the delate’s property attribute as weak is a follows: It is normal that the delegate already has a strong reference to the class for which it is a delegate (for example, a view controller with a reference to a table view, sets itself as a delegate to the table view). If two classes have strong references to each other, neither is ever released, resulting in a memory leak.

Observers

There are two ways to implement observers in iOS:
  1. NSNotificationCenter
  2. Key-Value-Observing

Best practices for using NSNotificationCenter include using constant strings to identify the NSNotification, using the same constant in the notifier and the receiver. Receivers must unsubscribe from the NSNotificationCenter in dealloc or when they are no longer interested in receiving notifications.

In order to use Key-Value-Observing, any programmatic changes to the observed property must occur using one of three mechanisms:
  1. call the synthesized setter, for example:
    self.total = 20;
  2. change the property using Key-Value-Coding, for example:
    [self setValue:20 forKey:@”total”];
    
  3. trigger notification of the observers, for example:
    [self willChangeValueForKey:@”total”];
    total = 20;
    [self didChangeValueForKey:@”total”];
    
The following table should be used as a guide when determining how one object notifies others of its change in state.



Criteria
Delegate
NSNotificationCenter
Key-Value-Observing
a change in one object requires a change in only one other object
YES
NO
OK (1 or more)
a change in one object requires a change in more than one other object
NO
YES
OK (1 or more)
the change in state is something which may be represented by a single property of one specific object
OK (1 or more)
NO
YES
the change in state is something which may not be represented by a single property of one specific object
OK (1 or more)
YES
NO

Let's look at some examples.

You have a model object called Post, which has three four properties: postDate, postText, postImage, and postFavoriteCount. After the object is created from the server JSON, the only property expected to change is the postFavoriteCount. So it would be perfectly acceptable to use Key-Value-Observing (KVO) on the postFavoriteCount property. As many other objects as needed could observe this property, and would be notified when it changed. However, suppose we added a new property called rePostCount, which was also expected to change. Now View Controllers would need to subscribe to two properties via KVO: postFavoriteCount, and rePostCount. As more and more state changes need to be communicated, a better solution would be delegates or NSNotificationCenter.

One more example: you have a table view that needs to update when the underlying data changes. If there is only one object (the View Controller which contains the table view) in the entire application which needs to know when the data changes, then using the delegate pattern to inform the view controller that the table view's data has changed work quite well. However, if more than one view controller needs to know about an underlying data change, then NSNotificationCenter is the only option out of the two.

It is not too hard to read documentation and learn the syntax of Objective-C. Swift is even easier. However, it's also easy to make a lot of mistakes in architectural decisions (or lack of decisions) when first starting as an iOS developer. It's only normal, natural, and expected to make mistakes. After all, that's how we learn. But learning from other's mistakes can often save you time, because you aren't left cleaning up the mess that you would have created, had you made the same mistake. This article should give the novice programmer some rules to follow when figuring out how to turn JSON from a web service into a functioning iOS application.

No comments:

Post a Comment