如何覆盖子类的 swift 协议函数(例如 UIView 中的 UILabel)

Posted

技术标签:

【中文标题】如何覆盖子类的 swift 协议函数(例如 UIView 中的 UILabel)【英文标题】:How to override swift protocol functions for subclasses (e.g. UILabel from UIView) 【发布时间】:2019-08-05 11:38:53 【问题描述】:

我正在尝试实现一个扩展函数,该函数应该根据使用它的类的类型而有所不同。 对象需要是 UIView(或子类)。它应该始终使用在指定类型上扩展的函数,但如果它不符合其中任何一个,则应该使用 UIView 方法(作为后备)。

这是我正在尝试做的一个示例:

protocol aProtocol 
    typealias completionBlock = (_ finished:Bool)->()
    func doSomething(completion: completionBlock)



extension UIView: aProtocol 
    func doSomething(completion: (Bool) -> ()) 
        print("Im an UIView")
    


extension aProtocol where Self: UILabel 
    func doSomething(completion: (Bool) -> ()) 
        print("im an UILabel")
    


extension aProtocol where Self: UIImageView 
    func doSomething(completion: (Bool) -> ()) 
        print("im an UIImageView")
    

执行:

UIView().doSomething  (foo) in  // Should print "Im an UIView"
UIButton().doSomething  (foo) in  // Should print "Im an UIView" (UIButton doesent have specified extended function so should fall back on the UIView function)
UILabel().doSomething  (foo) in  // Should print "im an UILabel"
UIImageView().doSomething  (foo) in  // Should print "im an UIImageView"

现在打印:

Im an UIView
Im an UIView
Im an UIView
Im an UIView

这意味着它总是使用 UIView 方法,即使我希望它使用它自己的方法。我的目标是这样打印:

Im an UIView
Im an UIView
im an UILabel
im an UIImageView

【问题讨论】:

因为一个UILabel永远是一个UIView, @vollan 当然,UILabel 永远是 UIView 的子类。我想弄清楚的是子类的指定函数是否可以覆盖 UIView 函数。 抱歉,误会了。在下面发布答案。 @Pointblaster 查看我的答案。我解释了为什么会发生这种情况以及如何满足您的需求。 问题是扩展没有创建覆盖。他们定义了一种新方法。 【参考方案1】:

您可以按如下方式实现,您只需将aProtocol 暴露给Objective-c 运行时,以便overriding 其在extension 中的方法。

@objc protocol aProtocol 
    typealias completionBlock = (_ finished:Bool)->()
    func doSomething(completion: completionBlock)


extension UIView: aProtocol 
    func doSomething(completion: (Bool) -> ()) 
        print("Im an UIView")
    


extension UILabel 
    override func doSomething(completion: (Bool) -> ()) 
        // you can call super.doSomething(completion: completion)
        print("im an UILabel")
    


extension UIImageView 
    override func doSomething(completion: (Bool) -> ()) 
        // you can call super.doSomething(completion: completion)
        print("im an UIImageView")
    

输出:

Im an UIView
Im an UIView
im an UILabel
im an UIImageView

【讨论】:

这确实既有趣又奇怪。你能解释一下为什么它可以使用@objc 协议吗? 这正是我想要的。谢谢!【参考方案2】:

- 答案

符合协议的具体类型将在协议约束上使用。所以通过改变这个:

extension UIView: aProtocol 
    func doSomething(completion: (Bool) -> ()) 
        print("Im an UIView")
    

到这里:

extension aProtocol where Self: UIView 
    func doSomething(completion: (Bool) -> ()) 
        print("im an UIView")
    


extension UIView: aProtocol 

您的代码将按预期工作。

- 替代方案

您可以通过以下方式获得所有想要的打印效果:

extension aProtocol 
    func doSomething(completion: completionBlock) 
        print("im a \(type(of: self))")
    


extension UIView: aProtocol 

这意味着您可以在协议扩展中检查对象的实际类型。

- 解释

协议扩展不会覆盖任何方法。事实上,如果实际的具体类型没有实现它,它们只是默认实现。

并且协议约束定义了witch type可以推断出它的默认实现。所以:

extension aProtocol where Self: UILabel

表示符合aProtocol 且未实现要求的UILabel 的任何子类应推断默认实现。所以这只有在UILabel 直接符合aProtocol 时才有效:

extension UILabel: aProtocol 

或者如果它的超类符合它:

extension UIView: aProtocol 

【讨论】:

【参考方案3】:
protocol aProtocol 
    typealias completionBlock = (_ finished:Bool)->()


extension aProtocol 

    func doSomething(completion: completionBlock) 
        switch self 
        case is UILabel:
            print("im a label")
            break
        case is UIView:
            print("im a view")
            break
        default:
            break
        

    


extension UIView: aProtocol
extension UILabel: aProtocol

func testing() 

    let view = UIView()
    view.doSomething  (value) in
        // Do something
    

    let label = UILabel()
    label.doSomething  (value) in
        // Do something
    

确保 UIView 的大小写在列表的底部(或至少在您想要屏蔽的所有内容的下方)

【讨论】:

以上是关于如何覆盖子类的 swift 协议函数(例如 UIView 中的 UILabel)的主要内容,如果未能解决你的问题,请参考以下文章

Swift 子类化 - 如何覆盖 Init()

Swift - 必须被子类覆盖的类方法

移动时如何访问 SKSpriteNode 子类的对象(Swift)

在 Swift 3.1 中为 UIView 子类设置默认外观而不覆盖 initialize()

Swift - 使用不同的默认参数覆盖函数

如何在 Swift 中覆盖协议扩展的计算属性