pointfreeco / swift-composable-architecture

A library for building applications in a consistent and understandable way, with composition, testing, and ergonomics in mind.
https://www.pointfree.co/collections/composable-architecture
MIT License
12.61k stars 1.46k forks source link

`Shared` subscription issue #3462

Closed ddanilyuk closed 1 month ago

ddanilyuk commented 1 month ago

Description

Found some weird behavior with AppStorageKey. The shared publisher is triggered after updating some unrelated UserDefaults values. Here is the XCTest, which is failing but should not.

func testSharedSubscription() throws {
    @Dependency(\.defaultAppStorage) var defaults
    var cancellable: (any Cancellable)?
    @Shared(.appStorage("testInt")) var testInt: Int = 0
    var isUpdated = false
    cancellable = $testInt.publisher.sink { _ in
        isUpdated = true
    }
    defaults.set(42, forKey: "randomDefaultsKey")
    XCTAssert(!isUpdated)
    _ = cancellable
}

Checklist

Expected behavior

No response

Actual behavior

No response

Reproducing project

https://github.com/ddanilyuk/SharedSubscriptionIssue/blob/main/TestSharedSubscriptionTests/TestSharedSubscriptionTests.swift

The Composable Architecture version information

1.15.2

Destination operating system

No response

Xcode version information

16.0

Swift Compiler version information

No response

stephencelis commented 1 month ago

@ddanilyuk This is the expected behavior as of today, and you'll see similar behavior in SwiftUI's @AppStorage. In order to subscribe to updates we use Notification Center because KVO does not work with user defaults keys that contain periods (e.g. "my.key"), and Notification Center does not allow for granular subscription per key. As such we just always trigger an update when user defaults update, just like @AppStorage in SwiftUI.

We are open to improvements here, but we don't consider it a bug, so I'm going to convert this to a discussion.