3

I have a protocol called NakedNavigationBar.

I also have an extension that extends all UIViewControllers that conform to NakedNavigationBar.

The problem is that in the extension I want to add default behaviour so that when the UIViewController initialises, we use method swizzling on the UIViewController.

Here is my protocol and extension:

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)
    }
}

The errors I get when building are: errors

Essentially telling me that I cannot extend the protocol because it doesn’t have the UIViewController methods. But as I understand it, the where Self: UIViewController should make it so that this only works on a UIViewController, extending the view controller only when it conforms to NakedNavigationBar.

Originally the extension was extension UIViewController: NakedNavigationBar but this makes all of my UIViewControllers instantly conform to NakedNavigationBar rather than only the ones I choose.

4

1 回答 1

4

据我了解,从 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 类型安全的做事方式(也许我们可以改进如果它允许对类扩展的自我要求#fingersCrossed,这将在以后的 Swift 版本中出现)

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

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 方法将被调用并在 ALL 上生效UIViewControllers,即使我们只验证那些符合NakedNavigationBar协议以运行敏感代码行的人。

我非常希望它有所帮助。如果有人找到另一种(更好的)方法,很高兴听到它!

于 2016-11-17T12:00:37.957 回答