Come passare un tipo di classe come parametro di funzione


146

Ho una funzione generica che chiama un servizio Web e serializza la risposta JSON su un oggetto.

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: AnyClass, completionHandler handler: ((T) -> ())) {

            /* Construct the URL, call the service and parse the response */
}

Quello che sto cercando di realizzare è l'equivalente di questo codice Java

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • La firma del mio metodo per quello che sto cercando di realizzare è corretta?
  • Più specificamente, specificare AnyClasscome tipo di parametro la cosa giusta da fare?
  • Quando si chiama il metodo, sto passando MyObject.selfcome valore returningClass, ma ottengo una compilation di errore "Impossibile convertire il tipo di espressione '()' digitare 'String'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

Modificare:

Ho provato a usare object_getClass, come detto da Holex, ma ora ottengo:

errore: "Tipo 'CityInfo.Type' non conforme al protocollo 'AnyObject'"

Cosa bisogna fare per conformarsi al protocollo?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}

Non penso che i generici di Swifts funzionino come i java. quindi l'inferrista non può essere così intelligente. ometto la cosa di classe <T> e specifica esplicitamente il tipo genericoCastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
Christian Dietrich,

Risposte:


144

Ti stai avvicinando nel modo sbagliato: in Swift, a differenza di Objective-C, le classi hanno tipi specifici e hanno persino una gerarchia di ereditarietà (ovvero, se la classe Beredita da A, quindi B.Typeeredita anche da A.Type):

class A {}
class B: A {}
class C {}

// B inherits from A
let object: A = B()

// B.Type also inherits from A.Type
let type: A.Type = B.self

// Error: 'C' is not a subtype of 'A'
let type2: A.Type = C.self

Ecco perché non dovresti usare AnyClass, a meno che tu non voglia davvero consentire qualsiasi classe. In questo caso sarebbe il tipo giusto T.Type, perché esprime il collegamento tra il returningClassparametro e il parametro della chiusura.

Infatti, usarlo invece di AnyClassconsentire al compilatore di inferire correttamente i tipi nella chiamata al metodo:

class func invokeService<T>(service: String, withParams params: Dictionary<String, String>, returningClass: T.Type, completionHandler handler: ((T) -> ())) {
    // The compiler correctly infers that T is the class of the instances of returningClass
    handler(returningClass())
}

Ora c'è il problema di costruire un'istanza di cui Tpassare handler: se provi ad eseguire subito il codice, il compilatore si lamenterà che Tnon è costruibile (). E giustamente: Tdeve essere esplicitamente vincolato per richiedere che implementi un inizializzatore specifico.

Questo può essere fatto con un protocollo come il seguente:

protocol Initable {
    init()
}

class CityInfo : NSObject, Initable {
    var cityName: String?
    var regionCode: String?
    var regionName: String?

    // Nothing to change here, CityInfo already implements init()
}

Quindi devi solo cambiare i vincoli generici di invokeServiceda <T>a <T: Initable>.

Mancia

Se ricevi strani errori come "Impossibile convertire il tipo di espressione '()' in 'String'", è spesso utile spostare ogni argomento della chiamata del metodo nella propria variabile. Aiuta a restringere il codice che causa l'errore e a scoprire i problemi di inferenza del tipo:

let service = "test"
let params = ["test" : "test"]
let returningClass = CityInfo.self

CastDAO.invokeService(service, withParams: params, returningClass: returningClass) { cityInfo in /*...*/

}

Ora ci sono due possibilità: l'errore si sposta su una delle variabili (il che significa che c'è la parte sbagliata) o viene visualizzato un messaggio criptico come "Impossibile convertire il tipo dell'espressione ()in tipo ($T6) -> ($T6) -> $T5".

La causa di quest'ultimo errore è che il compilatore non è in grado di inferire i tipi di ciò che hai scritto. In questo caso il problema è che Tviene utilizzato solo nel parametro della chiusura e la chiusura che hai superato non indica alcun tipo particolare, quindi il compilatore non sa quale tipo dedurre. Modificando il tipo di returningClassincludere, Tsi dà al compilatore un modo per determinare il parametro generico.


Grazie per il suggerimento T.Type - proprio quello di cui avevo bisogno per passare un tipo di classe come argomento.
Echelon,

Il "Consiglio" alla fine mi ha salvato
Alex

Invece di utilizzare una funzione templated <T: Initiable>, è anche possibile passare la classe
return

Nel codice originale che avrebbe prodotto risultati diversi. Il parametro generico Tviene utilizzato per esprimere la relazione tra returningClassl'oggetto e l'oggetto passato completionHandler. Se Initiable.Typeviene utilizzato, questa relazione viene persa.
EliaCereda,

E? I generici non permettono di scriverefunc somefunc<U>()
Gargo,

34

puoi ottenere la classe di AnyObjectvia in questo modo:

Swift 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

e puoi passarlo come parametro in seguito, se lo desideri.


1
puoi anche fareself.dynamicType
newacct

1
Ogni volta che provo ad usare myClass, si verifica un errore di "utilizzo del tipo non dichiarato" myClass ". Swift 3, ultime build di Xcode e iOS
Confused

@Confuso, non vedo questo problema, potrebbe essere necessario darmi maggiori informazioni sul contesto.
Holex,

Sto creando un pulsante. Nel codice di quel pulsante (è SpriteKit, quindi dentro touchesBegan) voglio che il pulsante chiami una funzione Class, quindi ho bisogno di un riferimento a quella classe. Quindi nella classe Button ho creato una variabile per contenere un riferimento alla classe. Ma non riesco a contenere una Classe, o il Tipo che è quella Classe, qualunque sia la giusta formulazione / terminologia. Posso solo ottenerlo per memorizzare i riferimenti alle istanze. Preferibilmente, vorrei passare un riferimento al tipo di classe nel pulsante. Ma ho appena iniziato con la creazione di un riferimento interno e non riuscivo nemmeno a farlo funzionare.
Confuso il

È stata una continuazione sulla metodologia e sul pensiero che stavo usando qui: stackoverflow.com/questions/41092440/… . Da allora ho ottenuto un po 'di logica lavorando con i protocolli, ma mi sono imbattuto rapidamente nel fatto che avrò anche bisogno di capire i tipi e i generici associati per ottenere ciò che pensavo di poter fare con meccanismi più semplici. O semplicemente copia e incolla molto codice in giro. Il che è quello che faccio normalmente;)
Confuso il

8

Ho un caso d'uso simile in swift5:

class PlistUtils {

    static let shared = PlistUtils()

    // write data
    func saveItem<T: Encodable>(url: URL, value: T) -> Bool{
        let encoder = PropertyListEncoder()
        do {
            let data = try encoder.encode(value)
            try data.write(to: url)
            return true
        }catch {
            print("encode error: \(error)")
            return false
        }
    }

    // read data

    func loadItem<T: Decodable>(url: URL, type: T.Type) -> Any?{
        if let data = try? Data(contentsOf: url) {
            let decoder = PropertyListDecoder()
            do {
                let result = try decoder.decode(type, from: data)
                return result
            }catch{
                print("items decode failed ")
                return nil
            }
        }
        return nil
    }

}

Sto cercando di fare qualcosa di simile, ma ottengo un errore al callsite: Static method … requires that 'MyDecodable.Type' conform to 'Decodable'. Ti dispiacerebbe aggiornare la tua risposta per fare una chiamata di esempio a loadItem?
Barry Jones,

Si scopre che questo è il punto in cui devo imparare la differenza tra .Typee .self(tipo e metatipo).
Barry Jones,

0

Utilizzare obj-getclass:

CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: obj-getclass(self)) { cityInfo in /*...*/

}

Supponendo che il sé sia ​​un oggetto informativo della città.


0

Swift 5

Non esattamente la stessa situazione, ma avevo un problema simile. Ciò che finalmente mi ha aiutato è stato questo:

func myFunction(_ myType: AnyClass)
{
    switch type
    {
        case is MyCustomClass.Type:
            //...
            break

        case is MyCustomClassTwo.Type:
            //...
            break

        default: break
    }
}

Quindi puoi chiamarlo all'interno di un'istanza di detta classe come questa:

myFunction(type(of: self))

Spero che questo aiuti qualcuno nella mia stessa situazione.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.