Using a feature of Apple's linker this implementation of Code Injection allows you to update the implementation (i.e. body) of functions in your app without having to relaunch it. This can save a developer a significant amount of time tweaking code or iterating over a design.
This repo is a refresh of the InjectionIII app that uses a different technique to determine how to rebuild source files that should be faster and more reliable for very large projects. Gone is the involved parsing of Xcode build logs (if you can locate them) and messy escaping of special characters in filenames. A new app is used to launch Xcode with a SourceKit debugging flag enabled which provides all the information you need to be able to recompile files and then the runtime implementation of injection included in the InjectionLite package looks after the rest.
The basic MO is to build the app in the App
directory, or
download one of the binary releases in this repo, quit Xcode and run the
resulting InjectionNext.app
and use that to re-launch Xcode using the menu item
Launch Xcode
from the status bar. You then add this repo as a Swift package
dependency of your project and that should be all that is required for injection
in the simulator, injection on devices and injection of a MacOS app. No more
code changes required to load binary code bundles etc and you can leave
the InjectionNext package configured into your project permanently as
its code is only included for a DEBUG build. Your code changes take effect
when you save a source for an app that has this package as a dependency
and has connected to the InjectIonNext app which has launched Xcode.
As ever, it is important to add the options -Xlinker
and -interposable
(without double quotes and on separate lines) to the "Other Linker Flags" of
the targets of your project (for the Debug
configuration only) to enable
"interposing". Otherwise, you will only be able to inject non-final class methods.
To inject SwiftUI sucessfully a couple of minor code changes to each View are
required. Consult the https://github.com/johnno1962/HotSwiftUI README or you
can make these changes automatically using the menu item "Prepare SwiftUI/".
When your app runs it should connect to the InjectionNext.app
and it's icon
change to orange. After that, by parsing the messages from the "supervised"
launch of Xcode it is possible to know when files are saved and exactly how
to recompile them for injection. Injection on a device uses the same
configuration but is opt-in through the menu item "Enable Devices"
(as it needs to open a network port). You also need to select the
project's "expanded codesigning identity" from the codesigning
phase of your build logs in the window that pops up. Sometimes a
device will not connect to the app first time after unlocking it.
If at first it doesn't succeed, try again.
If you'd rather not be adding a SPM dependency to your project, the app's resources contains pre-built bundles which you can copy into your app during the build by using a "Run Script/Build Phase" (while disabling the "user script sandboxing" build setting) such as the following:
export RESOURCES="/Applications/InjectionNext.app/Contents/Resources"
if [ -f "$RESOURCES/copy_bundle.sh" ]; then
"$RESOURCES/copy_bundle.sh"
fi
These bundles should load automatically if you've integerated the Inject or HotSwiftUI packages into your project. Otherwise, you can add the following code to run on startup of your app:
#if DEBUG
if let path = Bundle.main.path(forResource:
"iOSInjection", ofType: "bundle") ??
Bundle.main.path(forResource:
"macOSInjection", ofType: "bundle") {
Bundle(path: path)!.load()
}
#endif
The binary bundles also integrate Nimble and a slightly modified version of the Quick testing framework to inhibit spec caching under their respective Apache licences.
To inject tests on a device: use these bundles and, when enabling the "Enable Devices" menu item select "Enable testing on device" which will add the parameters shown to the link of each dynamic library. As you do this, a command will be inserted into the clipboard which you should add to your project as a "Run Script" "Build Phase" to copy the required libraries into the app bundle.
For more information consult the original InjectionIII README or for the bigger picture see this swift evolution post.
You can run InjectionNext from the command line and have it open your project in Xcode automatically using the -projectPath option.
open -a InjectionNext --args -projectPath /path/to/project
Set a user default with the same name if you want to always open this project inside the selected Xcode on launching the app.
The colors of the menu bar icon bar correspond to:
Please note: you can only inject changes to code inside a function body and you can not add/remove or rename properties with storage or add or reorder methods on a non final class or change function arguments.
The fabulous app icon is thanks to Katya of pixel-mixer.com.