In Protocol-Oriented Networking, I laid out a protocol for defining requests, and I used default implementations to add defaults and extra behavior to each Request
. First, let’s take a look at that protocol.
protocol Request {
var baseURL: NSURL? { get }
var method: String { get }
var path: String { get }
var parameters: Dictionary<String, String> { get }
var headers: Dictionary<String, String> { get }
}
I used a protocol extension with default implementations to prevent the user from having to provide the obvious defaults.
extension Request {
var method : String { return "GET" }
var path : String { return "" }
var parameters : Dictionary<String, String> { return Dictionary() }
var headers : Dictionary<String, String> { return Dictionary() }
}
The only thing that’s necessary to implement a complete Request
is a baseURL
. Everything else has a default. When I wrote the blog post, I (correctly) identified this as “pretty much the template method pattern”.
With Swift 2, however, while we could continue to use decoration to wrap our data with new functionality, we’ve been given a new power with protocol extensions. Protocol extensions let us add concrete methods to a protocol that are dependent on the abstract methods in that protocol. It’s a form of the template method pattern, but one that doesn’t rely on inheritance.
Protocols with default implementations are a much nicer and more compiler-friendly version of the template method pattern. Functionally, protocols with default implementations let you provide defaults, mark the “abstract” methods as abstract, and override the non-abstract ones as needed in the concrete classes. The compiler gives you an affordance for everything that you would normally user documentation or run-time errors for.
After this point, I feel like the post went awry. I added two more functions, one for building an NSURLRequest
and one for initiating that request.
extension Request {
func buildRequest() -> NSURLRequest? {
// build a URL for the request
// encode the parameters as JSON
// etc
// return the request
}
func sendRequest(success success: (result: AnyObject) -> (), failure: (error: ErrorType) -> ()) {
// send the request
// parse the result
// fire the blocks on success and failure
}
}
This extension is different and fundamentally worse than the previous extension, for a few reasons.
It locks me into building requests a specific way. What if I don’t want to encode the parameters in JSON? What if I have extra parameters to encode in the body, like in a multi-part request? What if I want to add a parameter, like in a paginatable request? What if I want to add headers for the authorization? What if I want to have the sendRequest()
function method return a Promise, or conditionally handle the sending through another library?
I could override the buildRequest()
method for specific requests, and do custom stuff in them on a per-request basis. I don’t want to do that, primarily because of the static dispatch of the methods buildRequest()
and sendRequest()
. Their exeuction is dependent on which type the compiler thinks they are at compile time. This issue is laid out well in this post. The behavior is highly counter-unintuitive.
I could also add more methods on Request
, like buildJSONRequest()
, buildURLEncodedRequest()
, buildMultipartRequest()
, but this would be very unelegant and unwieldy.
Ultimately, I want Request
to be dumb data. It shouldn’t know how to build an NSURLRequest
. That’s why the request construction code is in an extension. With Swift, however, if you put something in a protocol extension, you’re locked into it. You should use only extensions with default behavior when you expect to need that data or behavior every time.
Decoration in Swift
To solve our problem, we need something like decoration for Swift. We could use normal decoration, but Swift fortunately lets us do a similar thing without having to define new concrete types.
Taking inspriation from the Swift standard library (and from Olivier Halligon), we can just add a lot more protocols. The implementing object then decides which of those it wants to “conform to” and take behavior from. Let’s take a look at an example.
Let’s define a protocol called ConstructableRequest
. It itself conforms to Request
, and provides a method called buildRequest()
protocol ConstructableRequest: Request {
func buildRequest() -> NSURLRequest?
}
From that, we can add another, more “concrete” protocol for constructing a request with a JSON body.
protocol JSONConstructableRequest: ConstructableRequest { }
extension JSONConstructableRequest {
func buildRequest() -> NSURLRequest? {
// build a URL for the request
// encode the parameters as JSON
// etc
// return the request
}
}
This protocol would actually contain an implementation of buildRequest
for requests that will have a JSON body. Then, we take advantage of this new protocol in a third one called SendableRequest
:
protocol SendableRequest: ConstructableRequest { }
extension SendableRequest {
func sendRequest(success success: (string: String) -> (), failure: (error: ErrorType) -> ()) {
// send the request
// parse the result
// fire the blocks on success and failure
}
}
SendableRequest
relies only on ConstructableRequest
. It doesn’t know how the request will be constructed, just that it will have access to a function that will build an NSURLRequest
. When we go to define our request, we just mark which protocols (and thus which behaviors) we want:
struct ZenRequest: Request, JSONConstructableRequest, SendableRequest {
let baseURL = NSURL(string: "https://api.github.com/")
let path: String = "zen"
}
By choosing which protocols to conform to, we can add behavior in a dynamic fashion. Protocols can also be conformed to after the fact as well, so if you had a different sending mechanism, you could adapt your concrete request to that protocol after its definition:
extension ZenRequest: PromisedRequest { }
where PromisedRequest
is some protocol that takes a SendableRequest
and makes it return a Promise
instead of having completion blocks.
When I started this post, I thought I would end up with regular decoration again. I thought I would end up with some code like the Objective-C version:
SendableRequest(request: ZenRequest()).sendRequest(...
Writing the code this way would require us to to use the SendableRequest
keyword at every call-site, which is definitely worse than the protocol way: just declare it once at the definition of the request struct. Swift’s protocols let you do decoration in a weird and new way, and I think I like it.
Type Safety
If you want to add type-safe parsing to this scheme, sometimes you hit the annoying “Protocol X can only be used as a constraint because it has Self or associated type requirements” compiler error. I’m still wrapping my head around why it happens sometimes and doesn’t other times. I will have to read Russ Bishop’s post a few more times, I think.
To avoid the compiler error, make a new protocol with an associated type:
protocol ResultParsing {
associatedtype ParsedType
func parseData(data: NSData) -> ParsedType?
}
Like JSON parsing, you can now make a “concrete version” of this protocol, using a specific type in place of ParsedType
:
protocol StringParsing: ResultParsing { }
extension StringParsing {
func parseData(data: NSData) -> String? {
return NSString(data: data, encoding: NSUTF8StringEncoding) as? String
}
}
Then, make SendableRequest
use the abstract version of the new protocol:
protocol SendableRequest: ConstructableRequest, ResultParsing { }
extension SendableRequest {
func sendRequest(success success: (result: ParsedType) -> (), failure: (error: ErrorType) -> ()) {
// send the request
// parse the result
// fire the blocks on success and failure
}
}
Notice how SendableRequest
returns ParsedType
now. (You can take a look at the playground at the bottom of the post for an exact implementation.)
Finally, in your request, just declare which parser you want to use, as a protocol conformance.
struct ZenRequest: Request, JSONConstructableRequest, SendableRequest, StringParsing {
let baseURL = NSURL(string: "https://api.github.com/")
let path: String = "zen"
}
Small, reusable components.
JSON
You can make your ResultParsing
types do anything. For example, given some JSONConstructable
protocol:
protocol JSONConstructable {
static func fromData(data: NSData) -> Self?
}
struct User: JSONConstructable {
static func fromData(data: NSData) -> User? {
return User()
}
}
struct Tweet: JSONConstructable {
static func fromData(data: NSData) -> Tweet? {
return Tweet()
}
}
You can then create a JSONParsing
protocol that works much in the same way as the StringParsing
protocol, but with an associated type parameter, JSONType
.
protocol JSONParsing: ResultParsing {
associatedtype JSONType: JSONConstructable
func parseData(data: NSData) -> JSONType?
}
extension JSONParsing {
func parseData(data: NSData) -> JSONType? {
return JSONType.fromData(data)
}
}
To use it, just conform to JSONParsing
and add a typealias
in your request:
struct UserRequest: Request, JSONConstructableRequest, SendableRequest, JSONParsing {
let baseURL = NSURL(string: "https://api.khanlou.com/")
let path: String = "users/1"
typealias JSONType = User
}
Playground
I’ve made a playground with all the code from this post.
Protocols in Swift are very powerful, especially because they can include default implementations as of Swift 2.0. However, because of its static nature, protocol-oriented programming can still lock you in to certain patterns. Protocols should be very small, ideally containing only one responsibility each.
Swift’s standard library itself is built on a lot of the these concepts, with protocols like SequenceType
and IntegerLiteralConvertible
. The standard library uses these protocols to manage its internals. Conforming to your own structs and classes to these protocols nets you syntax features and functions like map
for free. Taking inspriation from the standard library in our own protocol design helps us get to protocol nirvana.