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.