Here's the closest I was able to get, with a few hours of tinkering. It involves using a fork of mach_override, a couple of DYLD quirks regarding load order, and a stomach for crazy hacks.
It will only work on the simulator, but that should suffice for the use case you seem to have (I certainly hope you don't have device-only dependencies).
The meat of the code looks something like this:
#include <objc/runtime.h>
#include <mach_override/mach_override.h>
// It is extremely important that we have DYLD run this constructor as soon as the binary loads. If we were to hook
// this at any other point (for example, another category on NSObject, in the main application), what could potentially
// happen is other `+load` implementations get invoked before we have a chance to hook `method_exchangeImplementation`,
// and we don't get to see those swizzles.
// It is also extremely important that this exists inside its own dylib, which will be loaded by DYLD before _main() is
// initialized. You must also make sure that this gets loaded BEFORE any other userland dylibs, which can be enforced by
// looking at the order of the 'link binary with libraries' phase.
__attribute__((constructor))
static void _hook_objc_runtime() {
kern_return_t err;
MACH_OVERRIDE(void, method_exchangeImplementations, (Method m1, Method m2), &err) {
printf("Exchanging implementations for method %s and %s.
", sel_getName(method_getName(m1)), sel_getName(method_getName(m2)));
method_exchangeImplementations_reenter(m1, m2);
}
END_MACH_OVERRIDE(method_exchangeImplementations);
MACH_OVERRIDE(void, method_setImplementation, (Method method, IMP imp), &err) {
printf("Setting new implementation for method %s.
", sel_getName(method_getName(method)));
method_setImplementation_reenter(method, imp);
}
END_MACH_OVERRIDE(method_setImplementation);
}
Which is surprisingly simple, and produces output like this:
Exchanging implementations for method description and custom_description.
There is no good way (without using a breakpoint and looking through the stack trace) to figure out which class is being swizzled, but for most things, just taking a peek at the selectors should give you a hint about where to go from there.
It appears to work with a couple of categories that I've created that swizzle during +load
, and from my reading of mach_override
and DYLD's internals, as long as you have your library load order properly setup, you can expect this to be initialized before any other user-land code, if you put it in it's own dynamic library.
Now, I can't vouch for safety of this, but it seems useful to keep around as a debugging tool, so I've published my example to github:
https://github.com/richardjrossiii/mach_override_example
It's MIT licensed, so feel free to use as you see fit.