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 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 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 returningClass
parámetro y el parámetro del cierre.
De hecho, usarlo en lugar de AnyClass
permite 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 T
para pasar handler
: si intenta ejecutar el código en este momento, el compilador se quejará de que T
no es constructible ()
. Y con razón: T
tiene 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 invokeService
from <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 T
solo 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 returningClass
incluir, T
le da al compilador una forma de determinar el parámetro genérico.
CastDAO.invokeService("test", withParams: ["test" : "test"]) { (ci:CityInfo) in }