Gear is an iPad app for managing equipment inventory and scheduling (a more complete description is on a previous blog post).
This post provides a description of the app’s service layer, the mechanism for its modules and view controllers to connect to its data store. Although Gear is a native iOS app, it requires a persistent network connection to load and alter information — the data store exists on a web server using a LAMP stack. This post won’t go into the details of the server side code, it is an overview of the service layer class in the app’s Objective-C code.
EQRWebData is the app’s service layer class (EQR is the namespace prefix). It is the sole class that makes a network connection to the web server hosting the data store. Here’s what the interface looks like:
It defines a delegate protocol and property:
And it has three public methods, one of which is the constructor:
queryForSingle:parameters:completion are the two ways that controllers make queries to the data store.
query: takes a Selector as an argument and responds by creating a stream of XML data. That data is given to the method’s caller using the method named by the Selector. It’s also passed a completion block so the caller knows when the data stream has ended. Whereas,
queryForSingle: is used to get a single object or when the caller is creating or altering an existing model object.
queryForSingle: doesn’t create a data stream, instead it responds by returning an object as the parameter to a completion block. Both methods are asynchronous and are usually invoked inside a queue created by Grand Central Dispatch. Here is an example of a query being invoked:
Any module calling on the service layer will include an endpoint to describe the exchange of data it is requesting. It’s passed as an
NSString to the query method as its first argument. Currently I have 90 endpoints (yikes!). Here is a random sampling:
Most endpoints require some parameters like an object ID or the property values to be altered or created. The app’s service layer doesn’t need to know about the endpoints. It uses them as the API endpoints when calling on the web server. It’s on the web server that I have 90 different PHP scripts to tackle the business of the queries and send back an XML stream (but I’ll save the server side code for another day).
The web server sends data back as an XML stream. As an NSXMLParserDelegate,
EQRWebData implements the methods to form objects from the XML feed and deliver them back to the controllers. NSXMLParser is a Cocoa streaming parser, so it parses in an event-driven manner.
An NSXMLParser notifies its delegate about the items (elements, attributes, CDATA blocks, comments, and so on) that it encounters as it processes an XML document. It does not itself do anything with those parsed items except report them.
As objects are formed from the XML stream,
EQRWebData hands them to its delegate, one at a time using the EQRWebDataDelegate method
performSelector is a Cocoa method that takes advantage of Objective-C’s dynamic binding.
addAsyncDataItem is the listener function that disperses objects received from the service layer to the controller’s appropriate methods. When
EQRWebData was initially queried, one of the method parameters was a selector which has been returned to the controller and it is now forwarding the data to that method (
pragma clang diagnostic is used to suppress Xcode’s caution about an unrecognized selector).
With that, the service layer has done its job and the controller that invoked it has been given the queried data.
As I continue expanding on this app, these are the issues that I want to work on for the service layer.
- Separate out the XML parsing. There is no reason for EQRWebData to both make network connections and parse XML data. I should make a separate class to employ the NSXMLParserDelegate methods.
- Too many endpoints. Most of them are “get” methods and I only have about a dozen different models. But in the interest of keeping the network activity to a minimum, each model has several different “get” endpoints in order for the caller to specify the granularity of the data needed. Instead I should have one endpoint but with query parameters that allow callers to specify how much of the related data it needs to retrieve. Something I’ve heard described as expressive services.
- Caching and syncing with CloudKit and Core Data. The dependency on a persistent network connection spells disaster when the network goes down. The app needs to cache its data and work from a local data store both for performance reasons and to cut down on the total network activity. I intend to accomplish this and to entirely replace the need for maintaining a web server LAMP stack by switching to CloudKit and Core Data. It my require a change in the data modeling since MySQL is a relational database and CloudKit uses a NoSQL data store. This approach will also allow me to make use of Apple’s remote notifications to ensure that multiple users stay up to date with each other.
- Service layer protocol. With CloudKit in mind, it’s worth defining a protocol that declares the properties and methods expected of a service layer class. I started experimenting with CloudKit/Core Data by making a subclass of EQRWebData and overriding the query methods but this is a perfect situation to use a protocol instead of class inheritance.