仅在符合特定协议时才对类进行 Swift 扩展

Posted

技术标签:

【中文标题】仅在符合特定协议时才对类进行 Swift 扩展【英文标题】:Swift extension of a class ONLY when it conforms to a specific protocol 【发布时间】:2016-11-17 12:44:05 【问题描述】:

您好 =) 我刚刚遇到了一个设计问题,我需要(基本上)执行以下操作:

我想在 任何符合协议 MyProtocolUIViewController 子类的 viewWillAppear: 上注入一些代码。代码解释:

protocol MyProtocol

    func protocolFunction() 
        //do cool stuff...
    


extension UIViewController where Self: MyProtocol //<-----compilation error

    public override class func initialize()
    
        //swizzling stuff switching viewWillAppear(_: Bool) with xxx_viewWillAppear(animated: Bool)
    

    //  MARK: - Swizzling

    func xxx_viewWillAppear(animated: Bool)
    
        self.xxx_viewWillAppear(animated)

        //invoke APIs from  
        self.protocolFunction() // MyProtocol APIs
        let viewLoaded = self.isViewLoaded // UIViewController APIs

    

这里的主要问题是我需要在 UIVIewController 扩展中做两件事:

    同时调用 MyProtocolUIViewController API 覆盖UIViewController 方法initialize() 以便能够调动viewWillAppear:

这 2 个功能似乎不兼容(从 Swift 3 开始),因为:

    我们不能用条件扩展类(即extension UIViewController where Self: MyProtocol) 如果我们改为扩展协议,我们可以添加条件extension MyProtocol where Self: UIViewController,但我们不能覆盖协议扩展中类的方法,这意味着我们可以' t public override class func initialize() 这是调酒所需要的。

所以我想知道是否有人可以为我面临的这个问题提供 Swifty 解决方案? =)

提前致谢!!

【问题讨论】:

为什么不使用简单继承?它是覆盖默认类方法的理想选择。 嘿@MaxPevsner 感谢您的评论! =) 我选择不使用继承,因为我将其构建为系统的一部分,以便在多个项目中使用,并且我不希望 API 强制每个项目的 UIViewControllers 从特定的超类继承(因为可能每个这些项目需要自己的基类)所以如果可能的话我想避免这种情况。 我相信,你在这里漏掉了一点。但这种讨论远远超出了这个问题的范围。 【参考方案1】:

您已接近解决方案。只需要换一种方式。仅当协议是 UIViewController 的一部分时才扩展协议。

protocol MyProtocol

  func protocolFunction() 
    //do cool stuff...
  


extension MyProtocol where Self: UIViewController 
  public override class func initialize()
  
    //swizzling stuff switching viewWillAppear(_: Bool) with xxx_viewWillAppear(animated: Bool)
  

  //  MARK: - Swizzling

  func xxx_viewWillAppear(animated: Bool)
  
    self.xxx_viewWillAppear(animated)

    //invoke APIs from  
    self.protocolFunction() // MyProtocol APIs
    let viewLoaded = self.isViewLoaded // UIViewController APIs
  

【讨论】:

嘿,感谢您的回复 =) 问题是我在描述中所说的:"如果我们改为扩展协议,我们可以添加条件扩展 MyProtocol where Self: UIViewController but 我们不能覆盖协议扩展中类的方法,这意味着我们不能公开覆盖 swizzling 所需的类 func initialize()。”【参考方案2】:

嗯,到目前为止,我还没有找到真正令人满意的方法,但我决定发布我最终为这个特定问题所做的事情。 简而言之,解决方案是这样的(使用原始示例代码):

protocol MyProtocol

    func protocolFunction() 
        //do cool stuff...
    


extension UIViewController //------->simple extension on UIViewController directly

    public override class func initialize()
    
        //swizzling stuff switching viewWillAppear(_: Bool) with xxx_viewWillAppear(animated: Bool)
    

    //  MARK: - Swizzling

    func xxx_viewWillAppear(animated: Bool)
    
        self.xxx_viewWillAppear(animated)

        //------->only run when self conforms to MyProtocol
        if let protocolConformingSelf = self as? MyProtocol  
            //invoke APIs from  
            protocolConformingSelf.protocolFunction() // MyProtocol APIs
            let viewLoaded = protocolConformingSelf.isViewLoaded // UIViewController APIs
        

    


缺点:

不是“快速”的做事方式 将调用 swizzling 方法并对所有 UIViewControllers 生效,即使我们仅验证符合 MyProtocol 协议的那些以运行敏感代码行。

我非常希望它可以帮助其他面临类似情况的人 =)

【讨论】:

以上是关于仅在符合特定协议时才对类进行 Swift 扩展的主要内容,如果未能解决你的问题,请参考以下文章

swift 学习- 25 -- 协议 02

通过框架之间的扩展实现 Swift 协议一致性

Swift 泛型和协议扩展

扩展符合 NSFetchRequestResult 的协议

仅当 iOS11 可用时才包含类的扩展

仅在满足某些条件时才调用 Safari Content Blocker 扩展?