Skip to content

Infinite recursion with @ObservableDefault #217

Description

@thefloweringash

I have run into a case with @ObservableDefault where modifying the value through the property wrapper results into the observer firing, which then writes the new value via the property wrapper, which once agains fires the observer, infinitely recursing.

I don't see any explicit logic to suppress this kind of recursion, and stepping through with a debugger it seems like even using a simpler type like Key<Int> calls results in the set {} block triggering the observation callback (ie, set {} -> observer -> set {}), but something in the underlying framework is suppressing the KVO callback.

There are some conditions under which it doesn't happen, so I wouldn't be surprised if there's some kind of pointer equality check within the underlying framework. Clearing the value out first seems to help reproduce.

Gist of crash.

Version: 9.0.5


Sources/TestObservableDefault/TestObservableDefault.swift

import Foundation

import Defaults
import DefaultsMacros

@main
struct TestObservableDefault {
  static func main() {
    Defaults[.testSet] = []

    let observer = Observer()
    observer.testSet.formUnion(1...10)
  }
}

extension Defaults.Keys {
  static let testSet = Key<Set<Int>>("int_set", default: [])
}

@Observable
final class Observer {
  @ObservableDefault(.testSet)
  @ObservationIgnored
  var testSet: Set<Int>

  init() {}
}
Package.swift
// swift-tools-version: 6.2

import PackageDescription

let package = Package(
    name: "TestObservableDefault",
    platforms: [
      .macOS(.v26)
    ],
    dependencies: [
        .package(url: "https://github.com/sindresorhus/Defaults", .upToNextMajor(from: "9.0.0")),
    ],
    targets: [
        .executableTarget(
            name: "TestObservableDefault",
            dependencies: [
              "Defaults",
              .product(name: "DefaultsMacros", package: "Defaults")
            ]
        ),
    ],
)

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions