objc2 icon indicating copy to clipboard operation
objc2 copied to clipboard

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

Open madsmtm opened this issue 6 months ago • 3 comments

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 avatar Jan 16 '24 23:01 madsmtm