InjectionIII

InjectionIII

Swift代码实时注入工具 提升开发效率和调试体验

InjectionIII是一个面向iOS、tvOS和macOS平台的Swift代码注入工具。它能够在应用运行时更新函数和方法的实现,无需重新构建或重启应用。这种方式显著提高了开发效率,使Xcode具备了实时程序编辑能力。InjectionIII支持在模拟器和真机上进行代码热重载,并兼容SwiftUI的实时更新。此工具还提供了详细的调试功能,可有效提升Swift开发过程中的生产力。

InjectionIIISwift热重载代码注入iOS开发Github开源项目

InjectionIII.app Project

Yes, HotReloading for Swift

Chinese language README: 中文集成指南

Icon

Code injection allows you to update the implementation of functions and any method of a class, struct or enum incrementally in the iOS simulator without 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 "source editor" to being a "program editor" where source changes are not just saved to disk but into your running program directly.

How to use it

Setting up your projects to use injection is now as simple as downloading one of the github releases of the app or from the Mac App Store and adding the code below somewhere in your app to be executed on startup (it is no longer necessary to actually run the app itself).

#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

It's also important to add the options -Xlinker and -interposable (without double quotes and on separate lines) to the "Other Linker Flags" of targets in your project (for the Debug configuration only) to enable "interposing" (see the explanation below).

Icon

After that, when you run your app in the simulator you should see a message saying a file watcher has started for your home directory and, whenever you save a source file in the current project it should report it has been injected. This means all places that formerly called the old implementation will have been updated to call the latest version of your code.

It's not quite as simple as that as to see results on the screen immediately the new code needs to have actually been called. For example, if you inject a view controller it needs to force a redisplay. To resolve this problem, classes can implement an @objc func injected() method which will be called after the class has been injected to perform any update to the display. One technique you can use is to include the following code somewhere in your program:

#if DEBUG extension UIViewController { @objc func injected() { viewDidLoad() } } #endif

Another solution to this problem is "hosting" using the Inject Swift Package introduced by this blog post.

What injection can't do

You can't inject changes to how data is laid out in memory i.e. you cannot add, remove or reorder properties with storage. For non-final classes this also applies to adding or removing methods as the vtable used for dispatch is itself a data structure which must not change over injection. Injection also can't work out what pieces of code need to be re-executed to update the display as discussed above. Also, don't get carried away with access control. private properties and methods can't be injected directly, particularly in extensions as they are not a global interposable symbol. They generally inject indirectly as they can only be acessed inside the file being injected but this can cause confusion. Finally, Injection doesn't cope well with source files being added/renamed/deleted during injection. You may need to build and relaunch your app or even close and reopen your project to clear out old Xcode build logs.

Injection of SwiftUI

SwiftUI is, if anything, better suited to injection than UIKit as it has specific mechanisms to update the display but you need to make a couple changes to each View struct you want to inject. To force redraw the simplest way is to add a property that observes when an injection has occurred:

    @ObserveInjection var forceRedraw

This property wrapper is available in either the HotSwiftUI or Inject Swift Package. It essentially contains an @Published integer your views observe that increments with each injection. You can use one of the following to make one of these packages available throughout your project:

@_exported import HotSwiftUI
or
@_exported import Inject

The second change you need to make for reliable SwiftUI injection is to "erase the return type" of the body property by wrapping it in AnyView using the .enableInjection() method extending View in these packages. This is because, as you add or remove SwiftUI elements it can change the concrete return type of the body property which amounts to a memory layout change that may crash. In summary, the tail end of each body should always look like this:

    var body: some View {
    	 VStack or whatever {
        // Your SwiftUI code...
        }
        .enableInjection()
    }

    @ObserveInjection var redraw

You can leave these modifications in your production code as, for a Release build they optimise out to a no-op.

Injection on an iOS, tvOS or visionOS device

This can work but you will need to actually run one of the github 4.8.0+ releases of the InjectionIII.app, set a user default to opt-in and restart the app.

$ defaults write com.johnholdsworth.InjectionIII deviceUnlock any

Then, instead of loading the injection bundles run this script in a "Build Phase": (You will also need to turn off the project build setting "User Script Sandboxing")

RESOURCES=/Applications/InjectionIII.app/Contents/Resources
if [ -f "$RESOURCES/copy_bundle.sh" ]; then
    "$RESOURCES/copy_bundle.sh"
fi

and, in your application execute the following code on startup:

    #if DEBUG
    if let path = Bundle.main.path(forResource:
            "iOSInjection", ofType: "bundle") ??
        Bundle.main.path(forResource:
            "macOSInjection", ofType: "bundle") {
        Bundle(path: path)!.load()
    }
    #endif

Once you have switched to this configuaration it will also work when using the simulator. Consult the README of the HotReloading project for details on how to debug having your program connect to the InjectionIII.app over Wi-Fi. You will also need to select the project directory for the file watcher manually from the pop-down menu.

Injection on macOS

It works but you need to temporarily turn off the "app sandbox" and "library validation" under the "hardened runtime" during development so it can dynamically load code. In order to avoid codesigning problems, use the new copy_bundle.sh script as detailed in the instructions for injection on real devices above.

How it works

Injection has worked various ways over the years, starting out using the "Swizzling" apis for Objective-C but is now largely built around a feature of Apple's linker called "interposing" which provides a solution for any Swift method or computed property of any type.

When your code calls a function in Swift, it is generally "statically dispatched", i.e. linked using the "mangled symbol" of the function being called. Whenever you link your application with the "-interposable" option however, an additional level of indirection is added where it finds the address of all functions being called through a section of writable memory. Using the operating system's ability to load executable code and the fishhook library to "rebind" the call it is therefore possible to "interpose" new implementations of any function and effectively stitch them into the rest of your program at runtime. From that point it will perform as if the new code had been built into the program.

Injection uses the FSEventSteam api to watch for when a source file has been changed and scans the last Xcode build log for how to recompile it and links a dynamic library that can be loaded into your program. Runtime support for injection then loads the dynamic library and scans it for the function definitions it contains which it then "interposes" into the rest of the program. This isn't the full story as the dispatch of non-final class methods uses a "vtable" (think C++ virtual methods) which also has to be updated but the project looks after that along with any legacy Objective-C "swizzling".

If you are interested knowing more about how injection works the best source is either my book Swift Secrets or the new, start-over reference implementation in the InjectionLite Swift Package. For more information about "interposing" consult this blog post or the README of the fishhook project. For more information about the organisation of the app itself, consult ROADMAP.md.

A bit of terminology

Getting injection to work has three components. A FileWatcher, the code to recompile any changed files and build a dynamic library that can be loaded and the injection code itself which stitches the new versions of your code into the app while it's running. How these three components are combined gives rise to the number of ways injection can be used.

"Injection classic" is where you download one of the binary releases from github and run the InjectionIII.app. You then load one of the bundles inside that app into your program as shown above in the simulator. In this configuration, the file watcher and source recompiling is done inside the app and the bundle connects to the app using a socket to know when a new dynamic library is ready to be loaded.

"App Store injection" This version of the app is sandboxed and while the file watcher still runs inside the app, the recompiling and loading is delegated to be performed inside the simulator. This can create problems with C header files as the simulator uses a case sensitive file system to be a faithful simulation of a real device.

"HotReloading injection" was where you are running your app on a device and because you cannot load a bundle off your Mac's filesystem on a real phone you add the HotReloading Swift Package to your project (during development only!) which contains all the code that would normally be in the bundle to perform the dynamic loading. This requires that you use one of the un-sandboxed binary releases. It has also been replaced by the copy_bundle.sh script described above.

"Standalone injection". This was the most recent evolution of the project where you don't run the app itself anymore but simply load one of the injection bundles and the file watcher, re-compilation and injection are all performed inside the simulator. By default this watches for changes to any Swift file inside your home directory though you can change this using the environment variable INJECTION_DIRECTORIES.

InjectionLite is a start-over minimal implementation of standalone injection for reference. Just add this Swift package and you should be able to inject in the simulator.

InjectionNext is a currently experimental version of Injection that should be faster and more reliable for large projects. It integrates into a debugging flag of Xcode to find out how to recompile files to avoid parsing build logs.

All these variations require you to add the "-Xlinker -interposble" linker flags for a Debug build or you will only be able to inject non-final methods of classes.

Further information

Consult the old README which if anything contained simply "too much information" including the various environment variables you can use for customisation. A few examples:

Environment var.Purpose
INJECTION_DETAILVerbose output of all actions performed
INJECTION_TRACELog calls to injected functions (v4.6.6+)
INJECTION_HOSTMac's IP address for on-device injection

With an INJECTION_TRACE environment variable, injecting any file will add logging of all calls to functions and methods in the file along with their argument values as an aid to debugging.

A little known feature of InjectionIII is that provided you have run the tests for your app at some point you can inject an individual XCTest class and have if run immediately – reporting if it has failed each time you modify it.

Acknowledgements:

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

The App Tracing functionality uses the OliverLetterer/imp_implementationForwardingToSelector trampoline implementation via the SwiftTrace project under an MIT license.

SwiftTrace uses the very handy https://github.com/facebook/fishhook. See the project source and header file included in the app bundle for licensing details.

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 coloured (line 66 and 303) in "canviz-0.1/canviz.js".

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

The fabulous app icon is thanks to Katya of pixel-mixer.com.

$Date: 2024/06/30

编辑推荐精选

TRAE编程

TRAE编程

AI辅助编程,代码自动修复

Trae是一种自适应的集成开发环境(IDE),通过自动化和多元协作改变开发流程。利用Trae,团队能够更快速、精确地编写和部署代码,从而提高编程效率和项目交付速度。Trae具备上下文感知和代码自动完成功能,是提升开发效率的理想工具。

AI工具TraeAI IDE协作生产力转型热门
蛙蛙写作

蛙蛙写作

AI小说写作助手,一站式润色、改写、扩写

蛙蛙写作—国内先进的AI写作平台,涵盖小说、学术、社交媒体等多场景。提供续写、改写、润色等功能,助力创作者高效优化写作流程。界面简洁,功能全面,适合各类写作者提升内容品质和工作效率。

AI辅助写作AI工具蛙蛙写作AI写作工具学术助手办公助手营销助手AI助手
问小白

问小白

全能AI智能助手,随时解答生活与工作的多样问题

问小白,由元石科技研发的AI智能助手,快速准确地解答各种生活和工作问题,包括但不限于搜索、规划和社交互动,帮助用户在日常生活中提高效率,轻松管理个人事务。

热门AI助手AI对话AI工具聊天机器人
Transly

Transly

实时语音翻译/同声传译工具

Transly是一个多场景的AI大语言模型驱动的同声传译、专业翻译助手,它拥有超精准的音频识别翻译能力,几乎零延迟的使用体验和支持多国语言可以让你带它走遍全球,无论你是留学生、商务人士、韩剧美剧爱好者,还是出国游玩、多国会议、跨国追星等等,都可以满足你所有需要同传的场景需求,线上线下通用,扫除语言障碍,让全世界的语言交流不再有国界。

讯飞智文

讯飞智文

一键生成PPT和Word,让学习生活更轻松

讯飞智文是一个利用 AI 技术的项目,能够帮助用户生成 PPT 以及各类文档。无论是商业领域的市场分析报告、年度目标制定,还是学生群体的职业生涯规划、实习避坑指南,亦或是活动策划、旅游攻略等内容,它都能提供支持,帮助用户精准表达,轻松呈现各种信息。

AI办公办公工具AI工具讯飞智文AI在线生成PPTAI撰写助手多语种文档生成AI自动配图热门
讯飞星火

讯飞星火

深度推理能力全新升级,全面对标OpenAI o1

科大讯飞的星火大模型,支持语言理解、知识问答和文本创作等多功能,适用于多种文件和业务场景,提升办公和日常生活的效率。讯飞星火是一个提供丰富智能服务的平台,涵盖科技资讯、图像创作、写作辅助、编程解答、科研文献解读等功能,能为不同需求的用户提供便捷高效的帮助,助力用户轻松获取信息、解决问题,满足多样化使用场景。

热门AI开发模型训练AI工具讯飞星火大模型智能问答内容创作多语种支持智慧生活
Spark-TTS

Spark-TTS

一种基于大语言模型的高效单流解耦语音令牌文本到语音合成模型

Spark-TTS 是一个基于 PyTorch 的开源文本到语音合成项目,由多个知名机构联合参与。该项目提供了高效的 LLM(大语言模型)驱动的语音合成方案,支持语音克隆和语音创建功能,可通过命令行界面(CLI)和 Web UI 两种方式使用。用户可以根据需求调整语音的性别、音高、速度等参数,生成高质量的语音。该项目适用于多种场景,如有声读物制作、智能语音助手开发等。

咔片PPT

咔片PPT

AI助力,做PPT更简单!

咔片是一款轻量化在线演示设计工具,借助 AI 技术,实现从内容生成到智能设计的一站式 PPT 制作服务。支持多种文档格式导入生成 PPT,提供海量模板、智能美化、素材替换等功能,适用于销售、教师、学生等各类人群,能高效制作出高品质 PPT,满足不同场景演示需求。

讯飞绘文

讯飞绘文

选题、配图、成文,一站式创作,让内容运营更高效

讯飞绘文,一个AI集成平台,支持写作、选题、配图、排版和发布。高效生成适用于各类媒体的定制内容,加速品牌传播,提升内容营销效果。

热门AI辅助写作AI工具讯飞绘文内容运营AI创作个性化文章多平台分发AI助手
材料星

材料星

专业的AI公文写作平台,公文写作神器

AI 材料星,专业的 AI 公文写作辅助平台,为体制内工作人员提供高效的公文写作解决方案。拥有海量公文文库、9 大核心 AI 功能,支持 30 + 文稿类型生成,助力快速完成领导讲话、工作总结、述职报告等材料,提升办公效率,是体制打工人的得力写作神器。

下拉加载更多