2016-05-31 58 views
13

我试着为UIView编写一个静态方法,它从nib实例化该类的视图。方法应该是通用的,并在每个UIView子类上工作。此外,我想保存类型的信息 - 这样,例如,在这段代码自我,协议扩展和非最终类

let myView = MyView.loadFromNib() 

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

它继续工作!

所以任何人都可以解释一下为什么我的第一个变体失败了,为什么第二个和第三个工作正常以及它如何与最终类相关?

+0

我不知道确切的答案你的问题,但为什么不只是延长'UIView'直接跳过协议? – JAL

+0

现在没有时间来测试它,但我不认为返回自我是一个很好的模式。你应该使用协议的关联类型: https://www.natashatherobot.com/swift-protocols-with-associated-types/ – 66o

+0

@JAL:在我开始使用协议之前,我尝试了这种方法。它不适合我,因为在这种情况下,我不能在方法体内使用'Self'。没有'Self'我就无法得到这种类型推断。 –

回答

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的任何子类都将有一个Self类型,它是所需的类型UIView

  3. 拆除那里,因为你改变了协议方法返回的UIView,协议扩展定义了返回Self匹配方法的实现,然后只UIView的越来越您的示例代码扩展条款仅适用于这个特定的情况下。因此,返回UIView的方法的协议要求与实现UIView获得的实现相匹配,其返回Self(在这种情况下恰好是UIView)。但是,如果您尝试使UIView以外的任何类型获得协议扩展方法,例如

    class SomeClass : NibLoadable {} 
    

    甚至

    class MyView:UIView, NibLoadable {} 
    

    编译器不会允许它,因为在协议扩展方法的Self返回类型将不匹配的协议所需的UIView。尽管我觉得就“MyView”或其他UIView子类而言,编译器错误可能是一个错误,因为如果MyView从UIView继承,返回MyView的方法将满足协议要求,即方法返回UIView

总结一些要点:

  • 它看起来并不像协议扩展在你提到的编译器错误的任何角色。就在今年也将创建错误:

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

    所以它看起来像编译器不允许非final类通过使用具有自我,期间的返回类型的方法的默认实现通过议定书。

  • 如果改变协议的方法签名返回UIView,而不是Self特定的编译器警告消失,因为不再有子类继承父类返回类型,并且打破了协议的可能性。然后,您可以使用协议扩展将协议添加到UIView。然而,如果您尝试采用比UIView的其他任何类型的协议,你会得到一个不同错误,因为UIView协议返回类型将不只是在单个情况下,协议扩展方法的的Self返回类型匹配UIView的。在我看来,这可能是一个错误,因为Self对于UIView的任何子类都应该符合要求的UIView返回类型合同。

  • 但奇怪的是,如果你采纳的UIView只,UIView的子类将继承他们是否符合协议(避免任何两个以上编译器错误的触发)的协议,从一开始他们的通用实现协议扩展,只要UIView没有明确实现协议方法本身。因此,子类将得到相应的Self的类型推断,并且满足该方法返回UIView的协议合约。

我很确定这里有一个或多个错误,但Swift团队中的某个人必须确认。

UPDATE

作出一些澄清雨燕队在这个Twitter的主题:

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

由于怀疑,这是一个编译器的限制(但显然没有考虑彻头彻尾的错误),该协议匹配不考虑子类型,只有精确类型匹配。这就是为什么当协议方法定义返回类型为UIView时,extension UIView:NibLoadable {}将工作,但extension MyView:NibLoadable {}不会。

+0

感谢您的详细解释,Daniel!我还有一个问题:为什么在第二个(和第三个)类型推理中起作用?如果我理解正确,现在所有'UIView'子类都有返回'UIView'的方法。编译器如何知道实际类型? –

+0

@AlexanderDoloz我广泛编辑了我的答案,以便更具体地了解编译器错误中的重要内容,什么不是,以及提供有关不同情况的更多细节以及编译器中可能存在的问题。现在还解决类型推断问题 –

+0

'因此,看起来编译器不允许非最终类采用具有自回归类型的方法,period。“这不是事实。我认为这仍然是一个bug,问题在于使用'as!自我'部分。如果你用'UIViewController'(它也是一个非final类)做类似的事情,它会通过。这是要点:https://gist.github.com/ozgur/4d42b4ca0d17caa2f3d2a770e478e2a9 – ozgur

0

使用下面的代码应该是(在斯威夫特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()