方法Swizzling不起作用

时间:2015-10-13 07:42:11

标签: ios xcode swift swift2 method-swizzling

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

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

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"还在印刷。从我的代码中可以看出,我在swizzle操作之前和之后都包含了每个IMP的打印件。这些打印显示交换确实发生,但输出保持不变。

输出:

0x000000010251a920
0x000000010251ad40
0x000000010251ad40
0x000000010251a920
A

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

PS。目前正在使用XCode 7.0.1

1 个答案:

答案 0 :(得分:12)

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

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

修改声明,它应该有效。

在Swift中使用方法调配时,您的类/方法必须遵守两个要求:

  • 您的课程必须延长NSObject
  • 您想要调配的功能必须具有dynamic属性

要完整解释为何需要这样做,请查看Using Swift with Cocoa and Objective-C

  

需要动态调度

     

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

     

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

Swift 3更新:

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

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