UPDATE: the example code on Github (at the end of the article) has been updated for Swift 5 syntax on may 26th, 2019.
Nothing changed except the mangled names of the Swift functions, now they have a $ dollar sign in front, and when calling dlsym() you must leave out the “_” at the beginning. Look the code for further details.
Objective-c method dispatching is dynamic. This means that, when you call for example an instance method, the objc runtime “searches” the address of the underlying function (*) in a dynamical way. It uses objc_msgSend() for the message dispatching.
I don’t want to go into details here, there is a lot of documentation on the web about it.
(*) every objective c method is a plain c function that takes a _cmd (selector) as the first parameter, and self as the second parameters, then a variable list of parameters.
One advantage of the dynamic dispatching is that you can add classes, variables and methods at runtime.
This is not possible with Swift, since it is based on a vtable approach (*)
(*) This is true for plain swift classes, since classes the inherith from objective-c uses, for compatibility reasons, the dynamic dispatching and the runtime of objective c. In some cases, even the vtable is not used: the function calls are inlined
This means that there is no dynamic search of the functions/methods at runtime, their addresses are statically written at compile time inside the vtable, and it’s not possible to change them at runtime.
If you try to look at the symbols of a Swift compiled app, you will see something like this (OverrideTest is a binary name that I’m analyzing):
LombaXAir:MacOS Lombardo$ nm OverrideTest 00000001000071e0 t _Mx_mem_size 00000001000071c0 t _Mx_reg_size U _NSApplicationMain U _OBJC_CLASS_$_NSObject U _OBJC_CLASS_$_NSWindow 0000000100010f90 S _OBJC_CLASS_$__TtC12OverrideTest11AppDelegate U _OBJC_METACLASS_$_NSObject 0000000100011070 D _OBJC_METACLASS_$__TtC12OverrideTest11AppDelegate 0000000100011010 s _OBJC_METACLASS_$___ARCLite__ U __Block_copy 0000000100004480 t __OSSwapInt32 0000000100004490 t __OSSwapInt64 0000000100001700 T __TF12OverrideTest16doSomethingSwiftFT_T_ 0000000100001aa0 T __TF12OverrideTest26doSomethingSwiftOverriddenFT_T_ 0000000100001820 T __TFC12OverrideTest11AppDelegate13aTestFunctionfS0_FT_T_ 00000001000018b0 T __TFC12OverrideTest11AppDelegate18anOverrideFunctionfS0_FT_T_ 00000001000017a0 T __TFC12OverrideTest11AppDelegate24applicationWillTerminatefS0_FGSqCSo14NSNotification_T_ 0000000100001690 T __TFC12OverrideTest11AppDelegate29applicationDidFinishLaunchingfS0_FGSqCSo14NSNotification_T_ 0000000100001a40 T __TFC12OverrideTest11AppDelegateCfMS0_FT_S0_ 0000000100001940 T __TFC12OverrideTest11AppDelegateD 00000001000019b0 T __TFC12OverrideTest11AppDelegatecfMS0_FT_S0_ 0000000100001590 T __TFC12OverrideTest11AppDelegateg6windowXwGSQCSo8NSWindow_ 0000000100001640 T __TFC12OverrideTest11AppDelegates6windowXwGSQCSo8NSWindow_ U __TFSS37_convertFromBuiltinUTF16StringLiteralfMSSFTBp17numberOfCodeUnitsBw_SS ... continue
Symbols like
__TFC12OverrideTest11AppDelegate13aTestFunctionfS0_FT_T_
are plain function/method names, mangled ( see Name Mangling ), that can be called by a C/ObjectiveC function without problems.
You can call them with the static address, or searching by name through dlsym (see this post: Swift Performselector alternative )
So, returning to the original topic, how to do something like method swizzling in Swift?
There is an alternative called mach_override. This is not safe and I discourage you to include it in a shipping application, however it’s possible.
mach_override “simply” add a jmp inside the assembly of the original function, making it jump to another custom function, and then jump back to the original one, if you want.
More info about mach_override Here (Github) and Here an high level explanation on how it works
After including mach_override in your project, you can override a method implementation with a c function like this:
void override() { void (*landing)(void * par); void (*originalFunctionAddress)(void * par); const void (*overrideFunctionAddress)(void * par); *(void **) (&originalFunctionAddress) = dlsym(RTLD_DEFAULT, "_TF15OverrideTestIOS16doSomethingSwiftFT_T_"); *(void **) (&overrideFunctionAddress) = dlsym(RTLD_DEFAULT, "_TF15OverrideTestIOS26doSomethingSwiftOverriddenFT_T_"); mach_override_ptr(originalFunctionAddress, overrideFunctionAddress, (void**)&landing); }
Method names are taken with “nm” from command line, but you can try to mangle it at runtime since the mangling syntax has been reverse engineered (an example by Evan Swick Here )
the “par” parameter is because every swift instance method underlying function, takes “self” as the last parameter.
To see this code in action, download a working example (for mac) from my GitHub account