INTRODUCING FEEDBINKIT


RSS isn’t dead! In fact, it’s alive and well, with many new services filling the now-defunct Google Reader’s niche of syncing subscriptions, entries, and read/unread status. My personal preference among the crowd of newcomers is Feedbin. It has a simple revenue model ($3/month or $30/year), an open-source core, and a RESTful API for implementing client reading apps.

Having a RESTful API means we can interact with the service relatively easily. However, because of the limitations of existing programming paradigms, this still involves a fair amount of legwork; and in turn, with Objective-C, a large amount of fragile, stringly-typed code. The release of Apple’s new Swift programming language presents an opportunity to revisit this approach, and hopefully improve it.

The status quo: Accessing RESTful APIs with Objective-C

Apple’s Foundation framework provides a robust set of classes for implementing HTTP networking. In Velos client applications, we typically use AFNetworking to abstract away mundane tasks like authentication, and translating JSON data to simple object types. We also use a home-grown pattern for lightweight mapping of simple object types to full model objects. For a relatively simple API call – getting a list of the user’s subscriptions – this would look something like:

@implementation FeedbinClient (Subscriptions)
// the main FeedbinClient implementation handles authentication, etc
 
- (AFHTTPURLRequest *)readAllSubscriptionsWithCompletion:(void (^)(NSArray *subscriptions, NSError *error)) {
    return [self GET:@"/subscriptions.json" parameters:nil success:^(AFHTTPRequestOperation *operation, id responseObject) {
    NSMutableArray *subscriptions = [[NSMutableArray alloc] initWithCapacity:[responseObject count]];
    for (NSDictionary *subscriptionJSON in responseObject) {
        Subscription *subscription = [[Subscription alloc] initWithDictionary:subscriptionJSON];
        if (subscription) {
            [subscriptions addObject:subscription]
        }
    }
    completionBlock(subscriptions, nil);
} failure:^(AFHTTPRequestOperation *operation, NSError *error) {
    completionBlock(nil, error);
}];
 
@end
@implementation NSDictionary (ValueForKeyOfClass)
 
- (id)valueForKey:(NSString *)key ofClass:(Class)aClass {
    id value = [self valueForKey:key];
    if ([value isKindOfClass:aClass]) {
        return value;
    } else {
        return nil;
    }
}
 
@end
@implementation Subscription // : ModelObject
// ModelObject implements -initWithDictionary:, which calls super and then -updateWithDictionary:
 
- (void)updateWithDictionary:(NSDictionary *)attributes {
    NSNumber *identifier = [attributes valueForKey:@"id" ofClass:[NSNumber class]];
    if (identifier) {
        self.identifier = [identifier unsignedIntegerValue];
    }
 
    NSString *createdAt = [attributes valueForKey:@"created_at" ofClass:[NSString class]];
    if (createdAt) {
        NSDateFormatter *dateFormatter = ...; // code to create/access an ISO 8601 date formatter
        self.createdAt = 
    }
}
 
@end

This code is not only inelegant, but error-prone: What if we call -valueForKey: instead of our -valueForKey:ofClass:? What if the response JSON is a dictionary, not an array? What if we mistype the URL for the endpoint? Additionally, we’ve left out a good deal of the manual authentication, type-checking, and error-checking that would be necessary in production-quality code.

A better tomorrow: Accessing RESTful APIs in Swift

By comparison, here’s the equivalent code in Swift:

public class Subscription: MapperProtocol {
    public class func map(mapper: Mapper, object: Subscription) {
        object.identifier       <= mapper["id"]
        object.createdAt        <= (mapper["created_at"], ISO8601DateTransform<NSDate, String>())
        object.feedIdentifier   <= mapper["feed_id"]
        object.title            <= mapper["title"]
        object.feedURL          <= (mapper["feed_url"], URLTransform<NSURL, String>())
        object.siteURL          <= (mapper["site_url"], URLTransform<NSURL, String>())
    }
}
public extension FeedbinClient {
    public func readAllSubscriptions() -> Future<[Subscription]> {
        return request(SubscriptionRouter.ReadAll()) { _, _, responseString in
            return Mapper().map(responseString, to: Subscription.self)
        }
    }
}

What happened here? First, using AFNetworking’s sister library Alamofire we’ve moved the routing logic to an enumeration:

enum SubscriptionRouter: URLRequestConvertible {
    static let baseURLString = "https://api.feedbin.com/v2/"
 
    case ReadAll()
    case ReadAllSince(sinceDate: NSDate)
    case Create(NSURL)
    case Read(Subscription)
    case Update(Subscription)
    case Delete(Subscription)
 
    var method: Alamofire.Method {
        switch self {
        case .ReadAll:
            return .GET
        case .ReadAllSince:
            return .GET
        // ...
    }
 
    var path: String {
        switch self {
        case .ReadAll:
            return "/subscriptions.json"
        case .ReadAllSince(let sinceDate):
            return "/subscriptions.json?since=\(sinceDate)"
        // ...
        }
    }
 
    // MARK: URLRequestConvertible
 
    var URLRequest: NSURLRequest {
        let URL = NSURL(string: SubscriptionRouter.baseURLString)
        let mutableURLRequest = NSMutableURLRequest(URL: URL!.URLByAppendingPathComponent(path))
        mutableURLRequest.HTTPMethod = method.rawValue
 
        return mutableURLRequest
    }
}

This separates the logic of creating a request for a specific API endpoint from the site of the request, additionally ensuring that we don’t munge (or leave out) any parameters.

Next, we’ve defined a method on FeedbinClient called request. It takes a value type conforming to URLRequestConvertible, like the router enum shown above, and returns a generic future. Using this method, we only need to provide a responseHandler closure for mapping the data ourselves:

public class FeedbinClient: Alamofire.Manager {
    internal func request(URLRequest: URLRequestConvertible, responseHandler: (request: NSURLRequest, response: NSHTTPURLResponse?, responseString: String) -> T?) -> Future {
        let promise = Promise()
 
        if self.credential == nil {
            promise.error(NSError())
            return promise.future
        }
 
        let request = self.request(URLRequest).responseString { (request, response, responseString, error) -> Void in
            if let responseString = responseString {
                let value = responseHandler(request: request, response: response, responseString: responseString)
                if let value = value {
                    promise.success(value)
                    return
                }
            }
 
            promise.error(error ?? NSError())
        }
 
        return promise.future
    }
}

Finally, ObjectMapper provides type-safe, bidirectional translation between JSON types and model objects. We extended the project to support mapping arrays, in addition to individual JSON dictionaries.

Is Swift worth it?

We’ve barely scratched the surface of what’s possible with Swift. Most of the time spent on FeedbinKit was trying to figure out how to translate existing Objective-C idioms to Swift, then rewriting them to take advantage of new language features. The benefits, though, are clear: cleaner code that works correctly the first time you write it.

Swift is still an immature language. Version 1.1 added failable initializers, a welcome feature that nonetheless could add a new wrinkle of complexity to existing code. Apple has made clear that we should expect similar changes in the future as the language continues to evolve and stabilize. This might outweigh the benefits in some cases, but certainly not very many.

Conclusion

FeedbinKit is now open-source under the MIT license on our GitHub page. There are a few rough edges, and we could use a lot more test cases, but if you’re curious how a few familiar patterns can be translated, please dive in.

Comment