extension - Tipo de instancia de retorno en Swift
swift protocol (4)
Estoy tratando de hacer esta extensión:
extension UIViewController
{
class func initialize(storyboardName: String, storyboardId: String) -> Self
{
let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! Self
return controller
}
}
Pero me sale un error de compilación:
error: no se puede convertir la expresión de retorno del tipo ''UIViewController'' al tipo de retorno ''Self''
¿Es posible?
También quiero hacerlo como
init(storyboardName: String, storyboardId: String)
Al igual que en el uso de ''self'' en las funciones de extensión de clase en Swift , puede definir un método auxiliar genérico que infiere el tipo de self del contexto de llamada:
extension UIViewController
{
class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
{
return instantiateFromStoryboardHelper(storyboardName, storyboardId: storyboardId)
}
private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewControllerWithIdentifier(storyboardId) as! T
return controller
}
}
Entonces
let vc = MyViewController.instantiateFromStoryboard("name", storyboardId: "id")
compila, y el tipo se infiere como
MyViewController
.
Actualización para Swift 3:
extension UIViewController
{
class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
{
return instantiateFromStoryboardHelper(storyboardName: storyboardName, storyboardId: storyboardId)
}
private class func instantiateFromStoryboardHelper<T>(storyboardName: String, storyboardId: String) -> T
{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: storyboardId) as! T
return controller
}
}
Otra posible solución, usando
unsafeDowncast
:
extension UIViewController
{
class func instantiateFromStoryboard(storyboardName: String, storyboardId: String) -> Self
{
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: storyboardId)
return unsafeDowncast(controller, to: self)
}
}
Otra forma es usar un protocolo, que también le permite regresar
Self
.
protocol StoryboardGeneratable {
}
extension UIViewController: StoryboardGeneratable {
}
extension StoryboardGeneratable where Self: UIViewController
{
static func initialize(storyboardName: String, storyboardId: String) -> Self
{
let storyboad = UIStoryboard(name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewController(withIdentifier: storyboardId) as! Self
return controller
}
}
Una solución más limpia (al menos visualmente más ordenada):
Swift 5.1
class func initialize(storyboardName: String, storyboardId: String) -> Self {
// The absurdity that''s Swift''s type system. If something is possible to do with two functions, why not let it be just one?
func loadFromImpl<T>() -> T {
let storyboard = UIStoryboard(name: storyboardName, bundle: nil)
return storyboard.instantiateViewController(withIdentifier: storyboardId).view as! T
}
return loadFromImpl()
}
Solución anterior
class func initialize(storyboardName: String, storyboardId: String) -> Self { // The absurdity that''s Swift''s type system. If something is possible to do with two functions, why not let it be just one? func loadFromImpl<T>() -> T { let storyboard = UIStoryboard(name: storyboardName, bundle: nil) return storyboard.instantiateViewController(withIdentifier: storyboardId).view as! T } return loadFromImpl() }
Self
se determina en tiempo de compilación, no en tiempo de ejecución.
En su código,
Self
es exactamente equivalente a
UIViewController
, no "la subclase que llama esto".
Esto devolverá
UIViewController
y la persona que llama tendrá que hacerlo en la subclase correcta.
Supongo que eso es lo que estaba tratando de evitar (aunque es la forma "normal de Cocoa" de hacerlo, por lo que simplemente devolver
UIViewController
es probablemente la mejor solución).
Nota: No debe nombrar la función
initialize
en ningún caso.
Esa es una función de clase existente de
NSObject
y causaría confusión en el mejor de los casos, errores en el peor.
Pero si desea evitar el interlocutor, la subclase no suele ser la herramienta para agregar funcionalidad en Swift. En cambio, generalmente quieres genéricos y protocolos. En este caso, los genéricos son todo lo que necesita.
func instantiateViewController<VC: UIViewController>(storyboardName: String, storyboardId: String) -> VC {
let storyboad = UIStoryboard(name name: storyboardName, bundle: nil)
let controller = storyboad.instantiateViewControllerWithIdentifier(storyboardId) as! VC
return controller
}
Este no es un método de clase. Es solo una función. No hay necesidad de una clase aquí.
let tvc: UITableViewController = instantiateViewController(name: name, storyboardId: storyboardId)