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

时间:2014-10-16 10:12:56

标签: ios objective-c swift swift-protocols

在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吗?

理由

如果您熟悉Factory Pattern,那么在能够返回实现特定接口的特定类的对象的上下文中这将是有意义的。

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

虽然提供的类型并不完全符合上面提到的接口,但工厂返回的对象也是如此,因此我希望能够灵活地与基类类型和声明的协议接口进行交互

5 个答案:

答案 0 :(得分:59)

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

var myVariable: MyClass & MyProtocol & MySecondProtocol

或作为方法的参数:

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

Apple在2017年WWDC上Session 402: Whats new in Swift

宣布了此消息
  

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

答案 1 :(得分: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>

答案 2 :(得分: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,你都可以访问struct的.viewController方面以及你需要协议方面的任何内容,你可以参考.protocol。

For Instance:

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允许我们在将来声明一个附加了协议的对​​象。但就目前而言,这是有效的。

希望这有帮助!

答案 3 :(得分:1)

  

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

也许我错了,但你不是在谈论为UITableCellView类添加协议一致性吗?在这种情况下,协议扩展到基类,而不是对象。请参阅关于Declaring Protocol Adoption with an Extension的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文章Generic functions for incompatible types以及其他示例。

  

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

     

还有另一种更明显的方法可能会丢失吗?

Protocol Adoption将做到这一点,使一个对象遵守给定的协议。然而,要注意不利方面,给定协议类型的变量知道协议之外的任何内容。但是,这可以通过定义具有所有所需方法/变量/ ...

的协议来规避
  

虽然提供的类型并不完全符合上面提到的接口,但工厂返回的对象也是如此,因此我希望能够灵活地与基类类型和声明的协议接口进行交互

如果你想要一个泛型方法,变量要符合协议和基类类型,你可能会运气不好。但听起来你需要将协议定义得足够广泛,以便拥有所需的一致性方法,同时又要足够小,以便可以选择将其用于基类而不需要太多工作(即只是声明一个类符合协议)。

答案 4 :(得分: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)
    }
}

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

要将此应用于您的情况,您可以让公共ivar属于&#34; YourBaseClass?&#34; (与AnyObject相对),并使用私有计算属性来强制执行协议一致性。 FWIW。