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::Id;
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<Id<NSObject>> = unsafe {
            msg_send_id![
                msg_send_id![cls, alloc],
                initWithNumber: number,
            ]
        };
        obj.map(Id::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: Id<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

source

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.

source

pub fn root<F>(name: &str, intitialize_fn: F) -> Option<Self>
where F: MethodImplementation<Callee = AnyClass, Arguments = (), Return = ()>,

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.

source

pub unsafe fn add_method<T, F>(&mut self, sel: Sel, func: F)
where T: Message + ?Sized, F: MethodImplementation<Callee = T>,

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.

source

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.

source

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.

source

pub fn add_protocol(&mut self, proto: &AnyProtocol)

Adds the given protocol to self.

§Panics

If the protocol wasn’t successfully added.

source

pub fn register(self) -> &'static AnyClass

Registers the ClassBuilder, consuming it, and returns a reference to the newly registered AnyClass.

Trait Implementations§

source§

impl Debug for ClassBuilder

source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
source§

impl Drop for ClassBuilder

source§

fn drop(&mut self)

Executes the destructor for this type. Read more
source§

impl Send for ClassBuilder

source§

impl Sync for ClassBuilder

Auto Trait Implementations§

Blanket Implementations§

source§

impl<T> Any for T
where T: 'static + ?Sized,

source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
source§

impl<T> Borrow<T> for T
where T: ?Sized,

source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
source§

impl<T> From<T> for T

source§

fn from(t: T) -> T

Returns the argument unchanged.

source§

impl<T, U> Into<U> for T
where U: From<T>,

source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

§

type Error = Infallible

The type returned in the event of a conversion error.
source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
source§

impl<T> AutoreleaseSafe for T
where T: ?Sized,