Skip to content

Commit

Permalink
Merge pull request #514 from madsmtm/msg-send-refactor
Browse files Browse the repository at this point in the history
Refactor message sending
  • Loading branch information
madsmtm authored Sep 20, 2023
2 parents 5e705a7 + 0a6df20 commit f0591af
Show file tree
Hide file tree
Showing 96 changed files with 3,566 additions and 3,132 deletions.
18 changes: 9 additions & 9 deletions LAYERED_SAFETY.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,14 +52,14 @@ Doing the Rust equivalent of Objective-C's `NSUInteger hash_code = [obj hash];`.

```rust
let obj: *const c_void = ...;
let sel = unsafe { sel_registerName(b"hash\0".as_ptr() as *const c_char) };
let fnptr = unsafe {
let sel = unsafe { ffi::sel_registerName(b"hash\0".as_ptr() as *const c_char) };
let msg_send_fn = unsafe {
mem::transmute::<
extern "C" fn(*const c_void, SEL) -> NSUInteger,
extern "C" fn(),
>(objc_msgSend)
unsafe extern "C" fn(),
unsafe extern "C" fn(*const c_void, SEL) -> NSUInteger,
>(ffi::objc_msgSend)
};
let hash_code = unsafe { fnptr(obj, sel) };
let hash_code = unsafe { msg_send_fn(obj, sel) };
```


Expand All @@ -68,8 +68,7 @@ let hash_code = unsafe { fnptr(obj, sel) };
We can improve on this using [`MessageReceiver::send_message`], which
abstracts away the calling convention details, as well as adding an `Encode`
bound on all the involved types. This ensures that we don't accidentally try
to pass e.g. a `Vec<T>`, which does not have a stable memory layout. It also
handles details surrounding Objective-C's `BOOL` type.
to pass e.g. a `Vec<T>`, which does not have a stable memory layout.

Additionally, when `debug_assertions` are enabled, the types involved in the
message send are compared to the types exposed in the Objective-C runtime.
Expand Down Expand Up @@ -97,7 +96,8 @@ let hash_code: NSUInteger = unsafe {

Introducing macros: [`msg_send!`] can abstract away the tediousness of writing
the selector expression, as well as ensuring that the number of arguments to
the method is correct.
the method is correct. It also handles details surrounding Objective-C's
`BOOL` type.

[`msg_send!`]: https://docs.rs/objc2/0.3.0-beta.4/objc2/macro.msg_send.html

Expand Down
16 changes: 15 additions & 1 deletion crates/icrate/tests/array.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
#![cfg(feature = "Foundation_NSArray")]
#![cfg(feature = "Foundation_NSNumber")]
use core::ptr;

use icrate::Foundation::{NSArray, NSNumber, NSObject};
use objc2::mutability::IsRetainable;
use objc2::rc::{Id, __RcTestObject, __ThreadTestData};
use objc2::runtime::ProtocolObject;
use objc2::runtime::{AnyObject, ProtocolObject};
use objc2::{extern_protocol, ProtocolType};

fn sample_array(len: usize) -> Id<NSArray<NSObject>> {
Expand Down Expand Up @@ -283,3 +285,15 @@ fn test_trait_retainable() {
let _ = NSArray::from_id_slice(&[obj.clone(), obj.clone()]);
let _ = NSArray::from_vec(vec![obj.clone(), obj.clone()]);
}

#[test]
fn test_access_anyobject() {
let obj: Id<AnyObject> = Id::into_super(NSObject::new());
let array = NSArray::from_id_slice(&[obj.clone(), obj.clone()]);
assert!(ptr::eq(&array[0], &*obj));
assert!(ptr::eq(array.get(0).unwrap(), &*obj));
assert!(ptr::eq(&*array.get_retained(0).unwrap(), &*obj));
for _ in array.iter() {}
for _ in array.iter_retained() {}
for _ in array {}
}
26 changes: 26 additions & 0 deletions crates/objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
* Added the following traits to the `mutability` module (see the documentation
for motivation and usage info):
- `HasStableHash`.
- `IsAllowedMutable`.
- `IsMainThreadOnly`.
- `CounterpartOrSelf`.
* Added new `encode` traits `EncodeReturn`, `EncodeArgument` and
Expand All @@ -25,6 +26,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This effectively means you can now `copy` a `ProtocolObject<dyn NSCopying>`.
* **BREAKING**: Allow implementing `DefaultId` for any type, not just those
who are `IsAllocableAnyThread`.
* **BREAKING**: Moved the `MethodImplementation` trait from the `declare`
module to the `runtime` module.
* **BREAKING**: Moved the `MessageReceiver` trait to the `runtime` module.
* **BREAKING**: Make the `MessageReceiver` trait no longer implemented for
references to `Id`. Dereference the `Id` yourself.

Note: Passing `&Id` in `msg_send!` is still supported.
* **BREAKING**: `MessageReceiver::send_message` and
`MessageReceiver::send_super_message` now take `EncodeArguments` and return
`EncodeReturn`, instead of internal traits.

This is done to make `MessageReceiver` more straightforward to understand,
although it now also has slightly less functionality than `msg_send!`.

In particular automatic conversion of `bool` is not supported in
`MessageReceiver`.
* Relaxed the requirements for receivers in `MethodImplementation`; now,
anything that implements `MessageReceiver` can be used as the receiver of
a method.
* **BREAKING**: Renamed the associated types `Ret` and `Args` on
`MethodImplementation` to `Return` and `Arguments`.

### Deprecated
* Soft deprecated using `msg_send!` without a comma between arguments (i.e.
Expand Down Expand Up @@ -52,10 +74,14 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).

### Fixed
* Fixed the name of the protocol that `NSObjectProtocol` references.
* Allow cloning `Id<AnyObject>`.
* **BREAKING**: Restrict message sending to `&mut` references to things that
implement `IsAllowedMutable`.

### Removed
* **BREAKING**: Removed `ProtocolType` implementation for `NSObject`.
Use the more precise `NSObjectProtocol` trait instead!
* **BREAKING**: Removed the `MessageArguments` trait.


## 0.4.1 - 2023-07-31
Expand Down
164 changes: 163 additions & 1 deletion crates/objc2/src/__macro_helpers/convert.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::encode::{EncodeArgument, EncodeReturn};
use crate::encode::{EncodeArgument, EncodeArguments, EncodeReturn};
use crate::rc::Id;
use crate::runtime::Bool;
use crate::Message;
Expand Down Expand Up @@ -31,6 +31,7 @@ pub trait ConvertArgument: argument_private::Sealed {
fn __into_argument(self) -> (Self::__Inner, Self::__StoredBeforeMessage);

#[doc(hidden)]
#[inline]
unsafe fn __process_after_message_send(_stored: Self::__StoredBeforeMessage) {}
}

Expand Down Expand Up @@ -121,6 +122,167 @@ impl ConvertReturn for bool {
}
}

pub trait ConvertArguments {
#[doc(hidden)]
type __Inner: EncodeArguments;

#[doc(hidden)]
type __StoredBeforeMessage: Sized;

#[doc(hidden)]
fn __into_arguments(self) -> (Self::__Inner, Self::__StoredBeforeMessage);

#[doc(hidden)]
unsafe fn __process_after_message_send(_stored: Self::__StoredBeforeMessage);
}

pub trait TupleExtender<T> {
#[doc(hidden)]
type PlusOneArgument;
#[doc(hidden)]
fn add_argument(self, arg: T) -> Self::PlusOneArgument;
}

macro_rules! args_impl {
($($a:ident: $t:ident),*) => (
impl<$($t: ConvertArgument),*> ConvertArguments for ($($t,)*) {
type __Inner = ($($t::__Inner,)*);

type __StoredBeforeMessage = ($($t::__StoredBeforeMessage,)*);

#[inline]
fn __into_arguments(self) -> (Self::__Inner, Self::__StoredBeforeMessage) {
let ($($a,)*) = self;
$(let $a = ConvertArgument::__into_argument($a);)*

(($($a.0,)*), ($($a.1,)*))
}

#[inline]
unsafe fn __process_after_message_send(($($a,)*): Self::__StoredBeforeMessage) {
$(
unsafe { <$t as ConvertArgument>::__process_after_message_send($a) };
)*
}
}

impl<$($t,)* T> TupleExtender<T> for ($($t,)*) {
type PlusOneArgument = ($($t,)* T,);

#[inline]
fn add_argument(self, arg: T) -> Self::PlusOneArgument {
let ($($a,)*) = self;
($($a,)* arg,)
}
}
);
}

args_impl!();
args_impl!(a: A);
args_impl!(a: A, b: B);
args_impl!(a: A, b: B, c: C);
args_impl!(a: A, b: B, c: C, d: D);
args_impl!(a: A, b: B, c: C, d: D, e: E);
args_impl!(a: A, b: B, c: C, d: D, e: E, f: F);
args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J);
args_impl!(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
h: H,
i: I,
j: J,
k: K
);
args_impl!(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
h: H,
i: I,
j: J,
k: K,
l: L
);
args_impl!(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
h: H,
i: I,
j: J,
k: K,
l: L,
m: M
);
args_impl!(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
h: H,
i: I,
j: J,
k: K,
l: L,
m: M,
n: N
);
args_impl!(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
h: H,
i: I,
j: J,
k: K,
l: L,
m: M,
n: N,
o: O
);
args_impl!(
a: A,
b: B,
c: C,
d: D,
e: E,
f: F,
g: G,
h: H,
i: I,
j: J,
k: K,
l: L,
m: M,
n: N,
o: O,
p: P
);

#[cfg(test)]
mod tests {
use super::*;
Expand Down
Loading

0 comments on commit f0591af

Please sign in to comment.