macro_rules! msg_send {
    [super($obj:expr), $selector:ident $(,)?] => { ... };
    [super($obj:expr), $($selector:ident : $argument:expr),+ $(,)?] => { ... };
    [super($obj:expr, $superclass:expr), $selector:ident $(,)?] => { ... };
    [super($obj:expr, $superclass:expr), $($selector:ident : $argument:expr $(,)?)+] => { ... };
    [$obj:expr, $selector:ident $(,)?] => { ... };
    [$obj:expr, $($selector:ident : $argument:expr $(,)?)+] => { ... };
}
Expand description

Send a message to an object or class.

This is wildly unsafe, even more so than sending messages in Objective-C, because this macro can’t inspect header files to see the expected types, and because Rust has more safety invariants to uphold. Make sure to review the safety section below!

The recommended way of using this macro is by defining a wrapper function:

unsafe fn do_something(obj: &Object, arg: c_int) -> *const c_char {
    msg_send![obj, doSomething: arg]
}

This way we are clearly communicating to Rust that: The method doSomething: works with a shared reference to the object. It takes a C-style signed integer, and returns a pointer to what is probably a C-compatible string. Now it’s much, much easier to make a safe abstraction around this!

There exists a variant of this macro, msg_send_id!, which can help with upholding certain requirements of methods that return Objective-C’s id, or other object pointers. Use that whenever you want to call such a method!

Specification

The syntax is similar to the message syntax in Objective-C, except with an (optional, though consider that deprecated) comma between arguments, since that works much better with rustfmt.

The first expression, know as the “receiver”, can be any type that implements MessageReceiver, like a reference or a pointer to an object, or even a reference to an rc::Id containing an object.

The expression can be wrapped in super, with an optional superclass as the second argument. If no specific superclass is specified, the direct superclass is retrieved from ClassType.

All arguments, and the return type, must implement Encode.

This macro translates into a call to sel!, and afterwards a fully qualified call to MessageReceiver::send_message. Note that this means that auto-dereferencing of the receiver is not supported, and that the receiver is consumed. You may encounter a little trouble with &mut references, try refactoring into a separate method or reborrowing the reference.

Variadic arguments are currently not supported.

bool handling

Objective-C’s BOOL is different from Rust’s bool, and hence a conversion step must be performed before using it. This is very easy to forget (because it’ll happen to work in most cases), so for ease of use, this macro does the conversion step automatically whenever the argument or return type is bool!

That means that any Objective-C method that take or return BOOL can simply be translated to use bool on the Rust side.

If you want to handle the conversion explicitly, or the Objective-C method expects a pointer to a BOOL, use runtime::Bool instead.

Panics

Panics if the "catch-all" feature is enabled and the Objective-C method throws an exception. Exceptions may still cause UB until extern "C-unwind" is stable, see RFC-2945.

Panics if the "verify_message" feature is enabled and the Objective-C method’s argument’s encoding does not match the encoding of the given arguments. This is highly recommended to enable while testing!

Safety

Similar to defining and calling an extern function in a foreign function interface. In particular, you must uphold the following requirements:

  1. The selector corresponds to a valid method that is available on the receiver.

  2. The argument types match what the receiver excepts for this selector.

  3. The return type match what the receiver returns for this selector.

  4. The call must not violate Rust’s mutability rules, for example if passing an &T, the Objective-C method must not mutate the variable (of course except if the variable is inside std::cell::UnsafeCell).

  5. If the receiver is a raw pointer it must be valid (aligned, dereferenceable, initialized and so on). Messages to null pointers are allowed (though heavily discouraged), but only if the return type itself is a pointer.

  6. The method must not (yet) throw an exception.

  7. You must uphold any additional safety requirements (explicit and implicit) that the method has. For example:

    • Methods that take pointers usually require that the pointer is valid, and sometimes non-null.
    • Sometimes, a method may only be called on the main thread.
    • The lifetime of returned pointers usually follows certain rules, and may not be valid outside of an autoreleasepool (msg_send_id! can greatly help with that).
  8. TODO: Maybe more?

Examples

Sending messages to an object.

use objc2::msg_send;
use objc2::runtime::Object;

let obj: *mut Object;
let description: *const Object = unsafe { msg_send![obj, description] };
// Usually you'd use msg_send_id here ^
let _: () = unsafe { msg_send![obj, setArg1: 1i32, arg2: true] };
let arg1: i32 = unsafe { msg_send![obj, getArg1] };
let arg2: bool = unsafe { msg_send![obj, getArg2] };

Sending messages to the direct superclass of an object.

use objc2::msg_send;

let obj: &MyObject; // Some object that implements ClassType
let _: () = unsafe { msg_send![super(obj), someMethod] };

Sending messages to a specific superclass of an object.

use objc2::msg_send;
use objc2::runtime::{Class, Object};

// Since we specify the superclass ourselves, this doesn't need to
// implement ClassType
let obj: *mut Object;
let superclass: &Class;
let arg3: u32 = unsafe { msg_send![super(obj, superclass), getArg3] };