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


96

在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

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

我想能够指定单元格是a UITableViewCell并符合MyProtocol变量声明中的?

理由

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

就像在我的示例中一样,有时我们希望定义在应用于特定对象时有意义的接口。我关于表格视图单元格的示例就是这样一种证明。

尽管提供的类型与所提到的接口不完全一致,但是工厂返回的对象却与之一致,因此我希望在与基类类型和声明的协议接口交互时具有灵活性


抱歉,此举的目的是什么。类型已经知道它们遵循什么协议。不仅仅是使用什么类型?
Kirsteins 2014年

1
@Kirsteins除非类型是从工厂返回的,否则是具有公共基类的泛型
Daniel Galasko 2014年

请举一个例子。
Kirsteins 2014年

NSSomething<ABCProtocolOne,ABCProtocolTwo> * object = ...;。该对象似乎NSSomething已经毫无用处,因为它已经知道它符合什么要求。如果它不符合其中的一种协议,<>则会unrecognised selector ...崩溃。这根本不提供类型安全性。
Kirsteins 2014年

@Kirsteins请再次查看我的示例,当您知道工厂出售的对象是符合指定协议的特定基类时使用
Daniel Galasko 2014年

Answers:


72

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

var myVariable: MyClass & MyProtocol & MySecondProtocol

要执行可选变量:

var myVariable: (MyClass & MyProtocol & MySecondProtocol)?

或作为方法的参数:

func shakeEm(controls: [UIControl & Shakeable]) {}

苹果在WWDC 2017 大会402上宣布了这一点:Swift的新增功能

第二,我想谈谈组成类和协议。因此,在这里,我为UI元素引入了这种可晃动的协议,该协议可以给人一些震撼效果,从而引起人们的注意。而且,我继续并扩展了一些UIKit类,以实际提供此震动功能。现在,我想写一些看起来很简单的东西。我只想编写一个函数,该函数需要一堆可摇晃的控件,并摇晃那些能够引起人们注意的控件。我可以在此数组中写什么类型?这实际上是令人沮丧和棘手的。因此,我可以尝试使用UI控件。但并非所有UI控件在此游戏中都是可摇晃的。我可以尝试可Shakable,但并非所有可Shakable都是UI控件。而且实际上在Swift 3中没有很好的方式来表示这一点。Swift 4引入了用任何数量的协议组成一个类的概念。


3
只要添加一个链接到迅速发展的建议github.com/apple/swift-evolution/blob/master/proposals/...
丹尼尔Galasko

谢谢菲利普!
奥马尔·阿尔贝克

如果需要这种类型的可选变量怎么办?
Vyachaslav Gerchicov

2
@VyachaslavGerchicov:您可以在括号内加上问号,如下所示:var myVariable:(MyClass&MyProtocol&MySecondProtocol)?
菲利普·奥托

30

你不能像这样声明变量

var object:Base,protocol<ProtocolOne,ProtocolTwo> = ...

也不像这样声明函数返回类型

func someFunc() -> Base,protocol<MyProtocol,Protocol2> { ... }

您可以像这样声明为函数参数,但基本上是向上转换。

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)

到目前为止,您所能做的就像:

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()
}

这样,在技术上cell就等同于asProtocol

但是,对于编译器来说,cell只有接口UITableViewCell,而asProtocol只有协议接口。因此,当您要调用UITableViewCell的方法时,必须使用cell变量。当您要调用协议方法时,请使用asProtocol变量。

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

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

由于工厂指定了返回类型,因此从技术上讲我不需要执行可选的强制转换吗?我可以依靠swifts隐式键入在显式声明协议的地方执行键入吗?
Daniel Galasko 2014年

我不明白您的意思,对于我的英语水平不佳感到抱歉。如果您说的是-> UITableViewCell<MyProtocol>,这是无效的,因为UITableViewCell不是通用类型。我认为这甚至无法编译。
rintaro

我不是指您的通用实现,而是您的示例实现示例。您说let asProtocol = ...
Daniel Galasko 2014年

或者,我可以做:var cell:protocol <ProtocolOne,ProtocolTwo> = someObject作为UITableViewCell并在一个变量中获得两者的好处
Daniel Galasko 2014年

2
我不这么认为。即使您可以那样做,cell也只有协议方法(适用于编译器)。
rintaro

2

不幸的是,Swift不支持对象级协议一致性。但是,有些尴尬的解决方法可能会满足您的目的。

struct VCWithSomeProtocol {
    let protocol: SomeProtocol
    let viewController: UIViewController

    init<T: UIViewController>(vc: T) where T: SomeProtocol {
        self.protocol = vc
        self.viewController = vc
    }
}

然后,在需要执行UIViewController所具有的任何操作的任何地方,都将访问该结构的.viewController方面,并且需要该协议的任何方面,都将引用.protocol。

例如:

class SomeClass {
   let mySpecialViewController: VCWithSomeProtocol

   init<T: UIViewController>(injectedViewController: T) where T: SomeProtocol {
       self.mySpecialViewController = VCWithSomeProtocol(vc: injectedViewController)
   }
}

现在,无论何时需要mySpecialViewController来执行与UIViewController相关的任何操作,都只需引用mySpecialViewController.viewController即可;只要需要它来执行某些协议功能,就可以引用mySpecialViewController.protocol。

希望Swift 4将允许我们在将来声明带有附加协议的对象。但是现在,这可行。

希望这可以帮助!


1

编辑: 我弄错了,但是如果其他人像我一样阅读这种误解,我就把这个答案留在那里。OP询问有关检查给定子类对象的协议一致性的问题,这是被接受的答案所显示的另一个故事。该答案讨论了基类的协议一致性。

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

extension UITableCellView : ProtocolOne {}

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

除了已经引用的Swift文档之外,还请参见Nate Cooks的文章“ 通用函数中的不兼容类型”以及更多示例。

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

还有其他更明显的方式可能会让我丢失吗?

协议采用会做到这一点,使对象遵守给定的协议。但是要注意不利的一面,给定协议类型的变量知道协议之外的任何信息。但这可以通过定义具有所有所需方法/变量/ ...的协议来规避。

尽管提供的类型与所提到的接口不完全一致,但是工厂返回的对象却与之一致,因此我希望在与基类类型和声明的协议接口交互时具有灵活性

如果您希望通用方法,变量同时符合协议和基类类型,那么您可能会不走运。但这听起来好像您需要定义足够宽的协议,以具有所需的一致性方法,同时又要足够狭窄,以使无需过多的工作即可选择采用该协议作为基类的选择(即,仅声明一个类符合协议)。


1
那根本不是我在说什么,但是谢谢:)我希望能够通过对象的类和特定协议与对象进行接口。就像在obj-c中如何执行NSObject <MyProtocol> obj = ...一样,不用说这不能很快完成,您必须将对象转换为其协议
Daniel Galasko 2015年

0

当我尝试在Storyboard中链接通用交互器连接时,我曾经遇到过类似的情况(IB不允许您将插座连接到协议,仅将对象实例连接),我通过用私有计算的基类public ivar掩盖了这种情况属性。虽然这并不能阻止某人本身进行非法分配,但它确实提供了一种方便的方法,可以安全地防止运行时与不合格实例的任何不必要的交互。(即,防止对不符合协议的对象调用委托方法。)

例:

@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)
    }
}

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

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

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.