自我,协议扩展和非最终类

时间:2016-05-31 18:20:11

标签: swift protocols swift-protocols swift-extensions

我尝试为UIView编写一个静态方法,它从nib实例化该类的视图。方法应该是通用的,并且适用于每个UIView子类。另外,我想保存类型信息 - 例如,在此代码中

let myView = MyView.loadFromNib()

编译器推断myView具有MyView类。经过几次试验后,我决定使用协议扩展,否则我将无法访问方法体内的Self

看起来应该可行:

protocol NibLoadable {
    static func loadFromNib(name: String?) -> Self
}

extension NibLoadable where Self: UIView {
    static func loadFromNib(name: String? = nil) -> Self {
        let nibName = name ?? "\(self)"
        let nib = UINib(nibName: nibName, bundle: nil)
        return nib.instantiateWithOwner(nil, options: nil)[0] as! Self
    }
}

extension UIView: NibLoadable {}

但它没有。我收到编译错误

Method 'loadFromNib' in non-final class 'UIView' must return `Self` to conform to protocol 'NibLoadable'

发生了两件奇怪的事情。首先,如果我将协议声明更改为

protocol NibLoadable {
    static func loadFromNib(name: String?) -> UIView
}

一切都很有效,包括类型推断。第二,我可以进一步完全删除where条款:

extension NibLoadable {
    ...
}

它继续工作!

所以有人可以解释一下为什么我的第一个变种失败,为什么第二个和第三个变得很好以及它与最终课程有什么关系?

2 个答案:

答案 0 :(得分:25)

这是我对你所看到的内容的理解:

  1. 您在声明Method 'loadFromNib' in non-final class 'UIView' must return 'Self' to conform to protocol 'NibLoadable'时收到编译错误extension UIView: NibLoadable {}。让我们看看这个语句对编译器意味着什么。它说" UIView(及其所有子类,因为它是非最终类)正在采用NibLoadable协议。对于UIView来说,这意味着会有签名static func loadFromNib(name: String?) -> UIView的方法,因为此上下文中的Self UIView。"

    但这对于UIView的子类意味着什么呢?他们继承他们的一致性,可能从UIView本身继承方法的实现。因此,UIView 的任何子类都可以使用带有签名static func loadFromNib(name: String? = nil) -> UIView的方法。但是,所有子类也符合的NibLoadable协议表示该方法的返回类型必须为Self。在任何UIView子类的情况下(例如,让我们说" MyView"),继承方法的返回类型将是UIView而不是MyView。因此任何子类都会违反协议的合同。我意识到您的协议扩展使用了Self并且不会创建该问题,但从技术上讲,您仍然可以直接在UIView扩展中实现该方法,并且看起来Swift编译器刚刚赢得了#。由于这个原因,完全允许它。更好的实现可能会发现Swift编译器验证是否存在提供实现的协议扩展,并且没有冲突的继承实现,但此时似乎不存在。因此,为了安全起见,我的猜测是编译器会阻止具有Self返回类型的方法的任何协议被非final类采用。因此你看到的错误。

    然而,让UIView成为一个最终类使得不符合方法的整个继承可能性和问题消失,这就解决了错误。

  2. 将协议中的返回类型更改为UIView的原因是因为没有' Self'因为返回类型现在减轻了编译器对具有不符合返回类型的方法的继承版本的顾虑。例如,如果UIView要实现方法static func loadFromNib(name: String?) -> UIView,并且子类继承了该方法,那么协议契约仍然适用于那些子类,所以没有问题!

    另一方面,类型推断起作用,因为UIView的子类从协议扩展中获取它们的方法实现(因为该方法不直接在UIView中实现)。该实现返回类型Self,它告诉编译器返回的值与调用方法的类型相同,并且协议满足,因为UIView的任何子类都将具有{{1} } type是所需类型Self的子类。

  3. 删除where子句仅适用于此特定情况,因为您更改了协议方法以返回UIView,并且协议扩展定义了一个返回UIView的匹配方法实现,然后只有UIView得到了示例代码中的扩展名。因此返回Self的方法的协议要求与实现UIView的实现相匹配,返回UIView(在这种情况下恰好为Self)。但是,如果您尝试使除UIView以外的任何类型获得协议扩展方法,例如

    UIView

    甚至

    class SomeClass : NibLoadable {}
    

    编译器不允许它,因为协议扩展方法中的class MyView:UIView, NibLoadable {} 返回类型与协议中所需的Self不匹配。我觉得在" MyView"或者其他UIView子类,编译器错误可能是一个错误,因为返回UIView的方法将满足方法返回MyView的协议要求,如果MyView继承自UIView。

  4. 总结一些要点:

    • 看起来协议扩展在您注意到的编译器错误中没有任何作用。这也会产生错误:

      UIView

      因此看起来编译器不允许非最终类通过使用返回类型为Self,period的方法的默认实现来采用协议。

    • 如果更改协议方法签名以返回protocol NibLoadable { static func loadFromNib(name: String?) -> Self } extension UIView: NibLoadable {} 而不是UIView特定编译器警告消失,因为子类不再有可能继承超类返回类型并打破协议。然后,您可以使用协议扩展向UIView添加协议的一致性。 然而,如果您尝试为UIView以外的任何类型采用协议,则会出现不同的错误,因为Self的协议返回类型将不匹配协议扩展方法的返回类型为UIView,但UIView的单例除外。在我看来,这可能是一个错误,因为Self的任何子类的Self都应符合所需的UIView返回类型合同。

    • 但奇怪的是,如果你只在UIView中采用协议,UIView的子类将继承它们与协议的一致性(避免触发上述两个编译器错误中的任何一个)和只要UIView没有明确地实现协议方法本身,就可以从协议扩展中获取它们的通用实现。因此,子类将获得适当UIView的类型推断,并满足协议合同,使该方法返回Self

    我非常确定所有这些中都有一个或多个错误,但Swift团队中的某个人必须确认这一点。

    更新

    Swift团队在这篇Twitter主题中做了一些澄清:

    https://twitter.com/_danielhall/status/737782965116141568

    如所怀疑的,它是编译器限制(虽然显然不被认为是彻头彻尾的错误),协议匹配不考虑子类型,只考虑精确类型匹配。当协议方法定义返回类型UIView时,extension UIView:NibLoadable {}将起作用的原因是什么,但UIView不会。extension MyView:NibLoadable {}

答案 1 :(得分:0)

使用以下代码应该没问题(在Swift 3中):

protocol Nibable {}
extension Nibable {
    static func loadFromNib() -> Self? {
        return Bundle.main.loadNibNamed(String(describing: 
type(of:Self.self)), owner: nil, options: nil)?.first as? Self
    }
}

final class ViewFromNib: UIView {}
extension ViewFromNib: Nibable {}

var nibView = ViewFromNib.loadFromNib()