Skip to main content

swiftui_native/views/
representable.rs

1//! `NSViewRepresentable` written in Rust — an AppKit view inside a SwiftUI tree.
2//!
3//! The thing a SwiftUI app reaches for when SwiftUI has no control for the job:
4//! an `NSTextView`, a `WKWebView`, an `MTKView`. A Swift program writes a
5//! `struct` with `makeNSView` and `updateNSView`; here the two are Rust closures
6//! and the conformance is synthesised.
7//!
8//! ```no_run
9//! # use swiftui_native::{objc, views};
10//! # use core::ffi::c_void;
11//! // Any `NSView` subclass, made and configured through the ObjC runtime.
12//! let rep = views::ns_view_fn(
13//!     || unsafe {
14//!         let cls = objc::class(c"OBJC_CLASS_$_NSTextField");
15//!         let v = objc::send(objc::send(cls, objc::sel(c"alloc")), objc::sel(c"init"));
16//!         objc::send_id(v, objc::sel(c"setStringValue:"), objc::nsstring("AppKit"));
17//!         v
18//!     },
19//!     |_view: *mut c_void| {},
20//! );
21//! let v = views::ns_view_representable(&rep);
22//! ```
23//!
24//! # The knot, again
25//!
26//! `NSViewRepresentable: View where Body == Never`, and the `View` witnesses come
27//! from SwiftUI's own extension on `NSViewRepresentable` — which wants
28//! `Self: NSViewRepresentable` where a `View` witness is handed `Self: View`. So
29//! each goes through a thunk that swaps the table, exactly as
30//! [`crate::views::customshape`]'s `body` does. One turn simpler than `Shape`,
31//! though: `Body` is `Never`, so there is no type that has to be instantiated
32//! before the conformance exists.
33//!
34//! # What is not offered
35//!
36//! A coordinator. `Coordinator` defaults to `Void` and that is what this
37//! supplies: the Rust closures own whatever state they need, so there is nothing
38//! for a coordinator to hold that a captured `Arc` does not.
39
40use super::{wrap_owned, ViewHandle};
41use crate::abi;
42use crate::conformance::{synthesize, Conformance, Protocol, Witness};
43use crate::resolve;
44use core::ffi::c_void;
45use std::sync::{Mutex, OnceLock};
46
47/// How an AppKit view is made and kept up to date.
48pub trait Representing: Send + Sync + 'static {
49    /// Make the view. Called once.
50    fn make(&self) -> *mut c_void;
51    /// Bring the view in line with the current state. Called on every update.
52    fn update(&self, view: *mut c_void);
53}
54
55/// A registered representable, as the one word SwiftUI stores.
56pub struct NSViewRep {
57    index: i64,
58}
59
60/// A representable from two closures.
61struct Pair<M, U> {
62    make: M,
63    update: U,
64}
65
66impl<M, U> Representing for Pair<M, U>
67where
68    M: Fn() -> *mut c_void + Send + Sync + 'static,
69    U: Fn(*mut c_void) + Send + Sync + 'static,
70{
71    fn make(&self) -> *mut c_void {
72        (self.make)()
73    }
74    fn update(&self, view: *mut c_void) {
75        (self.update)(view)
76    }
77}
78
79fn registry() -> &'static Mutex<Vec<&'static dyn Representing>> {
80    static R: OnceLock<Mutex<Vec<&'static dyn Representing>>> = OnceLock::new();
81    R.get_or_init(|| Mutex::new(Vec::new()))
82}
83
84/// Register a representable from a maker and an updater.
85///
86/// `make` must return a `+1` `NSView` subclass — SwiftUI takes ownership of it.
87pub fn ns_view_fn(
88    make: impl Fn() -> *mut c_void + Send + Sync + 'static,
89    update: impl Fn(*mut c_void) + Send + Sync + 'static,
90) -> NSViewRep {
91    let leaked: &'static dyn Representing = Box::leak(Box::new(Pair { make, update }));
92    let mut r = registry().lock().expect("representable registry");
93    r.push(leaked);
94    NSViewRep { index: r.len() as i64 - 1 }
95}
96
97// ── The witnesses SwiftUI calls ──
98//
99// `makeNSView(context:)` takes the context in x0 and `self` in x20, and returns
100// the view in x0 — a class reference, so no indirect result.
101core::arch::global_asm!(
102    ".p2align 2",
103    ".globl _rswift_nsvr_make",
104    "_rswift_nsvr_make:",
105    "mov x0, x20",
106    "b _rswift_nsvr_make_body",
107    // `updateNSView(_:context:)` — the view in x0, the context in x1, `self` in
108    // x20. Only `self` has to move.
109    ".p2align 2",
110    ".globl _rswift_nsvr_update",
111    "_rswift_nsvr_update:",
112    "mov x1, x20",
113    "b _rswift_nsvr_update_body",
114    // `makeCoordinator()` with `Coordinator == Void`: an indirect result of zero
115    // bytes, so there is nothing to write and nothing to read.
116    ".p2align 2",
117    ".globl _rswift_nsvr_coordinator",
118    "_rswift_nsvr_coordinator:",
119    "ret",
120);
121
122unsafe extern "C" {
123    #[link_name = "rswift_nsvr_make"]
124    fn nsvr_make_thunk();
125    #[link_name = "rswift_nsvr_update"]
126    fn nsvr_update_thunk();
127    #[link_name = "rswift_nsvr_coordinator"]
128    fn nsvr_coordinator_thunk();
129}
130
131/// The registered representable at `index`.
132fn representing(index: usize) -> &'static dyn Representing {
133    let r = registry().lock().expect("representable registry");
134    r.get(index).copied().expect("representable index out of range")
135}
136
137/// `makeNSView(context:) -> NSViewType`.
138///
139/// # Safety
140/// Reached through `rswift_nsvr_make`, which puts `self` in x0.
141#[unsafe(no_mangle)]
142unsafe extern "C" fn rswift_nsvr_make_body(this: *const u8) -> *mut c_void {
143    let index = unsafe { *(this as *const i64) } as usize;
144    let view = representing(index).make();
145    assert!(!view.is_null(), "the representable's `make` returned nothing");
146    view
147}
148
149/// `updateNSView(_:context:)`.
150///
151/// # Safety
152/// Reached through `rswift_nsvr_update`, which puts `self` in x1.
153#[unsafe(no_mangle)]
154unsafe extern "C" fn rswift_nsvr_update_body(view: *mut c_void, this: *const u8) {
155    let index = unsafe { *(this as *const i64) } as usize;
156    representing(index).update(view);
157}
158
159// The three `View` witnesses come from SwiftUI's `NSViewRepresentable`
160// extension, which wants `Self: NSViewRepresentable` in the register a `View`
161// witness fills with `Self: View`. Each thunk swaps that one register and tail
162// calls; the table to swap in is written once, when the conformance is built.
163#[unsafe(no_mangle)]
164static mut RSWIFT_NSVR_WT: *const c_void = core::ptr::null();
165
166crate::thunk::runtime_thunk! {
167    /// `_makeView(view:inputs:)` — x0/x1 the arguments, x2 `Self`, x3 the table.
168    fn nsvr_make_view_thunk = "rswift_nsvr_make_view" via RSWIFT_NSVR_MAKE_VIEW {
169        prologue: [
170            "adrp x3, _RSWIFT_NSVR_WT@PAGE",
171            "add x3, x3, _RSWIFT_NSVR_WT@PAGEOFF",
172            "ldr x3, [x3]",
173        ],
174        branch: "br",
175        epilogue: [],
176    }
177    /// `_makeViewList(view:inputs:)` — the same shape.
178    fn nsvr_make_view_list_thunk = "rswift_nsvr_make_list" via RSWIFT_NSVR_MAKE_LIST {
179        prologue: [
180            "adrp x3, _RSWIFT_NSVR_WT@PAGE",
181            "add x3, x3, _RSWIFT_NSVR_WT@PAGEOFF",
182            "ldr x3, [x3]",
183        ],
184        branch: "br",
185        epilogue: [],
186    }
187    /// `body` — a getter, so `Self` is in x0 and the table in x1.
188    fn nsvr_body_thunk = "rswift_nsvr_body" via RSWIFT_NSVR_BODY {
189        prologue: [
190            "adrp x1, _RSWIFT_NSVR_WT@PAGE",
191            "add x1, x1, _RSWIFT_NSVR_WT@PAGEOFF",
192            "ldr x1, [x1]",
193        ],
194        branch: "br",
195        epilogue: [],
196    }
197}
198
199/// The carrier type a representable value *is*: one `Int`, its index.
200fn carrier() -> (*const c_void, *const c_void) {
201    static C: OnceLock<(usize, usize)> = OnceLock::new();
202    let (d, m) = *C.get_or_init(|| unsafe {
203        let (d, m) =
204            crate::state::emit_int_carrier(b"RustNSViewRep\0", b"6rswift13RustNSViewRepV\0");
205        (d as usize, m as usize)
206    });
207    (d as *const c_void, m as *const c_void)
208}
209
210fn representable_conformance() -> &'static Conformance {
211    static C: OnceLock<Conformance> = OnceLock::new();
212    C.get_or_init(|| unsafe {
213        let protocol = Protocol::from_symbol(c"$s7SwiftUI19NSViewRepresentableMp");
214        assert_eq!(
215            protocol.requirement_count(),
216            15,
217            "NSViewRepresentable changed shape — it used to be View, two \
218             associated types, and twelve methods of which three must be supplied",
219        );
220
221        // `NSViewType` is `NSView`. An imported Objective-C class's Swift
222        // metadata is *not* simply its class object — the runtime wraps it, and
223        // `swift_getObjCClassMetadata` is what does the wrapping. Passing the
224        // bare class here is the kind of mistake that looks right: it is a
225        // pointer, it is the right class, and everything that reads it as
226        // metadata walks off into the class's `isa`.
227        let class = crate::objc::class(c"OBJC_CLASS_$_NSView");
228        assert!(!class.is_null(), "NSView class");
229        let nsview =
230            swift_runtime_sys::MetadataIntrospection::swift_getObjCClassMetadata(class);
231        assert!(!nsview.is_null(), "NSView metadata");
232
233        let view_wt = abi::get_view_wt(carrier().1);
234        assert!(!view_wt.is_null(), "the carrier type has no View conformance");
235
236        let conformance = synthesize(
237            &protocol,
238            carrier().0,
239            &[
240                Witness::Metadata(view_wt),
241                Witness::Metadata(nsview),
242                // `Coordinator`, from the protocol's own recorded default.
243                //
244                // Not `Witness::Metadata(())`. An associated type with a default
245                // records it as a *mangled name* the runtime resolves in context,
246                // and a raw metadata pointer in that slot is read as one — which
247                // faulted inside `swift_initEnumMetadataSinglePayload`, building
248                // an `Optional` of whatever it thought `Coordinator` was, a long
249                // way from here.
250                Witness::Default,
251                Witness::Function(nsvr_make_thunk as *const c_void),
252                Witness::Function(nsvr_update_thunk as *const c_void),
253                Witness::Default,
254                Witness::Default,
255                Witness::Function(nsvr_coordinator_thunk as *const c_void),
256                Witness::Default,
257                Witness::Default,
258                Witness::Default,
259                Witness::Default,
260                Witness::Default,
261                Witness::Default,
262                Witness::Default,
263            ],
264        );
265        RSWIFT_NSVR_WT = conformance.witness_table();
266
267        // The carrier's `View` table, as `crate::state` laid it out, pointed at
268        // SwiftUI's own representable machinery: `Body` is `Never`, and the three
269        // `_make…` entries and `body` come from the extension.
270        RSWIFT_NSVR_MAKE_VIEW = resolve::nsvr_make_view();
271        RSWIFT_NSVR_MAKE_LIST = resolve::nsvr_make_view_list();
272        RSWIFT_NSVR_BODY = resolve::nsvr_body();
273        let words = view_wt as *mut *const c_void;
274        *words.add(1) = resolve::never_view_wt();
275        *words.add(2) = resolve::never_meta();
276        *words.add(3) = nsvr_make_view_thunk as *const c_void;
277        *words.add(4) = nsvr_make_view_list_thunk as *const c_void;
278        *words.add(6) = nsvr_body_thunk as *const c_void;
279
280        conformance
281    })
282}
283
284/// The representable as a view.
285pub fn ns_view_representable(rep: &NSViewRep) -> ViewHandle {
286    unsafe {
287        let _ = representable_conformance();
288        let meta = carrier().1;
289        let wt = abi::get_view_wt(meta);
290        assert!(!wt.is_null(), "{} !: View", crate::diag::type_name(meta));
291        let mut bytes = vec![0u8; abi::type_bytes(meta)];
292        bytes[..8].copy_from_slice(&rep.index.to_le_bytes());
293        wrap_owned(bytes, meta, wt)
294    }
295}