Swift 协议扩展中的“关联类型”难以理解

Posted

技术标签:

【中文标题】Swift 协议扩展中的“关联类型”难以理解【英文标题】:Difficulty understanding `Associated Types` in Swift protocol extensions 【发布时间】:2015-10-11 00:43:48 【问题描述】:

我很难快速理解协议和协议扩展。

我想定义一系列可应用于类的协议,以及一组协议扩展以提供默认实现。示例代码:

// MARK: - Protocols & Protocol Extensions
protocol OutputItem 
    typealias ResultType
    func rawValue() -> ResultType
    // other requirements ...


protocol StringOutputItem : OutputItem 
extension StringOutputItem 
    typealias ResultType = String
    override func rawValue() -> Self.ResultType 
        return "string ouput"
    


protocol IntOutputItem: OutputItem 
extension IntOutputItem 
    typealias ResultType = Int
    override func rawValue() -> Self.ResultType 
        return 123
    

扩展中rawValue() 的上述覆盖函数会导致错误Ambiguous type name 'ResultType' in 'Self'。如果我从Self.ResultType 中删除Self,则会收到错误'ResultType' is ambiguous for type lookup in this context

我如何向协议扩展发出信号通知ResultType 使用哪种类型?

我的目标是能够将协议及其扩展应用到一个类中,如下所示:

// MARK: - Base Class
class DataItem 
    // Some base class methods
    func randomMethod() -> String 
        return "some random base class method"
    


// MARK: - Subclasses
class StringItem : DataItem, StringOutputItem 
    // Some subclass methods


class AnotherStringItem : DataItem, StringOutputItem 
    // Some subclass methods


class IntItem : DataItem, IntOutputItem 
    // Some subclass methods

这样:

let item1 = StringItem()
print(item1.rawValue())         // should give "string output"

let item2 = AnotherStringItem()
print(item2.rawValue())         // should give "string output"

let item3 = IntItem()
print(item3.rawValue())         // should give 123

如果我完全不了解协议扩展如何提供默认实现,我对如何实现相同结果持开放态度。

【问题讨论】:

【参考方案1】:

Swift 编译器通过实现的协议方法的类型签名推断ResultType 的类型。例如,在StringOutputItem 的以下声明中,编译器知道StringOutputItemResultTypeString 类型,即使没有显式声明:

protocol StringOutputItem: OutputItem 

extension StringOutputItem 
    func rawValue() -> String 
        return "string output"
    


class StringItem : DataItem, StringOutputItem 

let item = StringItem()
print(item.rawValue()) // prints "string output"

我们可以在StringOutputItem 中显式声明ResultType,这将确保StringOutputItem 符合OutputItem 协议并以正确的类型实现它。

为了说明关联类型的类型推断,假设OutputItem 指定了另一个方法作为其协议的一部分。如果我们提供了一个类型不匹配的默认实现,编译器将抛出一个错误,表明实现类型不符合协议。

protocol OutputItem 
    typealias ResultType
    func rawValue() -> ResultType
    func printValue(r: ResultType)


protocol StringOutputItem: OutputItem 

extension StringOutputItem 
    func rawValue() -> String 
        return "string output"
    
    func printValue(r: Int)   // Should be String
        ...
    


struct Test: StringOutputItem  // Error: Type 'Test' does not conform to protocol 'OutputItem'

通过在StringOutputItem 中显式声明typealias ResultType = String,我们确保在实现协议的方法时使用正确的类型。

protocol OutputItem 
    typealias ResultType
    func rawValue() -> ResultType
    func printValue(r: ResultType)


protocol StringOutputItem: OutputItem 

extension StringOutputItem 
    typealias ResultType = String // without this typealias declaration, the program WILL compile since ResultType is inferred to be of type Int
    func rawValue() -> Int 
        return 123
    
    func printValue(r: Int)   
        ...
    


struct Test: StringOutputItem  // Error: Type 'Test' does not conform to protocol 'OutputItem'

【讨论】:

以上是关于Swift 协议扩展中的“关联类型”难以理解的主要内容,如果未能解决你的问题,请参考以下文章

swift protocol(协议) associatedtype关联类型

Swift协议与关联类型

Swift 协议:我可以限制关联类型吗?

为什么协议的关联类型在Swift中不使用泛型类型语法?

Swift中协议的简单介绍

具有最终类的多态性,可在 swift 中实现关联类型协议