Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

declare_class! macro #190

Merged
merged 21 commits into from
Jul 17, 2022
Merged

declare_class! macro #190

merged 21 commits into from
Jul 17, 2022

Conversation

madsmtm
Copy link
Owner

@madsmtm madsmtm commented Jul 6, 2022

Blocked on #193.

Solves a big part of #30. Fixes upstream SSheldon/rust-objc#74.

Related to #161, and has a few of the same limitations when it comes to defining methods.

Syntax:

declare_class! {
    // Similar to `extern_class!`
    // Implements protocol `NSApplicationDelegate`
    unsafe struct CustomAppDelegate: NSResponder, NSObject <NSApplicationDelegate> {
        pub ivar: u8,
        another_ivar: Bool,
    }

    unsafe impl {
        @sel(abc:defGhi:)
        fn abc_def_ghi(&self, arg1: &Object, arg2: i32) {
            println!("{}", self.ivar + arg2);
        }
    }
}

// Generates

pub struct ivar;
unsafe impl objc2::declare::IvarType for ivar {
    type Type = u8;
    const NAME: &'static str = "ivar";
}

struct another_ivar;
unsafe impl objc2::declare::IvarType for another_ivar {
    type Type = Bool;
    const NAME: &'static str = "another_ivar";
}

struct CustomAppDelegate: NSResponder, NSObject <NSApplicationDelegate> {
    pub ivar: objc2::declare::Ivar<ivar>,
    another_ivar: objc2::declare::Ivar<another_ivar>,
}

impl CustomAppDelegate {
    // Added `extern "C"` and `Sel` argument
    extern "C" fn abc_def_ghi(&self, _: Sel, arg1: &Object, arg2: i32) {
        println!("{}", self.ivar + arg2);
    }
}

impl CustomAppDelegate {
    fn __create_class() -> &'static Class {
        let superclass = NSResponder::class(); // Subclasses `NSResponder`
        let mut builder = ClassBuilder::new("CustomAppDelegate", superclass).unwrap();

        // Implement protocols
        builder.add_protocol(Protocol::get("NSApplicationDelegate"));

        // Declare ivars
        builder.add_ivar::<<ivar as IvarType>::Type>(<ivar as IvarType>::NAME);
        builder.add_ivar::<<another_ivar as IvarType>::Type>(<another_ivar as IvarType>::NAME);

        // Declare methods
        unsafe {
            builder.add_method(
                sel!(abc:defGhi:),
                CustomAppDelegate::abc_def_ghi as extern "C" fn(_, _, _, _) -> _,
            );
        }

        builder.register()
    }

    pub fn class() -> &'static Class {
        static REGISTER: Once = Once::new();
        REGISTER.call_once(Self::__create_class);
        Class::get("CustomAppDelegate").unwrap()
    }
}

The really cool thing here is the instance variable setup, with that, users can just do self.ivar in a completely hassle-free manner!

@madsmtm madsmtm added enhancement New feature or request A-objc2 Affects the `objc2`, `objc2-exception-helper` and/or `objc2-encode` crates A-framework Affects the framework crates and the translator for them labels Jul 6, 2022
@madsmtm madsmtm added this to the objc2 v0.3 milestone Jul 6, 2022
@madsmtm
Copy link
Owner Author

madsmtm commented Jul 7, 2022

Consider the following method:

impl MyObject {
    @sel(myMethod:)
    fn my_method(&self, arg: i32) {
        // Body
    }
}

There are essentially three ways to handle converting the methods into the proper extern "C" methods:

  1. Transform it into a single inherent extern "C" method with a hidden _cmd parameter (this is actually basically equivalent to what clang does to Objective-C methods):
    impl MyObject {
        extern "C" fn my_method(&self, _cmd: Sel, arg: i32) {
            // Body
        }
    }
  2. Transform it into two inherent methods:
    impl MyObject {
        // Original
        fn my_method(&self, arg: i32) {
            // Body
        }
        // Helper
        extern "C" fn __my_method(&self, _cmd: Sel, arg: i32) {
            self.my_method(arg)
        }
    }
  3. Transform it into an inherent method and a helper function:
    impl MyObject {
        fn my_method(&self, arg: i32) {
            // Body
        }
    }
    
    // When creating class:
    
    extern "C" fn my_method(this: &MyObject, _cmd: Sel, arg: i32) {
        this.my_method(arg)
    }
    builder.add_method(...);

I like option 2 & 3 because they preserve what the user wrote, i.e. their function stays exactly the same and they can call it as such! However, option 2 has the downside that we can't name the __my_method function without proc macros, and option 3 has the downside that Self can no longer be used in the method definition.

So... Yeah. I'll probably go with option 1, if users want to access the method's functionality from Rust they should either use msg_send!, or just drag the functionality they want to access out into a separate inherent method themselves.

There might be merit in making it create said accessor method that uses msg_send! at the same time, similar to #161, but I'll defer that for now (see also #30 (comment)).

Idea: Maybe use extern "Objective-C" fn my_method... in the macro to signal that the method signature is rewritten? Or extern "Objective-C" impl? Or maybe just require users to write the _cmd argument?

@madsmtm madsmtm force-pushed the declare-macro branch 5 times, most recently from 6547b2c to aa12984 Compare July 9, 2022 21:55
@madsmtm madsmtm marked this pull request as ready for review July 9, 2022 21:57
@madsmtm
Copy link
Owner Author

madsmtm commented Jul 9, 2022

A downside is that rustfmt doesn't work inside the functions, don't really know how to fix that?

EDIT: See rust-lang/rustfmt#5437, a way to fix it would be to use pure-Rust syntax (e.g. no @sel trickery) and declare_class!(...); instead of declare_class! {...}. Which... Basically means that no, we can't do this yet!

@madsmtm madsmtm merged commit bbafcf3 into master Jul 17, 2022
@madsmtm madsmtm deleted the declare-macro branch July 17, 2022 00:21
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-framework Affects the framework crates and the translator for them A-objc2 Affects the `objc2`, `objc2-exception-helper` and/or `objc2-encode` crates enhancement New feature or request
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant