···11+//! Objective-C runtime FFI bindings and Rust abstractions.
22+//!
33+//! Provides safe(ish) wrappers around the Objective-C runtime for message
44+//! dispatch, class lookup, selector registration, and dynamic class creation.
55+//!
66+//! # Safety
77+//!
88+//! This module contains `unsafe` code for FFI with `libobjc.dylib`.
99+//! The `platform` crate is one of the few crates where `unsafe` is permitted.
1010+1111+use std::ffi::CStr;
1212+use std::fmt;
1313+use std::os::raw::c_char;
1414+use std::ptr::NonNull;
1515+1616+// ---------------------------------------------------------------------------
1717+// Raw FFI bindings to libobjc.dylib
1818+// ---------------------------------------------------------------------------
1919+2020+// Opaque types matching the ObjC runtime's internal structures.
2121+// These are public because they appear in the signatures of public methods
2222+// (e.g. `as_ptr`), but they are zero-sized and cannot be constructed.
2323+#[repr(C)]
2424+pub struct ObjcClass {
2525+ _private: [u8; 0],
2626+}
2727+2828+#[repr(C)]
2929+pub struct ObjcObject {
3030+ _private: [u8; 0],
3131+}
3232+3333+#[repr(C)]
3434+pub struct ObjcSelector {
3535+ _private: [u8; 0],
3636+}
3737+3838+/// An Objective-C method implementation function pointer.
3939+///
4040+/// The actual signature varies per method, but the runtime treats all IMPs
4141+/// as `extern "C" fn()` for registration purposes.
4242+pub type Imp = unsafe extern "C" fn();
4343+4444+#[link(name = "objc", kind = "dylib")]
4545+extern "C" {
4646+ fn objc_getClass(name: *const c_char) -> *mut ObjcClass;
4747+ fn sel_registerName(name: *const c_char) -> *mut ObjcSelector;
4848+ fn objc_allocateClassPair(
4949+ superclass: *mut ObjcClass,
5050+ name: *const c_char,
5151+ extra_bytes: usize,
5252+ ) -> *mut ObjcClass;
5353+ fn objc_registerClassPair(cls: *mut ObjcClass);
5454+ fn class_addMethod(
5555+ cls: *mut ObjcClass,
5656+ sel: *mut ObjcSelector,
5757+ imp: Imp,
5858+ types: *const c_char,
5959+ ) -> bool;
6060+ fn class_getName(cls: *mut ObjcClass) -> *const c_char;
6161+6262+ // objc_msgSend has a variadic ABI. On AArch64 the calling convention is
6363+ // the standard C calling convention (arguments in x0..x7, return in x0).
6464+ // We declare it with no extra arguments; the msg_send! macro transmutes
6565+ // the function pointer to the appropriate concrete signature at each call
6666+ // site.
6767+ fn objc_msgSend();
6868+}
6969+7070+// ---------------------------------------------------------------------------
7171+// Sel — a registered selector
7272+// ---------------------------------------------------------------------------
7373+7474+/// A registered Objective-C selector.
7575+///
7676+/// Selectors are interned strings that identify a method name. Two selectors
7777+/// with the same name are pointer-equal.
7878+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
7979+#[repr(transparent)]
8080+pub struct Sel(NonNull<ObjcSelector>);
8181+8282+impl Sel {
8383+ /// Register (or look up) a selector by name.
8484+ ///
8585+ /// `name` must be a nul-terminated C string (use `c"foo:"` literals or
8686+ /// the `sel!` helper).
8787+ ///
8888+ /// # Panics
8989+ ///
9090+ /// Panics if the runtime returns null (should never happen for valid names).
9191+ pub fn register(name: &CStr) -> Sel {
9292+ // SAFETY: sel_registerName is thread-safe and always returns a valid
9393+ // pointer for well-formed selector names.
9494+ let ptr = unsafe { sel_registerName(name.as_ptr()) };
9595+ Sel(NonNull::new(ptr).expect("sel_registerName returned null"))
9696+ }
9797+9898+ /// Return the raw pointer to the selector.
9999+ #[inline]
100100+ pub fn as_ptr(self) -> *mut ObjcSelector {
101101+ self.0.as_ptr()
102102+ }
103103+}
104104+105105+impl fmt::Debug for Sel {
106106+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
107107+ // We can't easily get the selector name without sel_getName,
108108+ // so just print the pointer.
109109+ write!(f, "Sel({:?})", self.0)
110110+ }
111111+}
112112+113113+// SAFETY: Selectors are globally interned pointers; they're safe to send
114114+// across threads.
115115+unsafe impl Send for Sel {}
116116+unsafe impl Sync for Sel {}
117117+118118+// ---------------------------------------------------------------------------
119119+// Class — a reference to an Obj-C class
120120+// ---------------------------------------------------------------------------
121121+122122+/// A reference to an Objective-C class.
123123+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
124124+#[repr(transparent)]
125125+pub struct Class(NonNull<ObjcClass>);
126126+127127+impl Class {
128128+ /// Look up a class by name.
129129+ ///
130130+ /// Returns `None` if no class with that name is registered.
131131+ pub fn get(name: &CStr) -> Option<Class> {
132132+ // SAFETY: objc_getClass is thread-safe; it returns null for unknown
133133+ // class names.
134134+ let ptr = unsafe { objc_getClass(name.as_ptr()) };
135135+ NonNull::new(ptr).map(Class)
136136+ }
137137+138138+ /// Allocate a new class pair (a new class and its metaclass).
139139+ ///
140140+ /// `superclass` is the parent class; `name` is the new class name;
141141+ /// `extra_bytes` is usually 0.
142142+ ///
143143+ /// Returns `None` if a class with `name` already exists.
144144+ ///
145145+ /// You must call [`Class::register`] on the returned class after adding
146146+ /// methods and ivars.
147147+ pub fn allocate(superclass: Class, name: &CStr, extra_bytes: usize) -> Option<Class> {
148148+ // SAFETY: objc_allocateClassPair returns null if the name is already
149149+ // taken. The superclass pointer is valid because it came from a Class.
150150+ let ptr =
151151+ unsafe { objc_allocateClassPair(superclass.as_ptr(), name.as_ptr(), extra_bytes) };
152152+ NonNull::new(ptr).map(Class)
153153+ }
154154+155155+ /// Register a class that was previously created with [`Class::allocate`].
156156+ ///
157157+ /// After this call the class is usable and no further methods/ivars can be
158158+ /// added.
159159+ pub fn register(self) {
160160+ // SAFETY: the pointer is valid and the class has not been registered
161161+ // yet (caller invariant).
162162+ unsafe { objc_registerClassPair(self.as_ptr()) }
163163+ }
164164+165165+ /// Add a method to a class that has not yet been registered.
166166+ ///
167167+ /// `sel` is the method selector, `imp` is the implementation function
168168+ /// pointer, and `types` is the ObjC type encoding string.
169169+ ///
170170+ /// Returns `true` if the method was added, `false` if a method with that
171171+ /// selector already existed.
172172+ pub fn add_method(self, sel: Sel, imp: Imp, types: &CStr) -> bool {
173173+ // SAFETY: the class pointer is valid and not yet registered.
174174+ unsafe { class_addMethod(self.as_ptr(), sel.as_ptr(), imp, types.as_ptr()) }
175175+ }
176176+177177+ /// Get the name of this class.
178178+ pub fn name(self) -> &'static str {
179179+ // SAFETY: class_getName always returns a valid C string for a valid
180180+ // class pointer.
181181+ let c_str = unsafe { CStr::from_ptr(class_getName(self.as_ptr())) };
182182+ c_str.to_str().unwrap_or("<invalid UTF-8>")
183183+ }
184184+185185+ /// Return the raw pointer to the class.
186186+ #[inline]
187187+ pub fn as_ptr(self) -> *mut ObjcClass {
188188+ self.0.as_ptr()
189189+ }
190190+}
191191+192192+impl fmt::Debug for Class {
193193+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194194+ write!(f, "Class(\"{}\")", self.name())
195195+ }
196196+}
197197+198198+// SAFETY: ObjC classes are globally registered and immutable after
199199+// registration; safe to share across threads.
200200+unsafe impl Send for Class {}
201201+unsafe impl Sync for Class {}
202202+203203+// ---------------------------------------------------------------------------
204204+// Id — a non-null pointer to an Obj-C object instance
205205+// ---------------------------------------------------------------------------
206206+207207+/// A non-null pointer to an Objective-C object.
208208+///
209209+/// This is an *unowned* (raw) reference — no automatic retain/release.
210210+/// The caller is responsible for memory management.
211211+#[derive(Clone, Copy, PartialEq, Eq, Hash)]
212212+#[repr(transparent)]
213213+pub struct Id(NonNull<ObjcObject>);
214214+215215+impl Id {
216216+ /// Create an `Id` from a raw non-null pointer.
217217+ ///
218218+ /// # Safety
219219+ ///
220220+ /// The pointer must point to a valid Objective-C object.
221221+ #[inline]
222222+ pub unsafe fn from_ptr(ptr: NonNull<ObjcObject>) -> Id {
223223+ Id(ptr)
224224+ }
225225+226226+ /// Try to create an `Id` from a raw pointer that might be null.
227227+ ///
228228+ /// # Safety
229229+ ///
230230+ /// If non-null, the pointer must point to a valid Objective-C object.
231231+ #[inline]
232232+ pub unsafe fn from_raw(ptr: *mut ObjcObject) -> Option<Id> {
233233+ NonNull::new(ptr).map(Id)
234234+ }
235235+236236+ /// Return the raw pointer.
237237+ #[inline]
238238+ pub fn as_ptr(self) -> *mut ObjcObject {
239239+ self.0.as_ptr()
240240+ }
241241+242242+ /// Cast to `*mut T` for use as a function argument.
243243+ ///
244244+ /// # Safety
245245+ ///
246246+ /// The caller must ensure the object is actually of type `T`.
247247+ #[inline]
248248+ pub unsafe fn cast<T>(self) -> *mut T {
249249+ self.0.as_ptr().cast()
250250+ }
251251+}
252252+253253+impl fmt::Debug for Id {
254254+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
255255+ write!(f, "Id({:?})", self.0)
256256+ }
257257+}
258258+259259+// SAFETY: ObjC objects can be sent across threads (whether this is safe
260260+// depends on the object, but at the pointer level it's fine).
261261+unsafe impl Send for Id {}
262262+unsafe impl Sync for Id {}
263263+264264+// ---------------------------------------------------------------------------
265265+// msg_send! macro
266266+// ---------------------------------------------------------------------------
267267+268268+/// Return the raw `objc_msgSend` function pointer.
269269+///
270270+/// The caller must transmute this to the correct function signature for the
271271+/// message being sent. The `msg_send!` macro does this automatically.
272272+#[inline]
273273+pub fn msg_send_fn() -> unsafe extern "C" fn() {
274274+ objc_msgSend
275275+}
276276+277277+/// Send an Objective-C message.
278278+///
279279+/// # Syntax
280280+///
281281+/// ```ignore
282282+/// // No arguments, returns Id:
283283+/// let obj: Id = msg_send![class.as_ptr(), alloc];
284284+///
285285+/// // With arguments, returns Id:
286286+/// let obj: Id = msg_send![obj.as_ptr(), initWithFrame: rect];
287287+///
288288+/// // No return value (returns ()):
289289+/// msg_send![obj.as_ptr(), release];
290290+/// ```
291291+///
292292+/// The first argument must be a `*mut T` (the receiver). The return type is
293293+/// inferred from context.
294294+///
295295+/// # Safety
296296+///
297297+/// This is inherently unsafe: wrong selector, wrong argument types, or wrong
298298+/// return type will cause undefined behavior.
299299+#[macro_export]
300300+macro_rules! msg_send {
301301+ // No arguments: msg_send![receiver, selector]
302302+ [$receiver:expr, $sel:ident] => {{
303303+ let sel_name = concat!(stringify!($sel), "\0");
304304+ let sel = $crate::objc::Sel::register(unsafe {
305305+ std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes())
306306+ });
307307+ let func: unsafe extern "C" fn(*mut std::os::raw::c_void, *mut std::os::raw::c_void) -> _
308308+ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) };
309309+ unsafe { func($receiver as *mut std::os::raw::c_void, sel.as_ptr() as *mut std::os::raw::c_void) }
310310+ }};
311311+312312+ // One argument: msg_send![receiver, selector: arg]
313313+ [$receiver:expr, $sel:ident : $arg:expr] => {{
314314+ let sel_name = concat!(stringify!($sel), ":\0");
315315+ let sel = $crate::objc::Sel::register(unsafe {
316316+ std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes())
317317+ });
318318+ let func: unsafe extern "C" fn(
319319+ *mut std::os::raw::c_void,
320320+ *mut std::os::raw::c_void,
321321+ _,
322322+ ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) };
323323+ unsafe {
324324+ func(
325325+ $receiver as *mut std::os::raw::c_void,
326326+ sel.as_ptr() as *mut std::os::raw::c_void,
327327+ $arg,
328328+ )
329329+ }
330330+ }};
331331+332332+ // Two arguments: msg_send![receiver, sel1: arg1, sel2: arg2]
333333+ [$receiver:expr, $sel1:ident : $arg1:expr, $sel2:ident : $arg2:expr] => {{
334334+ let sel_name = concat!(stringify!($sel1), ":", stringify!($sel2), ":\0");
335335+ let sel = $crate::objc::Sel::register(unsafe {
336336+ std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes())
337337+ });
338338+ let func: unsafe extern "C" fn(
339339+ *mut std::os::raw::c_void,
340340+ *mut std::os::raw::c_void,
341341+ _,
342342+ _,
343343+ ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) };
344344+ unsafe {
345345+ func(
346346+ $receiver as *mut std::os::raw::c_void,
347347+ sel.as_ptr() as *mut std::os::raw::c_void,
348348+ $arg1,
349349+ $arg2,
350350+ )
351351+ }
352352+ }};
353353+354354+ // Three arguments: msg_send![receiver, sel1: arg1, sel2: arg2, sel3: arg3]
355355+ [$receiver:expr, $sel1:ident : $arg1:expr, $sel2:ident : $arg2:expr, $sel3:ident : $arg3:expr] => {{
356356+ let sel_name = concat!(
357357+ stringify!($sel1), ":",
358358+ stringify!($sel2), ":",
359359+ stringify!($sel3), ":\0"
360360+ );
361361+ let sel = $crate::objc::Sel::register(unsafe {
362362+ std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes())
363363+ });
364364+ let func: unsafe extern "C" fn(
365365+ *mut std::os::raw::c_void,
366366+ *mut std::os::raw::c_void,
367367+ _,
368368+ _,
369369+ _,
370370+ ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) };
371371+ unsafe {
372372+ func(
373373+ $receiver as *mut std::os::raw::c_void,
374374+ sel.as_ptr() as *mut std::os::raw::c_void,
375375+ $arg1,
376376+ $arg2,
377377+ $arg3,
378378+ )
379379+ }
380380+ }};
381381+382382+ // Four arguments
383383+ [$receiver:expr, $sel1:ident : $arg1:expr, $sel2:ident : $arg2:expr, $sel3:ident : $arg3:expr, $sel4:ident : $arg4:expr] => {{
384384+ let sel_name = concat!(
385385+ stringify!($sel1), ":",
386386+ stringify!($sel2), ":",
387387+ stringify!($sel3), ":",
388388+ stringify!($sel4), ":\0"
389389+ );
390390+ let sel = $crate::objc::Sel::register(unsafe {
391391+ std::ffi::CStr::from_bytes_with_nul_unchecked(sel_name.as_bytes())
392392+ });
393393+ let func: unsafe extern "C" fn(
394394+ *mut std::os::raw::c_void,
395395+ *mut std::os::raw::c_void,
396396+ _,
397397+ _,
398398+ _,
399399+ _,
400400+ ) -> _ = unsafe { std::mem::transmute($crate::objc::msg_send_fn()) };
401401+ unsafe {
402402+ func(
403403+ $receiver as *mut std::os::raw::c_void,
404404+ sel.as_ptr() as *mut std::os::raw::c_void,
405405+ $arg1,
406406+ $arg2,
407407+ $arg3,
408408+ $arg4,
409409+ )
410410+ }
411411+ }};
412412+}
413413+414414+// ---------------------------------------------------------------------------
415415+// Convenience: sel! macro
416416+// ---------------------------------------------------------------------------
417417+418418+/// Register a selector from a string literal.
419419+///
420420+/// ```ignore
421421+/// let sel = sel!("init");
422422+/// let sel = sel!("initWithFrame:");
423423+/// ```
424424+#[macro_export]
425425+macro_rules! sel {
426426+ ($name:literal) => {{
427427+ $crate::objc::Sel::register(unsafe {
428428+ std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes())
429429+ })
430430+ }};
431431+}
432432+433433+/// Look up a class by name (string literal).
434434+///
435435+/// Returns `Option<Class>`.
436436+///
437437+/// ```ignore
438438+/// let cls = class!("NSObject").unwrap();
439439+/// ```
440440+#[macro_export]
441441+macro_rules! class {
442442+ ($name:literal) => {{
443443+ $crate::objc::Class::get(unsafe {
444444+ std::ffi::CStr::from_bytes_with_nul_unchecked(concat!($name, "\0").as_bytes())
445445+ })
446446+ }};
447447+}
448448+449449+// ---------------------------------------------------------------------------
450450+// Tests
451451+// ---------------------------------------------------------------------------
452452+453453+#[cfg(test)]
454454+mod tests {
455455+ use super::*;
456456+457457+ #[test]
458458+ fn get_nsobject_class() {
459459+ let cls = Class::get(c"NSObject").expect("NSObject class should exist");
460460+ assert_eq!(cls.name(), "NSObject");
461461+ }
462462+463463+ #[test]
464464+ fn get_nonexistent_class_returns_none() {
465465+ let cls = Class::get(c"ThisClassDoesNotExist12345");
466466+ assert!(cls.is_none());
467467+ }
468468+469469+ #[test]
470470+ fn register_selector() {
471471+ let sel1 = Sel::register(c"init");
472472+ let sel2 = Sel::register(c"init");
473473+ // Same selector name should return the same pointer.
474474+ assert_eq!(sel1, sel2);
475475+ }
476476+477477+ #[test]
478478+ fn different_selectors_differ() {
479479+ let sel1 = Sel::register(c"init");
480480+ let sel2 = Sel::register(c"alloc");
481481+ assert_ne!(sel1, sel2);
482482+ }
483483+484484+ #[test]
485485+ fn alloc_init_nsobject() {
486486+ let cls = Class::get(c"NSObject").unwrap();
487487+ // alloc
488488+ let obj: *mut std::os::raw::c_void = msg_send![cls.as_ptr(), alloc];
489489+ assert!(!obj.is_null());
490490+ // init
491491+ let obj: *mut std::os::raw::c_void = msg_send![obj, init];
492492+ assert!(!obj.is_null());
493493+ // release
494494+ let _: *mut std::os::raw::c_void = msg_send![obj, release];
495495+ }
496496+497497+ #[test]
498498+ fn class_macro_works() {
499499+ let cls = class!("NSObject").expect("NSObject should exist");
500500+ assert_eq!(cls.name(), "NSObject");
501501+ }
502502+503503+ #[test]
504504+ fn sel_macro_works() {
505505+ let sel = sel!("init");
506506+ let sel2 = Sel::register(c"init");
507507+ assert_eq!(sel, sel2);
508508+ }
509509+510510+ #[test]
511511+ fn allocate_and_register_custom_class() {
512512+ let superclass = Class::get(c"NSObject").unwrap();
513513+ // Use a unique class name to avoid conflicts with other tests.
514514+ let new_cls = Class::allocate(superclass, c"WeTestCustomClass", 0)
515515+ .expect("should allocate new class");
516516+ new_cls.register();
517517+ // Verify we can look it up.
518518+ let found = Class::get(c"WeTestCustomClass").expect("custom class should be registered");
519519+ assert_eq!(found.name(), "WeTestCustomClass");
520520+ }
521521+522522+ #[test]
523523+ fn add_method_to_custom_class() {
524524+ let superclass = Class::get(c"NSObject").unwrap();
525525+ let new_cls = Class::allocate(superclass, c"WeTestMethodClass", 0)
526526+ .expect("should allocate new class");
527527+528528+ // Define a simple method that returns an integer.
529529+ extern "C" fn my_method(
530530+ _this: *mut std::os::raw::c_void,
531531+ _sel: *mut std::os::raw::c_void,
532532+ ) -> i64 {
533533+ 42
534534+ }
535535+536536+ let sel = Sel::register(c"myMethod");
537537+ // Type encoding: returns long long (q), takes id (@) and SEL (:)
538538+ let added = new_cls.add_method(
539539+ sel,
540540+ unsafe { std::mem::transmute::<*const (), Imp>(my_method as *const ()) },
541541+ c"q@:",
542542+ );
543543+ assert!(added);
544544+545545+ new_cls.register();
546546+547547+ // Allocate an instance and call our method.
548548+ let cls = Class::get(c"WeTestMethodClass").unwrap();
549549+ let obj: *mut std::os::raw::c_void = msg_send![cls.as_ptr(), alloc];
550550+ let obj: *mut std::os::raw::c_void = msg_send![obj, init];
551551+ let result: i64 = msg_send![obj, myMethod];
552552+ assert_eq!(result, 42);
553553+ let _: *mut std::os::raw::c_void = msg_send![obj, release];
554554+ }
555555+556556+ #[test]
557557+ fn msg_send_with_argument() {
558558+ let cls = Class::get(c"NSObject").unwrap();
559559+ let obj: *mut std::os::raw::c_void = msg_send![cls.as_ptr(), alloc];
560560+ let obj: *mut std::os::raw::c_void = msg_send![obj, init];
561561+562562+ // respondsToSelector: takes a SEL, returns BOOL (i8 on AArch64).
563563+ let sel = Sel::register(c"init");
564564+ let responds: bool = msg_send![obj, respondsToSelector: sel.as_ptr()];
565565+ assert!(responds);
566566+567567+ let _: *mut std::os::raw::c_void = msg_send![obj, release];
568568+ }
569569+570570+ #[test]
571571+ fn class_debug_format() {
572572+ let cls = Class::get(c"NSObject").unwrap();
573573+ let debug = format!("{:?}", cls);
574574+ assert!(debug.contains("NSObject"));
575575+ }
576576+577577+ #[test]
578578+ fn sel_debug_format() {
579579+ let sel = Sel::register(c"init");
580580+ let debug = format!("{:?}", sel);
581581+ assert!(debug.contains("Sel("));
582582+ }
583583+}