├── .gitignore ├── Cargo.toml ├── Readme.md ├── LICENSE └── src └── lib.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | Cargo.lock 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "objc-derive" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [dependencies] 12 | syn = { version = "1.0", features = ["full"] } 13 | quote = "1.0" 14 | proc-macro2 = "1.0" -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | `objc-derive` allows Rust to interop with Objective-C seamlessly. 2 | 3 | ### Calling Objective-C 4 | 5 | Example 6 | 7 | ```Rust 8 | impl NSString { 9 | #[selector_export("initWithBytes:length:encoding:")] 10 | pub fn init_with_bytes_length_encoding(&self, bytes: *const c_void, length: usize, encoding: u64) -> NSString; 11 | 12 | #[selector_export("lengthOfBytesUsingEncoding:")] 13 | pub fn length_of_bytes_using_encoding(&self, encoding: u64) -> usize; 14 | 15 | #[selector_export("UTF8String")] 16 | pub fn utf8_string(&self) -> *const u8; 17 | } 18 | ``` 19 | 20 | ### Use Rust to implement Objective-C selectors 21 | 22 | ```Rust 23 | #[objc_impl(UIViewController)] 24 | impl MainViewController { 25 | #[selector_impl("loadView")] 26 | fn load_view(&self, this: &Object) { 27 | // Rust code here, will be called by UIKit 28 | } 29 | 30 | #[selector_impl("viewDidLoad")] 31 | fn view_did_load(&self, this: &Object) { 32 | // Rust code here 33 | } 34 | } 35 | ``` 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 wooden-worm 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | extern crate proc_macro; 2 | extern crate syn; 3 | #[macro_use] 4 | extern crate quote; 5 | 6 | use proc_macro::TokenStream; 7 | use proc_macro2::{Ident, Span}; 8 | use syn::{parse_macro_input, FnArg, ImplItemMethod, ItemImpl, Pat, Signature}; 9 | 10 | #[proc_macro_attribute] 11 | pub fn objc_impl(attr: TokenStream, item: TokenStream) -> TokenStream { 12 | let attr_string = attr.to_string(); 13 | let attributes = parse_export_objc_attributes(&attr_string); 14 | 15 | // println!("input: {}", item.to_string()); 16 | let input = parse_macro_input!(item as ItemImpl); 17 | let input_type = input.self_ty.clone(); 18 | 19 | let super_class_name = Ident::new(&attributes.super_class_name, Span::call_site()); 20 | let protocol_name = attributes 21 | .protocol 22 | .map(|val| Ident::new(&val, Span::call_site())); 23 | 24 | let protocol_block = if let Some(protocol_name) = protocol_name { 25 | quote! { 26 | { 27 | let p = Protocol::get(stringify!(#protocol_name)).unwrap(); 28 | decl.add_protocol(p); 29 | } 30 | } 31 | } else { 32 | quote! { 33 | {} 34 | } 35 | }; 36 | 37 | let all_methods = input 38 | .items 39 | .iter() 40 | .filter_map(|val| match val { 41 | syn::ImplItem::Method(val) => { 42 | if val.attrs.is_empty() { 43 | return None; 44 | } 45 | 46 | let path = val.attrs[0].path.clone(); 47 | if path.is_ident("selector_init") { 48 | let add_method = quote! { 49 | decl.add_method( 50 | objc::sel!(init), 51 | #input_type::generated_init as extern "C" fn(&Object, _) -> id, 52 | ); 53 | }; 54 | Some(add_method) 55 | } else if path.is_ident("selector_impl") { 56 | let objc_delegate = &val.attrs[0].tokens.to_string(); 57 | // let objc_delegate = quote!(#objc_delegate); 58 | let objc_delegate_name = get_objc_selector_full(&*objc_delegate); 59 | let objc_selector_token: proc_macro2::TokenStream = 60 | objc_delegate_name.parse().unwrap(); 61 | let sel_func_type = generate_sel_function_type(val); 62 | let sel_func_name = generate_sel_function_name(val); 63 | 64 | let add_method = quote! { 65 | decl.add_method( 66 | objc::sel!(#objc_selector_token), 67 | #input_type::#sel_func_name as #sel_func_type, 68 | ); 69 | }; 70 | Some(add_method) 71 | } else { 72 | None 73 | } 74 | } 75 | _ => None, 76 | }) 77 | .collect::>(); 78 | 79 | let dealloc_method = quote! { 80 | decl.add_method( 81 | objc::sel!(dealloc), 82 | #input_type::generated_dealloc as extern "C" fn(&objc::runtime::Object, _), 83 | ); 84 | }; 85 | 86 | let input_type_string = quote! { 87 | #input_type 88 | } 89 | .to_string(); 90 | let objc_class_ident = format_ident!("RUST_{}", input_type_string); 91 | let r = quote! { 92 | impl #input_type { 93 | pub fn objc_class_name() -> &'static str { 94 | stringify!(#objc_class_ident) 95 | } 96 | 97 | pub fn register_rust_class() -> *const objc::runtime::Class { 98 | use objc::{ 99 | class, 100 | declare::ClassDecl, 101 | msg_send, 102 | runtime::{Class, Object, Protocol, Sel}, 103 | sel, sel_impl, 104 | }; 105 | 106 | static mut CLASS_POINTER: *const objc::runtime::Class = 0 as *const objc::runtime::Class; 107 | static INIT: std::sync::Once = std::sync::Once::new(); 108 | 109 | INIT.call_once(|| unsafe { 110 | let class_name = Self::objc_class_name(); 111 | 112 | let superclass = objc::class!(#super_class_name); 113 | let mut decl = objc::declare::ClassDecl::new(class_name, superclass).unwrap(); 114 | 115 | decl.add_ivar::("RUST_OBJ_PTR"); 116 | 117 | #protocol_block 118 | 119 | #(#all_methods)* 120 | 121 | #dealloc_method 122 | 123 | 124 | // Launching Applications 125 | CLASS_POINTER = decl.register(); 126 | }); 127 | 128 | unsafe { CLASS_POINTER } 129 | } 130 | 131 | pub fn init_objc_proxy_obj(self: std::sync::Arc) -> *mut objc::runtime::Object { 132 | use objc::{ 133 | class, 134 | declare::ClassDecl, 135 | msg_send, 136 | runtime::{Class, Object, Protocol, Sel}, 137 | sel, sel_impl, 138 | }; 139 | 140 | let class = #input_type::register_rust_class(); 141 | let objc_object = unsafe { 142 | let ret: *mut objc::runtime::Object = msg_send![class, new]; 143 | ret 144 | }; 145 | let raw_ptr = std::sync::Arc::into_raw(self); 146 | let raw_ptr_value = raw_ptr as usize; 147 | 148 | unsafe { 149 | (&mut *objc_object).set_ivar("RUST_OBJ_PTR", raw_ptr_value); 150 | } 151 | 152 | objc_object 153 | } 154 | 155 | extern "C" fn generated_dealloc(this: &objc::runtime::Object, _: objc::runtime::Sel) { 156 | let arc = unsafe { 157 | let raw_ptr_value: usize = *this.get_ivar("RUST_OBJ_PTR"); 158 | let raw_ptr = raw_ptr_value as *const Self; 159 | std::sync::Arc::from_raw(raw_ptr) 160 | }; 161 | drop(arc); 162 | } 163 | } 164 | 165 | #input 166 | }; 167 | 168 | r.into() 169 | } 170 | 171 | #[proc_macro_attribute] 172 | pub fn selector_impl(_attr: TokenStream, item: TokenStream) -> TokenStream { 173 | let input = parse_macro_input!(item as ImplItemMethod); 174 | 175 | let method_name = input.sig.ident.clone(); 176 | 177 | let new_sig = generate_sel_function(&input); 178 | let args = generate_sel_function_args(&input); 179 | let fn_body = quote! { 180 | { 181 | let arc = unsafe { 182 | let raw_ptr_value: usize = *this.get_ivar("RUST_OBJ_PTR"); 183 | let raw_ptr = raw_ptr_value as *const Self; 184 | std::sync::Arc::from_raw(raw_ptr) 185 | }; 186 | 187 | let ret = arc.#method_name(this, #(#args,)*); 188 | std::sync::Arc::into_raw(arc); 189 | ret 190 | } 191 | }; 192 | 193 | let generated_func = quote! { 194 | #new_sig 195 | #fn_body 196 | }; 197 | 198 | let ret = quote! { 199 | #input 200 | 201 | #generated_func 202 | }; 203 | ret.into() 204 | } 205 | 206 | #[proc_macro_attribute] 207 | pub fn selector_init(_attr: TokenStream, item: TokenStream) -> TokenStream { 208 | let input = parse_macro_input!(item as ImplItemMethod); 209 | 210 | let method_name = input.sig.ident.clone(); 211 | 212 | let ret = quote! { 213 | #input 214 | 215 | extern "C" fn generated_init(this: &Object, _: objc::runtime::Sel) -> id { 216 | use objc::{ 217 | class, 218 | declare::ClassDecl, 219 | msg_send, 220 | runtime::{Class, Object, Protocol, Sel}, 221 | sel, sel_impl, 222 | }; 223 | 224 | Self::register_rust_class(); 225 | let arc = Self::#method_name(); 226 | 227 | let objc_object = unsafe { 228 | // TODO: don't hard code UIResponder 229 | let ret: *mut objc::runtime::Object = msg_send![super(this, class!(UIResponder)), init]; 230 | ret 231 | }; 232 | let raw_ptr = std::sync::Arc::into_raw(arc); 233 | let raw_ptr_value = raw_ptr as usize; 234 | 235 | unsafe { 236 | (&mut *objc_object).set_ivar("RUST_OBJ_PTR", raw_ptr_value); 237 | } 238 | 239 | objc_object 240 | } 241 | }; 242 | ret.into() 243 | } 244 | 245 | #[proc_macro_attribute] 246 | pub fn selector_export(attr: TokenStream, item: TokenStream) -> TokenStream { 247 | // eprintln!("attr {}", attr); 248 | // let attr_string = attr.to_string(); 249 | 250 | let input = parse_macro_input!(item as ImplItemMethod); 251 | 252 | let attr_string = attr.to_string(); 253 | let attributes = parse_trait_selector_attributes(&attr_string); 254 | // eprintln!("attr {:?}", attributes); 255 | 256 | let objc_selector = ObjcSelector::from(attributes.selector.as_str()); 257 | // eprintln!("objc_selector {:?}", objc_selector); 258 | 259 | let message = if objc_selector.n_args == 0 { 260 | let arg_name = Ident::new(&objc_selector.parts[0], Span::call_site()); 261 | quote!(#arg_name) 262 | } else { 263 | let arg_idents = function_input_idents(input.sig.clone(), attributes.class_name.is_some()); 264 | let pair_iter = objc_selector 265 | .parts 266 | .into_iter() 267 | .zip(arg_idents.into_iter()) 268 | .map(|(arg, arg_ident)| { 269 | let arg_name = Ident::new(&arg, Span::call_site()); 270 | let token = quote! { 271 | #arg_name: #arg_ident 272 | }; 273 | token 274 | }); 275 | quote!( 276 | #(#pair_iter )* 277 | ) 278 | }; 279 | 280 | // eprintln!("message {}", message); 281 | 282 | let vis = input.vis.clone(); 283 | let input_sig = input.sig.clone(); 284 | let ret_type = input_sig.output.clone(); 285 | let ret_type = match ret_type { 286 | syn::ReturnType::Default => quote!(()), 287 | syn::ReturnType::Type(_, type_) => quote!(#type_), 288 | }; 289 | 290 | let target_object = if let Some(class_name) = attributes.class_name { 291 | let class_name_ident = Ident::new(&class_name, Span::call_site()); 292 | quote! { 293 | let target_object = objc::class!(#class_name_ident); 294 | } 295 | } else { 296 | quote! { 297 | let target_object = self.objc_object(); 298 | } 299 | }; 300 | 301 | // eprintln!("target_object {}", target_object); 302 | let fn_body = quote! { 303 | { 304 | use objc::{ 305 | class, 306 | declare::ClassDecl, 307 | msg_send, 308 | runtime::{Class, Object, Protocol, Sel}, 309 | sel, sel_impl, 310 | }; 311 | unsafe { 312 | #target_object 313 | 314 | let ret: #ret_type = msg_send![target_object, #message]; 315 | ret 316 | } 317 | } 318 | }; 319 | 320 | let ret = quote! { 321 | #vis #input_sig 322 | #fn_body 323 | }; 324 | ret.into() 325 | } 326 | 327 | fn get_objc_selector_full(token: &str) -> &str { 328 | token 329 | .trim_start_matches('(') 330 | .trim_start_matches('"') 331 | .trim_end_matches(')') 332 | .trim_end_matches('"') 333 | } 334 | 335 | #[derive(Debug)] 336 | struct ObjcSelector { 337 | parts: Vec, 338 | n_args: usize, 339 | } 340 | 341 | impl From<&str> for ObjcSelector { 342 | fn from(input: &str) -> Self { 343 | let part_slit = input.split(':'); 344 | let (parts, has_empty) = part_slit.fold( 345 | (Vec::::new(), false), 346 | |(mut acc, has_empty), val| { 347 | if val != "" { 348 | acc.push(val.to_string()); 349 | (acc, has_empty) 350 | } else { 351 | (acc, true) 352 | } 353 | }, 354 | ); 355 | 356 | let n_args = if has_empty { 357 | parts.len() 358 | } else { 359 | parts.len() - 1 360 | }; 361 | 362 | ObjcSelector { parts, n_args } 363 | } 364 | } 365 | 366 | fn generate_sel_function_name(input: &ImplItemMethod) -> proc_macro2::Ident { 367 | let orginal_name = input.sig.ident.to_string(); 368 | let new_name_str = format!("generated_{}", orginal_name); 369 | let new_name_ident = Ident::new(&new_name_str, Span::call_site()); 370 | 371 | new_name_ident 372 | } 373 | 374 | fn generate_sel_function_args(input: &ImplItemMethod) -> Vec> { 375 | let args = { 376 | let mut ret = input.sig.inputs.clone().into_iter().collect::>(); 377 | ret.remove(0); 378 | ret.remove(0); 379 | ret.into_iter() 380 | .map(|val| match val { 381 | FnArg::Receiver(_) => todo!(), 382 | FnArg::Typed(val) => val.pat, 383 | }) 384 | .collect() 385 | }; 386 | 387 | args 388 | } 389 | 390 | fn generate_sel_function(input: &ImplItemMethod) -> proc_macro2::TokenStream { 391 | let new_name_ident = generate_sel_function_name(input); 392 | 393 | let vis = input.vis.clone(); 394 | let args = { 395 | let mut ret = input.sig.inputs.clone().into_iter().collect::>(); 396 | ret.remove(0); 397 | ret.remove(0); 398 | ret 399 | }; 400 | let return_type = input.sig.output.clone(); 401 | 402 | quote! { 403 | #vis extern "C" fn #new_name_ident(this: &objc::runtime::Object, _sel: objc::runtime::Sel, #(#args,)*) #return_type 404 | } 405 | } 406 | 407 | fn generate_sel_function_type(input: &ImplItemMethod) -> proc_macro2::TokenStream { 408 | let args = { 409 | let mut ret = input.sig.inputs.clone().into_iter().collect::>(); 410 | ret.remove(0); 411 | ret.remove(0); 412 | ret 413 | }; 414 | let return_type = input.sig.output.clone(); 415 | 416 | quote! { 417 | extern "C" fn(this: &objc::runtime::Object, _sel: objc::runtime::Sel, #(#args,)*) #return_type 418 | } 419 | } 420 | 421 | fn function_input_idents(sig: Signature, is_type_method: bool) -> Vec> { 422 | let args = { 423 | let mut ret = sig.inputs.clone().into_iter().collect::>(); 424 | if !is_type_method { 425 | ret.remove(0); 426 | } 427 | 428 | ret 429 | }; 430 | 431 | let arg_idents = args 432 | .into_iter() 433 | .map(|val| match val { 434 | FnArg::Receiver(_) => panic!("unexpected receiver type"), 435 | FnArg::Typed(pattype) => pattype.pat, 436 | }) 437 | .collect::>(); 438 | 439 | arg_idents 440 | } 441 | 442 | #[derive(Debug)] 443 | struct ExportObjCAttributes { 444 | super_class_name: String, 445 | protocol: Option, 446 | } 447 | 448 | fn parse_export_objc_attributes(attributes: &str) -> ExportObjCAttributes { 449 | let mut vals = attributes.split(','); 450 | 451 | let super_class_name = vals.next().unwrap().trim().to_string(); 452 | let protocol = vals.next().map(|val| val.trim().to_string()); 453 | 454 | ExportObjCAttributes { 455 | super_class_name, 456 | protocol, 457 | } 458 | } 459 | 460 | #[derive(Debug)] 461 | struct TraitSelectorAttributes { 462 | class_name: Option, 463 | selector: String, 464 | } 465 | 466 | fn parse_trait_selector_attributes(attributes: &str) -> TraitSelectorAttributes { 467 | let vals = attributes.split(',').collect::>(); 468 | 469 | if vals.len() == 1 { 470 | let selector = vals[0]; 471 | let selector = get_objc_selector_full(selector).to_string(); 472 | return TraitSelectorAttributes { 473 | class_name: None, 474 | selector, 475 | }; 476 | } 477 | 478 | let class_name = vals[0].trim().to_string(); 479 | let selector = vals[1].trim(); 480 | let selector = get_objc_selector_full(selector).to_string(); 481 | 482 | TraitSelectorAttributes { 483 | class_name: Some(class_name), 484 | selector, 485 | } 486 | } 487 | 488 | // fn ident_to_string(ident: &Ident) -> String { 489 | // format!("{}", ident) 490 | // } 491 | 492 | // /// syn -> string https://github.com/dtolnay/syn/issues/294 493 | // fn type_to_string(ty: &Type) -> String { 494 | // quote!(#ty).to_string().replace(" ", "") 495 | // } 496 | 497 | #[cfg(test)] 498 | mod tests { 499 | use crate::ObjcSelector; 500 | 501 | #[test] 502 | fn it_works() { 503 | let selector = "test:"; 504 | let parts = ObjcSelector::from(selector); 505 | dbg!(parts); 506 | assert_eq!(2 + 2, 4); 507 | } 508 | } 509 | --------------------------------------------------------------------------------