Как я могу использовать Erasure типа с протоколом, использующим связанный тип - PullRequest
1 голос
/ 11 марта 2019

Я работаю над проектом, в котором есть сетевой клиент, который в основном следует шаблону, приведенному ниже.

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.

let client: ClientType = Client<ProfilesAPI>() выдает ошибку:

ошибка: элемент 'request' не может использоваться для значения типа протокола 'ClientType ';вместо этого используйте общее ограничение

Я бы хотел сохранить текущий шаблон ... = Client<ProfilesAPI>()

Возможно ли добиться этого с помощью стирания типа?Я читал, но не уверен, как заставить это работать.

1 Ответ

2 голосов
/ 27 марта 2019

К вашему актуальному вопросу, тип ластика прост:

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, который не требует T. Затем сделайте , что может быть заменен при создании Client. Тогда вам не нужны ластики типов, и вам не нужно предоставлять дополнительный протокол для вызывающих (просто 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())
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...