ios - 方法调配不起作用

标签 ios xcode swift swift2 method-swizzling

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

给定两个方法,A 和 B,我想交换它们的实现,这样调用 A 就会执行 B。我遇到了一些 swizzling 的例子( 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”仍在打印。正如您从我的代码中看到的那样,我在混合操作之前和之后包含了每个 IMP 的打印。这些打印显示确实发生了交换,但输出保持不变。

输出:

0x000000010251a920
0x000000010251ad40
0x000000010251ad40
0x000000010251a920
A

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

附言。目前使用 XCode 7.0.1

最佳答案

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

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

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

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

  • 你的类必须扩展 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 不再可用。要执行相同的一次性操作,我们可以将代码包含在全局静态类常量的初始化 block 中。

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 .

关于ios - 方法调配不起作用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/33096873/

相关文章:

iOS UITableViewCell 附件无法正确显示(披露指示器除外)

ios - 内部带有 UITextView 的自定义 UITableViewCell 不可使用 layoutsubviews 进行编辑

ios - 部署目标是什么意思?

swift - 如何在App Delegate中使 Controller 独立?

swift - 测试 Swift Optionals 的惯用方法

ios - CocoaPods:增加了AFNetworking的一半依赖

ios - 将对象数组转换为数组对象

ios - 数据插入上的核心数据级联

ios - 如何从 NSMutableArray (JSON) 获取数据

xcode - 我可以使用没有 max 的 NSStepper 吗?