102

在 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

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

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

理由

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

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

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

4

5 回答 5

79

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

var myVariable: MyClass & MyProtocol & MySecondProtocol

做一个可选变量:

var myVariable: (MyClass & MyProtocol & MySecondProtocol)?

或作为方法的参数:

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

Apple 在 WWDC 2017 的Session 402 中宣布了这一点:Swift 中的新功能

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

于 2017-07-24T09:07:18.010 回答
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()
}

这样,在技术上cellasProtocol.

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

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

let cell = CellFactory.createCellForItem(special)
let asProtocol = cell as protocol<MyProtocol1,MyProtocol2>
于 2014-10-16T11:51:40.300 回答
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 将来允许我们声明一个带有附加协议的对象。但就目前而言,这是可行的。

希望这可以帮助!

于 2016-12-19T15:53:10.760 回答
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 文章不兼容类型的通用函数以及更多示例。

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

我可能会错过另一种更明显的方式吗?

协议采用就是这样做的,使一个对象遵守给定的协议。但是请注意不利的一面,即给定协议类型的变量知道协议之外的任何内容。但这可以通过定义一个包含所有需要的方法/变量/...的协议来规避。

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

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

于 2015-03-07T04:37:08.170 回答
0

当我试图在 Storyboards 中链接我的通用交互器连接时,我曾经遇到过类似的情况(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。

于 2015-11-05T05:51:10.357 回答