ios - 如何将类型删除与使用关联类型的协议(protocol)一起使用

标签 ios swift protocols type-erasure associated-types

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

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

作为整理此项目和编写测试的一部分,我发现无法注入(inject) client当符合 ClientType协议(protocol)。

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

error: member 'request' cannot be used on value of protocol type 'ClientType'; use a generic constraint instead

我想保持目前的模式... = Client<ProfilesAPI>()

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

最佳答案

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

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

对于协议(protocol)中的每个要求,您都需要这些 _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 的内部,并将非通用部分提取到不需要 TClientEngine 协议(protocol)中。然后在构造 Client 时使 that 可交换。这样您就不需要类型删除器,也不必向调用者公开额外的协议(protocol)(仅 EndpointType)。

例如引擎部分:

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

再一次,录音版本:

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

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

关于ios - 如何将类型删除与使用关联类型的协议(protocol)一起使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55104199/

相关文章:

ios - 如果应用程序在 ios 应用程序中处于后台,如何检测推送通知?

ios - 有没有一种方法可以检查 2 个 NSDictionaries 是否包含相同的键?

ios - 谷歌街景使用设备陀螺仪旋转相机

swift - 如何要求在 Swift 协议(protocol)中定义枚举

swift - 协议(protocol)是否可以默认实现静态工厂方法?

快速正确使用协议(protocol)扩展

ios - 升级后,XCode 在归档时给出 "The Package Does Not Contain an Info.plist"错误

ios - float 比较 >= 与 (> or ==)

swift - "subclassing"通用结构

ios - AVCapturePhotoOutput 崩溃