type tuple generic functions extension swift generics type-inference

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



tuple swift (3)

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 mi firma de método para lo que intento lograr?
  • Más específicamente, ¿especificar AnyClass como un tipo de parámetro es lo correcto?
  • Cuando llamo al método, paso MyObject.self como el valor de returningClass, pero obtengo 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 lo 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? }


Se está acercando de la 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 B hereda de A , entonces B.Type tambié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 deberías 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 parámetro returningClass y el parámetro del cierre.

De hecho, usarlo en lugar de AnyClass permite al compilador deducir 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 de T para pasar al handler : si intenta ejecutar el código ahora, el compilador se quejará de que T no es construible con () . Y con razón: T tiene que estar explícitamente restringido para exigir 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 invokeService de <T> a <T: Initable> .

Propina

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

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 pasa a una de las variables (lo que significa que la parte incorrecta está allí) o recibe un mensaje críptico como "No se puede convertir el tipo de expresión () a 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 T solo se usa en el parámetro del cierre y el cierre que pasó no indica ningún tipo particular, por lo que el compilador no sabe qué tipo inferir. Al cambiar el tipo de returningClass para incluir T le da al compilador una forma de determinar el parámetro genérico.


Use obj-getclass :

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

Asumir auto es un objeto de información de la ciudad.


puedes obtener la clase de AnyObject esta 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.