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
AnyClasscomo un tipo de parámetro es lo correcto? - Cuando llamo al método, paso
MyObject.selfcomo 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.