Macro objc2::extern_protocol

source ·
macro_rules! extern_protocol {
    (
        $(#[$m:meta])*
        $v:vis unsafe trait $name:ident $(: $conforms_to:ident $(+ $conforms_to_rest:ident)*)? {
            $($methods:tt)*
        }

        $(#[$impl_m:meta])*
        unsafe impl ProtocolType for dyn $for:ident {
            $(const NAME: &'static str = $name_const:expr;)?
        }
    ) => { ... };
}
Expand description

Create a new trait to represent a protocol.

This is similar to a @protocol declaration in Objective-C.

See Protocols - The Objective-C Programming Language and Working with Protocols - Programming with Objective-C for general information about protocols in Objective-C.

This macro will create an unsafe trait with methods which all have default implementations, such that an external class that conforms to the protocol can write unsafe impl MyProtocol for MyClass {}, and get access to the functionality exposed by the protocol.

Note that that conforming to a protocol in a custom object requires putting the implementation inside the declare_class! invocation.

Objective-C has a smart feature where you can write id<MyProtocol>, and then work with the protocol as-if it was an object; this is very similar to dyn traits in Rust, and we implement it in a similar way, see ProtocolObject for details.

§Specification

The syntax is similar enough to Rust syntax that if you invoke the macro with parentheses (as opposed to curly brackets), rustfmt will be able to format the contents.

This macro creates an unsafe trait with the specified methods. A default implementation of the method is generated based on the selector specified with #[method(a:selector:)] or #[method_id(a:selector:)].

Other protocols that this protocol conforms to / inherits can be specified as supertraits.

The new trait T is automatically implemented for ProtocolObject<dyn T>, which also means that ProtocolType is implemented for dyn T.

Finally, you can use the #[optional] attribute to mark optional methods. This currently doesn’t have any effect, but probably will have one in the future when implementing protocols in declare_class!.

This macro otherwise shares similarities with extern_class! and extern_methods!.

§Safety

The following are required for using the macro itself:

  • The specified name must be an exisiting Objective-C protocol.
  • The protocol must actually inherit/conform to the protocols specified as supertraits.
  • The protocol’s methods must be correctly specified.

While the following are required when implementing the unsafe trait for a new type:

  • The type must represent an object that implements the protocol.

§Examples

Create a trait to represent the NSItemProviderWriting protocol.

use std::ffi::c_void;
use objc2::ffi::NSInteger;
use objc2::rc::Retained;
use objc2::runtime::{NSObject, NSObjectProtocol};
use objc2::{extern_protocol, ProtocolType};

// Assume these were correctly defined, as if they came from
// `objc2-foundation`
type NSArray<T> = T;
type NSString = NSObject;
type NSProgress = NSObject;
type NSItemProviderRepresentationVisibility = NSInteger;

extern_protocol!(
    /// This comment will appear on the trait as expected.
    pub unsafe trait NSItemProviderWriting: NSObjectProtocol {
        //                                  ^^^^^^^^^^^^^^^^
        // This protocol inherits from the `NSObject` protocol

        // This method we mark as `unsafe`, since we aren't using the correct
        // type for the completion handler
        #[method_id(loadDataWithTypeIdentifier:forItemProviderCompletionHandler:)]
        unsafe fn loadData(
            &self,
            type_identifier: &NSString,
            completion_handler: *mut c_void,
        ) -> Option<Retained<NSProgress>>;

        #[method_id(writableTypeIdentifiersForItemProvider)]
        fn writableTypeIdentifiersForItemProvider_class()
            -> Retained<NSArray<NSString>>;

        // The rest of these are optional, which means that a user of
        // `declare_class!` would not need to implement them.

        #[optional]
        #[method_id(writableTypeIdentifiersForItemProvider)]
        fn writableTypeIdentifiersForItemProvider(&self)
            -> Retained<NSArray<NSString>>;

        #[optional]
        #[method(itemProviderVisibilityForRepresentationWithTypeIdentifier:)]
        fn itemProviderVisibilityForRepresentation_class(
            type_identifier: &NSString,
        ) -> NSItemProviderRepresentationVisibility;

        #[optional]
        #[method(itemProviderVisibilityForRepresentationWithTypeIdentifier:)]
        fn itemProviderVisibilityForRepresentation(
            &self,
            type_identifier: &NSString,
        ) -> NSItemProviderRepresentationVisibility;
    }

    // SAFETY:
    // - The name is correct
    // - The protocol does inherit `NSObject`
    // - The methods are correctly specified
    unsafe impl ProtocolType for dyn NSItemProviderWriting {
        //                       ^^^ - Remember to add this `dyn`.

        // We could have named the trait something else on the Rust-side,
        // and then used this to keep it correct from Objective-C.
        //
        // const NAME: &'static str = "NSItemProviderWriting";
    }
);

// Types can now implement `NSItemProviderWriting`, and use the methods
// from it as we specified.

See the source code of objc2-foundation for many more examples.