简体   繁体   English

如何使用带有关联类型的协议使用类型擦除

[英]How can I use Type Erasure with a protocol using associated type

I am working on a project that has a network client that basically follows the below pattern. 我正在一个项目中,该项目的网络客户端基本上遵循以下模式。

protocol EndpointType {
    var baseURL: String { get }
}

enum ProfilesAPI {
    case fetchProfileForUser(id: String)
}

extension ProfilesAPI: EndpointType {
    var baseURL: String {
        return "https://foo.bar"
    }
}

protocol ClientType: class {
    associatedtype T: EndpointType
    func request(_ request: T) -> Void
}

class Client<T: EndpointType>: ClientType {
    func request(_ request: T) -> Void {
        print(request.baseURL)
    }
}

let client = Client<ProfilesAPI>()

client.request(.fetchProfileForUser(id: "123"))

As part of tidying up this project and writing tests I have found the it is not possible to inject a client when conforming to the ClientType protocol. 作为整理该项目和编写测试的一部分,我发现在遵循ClientType协议时无法注入client

let client: ClientType = Client<ProfilesAPI>() produces an error: let client: ClientType = Client<ProfilesAPI>()产生错误:

error: member 'request' cannot be used on value of protocol type 'ClientType'; 错误:成员“ request”不能用于协议类型“ ClientType”的值; use a generic constraint instead 改用一般约束

I would like to maintain the current pattern ... = Client<ProfilesAPI>() 我想保持当前模式... = Client<ProfilesAPI>()

Is it possible to achieve this using type erasure? 是否可以使用类型擦除来实现? I have been reading but am not sure how to make this work. 我一直在阅读,但不确定如何进行这项工作。

To your actual question, the type eraser is straight-forward: 对于您的实际问题,类型橡皮擦很简单:

final class AnyClient<T: EndpointType>: ClientType {
    let _request: (T) -> Void
    func request(_ request: T) { _request(request) }

    init<Client: ClientType>(_ client: Client) where Client.T == T {
        _request = client.request
    }
}

You'll need one of these _func/func pairs for each requirement in the protocol. 对于协议中的每个要求,您将需要这些_func/func对之一。 You can use it this way: 您可以通过以下方式使用它:

let client = AnyClient(Client<ProfilesAPI>())

And then you can create a testing harness like: 然后,您可以创建一个测试工具,例如:

class RecordingClient<T: EndpointType>: ClientType {
    var requests: [T] = []
    func request(_ request: T) -> Void {
        requests.append(request)
        print("recording: \(request.baseURL)")
    }
}

And use that one instead: 改用那个:

let client = AnyClient(RecordingClient<ProfilesAPI>())

But I don't really recommend this approach if you can avoid it. 但是,如果可以避免的话,我并不真正推荐这种方法。 Type erasers are a headache. 类型橡皮擦是令人头疼的。 Instead, I would look inside of Client , and extract the non-generic part into a ClientEngine protocol that doesn't require T . 相反,我将查看Client内部,并将非通用部分提取到不需要TClientEngine协议中。 Then make that swappable when you construct the Client . 然后在构造Client时使可交换。 Then you don't need type erasers, and you don't have to expose an extra protocol to the callers (just EndpointType). 然后,您不需要类型擦除器,也不必向调用者公开额外的协议(只需EndpointType)。

For example, the engine part: 例如,引擎部分:

protocol ClientEngine: class {
    func request(_ request: String) -> Void
}

class StandardClientEngine: ClientEngine {
    func request(_ request: String) -> Void {
        print(request)
    }
}

The client that holds an engine. 拥有引擎的客户端。 Notice how it uses a default parameter so that callers don't have to change anything. 请注意,它如何使用默认参数,以便调用者不必更改任何内容。

class Client<T: EndpointType> {
    let engine: ClientEngine
    init(engine: ClientEngine = StandardClientEngine()) { self.engine = engine }

    func request(_ request: T) -> Void {
        engine.request(request.baseURL)
    }
}

let client = Client<ProfilesAPI>()

And again, a recording version: 再一次,一个录音版本:

class RecordingClientEngine: ClientEngine {
    var requests: [String] = []
    func request(_ request: String) -> Void {
        requests.append(request)
        print("recording: \(request)")
    }
}

let client = Client<ProfilesAPI>(engine: RecordingClientEngine())

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如何使用关联类型协议作为函数的参数类型? - How to use associated type protocol as argument's type to a function? 是否可以将类型擦除应用于协议中的返回值? - Is it possible to apply type erasure to returned value in protocol? 具有关联类型的返回协议 - return protocol with associated type 以关联类型作为属性的协议 - Protocol with associated type as property 符合协议的协议相关类型 - Protocol associated type that conforms to a protocol 具有通用枚举和通用协议的Swift类型擦除 - Swift Type Erasure with Generic Enum and Generic Protocol 我该如何修复Protocol只能用作通用约束,因为它具有Self或关联的类型需求错误 - How can I fix Protocol can only be used as a generic constraint because it has Self or associated type requirements error 如何拥有符合具有关联值的协议的泛型类型的实例或返回类型 - How can have an instance or a return type of a generic type conforming to a protocol with associated value Swift UI 在协议中使用视图(协议“视图”只能用作通用约束,因为它具有自我或关联类型要求) - Swift UI use View in a Protocol (Protocol 'View' can only be used as a generic constraint because it has Self or associated type requirements) 向下转换为受关联类型约束的协议 - Downcasting to Protocol constrained by associated type
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM