/// Create a new Objective-C class. /// /// This is useful in many cases since Objective-C frameworks tend to favour a /// design pattern using "delegates", where to hook into a piece of /// functionality in a class, you implement that class' delegate protocol in /// a custom class. /// /// This macro is the declarative way of creating classes, in contrast with /// [`ClassBuilder`], which allows creating classes in an imperative fashion. /// It is highly recommended that you use this macro though, since it contains /// a lot of extra debug assertions and niceties that help ensure the /// soundness of your code. /// /// The class is guaranteed to have been created and registered with the /// Objective-C runtime after the [`ClassType::class`] function has been /// called. /// /// See [Apple's documentation] on defining classes for a more in-depth /// introduction. /// /// [Apple's documentation]: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ProgrammingWithObjectiveC/DefiningClasses/DefiningClasses.html /// [`ClassBuilder`]: crate::runtime::ClassBuilder /// [`ClassType::class`]: crate::ClassType::class /// /// /// # Specification /// /// This macro consists of the following parts: /// - The type definition, along with special attributes. /// - Any number of inherent implementations. /// - Any number of protocol implementations. /// /// With the syntax generally resembling a combination of that in /// [`extern_class!`] and [`extern_methods!`]. /// /// This macro creates an opaque struct with implementations in [a similar /// manner as the `extern_class!` macro][ec_spec]. Additionally, it implements /// the [`DefinedClass`] trait, as well as any protocols specified in the /// protocol implementations. /// /// If the type implements [`Drop`], the macro will generate a `dealloc` /// method for you, which will call `drop` automatically. /// /// The macro does not support generic types. /// /// [`extern_class!`]: crate::extern_class /// [`extern_methods!`]: crate::extern_methods /// [ec_spec]: crate::extern_class#specification /// [`DefinedClass`]: crate::DefinedClass /// /// /// ## Attributes /// /// You can add most normal attributes to the class, including `#[cfg(...)]`, /// `#[allow(...)]` and doc comments. /// /// Exceptions and special attributes are noted below. /// /// /// ### `#[unsafe(super(...))]` (required) /// /// Same [as in `extern_class!`](crate::extern_class#unsafesuper-required). /// /// /// ### `#[thread_kind = ...]` (optional) /// /// Same [as in `extern_class!`](crate::extern_class#thread_kind---optional). /// /// /// ### `#[name = "..."]` (optional) /// /// Specify the runtime-name for the class. Must be unique across the entire /// application. This is useful if the name of a class is used elsewhere, such /// as when defining a delegate that needs to be named in e.g. `Info.plist`. /// /// If not set, this will default to: /// ```ignore /// concat!(module_path!(), "::", $class, env!("CARGO_PKG_VERSION")); /// ``` /// /// E.g. for example `"my_crate::my_module::MyClass0.1.0"`. /// /// If you're developing a library, it is recommended that you do not set /// this, and instead rely on the default naming, since that usually works /// better with users having multiple SemVer-incompatible versions of your /// library in the same binary. /// /// /// ### `#[ivars = ...]` (optional) /// /// Controls [the instance variables] of the class; this is the intended way /// to specify the data your class stores. If you don't set this attribute, /// the macro will default to [`()`][unit]. /// /// It is recommended that you wrap your instance variables in [`Cell`], /// [`RefCell`], atomics or other similar interior mutability abstractions to /// allow mutating your instance variables. See [the docs on interior /// mutability][interior_mutability] for further details. /// /// Beware that if you want to use the class' inherited initializers (such as /// `init`), you must override the subclass' designated initializers, and /// initialize your ivars properly in there. /// /// [the instance variables]: crate::DefinedClass::Ivars /// [`Cell`]: core::cell::Cell /// [`RefCell`]: core::cell::RefCell /// [interior_mutability]: crate::topics::interior_mutability /// /// /// ### `#[derive(...)]` /// /// This is overridden, and only works with [`PartialEq`], [`Eq`], [`Hash`] /// and [`Debug`]. /// /// The implementations delegate to the superclass' implementation, so if you /// want to change how they work, you should override the [`isEqual:`] and /// [`hash`] methods instead. /// /// The `Debug` implementation currently also debug print your ivars, but that /// may change in the future. Prefer to override [`description`] (and /// potentially [`debugDescription`]) instead. /// /// [`Hash`]: std::hash::Hash /// [`Debug`]: std::fmt::Debug /// [`isEqual:`]: crate::runtime::NSObjectProtocol::isEqual /// [`hash`]: crate::runtime::NSObjectProtocol::hash /// [`description`]: crate::runtime::NSObjectProtocol::description /// [`debugDescription`]: crate::runtime::NSObjectProtocol::debugDescription /// /// /// ### `#[cfg_attr(..., ...)]` /// /// Same [as in `extern_class!`](crate::extern_class#cfg_attr-). /// /// /// ### `#[repr(...)]` /// /// Same [as in `extern_class!`](crate::extern_class#repr). /// /// /// ## Inherent method definitions /// /// Within the `impl` block you can define two types of functions; /// ["associated functions"] and ["methods"]. These are then mapped to the /// Objective-C equivalents "class methods" and "instance methods". In /// particular, if you use `self` or the special name `this` (or `_this`), /// your method will be registered as an instance method, and if you don't it /// will be registered as a class method. /// /// On instance methods, you can freely choose between different types of /// receivers, e.g. `&self`, `self: *const Self`, `this: *const Self`, and so /// on. Note that using `&mut self` is not possible, if you need mutation of /// your class' instance variables, consider using [`Cell`] or similar /// instead. /// /// The desired selector can be specified using the /// `#[unsafe(method(my:selector:))]` or `#[unsafe(method_id(my:selector:))]` /// attributes, similar to the [`extern_methods!`] macro. /// /// If the `#[unsafe(method_id(...))]` attribute is used, the return type must /// be `Option<Retained<T>>` or `Retained<T>`. Additionally, if the selector /// is in the "init"-family, the `self`/`this` parameter must be /// `Allocated<Self>`. /// /// Putting other attributes on the method such as `cfg`, `allow`, `doc`, /// `deprecated` and so on is supported. However, note that `cfg_attr` may not /// work correctly, due to implementation difficulty - if you have a concrete /// use-case, please [open an issue], then we can discuss it. /// /// A transformation step is performed on the functions (to make them have the /// correct ABI) and hence they shouldn't really be called manually. (You /// can't mark them as `pub` for the same reason). Instead, use the /// [`extern_methods!`] macro to create a Rust interface to the methods. /// /// If the parameter or return type is [`bool`], a conversion is performed to /// make it behave similarly to the Objective-C `BOOL`. Use [`runtime::Bool`] /// if you want to control this manually. /// /// Note that `&mut Retained<_>` and other such out parameters are not yet /// supported, and may generate a panic at runtime. /// /// ["associated functions"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods /// ["methods"]: https://doc.rust-lang.org/reference/items/associated-items.html#methods /// [`Cell`]: core::cell::Cell /// [open an issue]: https://github.com/madsmtm/objc2/issues/new /// [`msg_send!`]: crate::msg_send /// [`runtime::Bool`]: crate::runtime::Bool /// /// /// ## Protocol implementations /// /// You can specify protocols that the class should implement, along with any /// required/optional methods for said protocols. /// /// The protocol must have been previously defined with [`extern_protocol!`]. /// /// The methods work exactly as normal, they're only put "under" the protocol /// definition to make things easier to read. /// /// Putting attributes on the `impl` item such as `cfg`, `allow`, `doc`, /// `deprecated` and so on is supported. /// /// [`extern_protocol!`]: crate::extern_protocol /// /// /// # Panics /// /// The implemented `ClassType::class` method may panic in a few cases, such /// as if: /// - A class with the specified name already exists. /// - Debug assertions are enabled, and an overridden method's signature is not /// equal to the one on the superclass. /// - Debug assertions are enabled, and the protocol's required methods are not /// implemented. /// /// And possibly more similar cases in the future. /// /// /// # Safety /// /// Using this macro requires writing a lot of `unsafe` markers: /// /// When writing `#[unsafe(super(...))]`, you must ensure that: /// - Any invariants that the superclass [`ClassType::Super`] may have must be /// upheld. /// - If your type implements `Drop`, the implementation must abide by the /// following rules: /// - It must not call any overridden methods. /// - It must not `retain` the object past the lifetime of the drop. /// - It must not `retain` in the same scope that `&mut self` is active. /// - TODO: And probably a few more. [Open an issue] if you would like /// guidance on whether your implementation is correct. /// /// `#[unsafe(method(...))]` asserts that the types match those that are /// expected when the method is invoked from Objective-C. Note that unlike /// with [`extern_methods!`], there are no safe-guards here; you can write /// `i8`, but if Objective-C thinks it's an `u32`, it will cause UB when /// called! /// /// `unsafe impl P for T { ... }` requires that all required methods of the /// specified protocol is implemented, and that any extra requirements /// (implicit or explicit) that the protocol has are upheld. /// /// [`ClassType::Super`]: crate::ClassType::Super /// [Open an issue]: https://github.com/madsmtm/objc2/issues/new /// /// /// # Thread safety /// /// The Objective-C runtime is thread-safe, so any classes you create yourself /// will automatically be [`Send`] and [`Sync`] (via auto traits) provided /// that: /// 1. The superclass is thread-safe, or is [`NSObject`]. /// 2. The ivars are thread-safe. /// 3. The thread kind is not [`MainThreadOnly`]. /// /// Note though that in many cases, [the frameworks] you will be interacting /// with will not be thread-safe, and so in many cases it will make sense to /// [use interior mutability] in your custom classes. /// /// [`NSObject`]: crate::runtime::NSObject /// [`MainThreadOnly`]: crate::MainThreadOnly /// [the frameworks]: crate::topics::about_generated /// [use interior mutability]: crate::topics::interior_mutability /// /// /// # Examples /// /// Define a class `MyCustomObject` that inherits `NSObject`, has a few /// instance variables and methods, and implements the `NSCopying` protocol. /// /// ``` /// use std::ffi::c_int; /// /// use objc2_foundation::{CopyingHelper, NSCopying, NSObject, NSObjectProtocol, NSZone}; /// use objc2::rc::{Allocated, Retained}; /// use objc2::{ /// define_class, extern_methods, extern_protocol, msg_send, AnyThread, /// ClassType, DefinedClass, ProtocolType, /// }; /// /// #[derive(Clone)] /// struct Ivars { /// foo: u8, /// bar: c_int, /// object: Retained<NSObject>, /// } /// /// define_class!( /// // SAFETY: /// // - The superclass NSObject does not have any subclassing requirements. /// // - `MyCustomObject` does not implement `Drop`. /// #[unsafe(super(NSObject))] /// /// // If we were implementing delegate methods like `NSApplicationDelegate`, /// // we would specify the object to only be usable on the main thread: /// // #[thread_kind = MainThreadOnly] /// /// // If we needed to refer to the class from elsewhere, we'd give it a /// // name here explicitly. /// // #[name = "MyCustomObject"] /// /// // Specify the instance variables this class has. /// #[ivars = Ivars] /// struct MyCustomObject; /// /// impl MyCustomObject { /// #[unsafe(method(foo))] /// fn __get_foo(&self) -> u8 { /// self.ivars().foo /// } /// /// #[unsafe(method_id(object))] /// fn __get_object(&self) -> Retained<NSObject> { /// self.ivars().object.clone() /// } /// /// #[unsafe(method(myClassMethod))] /// fn __my_class_method() -> bool { /// true /// } /// } /// /// unsafe impl NSObjectProtocol for MyCustomObject {} /// /// unsafe impl NSCopying for MyCustomObject { /// #[unsafe(method_id(copyWithZone:))] /// fn copyWithZone(&self, _zone: *const NSZone) -> Retained<Self> { /// let new = Self::alloc().set_ivars(self.ivars().clone()); /// unsafe { msg_send![super(new), init] } /// } /// /// // If we have tried to add other methods here, or had forgotten /// // to implement the method, we would have gotten an error. /// } /// ); /// /// // Specially required for `NSCopying`, but otherwise not needed. /// unsafe impl CopyingHelper for MyCustomObject { /// type Result = Self; /// } /// /// // Add creation method. /// impl MyCustomObject { /// fn new(foo: u8) -> Retained<Self> { /// // Initialize instance variables. /// let this = Self::alloc().set_ivars(Ivars { /// foo, /// bar: 42, /// object: NSObject::new(), /// }); /// // Call `NSObject`'s `init` method. /// unsafe { msg_send![super(this), init] } /// } /// } /// /// // Make an interface to the methods we defined. /// impl MyCustomObject { /// extern_methods!( /// #[unsafe(method(foo))] /// pub fn get_foo(&self) -> u8; /// /// #[unsafe(method(object))] /// pub fn get_object(&self) -> Retained<NSObject>; /// /// #[unsafe(method(myClassMethod))] /// pub fn my_class_method() -> bool; /// ); /// } /// /// # // Intentionally use `fn main` for clarity /// fn main() { /// let obj = MyCustomObject::new(3); /// assert_eq!(obj.ivars().foo, 3); /// assert_eq!(obj.ivars().bar, 42); /// assert!(obj.ivars().object.isKindOfClass(NSObject::class())); /// /// let obj = obj.copy(); /// /// assert_eq!(obj.get_foo(), 3); /// assert!(obj.get_object().isKindOfClass(NSObject::class())); /// /// assert!(MyCustomObject::my_class_method()); /// } /// ``` /// /// Approximately equivalent to the following ARC-enabled Objective-C code. /// /// ```text /// #import <Foundation/Foundation.h> /// /// @interface MyCustomObject: NSObject <NSCopying> /// - (instancetype)initWithFoo:(uint8_t)foo; /// - (uint8_t)foo; /// - (NSObject*)object; /// + (BOOL)myClassMethod; /// @end /// /// /// @implementation MyCustomObject { /// // Instance variables /// uint8_t foo; /// int bar; /// NSObject* _Nonnull object; /// } /// /// - (instancetype)initWithFoo:(uint8_t)foo_arg { /// self = [super init]; /// if (self) { /// self->foo = foo_arg; /// self->bar = 42; /// self->object = [NSObject new]; /// } /// return self; /// } /// /// - (uint8_t)foo { /// return self->foo; /// } /// /// - (NSObject*)object { /// return self->object; /// } /// /// + (BOOL)myClassMethod { /// return YES; /// } /// /// // NSCopying /// /// - (id)copyWithZone:(NSZone *)_zone { /// MyCustomObject* new = [[MyCustomObject alloc] initWithFoo: self->foo]; /// new->bar = self->bar; /// new->obj = self->obj; /// return new; /// } /// /// @end /// ``` #[doc(alias = "@interface")] #[doc(alias = "@implementation")] #[macro_export]
macro_rules! define_class {
{ // The following special attributes are supported: // - #[unsafe(super($($superclasses:path),*))] // - #[unsafe(super = $superclass:path)] // - #[thread_kind = $thread_kind:path] // - #[name = $name:literal] // - #[ivars = $ivars:path]
$(#[$($attrs:tt)*])*
$v:vis struct $class:ident;
// unsafe impl Protocol for $class { ... } // impl $class { ... }
$($impls:tt)*
} => { // Struct and various impls.
$crate::__extract_struct_attributes! {
($(#[$($attrs)*])*)
($($safety:tt $superclass:path $(, $superclasses:path)* $(,)?)?)
($($($thread_kind:tt)+)?)
($($name:tt)*)
($($ivars:tt)*)
($($derives:tt)*)
($($attr_struct:tt)*)
($($attr_impl:tt)*)
) => { // Ensure that the type has the same layout as the superclass. // #[repr(transparent)] doesn't work because the superclass is a ZST. #[repr(C)]
$($attr_struct)*
$v struct $class { // Superclasses are deallocated by calling `[super dealloc]`. // // Auto traits are taken from `__SubclassingType` (which is // usually the super class).
__superclass: $crate::__macro_helpers::ManuallyDrop<$crate::__fallback_if_not_set! {
($(<$superclass as $crate::ClassType>::__SubclassingType)?) // For better diagnostics, see also __extern_class_inner!
($crate::runtime::NSObject)
}>,
__phantom: $crate::__macro_helpers::PhantomData<( // Include ivars for auto traits.
<Selfas $crate::DefinedClass>::Ivars, // Translate thread kind to appropriate auto traits.
$crate::__macro_helpers::ThreadKindAutoTraits<<Selfas $crate::ClassType>::ThreadKind>,
)>,
}
// Creation unsafeimpl $crate::ClassType for $class { typeSuper = $crate::__fallback_if_not_set! {
($($superclass)?) // For better diagnostics, see also __extern_class_inner!
($crate::runtime::NSObject)
};
type ThreadKind = $crate::__fallback_if_not_set! {
($(dyn ($($thread_kind)+))?) // Default to the super class' thread kind
(<<Selfas $crate::ClassType>::Superas $crate::ClassType>::ThreadKind)
};
const NAME: &'static $crate::__macro_helpers::str = $crate::__fallback_if_not_set! {
($($name)*)
(
$crate::__macro_helpers::concat!( // Module path includes crate name when in library.
$crate::__macro_helpers::module_path!(), "::",
$crate::__macro_helpers::stringify!($class),
$crate::__macro_helpers::env!("CARGO_PKG_VERSION"),
)
)
};
fn class() -> &'static $crate::runtime::AnyClass { let _ = <Selfas $crate::__macro_helpers::ValidThreadKind<Self::ThreadKind>>::check; let _ = <Selfas $crate::__macro_helpers::MainThreadOnlyDoesNotImplSendSync<_>>::check;
// TODO: Use `std::sync::OnceLock` static REGISTER_CLASS: $crate::__macro_helpers::Once = $crate::__macro_helpers::Once::new();
// Implement protocols and methods
$crate::__define_class_register_impls! {
(__objc2_builder)
$($impls)*
}
let (__objc2_cls, __objc2_ivar_offset, __objc2_drop_flag_offset) = __objc2_builder.register();
// SAFETY: Modification is ensured by `Once` to happen // before any access to the variables. unsafe {
__OBJC2_CLASS.get().write($crate::__macro_helpers::MaybeUninit::new(__objc2_cls)); if <Selfas $crate::__macro_helpers::DefinedIvarsHelper>::HAS_IVARS {
__OBJC2_IVAR_OFFSET.get().write($crate::__macro_helpers::MaybeUninit::new(__objc2_ivar_offset));
} if <Selfas $crate::__macro_helpers::DefinedIvarsHelper>::HAS_DROP_FLAG {
__OBJC2_DROP_FLAG_OFFSET.get().write($crate::__macro_helpers::MaybeUninit::new(__objc2_drop_flag_offset));
}
}
});
// SAFETY: We just registered the class, so is now available unsafe { __OBJC2_CLASS.get().read().assume_init() }
}
impl $crate::DefinedClass for $class { type Ivars = $crate::__select_ivars!($($ivars)?);
#[inline] fn __ivars_offset() -> $crate::__macro_helpers::isize { // Only access ivar offset if we have an ivar. // // This makes the offset not be included in the final // executable if it's not needed. if <Selfas $crate::__macro_helpers::DefinedIvarsHelper>::HAS_IVARS { // SAFETY: Accessing the offset is guaranteed to only be // done after the class has been initialized. unsafe { __OBJC2_IVAR_OFFSET.get().read().assume_init() }
} else { // Fall back to an offset of zero. // // This is fine, since any reads here will only be via zero-sized // ivars, where the actual pointer doesn't matter. 0
}
}
#[inline] fn __drop_flag_offset() -> $crate::__macro_helpers::isize { if <Selfas $crate::__macro_helpers::DefinedIvarsHelper>::HAS_DROP_FLAG { // SAFETY: Same as above. unsafe { __OBJC2_DROP_FLAG_OFFSET.get().read().assume_init() }
} else { // Fall back to an offset of zero. // // This is fine, since the drop flag is never actually used in the // cases where it was not added. 0
}
}
// SAFETY: The offsets are implemented correctly const __UNSAFE_OFFSETS_CORRECT: () = ();
}
};
// SAFETY: This macro only allows non-generic classes and non-generic // classes are always valid downcast targets.
$($attr_impl)* unsafeimpl $crate::DowncastTarget for $class {}
// In case the user's function is marked `deprecated` #[allow(deprecated)] // In case the user did not specify any methods #[allow(unused_unsafe)] // SAFETY: Upheld by caller unsafe {
$crate::__define_class_register_methods! {
(__objc2_protocol_builder)
$($methods)*
}
}
// Finished creating protocol; get error message if any
__objc2_protocol_builder.finish();
}
// In case the user's function is marked `deprecated` #[allow(deprecated)] // In case the user did not specify any methods #[allow(unused_unsafe)] // SAFETY: Upheld by caller unsafe {
$crate::__define_class_register_methods! {
($builder)
#[doc(hidden)] #[macro_export]
macro_rules! __define_class_invalid_selectors {
(method(dealloc)) => {
$crate::__macro_helpers::compile_error!( "`#[unsafe(method(dealloc))]` is not supported. Implement `Drop` for the type instead"
)
};
(method_id(dealloc)) => {
$crate::__macro_helpers::compile_error!( "`#[unsafe(method_id(dealloc))]` is not supported. Implement `Drop` for the type instead"
)
};
(method_id(alloc)) => {
$crate::__macro_helpers::compile_error!($crate::__macro_helpers::concat!( "`#[unsafe(method_id(alloc))]` is not supported. ", "Use `#[unsafe(method(alloc))]` and do the memory management yourself",
))
};
(method_id(retain)) => {
$crate::__macro_helpers::compile_error!($crate::__macro_helpers::concat!( "`#[unsafe(method_id(retain))]` is not supported. ", "Use `#[unsafe(method(retain))]` and do the memory management yourself",
))
};
(method_id(release)) => {
$crate::__macro_helpers::compile_error!($crate::__macro_helpers::concat!( "`#[unsafe(method_id(release))]` is not supported. ", "Use `#[unsafe(method(release))]` and do the memory management yourself",
))
};
(method_id(autorelease)) => {
$crate::__macro_helpers::compile_error!($crate::__macro_helpers::concat!( "`#[unsafe(method_id(autorelease))]` is not supported. ", "Use `#[unsafe(method(autorelease))]` and do the memory management yourself",
))
};
($method_or_method_id:ident($($sel:tt)*)) => {};
}
#[doc(hidden)] #[macro_export]
macro_rules! __define_class_no_method_family {
() => {};
($($t:tt)+) => {
$crate::__macro_helpers::compile_error!( "`#[unsafe(method_family = ...)]` is not yet supported in `define_class!` together with `#[unsafe(method(...))]`"
)
};
}
#[doc(hidden)] #[macro_export]
macro_rules! __define_class_no_optional {
() => {};
(#[optional]) => {
$crate::__macro_helpers::compile_error!( "`#[optional]` is only supported in `extern_protocol!`"
)
};
}
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.