Crate objc2

Source
Expand description

§Objective-C interface and runtime bindings

Quick links:

Objective-C was the standard programming language on Apple platforms like macOS, iOS, iPadOS, tvOS and watchOS. It is an object-oriented language centered around “sending messages” to its instances - this can for the most part be viewed as a function call.

It has since been superseded by Swift, but most of the core libraries and frameworks that are in use on Apple systems are still written in Objective-C, and hence we would like the ability to interact with these using Rust. This crate enables bi-directional interop with Objective-C, in as safe a manner as possible.

§Example

Most of the time, you’ll want to use one of the framework crates, which contain bindings to CoreFoundation, Foundation, AppKit, Metal, UIKit, WebKit and so on.

In this example we’re going to be using objc2-foundation and objc2-app-kit to create a simple GUI application that displays a “Hello World” label.

$ # Add the necessary crates to your project.
$ cargo add objc2 objc2-foundation objc2-app-kit
#![deny(unsafe_op_in_unsafe_fn)]
use std::cell::OnceCell;

use objc2::rc::Retained;
use objc2::runtime::ProtocolObject;
use objc2::{define_class, msg_send, DefinedClass, MainThreadOnly};
use objc2_app_kit::{
    NSApplication, NSApplicationActivationPolicy, NSApplicationDelegate, NSAutoresizingMaskOptions,
    NSBackingStoreType, NSColor, NSFont, NSTextAlignment, NSTextField, NSWindow, NSWindowDelegate,
    NSWindowStyleMask,
};
use objc2_foundation::{
    ns_string, MainThreadMarker, NSNotification, NSObject, NSObjectProtocol, NSPoint, NSRect,
    NSSize,
};

#[derive(Debug, Default)]
struct AppDelegateIvars {
    window: OnceCell<Retained<NSWindow>>,
}

define_class!(
    // SAFETY:
    // - The superclass NSObject does not have any subclassing requirements.
    // - `Delegate` does not implement `Drop`.
    #[unsafe(super = NSObject)]
    #[thread_kind = MainThreadOnly]
    #[name = "Delegate"]
    #[ivars = AppDelegateIvars]
    struct Delegate;

    // SAFETY: `NSObjectProtocol` has no safety requirements.
    unsafe impl NSObjectProtocol for Delegate {}

    // SAFETY: `NSApplicationDelegate` has no safety requirements.
    unsafe impl NSApplicationDelegate for Delegate {
        // SAFETY: The signature is correct.
        #[unsafe(method(applicationDidFinishLaunching:))]
        fn did_finish_launching(&self, notification: &NSNotification) {
            let mtm = self.mtm();

            let app = unsafe { notification.object() }
                .unwrap()
                .downcast::<NSApplication>()
                .unwrap();

            let text_field = unsafe {
                let text_field = NSTextField::labelWithString(ns_string!("Hello, World!"), mtm);
                text_field.setFrame(NSRect::new(
                    NSPoint::new(5.0, 100.0),
                    NSSize::new(290.0, 100.0),
                ));
                text_field.setTextColor(Some(&NSColor::colorWithSRGBRed_green_blue_alpha(
                    0.0, 0.5, 0.0, 1.0,
                )));
                text_field.setAlignment(NSTextAlignment::Center);
                text_field.setFont(Some(&NSFont::systemFontOfSize(45.0)));
                text_field.setAutoresizingMask(
                    NSAutoresizingMaskOptions::ViewWidthSizable
                        | NSAutoresizingMaskOptions::ViewHeightSizable,
                );
                text_field
            };

            // SAFETY: We disable releasing when closed below.
            let window = unsafe {
                NSWindow::initWithContentRect_styleMask_backing_defer(
                    NSWindow::alloc(mtm),
                    NSRect::new(NSPoint::new(0.0, 0.0), NSSize::new(300.0, 300.0)),
                    NSWindowStyleMask::Titled
                        | NSWindowStyleMask::Closable
                        | NSWindowStyleMask::Miniaturizable
                        | NSWindowStyleMask::Resizable,
                    NSBackingStoreType::Buffered,
                    false,
                )
            };
            // SAFETY: Disable auto-release when closing windows.
            // This is required when creating `NSWindow` outside a window
            // controller.
            unsafe { window.setReleasedWhenClosed(false) };

            // Set various window properties.
            window.setTitle(ns_string!("A window"));
            let view = window.contentView().expect("window must have content view");
            unsafe { view.addSubview(&text_field) };
            window.center();
            unsafe { window.setContentMinSize(NSSize::new(300.0, 300.0)) };
            window.setDelegate(Some(ProtocolObject::from_ref(self)));

            // Show the window.
            window.makeKeyAndOrderFront(None);

            // Store the window in the delegate.
            self.ivars().window.set(window).unwrap();

            app.setActivationPolicy(NSApplicationActivationPolicy::Regular);

            // Activate the application.
            // Required when launching unbundled (as is done with Cargo).
            #[allow(deprecated)]
            app.activateIgnoringOtherApps(true);
        }
    }

    // SAFETY: `NSWindowDelegate` has no safety requirements.
    unsafe impl NSWindowDelegate for Delegate {
        #[unsafe(method(windowWillClose:))]
        fn window_will_close(&self, _notification: &NSNotification) {
            // Quit the application when the window is closed.
            unsafe { NSApplication::sharedApplication(self.mtm()).terminate(None) };
        }
    }
);

impl Delegate {
    fn new(mtm: MainThreadMarker) -> Retained<Self> {
        let this = Self::alloc(mtm).set_ivars(AppDelegateIvars::default());
        // SAFETY: The signature of `NSObject`'s `init` method is correct.
        unsafe { msg_send![super(this), init] }
    }
}

fn main() {
    let mtm = MainThreadMarker::new().unwrap();

    let app = NSApplication::sharedApplication(mtm);
    let delegate = Delegate::new(mtm);
    app.setDelegate(Some(ProtocolObject::from_ref(&*delegate)));

    app.run();
}

§Crate features

This crate exports several optional cargo features, see Cargo.toml for an overview and description of these.

The features in the framework crates are described here. Note that if you’re developing a library for others to use, you might want to reduce compile times by disabling default features and only enabling the features you need.

§Supported operating systems

  • macOS: 10.12-15.2
  • iOS: 10.0-18.2 (includes iPadOS and Mac Catalyst)
  • tvOS: 10.0-18.2
  • watchOS: 5.0-11.2
  • visionOS: 1.0-2.2

The minimum versions are the same as those supported by rustc. Higher versions will also work, but the framework crates will not have bindings available for newer APIs.

The framework bindings are generated from the SDKs in Xcode 16.2. The Xcode version will be periodically updated.

Note that the bindings are currently generated in a very macOS-centric, so they may try to use types from AppKit, even on iOS, see for example #637.

The bindings can also be used on Linux or *BSD utilizing the GNUstep Objective-C runtime, see the ffi module for how to configure this, but this is very much second-class.

§Minimum Supported Rust Version (MSRV)

The currently minimum supported Rust version is 1.71 (to be able to use extern "C-unwind" functions); this is not defined by policy, though, so it may change in at any time in a patch release.

Help us define a policy over in #203.

Re-exports§

pub use self::encode::Encode;
pub use self::encode::Encoding;
pub use self::encode::RefEncode;
pub use DefinedClass as DeclaredClass;

Modules§

declareDeprecated
Deprecated location for a few things that are now in the runtime module.
encode
Support for type-encodings.
exception
@throw and @try/@catch exceptions.
ffi
Raw bindings to Objective-C runtimes
rc
Reference counting utilities.
runtime
Direct runtime bindings.
topicsdocsrs
Various explanations and topics of discussion.

Macros§

available
Check if APIs from the given operating system versions are available.
class
Gets a reference to an AnyClass from the given name.
define_class
Create a new Objective-C class.
extern_class
Create a new type to represent a class.
extern_methods
Define methods on an external class.
extern_protocol
Create a new trait to represent a protocol.
msg_send
Send a message to an object or class.
msg_send_boolDeprecated
Use msg_send! instead, it now supports converting to/from bool.
msg_send_idDeprecated
Use msg_send! instead, it now supports converting to/from Retained.
sel
Register a selector with the Objective-C runtime.

Structs§

MainThreadMarker
A marker type taken by functions that can only be executed on the main thread.

Traits§

AllocAnyThread
Marker trait for classes (and protocols) that are usable from any thread, i.e. the opposite of MainThreadOnly.
ClassType
Marks types that represent specific classes.
DefinedClass
Marks class types whose implementation is defined in Rust.
DowncastTarget
Classes that can be safely downcasted to.
MainThreadOnly
Marker trait for classes and protocols that are only safe to use on the main thread.
Message
Types that can be sent Objective-C messages.
ProtocolType
Marks types that represent specific protocols.
ThreadKind
The allowed values in ClassType::ThreadKind.
impl<'a, T: ?Sized> Read for &'a Retained<T>
where\n &'a T: Read,
impl<T: ?Sized> Write for Retained<T>
where\n for<'a> &'a T: Write,
impl<'a, T: ?Sized> Write for &'a Retained<T>
where\n &'a T: Write,
impl<'a, T: ?Sized> Future for &'a Retained<T>
where\n &'a T: Future,
type Output = <&'a T as Future>::Output;
","Retained<:super>":"

Notable traits for Retained<T>

impl<T: ?Sized> Read for Retained<T>
where\n for<'a> &'a T: Read,
impl<'a, T: ?Sized> Read for &'a Retained<T>
where\n &'a T: Read,
impl<T: ?Sized> Write for Retained<T>
where\n for<'a> &'a T: Write,
impl<'a, T: ?Sized> Write for &'a Retained<T>
where\n &'a T: Write,
impl<'a, T: ?Sized> Future for &'a Retained<T>
where\n &'a T: Future,
type Output = <&'a T as Future>::Output;
","Retained":"

Notable traits for Retained<T>

impl<T: ?Sized> Read for Retained<T>
where\n for<'a> &'a T: Read,
impl<'a, T: ?Sized> Read for &'a Retained<T>
where\n &'a T: Read,
impl<T: ?Sized> Write for Retained<T>
where\n for<'a> &'a T: Write,
impl<'a, T: ?Sized> Write for &'a Retained<T>
where\n &'a T: Write,
impl<'a, T: ?Sized> Future for &'a Retained<T>
where\n &'a T: Future,
type Output = <&'a T as Future>::Output;
"}