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:
-
The selector corresponds to a valid method that is available on the receiver.
-
The argument types match what the receiver excepts for this selector.
-
The return type match what the receiver returns for this selector.
-
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 insidestd::cell::UnsafeCell
). -
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. -
The method must not (yet) throw an exception.
-
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).
-
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] };