Code Injection is the ability to add new code to a running app without relaunching it. In iOS development, large project compile time can be quite long, and injection can offer a huge productivity gain during development.

There are two projects I’m aware of that support this: Injection for Xcode and DyCI. I’ve spent more time with Injection for Xcode, so that’s the one I’m focusing on here.

In this post, I’m going to focus primary on how to get your project setup for successfully running your first injection.

Overview

A quick read of the project’s GitHub Readme offers the following overview for how this works:

It performs this by parsing the build logs of the application to determine how a source file was last compiled. With this it wraps the result of re-compiling into a bundle which is injected into the application using the dynamic loader. At this point there are two versions of a class in the app, the original and a new modified version from the bundle. The modified version is then “swizzled” onto the original class so changes take effect.

For a deep technical dive into the implementation, @orta has a great code spelunking article that walks through the codebase. He does a great job making a complex project more approachable.

Setup

Xcode 7

Getting injection setup on Xcode 7 is straightforward, particularly if you already have Alcatraz installed. Once again, @orta has a quick introduction blog post and video tutorial that will help you get setup.

Xcode 8

For Xcode 8 it’s more complicated. This year Apple introduced an official extensions API, but it is currently limited to source editor extensions. Apple has also signed Xcode to prevent unsigned code from being injected and executed (remember XcodeGhost 👻?). These are good steps for security, but also prevent tools like injection.

We can get code injection working in Xcode 8 with the following steps:

  1. Unsign Xcode 8.

    In my previous post Safely Running Extensions in Xcode 8, I describe how to do this and ensure that your workflow still provides the safety gains of using a signed copy of Xcode for deployment builds.

  2. Build Injection for Xcode from source. Start by downloading or cloning Injection for Xcode from GitHub.

    Open the project file in Xcode 8. Codesigning is now required, so udpate the plugin’s project file to include your codesigning, and update the template project that will be executed by your host project later to include codesigning as well. These two projects are InjectionPluginLite/InjectionPlugin.xcodeproj and InjectionPluginLite/iOSBundleTemplate/InjectionBundle.xcodeproj.

    If you have multiple signing identities (such as a personal identity and a company identity), then you may need to modify the Run Script build phase on the InjectionPlugin.xcodeproj to reference a specific identity:

    Build the scheme InjectionPlugin, which will install the bundle to ~/Library/Application Support/Developer/Shared/Xcode/Plug-ins/InjectionPlugin.xcplugin

  3. Approve the plugin to run. Now that the plugin is installed, quit and re-launch Xcode 8. When it relaunches, you’ll be presented with a dialog asking whether to load the plugin bundle. Choose “Load Bundle”.

    You should be able to verify that the plugin is successfully installed by going to the Product menu bar item, and you should have two new items at the bottom of that menu: Injection Plugin and Inject Source.

  4. Inject from your own project. Open your own project now, and let’s set it up to respond to injection. Add the following to one of your view controllers:

    func injected() { print("I've been injected: \(self)") }

    (This function is a special function that is called when injection is triggered. You can use it as a hook to do other actions in your app. For example, you may want to add code here to redraw a screen that you are currently developing.)

    Build and run the project. With that source file still visible, select Product > Inject Source, or press the hotkey ^=. You should see something like the following logged to the console:

    I've been injected: <InjectionDemo.ViewController: 0x7fc8ea505ec0>

    If so, success!

    However, you may see a codesigning error like this:

    If so, this is indicative of multiple iOS codesigning identities. By default, the injection bundle is trying to inject based on a generic developer profile name, but if you have multiple, you need to specify which to use. In your repo, find the file iOSInjectionProject/x86_64/identity.txt and modify iPhone Developer to be your fully qualified signing identity name. For me, that’s iPhone Developer: John McIntosh (XF7MPRXLNG).

    After saving that change, you should be good to go.

Learn More

The injection plugin’s GitHub page has a few good examples of use-cases for getting started with workflows to speed up your development.