【问题标题】:Swift - extending a class only when it conforms to a protocol to use method swizzlingSwift - 仅在符合协议以使用方法调配时扩展类
【发布时间】:2016-05-13 19:23:22
【问题描述】:

我有一个名为NakedNavigationBar 的协议。

我还有一个扩展,它扩展了所有符合 NakedNavigationBarUIViewControllers。

问题在于,我想在扩展中添加默认行为,以便在 UIViewController 初始化时,我们在 UIViewController 上使用方法调配。

这是我的协议和扩展:

import UIKit


protocol NakedNavigationBar
{

}

extension NakedNavigationBar where Self: UIViewController
{
    public override class func initialize()
    {
        struct Static
        {
            static var token: dispatch_once_t = 0
        }

        dispatch_once(&Static.token)
        {
            let originalSelector = #selector(viewWillAppear(_:))
            let swizzledSelector = #selector(nakedNavigationBar_viewWillAppear(_:))

            let originalMethod = class_getInstanceMethod(self, originalSelector)
            let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)

            let didAddMethod = class_addMethod(self,
                                               originalSelector,
                                               method_getImplementation(swizzledMethod),
                                               method_getTypeEncoding(swizzledMethod))

            if didAddMethod
            {
                class_replaceMethod(self,
                                    swizzledSelector,
                                    method_getImplementation(originalMethod),
                                    method_getTypeEncoding(originalMethod))
            }
            else
            {
                method_exchangeImplementations(originalMethod, swizzledMethod)
            }
        }
    }


    //  MARK: - Swizzling Methods

    func nakedNavigationBar_viewWillAppear(animated: Bool)
    {
        self.nakedNavigationBar_viewWillAppear(animated)

        print("VWA Swizzling...")

        //  Hide the navigation bar
        _setNavigationBarVisible(isVisible: false)
    }


    //  MARK: -

    private func _setNavigationBarVisible(isVisible isVisible: Bool)
    {
        // (Changes background and shadow image)
    }
}

我在构建时遇到的错误是:

基本上告诉我我不能扩展协议,因为它没有UIViewController 方法。但据我了解,where Self: UIViewController 应该使它仅适用于UIViewController,仅在符合NakedNavigationBar 时扩展视图控制器。

最初的扩展名是extension UIViewController: NakedNavigationBar,但这使得我所有的UIViewControllers 立即符合NakedNavigationBar 而不仅仅是我选择的那些。

【问题讨论】:

  • 即使编译后的方法也行不通。调配方法会影响每个UIViewController,而不仅仅是符合您协议的那些。
  • 好点:/你能想出一个解决办法吗?

标签: ios swift uiviewcontroller swift-extensions method-swizzling


【解决方案1】:

据我了解,从 swift 3.0 开始,您理想地想要做的事情如下:

protocol NakedNavigationBar
{
    //your protocol definition
}

extension UIViewController where Self: NakedNavigationBar
{
    //method swizzling and all the goodies
}

注意我改变了扩展声明的顺序,因为你想要的不是协议的默认实现,而是UIViewController类的默认实现当特定子类符合NakedNavigationBar 。问题是,目前该语法无法编译!!,我们无法将Self 要求添加到类扩展中 =((我感到你很痛苦)。

另一方面,您实际尝试执行的操作(具有Self 要求的协议扩展)编译得很好,但问题是您不能覆盖协议扩展内的类方法,并试图调配你:

public override class func initialize()

Self 要求添加到协议扩展时,您可以调用该类(在本例中为UIViewController)提供的任何公共API,因此您可以执行以下操作:

protocol NakedNavigationBar
{
    func a() {
        //define cool stuff...
    }
}

extension NakedNavigationBar where Self: UIViewController
{

    func b() {
        //invoke stuff from NakedNavigationBar
        a()
        //or invoke stuff from UIViewController
        let viewLoaded = self.isViewLoaded
    }
    //but what we CAN'T do are class overrides
    override class func initialize() {} //---->compilation error 
}

我希望这个解释对你有用,我很抱歉成为坏消息的承担者,但目前你想要实现的目标在我看来以 Swifty 类型安全的方式并不可行。

但不要担心!生活还在继续!当我遇到与您相同的用例并遇到相同的问题时,我设法让事情正常进行,但它有一点缺点:它不是 Swifty 类型安全的做事方式(也许我们可以改进如果它允许对类扩展的 Self 要求,这将在以后的 Swift 版本中出现#fingersCrossed)

简而言之,解决方案是这样的(以您的原始代码为例):

import UIKit

protocol NakedNavigationBar
{

}

extension UIViewController //------->simple extension on UIViewController directly
{
    public override class func initialize()
    {
        //swizzling stuff switching viewWillAppear(_: Bool) with nakedNavigationBar_viewWillAppear(animated: Bool)
    }

    //  MARK: - Swizzling Methods

    func nakedNavigationBar_viewWillAppear(animated: Bool)
    {
        self.nakedNavigationBar_viewWillAppear(animated)

        print("VWA Swizzling...")

        //------->only run when self conforms to NakedNavigationBar
        if let protocolConformingSelf = self as? NakedNavigationBar { 
            print("UIViewControllers that conform to NakedNavigationBar, yay!!")
            //here it's safe to call UIViewController methods OR NakedNavigationBar on protocolConformingSelf instance
        }

    }


    //  anything else you want for UIViewController
}

缺点:

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

我非常希望它有所帮助。如果有人找到了另一种(更好的)方法,那就太好了!

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-06-03
    • 2013-05-19
    • 1970-01-01
    • 2016-04-30
    相关资源
    最近更新 更多