- Why use?
- cursor自动代码+实时预览,来来回回运行调试的节省时间成本!
InjectIII
xcode录路径不能含有特殊字符(例如中文),否则不能正常热更新。
InjectIII 需要下载新版,app store 上的往往很旧
Injection 能够让你在 iOS 模拟器、真机、Arm 芯片 Mac 直接运行的 iOS app 上无需重新构建或者重启你的 app 就实现更新 class 的实现、方法,添加 struct 或者 enum。节省开发者大量调试代码和设计迭代的时间。它把 Xcode 的职责从“源代码编辑器”变成“程序编辑器”,源码的修改不再仅仅保存在磁盘中而是会直接注入到运行时的程序中。
如何使用
你可以在 github releases 下载最新的 app 也可以选择通过 Mac App Store 下载,然后你需要把下面这些代码添加到你的工程中并且在 app 启动时执行它(比如在 didFinishLaunchingWithOptions 的时候),这些配置工作就完成了。
#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
#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
另外一个非常重要的事情是添加 -Xlinker
and -interposable
这两个参数到 "Other Linker Flags" 你的工程文件中(注意只修改 Debug
配置如下图所示)
配置完成以后,当 app 运行起来以后控制台会输出一条关于文件监视器监听目录的消息,当前工程中包含的源文件保存的同时它也会同时被注入到您的设备中。所有旧的代码实现都会被替换为最新的代码实现。
通常来说,你想要在屏幕中立刻看到最新的效果,可能需要让某些函数被重新调用一次。比如你在 view controller 中注入了代码,想要让它被重新渲染。你可以实现 @objc func injected()
方法,这个函数将会被框架自动调用。在项目中使用可以参考下面这个样例代码:
#if DEBUG
extension UIViewController {
@objc func injected() {
viewDidLoad()
}
}
#endif
#if DEBUG
extension UIViewController {
@objc func injected() {
viewDidLoad()
}
}
#endif
Tip
根据笔者测试。 第一次启动xcode项目和InjectIII的时候,会要求选择监听的代码目录或者.xcodeproj
。
另外有哪些限制:
你不能修改数据在内存中的布局,比如你不能添加、删除、排序属性。对于非最终类(non-final classes),增加或删除方法也是不能工作的,因为用于分派的虚表(vtable)本身就是一种数据结构,不能靠 Injection 修改。Injection 也无法判断哪些代码段需要重新执行以更新显示,如上所述你需要自己判断。此外,不要过度使用访问控制。私有属性和方法不能直接被注入,特别是在扩展中,因为它们不是全局可替换的符号。它们通常通过间接方式进行注入,因为它们只能在被注入的文件内部访问,但这可能会引起混淆。最后,代码注入的同时又在对源文件执行添加、重命名或删除的操作可能会出问题。您可能需要重新构建并重新启动您的应用程序,甚至关闭并重新打开您的项目以清除旧的 Xcode 构建日志。
如何监听SwiftUI文件
SwiftUI 比 UIKit 更适合注入,因为它有特定的机制来更新显示,但你需要对每个想要注入的 View
结构体做一些修改。为了强制重新绘制,最简单的方法是添加一个属性来观察注入何时发生:
@ObserveInjection var forceRedraw
@ObserveInjection var forceRedraw
这个属性包装器可以在 HotSwiftUI 或 Inject Swift 包(SMP)中找到。它实际上就是包含了一个 @Published 整数,视图可以观察这个整数,它会在每次注入时递增。您可以使用以下任一方法保证相关的代码在整个项目中可用:
@_exported import HotSwiftUI
or
@_exported import Inject
@_exported import HotSwiftUI
or
@_exported import Inject
让 SwiftUI 注入所需的第二个更改是调用 View 的 .enableInjection()
方法将 body 属性的返回类型“擦除”为 AnyView
——这个技巧叫做"erase the return type"。这是因为,在添加或删除 SwiftUI 元素时,body 属性的具体返回类型可能会发生变化,这相当于内存布局的更改,可能会导致崩溃。总的来说,每个 body 的末尾都应该看起来像这样:
var body: some View {
VStack{
// Your SwiftUI code...
}
.enableInjection()
}
@ObserveInjection var redraw
var body: some View {
VStack{
// Your SwiftUI code...
}
.enableInjection()
}
@ObserveInjection var redraw
实践:
改造前修改SwiftUI文件时,模拟器不会实时更新。 让我们开始整改。
以Inject
https://github.com/krzysztofzablocki/Inject 举例:
1). 安装依赖
// SPM:
dependencies: [
.package(
url: "https://github.com/krzysztofzablocki/Inject.git",
from: "1.2.4"
)
]
// CocoaPods:
pod 'InjectHotReload'
// SPM:
dependencies: [
.package(
url: "https://github.com/krzysztofzablocki/Inject.git",
from: "1.2.4"
)
]
// CocoaPods:
pod 'InjectHotReload'
2). 调整代码
// 局部引入
import Inject
// or 全局引入
// @_exported import Inject
struct HelloView: View {
@ObserveInjection var inject
var body: some View {
Text("Hello, World!!!")
.enableInjection()
}
}
// 局部引入
import Inject
// or 全局引入
// @_exported import Inject
struct HelloView: View {
@ObserveInjection var inject
var body: some View {
Text("Hello, World!!!")
.enableInjection()
}
}
完成!