简体   繁体   中英

Downcast protocol conformance in extension

Let's say I have two non-generi c protocols (1)

protocol StringValue {
    var asString: String {get}
}

protocol StringProvider {
    var value: StringValue {get}
}

I want to have a generic version of the second one (2)

protocol TypedStringProvider: StringProvider { // inherits from StringProvider
    associatedtype TypedStringValue: StringValue
    var typedValue: TypedStringValue { get }
}

And extension with default implementation of non-generic version to have a code free conformance to StringProvider ( doesn't work , pls read below) (3)

extension TypedStringProvider { 
    var value: TypedStringValue { return typedValue }
}

Now I want several classes to conform both generic TypedStringProvider and non-generic StringProvider protocols (4)

extension UIView: TypedStringProvider {
    typealias TypedStringValue = String
    var typedValue: String { return "Some String" }
}

extension Double: TypedStringProvider {
    typealias TypedStringValue = String
    var typedValue: String { return String(self) }
}

extension String: StringValue {
    var asString: String { return self }
}

And get compiler error: Type 'UIView' does not conform to protocol 'StringProvider' .

Seemingly extension (3) doesn't work like I want because TypedStringValue is not a StringValue in despite of this constraint associatedtype TypedStringValue: StringValue from (2)

The question is how to conform to non-generic StringProvider while keeping value typed

Example :

0.5.value.lowercased()

Of course StringValue doesn't have lowercased method from String so it won't compile.

What I have tried:

First is to add untyped property to extension (3)

extension TypedStringProvider {
    var value: TypedStringValue { return typedValue }
    var value: StringValue { return typedValue }
}

Doesn't work because of Invalid redeclaration of 'value' error

Second is to extend my classes and add untyped property there (5)

extension UIView {
    var value: StringValue { return typedValue }
}

extension Double {
    var value: StringValue { return typedValue }
}

It works without compiler errors but

  1. No autocompletion for lowercased in our example.

  2. With extensions (5) I need to write a lot of code for every class conforming StringProvider and every property this protocol has

Any ideas?

value is defined as type StringValue , so this is the type you should specify in your extension of TypedStringProvider in order to complete the protocol conformance:

extension TypedStringProvider {
    var value: StringValue {
        return typedValue
    }
}

Found solution

extension StringProvider where Self: TypedStringProvider {
    var value: StringValue { return typedValue }
}

With this extension there's no need to write similar extensions to every class and autocompletion works too.

Full code:

protocol StringValue {
    var asString: String {get}
}

protocol StringProvider {
    var value: StringValue {get}
}

protocol TypedStringProvider: StringProvider {
    associatedtype TypedStringValue: StringValue
    var typedValue: TypedStringValue { get }
}

extension StringProvider where Self: TypedStringProvider {
    var value: StringValue { return typedValue }
}

extension TypedStringProvider {
    var value: TypedStringValue { return typedValue }
}

extension UIView: TypedStringProvider {
    typealias TypedStringValue = String
    var typedValue: String { return "some string" }
}

extension Double: TypedStringProvider {
    typealias TypedStringValue = String
    var typedValue: String { return String(self) }
}

extension String: StringValue {
    var asString: String { return self }
}

let doubleValue = 0.5.value.lowercased()

Problem

On StringProvider you are defining value to a StringValue :

protocol StringProvider {
    var value: StringValue { get }
}

but here you are defining the type to TypedStringValue which is not the same as StringValue . (underlying value can be TypedStringValue , however it needs to be typecasted from StringValue to TypedStringValue when using it)

extension TypedStringProvider { 
    var value: TypedStringValue { return typedValue }
}

Solution

There are two solutions that I can come up for this scenario right now:

1. Generic Approach

If you want to make value generic and change the type based on TypedStringProvider you can:

protocol StringProvider {
    associatedtype StringProviderValue: StringValue
    var value: StringProviderValue { get }
}

Conform to the protocol by defining StringProviderValue to TypedStringValue

extension TypedStringProvider {
    var value: TypedStringValue { return typedValue }
}

2. StringValue approach

Keep the StringProvider as it is:

protocol StringProvider {
    var value: StringValue { get }
}

Conform to the protocol by using the correct StringValue type, and inside it return typedValue: TypedStringValue which can be downcasted to StringValue

extension TypedStringProvider {
    var value: StringValue { return typedValue }
}

Output

Both solutions give the same output:

在此输入图像描述

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