Open filip-sakel opened 5 months ago
What about String
? That's also not available in embedded Swift, is it?
@mortenbekditlevsen No, String
is already available in the main snapshot toolchain.
@filip-sakel do you have the prototype implementation up somewhere? I'd be interested in having a look at how targeted updates (and preferences/layout values) could fit in.
Embedded mode was recently introduced as a subset of Swift for constrained environments, such as the Web where bundle size has to be really small. With Embedded Wasm, we can get binary sizes of ~100 KB for simple apps. Though not perfect, it’s still a huge improvement over the > 7 MB of current Tokamak web apps.
However, there are some challenges with the SwiftUI public API and Tokamak’s internal implementation. For one, SwiftUI publicly uses
KeyPath
(e.g. for custom environment values) which are unavailable in Embedded. This is a complex issue and will be discussed below in more depth. SwiftUI also uses metatype identifiers for identity and type metadata forAnyHashable
. Further, Tokamak relies on type metadata internally. We use reflection to find dynamic properties in views. We make heavy use of existentials for type erasure and to change between different implementation (e.g.AnyColorBox
). And we also rely on type casting (often with existentials) for things like finding primitive views in the FiberRenderer. Finally, Tokamak relies on modules like OpenCombine and Foundation which do not currently support Embedded mode.There are thankfully a couple of solutions. Most type erasers can be rewritten to be backed by closures instead of existentials. When existentials are used to provide different implementations, we can simply write an enum with all possible implementations which should also be more efficient. Also, instead of relying on type casting to traverse the view tree, we can add requirements directly to the
View
protocol and rewrite the renderer. Finally, features like reflection and metatype object identifiers can be replaced with macros. To retain compatibility with SwiftUI (where View conformance doesn’t require a macro), we could also use a pre-build plugin that automatically adds the macro attribute toView
and other stateful-type declarations.KeyPaths are quite complex to implement. They use non-final classes which are unsupported in Embedded Swift and it’s unclear if the metadata they require will ever be part of Embedded mode either. Instead, when we encounter a key path in
.environmentValue(\.myKeyPath, newValue)
we could transformmyKeyPath
into a custom object that is Hashable and offers a setter and getter. This transformation could happen with a macro that could be implicitly added by the aforementioned pre-build plugin. This is by no means a clean solution so please feel free to propose other solutions.To make all these changes, a lot of internal components will have to change. Though it will be challenging, it is also an opportunity to simplify the codebase, which among other issues has two renderers. I propose that based on the current
FiberRenderer
, we re-architect a simple renderer that not only works in Embedded, but also goes further in terms of implementing SwiftUI’s layout views and features (such as alignment guides). This rewrite should also strive to have more comprehensive testing.I currently have a prototype implementation of the renderer itself with support for
Embedded
and more expansive layout operations than the current Fiber renderer. There are a lot of steps left:Layout
operations as modifiersGeometryReader
with coordinate systemsbrightness
,shadow
, etc.)Button
,Shape
and other common views to use the new renderer