InjectionIII Documentation

repository·main·Indexed 26 days ago

https://github.com/johnno1962/injectioniii

A hot-reloading tool for Swift that allows developers to inject code changes into running applications without restarting. Supports iOS, tvOS, visionOS, and macOS, with specialized support for SwiftUI and UIKit. Includes configuration guides for Xcode 16.3, physical device setup, AppCode plugin installation, and technical details on memory layout limitations and environment variables.

Tokens
4.9K
Snippets
11
Records
28
Agent score
89%

What's inside InjectionIII

  1. Install the InjectionIII plugin for AppCode

    main

    You can install the InjectionIII plugin for AppCode using one of two methods:

    Method 1: Manual Installation

    1. Download the Injection.jar file.
    2. In AppCode, navigate to Preferences/Plugins.
    3. Click the ⚙️ icon and select Install plugin from disk....
    4. Select the downloaded Injection.jar.
    5. Restart AppCode.

    Method 2: JetBrains Plugin Store

    Install directly via the JetBrains plugin store.

  2. Configure InjectionIII for Swift/Objective-C/C++

    main

    To enable code injection in your project, you must perform two steps:

    1. Add Linker Flags: In your Xcode project settings, add -Xlinker -interposable to the Other Linker Flags for all targets in your Debug configuration.
    2. Load the Injection Bundle: Add the following code to your application delegate's applicationDidFinishLaunching: method to load the appropriate bundle for your platform.

    Note: If you encounter linking errors like Can't find ordinal for imported symbol..., add -Xlinker -undefined -Xlinker dynamic_lookup to your Other Linker Flags.

    #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
  3. Setup InjectionIII for Simulator (iOS, tvOS, macOS)

    main

    To use InjectionIII in the simulator, follow these steps:

    1. Download a release from GitHub or the Mac App Store.
    2. Add the following code to your app to be executed on startup (wrapped in #if DEBUG):
    #if DEBUG
    // For iOS:
    Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle")?.load()
    
    // For tvOS:
    // Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle")?.load()
    
    // For macOS:
    // Bundle(path: "/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle")?.load()
    #endif
    1. In your project's Other Linker Flags for the Debug configuration, add these two flags on separate lines:
      • -Xlinker
      • -interposable
    #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
  4. Handle UI updates after code injection

    main

    Injecting code updates the implementation, but it does not automatically trigger a UI refresh. To see changes on screen, you must force the view to reload.

    Option 1: Using @objc func injected()

    Add an @objc func injected() method to your UIViewController or class. InjectionIII will attempt to 'sweep' the application to find instances of the injected class and call this method.

    extension UIViewController {
        @objc func injected() {
            viewDidLoad()
        }
    }

    Option 2: Using Notifications (More Reliable)

    If the sweep method is unreliable (e.g., due to unowned variables), subscribe to the INJECTION_BUNDLE_NOTIFICATION instead:

    NotificationCenter.default.addObserver(self,
        selector: #selector(configureView),
        name: Notification.Name("INJECTION_BUNDLE_NOTIFICATION"), object: nil)
  5. Setup Injection on Physical iOS, tvOS, or visionOS Devices

    main

    To inject code into a real device, follow these steps:

    1. Use a GitHub release 4.8.0+ of InjectionIII.app.
    2. Run this command on your Mac to opt-in: defaults write com.johnholdsworth.InjectionIII deviceUnlock any
    3. In your Xcode project, add a Build Phase script (ensure "User Script Sandboxing" is turned OFF):
      RESOURCES=/Applications/InjectionIII.app/Contents/Resources
      if [ -f "$RESOURCES/copy_bundle.sh" ]; then
          "$RESOURCES/copy_bundle.sh"
      fi
    4. Add this startup code to 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
    RESOURCES=/Applications/InjectionIII.app/Contents/Resources
    if [ -f "$RESOURCES/copy_bundle.sh" ]; then
        "$RESOURCES/copy_bundle.sh"
    fi
  6. Inject SwiftUI views using type erasure

    main

    Injecting SwiftUI views requires type erasure because changing elements in a view's body changes the return type of the Content property, which causes a crash.

    1. Add the Injection Support Code: Include the InjectionObserver and View extensions provided in the documentation in your project.
    2. Apply Type Erasure: Use the .eraseToAnyView() modifier at the end of any view's body property you wish to inject.
    3. Automatic Refresh: To make the view redisplay automatically, add an @ObservedObject initialized to injectionObserver within your view.

    Alternatively, use the .onInjection { ... } modifier to execute specific code whenever the interface is injected.

  7. Use the Inject Source command in AppCode

    main

    Once the plugin is installed and your project is configured, you can trigger source injection in AppCode:

    1. Run your program.
    2. Wait for indexing to complete.
    3. Use the keyboard shortcut control-= or select Inject Source from the Run menu.

    Note: Use this plugin instead of running the standalone InjectionIII application, as they share the same port.

  8. Setup macOS/Catalyst injection

    main

    Injecting into macOS or Catalyst apps requires disabling security restrictions and re-signing the injection bundles.

    1. Disable Security: In your project settings, turn off App Sandbox and Disable Library Validation (under Hardened Runtime).
    2. On Apple Silicon: If your app has web content, you may also need to enable Allow execution of JIT-compiled code.
    3. Automated Re-signing: Add the following script as a Build Phase in your target to automatically sign the InjectionIII bundles with your app's identity:
    export CODESIGN_ALLOCATE=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
    INJECTION_APP_RESOURCES=/Applications/InjectionIII.app/Contents/Resources
    /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY  $INJECTION_APP_RESOURCES/maciOSInjection.bundle/maciOSInjection
    /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY  $INJECTION_APP_RESOURCES/maciOSInjection.bundle/Frameworks/SwiftTrace.framework/SwiftTrace
    # ... (additional signing commands)
    defaults write com.johnholdsworth.InjectionIII "$PROJECT_FILE_PATH" $EXPANDED_CODE_SIGN_IDENTITY
    export CODESIGN_ALLOCATE\=/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/codesign_allocate
    INJECTION_APP_RESOURCES=/Applications/InjectionIII.app/Contents/Resources
    /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY  $INJECTION_APP_RESOURCES/maciOSInjection.bundle/maciOSInjection
    /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY  $INJECTION_APP_RESOURCES/maciOSInjection.bundle/Frameworks/SwiftTrace.framework/SwiftTrace
    /usr/bin/codesign --force --sign $EXPANDED_CODE_SIGN_IDENTITY $INJECTION_APP_RESOURCES/maciOSInjection.bundle/Frameworks/SwiftTrace.framework/SwiftTrace
    defaults write com.johnholdsworth.InjectionIII "$PROJECT_FILE_PATH" $EXPANDED_CODE_SIGN_IDENTITY
  9. Setup Injection for SwiftUI

    main

    SwiftUI requires specific modifications to force redraws and prevent crashes due to memory layout changes.

    1. Force Redraw: Use the @ObserveInjection property wrapper (available via HotSwiftUI or Inject) inside your View struct.
    2. Type Erasure: Wrap your body return type in AnyView using the .enableInjection() method to ensure the concrete return type doesn't change during injection, which would cause a crash.

    Note for Xcode 16+: The SWIFT_ENABLE_OPAQUE_TYPE_ERASURE setting is ON by default, so you may not need explicit type erasure, but you still need @ObserveInjection to force redraws.

    @ObserveInjection var forceRedraw
    
    // Inside your View
    var body: some View {
      VStack {
        // Your SwiftUI code...
      }
      .enableInjection()
    }
  10. Setup InjectionIII for iOS, tvOS, or visionOS physical devices

    main

    To enable hot reloading on a physical device (not a simulator), follow these steps:

    1. Unlock Device Mode: Run the following command on your Mac to allow device injection: defaults write com.johnholdsworth.InjectionIII deviceUnlock any (Then restart the InjectionIII.app on your Mac).

    2. Add Build Phase Script: In Xcode, add a new Run Script phase and disable User Script Sandboxing. Use the following script to copy the necessary bundle:

      RESOURCES=/Applications/InjectionIII.app/Contents/Resources
      if [ -f "$RESOURCES/copy_bundle.sh" ]; then
          "$RESOURCES/copy_bundle.sh"
      fi
    3. Load the Bundle in Code: Use the following logic to find and load the correct bundle at runtime:

      #if DEBUG
      if let path = Bundle.main.path(forResource: "iOSInjection", ofType: "bundle") ??
          Bundle.main.path(forResource: "macOSInjection", ofType: "bundle") {
          Bundle(path: path)!.load()
      }
      #endif
    $ defaults write com.johnholdsworth.InjectionIII deviceUnlock any
  11. Configure InjectionIII for Xcode 16.3+

    main

    Xcode 16.3+ no longer records the swift-frontend compiler calls in build logs by default, which InjectionIII needs to recompile files. To restore this functionality, add a User-Defined Setting to your project's Debug build configuration:

    • Setting Name: EMIT_FRONTEND_COMMAND_LINES
    • Value: YES