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

Add closure wrapper to allow more easily calling action:target:-like methods? #561

Open
madsmtm opened this issue Jan 16, 2024 · 3 comments
Labels
A-framework Affects the framework crates and the translator for them enhancement New feature or request question Further information is requested

Comments

@madsmtm
Copy link
Owner

madsmtm commented Jan 16, 2024

It is common in AppKit and UIKit to have methods that expect to run a selector on an object. These are usually legacy methods that were introduced before blocks were added to / common in Objective-C, or work this way because the event bubbles upwards.

Examples include NSMenuItem, NSNotificationCenter and UIGestureRecognizer.

It would maybe be nice if we had some object wrapper around closures, such that one didn't have to go through the whole of declare_class! just to handle an event?

I'm thinking something similar to:

// Implementation

unsafe trait Wrapable {
    fn _get_sel() -> Sel;
}
impl Wrapable for Fn() -> R { ... }
impl Wrapable for Fn(T) -> R { ... }
impl Wrapable for Fn(T, U) -> R { ... }
// ...

fn wrap<F: 'static + Wrapable>(f: F) -> (Id<NSObject>, Sel) {
    extern "C" fn method<F: Wrapable>(obj: &NSObject, ...args) -> R {
        (IVAR.load(obj))(...args)
    }

    let sel = F::_get_sel();
    let cls = { // Wrapped in a Once somehow?
        // Unsure if TypeId is unique enough for different generics?
        let builder = ClassBuilder::new(format!("WrappedClosure_{:?}", TypeId::of::<F>())).unwrap();
        builder.add_ivar::<F>("closure");
        builder.add_method(sel, method::<F>);
        builder.register()
    };

    static IVAR = cls.instance_variable("closure").unwrap();

    let obj: Id<NSObject> = msg_send_id![cls, new];
    IVAR.load_ptr(&obj).write(f);

    (obj, sel)
}

// Usage
let center = DistributedNotificationCenter::defaultCenter();
let (obj, sel) = wrap(|notification: &NSNotification| {
    // handle notification
});
center.addObserver_selector_name_object(obj, sel, ns_string!("AppleInterfaceThemeChangedNotification"), None)
@madsmtm madsmtm added enhancement New feature or request question Further information is requested A-framework Affects the framework crates and the translator for them labels Jan 16, 2024
@eks5115
Copy link

eks5115 commented Apr 22, 2024

When leaving this scope, obj will be released and sel will not be called. How to solve.

@madsmtm
Copy link
Owner Author

madsmtm commented Apr 22, 2024

-[NSMenuItem target] is a weak property, which means that it will not by itself prevent the object from being deallocated.

You will have to keep the object that you want to use as the target around manually, probably by storing the Id<...> in a struct somewhere (depends on your setup).

@eks5115
Copy link

eks5115 commented Apr 23, 2024

Thank you. I know what to do, okay

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 enhancement New feature or request question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants