Swift: assegnare un'istanza di un tipo generico a una variabile di classe?

Aug 20 2020

Sto cercando di usare questo https://github.com/bricklife/JSONRPCKit. È una semplice implementazione di JSONRPC per Swift.

Questo è l'esempio nel file readme. Abbastanza semplice.

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

Vorrei tenerne traccia batch, anche se viene creato in funzione di una classe. In modo che un'altra funzione (quando ricevo una risposta dal mio server). Può accedere alla specifica richiesta / batch / batchelement ed eseguire qualsiasi funzione necessaria.

Non posso creare vars nella mia classe utilizzando nessuno dei tipi di JSONRPCKit.

Ottengo errori del compilatore sulla falsariga di:

Il protocollo "Batch" può essere utilizzato solo come vincolo generico perché ha requisiti di tipo Self o associati

Il riferimento al tipo generico "Batch1" richiede argomenti in <...>

Il valore del tipo di protocollo "Richiesta" non può essere conforme a "Richiesta"; solo i tipi struct / enum / class possono essere conformi ai protocolli

Ho provato a utilizzare il generico nelle funzioni in qualche modo per evitare i miei problemi, ma neanche questo ha aiutato.

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

Maggiori informazioni: batchè di tipo Batch1, in questo modo:

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

ed Batchè un protocollo

public protocol Batch {

C'è un modo semplice per tenere traccia della mia batchrichiesta e ottenere risposte / come posso utilizzare correttamente questi generici ?

Risposte

Max Aug 20 2020 at 03:16

Suddividi la firma del metodo di creazione e il tipo 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è una funzione generica che accetta un singolo parametro di qualsiasi tipo . Il vincolo <Request: JSONRPCKit.Request>specifica che il tipo di parametro deve essere conforme al protocollo JSONRPCKit.Request.

Batch1è una struttura generica che deve definire due tipi interni, entrambi associati a un tipo arbitrario . Di nuovo, <Request: JSONRPCKit.Request>specifica che quel tipo arbitrario deve essere conforme al protocollo JSONRPCKit.Request.

Il tipo restituito Batch1<Request>lega insieme questi due generici, dicendo che il tipo utilizzato per la Batch1struttura restituita corrisponderà al tipo del requestparametro.

Quando chiami il metodo create, devi compilare il tipo generico con un tipo concreto, come nell'esempio

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

Ora il compilatore può passare attraverso tutte le definizioni e creare implementazioni concrete per quel tipo:

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

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

Questo utilizza il fatto che Subtractdefinisce typealias Response = Int. Nota che niente è più generico; questi sono tutti i tipi concreti. Non avresti problemi con il tentativo di memorizzare una proprietà di tipo Batch1<Subtract>.


Questo è il motivo per cui non puoi archiviare facilmente il batch in una proprietà: Swift non ha idea di quali tipi inserire!

Un modo per aggirare questo è memorizzare invece una chiusura, questo può avvolgere il batch generico in modo che la classe non debba saperlo

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

Questo metodo accetta una chiusura specifica che corrisponde al tipo generico e la avvolge in una chiusura che non dipende più dal generico. In questo modo ogni batch può condividere la stessa proprietà di chiusura.