Struct objc2::runtime::ClassBuilder
source · pub struct ClassBuilder { /* private fields */ }
Expand description
A type for declaring a new class and adding new methods and ivars to it before registering it.
Note: You likely don’t need the dynamicism that this provides!
Consider using the declare_class!
macro instead.
§Example
Declare a class named MyNumber
that has one ivar, a u32
named _number
and a few constructor methods and methods for interfacing with the number
(using interior mutability, as is common for Objective-C objects).
use core::cell::Cell;
use objc2::declare::ClassBuilder;
use objc2::rc::Retained;
use objc2::runtime::{AnyClass, AnyObject, NSObject, Sel};
use objc2::{sel, msg_send, msg_send_id, ClassType};
fn register_class() -> &'static AnyClass {
// Inherit from NSObject
let mut builder = ClassBuilder::new("MyNumber", NSObject::class())
.expect("a class with the name MyNumber likely already exists");
// Add an instance variable of type `Cell<u32>`
builder.add_ivar::<Cell<u32>>("_number");
// Add an Objective-C method for initializing an instance with a number
//
// We "cheat" a bit here, and use `AnyObject` instead of `NSObject`,
// since only the former is allowed to be a mutable receiver (which is
// always safe in `init` methods, but not in others).
unsafe extern "C" fn init_with_number(
this: &mut AnyObject,
_cmd: Sel,
number: u32,
) -> Option<&mut AnyObject> {
let this: Option<&mut AnyObject> = msg_send![super(this, NSObject::class()), init];
this.map(|this| {
let ivar = AnyClass::get("MyNumber").unwrap().instance_variable("_number").unwrap();
// SAFETY: The ivar is added with the same type above
*ivar.load_mut::<Cell<u32>>(this) = Cell::new(number);
this
})
}
unsafe {
builder.add_method(
sel!(initWithNumber:),
init_with_number as unsafe extern "C" fn(_, _, _) -> _,
);
}
// Add convenience method for getting a new instance with the number
extern "C" fn with_number(
cls: &AnyClass,
_cmd: Sel,
number: u32,
) -> *mut NSObject {
let obj: Option<Retained<NSObject>> = unsafe {
msg_send_id![
msg_send_id![cls, alloc],
initWithNumber: number,
]
};
obj.map(Retained::autorelease_return).unwrap_or(std::ptr::null_mut())
}
unsafe {
builder.add_class_method(
sel!(withNumber:),
with_number as extern "C" fn(_, _, _) -> _,
);
}
// Add an Objective-C method for setting the number
extern "C" fn my_number_set(this: &NSObject, _cmd: Sel, number: u32) {
let ivar = AnyClass::get("MyNumber").unwrap().instance_variable("_number").unwrap();
// SAFETY: The ivar is added with the same type above
unsafe { ivar.load::<Cell<u32>>(this) }.set(number);
}
unsafe {
builder.add_method(sel!(setNumber:), my_number_set as extern "C" fn(_, _, _));
}
// Add an Objective-C method for getting the number
extern "C" fn my_number_get(this: &NSObject, _cmd: Sel) -> u32 {
let ivar = AnyClass::get("MyNumber").unwrap().instance_variable("_number").unwrap();
// SAFETY: The ivar is added with the same type above
unsafe { ivar.load::<Cell<u32>>(this) }.get()
}
unsafe {
builder.add_method(sel!(number), my_number_get as extern "C" fn(_, _) -> _);
}
builder.register()
}
// Usage
// Note: you should only do class registration once! This can be ensured
// with `std::sync::Once` or the `once_cell` crate.
let cls = register_class();
let obj: Retained<NSObject> = unsafe {
msg_send_id![cls, withNumber: 42u32]
};
let n: u32 = unsafe { msg_send![&obj, number] };
assert_eq!(n, 42);
let _: () = unsafe { msg_send![&obj, setNumber: 12u32] };
let n: u32 = unsafe { msg_send![&obj, number] };
assert_eq!(n, 12);
Implementations§
source§impl ClassBuilder
impl ClassBuilder
sourcepub fn new(name: &str, superclass: &AnyClass) -> Option<Self>
pub fn new(name: &str, superclass: &AnyClass) -> Option<Self>
Constructs a ClassBuilder
with the given name and superclass.
Returns None
if the class couldn’t be allocated, or a class with
that name already exist.
sourcepub fn root<F>(name: &str, intitialize_fn: F) -> Option<Self>
pub fn root<F>(name: &str, intitialize_fn: F) -> Option<Self>
Constructs a ClassBuilder
declaring a new root class with the
given name.
Returns None
if the class couldn’t be allocated.
An implementation for +initialize
must also be given; the runtime
calls this method for all classes, so it must be defined on root
classes.
Note that implementing a root class is not a simple endeavor!
For example, your class probably cannot be passed to Cocoa code unless
the entire NSObject
protocol is implemented.
Functionality it expects, like implementations of -retain
and
-release
used by ARC, will not be present otherwise.
sourcepub unsafe fn add_method<T, F>(&mut self, sel: Sel, func: F)
pub unsafe fn add_method<T, F>(&mut self, sel: Sel, func: F)
Adds a method with the given name and implementation.
§Panics
Panics if the method wasn’t sucessfully added (e.g. a method with that name already exists).
May also panic if the method was detected to be invalid in some way;
for example if debug_assertions
are enabled and the method is
overriding another method, we verify that their encodings are equal.
§Safety
The caller must ensure that the types match those that are expected when the method is invoked from Objective-C.
sourcepub unsafe fn add_class_method<F>(&mut self, sel: Sel, func: F)where
F: MethodImplementation<Callee = AnyClass>,
pub unsafe fn add_class_method<F>(&mut self, sel: Sel, func: F)where
F: MethodImplementation<Callee = AnyClass>,
Adds a class method with the given name and implementation.
§Panics
Panics in the same cases as add_method
.
§Safety
The caller must ensure that the types match those that are expected when the method is invoked from Objective-C.
sourcepub fn add_ivar<T: Encode>(&mut self, name: &str)
pub fn add_ivar<T: Encode>(&mut self, name: &str)
Adds an ivar with type T
and the provided name.
§Panics
If the ivar wasn’t successfully added for some reason - this usually happens if there already was an ivar with that name.
sourcepub fn add_protocol(&mut self, proto: &AnyProtocol)
pub fn add_protocol(&mut self, proto: &AnyProtocol)
sourcepub fn register(self) -> &'static AnyClass
pub fn register(self) -> &'static AnyClass
Registers the ClassBuilder
, consuming it, and returns a reference
to the newly registered AnyClass
.