[Pitch] Observation (Revised)

It was a consideration and addressed by [Observation] Ensure type access is qualified to the module name by phausler · Pull Request #66367 · apple/swift · GitHub which hopefully should address that concern.

That's great. So I'm guessing this will be a beta 3 thing that just didn't make the cut to beta 2 ?
Is there any way to test this ahead to confirm it will work?

We're actually part of the Swift Compatibility Suite, so thought this change would run against it:

Thank you !

The source compat suite cannot flag this, because you don’t import the new module. Both have to be imported for there to be an issue.

1 Like

Hey Philippe,
I've just downloaded the latest nightly snapshot of 5.9 and the problem still persists. Am I doing something wrong here?

image

image

1 Like

In this case one way to resolve this is to add a typealias Observable = RxSwift.Observable. The qualified namespaces were added to the macro emission so that if you do have a conflict you can actually still use both via type aliases.

1 Like

That's not really a super great solution (or a real solution in this case), because it means that every app that has RxSwift at the moment will not build.

It also means that every app that has more than one module (for example in our case almost 100 modules), will have to typealias RxSwift.Observable everywhere.

I'm sure there's a more reasonable solution to this:

  1. Is there any way to go back to Observation not being re-exported by SwiftUI? Explicitly importing Observation sounds like a reasonable thing when you need Observable.
  2. If not - perhaps it's possible to disambiguate, at the compiler level, a protocol Observable (from Observability) that has no associated type, vs. the RxSwift.Observable type which is always generic over some Element ?

Appreciate your help,
Shai

Really it sounds like Swift needs to generalize the feature added for Result where local symbols take precedence over Swift standard library symbols. I thought it was a general feature but it appears to only apply to the standard library. We ran into this with Regex as well. I don't know how the mechanism works now (I think @Douglas_Gregor wrote it), but I wonder if the standard library exports those frameworks if the alternate behavior will take effect.

3 Likes

Swift's form of name lookup means that introducing a new top-level public symbol is a breaking change that should require a semver major version bump and such, but obviously no one has been treating it like that. The standard library hack worked around the problem for specifically that, but this is an example of how it's not a problem specific to the standard library.

One solution might be to generalize the standard library hack to something like @introducedAt("2023-06-01") and if name lookup is ambiguous between symbols from two different libraries it picks the older one.

Another would be to go back to obj-c style name prefixes since Swift didn't actually remove the need for them.

I don’t see how treating every all additive API changes as breaking changes does anything to help the client who encounters a name collision when they upgrade.

What about availability pre-iOS 17 ?!

Ben addressed this in the review thread:

Regarding the topic of back deployment: whether or not a part of the Swift standard library will be back deployed is a decision for the platform vendor, not something that is part of the evolution review. It won't be decided here in this thread.

1 Like

Any chance we'll get some official acknowledgment into:

  1. Is the core team going to address the conflicting API issue at all? And
  2. If yes, how?

Thank you :pray:

Definitely not if no one raises the issue on the review thread. Active discussion of issues happens there once a proposal moves to review.

Obviously nothing on purpose. I didn't know there's a separate thread where it's needed to comment. Thanks.

@Observable
final class SomeClass {
    var name: String {
        get {
            access(keyPath: \.name)
            return UserDefaults.standard.string(forKey: "name") ?? ""
        }
        set {
            withMutation(keyPath: \.name) {
                UserDefaults.standard.setValue(newValue, forKey: "name")
            }
        }
    }
}

Can this be a macro @AppStorage? So that this can just be:

@Observable
final class SomeClass {
    @AppStorageMacro var name: String
}

Doesn't SwiftUI already have a property wrapper with the same name that does the same thing?

I think that robust implementation should also observe external modifications to UserDefaults using KVO or NSUserDefaultsDidChangeNotification.

In case of KVO, I think it should be possible to implement by calling willSet()/didSet() methods of registrar separately.

But IIUC, bridging APIs that offer only didChange event is not possible. That will be a pain point in my practice.

Sorry to bump this thread. Playing around with Observable I've found it doesn't work with property wrappers, and so I was looking for answers in the forums and have only found the quoted extract to go by. I'd love to know if @Philippe_Hausler ever found out a way to make it work or if it's a no-go from an technical perspective.

Member macros and property wrappers don't work together since the property wrapper phase is applied post-macro. Macros are just structured text and can only append. Property wrappers would need to adjust somehow to interplay with them (which tbh that gets VERY complicated VERY quickly).

1 Like

Is that necessary because of how macros operate at a purely syntactical level, or could the application order depend on the order in which the attributes appear? I.e. with @Observable @Clamped(min:0, max: 10) var clampedNumber: Int = 6 could the macro operate on the property-wrapper-synthesized code including private var _clampedNumber and so on?