Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Question: Convention for using UniFFI with SwiftUI (Update a SwiftUI view when a UniFFI interface object changes). #2145

Closed
GraniteLake opened this issue Jun 5, 2024 · 5 comments

Comments

@GraniteLake
Copy link

SwiftUI uses property wrappers such as @StateObject and @ObservedObject on an ObservableObject (a type of object with a publisher that emits before the object has changed) parameter of a SwiftUI view to update the view whenever the parameter's published properties change (see https://developer.apple.com/documentation/Combine/ObservableObject).

UniFFi issue #2097 discussed that UniFFi does not currently support swift property wrappers but the creator of the issue indicated that there is a convention for using UniFFI with SwiftUI and I was wondering which convention is used to have a SwiftUI view update when a UniFFI interface object changes?

@cadnza
Copy link

cadnza commented Jun 5, 2024

Answered here, to be taken with a huge grain of salt:

@cadnza What is the convention for writing SwiftUI code with UniFFI with regard to having a view update when a field in a UniFFi Interface / rust struct changes?

@GraniteLake As far as I understand—first pointing out how wide and varied my lack of experience is with this—the way it's usually done is by writing an intermediary stuct/class in Swift that wraps UniFFI's class and adds SwiftUI's property wrappers and macro annotations. As far as how you get UniFFI to update the struct based on those wrappers and annotations, I'm not sure, but something along those lines is what I've been seeing going through other peoples' code.

@mhammond
Copy link
Member

mhammond commented Jun 5, 2024

I think @Sajjon has some interest/knowledge here?

@Sajjon
Copy link
Contributor

Sajjon commented Jun 5, 2024

Hey! There are several dimension to using UniFFI and SwiftUI; let me start by trying to reduce the scope a bit...

What is your minimum deployment target? If you can target iOS 17, you can simply write one wrapping model per screen, lets call it ViewModel, which is a class and annotate it with @Observable macro . Which does not require its stored properties to have been marked with anything IIRC. So you can use UniFFI exported classes (or enums / srructs) as stored properties in this ViewModel.

This "solves" your issue completely without any changes needed to UniFFI.

If you need to target lower than iOS 17, you can employ the same technique with PointFrees Perception framework.

It might be interesting to explore possibilities to in Rust with UniFFI mark a uniffi::Object to be marking the Swift class with @Observable, but it being iOS 17+ we would need to wrap it with conditional compilation. We would also need to mark the generated swift file with #canImport(Observation) because it is not available for Linux.... so there are several missing pieces and challenges in doing so.

EDIT [clarification]: Even if we (conditionally for iOS/macOS etc) could I don't think we should "blanket mark" all classes with @Observable, it generates quite a bit of code, and for large classes in performance critical code one might not want said classes to grow even more. So best if we could make it possible - a new syntax - for UniFFI devs to tell UniFFI to mark specific classes with it.

Foremost we don't have any UDL spelling nor proc-macro for binding language specific settings for certain types. It would be very nice to have though! E.g. being able to express mutable/immutable stored properies on certain Swift types, and not catch em all, like we have today. Maybe @mhammond or @bendk can share some thoughts about that specifically...

If you are not using/required to use "vanilla SwiftUI" - whatever that means, since there is no clear standard; why I'm a huge fan of opinionated TCA by PointFree too - finally a standard! But I'm digressing... I was gonna say that I've recently managed to POC with TCA and SharedState with UniFFI.. Big PR... TL;DR; my app is completely driven by UniFFI, an object (swift class) for which I have a singleton, lets call it App. Hosts (iOS TCA+SwiftUI apps / Android Kotlin app) implement "Drivers" which are UniFFI traits which App uses to make use of ""peripherals"" on the host: networking (URLSession), file IO, unsafe storage (UserDefaults), secure storage (keychain), and "EventBus" (AsyncPassthroughSubject) with which App can emit "notifications" Rust side, with which I've implemented a custom PersistenceKey being the building block for @SharedReader in TCAs state - which is @ObservableState - TCAs value semantics analogue counterpart to @Observable.

@GraniteLake
Copy link
Author

Really appreciate all your responses! @Sajjon our minimum deployment is iOS 17 so that's a great solution. Excited to check out TCA and your app I wasn't familiar with TCA. I'll leave the issue open until at least tomorrow to allow members of the UniFFI team to share their thoughts on your suggestions.

@mhammond
Copy link
Member

mhammond commented Jun 6, 2024

Foremost we don't have any UDL spelling nor proc-macro for binding language specific settings for certain types. It would be very nice to have though! E.g. being able to express mutable/immutable stored properies on certain Swift types, and not catch em all, like we have today. Maybe @mhammond or @bendk can share some thoughts about that specifically...

We'd probably prefer to keep things which are language-specific like that in uniffi.toml for as long as we can - eg, you would list those types there and it would influence the binding generator. Expressing this inside proc-macros would be both tricky (because we don't really have a way to carry arbitrary metadata from proc-macros all the way to the scaffolding generation) and it seems like it would become unwieldy if all languages ended up with their own set of custom things. That said though, we do try and be pragmatic.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants