Skip to content
Re-write of Injection for Xcode in (mostly) Swift4
Branch: master
Clone or download
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
Bootstrap Inject Generics until Swift 4.1 Nov 28, 2017
DDHotKey README.markdown Nov 7, 2018
EvalApp Extracting Compile directory Dec 1, 2017
Helper Inject Generics until Swift 4.1 Nov 28, 2017
InjectionBundle Update storyboard injection for Xcode 10.2 (#133) Apr 17, 2019
InjectionIII.xcodeproj
InjectionIII Release 1.5.2 (#134) Apr 17, 2019
SwiftEval New version of Injection vIII Nov 7, 2017
SwiftEvalTests Initial Commit Nov 5, 2017
XprobePlugin @ d7bc1c6 add Xprobe. Dec 6, 2017
signer MAS version Oct 22, 2018
.gitignore Minor fixes and version 1.2 released to support Xcode 10 Oct 22, 2018
.gitmodules Include Xprobe Dec 6, 2017
LICENSE Initial Commit Nov 5, 2017
README.md Fix injecting Swift classes (#120) Mar 27, 2019

README.md

InjectionIII - overdue Swift4 rewrite of Injection

Icon

This start-over implementation on Injection for Xcode has been built into an app: InjectionIII.app included in the repo which runs in the status bar. Code injection allows you to update the implementation of methods of a class incrementally in the iOS simulator without having to rebuild or restart your application saving developer time. You can avoid the complications of code signing by using the pre-built binary which is available to download here. For some reason you may need to take the app out of qurantine manually or it will report it as damaged when you run it: xattr -d com.apple.quarantine ~/Downloads/InjectionIII.app. To use, copy/link it to /Applications and run the app. Injection also expects to find your current Xcode at path /Appplications/Xcode.app. Finally, you'll need to add one of the following to your application's applicationDidFinishLaunching:

Xcode 10.2 and later:

#if DEBUG
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
//for tvOS:
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle")?.load()
//Or for macOS:
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()
#endif

Xcode 10.1:

#if DEBUG
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection10.bundle")?.load()
//for tvOS:
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection10.bundle")?.load()
//Or for macOS:
Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection10.bundle")?.load()
#endif

Once injection is connected, a file watcher is started in the InjectionIII app and whenever you save a Swift or Objective-C source the target app is messaged to update the implementation. The file watcher can be disabled & enabled while the app is running using the status bar menu. While the file watcher is disabled you can force injection's through manually using a hotkey ctrl-=. If you inject a subclass of XCTest it will try running that individual test inside your application. When you run your application without rebuilding (^⌘R), recent injections will be re-applied.

Included is a manual implementation of "code injection". If you are stopped in a class, you can edit the class' implementation, save it and type "p inject()". Your changes will be applied without having to restart the application. To detect this in your code to reload a view controller for example, add an @objc injected() method or subscribe to the "INJECTION_BUNDLE_NOTIFICATION".

Included in this release is "Xprobe" which allows you to browse the objects in you application as a graph and execute code against them. If you want to build from source you'll need to use:

git clone https://github.com/johnno1962/InjectionIII --recurse-submodules

Available downloads

Xcode 10.1, Xcode 10.2
Mac app store

Limitations

To work, method dispatch must be through the classes "vtable" and not be "direct" i.e. statically linked. This means injection will not work for final methods or methods in final classes or structs. The @objc func injected() method relies on a sweep of all objects in your application to find those of the class you have just injected which can fail. If you encounter problems, use the notification.

If you are using Code Coverage, you will need to disable it or you may receive a:

Symbol not found: ___llvm_profile_runtime error.`

Go to Edit Scheme -> Test -> Info -> Code Coverage and (temporarily) disable.

Be mindful of global state -- If the file you're injecting as non instance-level variables e.g. singletons they will be reset when you inject the code as the new implementations will refer to the newly loaded version of the class.

As injection needs to know how to compile swift files individually it is incompatible with building using whole module optimisation. A workaround for this is to build with WMO switched off so there are logs of individual compiles then switching it back on if it suits your project best.

SwiftEval - Yes, it's eval() for Swift

Icon

SwiftEval is a single Swift source you can add to your iOS simulator or macOS projects to implement an eval function inside classes that inherit from NSObject. There is a generic form which has the following signature:

extension NSObject {
	public func eval<T>(_ expression: String, _ type: T.Type) -> T {

This takes a Swift expression as a String and returns an entity of the type specified. There is also a shorthand function for expressions of type String which accepts the contents of the String literal as it's argument:

	public func eval(_ expression: String) -> String {
	    return eval("\"" + expression + "\"", String.self)
	}

An example of how it is used can be found in the EvalApp example.

    @IBAction func performEval(_: Any) {
        textView.string = eval(textField.stringValue)
    }

    @IBAction func closureEval(_: Any) {
        if let block = eval(closureText.stringValue, (() -> ())?.self) {
            block()
        }
    }

implementation

The code works by adding an extension to your class source containing the expression. It then compiles and loads this new version of the class "swizzling" this extension onto the original class. The expression can refer to instance members in the class containing the eval class and global variables & functions in other class sources.

The command to rebuild the class containing the eval is parsed out of the logs of the last build of your application and the resulting object file linked into a dynamic library for loading. In the simulator, it was just not possible to codesign a dylib so you have to be running a small server "'signer", included in this project to do this alas.

Acknowledgements:

This project includes code from rentzsch/mach_inject, erwanb/MachInjectSample and davedelong/DDHotKey under their respective licenses.

This release includes a very slightly modified version of the excellent canviz library to render "dot" files in an HTML canvas which is subject to an MIT license. The changes are to pass through the ID of the node to the node label tag (line 212), to reverse the rendering of nodes and the lines linking them (line 406) and to store edge paths so they can be colored (line 66 and 303) in "canviz-0.1/canviz.js".

It now also includes CodeMirror JavaScript editor for the code to be evaluated using injection under an MIT license.

You can’t perform that action at this time.