I’ve put my new networking code in a small new codebase, and as I mentioned last week. There were a few extras that I built for it that I wanted to mention. Writing in Swift for a week was nice, but it’s back to home territory, where I can be extra effective.

Multipart Requests

One of the first roadblocks I hit when using the new networking library was multipart requests. A multipart request is just a request with a special body that can have text and values mixed in with raw data. It’s ideal for uploading images and other big files. This document at the W3C explains the standard in an effective and accessible way.

To support multipart requests, I needed a new request builder, and I needed SKSendableRequest to accept an injected request builder, so I changed its primary initializer to accept a request builder and changed its old initializer to be a convenience method.

@implementation SKSendableRequest

- (instancetype)initWithRequestBuilder:(id<SKRequestBuilder>)requestBuilder {
    self = [super init];
    if (!self) return nil;

    _requestBuilder = requestBuilder;

    return self;
}

- (instancetype)initWithRequestTemplate:(id<SKRequestTemplate>)template {
    SKRequestBuilder *requestBuilder = [[SKRequestBuilder alloc] initWithRequestTemplate:template];
    return [self initWithRequestBuilder:requestBuilder];
}

Multipart requests also send a boundary as part of the Content-Type ; this boundary defines where each “part” ends and the next begins. This is sent up in the header of the request itself.

- (NSDictionary *)headers {
    return @{
             @"Accept": @"application/json",
             @"Content-Type": self.contentType,
             };
}

- (NSString *)contentType {
    return [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", self.boundary];
}

- (NSString *)boundary {
    return //a random string here
}

This boundary property was also exposed as an optional part of the SKRequestTemplate protocol so that the request builder could access it. (It’s also added to the safe template as well.) Request templates that don’t need can ignore it. For the multipart request builder, it’s initialized with a template and some data.

@implementation SKMultipartRequestBuilder <SKRequestBuilder>

- (instancetype)initWithRequestTemplate:(id<SKRequestTemplate>)template data:(NSData *)data {
    self = [super init];
    if (!self) return nil;

    _safeTemplate = [[SKSafeRequestTemplate alloc] initWithTemplate:template];
    _data = data;

    return self;
}

Most of this request builder is similar to the normal request builder, but building the HTTP body is different. This particular multipart request builder is designed for only one “part”, but it could be generalized to accept multiple parts.

- (NSData *)HTTPBody {
    NSMutableData *body = [NSMutableData data];
    [body appendData:[self.bodyBeforePart dataUsingEncoding:NSUTF8StringEncoding]];
    [body appendData:self.data];
    [body appendData:[self.bodyAfterPart dataUsingEncoding:NSUTF8StringEncoding]];
    return body;
}

- (NSString *)bodyBeforePart {
    NSMutableString *string = [NSMutableString string];
    [string appendFormat:@"--%@\r\n", self.boundary];
    [string appendFormat:@"Content-Disposition: form-data; name=\"attachment\"; filename=\"filename\"\r\n"];
    [string appendFormat:@"Content-Type: %@\r\n\r\n", @"image/jpeg"];
    return string;
}

- (NSString *)bodyAfterPart {
    return [NSString stringWithFormat:@"\r\n--%@--\r\n", self.boundary];
}

- (NSString *)boundary {
    return self.safeTemplate.boundary;
}

Separating out request construction from request sending early in the process of designing this networking code made it obvious exactly where multipart request construction fits into the structure of the library.

Paginatable Requests

Some requests are paginatable, which means they have a page parameter that increments by 1. Let’s imagine a endpoint that gets the followers of a user.

@implementation SKFollowersRequest <SKRequestTemplate>

- (instancetype)initWithUserID:(NSString *)userID {
    self = [super init];
    if (!self) return nil;

    _userID = userID;

    return self;
}

- (NSURL *)baseURL {
    return [NSURL URLWithString:@"api.khanlou.com"];
}

- (NSDictionary *)parameters {
    return @{};
}

- (NSString *)path {
    return [NSString stringWithFormat:@"users/%@/followers", self.userID];
}

@end

Right now, this request is awesome. It’s initialized with everything it needs, it can’t be modified, and it’s easy to read and process. If we wanted to paginate this request, we’d have to add an extra parameter called page to the request. We’d need either a mutable property called page that we could update, or we include it in the initializer, preventing the mutation.

What if we had three more endpoints that were all paginated in the same way? Now we’ve got some duplication, and we’d love handle pagination in some kind of generic way.

Here is the part of the blog post where I pretend to propose using inheritance to solve the problem, and then describe why it’s a bad idea. But inheritance doesn’t even make sense here. We could subclass all our paginatable requests from one class, but then we’d have to initialize with the right page and merge each request’s parameters with its superclasses. It wouldn’t actually save us anything.

Ideally, the request template wouldn’t even know that it was paginatable. To that end, let’s use decoration to add a page parameter to any request we want. Let’s start with an initializer that takes a template and a page. Note that SKPaginatableRequest takes a template but also conforms to templateness. This is the pattern from Nestable.

@implementation SKPaginatableRequest <SKRequestTemplate>

- (instancetype)initWithRequestTemplate:(id<SKRequestTemplate>)template page:(NSInteger)page {
    self = [super init];
    if (!self) return nil;

    _template = template;
    _page = page;

    return self;
}

Let’s include a convenience initializer for the first page. We’re not monsters.

- (instancetype)initWithRequestTemplate:(id<SKRequestTemplate>)template {
    return [self initWithRequestTemplate:template page:1];
}

If you’ll remember, the SKRequestTemplate protocol requires a baseURL, so we have to add that to keep the compiler happy.

- (NSURL *)baseURL {
    return self.template.baseURL;
}

For all of the other parameters, like method, path, etc, they’re optional, so we don’t need to supply implementations for them. Objective-C allows us to forward any messages to a “friend”, with -forwardingTargetForSelector:. We know that SKSafeRequestTemplate wraps each property with -respondsToSelector: checks, so we just have to make sure to update the implementation of -respondsToSelector: as well.

- (BOOL)respondsToSelector:(SEL)aSelector {
    return [super respondsToSelector:aSelector] || [self.template respondsToSelector:aSelector];
}

If the runtime can’t find a particular implementation in this class, it should just go to the template. If a method can’t be found there either, it will just blow up, as expected.

- (id)forwardingTargetForSelector:(SEL)aSelector {
    return self.template;
}

For the parameters property, we want to do something special. We’ll get the page number, and add all the parameters from the request itself. Note the order: we won’t overwrite anything from the parameters the original request gives us. Instead, we allow the original request to overwrite our parameters.

- (NSDictionary *)parameters {
    NSMutableDictionary *dictionary = [@{@"page": self.pageAsString} mutableCopy];
    if ([self.template respondsToSelector:@selector(parameters)]) {
        [dictionary addEntriesFromDictionary:self.template.parameters];
    }
    return [dictionary copy];
}

- (NSString *)pageAsString {
    return [NSString stringWithFormat:@"%@", @(self.page)];
}

Finally, before we close out this class, we’ll add one nice little touch. Because our pagination is now genericized, we can add nice things like this in one place and get the benefits of them everywhere. Without -requestForNextPage, objects using this class would have to ask what the current page is, and then construct the request themselves. This way, we’re telling, not asking.

- (BAKPaginatableRequest *)requestForNextPage {  
	return [[BAKPaginatableRequest alloc] initWithRequestTemplate:self.template page:self.page+1];  
}

@end