Skip to content

Commit

Permalink
Fix the API error "can't mutate self is immutable" for BindKeyed in S…
Browse files Browse the repository at this point in the history
…wiftUI view (#31)

Since `KeyedValueBinding` was a variable on the wrapper and wrapper was
a variable on view that is structure — semantically we were mutating
self.

I moved it to the wrapper itself, and projectedValue now returns the
wrapper, and added a subscript that is non mutating semantically.
  • Loading branch information
MaximBazarov committed Aug 10, 2023
1 parent 6a21824 commit fdae42e
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 108 deletions.
6 changes: 3 additions & 3 deletions Decide-Tests/Container/KeyedState_Tests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ import DecideTesting
XCTAssertEqual(sut.updatesCount, 3)
XCTAssertEqual(sut.str[id], newValue)
XCTAssertEqual(sut.strMutableObserve[id], newValue)
XCTAssertEqual(sut.strMutable[id].wrappedValue, newValue)
XCTAssertEqual(sut.strMutable[id], newValue)
}

func test_Observation_BindSet() async {
Expand All @@ -107,12 +107,12 @@ import DecideTesting

let newValue = "\(#function)-modified"

sut2.strMutable[id].wrappedValue = newValue
sut2.strMutable[id] = newValue
env.setValue(newValue, \State.$str, at: id)


XCTAssertEqual(sut.updatesCount, 2)
XCTAssertEqual(sut.strMutable[id].wrappedValue, newValue)
XCTAssertEqual(sut.strMutable[id], newValue)
XCTAssertEqual(sut.str[id], newValue)
XCTAssertEqual(sut.strMutableObserve[id], newValue)
}
Expand Down
40 changes: 40 additions & 0 deletions Decide-Tests/SwiftUI_Tests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
//===----------------------------------------------------------------------===//
//
// This source file is part of the Decide package open source project
//
// Copyright (c) 2020-2023 Maxim Bazarov and the Decide package
// open source project authors
// Licensed under MIT
//
// See LICENSE.txt for license information
//
// SPDX-License-Identifier: MIT
//
//===----------------------------------------------------------------------===//

import SwiftUI
import Decide
import XCTest
import DecideTesting

@MainActor final class SwiftUI_Tests: XCTestCase {

final class State: KeyedState<Int> {
@Property var str = "str-default"
@Mutable @Property var strMutable = "strMutable-default"
}

struct ViewUnderTest: View {
@BindKeyed(\State.$strMutable) var strMutable
@ObserveKeyed(\State.$str) var str
@ObserveKeyed(\State.$strMutable) var strMutableObserved

var body: some View {
TextField("", text: strMutable[1])
Text(str[1])
Text(strMutableObserved[1])
}
}

}

27 changes: 27 additions & 0 deletions Decide/Accessor/Default/DefaultBindKeyed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,30 @@ import Foundation
set { fatalError() }
}
}

@MainActor public struct KeyedValueBinding<I: Hashable, S: KeyedState<I>, Value> {
unowned var environment: ApplicationEnvironment

let observer: Observer
let propertyKeyPath: KeyPath<S, Property<Value>>

init(
bind propertyKeyPath: KeyPath<S, Property<Value>>,
observer: Observer,
environment: ApplicationEnvironment
) {
self.propertyKeyPath = propertyKeyPath
self.observer = observer
self.environment = environment
}

public subscript(_ identifier: I) -> Value {
get {
environment.subscribe(observer, on: propertyKeyPath, at: identifier)
return environment.getValue(propertyKeyPath, at: identifier)
}
set {
environment.setValue(newValue, propertyKeyPath, at: identifier)
}
}
}
24 changes: 24 additions & 0 deletions Decide/Accessor/Default/DefaultObserveKeyed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -69,3 +69,27 @@ import Foundation
set { fatalError() }
}
}

@MainActor public struct KeyedValueObserve<I: Hashable, S: KeyedState<I>, Value> {
unowned var environment: ApplicationEnvironment

let observer: Observer
let propertyKeyPath: KeyPath<S, Property<Value>>

init(
bind propertyKeyPath: KeyPath<S, Property<Value>>,
observer: Observer,
environment: ApplicationEnvironment
) {
self.propertyKeyPath = propertyKeyPath
self.observer = observer
self.environment = environment
}

public subscript(_ identifier: I) -> Value {
get {
environment.subscribe(observer, on: propertyKeyPath, at: identifier)
return environment.getValue(propertyKeyPath, at: identifier)
}
}
}
44 changes: 0 additions & 44 deletions Decide/Accessor/KeyValueBinding.swift

This file was deleted.

41 changes: 0 additions & 41 deletions Decide/Accessor/KeyValueObserve.swift

This file was deleted.

36 changes: 23 additions & 13 deletions Decide/Accessor/SwiftUI/BindKeyed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,29 @@ import SwiftUI
self.propertyKeyPath = propertyKeyPath.appending(path: \.wrappedValue)
}

public lazy var wrappedValue: KeyedValueObserve<I, S, Value> = {
return KeyedValueObserve(
bind: propertyKeyPath,
observer: Observer(observer),
environment: environment
public subscript(_ identifier: I) -> Binding<Value> {
Binding<Value>(
get: {
environment.subscribe(
Observer(observer),
on: propertyKeyPath,
at: identifier)
return environment.getValue(propertyKeyPath, at: identifier)
},
set: {
return environment.setValue($0, propertyKeyPath, at: identifier)
}
)
}()
}

public lazy var projectedValue: KeyedValueBinding<I, S, Value> = {
return KeyedValueBinding(
bind: propertyKeyPath,
observer: Observer(observer),
environment: environment
)
}()
public subscript(_ identifier: I) -> Value {
get {
environment.subscribe(Observer(observer), on: propertyKeyPath, at: identifier)
return environment.getValue(propertyKeyPath, at: identifier)
}
}

public var wrappedValue: Self {
self
}
}
17 changes: 10 additions & 7 deletions Decide/Accessor/SwiftUI/ObserveKeyed.swift
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,14 @@ import SwiftUI
self.propertyKeyPath = propertyKeyPath.appending(path: \.wrappedValue)
}

public lazy var wrappedValue: KeyedValueObserve<I, S, Value> = {
return KeyedValueObserve(
bind: propertyKeyPath,
observer: Observer(observer),
environment: environment
)
}()
public subscript(_ identifier: I) -> Value {
get {
environment.subscribe(Observer(observer), on: propertyKeyPath, at: identifier)
return environment.getValue(propertyKeyPath, at: identifier)
}
}

public var wrappedValue: Self {
self
}
}

0 comments on commit fdae42e

Please sign in to comment.