方法在Swift中调整 [英] Method swizzling in Swift

查看:133
本文介绍了方法在Swift中调整的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正试图克服 UINavigationBar 的问题。在视图控制器中使用 prefersStatusBarHidden()方法隐藏状态栏时(我不想禁用整个应用程序的状态栏),导航栏丢失20pt它的高度属于状态栏。导航栏基本上会缩小。

There is a little problem with UINavigationBar which I'm trying to overcome. When you hide the status bar using prefersStatusBarHidden() method in a view controller (I don't want to disable the status bar for the entire app), the navigation bar loses 20pt of its height that belongs to the status bar. Basically the navigation bar shrinks.


我正在尝试不同的解决方法,但我发现它们中的每一个都有缺点。然后我遇到了使用方法调配的类别,添加了一个名为 fixedHeightWhenStatusBarHidden 到解决此问题的 UINavigationBar 类。我在Objective-C中进行了测试,它确实有效。以下是标题实现

I was trying out different workarounds but I found that each one of them had drawbacks. Then I came across this category which using method swizzling, adds a property called fixedHeightWhenStatusBarHidden to the UINavigationBar class which solves this issue. I tested it in Objective-C and it works. Here are the header and the implementation of the original Objective-C code.

现在,因为我在Swift中运行我的应用程序,所以我尝试将其转换为Swift。

Now since I'm doing my app in Swift, I tried translating it to Swift.

我遇到的第一个问题是Swift扩展无法存储属性。所以我不得不解决计算属性来声明 fixedHeightWhenStatusBarHidden 属性,这使我能够设置值。但这引发了另一个问题。显然,您无法为计算属性赋值。像这样。

The first problem I faced was Swift extension can't have stored properties. So I had to settle for computed property to declare the fixedHeightWhenStatusBarHidden property that enables me to set the value. But this sparks another problem. Apparently you can't assign values to computed properties. Like so.

self.navigationController?.navigationBar.fixedHeightWhenStatusBarHidden = true

我收到错误无法指定此表达式的结果

无论如何,下面是我的代码。它编译没有任何错误,但它不起作用。

Anyway below is my code. It compiles without any errors but it doesn't work.

import Foundation
import UIKit

extension UINavigationBar {

    var fixedHeightWhenStatusBarHidden: Bool {
        return objc_getAssociatedObject(self, "FixedNavigationBarSize").boolValue
    }

    func sizeThatFits_FixedHeightWhenStatusBarHidden(size: CGSize) -> CGSize {

        if UIApplication.sharedApplication().statusBarHidden && fixedHeightWhenStatusBarHidden {
            let newSize = CGSizeMake(self.frame.size.width, 64)
            return newSize
        } else {
            return sizeThatFits_FixedHeightWhenStatusBarHidden(size)
        }

    }
    /*
    func fixedHeightWhenStatusBarHidden() -> Bool {
        return objc_getAssociatedObject(self, "FixedNavigationBarSize").boolValue
    }
    */

    func setFixedHeightWhenStatusBarHidden(fixedHeightWhenStatusBarHidden: Bool) {
        objc_setAssociatedObject(self, "FixedNavigationBarSize", NSNumber(bool: fixedHeightWhenStatusBarHidden), UInt(OBJC_ASSOCIATION_RETAIN))
    }

    override public class func load() {
        method_exchangeImplementations(class_getInstanceMethod(self, "sizeThatFits:"), class_getInstanceMethod(self, "sizeThatFits_FixedHeightWhenStatusBarHidden:"))
    }

}

fixedHeightWhenStatusBarHidden()中间的方法被注释掉了,因为它给了我一个方法重新声明错误。

fixedHeightWhenStatusBarHidden() method in the middle is commented out because leaving it gives me a method redeclaration error.

我之前没有在Swift或Objective-C中进行过方法调整,所以我不确定下一步要解决这个问题,甚至根本不可能。

I haven't done method swizzling in Swift or Objective-C before so I'm not sure of the next step to resolve this issue or even it's possible at all.

有人可以请求e对此有所了解?

Can someone please shed some light on this?

谢谢。

更新1:感谢newacct,我的第一期关于属性的问题已经解决。但代码仍然不起作用。我发现执行没有到达扩展中的 load()方法。根据答案中的评论,我了解到您的班级需要是 NSObject的后代,在我的情况下, UINavigationBar 不是直接来自 NSObject ,但它确实实现了 NSObjectProtocol 。所以我不确定为什么这仍然不起作用。另一个选项是添加 @objc ,但Swift不允许您将其添加到扩展名。以下是更新后的代码。

UPDATE 1: Thanks to newacct, my first issue about properties was resolved. But the code doesn't work still. I found that the execution doesn't reach the load() method in the extension. From comments in this answer, I learned that either your class needs to be descendant of NSObject, which in my case, UINavigationBar isn't directly descended from NSObject but it does implement NSObjectProtocol. So I'm not sure why this still isn't working. The other option is adding @objc but Swift doesn't allow you to add it to extensions. Below is the updated code.

问题仍然存在。

import Foundation
import UIKit

let FixedNavigationBarSize = "FixedNavigationBarSize";

extension UINavigationBar {

    var fixedHeightWhenStatusBarHidden: Bool {
        get {
            return objc_getAssociatedObject(self, FixedNavigationBarSize).boolValue
        }
        set(newValue) {
            objc_setAssociatedObject(self, FixedNavigationBarSize, NSNumber(bool: newValue), UInt(OBJC_ASSOCIATION_RETAIN))
        }
    }

    func sizeThatFits_FixedHeightWhenStatusBarHidden(size: CGSize) -> CGSize {

        if UIApplication.sharedApplication().statusBarHidden && fixedHeightWhenStatusBarHidden {
            let newSize = CGSizeMake(self.frame.size.width, 64)
            return newSize
        } else {
            return sizeThatFits_FixedHeightWhenStatusBarHidden(size)
        }

    }

    override public class func load() {
        method_exchangeImplementations(class_getInstanceMethod(self, "sizeThatFits:"), class_getInstanceMethod(self, "sizeThatFits_FixedHeightWhenStatusBarHidden:"))
    }

}






更新2: Jasper帮助我实现了所需的功能,但显然它带来了几个主要缺点。


UPDATE 2: Jasper helped me to achieve the desired functionality but apparently it comes with a couple of major drawbacks.

load()扩展中的方法没有触发,正如Jasper建议的那样,我将以下代码块移动到app delegates' didFinishLaunchingWithOptions 方法。

Since the load() method in the extension wasn't firing, as Jasper suggested, I moved the following code block to app delegates' didFinishLaunchingWithOptions method.

method_exchangeImplementations(class_getInstanceMethod(UINavigationBar.classForCoder(), "sizeThatFits:"), class_getInstanceMethod(UINavigationBar.classForCoder(), "sizeThatFits_FixedHeightWhenStatusBarHidden:"))

我必须在 fixedHeightWhenStatusBarHidden 属性的getter中将返回值硬编码为'true',因为现在在代理委托中执行了混合代码,你可以不要从视图控制器设置值。这使得扩展在可重用性方面变得多余。

And I had to hardcode the return value to 'true' in the getter of fixedHeightWhenStatusBarHidden property because now that the swizzling code executes in the app delegate, you can't set a value from a view controller. This makes the extension redundant when it comes to reusability.

所以问题仍然或多或少地开放。如果有人有想法改进它,请回答。

So the question is still open more or less. If anyone has an idea to improve it, please do answer.

推荐答案

Objective-C,它使用动态调度支持以下内容:

Objective-C, which uses dynamic dispatch supports the following:

类方法调整:

您在上面使用的那种。类的所有实例都将使用新实现替换其方法。新的实现可以选择包装旧的。

The kind you're using above. All instances of a class will have their method replaced with the new implementation. The new implementation can optionally wrap the old.

Isa-pointer调配:

类的实例设置为一个新的运行时生成的子类。此实例的方法将替换为新方法,该方法可以选择性地包装现有方法。

An instance of a class is set to a new run-time generated sub-class. This instance's methods will be replaced with a new method, which can optionally wrap the existing method.

消息转发:

一个类充当另一个类的代理,通过执行一些工作,然后将消息转发给另一个处理程序。

A class acts as a proxy to another class, by performing some work, before forwarding the message to another handler.

这些都是强大的拦截模式的变种,这是Cocoa最好的功能所依赖的。

These are all variations on the powerful intercept pattern, which many of Cocoa's best features rely on.

输入Swift:

Swift延续了ARC的传统,因为编译器将代表您进行强大的优化。它将尝试内联您的方法或使用静态调度或vtable调度。虽然速度更快,但这些都会阻止方法拦截(在没有虚拟机的情况下)。但是你可以通过遵守以下内容向Swift表明你想要动态绑定(以及方法拦截):

Swift continues the tradition of ARC, in that the compiler will do powerful optimizations on your behalf. It will attempt to inline your methods or use static dispatch or vtable dispatch. While faster, these all prevent method interception (in the absence of a virtual machine). However you can indicate to Swift that you'd like dynamic binding (and therefore method interception) by complying with the following:


  • 通过扩展NSObject或使用@objc指令。

  • 通过向函数添加动态属性,例如 public dynamic func foobar() - > AnyObject

  • By extending NSObject or using the @objc directive.
  • By adding the dynamic attribute to a function, eg public dynamic func foobar() -> AnyObject

在上面提供的示例中,满足了这些要求。您的 类来自 NSObject ,通过 UIView 传递 UIResponder ,但该类别有些奇怪:

In the example you provide above, these requirements are being met. Your class is derived transitively from NSObject via UIView and UIResponder, however there is something odd about that category:


  • 该类别覆盖load方法,通常会为类调用一次。从类别中执行此操作可能并不明智,我猜测虽然它之前可能有用,但在Swift的情况下却没有。

尝试将Swizzling代码移动到AppDelegate中并完成启动:

//Put this instead in AppDelegate
method_exchangeImplementations(
    class_getInstanceMethod(UINavigationBar.self, "sizeThatFits:"), 
    class_getInstanceMethod(UINavigationBar.self, "sizeThatFits_FixedHeightWhenStatusBarHidden:"))  

这篇关于方法在Swift中调整的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆