Learning tweak development has been a very giving and exciting experience. I’ve not only been able to create tools which would have never been possible on non-jailbroken devices, but also had the possibility to tweak 3rd party apps, as well as testing vulnerabilities on projects I’ve been involved in. But since more and more projects are moving over to Swift, tweak development is becoming harder to do (for now). This was something that became painfully obvious after Swift 4, where the compiler became much more conservative in making Swift methods accessible in Objective-C; something that certainly wasn’t the case in previous Swift versions.
Methods in Objective-C vs Swift
There’s a large fundamental difference between methods in Objective-C and in Swift, which in the context of development, tweak or not, gives a trade-off between convenience and safety.
Objective-C uses message dispatch, a type of dynamic dispatch which resolves methods dynamically at runtime. As we can read in Apple’s Documentation:
The Objective-C language defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language; it’s what makes the language work.
This opens up a lot of possibilities for manipulation during runtime: creating new classes, adding new methods, manipulating existing methods (method swizzling), etc. While this approach is very convenient, it’s also easier to crash during runtime, for instance if a selector turns out to not exist.
Depending on the situation, Swift will use either direct dispatch, table dispatch or message dispatch.
- Direct dispatch is prioritized.
- If overriding is needed, table dispatch is the next candidate.
- Need both overriding and visibility to Objective-C? Then message dispatch.
So when dealing with classes, we’ll most likely either encounter table dispatch or message dispatch (for methods inherited from an Objective-C class).
Table dispatch in Swift works just like C++, where Swift classes contain a
vtable member, created at compile time, which lists the available methods. The compiler uses that lookup table to translate method calls to appropriate function pointers.
Since Swift 4, the compiler will only use the Objective-C compatible message dispatch when absolutely needed. This can however be forced by explicitly marking classes or methods with the
As mentioned in Objective-C vs Swift messages dispatch:
Another way to force Swift to use dynamic dispatch is to mark a class member declaration with the
dynamickeyword. Those declarations will be dispatched using Objective-C runtime. Those will be also implicitly marked with the
This doesn’t necessarily guarantee dynamic dispatch however, as the Swift Documentation explains: While the
@objcattribute exposes your Swift API to the Objective-C runtime, it does not guarantee dynamic dispatch of a property, method, subscript, or initialiser. The Swift compiler may still devirtualise or inline member access to optimise the performance of your code, bypassing the Objective-C runtime
Finding the Swift function symbol
For the following instructions you need to have Xcode and Theos installed, as well as a jailbroken device.
Fire up Xcode and create a new
Single View App for iOS. Make sure to choose Swift as development language and give it a random name. I’ll name my project
Build the project.
In the left sidebar, right click you compiled app under the product folder. Select
Show in Finder.
cmd + i to get the info tab. Copy your project path.
Now open the terminal and navigate to said path.
Using nm, we can dump the Swift symbols. Let’s take a look at some methods in
Here’s how the symbols look unmangled.
Both of the above methods, which are inherited from the Objective-C class
UIViewController, are both exposed to Swift and to Objective-C. So hooking them through logos is no problem.
Creating the tweak
Create a new theos tweak in the directory of your choice.
Open and replace everything in
Tweak.xm with the following:
Deploy the tweak to your device, and then run the App on your device through Xcode.
You should see your
NSLog in the console.
Adding a Swift method
Let’s add our own method to
If you build the project, and run nm again, you’ll notice that there’s no Objective-c equivalent:
Change viewDidLoad in
Tweak.xm to the following:
Deploy the tweak to your device, and run the app in Xcode again.
The app now crashes since
randomFunction is not exposed to Objective-C, hence no selector to be found.
Calling a Swift method
Through running nm previously, we found out the mangled signature of our new method:
Using MSFindSymbol we can find the function pointer to the Swift method, and call it.
Change viewDidLoad in
Tweak.xm to the following:
randomFunction should now get called. Check your console.
Hooking a Swift method
After finding the function pointer to a Swift method, we can use MSHookFunction to hook it.
Replace the following in %ctor with:
Deploy the tweak to your device, and run the app in Xcode again. Your method should be successfully hooked, and your console in Xcode should now look like this:
Trying to interact with native Swift objects will make your tweak crash - unless you do it in the above way (creating and modifying Swift objects by calling their function pointers). Unfortunately, this adds much more limitation and overhead when it comes to tweaking Swift apps. But as soon as (and if) Logos adds support for Swift, that problem will be solved. There has been talks about adding Swift compatibility in tweaks after Swift 5; a release which will hopefully bring ABI stability.
- Useyourloaf - Upgrading to Swift 4
- Untitledkingdom - Objective-C vs Swift messages dispatch
- Thuyen’s corner - Method dispatch in Swift
- Stackoverflow - What is the difference between dynamic and late binding
- Objective-C Runtime Documentation
- Mike Ash - Swift name mangling
- Securify - Hooking swift methods for fun and profit
- Apple - Swift Documentation
- Cydia Substrate