简体   繁体   English

方法Swizzling不起作用

[英]Method Swizzling does not work

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. 给定两个方法A和B,我想交换它们的实现,这样调用A就会执行B。 I came across a few examples of swizzling ( example1 and example2 ). 我遇到了一些混合的例子( example1example2 )。 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. 预期输出为“B”,但仍然打印“A”。 As you can see from my code, I've included prints of each IMP before and after the swizzle operation. 从我的代码中可以看出,我在swizzle操作之前和之后都包含了每个IMP打印件。 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. PS。 Currently using XCode 7.0.1 目前正在使用XCode 7.0.1

The issue is that your method() lacks the dynamic directive: 问题是你的method()缺少dynamic指令:

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: 在Swift中使用方法调配时,您的类/方法必须符合以下两个要求:

  • Your class must extend NSObject 你的类必须扩展NSObject
  • The functions you want to swizzle must have the dynamic attribute 您想要调配的函数必须具有dynamic属性

For a complete explanation of why this is required, check out Using Swift with Cocoa and Objective-C : 有关为什么需要这样做的完整说明,请查看使用Swift与Cocoa和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. 虽然@objc属性将Swift API暴露给Objective-C运行时,但它不保证动态调度属性,方法,下标或初始化程序。 The Swift compiler may still devirtualize or inline member access to optimize the performance of your code, bypassing the Objective-C runtime . Swift编译器仍然可以通过半虚拟化或内联成员访问来优化代码的性能,从而绕过Objective-C运行时 When you mark a member declaration with the dynamic modifier, access to that member is always dynamically dispatched. 使用dynamic修饰符标记成员声明时,始终会动态调度对该成员的访问。 Because declarations marked with the dynamic modifier are dispatched using the Objective-C runtime, they're implicitly marked with the @objc attribute. 因为使用Objective-C运行时调度使用dynamic修饰符标记的声明,所以使用@objc属性隐式标记它们。

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 . 但是,当您知道在运行时替换API的实现时,必须使用dynamic修饰符 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. 例如,您可以使用Objective-C运行时中的method_exchangeImplementations函数在应用程序运行时交换方法的实现。 If the Swift compiler inlined the implementation of the method or devirtualized access to it, the new implementation would not be used . 如果Swift编译器内联该方法的实现或对其进行虚拟化访问,则不会使用新的实现

Swift 3 Update: Swift 3更新:

There have been a few changes in regard to GCD and dispatch_once is not available anymore. 关于GCD已经有一些变化,并且dispatch_once不再可用。 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. Swift语言保证此代码在应用程序的生命周期内仅执行一次。

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: Swift 2.2更新:

I've updated the original example for the new #selector attribute: 我已经为新的#selector属性更新了原始示例:

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 . 如果您需要一个示例,请在github上查看此示例项目。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM