符合协议的所有类继承默认实现

时间:2016-07-09 23:45:49

标签: ios swift protocols swift-protocols

我已经为我的所有UIViewController子类添加了一个方法,允许我从类和它里面的故事板中实例化它。

所有方法都遵循以下格式:

class func instantiateFromStoryboard() -> CameraViewController? {

    let storyboard = UIStoryboard(name: "Camera", bundle: nil)

    let initial = storyboard.instantiateInitialViewController()

    guard let controller = initial as? CameraViewController else {
        return nil
    }

    return controller
}

相反,我想制定一个协议Instantiatable,它需要上面的方法和变量storyboardName: String

然后,我想扩展此Instantiatable,因此它包含与上面类似的实现。我的目标是,我可以声明UIViewController遵守此协议,我必须定义的所有内容都是storyboardName

我觉得我很接近这个实现:

protocol Instantiatable {
    var storyboardName: String { get }
    func instantiateFromStoryboard() -> Self?
}

extension Instantiatable where Self: UIViewController {
    func instantiateFromStoryboard() -> Self? {

        let storyboard = UIStoryboard(name: storyboardName, bundle: nil)

        let initial = storyboard.instantiateInitialViewController()

        guard let controller = initial as? Self else {
            return nil
        }

        return controller
    }
}

但是,当我尝试向CameraViewController添加一致性时,我收到错误:

  

非最终班级instantiateFromStoryboard()中的方法CameraViewController必须返回Self以符合协议Instantiatable

我错过了什么?

感谢。

2 个答案:

答案 0 :(得分:1)

您可以使用泛型来实现您的目标。像这样:

protocol Instantiatable {
    static func instantiateFromStoryboard<T: UIViewController>() -> T?
}

extension Instantiatable where Self: UIViewController {
    static func instantiateFromStoryboard<T: UIViewController>() -> T? {

        let storyboard = UIStoryboard(name: self.description(), bundle: nil)

        let initial = storyboard.instantiateInitialViewController() as? T

        guard let _ = initial as? Self else {
            return nil
        }     
        return initial
    }
}

因此,如果VCA为instantiatable,您可以说let vCA = VCA.InstantiateFromStoryboard()

我将函数更改为类函数,以便您可以在类上调用它,而不需要视图控制器的实例。此代码使用类名来检索storyboard文件,但这意味着您的storyboard需要被称为 projectname.classname.storyboard ,这有点难看。

另一种方法是要求视图控制器类实现一个返回故事板名称的函数:

protocol Instantiatable {
  //  var storyboardName: String { get }
    static func instantiateFromStoryboard<T: UIViewController>() -> T?
    static func storyboardName() -> String
}

extension Instantiatable where Self: UIViewController {
    static func instantiateFromStoryboard<T: UIViewController>() -> T? {

        let storyboard = UIStoryboard(name: self.storyboardName(), bundle: nil)           
        let initial = storyboard.instantiateInitialViewController() as? T

        guard let _ = initial as? Self else {
            return nil
        }            
        return initial
    }
}

然后在每个Instantiatable中你需要实现:

static func storyboardName() -> String {
    return "ViewController" // (or whatever the storyboard name is)
}

修改

根据您的回答和@AliSoftware的评论,第三个(可能是最好的)替代方法是制作UIViewController子类final

这可以让你使用

protocol Instantiatable {
  //  var storyboardName: String { get }
    static func instantiateFromStoryboard() -> Self?
    static func storyboardName() -> String
}

extension Instantiatable where Self: UIViewController {
    static func instantiateFromStoryboard() -> Self? {

        let storyboard = UIStoryboard(name: self.storyboardName(), bundle: nil)

        let initial = storyboard.instantiateInitialViewController() as? Self

        return initial
    }
}

只要您将视图控制器声明为:

final class ViewController: UIViewController, Instantiatable {
  ....

答案 1 :(得分:1)

添加final

这里的解决方案只是将final添加到子类UIViewController(在我的示例中,它是CameraViewController)。

这允许您的呼叫网站正确推断UIViewController的类型而不进行投射。在我的示例中,呼叫站点是:

guard let controller = CameraViewController.instantiate() else {
    return
}

但是,为什么?

为什么添加最终关键字很重要?

在与@AliSoftware讨论后,他解释了final的必要性。 (他还在Swift mixin存储库Reusable中添加了类似的协议。)

编译器会关注您的自定义VC是否为final,以确保可以静态推断Self要求Instantiatable提及。

在一个例子中:

class ParentVC: UIViewController, Instantiatable {
    // Because of Instantiatable, this class automatically has a method
    // with this signature:
    func instantiate() -> ParentVC // here, the "Self" coming from the Protocol, meaning "the conforming class", which is solved as "ParentVC"
}

class ChildVC: ParentVC {
    // Error: It inherits from ParentVC, and has to conform 
    // to Instantiatable as a Parent
    func instantiate() -> ParentVC
    // but, it also has this from being a Instantiatable object itself
    func instantiate() -> ChildVC
    // thus, the compiler cannot solve what "Self" should resolve to here, 
    // either ParentVC or ChildVC.
    //
    // Also, it can generate problems in various contexts being "of 
    // both types" here, which is why it's not permitted
}

为什么添加final是好的

  1. 您的自定义VC可以直接从UIViewController继承,但不需要再进行子类化,因此无论如何都应该将其标记为final

  2. 或者,您正在创建父抽象CommonVC。您打算从中继承多个子项(class CustomVC1: CommonVCclass CustomVC2: CommonVC),但在这种情况下,CommonVC是抽象的,可能不会直接实例化。因此,它不是标记为Instantiatable的那个,您应该将CustomVC1等标记为final + Instantiatable