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

时间:2019-03-11 14:33:11

标签: ios swift protocols type-erasure associated-types

我正在一个项目中,该项目的网络客户端基本上遵循以下模式。

<button 
   class="btn btn-primary start" 
   data-button-action="start" 
   type="submit">
Start 
</button>

作为整理该项目和编写测试的一部分,我发现在遵守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")) 协议时无法注入client

ClientType产生错误:

  

错误:成员“ request”不能用于协议类型的值   'ClientType';改用通用约束

我想保持当前的模式let client: ClientType = Client<ProfilesAPI>()

是否可以使用类型擦除来实现?我一直在阅读,但不确定如何进行这项工作。

1 个答案:

答案 0 :(得分:2)

对于您的实际问题,橡皮擦类型很简单:

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

对于协议中的每个要求,您将需要这些_func/func对之一。您可以通过以下方式使用它:

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

然后您可以创建一个测试工具,例如:

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

并改用那个:

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

但是,如果可以避免的话,我并不真正推荐这种方法。类型橡皮擦是令人头疼的。相反,我将查看Client的内部,并将非通用部分提取到不需要ClientEngine的{​​{1}}协议中。然后在构造T时使 that 可交换。然后,您不需要类型擦除器,也不必向调用者公开额外的协议(只需EndpointType)即可。

例如,引擎部分:

Client

拥有引擎的客户端。请注意,它如何使用默认参数,以便调用者不必更改任何内容。

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

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

再次是一个录音版本:

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>()