Problem Statement
One of the patterns I really like in Swift is the ability to use the dot shorthand when working with static functions. For example, if I have this code:
extension UIFont {
static func helvetica(size: CGFloat): UIFont { return UIFont(name: "Helvetica", size: size) }
}
I can then use that without the UIFont prefix, which makes the code really nice:
label.font = .helvetica(size: 20)
Unfortunately, if that were a protocol, I'd have an unfortunate side effect of that static function being able to be called directly on any implementation of the protocol. So if I did this:
protocol Image {
func uiImage() -> UIImage
}
extension UIImage: Image {
func uiImage() -> UIImage { return self }
}
extension Vector: Image {
func uiImage() -> UIImage { return functionToConvertToUIImage() }
}
and wanted to have my images be accessible as Image.backArrow, Image.appIcon:
extension Image {
static var backArrow: Image { return Vector(name: "backArrow") }
static var appIcon: Image { return UIImage(name: "appIcon") }
}
Unfortunately, because of the way protocols work, that also means I would be allowing this, which breaks expectations for me:
UIImage.backArrow
Vector.appIcon
"backArrow" is not a UIImage and "appIcon" is not a Vector. In short, I often find myself wanting to make a factory off of the protocol, but am unable to limit methods or functions to the protocol itself, which I think would be useful.
Proposal
Following the pattern of class methods, in which you can do this:
class Image {
class var backArrow: Image { return Image(named: "backArrow") }
}
it would look like this:
protocol Image {
func uiImage() -> UIImage
}
extension Image {
protocol var backArrow: Image { return Vector(named: "backArrow") }
}
The "protocol" in front of the var would mean that backArrow
would only be available on the Image
protocol directly, but not on any object that implements that protocol.
I would like to allow initializers, functions and variables to be used this way:
extension Image {
protocol init(isBlue: Bool) { return isBlue ? BlueImage() : RedImage() }
protocol var backArrow: Image { return Vector(named: "backArrow") }
protocol func backArrow(isInverted: Bool) {
let a = UIImage()
if isInverted { a.invert() }
return a
}
}
let a = Image(isBlue: true)
let b = Image.backArrow
let c = Image.backArrow(isInverted: false)
This would allow similar behavior to things like class clusters and factories that Obj-C supported.
The previous threads I've seen in Swift evolution about factories have been with regard to initialization on classes, but I feel that having them on protocols fits better with the protocol oriented programming model that Swift pushes. Is this something that the core team has thought about?