Simplifying RESTful API Use & Data Persistence on iOS with Mantle + Realm

Today’s blogpost is by Marcin Kmiec, a freelance software engineer at Toptal, and was originally published on on Toptal’s blog. You can find Marcin on GitHub and Twitter.

If you’d like to share your technical tutorial about Realm, please email Tim!


A large number of modern mobile applications interact with web services in one way or another, and iOS applications are no different. Mantle (a model framework) and Realm (a mobile database) come with the promise of simplifying some of the hurdles in consuming web services through RESTful APIs and persisting data locally.

Every iOS developer is familiar with Core Data, an object-graph and persistence framework from Apple. Apart from persisting data locally, the framework comes with a host of advanced features, such as object change tracking and undo. These features, although useful in many cases, don’t come for free. It requires a lot of boilerplate code, and the framework as a whole has a steep learning curve.

In 2014, Realm, a mobile database, was released and took the development world by storm. If all we need is to persist data locally, Realm is a good alternative. After all, not all use cases require the advanced features of Core Data. Realm is extremely easy to use and as opposed to Core Data, requires very little boilerplate code. It is also thread safe and is said to be faster than the persistence framework from Apple.

In most modern mobile applications, persisting data solves half the problem. We often need to fetch data from a remote service, usually through a RESTful API. This is where Mantle comes into play. It is an open-source model framework for Cocoa and Cocoa Touch. Mantle significantly simplifies writing data models for interacting with APIs that use JSON as their data exchange format.

In this article, we will be building an iOS application that fetches a list of articles along with links to them from the New York Times Article Search API v2. The list is going to be fetched using a standard HTTP GET request, with request and response models created using Mantle. We will get to see how easy it is with Mantle to handle value transformations (eg. from NSDate to string). Once the data is fetched, we will persist it locally using Realm. All this with minimal boilerplate code.

Getting Started

Let’s start by creating a new “Master-Detail Application” Xcode project for iOS named “RealmMantleTutorial”. We will be adding frameworks to it using CocoaPods. The podfile should resemble the following:

pod 'Mantle'
pod 'Realm'
pod 'AFNetworking'

Once the pods are installed we can open the newly created MantleRealmTutorial workspace. As you’ve noticed, the famous AFNetworking framework has been installed too. We will be using it to perform requests to the API.

As mentioned in the introduction, New York Times provides an excellent article search API. In order to use it, one needs to sign up to get an access key to the API. This can be done at https://developer.nytimes.com. With the API key in hand we’re ready to get started with coding.

Before we delve into creating Mantle data models, we need to get our network layer up and running. Let’s create a new group in Xcode and call it Network. In this group we’ll be creating two classes. Let’s call the first one SessionManager and make sure it is derived from AFHTTPSessionManager which is a session manager class from AFNetworking, the delightful networking framework. Our SessionManager class will be a singleton object that will we use to perform get requests to the API. Once the class has been created, please copy the code below into interface and implementation files respectively.

#import "AFHTTPSessionManager.h"

@interface SessionManager : AFHTTPSessionManager

+ (id)sharedManager;

@end
#import "SessionManager.h"

static NSString *const kBaseURL = @"https://api.nytimes.com";

@implementation SessionManager

- (id)init {
    self = [super initWithBaseURL:[NSURL URLWithString:kBaseURL]];
    if(!self) return nil;
    
    self.responseSerializer = [AFJSONResponseSerializer serializer];
    self.requestSerializer = [AFJSONRequestSerializer serializer];
    
    return self;
}

+ (id)sharedManager {
    static SessionManager *_sessionManager = nil;
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sessionManager = [[self alloc] init];
    });
    
    return _sessionManager;
}

@end

The session manager is initialized with the base URL defined in the static kBaseURL variable. It will also use JSON request and response serializers.

Now the second class that we are going to create in the Network group will be called APIManager. It shall be derived from our newly created SessionManager class. Once the necessary data models are created we will add a method to ApiManager that will be used to request a list of articles from the API.

New York Times Article Search API Overview

The official documentation for this excellent API is available at https://developer.nytimes.com/docs/read/article_search_api_v2. What we are going to do is use the following endpoint:

https://api.nytimes.com/svc/search/v2/articlesearch

… to fetch articles found using a search query term of our choosing bounded by a date range. For instance, what we could do is ask the API to return a list of all articles that appeared in the New York Times that had anything to do with basketball in the first seven days of July 2015. According to the API documentation, to do that we need to set the following parameters in the get request to that endpoint:

ParameterValue
q“basketBall”
begin_date“20150701”
end_date“20150707”


The response from the API is quite complex. Below is the response for a request with the above parameters limited to just one article (one item in docs array) with numerous fields omitted for clarity.
json { "response": { "docs": [ { "web_url": "https://www.nytimes.com/2015/07/04/sports/basketball/robin-lopez-and-knicks-are-close-to-a-deal.html", "lead_paragraph": "Lopez, a 7-foot center, joined Arron Afflalo, a 6-foot-5 guard, as the Knicks’ key acquisitions in free agency. He is expected to solidify the Knicks’ interior defense.", "abstract": null, "print_page": "1", "source": "The New York Times", "pub_date": "2015-07-04T00:00:00Z", "document_type": "article", "news_desk": "Sports", "section_name": "Sports", "subsection_name": "Pro Basketball", "type_of_material": "News", "_id": "5596e7ac38f0d84c0655cb28", "word_count": "879" } ] }, "status": "OK", "copyright": "Copyright (c) 2013 The New York Times Company. All Rights Reserved." }

What we basically get in response are three fields. The first one called response contains the array docs, which in turn contains items representing articles. The two other fields are status and copyright. Now that we know how the API works, it’s time to create data models using Mantle.

Introduction to Mantle

As mentioned earlier, Mantle is an open-source framework that significantly simplifies writing data models. Let’s start by creating an article list request model. Let’s call this class ArticleListRequestModel and make sure it is derived from MTLModel, which is a class that all Mantle models should be derived from. Additionally let’s make it conform to the MTLJSONSerializing protocol. Our request model should have three properties of suitable types: query, articlesFromDate, and articlesToDate. Just to make sure our project is well organized I suggest that this class be placed in Models group.

Here’s how the interface file of ArticleListRequestModel should look: ```objc #import “MTLModel.h” #import “Mantle.h”

@interface ArticleListRequestModel : MTLModel

@property (nonatomic, copy) NSString *query; @property (nonatomic, copy) NSDate *articlesFromDate; @property (nonatomic, copy) NSDate *articlesToDate;

@end ```

Now if we look up the docs for our article search endpoint or have a look at the table with request parameters above, we will notice that the names of the variables in the API request differ from those in our request model. Mantle handles this efficiently using the method:

+ (NSDictionary *)JSONKeyPathsByPropertyKey.

Here’s how this method should be implemented in the implementation of our request model:

#import "ArticleListRequestModel.h"

@implementation ArticleListRequestModel

#pragma mark - Mantle JSONKeyPathsByPropertyKey

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"query": @"q",
             @"articlesFromDate": @"begin_date",
             @"articlesToDate": @"end_date"
             };
}

@end

The implementation of this method specifies how the properties of the model are mapped into its JSON representations. Once the method JSONKeyPathsByPropertyKey has been implemented, we can get a JSON dictionary representation of the model with the class method +[MTLJSONAdapter JSONArrayForModels:].

One thing that is still left, as we know from list of parameters, is that the both date parameters are required to be in the format “YYYYMMDD”. This is where Mantle gets very handy. We can add custom value transformation for any property by implementing the optional method +<propertyName>JSONTransformer. By implementing it we tell Mantle how the value of a specific JSON field should be transformed during JSON deserialization. We can also implement a reversible transformer that will be used when creating a JSON from the model. Since we need to transform an NSDate object into a string, we will also make use of NSDataFormatter class. Here is the complete implementation of ArticleListRequestModel class:

#import "ArticleListRequestModel.h"

@implementation ArticleListRequestModel

+ (NSDateFormatter *)dateFormatter {
    NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
    dateFormatter.dateFormat = @"yyyyMMdd";
    return dateFormatter;
}

#pragma mark - Mantle JSONKeyPathsByPropertyKey

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"query": @"q",
             @"articlesFromDate": @"begin_date",
             @"articlesToDate": @"end_date"
             };
}

#pragma mark - JSON Transformers

+ (NSValueTransformer *)articlesToDateJSONTransformer {
    return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, 
    NSError *__autoreleasing *error) {
        return [self.dateFormatter dateFromString:dateString];
    } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {
        return [self.dateFormatter stringFromDate:date];
    }];
}

+ (NSValueTransformer *)articlesFromDateJSONTransformer {
    return [MTLValueTransformer transformerUsingForwardBlock:^id(NSString *dateString, BOOL *success, 
    NSError *__autoreleasing *error) {
        return [self.dateFormatter dateFromString:dateString];
    } reverseBlock:^id(NSDate *date, BOOL *success, NSError *__autoreleasing *error) {
        return [self.dateFormatter stringFromDate:date];
    }];
}

@end

Another great feature of Mantle is that all of these models conform to the NSCoding protocol, as well as implement isEqual and hash methods.

As we have already seen, the resulting JSON from the API call contains an array of objects that represent articles. If we want to model this response using Mantle, we will have to create two separate data models. One would model objects representing articles (docs array elements), and the other would model the whole JSON response except for the elements of the docs array. Now, we don’t have to map each and every property from the incoming JSON into our data models. Let’s suppose we are only interested in two fields of article objects, and those would be lead_paragraph and web_url. The ArticleModel class is rather straightforward to implement, as we can see below.

#import "MTLModel.h"
#import <Mantle/Mantle.h>

@interface ArticleModel : MTLModel <MTLJSONSerializing>

@property (nonatomic, copy) NSString *leadParagraph;
@property (nonatomic, copy) NSString *url;

@end
#import "ArticleModel.h"

@implementation ArticleModel

#pragma mark - Mantle JSONKeyPathsByPropertyKey

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"leadParagraph": @"lead_paragraph",
             @"url": @"web_url"
             };
}

@end

Now that the article model has been defined, we can finish response model definition by creating a model for the article list. Here’s how the class ArticleList response model is going to look.

#import "MTLModel.h"
#import <Mantle/Mantle.h>
#import "ArticleModel.h"

@interface ArticleListResponseModel : MTLModel <MTLJSONSerializing>

@property (nonatomic, copy) NSArray *articles;
@property (nonatomic, copy) NSString *status;

@end
#import "ArticleListResponseModel.h"

@class ArticleModel;

@implementation ArticleListResponseModel

#pragma mark - Mantle JSONKeyPathsByPropertyKey

+ (NSDictionary *)JSONKeyPathsByPropertyKey {
    return @{
             @"articles" : @"response.docs",
             @"status" : @"status"
             };
}

#pragma mark - JSON Transformer

+ (NSValueTransformer *)articlesJSONTransformer {
    return [MTLJSONAdapter arrayTransformerWithModelClass:ArticleModel.class];
}

@end

This class has only two properties: status and articles. If we compare it to the response from the endpoint we will see that the third JSON attribute copyright will not be mapped into the response model. If we look at the articlesJSONTransformer method, we will see that it returns a value transformer for an array containing objects of class ArticleModel.

It is also worth noting that in the method JSONKeyPathsByPropertyKey, the model property articles correspond to the array docs that is nested within JSON attribute response.

By now we should have three model classes implemented: ArticleListRequestModel, ArticleModel, and ArticleListResponseModel.

First API Request

Now that we have implemented all the data models, it is time to get back to the class APIManager to implement the method that we will use to perform GET requests to the API. The method:

- (NSURLSessionDataTask *) getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure

… takes an ArticleListRequestModel request model as a parameter and returns an ArticleListResponseModel in case of success or an NSError otherwise. The implementation of this method uses AFNetworking to perform a GET request to the API. Please note that in order to make a successful API request we need to provide a key that can be obtained as mentioned earlier, by registering at https://developer.nytimes.com.

#import "SessionManager.h"
#import "ArticleListRequestModel.h"
#import "ArticleListResponseModel.h"

@interface APIManager : SessionManager

- (NSURLSessionDataTask *)getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure;

@end
#import "APIManager.h"
#import "Mantle.h"

static NSString *const kArticlesListPath = @"/svc/search/v2/articlesearch.json";
static NSString *const kApiKey = @"replace this with your own key";

@implementation APIManager

- (NSURLSessionDataTask *)getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel
                                              success:(void (^)(ArticleListResponseModel *responseModel))success
                                              failure:(void (^)(NSError *error))failure{
    
    NSDictionary *parameters = [MTLJSONAdapter JSONDictionaryFromModel:requestModel error:nil];
    NSMutableDictionary *parametersWithKey = [[NSMutableDictionary alloc] initWithDictionary:parameters];
    [parametersWithKey setObject:kApiKey forKey:@"api-key"];
    
    return [self GET:kArticlesListPath parameters:parametersWithKey
             success:^(NSURLSessionDataTask *task, id responseObject) {
        
        NSDictionary *responseDictionary = (NSDictionary *)responseObject;
        
        NSError *error;
        ArticleListResponseModel *list = [MTLJSONAdapter modelOfClass:ArticleListResponseModel.class
                                                   fromJSONDictionary:responseDictionary error:&error];
        success(list);
        
    } failure:^(NSURLSessionDataTask *task, NSError *error) {
        
        failure(error);
        
    }];
}

There are two very important things happening in the implementation of this method. First let’s take a look at this line:

NSDictionary *parameters = [MTLJSONAdapter JSONDictionaryFromModel:requestModel error:nil];

What is happening here is that using the method provided by MTLJSONAdapter class we get a NSDictionary representation of our data model. That representation mirrors the JSON that is going to be sent to the API. This is where the beauty of Mantle lies. Having implemented JSONKeyPathsByPropertyKey and <propertyName>JSONTransformer methods in the ArticleListRequestModel class, we can get the correct JSON representation of our data model in no time with just a single line of code.

Mantle also allows us to perform transformations in the other direction too. And that’s exactly what’s happening with the data received from the API. The NSDictionary that we receive is mapped into an object of ArticleListResponseModel class using the following class method:

ArticleListResponseModel *list = [MTLJSONAdapter modelOfClass:ArticleListResponseModel.class fromJSONDictionary:responseDictionary error:&error];

Persisting Data with Realm

Now that we are able to fetch data from a remote API, it is time to persist it. As mentioned in the introduction, we will do it using Realm. Realm is a mobile database and a replacement for Core Data and SQLite. As we will see below, it is extremely easy to use.

To save a piece of data in Realm we first need to encapsulate an object that is derived from RLMObject class. What we need to do now is to create a model class that will store data for single articles. Here’s how easy it is to create such a class.

#import "RLMObject.h"

@interface ArticleRealm : RLMObject

@property NSString *leadParagraph;
@property NSString *url;

@end

And this could be basically it, the implementation of this class could remain empty. Please note that the properties in the model class have no attributes like nonatomic, strong, or copy. Realm takes care of those and we need not worry about them.

Since the articles that we can get are modelled with the Mantle model Article it would be convenient to initialize ArticleRealm objects with objects of class Article. To do that we will add initWithMantleModel method to our Realm model. Here’s the complete implementation of ArticleRealm class.

#import "RLMObject.h"
#import "ArticleModel.h"

@interface ArticleRealm : RLMObject

@property NSString *leadParagraph;
@property NSString *url;

- (id)initWithMantleModel:(ArticleModel *)articleModel;

@end
#import "ArticleRealm.h"

@implementation ArticleRealm

- (id)initWithMantleModel:(ArticleModel *)articleModel{
    self = [super init];
    if(!self) return nil;
    
    self.leadParagraph = articleModel.leadParagraph;
    self.url = articleModel.url;
    
    return self;
}

@end

We interact with the database using objects of class RLMRealm. We can easily get a RLMRealm object by invoking the method [RLMRealm defaultRealm]. It is important to remember that such an object is valid only within the thread it was created on and can not be shared across threads. Writing data to Realm is quite straightforward. A single write, or a series of them, need to be done within a write transaction. Here’s a sample write to the database:

RLMRealm *realm = [RLMRealm defaultRealm];
    
ArticleRealm *articleRealm = [ArticleRealm new];
articleRealm.leadParagraph = @"abc";
articleRealm.url = @"sampleUrl";
    
[realm beginWriteTransaction];
[realm addObject:articleRealm];
[realm commitWriteTransaction];

What happens here is the following. First we create a RLMRealm object to interact with the database. Then an ArticleRealm model object is created (please bear in mind that it is derived from RLMRealm class). Finally to save it, a write transaction begins, the object is added to the database, and once it is saved the write transaction is committed. As we can see, write transactions block the thread on which they are invoked. While Realm is said to be very fast, if we were to add multiple objects to the database within a single transaction on the main thread, that could lead to the UI becoming unresponsive until the transaction is finished. A natural solution to that is to perform such a write transaction on a background thread.

API Request and Persisting Response in Realm

This is all the information we need to persist articles using Realm. Let’s try to perform an API request using the method:

- (NSURLSessionDataTask *) getArticlesWithRequestModel:(ArticleListRequestModel *)requestModel success:(void (^)(ArticleListResponseModel *responseModel))success failure:(void (^)(NSError *error))failure

… and Mantle request and response models in order to get New York Times articles that had anything to do (as in the earlier example) with basketball and were published in the first seven days of June 2015. Once the list of such articles is available, we will persist it in Realm. Below is the code that does that. It’s placed in the viewDidLoad method of the table view controller in our app.

ArticleListRequestModel *requestModel = [ArticleListRequestModel new]; // (1)
requestModel.query = @"Basketball";
requestModel.articlesToDate = [[ArticleListRequestModel dateFormatter] dateFromString:@"20150706"];
requestModel.articlesFromDate = [[ArticleListRequestModel dateFormatter] dateFromString:@"20150701"];

[[APIManager sharedManager] getArticlesWithRequestModel:requestModel   // (2)
                        success:^(ArticleListResponseModel *responseModel){
  
  dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ // (3)
    @autoreleasepool {
      
    RLMRealm *realm = [RLMRealm defaultRealm];
    [realm beginWriteTransaction];
    [realm deleteAllObjects];
    [realm commitWriteTransaction];
    
    [realm beginWriteTransaction];
    for(ArticleModel *article in responseModel.articles){
      ArticleRealm *articleRealm = [[ArticleRealm alloc] initWithMantleModel:article]; // (4)
      [realm addObject:articleRealm];
    }
    [realm commitWriteTransaction];
     
      dispatch_async(dispatch_get_main_queue(), ^{ // (5)
        RLMRealm *realmMainThread = [RLMRealm defaultRealm]; // (6)
        RLMResults *articles = [ArticleRealm allObjectsInRealm:realmMainThread];
        self.articles = articles; // (7)
        [self.tableView reloadData];
      });
    }
  });
  
} failure:^(NSError *error) {
  self.articles = [ArticleRealm allObjects];
  [self.tableView reloadData];
}];

First, an API call is made (2) with a request model (1), which returns a response model that contains a list of articles. In order to persist those articles using Realm we need to create Realm model objects, which takes place in the for loop (4). It is also important to notice that since multiple objects are persisted within a single write transaction, that write transaction is performed on a background thread (3). Now, once all the articles are saved in Realm, we assign them to the class property self.articles (7). Since they’re going to be accessed later on the main thread in TableView datasource methods, it is safe to retrieve them from the Realm database on the main thread too (5). Again, to access the database from a new thread, a new RLMRealm object needs to be created (6) on that thread.

If getting new articles from the API fails for whatever reason, the existing ones are retrieved from the local storage in the failure block.

Wrapping Up

In this tutorial we learned how to configure Mantle, a model framework for Cocoa and Cocoa Touch, in order to interact with a remote API. We also learned how to locally persist data retrieved in the form of Mantle model objects using Realm mobile database.

In case you want to try out this application, you can retrieve the source code from its GitHub repository. You will need to generate and provide your own API key before running the application.


The original post can be found on Toptal’s blog. Thanks Marcin!