方法调配不起作用

Method Swizzling does not work

我想使用 method swizzling,但我无法找到适合我的简单示例。我可能误解了这个概念,但据我所知,它允许交换方法实现。

给定两个方法 A 和 B,我想交换它们的实现,以便调用 A 执行 B。我遇到了一些 swizzling 的例子 (example1 and example2)。我创建了一个带有 class 的新项目来测试它。

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

然后我尝试用

执行它
var s = Swizzle();
s.method();

预期输出为 "B",但仍在打印 "A"。正如您从我的代码中看到的那样,我在混合操作之前和之后包含了每个 IMP 的打印。这些打印显示确实发生了交换,但输出保持不变。

输出:

0x000000010251a920
0x000000010251ad40
0x000000010251ad40
0x000000010251a920
A

在使这些更改生效方面,我是否遗漏了什么?

PS。当前使用 XCode 7.0.1

问题是您的 method() 缺少 dynamic 指令:

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

修改声明,它应该可以工作。

在 Swift 中使用 method swizzling 时,您的 classes/methods 必须满足两个要求:

  • 您的 class 必须扩展 NSObject
  • 您要调配的函数必须具有 dynamic 属性

有关为什么需要这样做的完整解释,请查看 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 更新:

GCD 发生了一些变化,dispatch_once 不再可用。要执行相同的一次性操作,我们可以将代码包含在全局静态 class 常量的初始化块中。

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

我已经更新了新 #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())

如果你需要一个例子来玩,看看这个示例项目 on github