在Swift之前,在Objective-C中,我会使用<objc/runtime.h>
在类中调用或挂钩方法。
如果有人有任何关于修改Swift的运行时和挂钩函数的信息,比如CydiaSubstrate和其他帮助这个领域的库,请通知我。
答案 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
方法class MyObject {
@objc dynamic func noArgsNoReturnFunc() {
}
@objc dynamic func sumFunc(a: Int, b: Int) -> Int {
return a + b
}
@objc dynamic class func classMethodNoArgsNoReturnFunc() {
}
}
和@objc
的关键字是必需的
该类不必从NSObject继承。如果该类是由Objective-C编写的,则无需费劲就可以将其钩住
dynamic
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
。闭包的args必须为空或与method相同。返回类型必须为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
答案 7 :(得分:-3)
花了一些时间在它上面......今天早上醒来...... beta 6出来了 问题已在beta6中修复! 从发行说明 “动态调度现在可以调用类扩展中引入的方法和属性的覆盖,修复Xcode 6 beta 5中引入的回归。(17985819)!”