20

Before Swift, in Objective-C I would swizzle or hook methods in a class using <objc/runtime.h>.

If anyone has any info on the topic of modifying Swift's runtime and hooking functions like CydiaSubstrate and other libraries that helped in this area, please inform me.

AstroCB
  • 12,337
  • 20
  • 57
  • 73
atomikpanda
  • 1,845
  • 5
  • 33
  • 47

8 Answers8

40

I've succeed with method swizzling in Swift. This example shows how to hook description method on NSDictionary

My implementation:

extension NSDictionary {
     func myDescription() -> String!{
        println("Description hooked")
        return "Hooooked " + myDescription();
    }
}

Swizzling code:

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
    }

Edited: This code will work for any custom Swift class that inherits from NSObject (but will not work for classes that don't.) More examples - https://github.com/mbazaliy/MBSwizzler

mbazaliy
  • 416
  • 4
  • 4
  • 21
    To be clear, this example swizzles a method on a class that is defined in Objective-C, not swift. I guess, since the asker accepted the answer, that this is what they wanted, but for the record, this method will not swizzle a method on a pure swift class. – ipmcc Jun 04 '14 at 23:36
  • 1
    It works only if Swift class is subclass of NSObject – mbazaliy Jun 05 '14 at 10:16
  • 1
    Does anybody know how swift's runtime works? I mean everybody know how obj-c works. isa pointer, cmd, how do we call methods, dynamic binding and other. There are many documents about obj-c runtime, open source runtime library. But what about swift? Any links would be helpful. – BergP Jun 05 '14 at 19:19
  • AFAIK Swift runs on Obj-c runtime (that's why you need to subclass NSObject), but has and can run on his own runtime. – Macsen Liviu Jun 16 '14 at 22:43
  • 3
    @mbazaliy, in my testing, your `myDescription()` recursively calls itself and causes a stack overflow error. – Pang Aug 06 '14 at 09:15
  • I am not sure that you need to explicitly subclass from NSObject to get this behavior if you use the Swift @objc directive within your class definition for each method that you wish to be swizzlable. – ctpenrose Aug 13 '14 at 03:14
  • No, I could not swizzle a Swift class which used @objc directives from within Swift. But it is possible that it would be swizzlable from Objective-C. – ctpenrose Aug 13 '14 at 03:25
  • ``Selector``implements ``StringLiteralConvertible``protocol (tested on Swift 1.1). ``class_getInstanceMethod(object_getClass(dict),"description")`` will work. – Matteo Pacini Jan 10 '15 at 04:28
  • 2
    There's a good example of Swizzling with Swift [here](http://nshipster.com/swift-objc-runtime/), just after "Associated Objects" – TheDarkKnight Jun 09 '15 at 12:58
  • I agree @TheDarkKnight. That article is good. It covers instance methods well. For class methods, I explored and shared my findings at [Swizzle Swift Class Methods](http://www.finneycanhelp.com/2016/05/swizzle-swift-class-methods/) – finneycanhelp May 23 '16 at 10:14
  • For anyone looking at this, the mentioned article is outdated. In modern versions of swift you cannot override `initialize` method. – Nat Jun 05 '20 at 17:58
21

You would likely be able to swizzle swift-generated classes that inherit from Objective-C classes with no problem, since they appear to use dynamic method dispatch all the time. You may be able to swizzle methods of swift-defined classes that exist in the Objective-C runtime by virtue of being passed across the bridge, but the Objective-C side methods are likely to just be proxies back across the bridge to the swift-side runtime, so it's not clear that it'd be particularly helpful to swizzle them.

"Pure" swift method calls do not appear to be dispatched dynamically via anything like objc_msgSend and it appears (from brief experimentation) that the type safety of swift is implemented at compile time, and that much of the actual type information is absent (i.e. gone) at runtime for non-class types (both of which likely contribute to the purported speed advantages of swift.)

For these reasons, I expect that meaningfully swizzling swift-only methods will be significantly harder than swizzling Objective-C methods, and will probably look a lot more like mach_override than Objective-C method swizzling.

ipmcc
  • 29,581
  • 5
  • 84
  • 147
  • What about isa pointer style swizzling? http://stackoverflow.com/q/24050500/404201 – Jasper Blues Jun 05 '14 at 02:17
  • 1
    isa swizzling is also an Objective-C technique. I haven't tried it but it doesn't seem likely to work to me, based on what I've seen. At least not out of the box. – ipmcc Jun 05 '14 at 09:27
  • It seems to work OK here: http://bit.ly/TgLN2M . . . Swift seems to prioritize static dispatch, falling back to vtable or finally ObjC style dispatch where needed. If we can add methods at runtime, it would presumably have an ObjC style dispatch table. – Jasper Blues Jun 05 '14 at 09:53
  • 3
    I highly doubt that static- and vtable-dispatched methods are ever going to be swizzle-able using existing Objective-C swizzling methods, which is kinda the point I was trying to make, although it seems like that was not what the OP was asking, given that they accepted a different answer. – ipmcc Jun 05 '14 at 11:11
  • 1
    That didn't take long: https://github.com/rodionovd/SWRoute/wiki/Function-hooking-in-Swift – ipmcc Jun 20 '14 at 14:26
  • Wow, didn't realize this approach was possible. – Jasper Blues Jun 20 '14 at 14:54
5

I'm answering this question more than one year later because none of the other answers provide the definitive set of requirements for method swizzling for every kind of class.

What is described by other, while it will work flawlessly for extensions to foundation/uikit classes (like NSDictionary), will simply never work for your own Swift classes.

As described here, there is an additional requirement for method swizzling other than extending NSObject in your custom class.

The swift method you want to swizzle must be marked dynamic.

If you don't mark it, the runtime will simply continue to call the original method instead of the swizzled one, even if the method pointers appear to have been swapped correctly.

Update:

I've expanded this answer in a blog post.

Community
  • 1
  • 1
uraimo
  • 19,081
  • 8
  • 48
  • 55
2

I had a Xcode 7 iOS project written in Swift 2, using Cocoapods. In a specific Cocoapod, with Objective-C source, I wanted to override a short method, without forking the pod. Writing a Swift extension wouldn't work in my case.

For using method swizzling, I created a new Objective-C class in my main bundle with the method I wanted to replace/inject into the cocoapod. (Also added the bridging header)

Using mbazaliy 's solution on stackflow, I put my code similar to this into the didFinishLaunchingWithOptions in my 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)

This worked perfectly. The difference between @mbazaliy 's code is that I didn't need to create an instance of the SomeClassInAPod class first, which in my case would have been impossible.

Note: I put the code in the Appdelegate because every other time the code runs, it exchanges the method for the original - it should only run one time.

I also needed to copy some assets that were referenced in the Pod's bundle to the main bundle.

Community
  • 1
  • 1
Nate Flink
  • 3,934
  • 2
  • 30
  • 18
0

I wouldn't do it that way, I think closures provide the answers (as they give you a chance to intercept, evaluate, and forward the invocation of the function, additionally it will be easy to extend when and if we have reflection.

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

rougeExciter
  • 7,435
  • 2
  • 21
  • 17
0

I would like to extend the great answer provided by mbazaliy.

Another way of doing swizzling in Swift is by providing an implementation using an Objective-C block.

e.g. to replace descriptionmethod on class NSString we can write:

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)

This works as of Swift 1.1.

Matteo Pacini
  • 21,796
  • 7
  • 67
  • 74
0

A safe, easy, powerful and efficient hook framework for iOS (Support Swift and Objective-C). https://github.com/623637646/SwiftHook

For example, this is your class

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

#f03c15 The key words of methods @objc and dynamic are necessary

#f03c15 The class doesn't have to inherit from NSObject. If the class is written by Objective-C, Just hook it without any more effort

  1. Perform the hook closure before executing specified instance's method.
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. Perform the hook closure after executing specified instance's method. And get the parameters.
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 The key word @convention(block) is necessary

#f03c15 For hook at before and after. The closure's args have to be empty or the same as method. The return type has to be void

  1. Totally override the mehtod for specified instance. You can call original with the same parameters or different parameters. Don't even call the original method if you want.
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 For hook with instead. The closure's first argument has to be a closure which has the same types with the method. The rest args and return type have to be the same as the method.

  1. Perform the hook closure before executing the method of all instances of the class.
let token = try? hookBefore(targetClass: MyObject.self, selector: #selector(MyObject.noArgsNoReturnFunc)) {
    // run your code
    print("hooked!")
}
MyObject().noArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
  1. Perform the hook closure before executing the class method.
let token = try? hookClassMethodBefore(targetClass: MyObject.self, selector: #selector(MyObject.classMethodNoArgsNoReturnFunc)) {
    // run your code
    print("hooked!")
}
MyObject.classMethodNoArgsNoReturnFunc()
token?.cancelHook() // cancel the hook
Yanni
  • 580
  • 2
  • 6
  • 21
-3

After spending some time on it... Wake up this morning.... beta 6 is out and Problem Fixed in beta6! From release notes "Dynamic dispatch can now call overrides of methods and properties introduced in class extensions, fixing a regression introduced in Xcode 6 beta 5. (17985819)!"