在 Swift 之前,在 Objective-C 中,我会使用<objc/runtime.h>
.
如果有人对修改 Swift 的运行时和挂钩函数(如 CydiaSubstrate 和其他在该领域有帮助的库)有任何信息,请通知我。
我在 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
您可能能够毫无问题地调配从 Objective-C 类继承的 swift 生成的类,因为它们似乎一直在使用动态方法分派。通过跨桥传递,您可能能够调配存在于 Objective-C 运行时中的 swift 定义类的方法,但 Objective-C 端方法可能只是代理返回跨桥到 swift-侧运行时,因此不清楚调配它们是否特别有帮助。
“纯” swift 方法调用似乎没有通过类似的方式动态调度objc_msgSend
,并且(从简短的实验中)似乎(从简短的实验)swift 的类型安全是在编译时实现的,并且大部分实际类型信息都不存在(即消失)在运行时用于非类类型(这两者都可能有助于 swift 的所谓速度优势。)
由于这些原因,我预计有意义地混合 swift-only 方法将比混合 Objective-C 方法困难得多,并且可能看起来更像是mach_override
比 Objective-C 方法混合。
我有一个使用 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 包中引用的一些资产复制到主包中。
我想扩展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。
我不会那样做,我认为闭包提供了答案(因为它们让你有机会拦截、评估和转发函数的调用,此外,如果我们有反射,也很容易扩展。
http://www.swift-studies.com/blog/2014/7/13/method-swizzling-in-swift
一个安全、简单、强大和高效的 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() {
}
}
方法的关键词@objc
和dynamic
必要的
该类不必继承自 NSObject。如果类是Objective-C写的,直接hook就行了
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
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
关键词@convention(block)
是必须的
用于勾before
和after
。闭包的参数必须为空或与方法相同。返回类型必须是void
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
用instead
. 闭包的第一个参数必须是与方法具有相同类型的闭包。其余参数和返回类型必须与方法相同。
let token = try? hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.noArgsNoReturnFunc)) {
// run your code
print("hooked!")
}
MyObject().noArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
let token = try? hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.classMethodNoArgsNoReturnFunc)) {
// run your code
print("hooked!")
}
MyObject.classMethodNoArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
在花了一些时间之后......今天早上醒来...... beta 6 已经发布,问题在 beta6 中得到修复!来自发行说明“动态调度现在可以调用类扩展中引入的方法和属性的覆盖,修复 Xcode 6 beta 5 中引入的回归。(17985819)!”