20

在 Swift 之前,在 Objective-C 中,我会使用<objc/runtime.h>.

如果有人对修改 Swift 的运行时和挂钩函数(如 CydiaSubstrate 和其他在该领域有帮助的库)有任何信息,请通知我。

4

8 回答 8

39

我在 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

于 2014-06-04T22:31:50.127 回答
21

您可能能够毫无问题地调配从 Objective-C 类继承的 swift 生成的类,因为它们似乎一直在使用动态方法分派。通过跨桥传递,您可能能够调配存在于 Objective-C 运行时中的 swift 定义类的方法,但 Objective-C 端方法可能只是代理返回跨桥到 swift-侧运行时,因此不清楚调配它们是否特别有帮助。

“纯” swift 方法调用似乎没有通过类似的方式动态调度objc_msgSend,并且(从简短的实验中)似乎(从简短的实验)swift 的类型安全是在编译时实现的,并且大部分实际类型信息都不存在(即消失)在运行时用于非类类型(这两者都可能有助于 swift 的所谓速度优势。)

由于这些原因,我预计有意义地混合 swift-only 方法将比混合 Objective-C 方法困难得多,并且可能看起来更像是mach_override比 Objective-C 方法混合。

于 2014-06-03T17:04:02.340 回答
5

一年多后我才回答这个问题,因为其他答案都没有为每种类的方法调配提供明确的要求。

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

如此处所述,除了在自定义类中扩展 NSObject 之外,还需要方法调配。

必须标记 您要调配的 swift 方法dynamic

如果您不标记它,运行时将简单地继续调用原始方法而不是经过调配的方法,即使方法指针似乎已正确交换。

更新:

在博客文章中扩展了这个答案。

于 2015-10-13T09:06:40.740 回答
2

我有一个使用 Cocoapods 用 Swift 2 编写的 Xcode 7 iOS 项目。在具有 Objective-C 源代码的特定 Cocoapod 中,我想覆盖一个简短的方法,而无需分叉 pod。在我的情况下,编写一个 Swift 扩展是行不通的。

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

在 stackflow 上使用 mbazaliy 的解决方案,我将与此类似的代码放入didFinishLaunchingWithOptions我的 Appdelegate 中:

    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 包中引用的一些资产复制到主包中。

于 2016-02-24T22:48:43.817 回答
0

我想扩展mbazaliy提供的出色答案。

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

例如,要替换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。

于 2015-01-10T04:35:34.923 回答
0

我不会那样做,我认为闭包提供了答案(因为它们让你有机会拦截、评估和转发函数的调用,此外,如果我们有反射,也很容易扩展。

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

于 2014-07-14T13:15:18.593 回答
0

一个安全、简单、强大和高效的 iOS 钩子框架(支持 Swift 和 Objective-C)。 https://github.com/623637646/SwiftHook

例如,这是你的班级

class MyObject {
    @objc dynamic func noArgsNoReturnFunc() {
    }
    @objc dynamic func sumFunc(a: Int, b: Int) -> Int {
        return a + b
    }
    @objc dynamic class func classMethodNoArgsNoReturnFunc() {
    }
}

#f03c15 方法的关键词@objcdynamic必要的

#f03c15 该类不必继承自 NSObject。如果类是Objective-C写的,直接hook就行了

  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
  1. 执行指定实例的方法后执行钩子关闭。并获取参数。
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)是必须的

#f03c15 用于勾beforeafter。闭包的参数必须为空或与方法相同。返回类型必须是void

  1. 完全覆盖指定实例的方法。您可以使用相同的参数或不同的参数调用 original。如果您愿意,甚至不要调用原始方法。
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

#f03c15 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
  1. 在执行类方法之前执行钩子关闭。
let token = try? hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.classMethodNoArgsNoReturnFunc)) {
    // run your code
    print("hooked!")
}
MyObject.classMethodNoArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
于 2020-06-02T08:57:39.507 回答
-3

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

于 2014-08-19T07:54:27.910 回答