Remind iOS by Example: RestKit and You

One of those most common and important things mobile applications do today is communicate with an API. In this blog post, we’ll illustrate some of the design choices we made with our network stack, which is built on top of RestKit, by walking through how to implement a new API endpoint.


We recently added the ability to select an icon for your Remind class that will appear in different spots in the UI.

It was a fairly simple integration, so we’ll be walking through the components of this feature with the following steps:

  1. Create a Core Data model for our new data
  2. Request data from the new API endpoint
  3. Translate the response JSON into Core Data entities
  4. Unit test our implementation

###Core Data Setup

We use Core Data at Remind to persist the data retrieved from the API. For all of its faults (pun intended!), we rely heavily on Core Data to sync data updates across our application and provide a reliable read-only view of our application when the user is offline. The nitty-gritty of inserting, updating, and deleting entities across multiple contexts is handled under the hood by RestKit, so our application code can focus mainly on business logic if we just make the right calls into RestKit.

The first thing we have to do for this endpoint integration is prepare Core Data for our new RDAvatar entities. This is as simple as adding an entity to our .xcdatamodel with the appropriate properties. In this case our new entity has a remoteId and a fileUrlString. (We use SDWebImage to download and cache remote images)

Thanks to the magic of mogenerator, at this point we can simply define an associated RDAvatar class with our new entity and hit build, and mogenerator will create a convenient dedicated NSManagedObject subclass to use throughout the codebase. If you’re using Core Data for your application and haven’t heard of mogenerator, I highly suggest you look at that project.

###The Network Request

Now that we have an RDAvatar entity in our object graph, we need to retrieve data from our API to populate them in our local database. There are many of ways to construct network requests; at Remind, we’ve gone the route of defining a class for every request. One reason I like this is that it allows us to parameterize each request in a type-safe manner instead of relying on the consumer to provide the right keys and object types to put into an NSDictionary for parameters. Our next step, then, is to define a new RDAvatarListRequest class that will inherit from our RDAPIRequest base class. RDAPIRequest serves as our bucket-o’-data object that tells the core of our network stack how to make our request.

For our new request, the only parameter is a query string that will filter the list of avatars to only those relevant to the provided search terms.

@interface RDAvatarList : RDAPIRequest

- (instancetype)initWithQuery:(NSString *)query;

@end

The implementation of this class consists of overriding a small set of methods for the basic information about the request, such as the path, method, and parameters.

@implementation RDAvatarListRequest

- (instancetype)init
{
	return [self initWithQuery:nil];
}

- (instancetype)initWithQuery:(NSString *)query
{
    self = [super initWithMethod:RDAPIRequestMethodGet];
    if (self)
    {
	    _query = [query copy];
    }
    return self;
}

- (NSString *)requestPath
{
    return @"groups/avatars";
}

- (NSString *)requestParameters
{
	if ([self.query length] > 0)
	{
		return @{ @"q" : self.query };
	}
	return nil;
}

@end

That’s really all there is to it. Sitting below this level of abstraction is a set of classes that attempts to insulate the rest of the code from knowing anything about RestKit.

  • RDAPICommunicator constructs the correct method call into RestKit based on the parameters of the RDAPIRequest instance, then repackages the response data in an RDAPIResponse instance that is passed back up through the stack.
  • RDAPIRequestDescriptorFactory constructs and registers the appropriate RKRequestDescriptor and RKResponseDescriptor with RestKit for each RDAPIRequest
  • RDAPIDispatcher is the public interface to the rest of the application, which consists entirely of dispatchRequest:completion:.

The details and subtleties of the wrapper described above may be the subject of a future blog post, but that is the general structure of how we’ve quarantined RestKit as much as possible.

###JSON -> RDAvatar

JSON Response:
[
	{
		"id" : 123,
		"file_url" : "some_url.png"
	},
	{
		"id" : 124,
		"file_url" : "some_other_url.png"
	},
	...
]

Now that we’ve defined how to request the data, we need to then translate the API response into our RDAvatar Core Data entities. This is a service that RestKit provides pretty powerfully, and we’ve built a small layer on top to make these incremental changes easier. Many of the RestKit tutorials I found when it was first integrated had one setup method that contained the mapping information for every request the app was going to make. (Naturally, all of these examples only contained a couple of endpoints.) With a large and diverse API, this pattern quickly breaks down. Instead, we’ve distributed the mapping information across many “mapper” classes. These classes conform to the RDAPIResponseMappingProvider protocol, requiring that any implementers are able to construct an RKMapping, which the RDAPIRequestDescriptorFactory mentioned above uses when it registers response descriptors with RestKit. This avoids a monolithic registration method, facilitates code reuse when multiple endpoints return the same kind of object, and has the added benefit of lazily registering descriptors as they’re needed.

In this case, we have a very simple mapping to construct:

@interface RDAvatarMapper : NSObject<RDAPIResponseMappingProvider>

@end

@implementation RDAvatarMapper

- (RKMapping *)rkResponseMapping:(RKObjectManager *)objectManager
{
	// 1
    RKEntityMapping *mapping = [RKEntityMapping mappingForEntityForName:[RDAvatar entityName]
                                                   inManagedObjectStore:objectManager.managedObjectStore];

    // 2
    [mapping addAttributeMappingsFromDictionary:@{
                                                  @"id" : @"remoteId",
                                                  @"file_url" : @"fileUrlString"
                                                  }];

    // 3
    mapping.identificationAttributes = @[ @"remoteId" ];

    return mapping;
}

@end

There are only a few things going on here:

  1. We create a new RKEntityMapping, which tells RestKit that this is a Core Data entity (as opposed to a regular NSObject), and it’s for our RDAvatar entity. (entityName is a function generated by mogenerator)
  2. We provide a dictionary of keys from the API response to properties on our entity, so id from the response is mapped to remoteId on our RDAvatar entity. Likewise, file_url is mapped to fileUrlString.
  3. We provide an identification attribute for the entity. This is our de-duping key. When we get an object from the API whose remoteId matches one we already have in Core Data, we won’t create a new entity - we’ll just update the existing one.

(Bonus: These mappers are also used to handle events from our Pusher integration.)

The only other thing we still need to do is associate our request with this mapping provider; for that, we override another RDAPIRequest method.

@implementation RDAvatarListRequest

- (id<RDAPIRequestMappingProvider>)mappingProvider
{
	return [RDAvatarMapper new];
}

@end

We make similar adjustments to our existing RDGroupMapper, which now contains an embedded avatar object in the JSON response, and we’re done mapping the API response.

Now our application code can create a new RDAvatarListRequest instance and send it off to our API, and we can populate our UI with the avatars that just landed in Core Data.

###What to Test

One of the harder parts of testing an application is figuring out what components to test and at what granularity; it’s important to figure out how you’ll determine that things are working the way you expect. In the case of our API code, “correct” is determined by the state of Core Data after the request completes. After we make our request, we just make sure that the correct entities have been created, updated, or destroyed in Core Data.

The first version of our network stack basically had two classes: RDAPIRequest and RDAPIDispatcher, meaning that RDAPIRequest did basically everything. As such, the first version of our tests essentially ran full integration tests against stubbed data. (Stubbing our network responses with OHHTTPStubs is another topic worthy of its own blog post). With the necessary utilities for sending an asynchronous request and waiting in a unit test, we would have tests like this:

- (void)testRequestCreatesAvatar
{
	[self.avatarStub registerStub];
	[self sendRequestExpectingSuccess:self.avatarRequest];

	RDVerifyEntityCount(RDAvatar, 1);
}

This seemed fine at the time, but it soon became a little heavy. As is the nature with integration tests, there were many points of failure that affected the whole suite of tests at once without pointing to what the real issue was. After restructuring our code into the classes described above, we’ve been able to unit test each component of our network stack in isolation, so that in cases like this RDAvatar addition, all we really need to test is the mapper we’ve implemented.

@interface UTAvatarMapperTests : UTAPITestCase

@property (nonatomic, strong) RDAvatarMapper *subject;

@end

@implementation UTAvatarMapperTests

// These tests will fail if someone renames the entity's properties without also updating the mapper
- (void)testAvatarIdIsMapped
{
    RDAvatar *result = [[self performMapping:self.subject forStub:self.stub] firstObject];
    XCTAssertEqualObjects(result.remoteId, @(self.stub.remoteId));
}

- (void)testFileUrlIsMapped
{
    RDAvatar *result = [[self performMapping:self.subject forStub:self.stub] firstObject];
    XCTAssertEqualObjects(result.fileUrlString, self.stub.fileUrlString);
}

// This test fails if we remove the identificationAttributes from our mapper
- (void)testExistingAvatarsAreUpdated
{
    [self.singleStub generateAvatar];
    RDVerifyEntityCount(RDAvatar, 1);

    [self performMapping:self.subject forStub:self.stub];

    RDVerifyEntityCount(RDAvatar, 1);
}

@end

(Here, performMapping:stub: calls directly into RestKit’s object mapping service, bypassing any network request attempt.)

With this setup, we’ve essentially eliminated the networking aspect of our implementation, which is fine because the networking layer has its own suite of unit tests. These tests simply feed the expected response data through the RestKit mapping service and make sure Core Data is in the state we expect. It gets a little more complicated as relationships are added between entities and our API response contains nested objects, but we’re still able to test the behavior at this fairly modular level.

###Profit

In my opinion, the biggest win of our current network implementation is its approachability. With a few simple overrides and configuration, you can get a new endpoint up and running behind an expressive interface very quickly and with high confidence. Building up a modular wrapper on top of RestKit also grants us the opportunity to replace it with something else in the future without a total rewrite. Later, we may dive into the details of that wrapper or explore our framework for stubbing our API responses for testing, but for now, you have a good look at the tip of our networking iceberg.