madsmtm / objc2

Bindings to Apple's frameworks in Rust
https://docs.rs/objc2/
MIT License
281 stars 35 forks source link

Generate external categories #574

Closed madsmtm closed 3 months ago

madsmtm commented 3 months ago

Categories are a way for Objective-C code to add extra methods to a class. There are basically two ways we could handle these in header-translator:

  1. Emit extern_methods! directly on the class (done currently).

    This breaks when the class is defined in a different library/framework than the category, because of Rust's coherence rules.

  2. Create a trait, and implement that for the class (done in uikit-sys, and now by extern_category!).

    One problem here is that the category name is not part of the API in Objective-C, so we actually have no stable name to refer to. Relatedly, it's annoying to have to import category traits with confusing names all the time.

Both approaches have issues, so I've tried to strike a nice balance; we now emit extern_methods! when the category is defined in the same library/framework as the class, and extern_category! otherwise. This doesn't completely resolve the issues around categories not having a stable name, but it does somewhat alleviate them.

This allows us to fix https://github.com/madsmtm/objc2/issues/531, we now emit the missing key-value coding categories (and other such categories on NSObject). This should also help with splitting icrate up into multiple crates in the future, instead of it being a big monolithic one. Finally, this is required for header-translator to work on user-defined categories in the future (see https://github.com/madsmtm/objc2/issues/501).

CC @simlay as you've gone with the second approach in bindgen's Objective-C support, wanted to hear your thoughts about this combined solution? I note that yours is currently better, since it implements the category trait for subclasses as well.

simlay commented 3 months ago

Both approaches have issues, so I've tried to strike a nice balance; we now emit extern_methods! when the category is defined in the same library/framework as the class, and extern_category! otherwise. This doesn't completely resolve the issues around categories not having a stable name, but it does somewhat alleviate them.

Yeah, I think this is a reasonable trade off. I think the negative to this approach is that debugging/understanding is harder. There's two possible ways a function is added to a rust-side class - importing the trait that the category implements (a class has a category from a different library/framework) vs not.

The ergonomics of including a bunch of traits just for a few specific functions was pretty rough.

Semirelated, I wish there was a good way to do source maps/links to apple docs for things.

madsmtm commented 3 months ago

Both approaches have issues, so I've tried to strike a nice balance; we now emit extern_methods! when the category is defined in the same library/framework as the class, and extern_category! otherwise. This doesn't completely resolve the issues around categories not having a stable name, but it does somewhat alleviate them.

Yeah, I think this is a reasonable trade off. I think the negative to this approach is that debugging/understanding is harder. There's two possible ways a function is added to a rust-side class - importing the trait that the category implements (a class has a category from a different library/framework) vs not.

Thanks for the feedback; I'll try to document it at some point, tracking in https://github.com/madsmtm/objc2/issues/576.

Semirelated, I wish there was a good way to do source maps/links to apple docs for things.

Well, there is no good way, but there probably is a way, tracking in https://github.com/madsmtm/objc2/issues/309.