简体   繁体   中英

Difficulty understanding `Associated Types` in Swift protocol extensions

I'm struggling to understand protocols and protocol extensions in swift.

I'm wanting to define a series of protocols that can be applied to a class, along with a set of protocol extensions to provide default implementations. Example code:

// 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
    }
}

The above override functions for rawValue() in the extensions give an error Ambiguous type name 'ResultType' in 'Self' . If I remove Self , from Self.ResultType , I get the error 'ResultType' is ambiguous for type lookup in this context .

How do I signal to the protocol extension which type to use for ResultType ?

My aim is to be able to apply the protocols and their extensions to a class as follows:

// 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
}

So that:

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

If I'm completely off base with how protocol extensions work to provide default implementations, I'm open ideas of how to achieve the same outcome.

The Swift compiler infers the type of ResultType by the type signatures of the implemented protocol methods. For example, in the following declaration of StringOutputItem , the compiler knows that StringOutputItem 's ResultType is of type String , even without an explicit declaration:

protocol StringOutputItem: OutputItem {}

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

class StringItem : DataItem, StringOutputItem {}

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

We can explicitly declare the ResultType in StringOutputItem , which will just ensure that StringOutputItem conforms to the OutputItem protocol AND implements it with the right type.

To illustrate the type inference of the associated type, let's say that OutputItem specifies another method as part of its protocol. If we provide a default implementation whereby the types do not match, the compiler will throw an error indicating that the implementing type does not conform to the protocol.

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'

By explicitly declaring typealias ResultType = String in StringOutputItem , we are making sure that the correct type is used when implementing the protocol's methods.

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'

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM