Cómo pasar un tipo de clase como parámetro de función

146

Tengo una función genérica que llama a un servicio web y serializa la respuesta JSON a un objeto.

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

Lo que intento lograr es el equivalente de este código Java

public <T> T invokeService(final String serviceURLSuffix, final Map<String, String> params,
                               final Class<T> classTypeToReturn) {
}
  • ¿Es correcta la firma de mi método para lo que estoy tratando de lograr?
  • Más específicamente, ¿es lo que se debe especificar AnyClasscomo tipo de parámetro?
  • Cuando llamo al método, lo paso MyObject.selfcomo el valor returnClass, pero aparece un error de compilación "No se puede convertir el tipo de expresión '()' para escribir 'String'"
CastDAO.invokeService("test", withParams: ["test" : "test"], returningClass: CityInfo.self) { cityInfo in /*...*/

}

Editar:

Intenté usar object_getClass, como mencionó holex, pero ahora obtengo:

error: "Tipo 'CityInfo.Type' no se ajusta al protocolo 'AnyObject'"

¿Qué hay que hacer para cumplir con el protocolo?

class CityInfo : NSObject {

    var cityName: String?
    var regionCode: String?
    var regionName: String?
}
Jean-Francois Gagnon
fuente
No creo que los genéricos de Swift funcionen como javas. así, el inferidor no puede ser tan inteligente. id omitir la clase <T> y especificar explícitamente el tipo genéricoCastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }
Christian Dietrich

Respuestas:

144

Lo estás abordando de manera incorrecta: en Swift, a diferencia de Objective-C, las clases tienen tipos específicos e incluso tienen una jerarquía de herencia (es decir, si la clase Bhereda de A, entonces B.Typetambién hereda de 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

Es por eso que no debes usar AnyClass, a menos que realmente quieras permitir cualquier clase. En este caso, el tipo correcto sería T.Type, porque expresa el vínculo entre el returningClassparámetro y el parámetro del cierre.

De hecho, usarlo en lugar de AnyClasspermite al compilador inferir correctamente los tipos en la llamada al método:

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

Ahora está el problema de construir una instancia Tpara pasar handler: si intenta ejecutar el código en este momento, el compilador se quejará de que Tno es constructible (). Y con razón: Ttiene que estar explícitamente restringido para requerir que implemente un inicializador específico.

Esto se puede hacer con un protocolo como el siguiente:

protocol Initable {
    init()
}

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

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

Entonces solo tiene que cambiar las restricciones genéricas de invokeServicefrom <T>a <T: Initable>.

Propina

Si obtiene errores extraños como "No se puede convertir el tipo de expresión '()' a tipo 'Cadena'", a menudo es útil mover cada argumento de la llamada al método a su propia variable. Ayuda a reducir el código que está causando el error y descubrir problemas de inferencia de tipos:

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

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

}

Ahora hay dos posibilidades: el error se mueve a una de las variables (lo que significa que la parte incorrecta está allí) o aparece un mensaje críptico como "No se puede convertir el tipo de expresión ()en tipo ($T6) -> ($T6) -> $T5".

La causa del último error es que el compilador no puede inferir los tipos de lo que escribió. En este caso, el problema es que Tsolo se usa en el parámetro del cierre y el cierre que pasó no indica ningún tipo en particular, por lo que el compilador no sabe qué tipo inferir. Al cambiar el tipo de returningClassincluir, Tle da al compilador una forma de determinar el parámetro genérico.

EliaCereda
fuente
Gracias por la sugerencia de T.Type: justo lo que necesitaba para pasar un tipo de clase como argumento.
Echelon
El "Consejo" al final me salvó
Alex
En lugar de usar una función con plantilla <T: Initiable>, también es posible pasar el returnClass como Initiable.Type
Devous
En el código original que habría producido resultados diferentes. El parámetro genérico Tse utiliza para expresar la relación entre returningClassy el objeto al que se pasa completionHandler. Si Initiable.Typese usa esta relación se pierde.
EliaCereda
¿Y? Los genéricos no permiten escribirfunc somefunc<U>()
Gargo
34

puede obtener la clase de AnyObjectesta manera:

Swift 3.x

let myClass: AnyClass = type(of: self)

Swift 2.x

let myClass: AnyClass = object_getClass(self)

y puede pasarlo como parámetro más tarde, si lo desea.

holex
fuente
1
también puedes hacerloself.dynamicType
newacct
1
Cada vez que intento usar myClass está causando un error de "uso de tipo no declarado" myClass ". Swift 3, últimas compilaciones de Xcode e iOS
Confuso
@ Confundido, no veo ningún problema con eso, es posible que deba darme más información sobre el contexto.
holex
Estoy haciendo un botón. En el código de ese botón (es SpriteKit, así que por dentro tocaBegan) quiero que el botón llame a una función de clase, por lo que necesito una referencia a esa clase. Entonces, en la Clase de botón, he creado una variable para contener una referencia a la Clase. Pero no puedo lograr que contenga una Clase, o el Tipo que es esa Clase, sea cual sea la redacción / terminología correcta. Solo puedo conseguir que almacene referencias a instancias. Preferiblemente, me gustaría pasar una referencia al Tipo de clase en el botón. Pero recién comencé a crear una referencia interna, y ni siquiera pude hacer que funcionara.
Confuso
Fue una continuación de la metodología y el pensamiento que estaba usando aquí: stackoverflow.com/questions/41092440/… . Desde entonces, obtuve algo de lógica trabajando con Protocolos, pero rápidamente me encuentro con el hecho de que también necesitaré descubrir los tipos y genéricos asociados para obtener lo que pensé que podría hacer con mecanismos más simples. O simplemente copie y pegue una gran cantidad de código. Que es lo que normalmente hago;)
Confundido
8

Tengo un caso de uso similar en 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
    }

}
johney
fuente
Estoy tratando de hacer algo similar, pero me da un error en el callsite: Static method … requires that 'MyDecodable.Type' conform to 'Decodable'. ¿Te importaría actualizar tu respuesta para llamar a un ejemplo loadItem?
Barry Jones
Resulta que este es el punto donde tengo que aprender la diferencia entre .Typey .self(tipo y metatipo).
Barry Jones
0

Uso obj-getclass:

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

}

Asumir uno mismo es un objeto de información de la ciudad.

Deshacer
fuente
0

Swift 5

No es exactamente la misma situación, pero estaba teniendo un problema similar. Lo que finalmente me ayudó fue esto:

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

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

        default: break
    }
}

Luego puede llamarlo dentro de una instancia de dicha clase como esta:

myFunction(type(of: self))

Espero que esto ayude a alguien en mi misma situación.

Merricat
fuente