The question that vexes me the most in a new iOS project is the question of how to structure networking code.
Networking code is tough for three reasons. The first two reasons are related. First, it’s singular. There’s only one internet, you usually have one base URL for your API, and you want to deduplicate as many of the shared headers and URLs and access tokens as possible. Second, since you’ll need to access the network from all over, your networking code will need to be globally available. It’s just begging to be turned into a singleton.
The final problem is that making a request is a one-time process. It isn’t easily represented well in a long-lived object. While a network request maps well to a method on an object, it doesn’t easily graduate to a fully-fledged object.
Instant Cocoa itself faces this problem. It’s currently built on AFNetworking
, but as the Instant Cocoa progresses, using a dedicated library for networking seems more and more extraneous. Especially in a library that I hope other developers will use, I want to keep dependencies to a minimum.
There are a few different approaches that are commonly used, and I’ll enumerate them from least elegant to most.
Raw Access
If you’re accessing your AFHTTPRequestOperationManager
directly, or using NSURLRequest
and NSURLSession
objects without any abstraction around them, this is what I’d call raw access.
[manager POST:@"https://api.khanlou.com/users/login" parameters:@{ @"username": username, @"password": password } success:^{ ... } failure:^{ ... }];
There’s an advantage to the ease and directness of this method, but it comes with some cost. There’s no way to name or categorize the API calls. The API is totallly untyped. You can’t hide away any logic that needs to be associated with the request, such as saving an auth token or sending notifications to update the UI. You’ll also be duplicating code for base URLs, common headers, and the like. Also, tying your code directly to AFNetworking
will make it hard to change if you ever decide to move away from such a framework.
For early-stage projects this approach will work fine, but those projects usually level up to late-stage projects, and by that point, you’re going to need some organization around your network requests.
Singletons
The next, slightly more abstract, slightly more indirect way to perform your network requests is with a singleton. The interface for such a pattern might look like:
[SKAPI loginWithUsername:username password:password success:^{ ... } failure:^{ ... }];
This is a little bit better than raw access. You can name your endpoints, you can assign types to their parameters, and you can couple logic with the request. It’s a decent solution, but it’s fundamentally not object-oriented. You’ll notice that this is how the Swift version of AFNetworking
, Alamofire
, works:
Alamofire.request(.GET, "http://httpbin.org/get")
Here, Alamofire
is more of a namespace around the request, same as SKAPI
above. What you’re essentially doing is accessing a global function via that namespace. It works, since API requests fundamentally are just functions (they’re fired once, take parameters, return a result), but it still feels weird to try to shoehorn this into a class. There’s no state and no sense of self
. This is made obvious by the fact that it doesn’t matter that I made these class methods rather than instance methods.
Resource-centric
REST tries to map each remote path to an object representation. For example, users/1234
represents a user, and users/1234/follow
represents an action being done to that user. If you’ve got a local representation of that user in the form of a User
model, it makes sense to send a message to that model and have it perform the action, like:
[user followWithCompletionBlock:^{ ... }];
This is nice because is slightly more object-oriented, in that you’re sending a message to an entity. It maps to REST really nicely. It helps organize the requests in a nice way. You can also intuit things about the endpoint and parameters from innards of the model.
This is how Instant Cocoa’s resource gateways work. It works sort of like Active Record (the pattern), but designed to work asynchronously.
There are flaws, though. It’s only an interface; one of these other patterns has to actually perform the request. Also, the model probably shouldn’t know about how it’s connected to its remote representation, given that it probably already handles serialization, conversion for display, and validation logic. Finally, it can’t represent singleton resources very well (like /search
). Should singleton resources be singleton objects? It’s not clear.
Enumerating requests
Ash Furrow’s Moya library is another, Swiftier way of handling requests. I don’t have a lot of experience with Swift, but I still wanted to take a look at it for this roundup.
enum GitHub {
case Zen
// ...
}
extension GitHub : MoyaPath {
var path: String {
switch self {
case .Zen:
return "/zen"
}
// ...
}
}
extension GitHub : MoyaTarget {
var baseURL: NSURL { return NSURL(string: "https://api.github.com") }
// ...
}
You use it by creating a Swift enum with all of your requests, and then extending that enum with protocols that tell Moya what path to use, what base URL to use, and so on. Then you can pass your enum value into a MoyaProvider
to perform a request, and it can get any values it needs from methods on the enumeration itself.
This approach is a laudable for a few reasons. It feels pretty idiomatic to Swift, each endpoint has its own type (which is good!), and each endpoint can have its own well-typed parameters, since enums in Swift can take parameters.
The flaws of Moya are mostly in the organization. Moya requires that all of your API paths be in the same big switch. Adding a new API endpoint requires creating a new option in the enumeration, and adding options to all of the switches that need data for that endpoint. While sometimes it can be useful to see all of your endpoints’s paths in one place, I’d rather see all of the data related to one endpoint clumped together instead. In other words, I want something more like a struct for each endpoint rather than an enum for all of them.
One object per request
The last solution we’re going to cover is making one object for every request. It can be initialized with whatever parameters it needs, knows how to fire itself, and has an internal state machine so that objects can know if the request is midflight or completed or any other state. AFNetworking
‘s NSOperation
objects function like this, though you usually don’t have to interact with them directly.
[[SKLoginRequest alloc] initWithDelegate:self username:username password:password];
The primary problem with objects like this is that they have a very specific lifecycle. You can’t discard them early, because the object will be deallocated and the request won’t complete, and you can’t keep them around after they’re finished, since they’re no good anymore (they’re in the Completed
state and can’t — and shouldn’t! — be reset). You have to trash them at just the right time, and that’s annoying.
There’s also a lot of boilerplate around them; you usually only need to vary the path and the parameters. You might be tempted to use inheritance with the Template Method pattern to add shared behavior to these request objects, but that temptation is worth resisting.
The sweet spot
Each of these approaches has its own flaws. Next week, I’ll bring together some ideas from previous posts (Prefer Composition to Inheritance, Nestable, and Graduation) to propose something that’s flexible, well-architected, and satisfactory from an object-oriented perspective.