Skip to main content

objc_rs/
declare.rs

1/*!
2Functionality for declaring Objective-C classes.
3
4Classes can be declared using the `ClassDecl` struct. Instance variables and
5methods can then be added before the class is ultimately registered.
6
7# Example
8
9The following example demonstrates declaring a class named `MyNumber` that has
10one ivar, a `u32` named `_number` and a `number` method that returns it:
11
12``` no_run
13# #[macro_use] extern crate objc;
14# use objc::declare::ClassDecl;
15# use objc::runtime::{Class, Object, Sel};
16# fn main() {
17let superclass = class!(NSObject);
18let mut decl = ClassDecl::new("MyNumber", superclass).unwrap();
19
20// Add an instance variable
21decl.add_ivar::<u32>("_number");
22
23// Add an ObjC method for getting the number
24extern "C" fn my_number_get(this: &Object, _cmd: Sel) -> u32 {
25    unsafe { *this.get_ivar("_number") }
26}
27unsafe {
28    decl.add_method(sel!(number),
29        my_number_get as extern "C" fn(&Object, Sel) -> u32);
30}
31
32decl.register();
33# }
34```
35*/
36
37use std::ffi::CString;
38use std::mem;
39use std::ptr;
40
41use crate::runtime::{self, BOOL, Class, Imp, NO, Object, Protocol, Sel};
42use crate::{Encode, EncodeArguments, Encoding, Message};
43
44/// Types that can be used as the implementation of an Objective-C method.
45pub trait MethodImplementation {
46    /// The callee type of the method.
47    type Callee: Message;
48    /// The return type of the method.
49    type Ret: Encode;
50    /// The argument types of the method.
51    type Args: EncodeArguments;
52
53    /// Returns self as an `Imp` of a method.
54    fn imp(self) -> Imp;
55}
56
57macro_rules! method_decl_impl {
58    (-$s:ident, $r:ident, $f:ty, $($t:ident),*) => (
59        impl<$s, $r $(, $t)*> MethodImplementation for $f
60                where $s: Message, $r: Encode $(, $t: Encode)* {
61            type Callee = $s;
62            type Ret = $r;
63            type Args = ($($t,)*);
64
65            fn imp(self) -> Imp {
66                unsafe { mem::transmute(self) }
67            }
68        }
69    );
70    ($($t:ident),*) => (
71        method_decl_impl!(-T, R, extern "C" fn(&T, Sel $(, $t)*) -> R, $($t),*);
72        method_decl_impl!(-T, R, extern "C" fn(&mut T, Sel $(, $t)*) -> R, $($t),*);
73    );
74}
75
76method_decl_impl!();
77method_decl_impl!(A);
78method_decl_impl!(A, B);
79method_decl_impl!(A, B, C);
80method_decl_impl!(A, B, C, D);
81method_decl_impl!(A, B, C, D, E);
82method_decl_impl!(A, B, C, D, E, F);
83method_decl_impl!(A, B, C, D, E, F, G);
84method_decl_impl!(A, B, C, D, E, F, G, H);
85method_decl_impl!(A, B, C, D, E, F, G, H, I);
86method_decl_impl!(A, B, C, D, E, F, G, H, I, J);
87method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K);
88method_decl_impl!(A, B, C, D, E, F, G, H, I, J, K, L);
89
90fn count_args(sel: Sel) -> usize {
91    sel.name().chars().filter(|&c| c == ':').count()
92}
93
94fn method_type_encoding(ret: &Encoding, args: &[Encoding]) -> CString {
95    let mut types = ret.as_str().to_owned();
96    // First two arguments are always self and the selector
97    types.push_str(<*mut Object>::encode().as_str());
98    types.push_str(Sel::encode().as_str());
99    types.extend(args.iter().map(|e| e.as_str()));
100    CString::new(types).unwrap()
101}
102
103fn log2_align_of<T>() -> u8 {
104    let align = mem::align_of::<T>();
105    // Alignments are required to be powers of 2
106    debug_assert!(align.count_ones() == 1);
107    // log2 of a power of 2 is the number of trailing zeros
108    align.trailing_zeros() as u8
109}
110
111/// A type for declaring a new class and adding new methods and ivars to it
112/// before registering it.
113pub struct ClassDecl {
114    cls: *mut Class,
115}
116
117impl ClassDecl {
118    fn with_superclass(name: &str, superclass: Option<&Class>) -> Option<ClassDecl> {
119        let name = CString::new(name).unwrap();
120        let super_ptr = superclass.map_or(ptr::null(), |c| c);
121        let cls = unsafe { runtime::objc_allocateClassPair(super_ptr, name.as_ptr(), 0) };
122        if cls.is_null() {
123            None
124        } else {
125            Some(ClassDecl { cls: cls })
126        }
127    }
128
129    /// Constructs a `ClassDecl` with the given name and superclass.
130    /// Returns `None` if the class couldn't be allocated.
131    pub fn new(name: &str, superclass: &Class) -> Option<ClassDecl> {
132        ClassDecl::with_superclass(name, Some(superclass))
133    }
134
135    /**
136    Constructs a `ClassDecl` declaring a new root class with the given name.
137    Returns `None` if the class couldn't be allocated.
138
139    An implementation for `+initialize` must also be given; the runtime calls
140    this method for all classes, so it must be defined on root classes.
141
142    Note that implementing a root class is not a simple endeavor.
143    For example, your class probably cannot be passed to Cocoa code unless
144    the entire `NSObject` protocol is implemented.
145    Functionality it expects, like implementations of `-retain` and `-release`
146    used by ARC, will not be present otherwise.
147    */
148    pub fn root(name: &str, intitialize_fn: extern "C" fn(&Class, Sel)) -> Option<ClassDecl> {
149        let mut decl = ClassDecl::with_superclass(name, None);
150        if let Some(ref mut decl) = decl {
151            unsafe {
152                decl.add_class_method(sel!(initialize), intitialize_fn);
153            }
154        }
155        decl
156    }
157
158    /// Adds a method with the given name and implementation to self.
159    /// Panics if the method wasn't sucessfully added
160    /// or if the selector and function take different numbers of arguments.
161    /// Unsafe because the caller must ensure that the types match those that
162    /// are expected when the method is invoked from Objective-C.
163    pub unsafe fn add_method<F>(&mut self, sel: Sel, func: F)
164    where
165        F: MethodImplementation<Callee = Object>,
166    {
167        let encs = F::Args::encodings();
168        let encs = encs.as_ref();
169        let sel_args = count_args(sel);
170        assert!(
171            sel_args == encs.len(),
172            "Selector accepts {} arguments, but function accepts {}",
173            sel_args,
174            encs.len(),
175        );
176
177        let types = method_type_encoding(&F::Ret::encode(), encs);
178        let success =
179            unsafe { runtime::class_addMethod(self.cls, sel, func.imp(), types.as_ptr()) };
180        assert!(success != NO, "Failed to add method {:?}", sel);
181    }
182
183    /// Adds a class method with the given name and implementation to self.
184    /// Panics if the method wasn't sucessfully added
185    /// or if the selector and function take different numbers of arguments.
186    /// Unsafe because the caller must ensure that the types match those that
187    /// are expected when the method is invoked from Objective-C.
188    pub unsafe fn add_class_method<F>(&mut self, sel: Sel, func: F)
189    where
190        F: MethodImplementation<Callee = Class>,
191    {
192        let encs = F::Args::encodings();
193        let encs = encs.as_ref();
194        let sel_args = count_args(sel);
195        assert!(
196            sel_args == encs.len(),
197            "Selector accepts {} arguments, but function accepts {}",
198            sel_args,
199            encs.len(),
200        );
201
202        let types = method_type_encoding(&F::Ret::encode(), encs);
203        let metaclass = unsafe { (*self.cls).metaclass() as *const _ as *mut _ };
204        let success =
205            unsafe { runtime::class_addMethod(metaclass, sel, func.imp(), types.as_ptr()) };
206        assert!(success != NO, "Failed to add class method {:?}", sel);
207    }
208
209    /// Adds an ivar with type `T` and the provided name to self.
210    /// Panics if the ivar wasn't successfully added.
211    pub fn add_ivar<T>(&mut self, name: &str)
212    where
213        T: Encode,
214    {
215        let c_name = CString::new(name).unwrap();
216        let encoding = CString::new(T::encode().as_str()).unwrap();
217        let size = mem::size_of::<T>();
218        let align = log2_align_of::<T>();
219        let success = unsafe {
220            runtime::class_addIvar(self.cls, c_name.as_ptr(), size, align, encoding.as_ptr())
221        };
222        assert!(success != NO, "Failed to add ivar {}", name);
223    }
224
225    /// Adds a protocol to self. Panics if the protocol wasn't successfully
226    /// added
227    pub fn add_protocol(&mut self, proto: &Protocol) {
228        let success = unsafe { runtime::class_addProtocol(self.cls, proto) };
229        assert!(success != NO, "Failed to add protocol {:?}", proto);
230    }
231
232    /// Registers self, consuming it and returning a reference to the
233    /// newly registered `Class`.
234    pub fn register(self) -> &'static Class {
235        unsafe {
236            let cls = self.cls;
237            runtime::objc_registerClassPair(cls);
238            // Forget self otherwise the class will be disposed in drop
239            mem::forget(self);
240            &*cls
241        }
242    }
243}
244
245impl Drop for ClassDecl {
246    fn drop(&mut self) {
247        unsafe {
248            runtime::objc_disposeClassPair(self.cls);
249        }
250    }
251}
252
253/// A type for declaring a new protocol and adding new methods to it
254/// before registering it.
255pub struct ProtocolDecl {
256    proto: *mut Protocol,
257}
258
259impl ProtocolDecl {
260    /// Constructs a `ProtocolDecl` with the given name. Returns `None` if the
261    /// protocol couldn't be allocated.
262    pub fn new(name: &str) -> Option<ProtocolDecl> {
263        let c_name = CString::new(name).unwrap();
264        let proto = unsafe { runtime::objc_allocateProtocol(c_name.as_ptr()) };
265        if proto.is_null() {
266            None
267        } else {
268            Some(ProtocolDecl { proto: proto })
269        }
270    }
271
272    fn add_method_description_common<Args, Ret>(
273        &mut self,
274        sel: Sel,
275        is_required: bool,
276        is_instance_method: bool,
277    ) where
278        Args: EncodeArguments,
279        Ret: Encode,
280    {
281        let encs = Args::encodings();
282        let encs = encs.as_ref();
283        let sel_args = count_args(sel);
284        assert!(
285            sel_args == encs.len(),
286            "Selector accepts {} arguments, but function accepts {}",
287            sel_args,
288            encs.len(),
289        );
290        let types = method_type_encoding(&Ret::encode(), encs);
291        unsafe {
292            runtime::protocol_addMethodDescription(
293                self.proto,
294                sel,
295                types.as_ptr(),
296                is_required as BOOL,
297                is_instance_method as BOOL,
298            );
299        }
300    }
301
302    /// Adds an instance method declaration with a given description to self.
303    pub fn add_method_description<Args, Ret>(&mut self, sel: Sel, is_required: bool)
304    where
305        Args: EncodeArguments,
306        Ret: Encode,
307    {
308        self.add_method_description_common::<Args, Ret>(sel, is_required, true)
309    }
310
311    /// Adds a class method declaration with a given description to self.
312    pub fn add_class_method_description<Args, Ret>(&mut self, sel: Sel, is_required: bool)
313    where
314        Args: EncodeArguments,
315        Ret: Encode,
316    {
317        self.add_method_description_common::<Args, Ret>(sel, is_required, false)
318    }
319
320    /// Adds a requirement on another protocol.
321    pub fn add_protocol(&mut self, proto: &Protocol) {
322        unsafe {
323            runtime::protocol_addProtocol(self.proto, proto);
324        }
325    }
326
327    /// Registers self, consuming it and returning a reference to the
328    /// newly registered `Protocol`.
329    pub fn register(self) -> &'static Protocol {
330        unsafe {
331            runtime::objc_registerProtocol(self.proto);
332            &*self.proto
333        }
334    }
335}
336
337#[cfg(test)]
338mod tests {
339    use crate::test_utils;
340
341    #[test]
342    fn test_custom_class() {
343        // Registering the custom class is in test_utils
344        let obj = test_utils::custom_object();
345        unsafe {
346            let _: () = msg_send![obj, setFoo:13u32];
347            let result: u32 = msg_send![obj, foo];
348            assert!(result == 13);
349        }
350    }
351
352    #[test]
353    fn test_class_method() {
354        let cls = test_utils::custom_class();
355        unsafe {
356            let result: u32 = msg_send![cls, classFoo];
357            assert!(result == 7);
358        }
359    }
360}