Swift中Binding的前缀!运算符实现及相关问题咨询
Bool绑定反转的测试与问题解答
测试代码
struct TestView: View { @State private var isOn: Bool = false @State private var parameter: Parameter = .init() @StateObject private var vm = ViewModel() var body: some View { VStack { Toggle("isOff = \(isOn.description)", isOn: !$isOn) Toggle("isOff = \(parameter.isOn.description)", isOn: !$parameter.isOn) Toggle("isOff = \(vm.isOn.description)", isOn: !$vm.isOn) Toggle("isOff = \(vm.parameter.isOn.description)", isOn: !$vm.parameter.isOn) InnerView { isOn in Toggle("isOff = \(isOn.wrappedValue.description)", isOn: !isOn) } t2: { parameter in Toggle("isOff = \(parameter.wrappedValue.isOn.description)", isOn: !parameter.isOn) } } .padding() } @MainActor class ViewModel: ObservableObject { @Published var isOn: Bool = false @Published var parameter: Parameter = .init() } struct Parameter { var isOn: Bool = false } struct InnerView<T>: View where T: View { @State private var isOn: Bool = false @State private var parameter: Parameter = .init() @StateObject private var vm: ViewModel = .init() let t1: (Binding<Bool>) -> T let t2: (Binding<Parameter>) -> T var body: some View { VStack { t1($isOn) t2($parameter) t1($vm.isOn) t1($vm.parameter.isOn) } } } }
问题1
我通过全局函数和Binding扩展两种方式定义了前缀运算符!,两者共存无冲突,单独使用也都能正常工作,请问哪种方式是生效的?另外,若注释全局定义,将扩展中的实现改为返回!value,会出现编译成功但运行崩溃的情况,这是为什么?
对应代码
//prefix operator ! // //prefix func !(value: Binding<Bool>) -> Binding<Bool> { // Binding<Bool>( // get: { !value.wrappedValue }, // set: { value.wrappedValue = !$0 } // ) //} extension Binding where Value == Bool { static prefix func !(value: Binding<Bool>) -> Binding<Bool> { !value } }
解答
- 生效优先级:全局函数会优先生效。Swift的运算符解析规则中,全局函数的优先级高于类型扩展中定义的运算符。你可以分别注释其中一个验证:注释全局函数后,扩展版本才会触发;注释扩展,全局版本正常工作。
- 编译成功但崩溃的原因:注释全局函数后,扩展里写
return !value会触发无限递归。此时!value调用的就是当前扩展定义的运算符,没有终止条件,编译阶段类型检查能通过,但运行时会不断递归调用直到栈溢出崩溃。正确写法应该在扩展里直接创建新的Binding实例,而非调用自身的!运算符。
问题2
若将前缀运算符!的定义放入Swift Package框架中,部分测试会出现编译失败,报错信息为“Cannot convert value of type 'Binding' to expected argument type 'Bool'. Cannot convert value of type 'Bool' to expected argument type 'Binding'”,这是定义方式错误还是Swift Package的问题?
解答
这是定义方式的问题,和Swift Package本身无关,常见原因有两个:
- 可见性不足:Swift Package中定义的全局运算符默认是
internal可见性,如果测试代码在独立的测试目标中,需要将运算符标记为public才能被访问。否则测试模块找不到自定义的Binding<Bool>版本!,会 fallback 到默认的Bool取反运算符,导致类型不匹配。 - 未正确导入:测试模块没有导入包含运算符定义的Package模块,编译器无法识别自定义运算符,同样会尝试使用默认的Bool取反逻辑,引发类型错误。
解决方法:
- 给全局运算符添加
public修饰:
public prefix operator ! public prefix func !(value: Binding<Bool>) -> Binding<Bool> { Binding<Bool>( get: { !value.wrappedValue }, set: { value.wrappedValue = !$0 } ) }
- 在测试模块代码顶部导入你的Package:
import YourPackageName。
问题3
在代码Toggle("isOff = \(vm.isOn.description)", isOn: !$vm.isOn)中,$vm是Binding<ViewModel>,isOn是Bool类型,请问$vm.isOn是否为Binding<Bool>?若不是,它是什么类型?因为若不是的话,前缀运算符!就无法作用于它,对此我存在疑惑。
解答
$vm.isOn确实是Binding<Bool>类型。这是Swift的绑定键路径访问语法:
$vm返回Binding<ViewModel>,当访问它的isOn属性时,Swift会通过Binding的动态成员下标,自动生成一个新的Binding<Bool>实例。这个新Binding会把get和set操作转发给原Binding<ViewModel>的对应属性:get时,会获取$vm.wrappedValue.isOn的值;set时,会修改$vm.wrappedValue.isOn的值。
正因为$vm.isOn是Binding<Bool>,你的自定义!运算符才能正常作用于它,实现绑定反转的效果。
内容的提问来源于stack exchange,提问作者foolbear
相关产品推荐
相关产品推荐

