一个热重载工作流助手,无论你使用 UIKit
、AppKit
还是 SwiftUI
,都可以每周为你节省大量时间。
如果你想支持我的工作并改进你的工程工作流,请查看我的 SwiftyStack 课程。
TLDR:只需更改一行代码即可实时编码 UIKit
屏幕。
繁重的工作由出色的 InjectionIII 完成。 这个库只是一个简单的封装,旨在提供最佳的开发者体验,同时只需最小的努力。
我已经使用它很多年了。
热重载是一种技术,允许你摆脱编译整个应用程序,并尽可能避免部署/重启循环,同时允许你编辑正在运行的应用程序代码,并以尽可能接近实时的方式看到更改的反映。
这通过减少你花费在等待应用程序重建、重启、重新导航到应用程序本身之前所在的位置、重新生成所需数据的时间,来显著提高你的工作效率。
这可以为你节省每天的大量开发时间!
一旦你最初配置好你的项目,它几乎是免费的。
你不需要添加条件编译或从生产应用程序中删除 Inject
代码,它已经被设计为在非调试构建中,会被 LLVM 剥离的无操作内联代码。
这意味着你可以为每个视图启用一次,并继续使用它多年。
要集成 Inject
,只需将其添加为 SPM 依赖项
打开你的项目,点击 File → Swift Packages → Add Package Dependency…,输入仓库 URL (https://github.com/krzysztofzablocki/Inject.git
),并将包产品添加到你的应用程序目标。
dependencies: [
.package(
url: "https://github.com/krzysztofzablocki/Inject.git",
from: "1.2.4"
)
]
pod 'InjectHotReload'
如果你的项目中的任何人想要使用注入,他们只需要
/Applications
下/Applications/Xcode.app
在 Injection 应用程序中选择项目后,启动应用程序
💉 InjectionIII connected /Users/merowing/work/SourceryPro/App.xcworkspace
💉 Watching files under /Users/merowing/work/SourceryPro
你可以在项目中的单个文件中添加 import Inject
,或者在你的项目目标中使用 @_exported import Inject
,以使其在所有文件中自动可用。
只需 2 个步骤即可在你的 SwiftUI
视图中启用注入
.enableInjection()
@ObserveInjection var inject
添加到你的视图结构体记住,你完成之后不需要删除此代码,它在生产版本中是 NO-OP。
如果你想看到你的更改生效,你可以在 InjectConfiguration.animation
上启用一个可选的 Animation
变量,当有新的源代码注入到你的应用程序时,它将被使用。
InjectConfiguration.animation = .interactiveSpring()
Inject
的使用在这个 示例应用程序 中进行了演示
对于标准的命令式 UI 框架,我们需要一种方法来在代码注入阶段之间清理状态。
我创建了 Hosts 的概念,它在这种情况下运行良好,共有 2 个
ViewControllerHost
ViewHost
我们如何集成这个? 我们在父级别包装我们想要迭代的类,所以我们不会修改我们想要注入的类,而是修改父调用点。
例如,如果你有一个创建 PaneA
和 PaneB
的 SplitViewController
,并且你想在 PaneA
中迭代布局/逻辑代码,你修改 SplitViewController
中的调用点
paneA = Inject.ViewHost(
PaneAView(whatever: arguments, you: want)
)
这就是你需要做的所有更改,你的应用程序现在允许你更改 PaneAView
中的任何内容,除了其初始化器 API,并且更改几乎会立即反映在你的应用程序中。
确保在 Inject.ViewControllerHost(...)
或 Inject.ViewHost(...)
内部调用初始化器。 Inject 依赖于 @autoclosure
在热重载发生时重新加载视图。 例子
// WRONG
let viewController = YourViewController()
rootViewController.pushViewController(Inject.ViewControllerHost(viewController), animated: true)
// CORRECT
let viewController = Inject.ViewControllerHost(YourViewController())
rootViewController.pushViewController(viewController, animated: true)
记住,你完成之后不需要删除此代码,它在生产版本中是 NO-OP。
根据你的 UIKit 应用程序中使用的架构,你可能希望附加一个钩子,以便在每次重新加载视图控制器时执行。
例如,你可能希望每次重新加载时将 UIViewController
绑定到 presenter,为了实现这一点,你可以使用 onInjectionHook
。例子
myView.onInjectionHook = { hostedViewController in
//any thing here will be executed each time the controller is reloaded
// for example, you might want to re-assign the controller to your presenter
presenter.ui = hostedViewController
}
警告: 此脚本会自动修改你的 Swift 源代码。 它作为一种便利提供,但请谨慎使用! 仔细检查它所做的更改。 它可能不适用于所有项目或编码风格。 考虑使用 Xcode 代码片段进行更多手动控制。
要自动将 import Inject
、@ObserveInjection var inject
和 .enableInjection()
添加到你的 SwiftUI 视图,你可以将以下脚本作为 Xcode 项目中的“Run Script”构建阶段添加
#!/bin/bash
# Function to modify a single Swift file
modify_swift_file() {
local filepath="$1"
local filename=$(basename "$filepath")
local tempfile="$filepath.tmp"
# Check if the file should be processed
if [[ $(grep -c ": View {" "$filepath") -eq 0 ]]; then
echo "Skipping: $filename (No ': View {' found)"
return
fi
# Create a temporary file for modifications
cp "$filepath" "$tempfile"
# 1. Add import Inject if needed
if ! grep -q "import Inject" "$tempfile"; then
sed -i '' -e '/^import SwiftUI/a\
import Inject' "$tempfile"
fi
# 2. Add @ObserveInjection var inject if needed
if ! grep -q "@ObserveInjection var inject" "$tempfile"; then
sed -i '' -e '/struct.*: View {/a\
@ObserveInjection var inject' "$tempfile"
fi
# 3. Add .enableInjection() just before the closing brace of the body
# Find the start of var body: some View {
local body_start_line=$(grep -n "var body: some View {" "$tempfile" | cut -d ':' -f 1)
if [[ -n "$body_start_line" ]]; then
# Get the line number of the closing brace of the body
local body_end_line=$(awk -v start="$body_start_line" '
NR == start { count = 1 }
NR > start {
if ($0 ~ /{/) count++
if ($0 ~ /}/) {
count--
if (count == 0) {
print NR
exit
}
}
}
' "$tempfile")
if [[ -n "$body_end_line" ]]; then
# Check if .enableInjection() is already present
if ! grep -q ".enableInjection()" "$tempfile"; then
# Insert .enableInjection() before the closing brace of the body
sed -i '' -e "${body_end_line}i\\
.enableInjection()" "$tempfile"
fi
fi
fi
# Check if modifications were made and overwrite the original file
if ! cmp -s "$filepath" "$tempfile"; then
mv "$tempfile" "$filepath"
echo "Modified: $filename"
else
echo "No changes for: $filename"
fi
rm -f "$tempfile"
}
# Main script
find "$SRCROOT" -name "*.swift" -print0 | while IFS= read -r -d $'\0' filepath; do
modify_swift_file "$filepath"
done
echo "Inject modification script completed."
你需要将 -weak_framework SwiftUI 添加到 iOS 12 的 Other Linker Flags 中才能工作。
自从引入 ReducerProtocol 之后,你可以在没有支持代码的情况下将 Inject 与 TCA 一起使用。