首页 文章

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

提问于
浏览
79

在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 回答

  • 1

    我曾经尝试在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(a.k.a.委托),我有效地过滤掉任何不符合协议的对象 . 现在我可以简单地使用可选链接来安全地调用委托对象,无论它是否实现协议,甚至是否存在 .

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

  • 48

    在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,但不是所有的shakable都是UI控件 . 实际上,在Swift 3中没有很好的方法来表示它.Swift 4引入了使用任意数量的协议组成一个类的概念 .

  • 0

    你不能声明变量之类的

    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

    编辑:我错了,但如果有人读到这种误解就好了我,我把这个答案留在那里 . 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将做到这一点,使一个对象遵守给定的协议 . 然而,要注意不利方面,给定协议类型的变量不知道协议之外的任何内容 . 但是这可以通过定义具有所有所需方法/变量/的协议来规避/ ...

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

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

  • 30

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

    希望这可以帮助!

相关问题