Question?

struct porque: View {
    @State private var flag = false
    private var anotherFlag = false

    mutating func changeMe(_ value: Bool) {
        self.anotherFlag = value
    }

    var body: some View {
        Button(action: {
            // This is okay
            self.flag = true
            // Cannot assign to property: 'self' is immutable
            self.anotherFlag = true
            // Cannot use mutating member on immutable value: 'self' is immutable
            changeMe(true)
        }) {
            Text("Hi")
        }
    }
}

Why I can mutate @State var, but not regular member var?

Answer

这里(至少)有两个问题:为什么我们可以改变flag,为什么我们不能改变另一个anotherFlag ?
让我们从为什么我们不能改变anotherFlag开始。

下面是一个关于 immutable property 错误的简单例子:

struct SimpleStruct {
    var anotherFlag: Bool {
        _anotherFlag = true
//      ^~~~~~~~~~~~
//      error: cannot assign to property: 'self' is immutable
        return _anotherFlag
    }

    private var _anotherFlag = false
}

通常,在属性getter中,self是不可变的,而且通常, 我们不能给不可变值的属性赋值。因此,报错。

但是我说了两遍“usually”,每个“usually”都有一个方法。

第一个usually是,在属性getter中,self是不可变的。我们可以通过声明getter mutating来让self成为mutable:

struct SimpleStruct {
    var anotherFlag: Bool {
        mutating get {
            _anotherFlag = true
            return _anotherFlag
        }
    }

    private var _anotherFlag = false
}

self在getter中是可变的,所以Swift允许我们赋值给它的可变属性。 但这有两个问题。首先,我们只能在可变值上使用这个getter:

var s0 = SimpleStruct()
_ = s0.anotherFlag // ok, and modifies s0

let s1 = SimpleStruct()
_ = s1.anotherFlag
//  ^~ error: cannot use mutating getter on immutable value: 's1' is a 'let' constant

其次,SwiftUI不允许我们用mutating get声明body :

struct SimpleView: View {
//     ^ error: type 'SimpleView' does not conform to protocol 'View' 
    var body: some View {
        mutating get { Text("Hello") }
    }
}

View协议要求body的getternon-mutating

第二个“usually”是我们不能给一个immutable值的属性赋值。可以通过声明setter nonmutating使属性可赋值:

struct SimpleStruct {
    var anotherFlag: Bool {
        get {
            _anotherFlag = true
            return _anotherFlag
        }
    }

    private var _anotherFlag: Bool {
        get { return storage.pointee }
        nonmutating set { storage.pointee = newValue }
    }

    private let storage = UnsafeMutablePointer<Bool>.allocate(capacity: 1)
}

这是有效的,但请注意它的作用类似于引用类型:

let s0 = SimpleStruct()
let s1 = s0
_ = s1.get // effectively mutates both s0 and s1

SwiftUI的State使用nonmutating set

具体地说,State是一个属性包装器,它的wrappedValue 属性是nonmutating set。所以当你像这样声明flag:

@State private var flag = false

Swift将其转换为三个属性:

private var _flag: State<Bool> = State(initialValue: false)
private var $flag: Binding<Bool> { return _flag.projectedValue }
private var flag: Bool {
    get { return _flag.wrappedValue }
    nonmutating set { _flag.wrappedValue = newValue }
}

这里的flag属性是nonmutating set,因为State的wrappedValuenonmutating set

在内部,SwiftUI为您的flag属性的当前值分配存储空间,并设置State包装器的内部属性来标识该存储空间。您可以通过调用*dump(self._flag)*来了解其内部工作原理。例子:

struct ExampleView: View {
    @State var flag = false

    init() {
        dump(self._flag)
    }

    var body: some View {
        Button("flag = \(flag)" as String, action: { self.flag = true; dump(self._flag) })
    }
}

调用dump在init中打印如下:

▿ SwiftUI.State<Swift.Bool>
  - _value: false
  - _location: nil

内部的*_location属性标识SwiftUI私下存储属性当前值的位置。在你将视图传递给SwiftUI后,它会被初始化。当你点击按钮时,在action闭包中的dump*打印如下:

▿ SwiftUI.State<Swift.Bool>
  - _value: false
  ▿ _location: Optional(SwiftUI.StoredLocation<Swift.Bool>)
    ▿ some: SwiftUI.StoredLocation<Swift.Bool> #0
      - super: SwiftUI.AnyLocation<Swift.Bool>
        - super: SwiftUI.AnyLocationBase
      ▿ viewGraph: Optional(SwiftUI.ViewGraph)
        - some: SwiftUI.ViewGraph #1
      ▿ signal: AttributeGraph.WeakAttribute<()>
        - _subgraph: <AGSubgraph 0x60000353c840 [0x7fff805cbf90]> #2
          - super: NSObject
        ▿ _attribute: AttributeGraph.Attribute<()>
          ▿ identifier: __C.AGAttribute
            - id: 36
      ▿ valueLock: SwiftUI.SpinLock
        ▿ lock: __C.os_unfair_lock_s
          - _os_unfair_lock_opaque: 0
      - value: true
      ▿ savedValues: 1 element
        - false
      - _wasRead: true
      ▿ cache: SwiftUI.AtomicVariable<SwiftUI.LocationProjectionCache>
        ▿ _value: SwiftUI.LocationProjectionCache
          - cache: 0 key/value pairs
        ▿ lock: SwiftUI.SpinLock
          ▿ lock: __C.os_unfair_lock_s
            - _os_unfair_lock_opaque: 0

注意,即使在赋值self.flag = true_flag._value仍然为false。该*_value属性仅存储该属性的初始值; 它不能在body内部被改变(因为self在body的getter内部是不可变的,因为body的getter没有声明为mutating*)。相反,current value 是在*_flag._location!.value*中找到的。