Swift: назначить экземпляр универсального типа переменной класса?

Aug 20 2020

Я пытаюсь использовать это https://github.com/bricklife/JSONRPCKit. Это простая реализация JSONRPC для Swift.

Это пример в readme. Довольно просто.

// Generating request JSON
let batchFactory = BatchFactory(version: "2.0", idGenerator: NumberIdGenerator())
let request = Subtract(minuend: 42, subtrahend: 23)
let batch = batchFactory.create(request)
batch.requestObject // ["jsonrpc": "2.0", "method": "subtract", "params": [42, 23], "id": 1]

// Parsing response JSON
let responseObject: Any = ["jsonrpc": "2.0", "result": 19, "id": 1]
let response = try! batch.responses(from: responseObject)

Я бы хотел отслеживать batch, даже если он создан в функции класса. Так что другая функция (когда я получаю ответ от моего сервера). Может получить доступ к конкретному запросу / пакету / пакетному элементу и выполнить любую функцию, которая ему нужна.

Я не могу создавать вары в своем классе, используя любой из типов из JSONRPCKit.

Я получаю ошибки компилятора в следующих строках:

Протокол "Пакетный" можно использовать только как общее ограничение, поскольку он имеет требования типа Self или связанные с ним.

Ссылка на универсальный тип Batch1 требует аргументов в <...>

Значение типа протокола «Запрос» не может соответствовать «Запросу»; только типы struct / enum / class могут соответствовать протоколам

Я пытался как-то использовать универсальные функции в функциях, чтобы избежать проблем, но это тоже не помогло.

    func store_b<Batch: JSONRPCKit.Batch>(_ batch: Batch){

Дополнительная информация: batchимеет тип Batch1, например:

public struct Batch1<Request: JSONRPCKit.Request>: Batch {

и Batchэто протокол

public protocol Batch {

Есть ли простой способ отслеживать мой batchзапрос и получать ответы / как мне правильно использовать эти дженерики ?

Ответы

Max Aug 20 2020 at 03:16

Разбейте сигнатуру метода создания и тип Batch1

public func create<Request: JSONRPCKit.Request>(_ request: Request) -> Batch1<Request>

public struct Batch1<Request: JSONRPCKit.Request>: Batch {
    public typealias Responses = Request.Response
    public typealias Results = Result<Request.Response, JSONRPCError>
}

create- это универсальная функция, которая принимает один параметр любого типа . Ограничение <Request: JSONRPCKit.Request>указывает, что тип параметра должен соответствовать протоколу JSONRPCKit.Request.

Batch1- это универсальная структура, которая должна определять два внутренних типа, каждый из которых связан с некоторым произвольным типом . Опять же, <Request: JSONRPCKit.Request>указывает, что этот произвольный тип должен соответствовать протоколу JSONRPCKit.Request.

Тип возвращаемого значения Batch1<Request>связывает эти два обобщения вместе, говоря, что тип, используемый для возвращаемой Batch1структуры, будет соответствовать типу requestпараметра.

Когда вы вызываете метод create, вы должны заполнить универсальный тип конкретным типом, как в примере

let batch = batchFactory.create(Subtract(minuend: 42, subtrahend: 23))

Теперь компилятор может просмотреть все определения и создать конкретные реализации для этого типа:

public func create(_ request: Subtract) -> Batch1<Subtract>

public struct Batch1<Subtract>: Batch {
    public typealias Responses = Int
    public typealias Results = Result<Int, JSONRPCError>
}

Здесь используется тот факт, что Subtractопределяет typealias Response = Int. Обратите внимание, что больше нет ничего общего; это все конкретные типы. У вас не возникнет проблем с попыткой сохранить свойство типа Batch1<Subtract>.


Вот почему вы не можете легко сохранить пакет в свойстве: Swift не знает, какие типы в него помещать!

Один из способов обойти это - вместо этого сохранить закрытие, это может обернуть общий пакет, так что класс не должен знать об этом

// closure property
var responseProcessor: ((Any) -> Void)?

func createBatch<R: JSONRPCKit.Request>(request: R, processor: @escaping (R.Response) -> Void) {
    let batch = batchFactory.create(request)
    self.responseProcessor = { responseObject in
        let response = try! batch.responses(from: responseObject)
        processor(response)
    }
}

// somewhere else when you get the responseObject
responseProcessor?(responseObject)

Этот метод принимает конкретное закрытие, которое соответствует универсальному типу, и оборачивает его в закрытие, которое больше не зависит от универсального типа. Таким образом, каждая партия может иметь одно и то же свойство закрытия.