[Pitch] Observation

Edit: I think Foundation offers some observable API as Combine publishers. And its 1st import is Combine – if that means anything.

On that note: Would or could Foundation then migrate to this new and more native observer mechanism?

1 Like

I'm beginning to understand the necessity for a second solution, I'm just seeing all this also from the perspective of a newbie to the Swift world. How would I explain there are two different and relatively new observer mechanisms here? It's a bit unfortunate. With common sense (unbiased by expertise) you would expect that Combine will grow to serve also these new demands, that it will go open-source and x-platform, move to the standard library or expand and evolve in whatever way necessary ... I wish it was just named "SwiftUI View Model Observability Kit" haha :sweat_smile:

But surely @State vars are only created once, and then managed by SwiftUI, independently of the lifetime of the ContentView? Isn't that the point?

No StateObject has a closure that it uses to generate the object once and lazily, on every re-creation of the view there is no new object spawned. State will spawn new value every single time the view is instantiated, throw it away when re-injecting the last value that corresponds the view‘s lifetime. Put a very heavy value into State and you will smash your performance every time that view needs to be instantiated.

2 Likes

There was a fair discussion of the problems people had with the willSet semantics of @Published in this thread.

That is a known issue and one in-which is definitely under consideration by the SwiftUI team to adjust in light of Observable. To how they will change it to account for it? That I don't know yet.

1 Like

In general, very happy to see progress. There have been some great points already brought up so I won't reiterate them. However, I am curious how the ObservedChanges sequence is going to work. Is this going to be a unicast AsyncSequence or a multicast one? Furthermore, is the iterator going to retain the object that gets observed?

ObservedChanges is intended to be unicast. The ObservedChanges and ObservedChanges.Iterator will only hold the Observable weakly. However if we decide to relax the requirements from AnyObject to include structures too that may have to change. Sadly it must have a way to remove the observer in case of cancellation; so we need some sort of back reference.

I'm excited for this direction, it looks very promising for some (non-SwiftUI) problems in a codebase I work on. With Holly's recent update about the need for type wrappers (likely) being obviated by declaration macros, I wondered if you had had any further thoughts about what a macro-based approach for Observable would look like here?

Holly and I have been collaborating on some updates to this for the use of macros. I am in the process of revising the pitch; stay tuned - some of the feedback here and from direct sources has been quite helpful and I think it is turning into something pretty awesome.

24 Likes

Waiting with bated breath! :)

3 Likes

Hum… this is a great idea but I suggest “Observable” as a Swift keyword like “async”, more at language core.

observable class FoodStore { 
    observable var foods: [Food] = []
    var nonObservableProperty: String = “”
}
var foodStore = FoodStore()

for await change in foodStore { … }
for await change in foodStore.foods { … }

Or just

class FoodStore { 
    var foods: [Food] = []
}
observable var foodStore = FoodStore()

for await change in foodStore { … }
for await change in foodStore.foods { … }

Or… let (constant), var (variable), obs (observable)

class FoodStore { 
    obs foods: [Food] = []
    var nonObservableProperty: String = “”
}
obs foodStore = FoodStore()

for await change in foodStore { … }
for await change in foodStore.foods { … }
class NotificationStore {
    static obs shared = NotificationStore()

    obs lastNotification: Notification? = nil
}
for await notification in NotificationStore.shared.lastNotification { … }

Granted observation could be more of a runtime level feature but that would mean that the cost of making something observable would impact the potential complexity and code-generation of non-observable things. I'm not sure (even as powerful as observation is) that meets the bar of what it would mean to be language level. Instead it makes a lot more sense to have the implementation of observation first off be approachable (that folks can tinker on it without needing to build the compiler or runtime libraries from scratch) as well as integratabtle into other use cases more than just observing async sequences of values (e.g. SwiftUI's tracking of values to observe). Macros give us the power of the compiler without needing to go to the extent of inventing new things like obs.

I totally agree with you that making a type able to be observed should be as dead simple as just tossing on some sort of decorator. Hopefully next week I will have the new pitch ready - truth be told it is a pretty extensive document covering all of the nooks and crannies of how things should be observed and what that means so it has taken me a bit to re-write it and explore those bits and the implications for performance and integration. One of the key areas that I think will help is that the new pitch has detailed examples for almost all of the different bits.

16 Likes

I have been a bit under-water with a few tasks - but some of the changes are in a pull request pending on the compiler: [WIP] Initial draft of observation by phausler · Pull Request #63725 · apple/swift · GitHub. It was requested previously to provide some stuff to tinker with and see how it works - right now I am finishing up some of the macro integration (which is still a bit broken in that PR) so it is not fully ready just yet. Here soon @nnnnnnnn and I will be wrangling up a new rendition of the pitch to bring forth some of the changes to the pitch.

The plan of record is to build up the implementation along side the pitch such that it can be used as an experimental module, both for integration into other parts, as well as stand-alone evaluation.

Thanks for your patience.

14 Likes

Thank you for the update. I've been lurking in the implementation details since I noticed it and I'm really exiting to see where all this is heading.

I have a few questions that may or may not influence the direction the implementation is following:

  • Will there be a solution for lazy initialization as needed for SwiftUI?
  • Will there be a good default for discarding duplicates as an opt-in/out?
  • Can the macro provide the synthesis for the protocol conformance, so that we don't have @Observable attribute near the protocol conformance : Observable?!

While not particularly related to observation, but some simple values could use some form of a 'binding', which either assigns the value to some property or is a referencing structure similar to SwiftUI's Binding property wrapper.

Will we possibly get some ergonomic solution for this to bind observed values as part of the implementation, or at least as a future direction?
Maybe a property wrapper like macro could do both tricks?! :thinking:

1 Like

This is nice but really Swift needs a Flow like library that is built from the ground up with async await. Thus, a Combine like lib but with

  • Multi platform support
  • 100% interop with async functions

Take a look at Async Algorithms

3 Likes

In game development, the order in which interested parties receive events is often crucial. Will observers receive notifications in a way that's deterministic across platforms?

Better yet, would it be possible to make something like an OrderedObservable which allows for fine-tuned sorting of the observers? This would be a huge benefit to areas such as turn-based game design.

At any rate, and as others have said, I'm very exited to see where all this goes!

6 Likes

For those interested; after a ton of work revising things and accounting for a lot of the initial feedback here and from folks directly relaying info, there is a newly minted version of this pitch here.

The pitch shifted a touch more towards transactional based changes (since we feel that is more appropriate for observation) and refined a lot of the naming to more clearly indicate both the utility of specific APIs and fall more in-line with other existing APIs. The SwiftUI part is pretty much unchanged from the initial pitch here. In addition we have some initial versions posted as a pull request which is nearly ready for merging.

We think we are really close to a great solution here and would like to drive it forward with yall's help and feedback.

10 Likes

I think the changes look fantastic! The api is so clear and straightforward.

1 Like