自身、协议扩展和非最终 class

Self, protocol extension and non-final class

我尝试为 UIView 编写一个静态方法,它从笔尖实例化 class 的视图。方法应该是通用的并且适用于每个 UIView subclasses。我还想保存类型信息——所以,例如,在这段代码中

let myView = MyView.loadFromNib()

编译器推断 myViewMyView class。经过几次试验后,我决定使用协议扩展,否则我将无法访问方法体内的 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 {
    ...
}

而且它一直在工作!

那么谁能解释一下为什么我的第一个变体失败了,为什么第二个和第三个变体工作正常以及它与最终 classes 有什么关系?

以下是我对您所看到情况的理解:

  1. 您在声明 extension UIView: NibLoadable {} 时遇到编译错误 Method 'loadFromNib' in non-final class 'UIView' must return 'Self' to conform to protocol 'NibLoadable'。让我们看看这条语句对编译器意味着什么。意思是 "UIView (and all of its subclasses since it is a non-final class) are adopting the NibLoadable protocol. It means, for UIView, that there will be method with the signature static func loadFromNib(name: String?) -> UIView, because Self in this context is UIView."

    但这对 UIView 的子class意味着什么?它们继承它们的一致性并且可能从 UIView 本身继承方法的实现。因此,UIView 的任何子class 都可以 具有签名为static func loadFromNib(name: String? = nil) -> UIView 的方法。但是,所有子classes 也遵守的NibLoadable 协议规定该方法的return 类型必须是Self。在任何 UIView subclass 的情况下(例如,假设 "MyView"),继承方法的 return 类型将是 UIView 而不是 MyView.所以任何 subclass 都会违反协议的约定。我知道你的协议扩展使用 Self 并且不会产生那个问题,但从技术上讲,你仍然可以直接在 UIView 扩展中实现该方法,而且 Swift 编译器似乎不会'出于这个原因,根本不允许这样做。更好的实现可能会发现 Swift 编译器验证存在提供实现的协议扩展并且没有冲突的继承实现,但这似乎只是此时不存在。因此,为了安全起见,我的猜测是编译器会阻止任何具有 Self return 类型方法的协议被非最终 class 采用。因此你看到的错误。

    但是,将 UIView 设置为 final class 会消除整个继承不一致方法的可能性和问题,从而修复错误。

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

    另一方面,类型推断有效,因为 UIView 的子classes 正在从协议扩展中获取它们的方法实现(因为该方法没有直接在 UIView 中实现)。该实现 return 是类型 Self,它告诉编译器 returned 值与调用该方法的类型具有相同的类型,并且满足协议,因为任何UIView 的 subclass 将有一个 Self 类型,它是 UIView.

  3. 所需类型的 subclass 类型
  4. 删除 where 子句仅在这种特定情况下有效,因为您将协议方法更改为 return UIView,并且协议扩展定义了 returns 的匹配方法实现Self,然后只有 UIView 在您的示例代码中获得该扩展。因此,returning UIView 方法的协议要求与 UIView 正在获取 returns Self 的实现相匹配(在这种情况下恰好是 UIView) .但是,您是否应该尝试使 UIView 以外的任何类型获得协议扩展方法,例如

    class SomeClass : NibLoadable {}
    

    甚至

    class MyView:UIView, NibLoadable {}
    

    编译器不允许,因为协议扩展方法中的 Self return 类型与协议中要求的 UIView 不匹配。我觉得在 "MyView" 或其他 UIView subclasses 的情况下,编译器错误可能是一个错误,因为 returns MyView 的方法会满足协议要求方法 return 一个 UIView,如果 MyView 确实继承自 UIView。

总结一些要点:

  • 协议扩展似乎与您提到的编译器错误无关。这也会产生错误:

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

    因此看起来编译器不允许非最终 classes 通过使用具有 return 类型的 Self,句号的方法的默认实现来采用协议。

  • 如果您将协议方法签名更改为 return UIView 而不是 Self 那个特定的编译器警告就会消失,因为不再有 sub 的可能性classes 继承了 superclass return 类型并破坏了协议。然后,您可以使用协议扩展将协议的一致性添加到 UIView。 但是,如果您尝试为 UIView 以外的任何类型采用该协议,您将得到一个 不同的 错误,因为协议 return UIView 的类型将与协议扩展方法的 return 类型的 Self 不匹配,除了 UIView 的单一情况。在我看来,这可能是一个错误,因为 Self 对于 UIView 的任何子 class 应该满足要求的 UIView return 类型合同。

  • 但奇怪的是,如果仅在 UIView 中采用协议,UIView 的子classes 将继承它们对协议的遵守(避免触发上述两个编译器错误中的任何一个)并从协议扩展中获取它们的通用实现,只要 UIView 没有明确实现协议方法本身。所以 subclasses 将获得适当的类型推断 Self,并满足协议约定让该方法 return 成为 UIView

我很确定在这一切中混合了一个或多个错误,但 Swift 团队中的某个人必须确认这一点才能确定。

更新

Swift 团队在此 Twitter 线程中的一些说明:

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

正如所怀疑的那样,协议匹配不考虑子类型,只考虑精确类型匹配,这是一个编译器限制(虽然显然不被认为是一个彻头彻尾的错误)。这就是为什么当协议方法定义 return 类型的 UIViewextension UIView:NibLoadable {} 会起作用,但 extension MyView:NibLoadable {} 不会。

使用下面的代码应该没问题(在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()