Xcode链式添加过多修饰符编译失败报类型检查超时如何解决
Xcode SwiftUI 编译超时问题解决方案
问题现象
开发过程中Xcode会出现编译失败,抛出如下错误:
The compiler is unable to type-check this expression in reasonable time; try breaking up the expression into distinct sub-expressions
可以通过以下示例项目复现错误:取消注释代码中第11个及之后的confirmationDialog条目时,项目就会编译失败。
// // ContentView.swift // confDialogTest // // Created by Max on 13.06.22. // import SwiftUI struct ContentView: View { @State private var showingConf1 = false @State private var showingConf2 = false @State private var showingConf3 = false @State private var showingConf4 = false @State private var showingConf5 = false var body: some View { Button(role: .destructive) { self.showingConf1.toggle() } label: { Label("Conf 1", systemImage: "trash.fill") } .padding() Button(role: .destructive) { self.showingConf2.toggle() } label: { Label("Conf 2", systemImage: "trash.fill") } .padding() Button(role: .destructive) { self.showingConf3.toggle() } label: { Label("Conf 3", systemImage: "trash.fill") } .padding() Button(role: .destructive) { self.showingConf4.toggle() } label: { Label("Conf 4", systemImage: "trash.fill") } .padding() Button(role: .destructive) { self.showingConf5.toggle() } label: { Label("Conf 5", systemImage: "trash.fill") } // .padding() .confirmationDialog("Select location", isPresented: $showingConf1, titleVisibility: .visible) { Text("Conf") } .confirmationDialog("Select location", isPresented: $showingConf2, titleVisibility: .visible) { Text("Conf") } .confirmationDialog("Select location", isPresented: $showingConf3, titleVisibility: .visible) { Text("Conf") } .confirmationDialog("Select location", isPresented: $showingConf4, titleVisibility: .visible) { Text("Conf") } .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { Text("Conf") } //next group is dialogs 6-10 .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { Text("Conf") } .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { Text("Conf") } .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { Text("Conf") } .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { Text("Conf") } .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { Text("Conf") } //next group is dialogs 11-15 // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } //next group is dialogs 16-20 // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } // .confirmationDialog("Select location", isPresented: $showingConf5, titleVisibility: .visible) { // Text("Conf") // } } } struct ContentView_Previews: PreviewProvider { static var previews: some View { ContentView() } }
示例场景下把部分confirmationDialog修饰符移动到其他Button视图上即可修复,但在List中为NavigationLink添加大量修饰符的场景下,无法通过移动修饰符位置解决。
解决方案
这个问题的根因是SwiftUI修饰符的链式调用会生成多层嵌套的泛型类型,当单条链上的修饰符数量到10个左右时,Swift类型检查器的推理复杂度会指数级上涨,直接触发超时。所有方案都不需要改业务逻辑,核心就是降低单条视图链的泛型嵌套深度:
- 抽取自定义ViewModifier封装关联修饰符
把逻辑相关的多个修饰符打包为自定义ViewModifier,将原来10个以上的链式调用压缩为2-3个自定义修饰符调用,直接降低类型推理的复杂度。示例代码如下:
如果修饰符总量超过20个,可以再拆分第二个自定义Modifier,外层只需要链式调用2个自定义修饰符即可,不会触发类型检查超时。struct MultiDialogModifier: ViewModifier { // 传入所有需要绑定的弹窗状态 @Binding var showA: Bool @Binding var showB: Bool @Binding var showC: Bool @Binding var showD: Bool @Binding var showE: Bool func body(content: Content) -> some View { content // 单个Modifier内部链式调用不超过9个修饰符即可 .confirmationDialog("弹窗A", isPresented: $showA, titleVisibility: .visible) { Text("操作A") } .confirmationDialog("弹窗B", isPresented: $showB, titleVisibility: .visible) { Text("操作B") } .confirmationDialog("弹窗C", isPresented: $showC, titleVisibility: .visible) { Text("操作C") } .confirmationDialog("弹窗D", isPresented: $showD, titleVisibility: .visible) { Text("操作D") } .confirmationDialog("弹窗E", isPresented: $showE, titleVisibility: .visible) { Text("操作E") } } } extension View { func bindMultiDialogs(showA: Binding<Bool>, showB: Binding<Bool>, showC: Binding<Bool>, showD: Binding<Bool>, showE: Binding<Bool>) -> some View { self.modifier(MultiDialogModifier(showA: showA, showB: showB, showC: showC, showD: showD, showE: showE)) } } - 拆分视图层级下沉修饰符
把NavigationLink、List上的部分样式类修饰符下沉到内部子视图中,比如NavigationLink的label内容单独抽为独立子View,把label相关的字体、颜色、圆角等样式修饰符放到子View内部实现,外层只保留跳转、弹窗、手势等和导航逻辑相关的修饰符,缩短外层链式调用的长度。 - 分散挂载修饰符到同层级空视图
SwiftUI的弹窗、提示类修饰符只要挂载在当前视图层级内的任意子视图上,触发效果和挂载在最外层完全一致。可以在List内部添加一个不影响布局的EmptyView(),把一半的弹窗修饰符挂载到这个EmptyView上,直接把单条链的修饰符数量砍半,不需要改动任何业务逻辑。 - 显式声明闭包类型减少推理负担
对修饰符的action、label闭包显式添加@ViewBuilder标注,或者对复杂返回值显式声明类型,避免编译器做不必要的类型推理,也能降低类型检查的耗时。
内容的提问来源于stack exchange,提问作者Max B
相关产品推荐
相关产品推荐

