Copyright (c) John Holdsworth 2012
Injection is a plugin for Xcode that allows you to "inject" Objective-C code changes into a running application without having to restart it during development and testing. After making a couple of minor changes to your application's "main.m" and pre-compilation header it will connect to a server running inside Xcode during testing to receive commands to load bundles containing the code changes you make.
Stop Press: Injection no longer has to convert your classes into categories so no changes are made to your source code in order for it to work. It works for OS X and iOS projects in the simulator and on the device (if you add an extra "run script" build phase as instructed.) The time taken to inject is the amount of time it takes to recompile the class modified.
A quick demonstration video/tutorial of Injection in action is available here:
Announcements of major commits to the repo will be made on twitter @Injection4Xcode.
To use injection, open the InjectionPlugin project, build it and restart Xcode. This should add a submenu and an "Inject Source" item to Xcode's "Product" menu. Open a simple example project such as UICatalog or GLEssentials from Apple and use the "Product/Patch Project for Injection" menu item to prepare the project and rebuild it. When you run the project it should connect to Xcode which will display a red badge showing the application is prepared to load patch bundles. Select text in an implementation source file and use menu item "Product/Inject Source" to inject any changes you may have made into the app. Be sure to #define DEBUG.
While injection no longer has to patch your class's implementation, it does have to patch the class header once to make explicit ivars (the old declarative way) for @properties defined in the class and extension. This is required to ensure the ivars are created in the same order and at the same offsets in the main application and bundle compile of the class. Note, the code that does this assumes there is only one class defined in any injected source file and that this is only applied during DEBUG builds.
Support for injecting projects using "CocoaPods" which use "workspaces" added since version 2.6. The plugin assumes the workspace file has the same name as the actual project ".xcodeproj". Classes in the project or Pods can be injected as well as categories or extensions provided the original class is defined in the same file as the category.
License.
The source code is provided on the understanding it will not be redistributed in whole or part for payment and can only be redistributed with it's licensing code left in. License is hereby granted to evaluate this software for two weeks after which if you are finding it useful I would prefer you made a payment of $10 (or $25 in a commercial environment) as suggested by the licensing code included in the software in order to continue using it.
The four projects in the source tree are related as follows:
ObjCpp: A type of "Foundation++" set of C++ classes I use for operators on common objects.
Injection: The original application which worked alongside Xcode as submitted to Apple
InjectionPlugin: The plugin source packaging the application for use inside Xcode **
InjectionInstallerIII: an installer application for the Xcode plugin.
** "InjectionPlugin" is the only project you actually need to build to use injection.
If you find (m)any issues in the code, get in contact using the email injection (at) johnholdsworth.com
InjectionPlugin/Classes/InInjectionPlugin.m
Singleton subclass of original InAppDelegate class responding to Xcode Menu events. Superclass responsible for running up TCP server process on port 31442 receiving connections from applications with their main.m patched for injection. When an incoming connection arrives it opens an instance of InPluginDocument associated with the project of the application.
InjectionPlugin/Classes/InPluginDocument.m
An instance is created of this INDocument(NSDocument) subclass to shadow each project being injected. Superclass runs a series of Perl scripts in response to menu events to patch projects or code for injection and load the resulting bundles.
Injection/Injection/InAppDelegate.m
The original standalone application delegate running the injection service and managing licensing.
Injection/Injection/InDirectory.m
A utility class for OS X 10.6 where FS events can not be selected down to the file level.
Injection/Injection/InDocument.m
The subclass responsible for running the scripts used by injection to patch projects and classes and parsing their output for actions to perform.
Injection/Injection/InImageView.m
NSImageView subclass that knows the path of the file dragged onto it.
Injection/Injection/validatereceipt.m
copy protection formerly used for App Store
Injection/Injection/listDevice.pl
Lists the files in the sandbox on an iOS device.
Injection/Injection/openBundle.pl
Opens the bundle project containing the class categories used for injection.
Injection/Injection/openProject.pl
Run when a project is first used for injection to patch main.m and the ".pch" file
Injection/Injection/openURL.pl
Opens special URLs used by injection to patch/un-patch specific files.
Injection/Injection/prepareBundle.pl
The script called when you inject a source file to patch the source class into a category, build the injection bundle project and signal the client application to load the resulting bundle to apply the code changes.
Injection/Injection/revertProject.pl
Un-patches main.m and the project's .pch file when you have finished using injection.
Injection/Injection/common.pm
Code shared across the above scripts including the code that patches classes into categories.
#/ directory to be monitored for individual file changes (n/a in plugin version)
#! directory to be added to array for FSEvents (n/a for plugin version)
## Start FSEvent stream (n/a "") and use the regexp given for file filter.
> open local file for write
< read from local file (and send to local file or to application)
!> open file on device/simulator for write
!< open file on device/simulator for read (can be directory)
!/ load bundle at remote path into client application
!: set local "key file" variable (main.m and .pch locations)
%! evaluate javascript in source status window
%2 load line as HTML into source status window
%1 append line as HTML to console NSTextView
? display alert to user with message
Otherwise the line is appended as rich text to the console NSTextView.
$injectionResources Path to "Resources" directory of plugin for headers etc.
$appVersion Injection application version (currently "2.6")
$urlPrefix Prefix for URLs in links in the console view ("file://" for plugin)
$appRoot Path to "Resources" directory of plugin again
$projectFile Path to ".xcodeproj" for current project document
$executablePath Path to application binary connected to plugin for this project
$patchNumber Incrementing counter for sequentially naming bundles
$unlockCommand Command to be used to make files writable from "app parameters" panel
$flags As defined below...
$spare1, $spare2, $spare3, $spare4 reserved for future use..
@extra Arguments to script for example: file(s) to inject
1<<0 Project is a "demo" application i.e. /UICatalog|(iOS|OSX)GLEssentials/
1<<1 Pre-convert headers of all writable class implementations.
1<<2 Suppress application alert on load of changes.
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.