[swift-evolution] Proposal: Allow class cluster pattern via dynamic initializer return type
Brent Royal-Gordon
brent at architechies.com
Mon Dec 7 17:24:12 CST 2015
> Throughout its frameworks, Apple makes use of the “class cluster” pattern as a means to separate the public API out from the (potentially complex) internal representations of the data. Clients of the API simply use the public API, while under the hood a different implementation is chosen to most efficiently represent the provided initialization parameter values.
>
> Unfortunately, because initializers in Swift are not methods like in Objective-C, there is no way to specify what the actual return value should be (short of returning nil for failable initializers). This makes it *impossible* to actually implement the class cluster pattern in Swift.
I’d actually like to put Objective-C interop aside for a moment. There are many class clusters in Objective-C, but most of them are things you rarely need to subclass yourself. When was the last time you wrote an NSArray subclass? What’s more interesting to me is how we can provide a similar native Swift pattern.
Here’s my thinking.
First of all, we call them “class” clusters, but there’s no reason this should be only for classes. A “class cluster” is basically a single API which offers access to many different implementations of an interface. The phrase “many different implementations of an interface” is a hint that we should be thinking about protocols.
Here’s what I think Swift should support:
protocol HTTPParameterListType {
var all: DictionaryLiteral<String, Strong> { get }
subscript (name: String) -> [String] { get }
}
// Various implementations include:
struct JSONParameterList: HTTPParameterList {...}
struct MultipartParameterList: HTTParameterList {…}
struct URLEncodedFormParameterList: HTTPParameterList {…}
struct QueryParameterList: HTTPParameterList {…}
extension HTTPParameterListType {
protocol init(request: NSHTTPRequest, bodyData: NSData) throws {
switch request.valueForHTTPHeaderField(“Content-Type”).map({ MIMEType(rawValue: $0) }) {
case .JSON?:
return try JSONParameterList(data: bodyData)
case .MultipartFormData?:
return try MultipartParameterList(data: bodyData)
case .URLEncodedForm?:
return try URLEncodedFormParameterList(data: bodyData)
default:
return try QueryParameterList(request: request)
}
}
}
// Usage:
self.parameters = HTTPParameterListType(request: request, bodyData: data)
A `protocol init` in a protocol extension creates an initializer which is *not* applied to types conforming to the protocol. Instead, it is actually an initializer on the protocol itself. `self` is the protocol metatype, not an instance of anything. The provided implementation should `return` an instance conforming to (and implicitly casted to) the protocol. Just like any other initializer, a `protocol init` can be failable or throwing.
Unlike other initializers, Swift usually won’t be able to tell at compile time which concrete type will be returned by a protocol init(), reducing opportunities to statically bind methods and perform other optimization tricks. Frankly, though, that’s just the cost of doing business. If you want to select a type dynamically, you’re going to lose the ability to aggressively optimize calls to the resulting instance.
Perhaps this feature can then be extended back to classes, with a `class init` being an un-inherited initializer which uses `return` to give the caller a new object. `self` would be the class object, and only class variables and methods would be callable from it. But like I said, I’m not particularly interested in Objective-C interop right now.
--
Brent Royal-Gordon
Architechies
More information about the swift-evolution
mailing list