Skip to main content

objc_rs/
runtime.rs

1//! A Rust interface for the functionality of the Objective-C runtime.
2//!
3//! For more information on foreign functions, see Apple's documentation:
4//! <https://developer.apple.com/library/mac/documentation/Cocoa/Reference/ObjCRuntimeRef/index.html>
5
6#![allow(missing_docs)]
7
8use malloc_buf::Malloc;
9use std::ffi::{CStr, CString};
10use std::fmt;
11use std::os::raw::{c_char, c_int, c_uint, c_void};
12use std::ptr;
13use std::str;
14
15use crate::encode;
16use crate::{Encode, Encoding};
17
18/// The Objective-C `BOOL` type.
19///
20/// To convert an Objective-C `BOOL` into a Rust `bool`, compare it with `NO`.
21#[cfg(not(target_arch = "aarch64"))]
22pub type BOOL = ::std::os::raw::c_schar;
23/// The equivalent of true for Objective-C's `BOOL` type.
24#[cfg(not(target_arch = "aarch64"))]
25pub const YES: BOOL = 1;
26/// The equivalent of false for Objective-C's `BOOL` type.
27#[cfg(not(target_arch = "aarch64"))]
28pub const NO: BOOL = 0;
29
30#[cfg(target_arch = "aarch64")]
31pub type BOOL = bool;
32#[cfg(target_arch = "aarch64")]
33pub const YES: BOOL = true;
34#[cfg(target_arch = "aarch64")]
35pub const NO: BOOL = false;
36
37/// A type that represents a method selector.
38#[repr(C)]
39pub struct Sel {
40    ptr: *const c_void,
41}
42
43/// A marker type to be embedded into other types just so that they cannot be
44/// constructed externally.
45type PrivateMarker = [u8; 0];
46
47/// A type that represents an instance variable.
48#[repr(C)]
49pub struct Ivar {
50    _priv: PrivateMarker,
51}
52
53/// A type that represents a method in a class definition.
54#[repr(C)]
55pub struct Method {
56    _priv: PrivateMarker,
57}
58
59/// A type that represents an Objective-C class.
60#[repr(C)]
61pub struct Class {
62    _priv: PrivateMarker,
63}
64
65/// A type that represents an Objective-C protocol.
66#[repr(C)]
67pub struct Protocol {
68    _priv: PrivateMarker,
69}
70
71/// A type that represents an instance of a class.
72#[repr(C)]
73pub struct Object {
74    _priv: PrivateMarker,
75}
76
77/// A pointer to the start of a method implementation.
78pub type Imp = unsafe extern "C" fn();
79
80#[link(name = "objc", kind = "dylib")]
81unsafe extern "C" {
82    pub fn sel_registerName(name: *const c_char) -> Sel;
83    pub fn sel_getName(sel: Sel) -> *const c_char;
84
85    pub fn class_getName(cls: *const Class) -> *const c_char;
86    pub fn class_getSuperclass(cls: *const Class) -> *const Class;
87    pub fn class_getInstanceSize(cls: *const Class) -> usize;
88    pub fn class_getInstanceMethod(cls: *const Class, sel: Sel) -> *const Method;
89    pub fn class_getInstanceVariable(cls: *const Class, name: *const c_char) -> *const Ivar;
90    pub fn class_copyMethodList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Method;
91    pub fn class_copyIvarList(cls: *const Class, outCount: *mut c_uint) -> *mut *const Ivar;
92    pub fn class_addMethod(cls: *mut Class, name: Sel, imp: Imp, types: *const c_char) -> BOOL;
93    pub fn class_addIvar(
94        cls: *mut Class,
95        name: *const c_char,
96        size: usize,
97        alignment: u8,
98        types: *const c_char,
99    ) -> BOOL;
100    pub fn class_addProtocol(cls: *mut Class, proto: *const Protocol) -> BOOL;
101    pub fn class_conformsToProtocol(cls: *const Class, proto: *const Protocol) -> BOOL;
102    pub fn class_copyProtocolList(cls: *const Class, outCount: *mut c_uint)
103    -> *mut *const Protocol;
104
105    pub fn objc_allocateClassPair(
106        superclass: *const Class,
107        name: *const c_char,
108        extraBytes: usize,
109    ) -> *mut Class;
110    pub fn objc_disposeClassPair(cls: *mut Class);
111    pub fn objc_registerClassPair(cls: *mut Class);
112
113    pub fn class_createInstance(cls: *const Class, extraBytes: usize) -> *mut Object;
114    pub fn object_dispose(obj: *mut Object) -> *mut Object;
115    pub fn object_getClass(obj: *const Object) -> *const Class;
116
117    pub fn objc_getClassList(buffer: *mut *const Class, bufferLen: c_int) -> c_int;
118    pub fn objc_copyClassList(outCount: *mut c_uint) -> *mut *const Class;
119    pub fn objc_getClass(name: *const c_char) -> *const Class;
120    pub fn objc_getProtocol(name: *const c_char) -> *const Protocol;
121    pub fn objc_copyProtocolList(outCount: *mut c_uint) -> *mut *const Protocol;
122    pub fn objc_allocateProtocol(name: *const c_char) -> *mut Protocol;
123    pub fn objc_registerProtocol(proto: *mut Protocol);
124
125    pub fn objc_autoreleasePoolPush() -> *mut c_void;
126    pub fn objc_autoreleasePoolPop(context: *mut c_void);
127
128    pub fn protocol_addMethodDescription(
129        proto: *mut Protocol,
130        name: Sel,
131        types: *const c_char,
132        isRequiredMethod: BOOL,
133        isInstanceMethod: BOOL,
134    );
135    pub fn protocol_addProtocol(proto: *mut Protocol, addition: *const Protocol);
136    pub fn protocol_getName(proto: *const Protocol) -> *const c_char;
137    pub fn protocol_isEqual(proto: *const Protocol, other: *const Protocol) -> BOOL;
138    pub fn protocol_copyProtocolList(
139        proto: *const Protocol,
140        outCount: *mut c_uint,
141    ) -> *mut *const Protocol;
142    pub fn protocol_conformsToProtocol(proto: *const Protocol, other: *const Protocol) -> BOOL;
143
144    pub fn ivar_getName(ivar: *const Ivar) -> *const c_char;
145    pub fn ivar_getOffset(ivar: *const Ivar) -> isize;
146    pub fn ivar_getTypeEncoding(ivar: *const Ivar) -> *const c_char;
147
148    pub fn method_getName(method: *const Method) -> Sel;
149    pub fn method_getImplementation(method: *const Method) -> Imp;
150    pub fn method_copyReturnType(method: *const Method) -> *mut c_char;
151    pub fn method_copyArgumentType(method: *const Method, index: c_uint) -> *mut c_char;
152    pub fn method_getNumberOfArguments(method: *const Method) -> c_uint;
153    pub fn method_setImplementation(method: *mut Method, imp: Imp) -> Imp;
154    pub fn method_exchangeImplementations(m1: *mut Method, m2: *mut Method);
155
156    pub fn objc_retain(obj: *mut Object) -> *mut Object;
157    pub fn objc_release(obj: *mut Object);
158    pub fn objc_autorelease(obj: *mut Object);
159
160    pub fn objc_loadWeakRetained(location: *mut *mut Object) -> *mut Object;
161    pub fn objc_initWeak(location: *mut *mut Object, obj: *mut Object) -> *mut Object;
162    pub fn objc_destroyWeak(location: *mut *mut Object);
163    pub fn objc_copyWeak(to: *mut *mut Object, from: *mut *mut Object);
164}
165
166impl Sel {
167    /// Registers a method with the Objective-C runtime system,
168    /// maps the method name to a selector, and returns the selector value.
169    pub fn register(name: &str) -> Sel {
170        let name = CString::new(name).unwrap();
171        unsafe { sel_registerName(name.as_ptr()) }
172    }
173
174    /// Returns the name of the method specified by self.
175    pub fn name(&self) -> &str {
176        let name = unsafe { CStr::from_ptr(sel_getName(*self)) };
177        str::from_utf8(name.to_bytes()).unwrap()
178    }
179
180    /// Wraps a raw pointer to a selector into a `Sel` object.
181    ///
182    /// This is almost never what you want; use `Sel::register()` instead.
183    #[inline]
184    pub unsafe fn from_ptr(ptr: *const c_void) -> Sel {
185        Sel { ptr: ptr }
186    }
187
188    /// Returns a pointer to the raw selector.
189    #[inline]
190    pub fn as_ptr(&self) -> *const c_void {
191        self.ptr
192    }
193}
194
195impl PartialEq for Sel {
196    fn eq(&self, other: &Sel) -> bool {
197        self.ptr == other.ptr
198    }
199}
200
201impl Eq for Sel {}
202
203// Sel is safe to share across threads because it is immutable
204unsafe impl Sync for Sel {}
205unsafe impl Send for Sel {}
206
207impl Copy for Sel {}
208
209impl Clone for Sel {
210    fn clone(&self) -> Sel {
211        *self
212    }
213}
214
215impl fmt::Debug for Sel {
216    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
217        write!(f, "{}", self.name())
218    }
219}
220
221impl Ivar {
222    /// Returns the name of self.
223    pub fn name(&self) -> &str {
224        let name = unsafe { CStr::from_ptr(ivar_getName(self)) };
225        str::from_utf8(name.to_bytes()).unwrap()
226    }
227
228    /// Returns the offset of self.
229    pub fn offset(&self) -> isize {
230        let offset = unsafe { ivar_getOffset(self) };
231        offset as isize
232    }
233
234    /// Returns the `Encoding` of self.
235    pub fn type_encoding(&self) -> Encoding {
236        let encoding = unsafe { CStr::from_ptr(ivar_getTypeEncoding(self)) };
237        let s = str::from_utf8(encoding.to_bytes()).unwrap();
238        encode::from_str(s)
239    }
240}
241
242impl Method {
243    /// Returns the name of self.
244    pub fn name(&self) -> Sel {
245        unsafe { method_getName(self) }
246    }
247
248    /// Returns the `Encoding` of self's return type.
249    pub fn return_type(&self) -> Encoding {
250        unsafe {
251            let encoding = method_copyReturnType(self);
252            encode::from_malloc_str(encoding)
253        }
254    }
255
256    /// Returns the `Encoding` of a single parameter type of self, or
257    /// `None` if self has no parameter at the given index.
258    pub fn argument_type(&self, index: usize) -> Option<Encoding> {
259        unsafe {
260            let encoding = method_copyArgumentType(self, index as c_uint);
261            if encoding.is_null() {
262                None
263            } else {
264                Some(encode::from_malloc_str(encoding))
265            }
266        }
267    }
268
269    /// Returns the number of arguments accepted by self.
270    pub fn arguments_count(&self) -> usize {
271        unsafe { method_getNumberOfArguments(self) as usize }
272    }
273
274    /// Returns the implementation of self.
275    pub fn implementation(&self) -> Imp {
276        unsafe { method_getImplementation(self) }
277    }
278}
279
280impl Class {
281    /// Returns the class definition of a specified class, or `None` if the
282    /// class is not registered with the Objective-C runtime.
283    pub fn get(name: &str) -> Option<&'static Class> {
284        let name = CString::new(name).unwrap();
285        unsafe {
286            let cls = objc_getClass(name.as_ptr());
287            if cls.is_null() { None } else { Some(&*cls) }
288        }
289    }
290
291    /// Obtains the list of registered class definitions.
292    pub fn classes() -> Malloc<[&'static Class]> {
293        unsafe {
294            let mut count: c_uint = 0;
295            let classes = objc_copyClassList(&mut count);
296            Malloc::from_array(classes as *mut _, count as usize)
297        }
298    }
299
300    /// Returns the total number of registered classes.
301    pub fn classes_count() -> usize {
302        unsafe { objc_getClassList(ptr::null_mut(), 0) as usize }
303    }
304
305    /// Returns the name of self.
306    pub fn name(&self) -> &str {
307        let name = unsafe { CStr::from_ptr(class_getName(self)) };
308        str::from_utf8(name.to_bytes()).unwrap()
309    }
310
311    /// Returns the superclass of self, or `None` if self is a root class.
312    pub fn superclass(&self) -> Option<&Class> {
313        unsafe {
314            let superclass = class_getSuperclass(self);
315            if superclass.is_null() {
316                None
317            } else {
318                Some(&*superclass)
319            }
320        }
321    }
322
323    /// Returns the metaclass of self.
324    pub fn metaclass(&self) -> &Class {
325        unsafe {
326            let self_ptr: *const Class = self;
327            &*object_getClass(self_ptr as *const Object)
328        }
329    }
330
331    /// Returns the size of instances of self.
332    pub fn instance_size(&self) -> usize {
333        unsafe { class_getInstanceSize(self) as usize }
334    }
335
336    /// Returns a specified instance method for self, or `None` if self and
337    /// its superclasses do not contain an instance method with the
338    /// specified selector.
339    pub fn instance_method(&self, sel: Sel) -> Option<&Method> {
340        unsafe {
341            let method = class_getInstanceMethod(self, sel);
342            if method.is_null() {
343                None
344            } else {
345                Some(&*method)
346            }
347        }
348    }
349
350    /// Returns the ivar for a specified instance variable of self, or `None`
351    /// if self has no ivar with the given name.
352    pub fn instance_variable(&self, name: &str) -> Option<&Ivar> {
353        let name = CString::new(name).unwrap();
354        unsafe {
355            let ivar = class_getInstanceVariable(self, name.as_ptr());
356            if ivar.is_null() { None } else { Some(&*ivar) }
357        }
358    }
359
360    /// Describes the instance methods implemented by self.
361    pub fn instance_methods(&self) -> Malloc<[&Method]> {
362        unsafe {
363            let mut count: c_uint = 0;
364            let methods = class_copyMethodList(self, &mut count);
365            Malloc::from_array(methods as *mut _, count as usize)
366        }
367    }
368
369    /// Checks whether this class conforms to the specified protocol.
370    pub fn conforms_to(&self, proto: &Protocol) -> bool {
371        unsafe { class_conformsToProtocol(self, proto) == YES }
372    }
373
374    /// Get a list of the protocols to which this class conforms.
375    pub fn adopted_protocols(&self) -> Malloc<[&Protocol]> {
376        unsafe {
377            let mut count: c_uint = 0;
378            let protos = class_copyProtocolList(self, &mut count);
379            Malloc::from_array(protos as *mut _, count as usize)
380        }
381    }
382
383    /// Describes the instance variables declared by self.
384    pub fn instance_variables(&self) -> Malloc<[&Ivar]> {
385        unsafe {
386            let mut count: c_uint = 0;
387            let ivars = class_copyIvarList(self, &mut count);
388            Malloc::from_array(ivars as *mut _, count as usize)
389        }
390    }
391}
392
393impl PartialEq for Class {
394    fn eq(&self, other: &Class) -> bool {
395        let self_ptr: *const Class = self;
396        let other_ptr: *const Class = other;
397        self_ptr == other_ptr
398    }
399}
400
401impl Eq for Class {}
402
403impl fmt::Debug for Class {
404    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
405        write!(f, "{}", self.name())
406    }
407}
408
409impl Protocol {
410    /// Returns the protocol definition of a specified protocol, or `None` if the
411    /// protocol is not registered with the Objective-C runtime.
412    pub fn get(name: &str) -> Option<&'static Protocol> {
413        let name = CString::new(name).unwrap();
414        unsafe {
415            let proto = objc_getProtocol(name.as_ptr());
416            if proto.is_null() { None } else { Some(&*proto) }
417        }
418    }
419
420    /// Obtains the list of registered protocol definitions.
421    pub fn protocols() -> Malloc<[&'static Protocol]> {
422        unsafe {
423            let mut count: c_uint = 0;
424            let protocols = objc_copyProtocolList(&mut count);
425            Malloc::from_array(protocols as *mut _, count as usize)
426        }
427    }
428
429    /// Get a list of the protocols to which this protocol conforms.
430    pub fn adopted_protocols(&self) -> Malloc<[&Protocol]> {
431        unsafe {
432            let mut count: c_uint = 0;
433            let protocols = protocol_copyProtocolList(self, &mut count);
434            Malloc::from_array(protocols as *mut _, count as usize)
435        }
436    }
437
438    /// Checks whether this protocol conforms to the specified protocol.
439    pub fn conforms_to(&self, proto: &Protocol) -> bool {
440        unsafe { protocol_conformsToProtocol(self, proto) == YES }
441    }
442
443    /// Returns the name of self.
444    pub fn name(&self) -> &str {
445        let name = unsafe { CStr::from_ptr(protocol_getName(self)) };
446        str::from_utf8(name.to_bytes()).unwrap()
447    }
448}
449
450impl PartialEq for Protocol {
451    fn eq(&self, other: &Protocol) -> bool {
452        unsafe { protocol_isEqual(self, other) == YES }
453    }
454}
455
456impl Eq for Protocol {}
457
458impl fmt::Debug for Protocol {
459    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
460        write!(f, "{}", self.name())
461    }
462}
463
464impl Object {
465    /// Returns the class of self.
466    pub fn class(&self) -> &Class {
467        unsafe { &*object_getClass(self) }
468    }
469
470    /// Returns a reference to the ivar of self with the given name.
471    /// Panics if self has no ivar with the given name.
472    /// Unsafe because the caller must ensure that the ivar is actually
473    /// of type `T`.
474    pub unsafe fn get_ivar<T>(&self, name: &str) -> &T
475    where
476        T: Encode,
477    {
478        let offset = {
479            let cls = self.class();
480            match cls.instance_variable(name) {
481                Some(ivar) => {
482                    assert!(ivar.type_encoding() == T::encode());
483                    ivar.offset()
484                }
485                None => panic!("Ivar {} not found on class {:?}", name, cls),
486            }
487        };
488        let ptr = unsafe {
489            let self_ptr: *const Object = self;
490            (self_ptr as *const u8).offset(offset) as *const T
491        };
492        unsafe { &*ptr }
493    }
494
495    /// Returns a mutable reference to the ivar of self with the given name.
496    /// Panics if self has no ivar with the given name.
497    /// Unsafe because the caller must ensure that the ivar is actually
498    /// of type `T`.
499    pub unsafe fn get_mut_ivar<T>(&mut self, name: &str) -> &mut T
500    where
501        T: Encode,
502    {
503        let offset = {
504            let cls = self.class();
505            match cls.instance_variable(name) {
506                Some(ivar) => {
507                    assert!(ivar.type_encoding() == T::encode());
508                    ivar.offset()
509                }
510                None => panic!("Ivar {} not found on class {:?}", name, cls),
511            }
512        };
513        let ptr = unsafe {
514            let self_ptr: *mut Object = self;
515            (self_ptr as *mut u8).offset(offset) as *mut T
516        };
517        unsafe { &mut *ptr }
518    }
519
520    /// Sets the value of the ivar of self with the given name.
521    /// Panics if self has no ivar with the given name.
522    /// Unsafe because the caller must ensure that the ivar is actually
523    /// of type `T`.
524    pub unsafe fn set_ivar<T>(&mut self, name: &str, value: T)
525    where
526        T: Encode,
527    {
528        unsafe {
529            *self.get_mut_ivar::<T>(name) = value;
530        }
531    }
532}
533
534impl fmt::Debug for Object {
535    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
536        write!(f, "<{:?}: {:p}>", self.class(), self)
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use super::{Class, Protocol, Sel};
543    use crate::Encode;
544    use crate::test_utils;
545
546    #[test]
547    fn test_ivar() {
548        let cls = test_utils::custom_class();
549        let ivar = cls.instance_variable("_foo").unwrap();
550        assert!(ivar.name() == "_foo");
551        assert!(ivar.type_encoding() == <u32>::encode());
552        assert!(ivar.offset() > 0);
553
554        let ivars = cls.instance_variables();
555        assert!(ivars.len() > 0);
556    }
557
558    #[test]
559    fn test_method() {
560        let cls = test_utils::custom_class();
561        let sel = Sel::register("foo");
562        let method = cls.instance_method(sel).unwrap();
563        assert!(method.name().name() == "foo");
564        assert!(method.arguments_count() == 2);
565        assert!(method.return_type() == <u32>::encode());
566        assert!(method.argument_type(1).unwrap() == Sel::encode());
567
568        let methods = cls.instance_methods();
569        assert!(methods.len() > 0);
570    }
571
572    #[test]
573    fn test_class() {
574        let cls = test_utils::custom_class();
575        assert!(cls.name() == "CustomObject");
576        assert!(cls.instance_size() > 0);
577        assert!(cls.superclass().is_none());
578
579        assert!(Class::get(cls.name()) == Some(cls));
580
581        let metaclass = cls.metaclass();
582        // The metaclass of a root class is a subclass of the root class
583        assert!(metaclass.superclass().unwrap() == cls);
584
585        let subclass = test_utils::custom_subclass();
586        assert!(subclass.superclass().unwrap() == cls);
587    }
588
589    #[test]
590    fn test_classes() {
591        assert!(Class::classes_count() > 0);
592        let classes = Class::classes();
593        assert!(classes.len() > 0);
594    }
595
596    #[test]
597    fn test_protocol() {
598        let proto = test_utils::custom_protocol();
599        assert!(proto.name() == "CustomProtocol");
600        let class = test_utils::custom_class();
601        assert!(class.conforms_to(proto));
602        let class_protocols = class.adopted_protocols();
603        assert!(class_protocols.len() > 0);
604    }
605
606    #[test]
607    fn test_protocol_method() {
608        let class = test_utils::custom_class();
609        let result: i32 = unsafe { msg_send![class, addNumber:1 toNumber:2] };
610        assert_eq!(result, 3);
611    }
612
613    #[test]
614    fn test_subprotocols() {
615        let sub_proto = test_utils::custom_subprotocol();
616        let super_proto = test_utils::custom_protocol();
617        assert!(sub_proto.conforms_to(super_proto));
618        let adopted_protocols = sub_proto.adopted_protocols();
619        assert_eq!(adopted_protocols[0], super_proto);
620    }
621
622    #[test]
623    fn test_protocols() {
624        // Ensure that a protocol has been registered on linux
625        let _ = test_utils::custom_protocol();
626
627        let protocols = Protocol::protocols();
628        assert!(protocols.len() > 0);
629    }
630
631    #[test]
632    fn test_object() {
633        let mut obj = test_utils::custom_object();
634        assert!(obj.class() == test_utils::custom_class());
635        let result: u32 = unsafe {
636            obj.set_ivar("_foo", 4u32);
637            *obj.get_ivar("_foo")
638        };
639        assert!(result == 4);
640    }
641}