I built a thing! InterposeKit is a modern library for elegantly swizzling in Swift. It’s on GitHub, fully written in Swift 5.2+, and works on
@objc dynamic Swift functions or Objective-C instance methods.
The inspiration for InterposeKit was a race condition in Mac Catalyst, which required some tricky swizzling to fix. With InterposeKit, this is now much cleaner. Since everything’s explained much better on the project website, I’m just writing some random thoughts on building this that didn’t fit into the README.
Wow! I didn’t have the time to play with this before, but damn is it 💖 good. GitHub Actions is fast, easy to set up, reliable, and superbly well integrated — all the way down to automatic badges for CI state.
There are a few annoyances, like not being able to run Docker containers on macOS (this isn’t technical, just a money thing). The default Jazzy setup to generate documentation runs via Docker, so I had to jump through some hoops to make my project compile on Linux.
Swift and Type Aliases
It’s extremely unfortunate that the
@convention() modifier can’t be used on existing type aliases — this would have made Interpose way more convenient. I’m honestly tempted to write a proposal to get this into Swift because it would be cool and I’d be really interested in the learning experience.
Swift question: Is there a way to apply a calling convention to a pre-existing type signature? pic.twitter.com/2m7pkRX1Gp— Peter Steinberger (@steipete) May 30, 2020
Swift Package Manager
I finally watched WWDC2019:410 Creating Swift Packages and WWDC2019:408 Adopting Swift Packages in Xcode (Hi Boris!) and I really like SwiftPM. Yes, there’s a still a lot to do, but it’s getting Package Resources (SE-0271) with Xcode 12, and the integration with GitHub is nice. Not being allowed to delete
DerivedData anymore will be difficult though.
Class Load Events
objc_addLoadImageFunc is a big no-no, and it probably shouldn’t even exist in the header at all. However, there’s
_dyld_register_func_for_add_image, which is great. This includes a C callback, and while Swift does a really good job of making it just blend into the language, this callback is not a block and it can’t capture state. I eventually found out that I can put everything into a struct, as long as it’s all static, in order to not have this pollute my global namespace.
Why would I need this? There was a particular bug in Mac Catalyst that required such a trick.
Swift 5.2 callAsFunction
Well well well… here I was bitchin’ about Swift getting useless features, only to be extremely happy about
callAsFunction a few months later. In InterposeKit, I use it to have a shorthand for calling the original implementation of a function. It even does generics!
imp_implementationWithBlock has no way to undo or deregister the IMP, so once you submit a block that captures state, you have a permanent memory leak? Oh well.
This is my first Swift-specific open source project, apart from the usual gists. I’d like to learn, so please: BE harsh on me. I had a lot of fun and built this in a weekend. It helped me forget time and space (and current world events) for a bit and just tinker. I’m also sure I got things wrong, so please do tell me what can be made better.