{"id":13387597,"url":"https://github.com/johnno1962/injectioniii","last_synced_at":"2025-05-13T20:22:42.246Z","repository":{"id":37405662,"uuid":"109531717","full_name":"johnno1962/InjectionIII","owner":"johnno1962","description":"Re-write of Injection for Xcode in (mostly) Swift","archived":false,"fork":false,"pushed_at":"2025-04-18T12:56:46.000Z","size":1897,"stargazers_count":4289,"open_issues_count":6,"forks_count":334,"subscribers_count":40,"default_branch":"main","last_synced_at":"2025-04-19T01:56:03.922Z","etag":null,"topics":["eval","hot-reload","injection","ios-simulator","macos","remote-control","reverse-engineering","storyboard-injection","swift","xcode"],"latest_commit_sha":null,"homepage":"","language":"Objective-C","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/johnno1962.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":"johnno1962","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2017-11-04T21:31:08.000Z","updated_at":"2025-04-18T12:56:50.000Z","dependencies_parsed_at":"2024-01-18T22:34:37.668Z","dependency_job_id":"637ed38b-ead9-43be-b524-7fe564d088c6","html_url":"https://github.com/johnno1962/InjectionIII","commit_stats":{"total_commits":423,"total_committers":13,"mean_commits":32.53846153846154,"dds":0.08510638297872342,"last_synced_commit":"7effd5c93d9dbc305f0327f792cd246367671fb0"},"previous_names":[],"tags_count":255,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnno1962%2FInjectionIII","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnno1962%2FInjectionIII/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnno1962%2FInjectionIII/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/johnno1962%2FInjectionIII/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/johnno1962","download_url":"https://codeload.github.com/johnno1962/InjectionIII/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":251325890,"owners_count":21571621,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["eval","hot-reload","injection","ios-simulator","macos","remote-control","reverse-engineering","storyboard-injection","swift","xcode"],"created_at":"2024-07-30T12:01:24.700Z","updated_at":"2025-05-13T20:22:42.240Z","avatar_url":"https://github.com/johnno1962.png","language":"Objective-C","readme":"# InjectionIII.app Project\n\n## Yes, HotReloading for Swift \n\nChinese language README:  [中文集成指南](https://github.com/johnno1962/InjectionIII/blob/main/README_Chinese.md)\n\n![Icon](http://johnholdsworth.com/Syringe_128.png)\n\nCode injection allows you to update the implementation of functions and any method of a class, struct or enum incrementally in the iOS simulator\nwithout having to perform a full rebuild or restart your application. This saves the developer a significant amount of time tweaking code or iterating over a design. Effectively it changes Xcode from being a\n\"source editor\" to being a _\"program editor\"_ where source changes are \nnot just saved to disk but into your running program directly.\n\n### Stop Press: Injection and Xcode 16.3\n\nInjectionIII works by recompiling edited source files into a dynamic library\nwhich is then loaded into your app. It determines how to recompile the file\nby searching the most recent Xcode build logs for the `swift-frontend` \ncompiler invocation. Unfortunately, after this having worked for 10 \nyears Xcode 16.3 no longer logs this information by default though it \nwill if you use \"Editor/Add Build Setting/Add User-Defined Setting\"\nto add a value for `EMIT_FRONTEND_COMMAND_LINES` (set to \"YES\") to your project's\n`Debug` build settings, then InjectionIII can continue to work as before.\n\n### How to use it\n\nSetting up your projects to use injection is now as simple as downloading\none of the [github\nreleases](https://github.com/johnno1962/InjectionIII/releases) of the app \nor from the [Mac App Store](https://itunes.apple.com/app/injectioniii/id1380446739?mt=12) \nand adding the code below somewhere in your app to be executed on\nstartup (it is no longer necessary to actually run the app itself).\n\n```Swift\n#if DEBUG\nBundle(path: \"/Applications/InjectionIII.app/Contents/Resources/iOSInjection.bundle\")?.load()\n//for tvOS:\nBundle(path: \"/Applications/InjectionIII.app/Contents/Resources/tvOSInjection.bundle\")?.load()\n//Or for macOS:\nBundle(path: \"/Applications/InjectionIII.app/Contents/Resources/macOSInjection.bundle\")?.load()\n#endif\n```\n It's also important to add the options `-Xlinker` and `-interposable` (without double\n quotes and on separate lines) to the \"Other Linker Flags\" of targets in your project \n(for the `Debug` configuration only) to enable \"interposing\" (see the explanation below).\n\n![Icon](interposable.png)\n\nAfter that, when you run your app in the simulator you should see a \nmessage saying a file watcher has started for your home directory \nand, whenever you save a source file in the current project it should \nreport it has been injected. This means all places that formerly \ncalled the old implementation will have been updated to call the \nlatest version of your code.\n\nIt's not quite as simple as that as to see results on the screen\nimmediately the new code needs to have actually been called.\nFor example, if you inject a view controller it needs to force a\nredisplay. To resolve this problem, classes can implement an \n`@objc func injected()` method which will be called after the \nclass has been injected to perform any update to the display. \nOne technique you can use is to include the following code \nsomewhere in your program:\n\n```Swift\n#if DEBUG\nextension UIViewController {\n    @objc func injected() {\n        viewDidLoad()\n    }\n}\n#endif\n```\nAnother solution to this problem is \"hosting\" using the \n[Inject](https://github.com/krzysztofzablocki/Inject)\nSwift Package introduced by this \n[blog post](https://merowing.info/2022/04/hot-reloading-in-swift/).\n\n### What injection can't do\n\nYou can't inject changes to how data is laid out in memory i.e.\nyou cannot add, remove or reorder properties with storage. \nFor non-final classes this also applies to adding\nor removing methods as the `vtable` used for dispatch is \nitself a data structure which must not change over injection.\nInjection also can't work out what pieces of code need to\nbe re-executed to update the display as discussed above.\nAlso, don't get carried away with access control. `private`\nproperties and methods can't be injected directly, particularly \nin extensions as they are not a `global` interposable symbol. \nThey generally inject indirectly as they can only be accessed \ninside the file being injected but this can cause confusion.\nFinally, Injection doesn't cope well with source files being \nadded/renamed/deleted during injection. You may need to \nbuild and relaunch your app or even close and reopen\nyour project to clear out old Xcode build logs.\n\n### Injection of SwiftUI\n\nSwiftUI is, if anything, better suited to injection than UIKit\nas it has specific mechanisms to update the display but you need\nto make a couple changes to each \t`View` struct you want to inject.\nTo force redraw the simplest way is to add a property that\nobserves when an injection has occurred:\n\n```swift\n@ObserveInjection var forceRedraw\n```\nThis property wrapper is available in either the \n[HotSwiftUI](https://github.com/johnno1962/HotSwiftUI) or\n[Inject](https://github.com/krzysztofzablocki/Inject)\nSwift Package. It essentially contains an `@Published` \ninteger your views observe that increments with each \ninjection. You can use one of the following to make one\nof these packages available throughout your project:\n\n```swift\n@_exported import HotSwiftUI\nor\n@_exported import Inject\n```\nThe second change you need to make for reliable SwiftUI\ninjection is to \"erase the return type\" of the body property\nby wrapping it in `AnyView` using the `.enableInjection()` \nmethod extending `View` in these packages. This is because, \nas you add or remove SwiftUI elements it can change the concrete \nreturn type of the body property which amounts to a memory layout \nchange that may crash. In summary, the tail end of each body should\nalways look like this:\n\n```swift\nvar body: some View {\n  VStack or whatever {\n    // Your SwiftUI code...\n    }\n    .enableInjection()\n}\n\n@ObserveInjection var redraw\n```\nYou can leave these modifications in your production code as, \nfor a `Release` build they optimise out to a no-op.\n\n#### Xcode 16\n\nNew in Xcode 16 is `SWIFT_ENABLE_OPAQUE_TYPE_ERASURE` build setting. \nThis setting is turned ON by default and you don't need to erase view\nbody explicitly. You'll still need to `@ObserveInjection` to force redraws.\n\nFor more info, see [Xcode 16.2 release notes](https://developer.apple.com/documentation/xcode-release-notes/xcode-16_2-release-notes).\n\n### Injection on an iOS, tvOS or visionOS device\n\nThis can work but you will need to actually run one of the [github \n4.8.0+ releases](https://github.com/johnno1962/InjectionIII/releases) \nof the InjectionIII.app, set a user default to opt-in and restart the app.\n\n```\n$ defaults write com.johnholdsworth.InjectionIII deviceUnlock any\n```\nThen, instead of loading the injection bundles run this script in a \"Build Phase\":\n(You will also need to turn off the project build setting \"User Script Sandboxing\")\n\n```\nRESOURCES=/Applications/InjectionIII.app/Contents/Resources\nif [ -f \"$RESOURCES/copy_bundle.sh\" ]; then\n    \"$RESOURCES/copy_bundle.sh\"\nfi\n```\nand, in your application execute the following code on startup:\n\n```swift\n#if DEBUG\nif let path = Bundle.main.path(forResource:\n        \"iOSInjection\", ofType: \"bundle\") ??\n    Bundle.main.path(forResource:\n        \"macOSInjection\", ofType: \"bundle\") {\n    Bundle(path: path)!.load()\n}\n#endif\n```\nOnce you have switched to this configuration it will also\nwork when using the simulator. Consult the README of the\n[HotReloading project](https://github.com/johnno1962/HotReloading) \nfor details on how to debug having your program connect to the \nInjectionIII.app over Wi-Fi. You will also need to select the project \ndirectory for the file watcher manually from the pop-down menu.\n\n### Injection on macOS\n\nIt works but you need to temporarily turn off the \"app sandbox\" and\n\"library validation\" under the \"hardened runtime\" during development \nso it can dynamically load code. In order to avoid codesigning problems,\nuse the new `copy_bundle.sh` script as detailed in the instructions for\ninjection on real devices above.\n\n### How it works\n\nInjection has worked various ways over the years, starting out using \nthe \"Swizzling\" apis for Objective-C but is now largely built around \na feature of Apple's linker called \"interposing\" which provides a \nsolution for any Swift method or computed property of any type.\n\nWhen your code calls a function in Swift, it is generally \"statically\ndispatched\", i.e. linked using the \"mangled symbol\" of the function being called.\nWhenever you link your application with the \"-interposable\" option\nhowever, an additional level of indirection is added where it finds \nthe address of all functions being called through a section of \nwritable memory. Using the operating system's ability to load \nexecutable code and the [fishhook](https://github.com/facebook/fishhook) \nlibrary to \"rebind\" the call it is therefore possible to \"interpose\"\nnew implementations of any function and effectively stitch \nthem into the rest of your program at runtime. From that point it will \nperform as if the new code had been built into the program. \n\nInjection uses the `FSEventSteam` api to watch for when a source\nfile has been changed and scans the last Xcode build log for how to\nrecompile it and links a dynamic library that can be loaded into your\nprogram. Runtime support for injection then loads the dynamic library \nand scans it for the function definitions it contains which it then\n\"interposes\" into the rest of the program. This isn't the full story as\nthe dispatch of non-final class methods uses a \"vtable\" (think C++ \nvirtual methods) which also has to be updated but the project looks \nafter that along with any legacy Objective-C \"swizzling\".\n\nIf you are interested knowing more about how injection works\nthe best source is either my book [Swift Secrets](http://books.apple.com/us/book/id1551005489) or the new, start-over reference implementation\nin the [InjectionLite](https://github.com/johnno1962/InjectionLite) \nSwift Package. For more information about \"interposing\" consult [this\nblog post](https://www.mikeash.com/pyblog/friday-qa-2012-11-09-dyld-dynamic-linking-on-os-x.html) \nor the README of the [fishhook project](https://github.com/facebook/fishhook). \nFor more information about the organisation of the app itself, consult [ROADMAP.md](https://github.com/johnno1962/InjectionIII/blob/main/ROADMAP.md).\n\n### A bit of terminology\n\nGetting injection to work has three components. A FileWatcher, the code to\nrecompile any changed files and build a dynamic library that can be loaded \nand the injection code itself which stitches the new versions of your code\ninto the app while it's running. How these three components are combined\ngives rise to the number of ways injection can be used.\n\n\"Injection classic\" is where you download one of the [binary releases](https://github.com/johnno1962/InjectionIII/releases)\nfrom github and run the InjectionIII.app. You then load one of the bundles\ninside that app into your program as shown above in the simulator. \nIn this configuration, the file watcher and source recompiling is done \ninside the app and the bundle connects to the app using a socket to \nknow when a new dynamic library is ready to be loaded.\n\n\"App Store injection\" This version of the app is sandboxed and while\nthe file watcher still runs inside the app, the recompiling and loading\nis delegated to be performed inside the simulator. This can create \nproblems with C header files as the simulator uses a case sensitive \nfile system to be a faithful simulation of a real device.\n\n\"HotReloading injection\" was where you are running your app on a device\nand because you cannot load a bundle off your Mac's filesystem on a real \nphone you add the [HotReloading Swift Package](https://github.com/johnno1962/HotReloading)\nto your project (during development only!) which contains all the code that\nwould normally be in the bundle to perform the dynamic loading. This \nrequires that you use one of the un-sandboxed binary releases. It has\nalso been replaced by the `copy_bundle.sh` script described above.\n\n\"Standalone injection\". This was the most recent evolution of the project \nwhere you don't run the app itself anymore but simply load one of the \ninjection bundles and the file watcher, re-compilation and injection are \nall performed inside the simulator. By default this watches for changes \nto any Swift file inside your home directory though you can change this\nusing the environment variable `INJECTION_DIRECTORIES`.\n\n[InjectionLite](https://github.com/johnno1962/InjectionLite) is a start-over\nminimal implementation of standalone injection for reference. Just add\nthis Swift package and you should be able to inject in the simulator.\n\n[InjectionNext](https://github.com/johnno1962/InjectionNext) is a \ncurrently experimental version of Injection that should be faster and \nmore reliable for large projects. It integrates into a debugging flag of \nXcode to find out how to recompile files to avoid parsing build logs\nand re-uses the client implementation of injection from `InjectionLite`.\nTo use with external editors such as `Cursor`, InjectionNext can also\nuse a file watcher to detect edits and fall back to build log parsing code.\n\nAll these variations require you to add the \"-Xlinker -interposble\" linker flags \nfor a Debug build or you will only be able to inject non-final methods of classes\nand all can be used in conjunction with either of the higher level \n[Inject](https://github.com/krzysztofzablocki/Inject) or\n[HotSwiftUI](https://github.com/johnno1962/HotSwiftUI).\n\n### Further information\n\nConsult the [old README](https://github.com/johnno1962/InjectionIII/blob/main/OLDME.md) which if anything contained \nsimply \"too much information\" including the various environment\nvariables you can use for customisation. A few examples:\n\n| Environment var. | Purpose |\n| ------------- | ------------- |\n| **INJECTION_DETAIL** | Verbose output of all actions performed |\n| **INJECTION_TRACE** | Log calls to injected functions (v4.6.6+) |\n| **INJECTION_HOST** | Mac's IP address for on-device injection |\n\nWith an **INJECTION_TRACE** environment variable, injecting \nany file will add logging of all calls to functions and methods in\nthe file along with their argument values as an aid to debugging.\n\nA little known feature of InjectionIII is that provided you have \nrun the tests for your app at some point you can inject an \nindividual XCTest class and have if run immediately – \nreporting if it has failed each time you modify it.\n\n### Acknowledgements:\n\nThis project includes code from [rentzsch/mach_inject](https://github.com/rentzsch/mach_inject),\n[erwanb/MachInjectSample](https://github.com/erwanb/MachInjectSample),\n[davedelong/DDHotKey](https://github.com/davedelong/DDHotKey) and\n[acj/TimeLapseBuilder-Swift](https://github.com/acj/TimeLapseBuilder-Swift) under their\nrespective licenses.\n\nThe App Tracing functionality uses the [OliverLetterer/imp_implementationForwardingToSelector](https://github.com/OliverLetterer/imp_implementationForwardingToSelector) trampoline implementation via the [SwiftTrace](https://github.com/johnno1962/SwiftTrace) project under an MIT license.\n\nSwiftTrace uses the very handy [https://github.com/facebook/fishhook](https://github.com/facebook/fishhook).\nSee the project source and header file included in the app bundle\nfor licensing details.\n\nThis release includes a very slightly modified version of the excellent\n[canviz](https://code.google.com/p/canviz/) library to render \"dot\" files\nin an HTML canvas which is subject to an MIT license. The changes are to pass\nthrough the ID of the node to the node label tag (line 212), to reverse\nthe rendering of nodes and the lines linking them (line 406) and to\nstore edge paths so they can be coloured (line 66 and 303) in \"canviz-0.1/canviz.js\".\n\nIt also includes [CodeMirror](http://codemirror.net/) JavaScript editor\nfor the code to be evaluated using injection under an MIT license.\n\nThe fabulous app icon is thanks to Katya of [pixel-mixer.com](http://pixel-mixer.com/).\n\n$Date: 2025/04/07 $\n","funding_links":["https://github.com/sponsors/johnno1962"],"categories":["\u003ca id=\"977cef2fc942ac125fa395254ab70eea\"\u003e\u003c/a\u003eXCode"],"sub_categories":["\u003ca id=\"7037d96c1017978276cb920f65be2297\"\u003e\u003c/a\u003e工具"],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnno1962%2Finjectioniii","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjohnno1962%2Finjectioniii","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjohnno1962%2Finjectioniii/lists"}