Injection for Xcode

Injection allows you to inject code changes into an application while it is running for development and debugging. It is intended as a replacement for the "Fix and Continue" functionality which was removed in Xcode® 4. By using a set of simple preprocessor macros it allows your classes to be recompiled selectively as class categories which can be loaded at runtime by placing them in a bundle. The Objective-C runtime will always take the category version of method implementations in preference to the version the application was originally compiled with. This process can be repeated indefinitely and works for OS X and iOS applications and on iOS devices when the bundle is code-signed and copied onto the device.

To get started straight away, follow the Quick-start Guide at the end of this document.

To use Injection, check out the classes you would like to work with launch the Injection application and open the project you would like to develop classes for or drag and drop the ".xcodeproj" file onto the application icon. This will patch all writable classes in the project so they can be loaded at run time by inserting the macros described below . It also creates an Xcode bundle project inside your main project which can be compiled separately. You then rebuild and run your application and it should connect automatically through a socket to the Injection application using code inserted into the project's "main.m" source file.

When a project is open, the build button includes an indicator showing the following status colours:

Client application is not connected to Injection.
Client application connected waiting for code changes.
A script is running to open the project or build and load bundle.
A bundle compilation or load error has occurred.
Automatic build is turned off and source files have changed.

Thereafter, each time you save a class the change will be detected and it will be imported into to the BundleContents.m source file in the bundle project. The bundle project is then built inside Injection and the client application messaged to load the new version of the class' implementation. Max OS X applications will become active indicating the changes bundle has loaded, iOS applications will display a message when the changes bundle has loaded. Otherwise Injection will make itself active displaying any errors in the build or load. You may need to edit the bundle project directly to ensure that it links against all required Frameworks and make sure it has the same compiler type and parameters for architecture and memory management model. Make sure also that the build option 'Symbols Hidden by Default' is not enabled in your application's main project.

While an application is connected 5 tunable parameter values are available to the application through the global variable INParameters. These can be used in to tune your code during development and modified in real time using the sliders in the "Parameters" pop-down panel. Object oriented access to these parameters is available using the +[NSObject inParameter:tag] method. You can also set a delegate using +[NSObject setDelegate:delegate forInParameter:tag] and receive -[delegate inParameter:tag hasChanged:value] notifications as the sliders are moved.

To allow classes to be converted into categories when compiled in a bundle, and to deal with class variables, Injection patches your class automatically to use 6 preprocessor macros that are added to the projects ".pch" file (so they are available to all project source.) Note however, changes to instance variables will require the program to be rebuilt and re-launched. Also, classes with @private or @synthesized instance variables can not be injected as their symbols are not published. These macros are defined as follows (the INJECTION_BUNDLE macro is the incrementing product name of the bundle being built.)

Pre-Processor MacroBuilding ProjectDefinition while building bundle
_inglobal/* */extern
_instatic/* */extern
_inval(_value...)= _value/* _value */

The lowercase macros are to deal with class variables which need to be made global so the bundle class category can refer to them and the class does not loose it's internal state. This means, static variables must be made global and their names must be unique across classes being injected. If a static variable is constant across the execution of a class i.e. it is not messaged or assigned to it will become an "_inprivate" variable. These macros are inserted automatically provided the class source is writable when the project is opened or a class is bundled. Any alphanumeric character in column one, not in a comment is taken to be the start of a class variable definition. The _inval() macro is inserted around variable initialisers to make sure they are not provided when the class source is being built in a bundle to avoid compilation warnings.

The hook for your application to connect to Injection is placed in it's main.m file where it includes the file "BundleInjection.h" from the Application Support directory. To ensure your applications are not released to AppStore looking to connect to Injection you must uncomment the #ifdef DEBUG preprocessor directives in the preprocessor ".pch" file to prevent the macro INJECTION_ENABLED from being defined. One final patch sometimes made to class headers is to declare instance variables explicitly where they have been specified using the new @property/@synthesize syntax. Otherwise a class can not refer to them when compiled as a category. A similar problem occurs with instance variables declared in "extensions". These declarations can not be injected and must be moved into the main class.

This process of automatically inserting the above macros is robust but can likely always be improved. If you have any particular examples of code which is patched incorrectly or any other problem using or suggestion to improve "Injection", do please contact the developer so I can rectify it using the email address here. These instructions will close automatically when you open a project but you can always view the again by using the "File/Introduction" or "Help" menu items.

OS X, iOS and Xcode are trademarks of Apple Inc.

QuickStart Guide

To get started using Injection, follow these steps:

  • Launch Injection.

  • Open this guide in a web browser as it will close automatically when you open a project.

  • Download the UICatalog example project and unzip it.

  • Drag the UICatalog/UICatalog.xcodeproj file onto the Injection Icon to open and convert it for run-tine loading.

  • Click Open App Project button to open the UICatalog project in Xcode the build and run the application in the iOS Simulator (change the iOS deployment target on the project info page to iOS5.0 if you have any problems building.)

  • When the application starts it will connect to Injection which will display a red (1) badge indicating one connected application.

  • Make a change to a file and save it. Injection will pick up the change, roll the class changed into a bundle and message the application to load it applying the changes made.

  • If linking errors are displayed in the Injection window make sure the main bundle project does not have the 'Symbols Hidden by Default' build option set. If so rebuild and launch the application. Other linking errors can be caused by missing frameworks or using the incorrect architecture, compiler or memory model in the bundle project created to inject the code. Click the Open Bundle Project button to edit the sub-project until it builds. Any problems loading the bundle will be displayed in the debug console of the client application itself.

  • To inject code into an application running on an iOS device an additional manual step is required. Add the following as a "Run Script" step in the target "Build Phases" tab:

    echo $CODESIGNING_FOLDER_PATH >/tmp/"$PROJECT_NAME".info && echo $CODE_SIGN_IDENTITY >>/tmp/"$PROJECT_NAME".info && exit;

    This script allows Injection to know the build location of the app before it is copied to the device and the code signing identity being used. Rebuild the application and you can then use Injection as before.