Skip to content

Bindable invalidates all bindings for any binding change #137

@watt

Description

@watt

Description

It seems that a change to any binding derived from @Perception.Bindable will invalidate every binding that is derived from that same bindable.

Looking at the source for Bindable, it seems this is the reason:

    public subscript<Subject>(
      dynamicMember keyPath: ReferenceWritableKeyPath<Value, Subject>
    ) -> Binding<Subject> where Value: AnyObject {
      withPerceptionTracking {
        self.$observer[dynamicMember: (\Observer.object).appending(path: keyPath)]
      } onChange: { [send = UncheckedSendable(self.observer.objectWillChange.send)] in
        send.value()
      }
    }

A withPerceptionTracking block is run for each keyPath that is subscripted, but the onChange is not scoped in any way, so any change will invalidate the entire Bindable.

This is a regression from 1.1.4 to 1.1.5 and all later versions.

Checklist

  • I have determined that this bug is not reproducible using Swift's observation tools. If the bug is reproducible using the @Observable macro or another tool from the Observation framework, please file it directly with Apple.
  • If possible, I've reproduced the issue using the main branch of this package.
  • This issue hasn't been addressed in an existing GitHub issue or discussion.

Expected behavior

Changes to a single binding derived from a bindable only invalidate observations of that binding.

Actual behavior

Changes to a single binding invalidate every binding derived from that bindable.

Steps to reproduce

Run this view from the example app:

@Perceptible
class DemoModel {
  var toggle1 = false
  var toggle2 = false
}

struct DemoView: View {
  @Perception.Bindable
  var model: DemoModel

  var body: some View {
    WithPerceptionTracking {
      VStack {
        ToggleView(name: "1", isOn: $model.toggle1)
        ToggleView(name: "2", isOn: $model.toggle2)
      }
    }
  }
}

struct ToggleView: View {
  var name: String
  @Binding var isOn: Bool

  var body: some View {
    let _ = print("evaluated ToggleView \(name)")
    Toggle("isOn", isOn: $isOn)
  }
}

When run with Perception 1.1.4 or Apple Observation, changing a toggle causes a single log for the ToggleView that is invalidated.

Under Perception 1.1.5 and later, changing a toggle emits logs for both ToggleViews.

Perception version information

1.6.0

Destination operating system

iOS 16.4

Xcode version information

Version 16.1 (16B40)

Swift Compiler version information

swift-driver version: 1.115 Apple Swift version 6.0.2 (swiftlang-6.0.2.1.2 clang-1600.0.26.4)
Target: arm64-apple-macosx15.0

Metadata

Metadata

Assignees

No one assigned

    Labels

    apple bugSomething isn't working due to a bug on Apple's platforms.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions