【问题标题】:Return instancetype in Swift在 Swift 中返回实例类型
【发布时间】:2021-05-21 03:02:14
【问题描述】:

我正在尝试做这个扩展:

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
    }
}

但我得到编译错误:

错误:无法将“UIViewController”类型的返回表达式转换为 返回类型“自我”

有可能吗?我也想让它成为init(storyboardName: String, storyboardId: String)

【问题讨论】:

  • 请不要在您的问题中发布答案。

标签: swift swift2 swift-extensions


【解决方案1】:

类似于Using 'self' in class extension functions in 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)
    }
}

【讨论】:

  • 嗨 Martin,您能否解释一下编译器如何识别泛型类型 T 并将其转换为 Self?
  • 说“私有类函数”而不是“静态函数”的人有很多风格。 :)
  • 有趣的是,如果你只是让它返回基类型 - 不要为泛型而烦恼 - 它在 runtime 上完美运行(返回实际的子类)但是你' d 必须在“编辑时”将结果转换为代码。示例:stackoverflow.com/a/42053648/294884
  • @JoeBlow:是的,这里的目标只是为了避免演员阵容。 – “static”是“class + final”,因此与“private”无关。
  • 相当。谢谢@MartinR。我花了好几天才找到这个概念上相似问题的答案stackoverflow.com/q/42041150/294884
【解决方案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】:

更清洁的解决方案(至少在视觉上更整洁):

斯威夫特 5.1

class func initialize(storyboardName: String, storyboardId: String) -> Self {
    return UIStoryboard(name: storyboardName, bundle: nil)
        .instantiateViewController(withIdentifier: storyboardId).view as! Self
}

【讨论】:

    【解决方案4】:

    另一种方法是使用协议,它也允许您返回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
        }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-02-24
      相关资源
      最近更新 更多