在 Swift 中,如何声明符合一个或多个协议的特定类型的变量?

在 Swift 中,我可以通过如下声明来显式地设置变量的类型:

var object: TYPE_NAME

如果我们想更进一步,声明一个符合多个协议的变量,我们可以使用 protocol声明:

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

如果我想声明一个符合一个或多个协议并且具有特定基类类型的对象,该怎么办?Objective-C 等价物看起来是这样的:

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

在 Swift 中,我希望它看起来是这样的:

var object: TYPE_NAME,ProtocolOne//etc

这使我们能够灵活地处理基类型的实现以及协议中定义的添加接口。

还有更明显的可能是我失踪了吗?

例子

例如,假设我有一个 UITableViewCell工厂,负责返回符合协议的单元。我们可以很容易地设置一个通用函数,返回符合协议的单元格:

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

稍后,我想利用类型和协议来取消这些单元格的队列

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

这将返回一个错误,因为表视图单元格不符合协议..。

我希望能够指定的单元格是一个 UITableViewCell和符合 MyProtocol的变量声明?

正当理由

如果您熟悉 工厂模式,那么这在能够返回实现某个接口的特定类的对象的上下文中是有意义的。

就像在我的例子中一样,有时我们喜欢定义应用于特定对象时有意义的接口。我的表视图单元格示例就是这样一个例子。

虽然提供的类型并不完全符合所提到的接口,但是工厂返回的对象完全符合,因此我希望在与基类类型和声明的协议接口交互时具有灵活性

41345 次浏览

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>

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).

I once had a similar situation when trying to link my generic interactor connections in Storyboards (IB won't allow you to connect outlets to protocols, only object instances), which I got around by simply masking the base class public ivar with a private computed property. While this does not prevent someone from making illegal assignments per se, it does provide a convenient way to safely prevent any unwanted interaction with a non-conforming instance at runtime. (i.e. prevent calling delegate methods to objects that don't conform to the protocol.)

Example:

@objc protocol SomeInteractorInputProtocol {
func getSomeString()
}


@objc protocol SomeInteractorOutputProtocol {
optional func receiveSomeString(value:String)
}


@objc class SomeInteractor: NSObject, SomeInteractorInputProtocol {


@IBOutlet var outputReceiver : AnyObject? = nil


private var protocolOutputReceiver : SomeInteractorOutputProtocol? {
get { return self.outputReceiver as? SomeInteractorOutputProtocol }
}


func getSomeString() {
let aString = "This is some string."
self.protocolOutputReceiver?.receiveSomeString?(aString)
}
}

The "outputReceiver" is declared optional, as is the private "protocolOutputReceiver". By always accessing the outputReceiver (a.k.a. delegate) through the latter (the computed property), I effectively filter out any objects that do not conform to the protocol. Now I can simply use optional chaining to safely call out to the delegate object whether or not it implements the protocol or even exists.

To apply this to your situation, you can have the public ivar be of type "YourBaseClass?" (as opposed to AnyObject), and use the private computed property to enforce the protocol conformance. FWIW.

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!

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.