Swift Associated Types With Default Values
I was browsing the Swift forums when I stumbled across a discussion regarding an undocumented feature of Swift (as of this post's date): the ability to give associated types a default value.
I've shown associated types in action before in my article about loadView() - they are a great tool to define generic behaviour inside protocols in the shape of a specific type. As the feature implies, setting a default value for an associated type will allow the children of the protocol to skip having to define a typealias for the type, unless they specifically want to use a different type.
This is the regular way to define a associated type:
protocol Foo {
associatedtype FooType
}
struct Bar: Foo {
typealias Foo = BarType
}
With just an equal sign, you can define a default type to it.
protocol Foo {
associatedtype FooType = BarType
}
struct Bar: Foo {} //Automatically sets FooType to BarType
//unless another type is specified
It is a very simple thing in nature, but I've found this undocumented feature very interesting. Having to set typealiases really bothered me - being forced to provide a typealias means that the type inherting the protocol is certainly going to do something unique, which might not really be true. Consider this structure used to define a HTTP request:
/// The representation of a HTTPClient's request.
public protocol HTTPRequest {
/// The Value of a HTTPRequest is the response object retrieved after parsing the request's response.
associatedtype Value
/// The endpoint path of the request, to be appended after the HTTPClient's baseURL property.
var path: String { get }
/// Serializes the response of this request to its associated value type.
func serialize(data: Data) throws -> Value
}
extension HTTPRequest where Value: Unboxable {
public func serialize(data: Data) throws -> Value {
let value: Value = try unbox(data: data)
return value
}
}
This structure works very well for my current project because defining path
and the Value
type is all a request needs to do in order to work, and if Value
conforms to Unboxable
, the object is even retrieved automatically. This works perfectly for requests such as this one that retrieves an user:
struct UserRequest: HTTPRequest {
typealias Value = User // is Unboxable
let path: String = "v1/profiles"
}
But that implementation is not perfect: Using associated types like that means that I can't test an endpoint without defining a fully fledgled response type to it.
Worse: What if I don't need my request to return something meaningful? Perhaps I don't care about the response, perhaps I just need to cache some Data
on the device, or a plain dictionary is enough. For all these cases, I have to explicitly set Value
to something and code a custom serialize()
method.
If that situation was common, I would simply not use associated types for my requests. Luckily, the existence of default values for associated types solve this problem entirely. Now, I can solve it by making all HTTPRequest
objects return a Data
object by default:
/// The representation of a HTTPClient's request.
public protocol HTTPRequest {
/// The Value of a HTTPRequest is the response object retrieved after parsing the request's response.
associatedtype Value = Data
/// The endpoint path of the request, to be appended after the HTTPClient's baseURL property.
var path: String { get }
/// Serializes the response of this request to it's associated value type.
func serialize(data: Data) throws -> Value
}
extension HTTPRequest where Value: Data {
public func serialize(data: Data) throws -> Value {
return data
}
}
extension HTTPRequest where Value: Unboxable {
public func serialize(data: Data?, error: Error?) throws -> Value {
let value: Value = try unbox(data: data)
return value
}
}
Plain requests can now work purely with a path
property (and return a plain Data
object) while still allowing the regular requests to provide their custom responses.
struct ABTestDataRequest: HTTPRequest {
let path: String = "v1/abtest"
}
Follow me on my Twitter - @rockbruno_, and let me know of any suggestions and corrections you want to share.