Skip to content

Commit

Permalink
Allow using MainThreadMarker in extern_methods!
Browse files Browse the repository at this point in the history
  • Loading branch information
madsmtm committed Feb 1, 2023
1 parent 2393bf2 commit d817eff
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 0 deletions.
1 change: 1 addition & 0 deletions crates/objc2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This includes removing the `EncodeConvert` and `EncodeArguments` traits.
* Added support for out-parameters like `&mut Id<_, _>` in `msg_send!`,
`msg_send_id!` and `extern_methods!`.
* Allow using `MainThreadMarker` in `extern_methods!`.

### Changed
* **BREAKING**: Using the automatic `NSError**`-to-`Result` functionality in
Expand Down
50 changes: 50 additions & 0 deletions crates/objc2/src/macros/__method_msg_send.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,30 @@ macro_rules! __method_msg_send {
}
};

// Skip using `MainThreadMarker` in the message send.
//
// This is a purely textual match, and using e.g.
// `Foundation::MainThreadMarker` would fail - but that would just be
// detected as giving a wrong number of arguments, so it's fine for now.
(
($receiver:expr)
($($sel_rest:tt)*)
($arg:ident: MainThreadMarker $(, $($args_rest:tt)*)?)

($($sel_parsed:tt)*)
($($arg_parsed:tt)*)
) => ({
let _ = $arg;
$crate::__method_msg_send! {
($receiver)
($($sel_rest)*)
($($($args_rest)*)?)

($($sel_parsed)*)
($($arg_parsed)*)
}
});

// Parse each argument-selector pair
(
($receiver:expr)
Expand Down Expand Up @@ -170,6 +194,32 @@ macro_rules! __method_msg_send_id {
}
};

// Skip using `MainThreadMarker` in the message send.
//
// This is a purely textual match, and using e.g.
// `Foundation::MainThreadMarker` would fail - but that would just be
// detected as giving a wrong number of arguments, so it's fine for now.
(
($receiver:expr)
($($sel_rest:tt)*)
($arg:ident: MainThreadMarker $(, $($args_rest:tt)*)?)

($($sel_parsed:tt)*)
($($arg_parsed:tt)*)
($($retain_semantics:ident)?)
) => ({
let _ = $arg;
$crate::__method_msg_send_id! {
($receiver)
($($sel_rest)*)
($($($args_rest)*)?)

($($sel_parsed)*)
($($arg_parsed)*)
($($retain_semantics)?)
}
});

// Parse each argument-selector pair
(
($receiver:expr)
Expand Down
4 changes: 4 additions & 0 deletions crates/objc2/src/macros/extern_methods.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
/// [`Result`]. See the error section in [`msg_send!`] and [`msg_send_id!`]
/// for details.
///
/// If you use `icrate::Foundation::MainThreadMarker` as a parameter type, the
/// macro will ignore it, allowing you to neatly specify "this method must be
/// run on the main thread".
///
/// Putting other attributes on the method such as `cfg`, `allow`, `doc`,
/// `deprecated` and so on is supported. However, note that `cfg_attr` may not
/// work correctly, due to implementation difficulty - if you have a concrete
Expand Down
77 changes: 77 additions & 0 deletions crates/objc2/tests/macros_mainthreadmarker.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
use objc2::rc::{Id, Shared};
use objc2::runtime::{NSObject, NSObjectProtocol};
use objc2::{declare_class, extern_methods, extern_protocol, ClassType, ProtocolType};

extern_protocol!(
unsafe trait Proto: NSObjectProtocol {
#[method(myMethod:)]
fn protocol_method(mtm: MainThreadMarker, arg: i32) -> i32;

#[method_id(myMethodId:)]
fn protocol_method_id(mtm: MainThreadMarker, arg: &Self) -> Id<Self, Shared>;
}

unsafe impl ProtocolType for dyn Proto {
const NAME: &'static str = "MainThreadMarkerTestProtocol";
}
);

declare_class!(
#[derive(PartialEq, Eq, Hash, Debug)]
struct Cls;

unsafe impl ClassType for Cls {
type Super = NSObject;
const NAME: &'static str = "MainThreadMarkerTest";
}

unsafe impl Proto for Cls {
#[method(myMethod:)]
fn _my_mainthreadonly_method(arg: i32) -> i32 {
arg + 1
}

#[method_id(myMethodId:)]
fn _my_mainthreadonly_method_id(arg: &Self) -> Id<Self, Shared> {
unsafe { Id::retain(arg as *const Self as *mut Self).unwrap() }
}
}
);

unsafe impl NSObjectProtocol for Cls {}

// The macro does a textual match; but when users actually use
// `icrate::Foundation::MainThreadMarker` to ensure soundness, they will not
// do this!
type MainThreadMarker = ();

extern_methods!(
unsafe impl Cls {
#[method_id(new)]
fn new() -> Id<Self, Shared>;

#[method(myMethod:)]
fn method(mtm: MainThreadMarker, arg: i32, mtm2: MainThreadMarker) -> i32;

#[method_id(myMethodId:)]
fn method_id(mtm: MainThreadMarker, arg: &Self, mtm2: MainThreadMarker)
-> Id<Self, Shared>;
}
);

#[test]
fn call() {
let obj1 = Cls::new();
let mtm = ();

let res = Cls::method(mtm, 2, mtm);
assert_eq!(res, 3);
let res = Cls::protocol_method(mtm, 3);
assert_eq!(res, 4);

let obj2 = Cls::method_id(mtm, &obj1, mtm);
assert_eq!(obj1, obj2);

let obj2 = Cls::protocol_method_id(mtm, &obj1);
assert_eq!(obj1, obj2);
}

0 comments on commit d817eff

Please sign in to comment.