In Swift, how can I declare a variable of a specific type that conforms to one or more protocols?

40,063

Solution 1

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, here I've introduced this shakable protocol for a UI element that can give a little shake effect to draw attention to itself. And I've gone ahead and extended some of the UIKit classes to actually provide this shake functionality. And now I want to write something that seems simple. I just want to write a function that takes a bunch of controls that are shakable and shakes the ones that are enabled to draw attention to them. What type can I write here in this array? It's actually frustrating and tricky. So, I could try to use a UI control. But not all UI controls are shakable in this game. I could try shakable, but not all shakables are UI controls. And there's actually no good way to represent this in Swift 3. Swift 4 introduces the notion of composing a class with any number of protocols.

Solution 2

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>

Solution 3

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!

Solution 4

EDIT: I was mistaken, but if somebody else read this misunderstanding like me, I leave this answer out there. The OP asked about checking for protocol conformance of the object of a given subclass, and that is another story as the accepted answer shows. This answer talks about protocol conformance for the base class.

Maybe I'm mistaken, but are you not talking about adding protocol conformance to the UITableCellView class? The protocol is in that case extended to the base class, and not the object. See Apple's documentation on Declaring Protocol Adoption with an Extension which in your case would be something like:

extension UITableCellView : ProtocolOne {}

// Or alternatively if you need to add a method, protocolMethod()
extension UITableCellView : ProcotolTwo {
   func protocolTwoMethod() -> String {
     return "Compliant method"
   }
}

In addition to the already referenced Swift documentation, also see Nate Cooks article Generic functions for incompatible types with further examples.

This gives us the flexibility of being able to deal with the implementation of the base type as well as the added interface defined in the protocol.

Is there another more obvious way that I might be missing?

Protocol Adoption will do just this, make an object adhere to the given protocol. Be however aware of the adverse side, that a variable of a given protocol type does not know anything outside of the protocol. But this can be circumvented by defining a protocol which has all the needed methods/variables/...

Whilst the supplied type does not exactly conform to the mentioned interface, the object the factory returns does and so I would like the flexibility in interacting with both the base class type and the declared protocol interface

If you would like for a generic method, variable to conform to both a protocol and base class types, you could be out of luck. But it sounds like you need to define the protocol wide enough to have the needed conformance methods, and at the same time narrow enough to have the option to adopt it to base classes without too much work (i.e. just declaring that a class conforms to the protocol).

Share:
40,063

Related videos on Youtube

Daniel Galasko
Author by

Daniel Galasko

Aspiring NSHipster and passionate iOS Developer. Quotes to work by: To be an NSHipster is to care deeply about the craft of writing code. In cultivating a deep understanding and appreciation of Objective-C, its frameworks and ecosystem, one is able to create apps that delight and inspire users - Mattt Thompson. We believe that technology is at it's very best, at its most empowering, when it simply disappears - Jony Ive

Updated on June 23, 2020

Comments

  • Daniel Galasko
    Daniel Galasko about 4 years

    In Swift I can explicitly set the type of a variable by declaring it as follows:

    var object: TYPE_NAME
    

    If we want to take it a step further and declare a variable that conforms to multiple protocols we can use the protocol declarative:

    var object: protocol<ProtocolOne,ProtocolTwo>//etc
    

    What if I would like to declare an object that conforms to one or more protocols and is also of a specific base class type? The Objective-C equivalent would look like this:

    NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;
    

    In Swift I would expect it to look like this:

    var object: TYPE_NAME,ProtocolOne//etc
    

    This gives us the flexibility of being able to deal with the implementation of the base type as well as the added interface defined in the protocol.

    Is there another more obvious way that I might be missing?

    Example

    As an example, say I have a UITableViewCell factory that is responsible for returning cells conforming to a protocol. We can easily setup a generic function that returns cells conforming to a protocol:

    class CellFactory {
        class func createCellForItem<T: UITableViewCell where T:MyProtocol >(item: SpecialItem,tableView: UITableView) -> T {
            //etc
        }
    }
    

    later on I want to dequeue these cells whilst leveraging both the type and the protocol

    var cell: MyProtocol = CellFactory.createCellForItem(somethingAtIndexPath) as UITableViewCell
    

    This returns an error because a table view cell does not conform to the protocol...

    I would like to be able to specify that cell is a UITableViewCell and conforms to the MyProtocol in the variable declaration?

    Justification

    If you are familiar with the Factory Pattern this would make sense in the context of being able to return objects of a particular class that implement a certain interface.

    Just like in my example, sometimes we like to define interfaces that make sense when applied to a particular object. My example of the table view cell is one such justification.

    Whilst the supplied type does not exactly conform to the mentioned interface, the object the factory returns does and so I would like the flexibility in interacting with both the base class type and the declared protocol interface

    • Kirsteins
      Kirsteins over 9 years
      Sorry, but what is the point of this in swift. Types already know what protocols they conform. What not just use the type?
    • Daniel Galasko
      Daniel Galasko over 9 years
      @Kirsteins Not unless the type is returned from a factory and is thus a generic type with a common base class
    • Kirsteins
      Kirsteins over 9 years
      Please give an example if possible.
    • Kirsteins
      Kirsteins over 9 years
      NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;. This object seems quite useless as NSSomething already knows what it conforms to. If it doesn't conform to one of the protocols in <> you will get unrecognised selector ... crashes. This provides no type safety at all.
    • Daniel Galasko
      Daniel Galasko over 9 years
      @Kirsteins Please see my example again, its used when you know that the object your factory vends out is of a particular base class conforming to a specified protocol
    • Daniel Galasko
      Daniel Galasko over 9 years
      @Kirsteins See my justification section i added:)
    • Daniel Galasko
      Daniel Galasko over 9 years
  • Daniel Galasko
    Daniel Galasko over 9 years
    Since the factory specifies the return types I don't technically need to perform the optional cast? I could just rely on swifts implicit typing to perform the typing where I explicitly declare the protocols?
  • rintaro
    rintaro over 9 years
    I don't understand what you mean, sorry for my bad English skills. If you are saying about -> UITableViewCell<MyProtocol>, this is invalid, because UITableViewCell is not a generic type. I think this doesn't even compile.
  • Daniel Galasko
    Daniel Galasko over 9 years
    Im not referring to your generic implementation but rather your example illustration of implementation. where you say let asProtocol =...
  • Daniel Galasko
    Daniel Galasko over 9 years
    or, i could just do: var cell: protocol<ProtocolOne,ProtocolTwo> = someObject as UITableViewCell and get the benefit of both in one variable
  • rintaro
    rintaro over 9 years
    I don't think so. Even if you could do like that, cell has only protocols methods (for compiler).
  • Daniel Galasko
    Daniel Galasko over 9 years
    no thats how I have been doing it actually:) Swift infers both the protocol conformance and the type
  • rintaro
    rintaro over 9 years
    Wow, really? My Xcode 6.0.1 emits an error like Type 'UITableViewCell' does not conform to protocol 'MyProtocol1'. Which version of Xcode you are using?
  • Daniel Galasko
    Daniel Galasko over 9 years
    My apologies you are correct, I just realized this has been asked before: stackoverflow.com/questions/25214484/…
  • Daniel Galasko
    Daniel Galasko over 9 years
    Thats not what I'm talking about at all but thanks:) I wanted to be able to interface with an object through both its class and a specific protocol. Just like how in obj-c i can do NSObject<MyProtocol> obj = ... Needless to say this can't be done in swift, you have to cast the object to its protocol
  • Just a coder
    Just a coder over 8 years
    @rintaro is this the best way to do this? While it does work, it seems a little "un-natural"?
  • Kakashi
    Kakashi about 8 years
    @rintaro I'm still learning Swift. Please show declare UIView conform someProtocol in Swift “UIView<SomeProtocol> *someView = nil;”.
  • Charles A.
    Charles A. almost 8 years
    Swift 3 has replaced protocol<SomeProtocol, SomeOtherProtocol> with a new operator as so: SomeProtocol & SomeOtherProtocol
  • Daniel Galasko
    Daniel Galasko almost 7 years
    Just adding a link to the swift evolution proposal github.com/apple/swift-evolution/blob/master/proposals/…
  • Omar Albeik
    Omar Albeik about 6 years
    Thank you Philipp!
  • Vyachaslav Gerchicov
    Vyachaslav Gerchicov about 6 years
    what if need an optional variable of this type?
  • Philipp Otto
    Philipp Otto about 6 years
    @VyachaslavGerchicov: You can put parentheses around it and then the question mark like this: var myVariable: (MyClass & MyProtocol & MySecondProtocol)?