方法Swizzling不起作用 [英] Method Swizzling does not work

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

问题描述

我想利用方法调配,但我无法得到简单的例子来为我工作。我可能误解了这个概念是什么,但据我所知,它允许交换方法实现。



给定两种方法,A和B,我想交换他们的实现,这样调用A将执行B。我遇到了一些混合的例子( example1 example2 )。我创建了一个带有类的新项目来测试它。

  class Swizzle:NSObject 
{
func method()
{
print(A);
}
}

扩展名Swizzle
{
覆盖类func initialize()
{
struct Static
{
static var token:dispatch_once_t = 0;
}

//确保这不是子类
if(self!== Swizzle.self)
{
return;
}

dispatch_once(& Static.token)
{
let originalSelector = Selector(method);
let swizzledSelector = Selector(methodExt);

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

print(method_getImplementation(originalMethod));
print(method_getImplementation(swizzledMethod));

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

print(method_getImplementation(originalMethod));
print(method_getImplementation(swizzledMethod));
}
}

func methodExt()
{
print(B);
}
}

然后我尝试用

执行它

  var s = Swizzle(); 
s.method();

预期输出为B,但仍然打印A。从我的代码中可以看出,我在swizzle操作之前和之后都包含了每个 IMP 的打印件。这些打印显示交易确实发生,但输出保持不变。



输出:

  0x000000010251a920 
0x000000010251ad40
0x000000010251ad40
0x000000010251a920
A

在让这些更改生效时,我有什么遗漏吗?



PS。目前正在使用XCode 7.0.1

解决方案

问题是您的方法()缺少动态指令:

  class Swizzle:NSObject 
{
dynamic func method()
{
print(A)
}
}

修改声明,它应该有效。



在Swift中使用方法调配时,有两个要求:你的类/方法必须符合:




  • 你的类必须扩展 NSObject

  • 您想要调配的函数必须具有动态属性



如需完整解释原因,请查看使用Swift机智h Cocoa和Objective-C


需要动态调度



虽然 @objc 属性将Swift API暴露给Objective-C
运行时,但它不保证动态调度属性,方法,
下标或初始化器。 Swift编译器仍然可以虚拟化
或内联成员访问来优化代码的性能,
绕过Objective-C运行时
。当您使用 dynamic 修饰符标记成员声明
时,对该成员的访问始终是动态
调度。因为用 dynamic 修饰符标记的声明是使用Objective-C运行时调度的
,所以使用隐式标记
@objc
属性。



很少需要动态调度。 但是,当您知道在运行时替换API
的实现
时,必须使用
动态修饰符。例如,您可以在Objective-C运行时使用
method_exchangeImplementations 函数,以便
在应用程序运行时交换方法的实现。如果
Swift编译器内联方法的实现或
对其进行虚拟化访问,将不使用新实现




Swift 3更新:



关于GCD和 dispatch_once 不再可用。要执行相同的一次操作,我们可以将代码括在全局静态类常量的初始化块中。



Swift语言保证只执行此代码一次在应用程序的生命周期内。

  class TestSwizzling:NSObject {
dynamic func methodOne() - > Int {
返回1
}
}

扩展名TestSwizzling {

//在Objective-C中,你会在负载中执行调配(),
//但在Swift中不允许使用此方法
override class func initialize()
{

struct Inner {
static let i :()= {

let originalSelector = #selector(TestSwizzling.methodOne)
let swizzledSelector = #selector(TestSwizzling.methodTwo)
let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector);
let swizzledMethod = class_getInstanceMethod(TestSwizzling.self,swizzledSelector)
method_exchangeImplementations(originalMethod,swizzledMethod)
}
}
let _ = Inner.i
}

func methodTwo() - > Int {
//在调整后的
返回methodTwo()+ 1
} $之后不再是递归调用b $ b}

var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())

Swift 2.2更新:



我是更新了新的 #selector 属性的原始示例:

  class TestSwizzling :NSObject {
dynamic func methodOne() - > Int {
return 1
}
}

扩展名TestSwizzling {

//在Objective-C中你要在load(),
//中执行调配,但不允许使用此方法in Swift
override class func initialize()
{
struct Static
{
static var token:dispatch_once_t = 0
}

//仅执行一次
dispatch_once(& Static.token)
{
let originalSelector = #selector(TestSwizzling.methodOne)
let swizzledSelector = #selector( TestSwizzling.methodTwo)
let originalMethod = class_getInstanceMethod(self,originalSelector);
let swizzledMethod = class_getInstanceMethod(self,swizzledSelector)
method_exchangeImplementations(originalMethod,swizzledMethod)
}
}

func methodTwo() - > Int {
//在混合
返回methodTwo()+ 1
}
}

var c = TestSwizzling之后不再是递归调用)
print(c.methodOne())
print(c.methodTwo())

如果您需要一个示例,请查看此示例项目在github上。 / p>

I would like to make use of method swizzling, but am unable to get even simple examples to work for me. It is possible that I am misunderstanding what the concept is, but as far as I know it allows for method implementations to be swapped.

Given two methods, A and B, I would like to swap their implementations such that calling A would execute B instead. I came across a few examples of swizzling (example1 and example2). I created a new project with a class to test this.

class Swizzle: NSObject
{
    func method()
    {
        print("A");
    }
}

extension Swizzle
{
    override class func initialize()
    {
        struct Static
        {
            static var token: dispatch_once_t = 0;
        }

        // make sure this isn't a subclass
        if (self !== Swizzle.self)
        {
            return;
        }

        dispatch_once(&Static.token)
        {
            let originalSelector = Selector("method");
            let swizzledSelector = Selector("methodExt");

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

            print(method_getImplementation(originalMethod));
            print(method_getImplementation(swizzledMethod));

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

            print(method_getImplementation(originalMethod));
            print(method_getImplementation(swizzledMethod));
        }
    }

    func methodExt()
    {
        print("B");
    }
}

I then try to execute it with

var s = Swizzle();
s.method();

The expected output is "B", but "A" is still being printed. As you can see from my code, I've included prints of each IMP before and after the swizzle operation. These prints show that the exchange does take place, yet the output remains the same.

Output:

0x000000010251a920
0x000000010251ad40
0x000000010251ad40
0x000000010251a920
A

Is there anything I am missing when it comes to getting these changes to take effect?

PS. Currently using XCode 7.0.1

解决方案

The issue is that your method() lacks the dynamic directive:

class Swizzle: NSObject
{
    dynamic func method()
    {
        print("A")
    }
}

Modify the declaration and it should work.

When using method swizzling in Swift there are two requirements that your classes/methods must comply with:

  • Your class must extend NSObject
  • The functions you want to swizzle must have the dynamic attribute

For a complete explanation of why this is required, check out Using Swift with Cocoa and Objective-C:

Requiring Dynamic Dispatch

While the @objc attribute exposes your Swift API to the Objective-C runtime, it does not guarantee dynamic dispatch of a property, method, subscript, or initializer. The Swift compiler may still devirtualize or inline member access to optimize the performance of your code, bypassing the Objective-C runtime. When you mark a member declaration with the dynamic modifier, access to that member is always dynamically dispatched. Because declarations marked with the dynamic modifier are dispatched using the Objective-C runtime, they’re implicitly marked with the @objc attribute.

Requiring dynamic dispatch is rarely necessary. However, you must use the dynamic modifier when you know that the implementation of an API is replaced at runtime. For example, you can use the method_exchangeImplementations function in the Objective-C runtime to swap out the implementation of a method while an app is running. If the Swift compiler inlined the implementation of the method or devirtualized access to it, the new implementation would not be used.

Swift 3 Update:

There have been a few changes in regard to GCD and dispatch_once is not available anymore. To perform the same one time operation, we can enclose the code in the initialization block of a global static class constant.

The Swift language guarantees that this code will be executed only once during the lifetime of the application.

class TestSwizzling : NSObject {
    dynamic func methodOne()->Int{
        return 1
    }
}

extension TestSwizzling {

    //In Objective-C you'd perform the swizzling in load(), 
    //but this method is not permitted in Swift
    override class func initialize()
    {

        struct Inner {
            static let i: () = {

                let originalSelector = #selector(TestSwizzling.methodOne)
                let swizzledSelector = #selector(TestSwizzling.methodTwo)                 
                let originalMethod = class_getInstanceMethod(TestSwizzling.self, originalSelector);
                let swizzledMethod = class_getInstanceMethod(TestSwizzling.self, swizzledSelector)                
                method_exchangeImplementations(originalMethod, swizzledMethod)
            }
        }
        let _ = Inner.i
    }

    func methodTwo()->Int{
        // It will not be a recursive call anymore after the swizzling
        return methodTwo()+1
    }
}

var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())

Swift 2.2 Update:

I've updated the original example for the new #selector attribute:

class TestSwizzling : NSObject {
    dynamic func methodOne()->Int{
        return 1
    }
}

extension TestSwizzling {

    //In Objective-C you'd perform the swizzling in load(), 
    //but this method is not permitted in Swift
    override class func initialize()
    {
        struct Static
        {
            static var token: dispatch_once_t = 0
        }

        // Perform this one time only
        dispatch_once(&Static.token)
        {
                let originalSelector = #selector(TestSwizzling.methodOne)
                let swizzledSelector = #selector(TestSwizzling.methodTwo)                 
                let originalMethod = class_getInstanceMethod(self, originalSelector);
                let swizzledMethod = class_getInstanceMethod(self, swizzledSelector)                
                method_exchangeImplementations(originalMethod, swizzledMethod)
        }
    }

    func methodTwo()->Int{
        // It will not be a recursive call anymore after the swizzling
        return methodTwo()+1
    }
}

var c = TestSwizzling()
print(c.methodOne())
print(c.methodTwo())

If you need an example to play with, check out this sample project on github.

这篇关于方法Swizzling不起作用的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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