In Swift, how can I declare a variable of a specific type that conforms to one or more protocols?
In Swift 4 it is now possible to declare a variable that is a subclass of a type and implements one or more protocols at the same time.
var myVariable: MyClass & MyProtocol & MySecondProtocol
To do an optional variable:
var myVariable: (MyClass & MyProtocol & MySecondProtocol)?
or as the parameter of a method:
func shakeEm(controls: [UIControl & Shakeable]) {}
Apple announced this at WWDC 2017 in Session 402: Whats new in Swift
Second, I want to talk about composing classes and protocols. So, hereI've introduced this shakable protocol for a UI element that can givea little shake effect to draw attention to itself. And I've gone aheadand extended some of the UIKit classes to actually provide this shakefunctionality. And now I want to write something that seems simple. Ijust want to write a function that takes a bunch of controls that areshakable and shakes the ones that are enabled to draw attention tothem. What type can I write here in this array? It's actuallyfrustrating and tricky. So, I could try to use a UI control. But notall UI controls are shakable in this game. I could try shakable, butnot all shakables are UI controls. And there's actually no good way torepresent this in Swift 3. Swift 4 introduces the notion of composinga class with any number of protocols.
You cannot declare variable like
var object:Base,protocol<ProtocolOne,ProtocolTwo> = ...
nor declare function return type like
func someFunc() -> Base,protocol<MyProtocol,Protocol2> { ... }
You can declare as a function parameter like this, but it's basically up-casting.
func someFunc<T:Base where T:protocol<MyProtocol1,MyProtocol2>>(val:T) { // here, `val` is guaranteed to be `Base` and conforms `MyProtocol` and `MyProtocol2`}class SubClass:BaseClass, MyProtocol1, MyProtocol2 { //...}let val = SubClass()someFunc(val)
As of now, all you can do is like:
class CellFactory { class func createCellForItem(item: SpecialItem) -> UITableViewCell { return ... // any UITableViewCell subclass }}let cell = CellFactory.createCellForItem(special)if let asProtocol = cell as? protocol<MyProtocol1,MyProtocol2> { asProtocol.protocolMethod() cell.cellMethod()}
With this, technically cell
is identical to asProtocol
.
But, as for compiler, cell
has interface of UITableViewCell
only, while asProtocol
has only protocols interface. So, when you want to call UITableViewCell
's methods, you have to use cell
variable. When you want to call protocols method, use asProtocol
variable.
If you are sure that cell conforms to protocols you don't have to use if let ... as? ... {}
. like:
let cell = CellFactory.createCellForItem(special)let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>
Unfortunately, Swift does not support object level protocol conformance. However, there is a somewhat awkward work-around that may serve your purposes.
struct VCWithSomeProtocol { let protocol: SomeProtocol let viewController: UIViewController init<T: UIViewController>(vc: T) where T: SomeProtocol { self.protocol = vc self.viewController = vc }}
Then, anywhere you need to do anything that UIViewController has, you would access the .viewController aspect of the struct and anything you need the protocol aspect, you would reference the .protocol.
For Instance:
class SomeClass { let mySpecialViewController: VCWithSomeProtocol init<T: UIViewController>(injectedViewController: T) where T: SomeProtocol { self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController) }}
Now anytime you need mySpecialViewController to do anything UIViewController related, you just reference mySpecialViewController.viewController and whenever you need it to do some protocol function, you reference mySpecialViewController.protocol.
Hopefully Swift 4 will allow us to declare an object with protocols attached to it in the future. But for now, this works.
Hope this helps!