You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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
    }
}

解答

  1. 生效优先级:全局函数会优先生效。Swift的运算符解析规则中,全局函数的优先级高于类型扩展中定义的运算符。你可以分别注释其中一个验证:注释全局函数后,扩展版本才会触发;注释扩展,全局版本正常工作。
  2. 编译成功但崩溃的原因:注释全局函数后,扩展里写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取反逻辑,引发类型错误。

解决方法:

  1. 给全局运算符添加public修饰:
public prefix operator !

public prefix func !(value: Binding<Bool>) -> Binding<Bool> {
    Binding<Bool>(
        get: { !value.wrappedValue },
        set: { value.wrappedValue = !$0 }
    )
}
  1. 在测试模块代码顶部导入你的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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.27 09:13:23