Source code
Revision control
Copy as Markdown
Other Tools
use core::ffi::{c_char, c_void, CStr};
use core::ptr;
use core::str;
use core::sync::atomic::{AtomicPtr, Ordering};
use crate::ffi;
use crate::runtime::{AnyClass, Sel};
/// Allows storing a [`Sel`] in a static and lazily loading it.
#[derive(Debug)]
pub struct CachedSel {
ptr: AtomicPtr<c_void>,
}
impl CachedSel {
/// Constructs a new [`CachedSel`].
#[allow(clippy::new_without_default)]
pub const fn new() -> Self {
Self {
ptr: AtomicPtr::new(ptr::null_mut()),
}
}
// Mark as cold since this should only ever be called once (or maybe twice
// if running on multiple threads).
#[cold]
unsafe fn fetch(&self, name: *const c_char) -> Sel {
// The panic inside `Sel::register_unchecked` is unfortunate, but
// strict correctness is more important than speed
// SAFETY: Input is a non-null, NUL-terminated C-string pointer.
//
// We know this, because we construct it in `sel!` ourselves
let sel = unsafe { Sel::register_unchecked(name) };
self.ptr.store(sel.as_ptr() as *mut _, Ordering::Relaxed);
sel
}
/// Returns the cached selector. If no selector is yet cached, registers
/// one with the given name and stores it.
#[inline]
pub unsafe fn get(&self, name: &str) -> Sel {
// `Relaxed` should be fine since `sel_registerName` is thread-safe.
let ptr = self.ptr.load(Ordering::Relaxed);
if let Some(sel) = unsafe { Sel::from_ptr(ptr) } {
sel
} else {
// SAFETY: Checked by caller
unsafe { self.fetch(name.as_ptr().cast()) }
}
}
}
/// Allows storing a [`AnyClass`] reference in a static and lazily loading it.
#[derive(Debug)]
pub struct CachedClass {
ptr: AtomicPtr<AnyClass>,
}
impl CachedClass {
/// Constructs a new [`CachedClass`].
#[allow(clippy::new_without_default)]
pub const fn new() -> CachedClass {
CachedClass {
ptr: AtomicPtr::new(ptr::null_mut()),
}
}
// Mark as cold since this should only ever be called once (or maybe twice
// if running on multiple threads).
#[cold]
#[track_caller]
unsafe fn fetch(&self, name: *const c_char) -> &'static AnyClass {
let ptr: *const AnyClass = unsafe { ffi::objc_getClass(name) }.cast();
self.ptr.store(ptr as *mut AnyClass, Ordering::Relaxed);
if let Some(cls) = unsafe { ptr.as_ref() } {
cls
} else {
// Recover the name from the pointer. We do it like this so that
// we don't have to pass the length of the class to this method,
// improving binary size.
let name = unsafe { CStr::from_ptr(name) };
let name = str::from_utf8(name.to_bytes()).unwrap();
panic!("class {name} could not be found")
}
}
/// Returns the cached class. If no class is yet cached, gets one with
/// the given name and stores it.
#[inline]
#[track_caller]
pub unsafe fn get(&self, name: &str) -> &'static AnyClass {
// `Relaxed` should be fine since `objc_getClass` is thread-safe.
let ptr = self.ptr.load(Ordering::Relaxed);
if let Some(cls) = unsafe { ptr.as_ref() } {
cls
} else {
// SAFETY: Checked by caller
unsafe { self.fetch(name.as_ptr().cast()) }
}
}
}
#[cfg(test)]
mod tests {
#[test]
#[should_panic = "class NonExistentClass could not be found"]
#[cfg(not(feature = "unstable-static-class"))]
fn test_not_found() {
let _ = crate::class!(NonExistentClass);
}
}