I see that installGetClassHook_untrusted is run as an initializer and that this function can take 100ms, largely due to dladdr. At other times, it seems to finish instantly and my symbolic breakpoint for it stops working (but it's still listed in DYLD_PRINT_INITIALIZERS as being called). Also, when I set the minimum app version from 12 to 13, it no longer gets called. So, I have a few questions:
How can I prevent it from being run, while still leaving the minimum iOS version at 12.0? It seems to be a Swift 5.0 compatibility thing, does that mean I need to remove all code built for Swift 5.0?
Why might I see it being called in DYLD_PRINT_INITIALIZERS but have it not take any time at all, nor trigger symbolic breakpoints?
Any answers, guesses, or just additional context would be greatly appreciated.
The Objective-C runtime in iOS 12 and earlier did not have correct support for instantiating Swift classes, so that initializer in the backward compatibility library replaces the ObjC runtime hook for instantiating Swift classes with a correct implementation. This is necessary for code generated with new Swift compilers to run properly on older OSes. You can see the implementation here:
The dladdr check ensures that the constructor is actually running on behalf of the main executable, and not a dylib, because some build systems improperly link the libswiftCompatibility* libraries into dylibs, and we don't want an endless chain of hooks piled on each other. If you are certain that none of the dylibs your executable links against includes this hook, then you could patch it out. Or, you could remove the constructor entirely, as long as you still install the fixed hook manually at some point before NSClassFromString or objc_getClass are ever called in the process.
By "patch it out", I mean that the libswiftCompatibility50.a library is part of the developer toolchain, not the OS, and it gets statically linked into executables that need to back deploy to older OSes, so you can conceivably replace it with a modified version that meets your needs, as long the behavior modifications it installs still get installed early enough. If you were going to use a modified libswiftCompatibility library that didn't include the static constructor, then yeah, you should be able to run the initializer code first thing in main before any Swift-ObjC interop calls happen.
I haven't heard anything about it not working; the change probably just hasn't been made yet. I filed rdar://76538727 to make sure our team at Apple takes a look at this again (if nobody beats us to it).
Yes, the compatibility libraries are static libraries that are linked into your main executable, so compiling with a newer toolchain will pick up the new implementation. However, that linking happens at the bitcode level, so the App Store recompiling wouldn't really be able to replace the implementation without you resubmitting your app.