1use std::ffi::{CString, c_char, c_void};
7use std::sync::OnceLock;
8
9#[repr(transparent)]
14#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
15pub struct Sel(pub(crate) *const c_void);
16
17unsafe impl Send for Sel {}
19unsafe impl Sync for Sel {}
20
21#[repr(transparent)]
26#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
27pub struct Class(pub(crate) *const c_void);
28
29unsafe impl Send for Class {}
31unsafe impl Sync for Class {}
32
33#[link(name = "objc")]
35unsafe extern "C" {
36 fn sel_registerName(name: *const c_char) -> Sel;
37 fn objc_lookUpClass(name: *const c_char) -> *const c_void;
38 fn objc_getProtocol(name: *const c_char) -> *const c_void;
39 fn class_getName(cls: *const c_void) -> *const c_char;
40 fn class_getInstanceMethod(cls: *const c_void, sel: Sel) -> *const c_void;
41 fn class_getClassMethod(cls: *const c_void, sel: Sel) -> *const c_void;
42 fn protocol_getMethodDescription(
43 proto: *const c_void,
44 sel: Sel,
45 is_required: bool,
46 is_instance: bool,
47 ) -> MethodDescription;
48}
49
50#[repr(C)]
52#[derive(Copy, Clone, Debug)]
53pub struct MethodDescription {
54 pub sel: Sel,
56 pub types: *const c_char,
58}
59
60impl Sel {
61 #[inline]
66 pub fn register(name: &str) -> Self {
67 let c_name = CString::new(name).expect("selector name contains null byte");
68 unsafe { sel_registerName(c_name.as_ptr()) }
69 }
70
71 #[inline]
77 pub unsafe fn register_cstr(name: *const c_char) -> Self {
78 unsafe { sel_registerName(name) }
79 }
80
81 #[inline]
83 pub fn is_null(&self) -> bool {
84 self.0.is_null()
85 }
86
87 #[inline]
89 pub fn as_ptr(&self) -> *const c_void {
90 self.0
91 }
92}
93
94impl Class {
95 #[inline]
99 pub fn get(name: &str) -> Option<Self> {
100 let c_name = CString::new(name).expect("class name contains null byte");
101 let ptr = unsafe { objc_lookUpClass(c_name.as_ptr()) };
102 if ptr.is_null() { None } else { Some(Self(ptr)) }
103 }
104
105 #[inline]
111 pub unsafe fn get_cstr(name: *const c_char) -> Option<Self> {
112 let ptr = unsafe { objc_lookUpClass(name) };
113 if ptr.is_null() { None } else { Some(Self(ptr)) }
114 }
115
116 #[inline]
118 pub fn as_ptr(&self) -> *const c_void {
119 self.0
120 }
121
122 #[inline]
124 pub fn is_null(&self) -> bool {
125 self.0.is_null()
126 }
127
128 #[inline]
132 pub fn instances_respond_to(&self, sel: Sel) -> bool {
133 let method = unsafe { class_getInstanceMethod(self.0, sel) };
134 !method.is_null()
135 }
136
137 #[inline]
141 pub fn responds_to(&self, sel: Sel) -> bool {
142 let method = unsafe { class_getClassMethod(self.0, sel) };
143 !method.is_null()
144 }
145
146 pub unsafe fn name(&self) -> &'static str {
152 let name_ptr = unsafe { class_getName(self.0) };
153 if name_ptr.is_null() {
154 "<unknown>"
155 } else {
156 unsafe { std::ffi::CStr::from_ptr(name_ptr) }
157 .to_str()
158 .unwrap_or("<invalid utf8>")
159 }
160 }
161}
162
163#[repr(transparent)]
167#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
168pub struct Protocol(pub(crate) *const c_void);
169
170unsafe impl Send for Protocol {}
172unsafe impl Sync for Protocol {}
173
174impl Protocol {
175 #[inline]
179 pub fn get(name: &str) -> Option<Self> {
180 let c_name = CString::new(name).expect("protocol name contains null byte");
181 let ptr = unsafe { objc_getProtocol(c_name.as_ptr()) };
182 if ptr.is_null() { None } else { Some(Self(ptr)) }
183 }
184
185 #[inline]
189 pub fn has_instance_method(&self, sel: Sel) -> bool {
190 let desc = unsafe { protocol_getMethodDescription(self.0, sel, true, true) };
192 if !desc.sel.is_null() {
193 return true;
194 }
195 let desc = unsafe { protocol_getMethodDescription(self.0, sel, false, true) };
197 !desc.sel.is_null()
198 }
199
200 #[inline]
204 pub fn has_class_method(&self, sel: Sel) -> bool {
205 let desc = unsafe { protocol_getMethodDescription(self.0, sel, true, false) };
207 if !desc.sel.is_null() {
208 return true;
209 }
210 let desc = unsafe { protocol_getMethodDescription(self.0, sel, false, false) };
212 !desc.sel.is_null()
213 }
214
215 #[inline]
217 pub fn is_null(&self) -> bool {
218 self.0.is_null()
219 }
220
221 #[inline]
223 pub fn as_ptr(&self) -> *const c_void {
224 self.0
225 }
226}
227
228#[inline]
232pub fn get_protocol(name: &str) -> *const c_void {
233 let c_name = CString::new(name).expect("protocol name contains null byte");
234 unsafe { objc_getProtocol(c_name.as_ptr()) }
235}
236
237#[derive(Default)]
239pub struct CachedSel {
240 inner: OnceLock<Sel>,
241}
242
243impl CachedSel {
244 pub const fn new() -> Self {
246 Self {
247 inner: OnceLock::new(),
248 }
249 }
250
251 #[inline]
253 pub fn get(&self, name: &str) -> Sel {
254 *self.inner.get_or_init(|| Sel::register(name))
255 }
256}
257
258#[derive(Default)]
260pub struct CachedClass {
261 inner: OnceLock<Class>,
262}
263
264impl CachedClass {
265 pub const fn new() -> Self {
267 Self {
268 inner: OnceLock::new(),
269 }
270 }
271
272 #[inline]
278 pub fn get(&self, name: &str) -> Class {
279 *self
280 .inner
281 .get_or_init(|| Class::get(name).unwrap_or_else(|| panic!("class {} not found", name)))
282 }
283
284 #[inline]
286 pub fn try_get(&self, name: &str) -> Option<Class> {
287 if let Some(cls) = self.inner.get() {
289 return Some(*cls);
290 }
291 let cls = Class::get(name)?;
293 Some(*self.inner.get_or_init(|| cls))
294 }
295}
296
297#[cfg(test)]
298mod tests {
299 use super::*;
300
301 #[test]
302 fn test_selector_registration() {
303 let sel1 = Sel::register("init");
304 let sel2 = Sel::register("init");
305 assert_eq!(sel1, sel2);
306 assert!(!sel1.is_null());
307 }
308
309 #[test]
310 fn test_class_lookup() {
311 let cls = Class::get("NSObject");
312 assert!(cls.is_some());
313 assert!(!cls.unwrap().is_null());
314 }
315
316 #[test]
317 fn test_class_not_found() {
318 let cls = Class::get("NonExistentClass12345");
319 assert!(cls.is_none());
320 }
321}