Вернуть тип instancetype в Swift
Я пытаюсь сделать это расширение:
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
}
}
Но я получаю ошибку компиляции:
error: невозможно преобразовать возвращаемое выражение типа 'UIViewController' в return type 'Self'
Возможно ли это? Также я хочу сделать это как init(storyboardName: String, storyboardId: String)
Ответы
Ответ 1
Как и в случае использования "self" в функциях расширения класса в Swift, вы можете определить универсальный вспомогательный метод, который выводит тип self из вызывающего контекста:
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
}
}
затем
let vc = MyViewController.instantiateFromStoryboard("name", storyboardId: "id")
компилируется, и тип выводится как MyViewController
.
Обновление для 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
}
}
Другое возможное решение, использующее 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)
}
}
Ответ 2
Self
определяется во время компиляции, а не во время выполнения. В вашем коде Self
в точности эквивалентен UIViewController
, а не "подклассу, который вызывает это". Это вернет UIViewController
, и вызывающий должен будет as
в правый подкласс. Я предполагаю, что вы пытались избежать (хотя это обычный способ Cocoa ", так что просто возвращение UIViewController
, вероятно, лучшее решение).
Примечание. Вы не должны называть функцию initialize
в любом случае. Это существующая функция класса NSObject
и в лучшем случае вызовет путаницу, ошибки в худшем случае.
Но если вы хотите избежать вызова as
, подкласс обычно не является инструментом для добавления функций в Swift. Вместо этого вам обычно нужны дженерики и протоколы. В этом случае вам нужны только дженерики.
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
}
Это не метод класса. Это просто функция. Здесь нет необходимости в классе.
let tvc: UITableViewController = instantiateViewController(name: name, storyboardId: storyboardId)
Ответ 3
Другой способ - использовать протокол, который также позволяет вам возвращать 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
}
}
Ответ 4
Более чистое решение (по крайней мере, визуально аккуратнее):
Swift 5.1
class func initialize(storyboardName: String, storyboardId: String) -> Self {
return UIStoryboard(name: storyboardName, bundle: nil)
.instantiateViewController(withIdentifier: storyboardId).view as! Self // this is now possible in since 5.1
}
Старое решение
class func initialize(storyboardName: String, storyboardId: String) -> Self {
// The absurdity that Swift 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()
}