Swift函数调配/运行时

时间:2014-06-03 15:57:56

标签: runtime swift swizzling

在Swift之前,在Objective-C中,我会使用<objc/runtime.h>在类中调用或挂钩方法。

如果有人有任何关于修改Swift的运行时和挂钩函数的信息,比如CydiaSubstrate和其他帮助这个领域的库,请通知我。

8 个答案:

答案 0 :(得分:36)

我在Swift中使用方法调配成功了。此示例显示如何在NSDictionary上挂钩描述方法

我的实施:

extension NSDictionary {
     func myDescription() -> String!{
        println("Description hooked")
        return "Hooooked " + myDescription();
    }
}

混合代码:

func swizzleEmAll() {
        var dict:NSDictionary = ["SuperSecret": kSecValueRef]
        var method: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("description"))

        println(dict.description) // Check original description

        var swizzledMethod: Method = class_getInstanceMethod(object_getClass(dict), Selector.convertFromStringLiteral("myDescription"))
        method_exchangeImplementations(method, swizzledMethod)

        println(dict.description) //Check that swizzling works
    }

<强>编辑: 此代码适用于任何继承自 NSObject 的自定义Swift类(但不适用于不使用的类。)更多示例 - https://github.com/mbazaliy/MBSwizzler

答案 1 :(得分:19)

你很可能会在没有任何问题的情况下调用从Objective-C类继承的swift生成的类,因为它们似乎一直使用动态方法调度。您可以通过跨桥传递在Objective-C运行时中存在的快速定义的类的方法,但Objective-C方法可能只是通过桥接到swift的代理 - 边运行时,所以不清楚它们是否特别有助于调整它们。

“Pure”swift方法调用似乎不是通过像objc_msgSend之类的动态调度的,并且(在简短的实验中)看起来swift的类型安全性是在编译时实现的,并且实际上是实际的类型信息在运行时不存在(即消失)非类型类型(这两种类型都可能有助于swift的速度优势。)

由于这些原因,我认为有意义的混合swift-only方法将比调整Objective-C方法更难,并且可能看起来更像mach_override而不是Objective-C方法调整。

答案 2 :(得分:5)

一年多以后,我正在回答这个问题,因为没有其他答案能为各类课程提供明确的方法调整要求。

其他人描述的内容,虽然它可以完美地用于基础/ uikit类的扩展(如NSDictionary),但永远不会为你自己的Swift类工作。

正如here所描述的,除了在自定义类中扩展NSObject之外,还需要进行方法调整。

您想要调动的快速方法必须标记为 dynamic

如果你没有标记它,运行时只会继续调用原始方法而不是调整方法,即使方法指针看起来已被正确交换。

更新

我已经扩展了这个答案in a blog post

答案 3 :(得分:2)

我使用Cocoapods在Swift 2中编写了一个Xcode 7 iOS项目。在具有Objective-C源的特定Cocoapod中,我想要覆盖一个简短的方法,而不需要分析pod。写一个Swift扩展在我的案例中不会起作用。

对于使用方法调配,我在我的主包中创建了一个新的Objective-C类,其中包含我想要替换/注入cocoapod的方法。 (还添加了桥接标题)

使用mbazaliy&#39; solution on stackflow,我将我的代码与我的Appdelegate中的didFinishLaunchingWithOptions类似:

    let mySelector: Selector = "nameOfMethodToReplace"
    let method: Method = class_getInstanceMethod(SomeClassInAPod.self, mySelector)
    let swizzledMethod: Method = class_getInstanceMethod(SomeOtherClass.self, mySelector)
    method_exchangeImplementations(method, swizzledMethod)

这完美无缺。 @mbazaliy代码之间的区别在于我不需要首先创建SomeClassInAPod类的实例,在我看来这是不可能的。

注意:我将代码放在Appdelegate中,因为代码每次运行时都会交换原始方法 - 它应该只运行一次。

我还需要将Pod的软件包中引用的一些资产复制到主软件包中。

答案 4 :(得分:0)

我不会这样做,我认为闭包提供了答案(因为它们给你一个机会来拦截,评估和转发函数的调用,另外它很容易扩展,当时和如果我们有反思。

http://www.swift-studies.com/blog/2014/7/13/method-swizzling-in-swift

答案 5 :(得分:0)

我想扩展 mbazaliy 提供的好答案。

在Swift中进行调配的另一种方法是使用Objective-C块提供实现。

e.g。要替换类description上的NSString方法,我们可以写:

let originalMethod = class_getInstanceMethod(NSString.self, "description")

let impBlock : @objc_block () -> NSString =
        { () in return "Bit of a hack job!" }

let newMethodImp = imp_implementationWithBlock(unsafeBitCast(impBlock, AnyObject.self))

method_setImplementation(originalMethod, newMethodImp)

这适用于Swift 1.1。

答案 6 :(得分:0)

适用于iOS的安全,便捷,强大和高效的挂钩框架(支持Swift和Objective-C)。 https://github.com/623637646/SwiftHook

例如,这是您的课程

rails-observable

#f03c15 方法class MyObject { @objc dynamic func noArgsNoReturnFunc() { } @objc dynamic func sumFunc(a: Int, b: Int) -> Int { return a + b } @objc dynamic class func classMethodNoArgsNoReturnFunc() { } } @objc的关键字是必需的

#f03c15 该类不必从NSObject继承。如果该类是由Objective-C编写的,则无需费劲就可以将其钩住

  1. 在执行指定实例的方法之前执行钩子闭合。
dynamic
  1. 在执行指定实例的方法之后执行挂钩关闭。并获取参数。
let object = MyObject()
let token = try? hookBefore(object: object, selector: #selector(MyObject.noArgsNoReturnFunc)) {
    // run your code
    print("hooked!")
}
object.noArgsNoReturnFunc()
token?.cancelHook() // cancel the hook

#f03c15 关键字let object = MyObject() let token = try? hookAfter(object: object, selector: #selector(MyObject.sumFunc(a:b:)), closure: { a, b in // get the arguments of the function print("arg1 is \(a)") // arg1 is 3 print("arg2 is \(b)") // arg2 is 4 } as @convention(block) (Int, Int) -> Void) _ = object.sumFunc(a: 3, b: 4) token?.cancelHook() // cancel the hook 是必需的

#f03c15 用于钩住@convention(block)before。闭包的args必须为空或与method相同。返回类型必须为after

  1. 完全覆盖指定实例的方法。您可以使用相同的参数或不同的参数来调用原始文件。如果需要,甚至不要调用原始方法。
void

#f03c15 用于与let object = MyObject() let token = try? hookInstead(object: object, selector: #selector(MyObject.sumFunc(a:b:)), closure: { original, a, b in // get the arguments of the function print("arg1 is \(a)") // arg1 is 3 print("arg2 is \(b)") // arg2 is 4 // run original function let result = original(a, b) // Or change the parameters: let result = original(-1, -2) print("original result is \(result)") // result = 7 return 9 } as @convention(block) ((Int, Int) -> Int, Int, Int) -> Int) let result = object.sumFunc(a: 3, b: 4) // result print("hooked result is \(result)") // result = 9 token?.cancelHook() // cancel the hook 挂钩。闭包的第一个参数必须是与该方法具有相同类型的闭包。其余参数和返回类型必须与方法相同。

  1. 在执行类的所有实例的方法之前执行钩子闭合。
instead
  1. 在执行类方法之前执行钩子闭合。
let token = try? hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.noArgsNoReturnFunc)) {
    // run your code
    print("hooked!")
}
MyObject().noArgsNoReturnFunc()
token?.cancelHook() // cancel the hook

答案 7 :(得分:-3)

花了一些时间在它上面......今天早上醒来...... beta 6出来了 问题已在beta6中修复! 从发行说明 “动态调度现在可以调用类扩展中引入的方法和属性的覆盖,修复Xcode 6 beta 5中引入的回归。(17985819)!”