protocol extension associated swift swift2 swift-extensions

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)