Swift函数混淆/运行时

20
在Swift之前,我会使用<objc/runtime.h>在Objective-C类中进行拦截或挂钩方法。 如果有人对修改Swift运行时和挂接函数的主题拥有像CydiaSubstrate和其他帮助过这个领域的库方面的信息,请告诉我。
8个回答

40

我已经成功地使用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


21
要明确的是,这个例子是在一个由Objective-C定义的类上进行方法交换,而不是Swift。我猜测,既然提问者接受了答案,那么这就是他们想要的,但是需要说明的是,这种方法无法在纯Swift类上进行方法交换。 - ipmcc
1
只有当Swift类是NSObject的子类时,它才能正常工作。 - mbazaliy
1
有人知道Swift的运行时是如何工作的吗?我的意思是大家都知道Obj-C是如何工作的。 isa指针、cmd、我们如何调用方法、动态绑定等等。有关Obj-C运行时、开源运行时库的文档很多。但Swift呢?任何链接都会有所帮助。 - BergP
3
在我的测试中,您的myDescription()会递归调用自身并导致栈溢出错误。 - Pang
2
这里有一个很好的Swift Swizzling示例,位于“Associated Objects”之后:http://nshipster.com/swift-objc-runtime/ - TheDarkKnight
显示剩余6条评论

21

由于Swift生成的类似乎始终使用动态方法调度,因此您很可能能够轻松地对继承自Objective-C类的Swift生成的类进行操作。如果Swift定义的类存在于Objective-C运行时中并通过桥接被传递,则您可能能够交换这些类的方法。但是Objective-C侧的方法很可能只是返回到Swift侧运行时的代理,因此交换它们可能不会特别有用。

“纯”Swift方法调用似乎没有通过任何类似objc_msgSend的方式进行动态分派,并且(经过简短的实验)似乎Swift的类型安全是在编译时实现的,并且许多实际类型信息在非类类型的运行时中缺失(这两个原因很可能有助于Swift声称的速度优势)。

出于这些原因,我预计有意义地交换仅限于Swift的方法将比交换Objective-C方法要困难得多,并且可能看起来更像mach_override而不是Objective-C方法交换。


指针样式的交换是怎么样的呢?https://dev59.com/rGAg5IYBdhLWcg3wBXER - Jasper Blues
1
isa交换也是Objective-C的一种技术。我没有尝试过,但根据我所见,它似乎不太可能起作用。至少不能直接使用。 - ipmcc
它在这里似乎工作正常:http://bit.ly/TgLN2M……Swift似乎优先考虑静态分派,必要时回退到vtable或最终的ObjC样式分派。如果我们可以在运行时添加方法,那么它可能会有一个ObjC样式的分派表。 - Jasper Blues
3
我非常怀疑使用现有的Objective-C swizzling方法可以实现静态和虚函数调度方法的混淆,这也是我试图表达的观点,尽管看起来这不是原帖作者所问的问题,因为他们接受了一个不同的答案。 - ipmcc
1
很快就完成了:https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift - ipmcc
哇,没想到这种方法是可行的。 - Jasper Blues

5
我一年后回答这个问题,因为其他答案没有提供适用于各种类的方法交换的明确要求。
其他人所描述的虽然对于扩展到 foundation/uikit 类(如 NSDictionary)完美运作,但对于您自己的 Swift 类则永远不会起作用。
此处所述,方法交换除了在自定义类中扩展 NSObject 之外,还有一个额外的要求。
您想交换的 swift 方法必须标记为 dynamic。
如果您不标记它,即使方法指针似乎已被正确交换,运行时仍将继续调用原始方法而不是交换后的方法。
更新:
我在博客文章中扩展了这个答案。

2
我有一个使用Cocoapods编写的Swift 2 iOS项目,使用Xcode 7。在特定的Cocoapod中,使用Objective-C源代码,我想覆盖一个简短的方法,而不需要分叉该pod。在我的情况下,编写Swift扩展无法解决问题。
为了使用方法交换,我在我的主捆绑包中创建了一个新的Objective-C类,其中包含我想要替换/注入到cocoapod中的方法。(还添加了桥接头文件)
使用mbazaliy在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的bundle中引用的资源复制到主bundle中。


0
我想扩展mbazaliy提供的绝妙答案。
另一种在Swift中进行交换的方法是通过提供使用Objective-C块的实现。
例如,要替换类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版本中可行。


这不是交换方法。交换方法涉及交换方法实现,而不是替换它们。 - Vatsal Manot
@VatsalManot 你能详细说明一下吗? - Ali
Swizzling是交换两个实现,而不是替换一个实现。 - Vatsal Manot

0

0

一款安全、易用、强大且高效的 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() {
    }
}

#f03c15 方法中的关键词@objcdynamic是必需的

#f03c15 该类不必继承自NSObject。如果该类由Objective-C编写,则只需轻松挂钩即可

  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

执行指定实例方法后,执行钩子闭包。并获取参数。
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. 完全覆盖指定实例的方法。您可以使用相同的参数或不同的参数调用原始方法。如果需要,甚至不要调用原始方法。
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

在执行类方法之前进行钩子闭包操作。
let token = try? hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.classMethodNoArgsNoReturnFunc)) {
    // run your code
    print("hooked!")
}
MyObject.classMethodNoArgsNoReturnFunc()
token?.cancelHook() // cancel the hook

-3

在花费了一些时间之后...今天早上醒来...beta 6发布了,问题在beta6中得到解决!从发布说明中可以看到:"动态调度现在可以调用类扩展中引入的方法和属性的重写,修复了Xcode 6 beta 5中引入的一个回归错误。(17985819)!"


2
最好将其写为注释。 - عثمان غني

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接