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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/26401778/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-31 03:14:04  来源:igfitidea点击:

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

iosobjective-cswiftswift-protocols

提问by Daniel Galasko

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

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

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 protocoldeclarative:

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

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:

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

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

In Swift I would expect it to look like this:

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

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 UITableViewCellfactory that is responsible for returning cells conforming to a protocol. We can easily setup a generic function that returns cells conforming to a protocol:

例如,假设我有一个UITableViewCell工厂负责返回符合协议的细胞。我们可以轻松设置一个通用函数,该函数返回符合协议的单元格:

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 UITableViewCelland conforms to the MyProtocolin the variable declaration?

我希望能够在变量声明中指定单元格是 aUITableViewCell并且符合MyProtocol

Justification

理由

If you are familiar with the Factory Patternthis 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

虽然提供的类型不完全符合上述接口,但工厂返回的对象符合,所以我希望与基类类型和声明的协议接口交互的灵活性

采纳答案by Philipp Otto

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.

在 Swift 4 中,现在可以声明一个变量,它是一个类型的子类并同时实现一个或多个协议。

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

Apple 在 WWDC 2017 的Session 402 中宣布了这一点: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.

其次,我想谈谈编写类和协议。所以,在这里我为 UI 元素引入了这个可震动的协议,它可以产生一点震动效果来吸引注意力。我已经继续扩展了一些 UIKit 类来实际提供这种摇动功能。现在我想写一些看起来很简单的东西。我只想编写一个函数,它接受一堆可摇动的控件,并摇动那些能够引起人们注意的控件。我可以在这个数组中写什么类型?这实际上令人沮丧和棘手。所以,我可以尝试使用 UI 控件。但并不是所有的 UI 控件在这个游戏中都是可动的。我可以尝试 shakable,但并非所有 shakable 都是 UI 控件。而且实际上在 Swift 3 中没有很好的方法来表示这一点。Swift 4 引入了用任意数量的协议组合一个类的概念。

回答by rintaro

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 cellis identical to asProtocol.

这样,在技术上cellasProtocol.

But, as for compiler, cellhas interface of UITableViewCellonly, while asProtocolhas only protocols interface. So, when you want to call UITableViewCell's methods, you have to use cellvariable. When you want to call protocols method, use asProtocolvariable.

但是,对于编译器,cell只有接口UITableViewCell,而asProtocol只有协议接口。所以,当你想调用UITableViewCell的方法时,你必须使用cell变量。当你想调用协议方法时,使用asProtocol变量。

If you are sure that cell conforms to protocols you don't have to use if let ... as? ... {}. like:

如果您确定单元格符合协议,则不必使用if let ... as? ... {}. 喜欢:

let cell = CellFactory.createCellForItem(special)
let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>

回答by Michael Curtis

Unfortunately, Swift does not support object level protocol conformance. However, there is a somewhat awkward work-around that may serve your purposes.

不幸的是,Swift 不支持对象级协议一致性。但是,有一个有点笨拙的变通方法可以满足您的目的。

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.

然后,在任何需要执行 UIViewController 具有的任何操作的地方,您都可以访问结构的 .viewController 方面以及需要协议方面的任何内容,您将引用 .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.

现在任何时候你需要 mySpecialViewController 来做任何与 UIViewController 相关的事情,你只需要引用 mySpecialViewController.viewController 并且当你需要它做一些协议功能时,你引用 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.

希望 Swift 4 将允许我们在未来声明一个附加了协议的对​​象。但就目前而言,这是有效的。

Hope this helps!

希望这可以帮助!

回答by holroy

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.

编辑:我错了,但如果其他人像我一样读到这个误解,我会把这个答案留在那里。OP 询问是否检查给定子类的对象的协议一致性,正如已接受的答案所示,这是另一个故事。这个答案讨论了基类的协议一致性。

Maybe I'm mistaken, but are you not talking about adding protocol conformance to the UITableCellViewclass? 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 Extensionwhich in your case would be something like:

也许我错了,但您不是在谈论向UITableCellView类添加协议一致性吗?在这种情况下,协议扩展到基类,而不是对象。请参阅 Apple 的关于使用扩展声明协议采用的文档,在您的情况下,它类似于:

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 typeswith further examples.

除了已经引用的 Swift 文档,还可以参阅 Nate Cooks 文章Generic functions for incompatible types以及更多示例。

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

如果您希望通用方法、变量同时符合协议和基类类型,那么您可能不走运。但听起来您需要定义足够宽的协议以拥有所需的一致性方法,同时足够窄以可选择将其应用于基类而无需太多工作(即仅声明一个类符合协议)。

回答by quickthyme

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

当我尝试在 Storyboards 中链接我的通用交互器连接时,我曾经遇到过类似的情况(IB 不允许你将插座连接到协议,只有对象实例),我通过简单地用私有计算屏蔽基类 public ivar 来解决这个问题财产。虽然这并不能阻止某人进行非法分配本身,但它确实提供了一种方便的方法来安全地防止在运行时与不符合规范的实例进行任何不需要的交互。(即防止对不符合协议的对象调用委托方法。)

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.

“outputReceiver”被声明为可选,私有“protocolOutputReceiver”也是如此。通过始终通过后者(计算属性)访问 outputReceiver(又名委托),我有效地过滤掉了任何不符合协议的对象。现在我可以简单地使用可选链来安全地调用委托对象,无论它是否实现了协议甚至存在。

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.

要将其应用于您的情况,您可以将公共 ivar 设为“YourBaseClass?”类型。(与 AnyObject 相对),并使用私有计算属性来强制执行协议一致性。FWIW。