pub unsafe trait ClassType: Message {
type Super: Message;
type Mutability: Mutability;
const NAME: &'static str;
// Required methods
fn class() -> &'static AnyClass;
fn as_super(&self) -> &Self::Super;
fn as_super_mut(&mut self) -> &mut Self::Super;
// Provided methods
fn retain(&self) -> Retained<Self> ⓘ
where Self: IsRetainable + Sized { ... }
fn alloc() -> Allocated<Self>
where Self: IsAllocableAnyThread + Sized { ... }
}
Expand description
Marks types that represent specific classes.
Sometimes it is enough to generically know that a type is messageable,
e.g. Retained
works with any type that implements the Message
trait. But often, you have an object that you know represents a specific
Objective-C class - this trait allows you to communicate that, as well as
a few properties of the class to the rest of the type-system.
This is implemented automatically for your type by the
declare_class!
and
extern_class!
macros.
§Safety
-
The type must represent a specific class.
-
Self::Super
must be a superclass of the class (or something that represents any object, likeAnyObject
). -
Self::Mutability
must be specified correctly.Note that very little Objective-C code follows Rust’s usual ownership model. If you think your type’s mutability should be
Mutable
, think again, it very rarely should!If you’re unsure of what to do,
InteriorMutable
+ avoiding&mut
is usually a good starting point. -
Self::NAME
must be correct. -
The class returned by
Self::class
must be the class that this type represents.
§Examples
Use the trait to access the AnyClass
of an object.
use objc2::{ClassType, msg_send_id};
use objc2::rc::Retained;
// Get the class of the object.
let cls = <MyObject as ClassType>::class();
// Or, since the trait is in scope.
let cls = MyObject::class();
// We can now access properties of the class.
assert_eq!(cls.name(), MyObject::NAME);
// And we can send messages to the class.
//
// SAFETY:
// - The class is `MyObject`, which can safely be initialized with `new`.
// - The return type is correctly specified.
let obj: Retained<MyObject> = unsafe { msg_send_id![cls, new] };
Use the trait to allocate a new instance of an object.
use objc2::{ClassType, msg_send_id};
use objc2::rc::Retained;
let obj = MyObject::alloc();
// Now we can call initializers on this newly allocated object.
//
// SAFETY: `MyObject` can safely be initialized with `init`.
let obj: Retained<MyObject> = unsafe { msg_send_id![obj, init] };
Use the extern_class!
macro to implement this
trait for a type.
use objc2::runtime::NSObject;
use objc2::{extern_class, mutability, ClassType};
extern_class!(
struct MyClass;
// SAFETY: The superclass and the mutability is correctly specified.
unsafe impl ClassType for MyClass {
type Super = NSObject;
type Mutability = mutability::InteriorMutable;
}
);
let cls = MyClass::class();
let obj = MyClass::alloc();
Implement the trait manually for a class with a lifetime parameter.
//! Note: We can't use the `declare_class!` macro for this, it doesn't support
//! such use-cases (yet). Instead, we'll declare the class manually.
#![deny(unsafe_op_in_unsafe_fn)]
use std::marker::PhantomData;
use std::sync::Once;
use objc2::mutability::Mutable;
use objc2::rc::Retained;
use objc2::runtime::{AnyClass, ClassBuilder, NSObject, Sel};
use objc2::{msg_send, msg_send_id, sel};
use objc2::{ClassType, Encoding, Message, RefEncode};
/// Struct that represents our custom object.
#[repr(C)]
struct MyObject<'a> {
// Required to give MyObject the proper layout
superclass: NSObject,
p: PhantomData<&'a mut u8>,
}
unsafe impl RefEncode for MyObject<'_> {
const ENCODING_REF: Encoding = NSObject::ENCODING_REF;
}
unsafe impl Message for MyObject<'_> {}
impl<'a> MyObject<'a> {
unsafe extern "C" fn init_with_ptr<'s>(
&'s mut self,
_cmd: Sel,
ptr: Option<&'a mut u8>,
) -> Option<&'s mut Self> {
let this: Option<&mut Self> = unsafe { msg_send![super(self), init] };
this.map(|this| {
let ivar = Self::class().instance_variable("number").unwrap();
// SAFETY: The ivar is added with the same type below
unsafe {
ivar.load_ptr::<&mut u8>(&this.superclass)
.write(ptr.expect("got NULL number ptr"))
};
this
})
}
fn new(number: &'a mut u8) -> Retained<Self> {
// SAFETY: The lifetime of the reference is properly bound to the
// returned type
unsafe { msg_send_id![Self::alloc(), initWithPtr: number] }
}
fn get(&self) -> u8 {
let ivar = Self::class().instance_variable("number").unwrap();
// SAFETY: The ivar is added with the same type below, and is initialized in `init_with_ptr`
unsafe { **ivar.load::<&mut u8>(&self.superclass) }
}
fn set(&mut self, number: u8) {
let ivar = Self::class().instance_variable("number").unwrap();
// SAFETY: The ivar is added with the same type below, and is initialized in `init_with_ptr`
unsafe { **ivar.load_mut::<&mut u8>(&mut self.superclass) = number };
}
}
unsafe impl<'a> ClassType for MyObject<'a> {
type Super = NSObject;
type Mutability = Mutable;
const NAME: &'static str = "MyObject";
fn class() -> &'static AnyClass {
// TODO: Use std::lazy::LazyCell
static REGISTER_CLASS: Once = Once::new();
REGISTER_CLASS.call_once(|| {
let superclass = NSObject::class();
let mut builder = ClassBuilder::new(Self::NAME, superclass).unwrap();
builder.add_ivar::<&mut u8>("number");
unsafe {
builder.add_method(
sel!(initWithPtr:),
Self::init_with_ptr as unsafe extern "C" fn(_, _, _) -> _,
);
}
let _cls = builder.register();
});
AnyClass::get("MyObject").unwrap()
}
fn as_super(&self) -> &Self::Super {
&self.superclass
}
fn as_super_mut(&mut self) -> &mut Self::Super {
&mut self.superclass
}
}
fn main() {
let mut number = 54;
let mut obj = MyObject::new(&mut number);
assert_eq!(obj.get(), 54);
// It is not possible to convert to `Retained<NSObject>`, since that would
// loose the lifetime information that `MyObject` stores.
//
// let obj = Retained::into_super(obj);
//
// Neither is it possible to clone or retain the object, since it is
// marked as `Mutable` in `ClassType::Mutability`.
//
// let obj2 = obj.clone();
//
// Finally, it is not possible to access `number` any more, since `obj`
// holds a mutable reference to it.
//
// assert_eq!(number, 7);
// But we can now mutate the referenced `number`
obj.set(7);
assert_eq!(obj.get(), 7);
drop(obj);
// And now that we've dropped `obj`, we can access `number` again
assert_eq!(number, 7);
}
Required Associated Types§
sourcetype Super: Message
type Super: Message
The superclass of this class.
If you have implemented Deref
for your type, it is highly
recommended that this is equal to Deref::Target
.
This may be AnyObject
if the class is a root class.
sourcetype Mutability: Mutability
type Mutability: Mutability
Whether the type is mutable or immutable.
See the mutability
module for further details
about class mutability.
Required Associated Constants§
Required Methods§
sourcefn class() -> &'static AnyClass
fn class() -> &'static AnyClass
Get a reference to the Objective-C class that this type represents.
May register the class with the runtime if it wasn’t already.
§Panics
This may panic if something went wrong with getting or declaring the class, e.g. if the program is not properly linked to the framework that defines the class.
sourcefn as_super_mut(&mut self) -> &mut Self::Super
fn as_super_mut(&mut self) -> &mut Self::Super
Get a mutable reference to the superclass.
Provided Methods§
sourcefn retain(&self) -> Retained<Self> ⓘwhere
Self: IsRetainable + Sized,
fn retain(&self) -> Retained<Self> ⓘwhere
Self: IsRetainable + Sized,
Increment the reference count of the receiver.
This extends the duration in which the receiver is alive by detaching it from the lifetime information carried by the reference.
This is similar to using Clone
on Retained<Self>
, with
the addition that it can be used on a plain reference. Note however
that this is not possible to use on certain types like NSString
,
since if you only hold &NSString
, that may have come from
&mut NSMutableString
, in which case it would be unsound to erase the
lifetime information carried by the reference.
In cases like that, you should rather use NSCopying::copy
(since
that gives you a NSString
whether the string was originally a
NSString
or a NSMutableString
).
sourcefn alloc() -> Allocated<Self>where
Self: IsAllocableAnyThread + Sized,
fn alloc() -> Allocated<Self>where
Self: IsAllocableAnyThread + Sized,
Allocate a new instance of the class.
The return value can be used directly inside msg_send_id!
to
initialize the object.
For classes that are only usable on the main thread, you can use
MainThreadMarker::alloc
instead.