<objc/runtime.h>
在Objective-C类中进行拦截或挂钩方法。
如果有人对修改Swift运行时和挂接函数的主题拥有像CydiaSubstrate和其他帮助过这个领域的库方面的信息,请告诉我。我已经成功地使用Swift中的方法交换技术。这个例子展示了如何钩取NSDictionary上的description方法。
我的实现:
extension NSDictionary {
func myDescription() -> String!{
println("Description hooked")
return "Hooooked " + myDescription();
}
}
Swizzling代码:
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
由于Swift生成的类似乎始终使用动态方法调度,因此您很可能能够轻松地对继承自Objective-C类的Swift生成的类进行操作。如果Swift定义的类存在于Objective-C运行时中并通过桥接被传递,则您可能能够交换这些类的方法。但是Objective-C侧的方法很可能只是返回到Swift侧运行时的代理,因此交换它们可能不会特别有用。
“纯”Swift方法调用似乎没有通过任何类似objc_msgSend
的方式进行动态分派,并且(经过简短的实验)似乎Swift的类型安全是在编译时实现的,并且许多实际类型信息在非类类型的运行时中缺失(这两个原因很可能有助于Swift声称的速度优势)。
出于这些原因,我预计有意义地交换仅限于Swift的方法将比交换Objective-C方法要困难得多,并且可能看起来更像mach_override
而不是Objective-C方法交换。
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的bundle中引用的资源复制到主bundle中。
NSString
上的description
方法,我们可以编写: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 Hook 框架(支持 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编写,则只需轻松挂钩即可
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)!"
myDescription()
会递归调用自身并导致栈溢出错误。 - Pang