RestKit is a well-known framework without much documentation. Even with its reference manual and accompanying blog posts, not much has been covered, especially in terms of practical usage. Not every developer has the time to indulge in figuring out RestKit on his own before starting a time-constrained project. Sounds familiar?
Learning a new framework comes with its required steps. Before jumping into RestKit libraries, object mapping fundamentals, and data modeling, we need to make the proper introductions. This chapter will start with a simple usage example to warm up the crowd, before elaborating on the whats, whys, and hows of RestKit, in addition to its components. This compact introduction will already have demonstrated how select real-life examples can provide the required insight into the world of RestKit.
As you know, nothing in this world is perfect, and so are REST APIs. Every single API I worked with has its own glitches and bottlenecks. So, we will discuss some of the possible bottlenecks with APIs, how to overcome them, and we will experience a few in the API that we will use in the example.
We can show a simple example of using RestKit by loading this kind of JSON:
[ { "hostname": "sandbox.mongohq.com", "name": "Test", "plan": "Sandbox", "port": 10097, "shared": true }, { "hostname": "second.mongohq.com", "name": "Second", "plan": "Second", "port": 10097, "shared": true } ]
And mapping it to a list of database objects:
@interface MDatabase : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) NSString *plan; @property (nonatomic, strong) NSString *hostname; @property (nonatomic, strong) NSNumber *port; @end
Would be just invoking this piece of code:
RKObjectManager *manager = [RKObjectManager sharedManager]; [manager getObjectsAtPath:@"/databases" parameters:nil success:^(RKObjectRequestOperation *operation, RKMappingResult *mappingResult) { NSLog(@"Loaded databases: %@", [mappingResult array]); } failure:^(RKObjectRequestOperation *operation, NSError *error) { NSLog(@"Error on loading: %@", [error localizedDescription]) }];
As we see here, not much code. Of course, we will need some additional pre-setup to get this working, which we will cover later in this and the following chapter.
"I once told an Objective-C joke, but nobody got the message."
Many of us are being introduced with the concept of networking in iOS and Objective-C by playing with
NSURLConnection
. It's a base class for making any outgoing HTTP connection. However, after using it several times in a project, you may start building your personal networking library, of course, under the hood; it's still the same NSURLConnection
, but most likely, you would write your own wrappers for it with additional bells and whistles. In addition, while using it, you may experience different bugs, glitches, and performance issues. Also, while using it in different projects, you will probably modify the code, find and fix bugs in it, but you will experience a lot of hassles in maintaining a "one codebase" of such a homegrown library.
So my personal opinion is "Don't re-invent the wheel. Don't re-invent a bicycle. Unless you really need a very custom one, with a big front wheel (as shown in the next figure), which might happen only in 1 percent of cases."
Try to keep things simple. In our case, the working bicycle can be a library called AFNetworking, which is a very useful networking library for iOS and OS X. The framework is built on top of Apple's Foundation technologies such as NSURLConnection
, NSOperation
, and others. It has a modular architecture and a well-designed API, which is quite easy to use. While being simple and easy-to-use, it also supports block-based programming, file uploads, reachability, and lots of other useful tasks that any developer might need. It is used by thousands of app developers and is highly maintained.
Now RestKit itself is a framework for implementing clients of RESTful web services, and feels like AFNetworking is on steroids. It provides a simple interface to build network communication while mapping HTTP requests and responses. Additionally, it has a powerful object-mapping engine that seamlessly integrates with the Core Data. It has an elegant, well-designed, fully documented, and fully tested set of APIs magically allowing easy accessing and modeling RESTful resources.
It greatly simplifies the application development by providing a solution by interconnecting your application's data model with JSON or XML documents, provided by a web service you are communicating with. It shifts a lot of logic for making requests and mapping the data to the library, thus giving a developer the ability to keep an application code simpler and less cluttered. It speeds up the development process by giving solutions and patterns for common problems that one can face, such as working with Core Data and doing network-related coding.
When thinking whether to use a RestKit library in your next project, consider a few things:
Not every API will work with a RestKit library, especially the ones that follow the RPC (Remote Procedure Call) paradigm.
Your backend web-service API is more or less RESTful. You can describe interactions with your web application with the CRUD (Create, Read, Update, and Delete) operations on resources.
"Don't use a sledgehammer to crack a nut." If you just need a one-time request to a simple JSON in your app, reconsider using a RestKit library in favor of using something simple, such as AFNetworking.
While developing a library, think if you can minimize a footprint of it and exclude dependencies on big third-party libraries, such as RestKit.
We can compare RestKit to some other popular or similar solutions:
AFIncrementalStore (https://github.com/AFNetworking/AFIncrementalStore): A new library from the creator of AFNetworking that works in a tight connection with Core Data. It is not yet very customizable, but still in its early development and has some possible bugs/performance issues.
MagicalRecord mappers (https://github.com/magicalpanda/MagicalRecord): It is still in development, not well-documented, and not actively used by developers.
Parse (https://www.parse.com): Can be used only with their web services.
KeyValueObjectMapping (https://github.com/dchohfi/KeyValueObjectMapping): It is a small new library that is not documented yet, not fully-tested, and possibly has some performance issues.
SLRESTfulCoreData (https://github.com/OliverLetterer/SLRESTfulCoreData): It is new and documentation is a bit confusing, and the code is not documented.
Mantle (https://github.com/github/Mantle/): An interesting library from GitHub for creating a model layer. It is designed to be used more as in-memory storage, doesn't connect easily with Core Data, and needs some experience to set up and configure every model.
As we can see here, there are numerous libraries that do mapping of objects, or simplify the development by making hidden requests while fetching data from Core Data. Some of them are quite interesting to check and study—depending on a type of project one is doing. The downside of the earlier-mentioned libraries is that these solutions are relatively new, still in active development, or quite complex to set up.
RestKit ships as a single framework to the end user, but internally, it is composed by several interconnected components. We have described the parts of RestKit in the following list:
Object manager: One of the main components of a RestKit library. It works as a bridge between it and other components, and provides one-line methods for "getting the work done".
Object mapping: One of the main functional components of RestKit. The object mapping system enables a mechanism to express the transformation of objects between representations using KVC and the dynamic features of the Objective-C runtime.
Networking: This feature integrates object mapping with the HTTP network layer provided by AFNetworking. It has the ability to make serialization and deserialization of JSON/XML objects, bind mapping descriptions with HTTP requests and responses, generate URLs from path patterns, route, and serialize local objects into HTTP parameters.
Core Data: This component is responsible for the integration between the object mapping and networking components and Apple's Core Data framework. Mostly, it includes a special Core-Data-related implementation of some parts from the object mapping and networking layers, and also specific functionality for the relationship connection of managed objects.
Search: Specific parts related to Core Data components that are responsible for indexing and searching of managed objects. It includes a tokenizer, indexer, and API for generating search predicates to be used while querying indexed objects.
Testing: Helps developing unit tests with ease by providing helpers and mocking abilities for RestKit components. Also includes helpers to build and use test fixtures.
We can describe how you interact with RestKit by looking at the following sample sequence diagram for getting data:
When you, as a
Caller, wish to get data from a
Remote web service, you ask for it on RestKit. It then decides on a strategy and gets the paths and mapping information by checking a configuration for the particular type of objects that you want. RestKit uses AFNetworking under the hood to do the actual data retrieving from the Remote and parsing in to NSDictionary
or NSArray
. AFNetworking itself checks with NSURLCache if it should make a request again, or use the cache. (We'll discuss this in detail in Chapter 4, Advanced Stuff in the HTTP Caching section). AFNetworking then gives back the response to a RestKit, along with all additional information, which were gathered during the "request-response-parse sequence".
If you're not using Core Data for this type of objects (we can call it "In-Memory object"), RestKit creates new instances of the object, and maps a response data to it. It then returns the object(s) back to the Caller.
Now if you are using Core Data, and the object is a Managed object, RestKit will first check with the Core Data if it already has an object with a similar ID. It will also check if the object in response has a deleted
flag. It will then do the mapping and update/delete the particular object, and check how to deal with orphan objects. At the end, it will return the resultant objects to the user. Core Data itself will notify all its observers via Key Value Observing (KVO) about the changes.
It used to be quite hard to add third-party libraries to Mac or iOS projects. You would have to deal with all sorts of dependencies, configuring special behavior, and spend days on integrating big libraries to your project.
This was not an issue for some other platforms. C# with Visual Studio has had the NuGet package manager for quite a while. And Ruby has its RubyGems with Bundler. Recently, the situation has changed for iOS and Mac app developers. Highly inspired by Ruby's Bundler, a new package manager for us arrived—CocoaPods. It is the best way to manage library discrepancies in Objective-C projects.
Now in comparison to RubyGems' Gemfile
, CocoaPods uses a so-called Podfile
, where a developer lists the names of a library he is willing to use and his version. By the way, it uses
Podspec
files to describe how a particular library should be integrated with your project. Actually, CocoaPods is using an Xcode Workspace for the integration between your project and a Pods
project, which includes all third-party libraries.
If you have never installed a CocoaPods package manager before, let's do so! You start by executing the following commands:
$ [sudo] gem install cocoapods $ pod setup
The pod
is an executable package, which is installed with CocoaPods.
Next, we want to search the Spec
repository using the following command to get, which version of RestKit is available as of today:
$ pod search RestKit
The result might look like this:
bash-3.2$ pod search RestKit -> RestKit (0.20.3) RestKit is a framework for consuming and modeling RESTful web resources on iOS and OS X. pod 'RestKit', '~> 0.20.3' - Homepage: http://www.restkit.org - Source: https://github.com/RestKit/RestKit.git - Versions: 0.20.3, 0.20.2, 0.20.1, 0.20.0, 0.20.0-rc1, 0.20.0-pre6, 0.20.0pre5, 0.20.0-pre4, 0.20.0-pre3, 0.20.0-pre2, 0.20.0-pre1, 0.10.3, 0.10.2, 0.10.1, 0.10.0 [master repo] - Sub specs: - RestKit/Core (0.20.3) - RestKit/ObjectMapping (0.20.3) - RestKit/Network (0.20.3) - RestKit/CoreData (0.20.3) - RestKit/Testing (0.20.3) - RestKit/Search (0.20.3) - RestKit/Support (0.20.3)
In addition, you can also hit your browser to view the official website of CocoaPods (http://CocoaPods.org), where you will be able to use the web-based search and get more info on the available packages and CocoaPods news.
Now change to the directory of your Xcode project, and create (or edit) your Podfile
with your favorite text editor and add RestKit (or create it using Xcode if you like):
$ cd /path/to/MyProject $ nano Podfile # Platform - ios, mac platform :ios, '5.1' # List of libraries to install pod 'RestKit', '~> 0.20.3' # Testing and Search are optional components pod 'RestKit/Testing' pod 'RestKit/Search'
By specifying the platform and its version we want to be sure that all libraries and dependencies we are using will smoothly run on the target.
Now with RestKit, ~> 0.20.3
in the previous command-line snippet, means we want to use at least Version 0.20.3 or advanced in the range of 0.20.X. If you skip specifying the version, CocoaPods will install the latest.
Tip
If you enter pod
'RestKit', :head
, CocoaPods will install the library from the latest sources. Or you can provide your source path (if you forked it, for example) by entering the following line of code:
pod
'RestKit',
:git
=>
'https://github.com/RestKit/RestKit.git'
Now it's time to install it in your project. Just run the following command:
$ pod install
And you will probably see the following output:
bash-3.2$ pod install Analyzing dependencies Downloading dependencies Installing AFNetworking (1.3.2) Installing RestKit (0.20.3) Installing SOCKit (1.1) Installing TransitionKit (1.1.1) Generating Pods project Integrating client project
CocoaPods downloads the third-party code and creates a new workspace—a file named YourProject.xcworkspace
. From now on, you will use the .xcworkspace
file to open your project in Xcode.
As you can also see, CocoaPods will install some libraries (such as SOCKit) that we did not ask for. They are dependencies of a RestKit library, and are specified in a RestKit's Podspec
file.
Tip
Run pod update
to fetch and install the latest versions of packages. In addition, running [sudo] gem update cocoapods
will update the CocoaPods manager to the latest version.
Once again, do not forget that you need to use workspace from now on. So to open your project in Xcode, one can shoot the following command in the terminal:
$ open MyProject.xcworkspace
The following screenshot shows the sample project tree in the workspace after installing RestKit and few other libraries.
Add the following line to your .pch
file (it is a precompiled header file, which is automatically included in all source files of your project) to be able to use all RestKit components through your code:
#import <RestKit/RestKit.h>
Tip
If you've worked previously with RestKit 0.10, you should know that with the release of Version 0.20, it had major API changes, which are backwards incompatible. Consider checking RestKit's Wiki article Upgrading from v0.10.x to v0.20.0 in the following link:
https://github.com/RestKit/RestKit/wiki/Upgrading-from-v0.10.x-to-v0.20.0
Please note that if your installation fails, it may be because you are installing with a version of Git lower than what CocoaPods is expecting. Please ensure that you are running Git 1.8.0 or higher by executing git --version
. You can get a full picture of the installation details by executing pod install --verbose
.
If you want to install RestKit as a Git submodule or from a release package, the best way is to follow the instructions on RestKit's Wiki article Installing RestKit v0.20.x as a Git Submodule in the following link:
https://github.com/RestKit/RestKit/wiki/Installing-RestKit-v0.20.x-as-a-Git-Submodule
"Three DBAs walk into a NoSQL bar. A little while later they walk out because they couldn't find a table."
For our examples in this book, we will use a service in a cloud called MongoHQ. It's the most powerful platform for MongoDB hosting. Apart from providing one of the best MongoDB hosting solutions, they recently released a beta REST API for accessing their services. This is quite interesting for using in mobile clients, as accessing directly a MongoDB server is not the easiest of tasks.
MongoDB's main difference from "classical" relational databases is that instead of storing data in tables, MongoDB stores structured data as JSON-like documents with dynamic schemas (MongoDB calls the format BSON), making the integration of data in certain types of applications easier and faster.
The nice part about it is if you are not sure beforehand on what your data will look like, the document-type databases are a weapon of choice. You can change the structure with ease almost on the fly; you don't need to run any migration scripts. This greatly simplifies development in the early stages and/or the startup phase.
For our basic example, we will check the status of MongoHQ servers. For showing statuses, MongoHQ uses the Stashboard web app, which you can access at http://status.mongohq.com.
The following screenshot (cropped) shows the status page of the MongoHQ servers:
An example of Stashboard App usage is Twilio, with the status page at http://status.twilio.com, and the Status API endpoint at http://status.twilio.com/api/v1/.
The documentation for a Stashboard API can be found at https://stashboard.readthedocs.org/en/latest/restapi.html.
To keep going, first let's define our StatusItem
object, looking at a possible data we will need:
// StatusItem.h @interface StatusItem : NSObject @property (nonatomic, strong) NSString *name; @property (nonatomic, strong) NSString *itemDescription; @property (nonatomic, strong) NSDate *timestamp; @property (nonatomic, strong) NSString *eventMessage; @property (nonatomic, strong) NSString *statusName; @property (nonatomic, strong) NSString *imageUrl; @end // StatusItem.m @implementation StatusItem // for better debug information output - (NSString *)description { return [NSString stringWithFormat:@"%@ - %@", self.name, self.eventMessage]; } @end
Now let's try and load the current statuses:
// Method to load the status items - (void)refresh { // Setup the object mapping RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[StatusItem class]]; // From JSON -> To property [mapping addAttributeMappingsFromDictionary:@{ @"name" : @"name", @"description" : @"itemDescription", @"current-event.status.name" : @"statusName", @"current-event.status.image" : @"imageUrl", @"current-event.timestamp" : @"timestamp", @"current-event.message" : @"eventMessage", }]; // Define the response mapping // Map response with any status code in 2xx NSIndexSet *statusCodes = RKStatusCodeIndexSetForClass(RKStatusCodeClassSuccessful); RKResponseDescriptor *responseDescriptor = [RKResponseDescriptor responseDescriptorWithMapping:mapping method:RKRequestMethodAny pathPattern:@"/api/v1/services" keyPath:@"services" statusCodes:statusCodes]; // Prepare the request operation NSURLRequest *request = [NSURLRequest requestWithURL:[NSURL URLWithString:@"http://status.twilio.com/api/v1/services"]]; RKObjectRequestOperation *operation = [[RKObjectRequestOperation alloc] initWithRequest:request responseDescriptors:@[responseDescriptor]]; // Set on completion and on error blocks [operation setCompletionBlockWithSuccess:^(RKObjectRequestOperation *operation, RKMappingResult *result) { NSLog(@"Loaded items: %@", [result array]); } failure:^(RKObjectRequestOperation *operation, NSError *error) { NSLog(@"Failed with error: %@", [error localizedDescription]); }]; [operation start]; //Fire the request }
Tip
Downloading the example code
You can download the example code files for all Packt books you have purchased from your account at http://www.packtpub.com. If you purchased this book elsewhere, you can visit http://www.packtpub.com/support and register to have the files e-mailed directly to you.
In this example, we are using a
RKObjectRequestOperation
object. It performs a base operation in all RestKit requests. Even for more high-end user-friendly methods, it will be run under the hood.
When we run the example, we see the debugger output:
Loaded items: ( "Aaron - All systems operational.", "Alex - Alex is operational.", "Arrow - All databases accessible and operating normally." )
If we put it in a table view (we will discuss about integrating RestKit and user interface best practices in Chapter 2, Modeling and Loading Remote Objects), it will look like the next screenshot:
The object mapping engine of a RestKit is built on the KVC informal protocol, which is foundational to numerous Cocoa technologies, such as Key-Value observing, bindings, and Core Data. After the response body was parsed, RestKit relies on KVC to identify the content that can be mapped and dynamically updates the attributes and relationships of your local domain objects with the appropriate content. Before diving into the details of RestKit's object mapping system, be sure to get familiar with Apple's Key-Value Coding and go through its programming guide at the following link:
Using a highly dynamic Objective-C runtime, RestKit examines the type of source and destination properties of object and performs appropriate type transformations. For example, when a JSON is parsed and a source key path created_at
(with a string content) is configured to be mapped to a destination key path, creationDate
(this is an NSDate
property on a target object), RestKit will transform the date from a string into an NSDate
property using an
NSDateFormatter
. The other transformations can be string to number and vice versa, or a developer can build his own transformation strategy, if needed.
The mapper also fully supports relationship mappings, where nested to-one or to-many child objects are mapped recursively.
Let's discuss our basic example. We were loading data from the Status API endpoint, http://status.twilio.com/api/v1/services. If we visit this URL with our web browser, we see a JSON output similar to the following code:
{ "services": [ { "current-event": { "informational": false, "message": "All systems operational.", "sid": "ag5tb25nb2hxLXN0YXR1c3INCxIFRXZlbnQYi-gUDA", "status": { "description": "The service is up", "id": "up", "image": "http:\/\/status.mongohq.com\/images\/status\/tick-circle.png", "level": "NORMAL", "name": "Up", "url": "http:\/\/status.mongohq.com\/api\/v1\/statuses\/up" }, "timestamp": "Wed, 13 Mar 2013 18:10:21 GMT", "url": "http:\/\/status.mongohq.com\/api\/v1\/services\/aaron\/events\/ag5tb25nb2hxLXN0YXR1c3INCxIFRXZlbnQYi-gUDA" }, "description": "MongoDB Server", "id": "aaron", "name": "Aaron", "url": "http:\/\/status.mongohq.com\/api\/v1\/services\/aaron" }, { "current-event": { "informational": false, "message": "Alex is operational.", "sid": "ag5tb25nb2hxLXN0YXR1c3INCxIFRXZlbnQY0coWDA", "status": { "description": "The service is up", "id": "up", "image": "http:\/\/status.mongohq.com\/images\/status\/tick-circle.png", "level": "NORMAL", "name": "Up", "url": "http:\/\/status.mongohq.com\/api\/v1\/statuses\/up" }, "timestamp": "Sat, 20 Apr 2013 15:23:20 GMT", "url": "http:\/\/status.mongohq.com\/api\/v1\/services\/alex\/events\/ag5tb25nb2hxLXN0YXR1c3INCxIFRXZlbnQY0coWDA" }, "description": "A sandbox environment for MongoHQ.", "id": "alex", "name": "Alex", "url": "http:\/\/status.mongohq.com\/api\/v1\/services\/alex" } ] }
When RestKit loads this JSON, first of all it parses it to the NSDictionary
or NSArray
response object. Then, looking at the mapping we provided, it will make a KVC query on the response object. If the value is found, it will check its type as well as the type of our target property. If the types don't match, RestKit will try to transform it. If one of such transformations is a timestamp mapping, which in JSON is a string, and on the target property it is NSDate
, RestKit will parse the JSON string in to NSDate
with default (or custom provided) NSDate
formatter(s). If the parsing is successful, it will update our destination property.
Let's check our mapping again line by line:
We create the mapping for the StatusItem
class:
RKObjectMapping *mapping = [RKObjectMapping mappingForClass:[StatusItem class]];
We tell how to map JSON objects to properties by providing an NSDictionary
to the addAttributeMappingsFromDictionary
method:
[mapping addAttributeMappingsFromDictionary:@{
Map name
from JSON to the property name
:
@"name": @"name",
We can't use some names for properties, such as id
or description
. So, we named description
from JSON as itemDescription
property:
@"description": @"itemDescription",
Here, RestKit will use KVC to get the value (asking name
from status
from current-event
):
@"current-event.status.name" : @"statusName", @"current-event.status.image": @"imageUrl",
It will parse the timestamp
string in JSON and store it as an NSDate
property:
@"current-event.timestamp": @"timestamp",
And here again it will use KVC to get message
and store it in the eventMessage
property:
@"current-event.message": @"eventMessage", }];
Now that wasn't hard, was it?
In this chapter, we discovered what is RestKit, its components, and why it's good to keep things simple. We discovered a CocoaPods library manager and installed RestKit through it. We covered basic data modeling techniques, and tried a simple example by sending a request to a status page and getting parsed and mapped objects in response.
The next chapter will cover more about how to configure the RestKit, do the RESTful object manipulation, and integrate the code with our user interface, in detail. So let's move on!