如何修复 SwiftUI Shape 的 Animatable 主线程隔离错误?
结论
这段代码中的动画形状和向量运算可能在非主线程执行,因此不能依赖 MainActor 隔离状态。除了将 MoodBodyShape 声明为 nonisolated,还需要让 AnimatableRadiiVector 的 VectorArithmetic 实现及其使用的 ShapeCalculator 脱离默认的 MainActor 隔离。
SwiftUI 说明,Shape 的路径计算可能在后台线程进行,动画中间值也会经过 VectorArithmetic 运算。Swift 6.2 默认将未显式标记的声明隔离到 MainActor。Apple 的 SwiftUI 并发说明;Swift 编译器的隔离诊断说明
修改纯计算类型的隔离
先将向量类型和形状计算器标记为 nonisolated。它们只保存数值并执行纯计算,无需访问 UI 状态:
nonisolated struct ShapeCalculator {
static let points = 100
// calculateRadii、calculatePolygon、calculateCircle、
// calculateFlower 和 smoothArray 保持原实现
}
nonisolated struct AnimatableRadiiVector: VectorArithmetic, Sendable {
var values: [Double]
static var zero: AnimatableRadiiVector {
AnimatableRadiiVector(
values: Array(repeating: 0, count: ShapeCalculator.points)
)
}
static func + (
lhs: AnimatableRadiiVector,
rhs: AnimatableRadiiVector
) -> AnimatableRadiiVector {
AnimatableRadiiVector(values: zip(lhs.values, rhs.values).map(+))
}
static func - (
lhs: AnimatableRadiiVector,
rhs: AnimatableRadiiVector
) -> AnimatableRadiiVector {
AnimatableRadiiVector(values: zip(lhs.values, rhs.values).map(-))
}
mutating func scale(by rhs: Double) {
values = values.map { $0 * rhs }
}
var magnitudeSquared: Double {
values.reduce(0) { $0 + $1 * $1 }
}
}
MoodBodyShape 也应保留为 nonisolated,并让 animatableData 使用上面的非隔离向量类型:
nonisolated struct MoodBodyShape: Shape {
var radiiVector: AnimatableRadiiVector
var animatableData: AnimatableRadiiVector {
get { radiiVector }
set { radiiVector = newValue }
}
// init(radii:) 和 path(in:) 保持原实现
}
如果当前 Swift 编译器不接受 nonisolated struct 写法,可以在 Xcode 的 Build Settings 中将该 target 的 Default Actor Isolation 设为 nonisolated,再只给需要操作 UI 的声明标注 @MainActor。不要将 Shape 或向量运算隔离到主 actor 来消除这个错误,因为 SwiftUI 可能会在主线程以外计算路径和动画帧。
检查向量长度
zip(lhs.values, rhs.values) 遇到长度不同的数组时会静默截断结果。zero 固定使用 100 个元素,而 MoodBodyShape.init(radii:) 接收任意长度,因此应确保传入的半径数组长度一致,并与动画目标的长度相同。否则,动画运算可能得到不完整的向量。这个长度问题与 actor 隔离错误无关。
如果按上述方式标记后,Xcode 26 beta 2 仍报告相同错误,可以先检查报错指向的具体声明,再用较新的 Swift 工具链编译一个只包含 ShapeCalculator、AnimatableRadiiVector 和 MoodBodyShape 的最小示例。现有材料不足以确认 beta 版本是否还存在其他编译器问题。
备注:内容仅供参考。
