Rust 包装 objc Block

Block 简介

使用 objc 开发 App 时, 经常会使用到 Block, 这个语法糖是 ClangC 语言实现的一个拓展. Block 是可以被编译成 C 语言的代码的.
如果有想法可以直接看 Clang 官方关于 Block 的文档 Block-ABI-Apple

rewrite-objc 生成 cpp 代码

先来用 Clang 把一个普通的 objc 文件生成到 Cpp 代码, 看看 Block 生成的 C 语言代码长啥样. 先写个简单的 hello world 程序

1#import <stdio.h> 2 3int main(void) { 4 @autoreleasepool { 5 void (^test)(void) = ^{ 6 printf("hello, world!\n"); 7 }; 8 test(); 9 } 10 return 0; 11}

然后再用 clang 程序把上面的代码生成到 cpp 代码

1clang -rewrite-objc ./test.m

然后会生成一堆代码, 我们找里面的关键内容

1struct __block_impl { 2 void *isa; 3 int Flags; 4 int Reserved; 5 void *FuncPtr; 6}; 7 8struct __main_block_impl_0 { 9 struct __block_impl impl; 10 struct __main_block_desc_0* Desc; 11 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { 12 impl.isa = &_NSConcreteStackBlock; 13 impl.Flags = flags; 14 impl.FuncPtr = fp; 15 Desc = desc; 16 } 17}; 18static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 19 20 printf("hello, world!\n"); 21 } 22 23static struct __main_block_desc_0 { 24 size_t reserved; 25 size_t Block_size; 26} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; 27int main(void) { 28 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 29 void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA)); 30 ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test); 31 } 32 return 0; 33}

从代码上基本可以肯定

1static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 2 3 printf("hello, world!"); 4 }

表示的是

1^{ 2 printf("hello, world!"); 3};

因为 __main_block_impl_0 包含 __block_impl 这个结构体, 所以

1struct __main_block_impl_0 { 2 struct __block_impl impl; 3 /* 4 void *isa; 5 int Flags; 6 int Reserved; 7 void *FuncPtr; 8 */ 9 struct __main_block_desc_0* Desc; 10 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { 11 impl.isa = &_NSConcreteStackBlock; 12 impl.Flags = flags; 13 impl.FuncPtr = fp; 14 Desc = desc; 15 } 16};

接着看 main 函数里, 把 __main_block_impl_0 构造函数用指针指向它

1// void (*test)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA); 2void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

结构体的构造函数执行后把 fp 指针传给 FuncPtr, fp 指针就是 __main_block_func_0, 也就是那个 hello world 代码.

1__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { 2 // ... 3 impl.FuncPtr = fp; 4 // ... 5 }

使用外部变量的 Block

Block 具备使用外部变量的能力, 有些类似其他语言的闭包, 对于变量的使用分为局部变量跟全局变量, 先来看局部变量

局部变量

局部变量的处理, 又分别针对 auto 变量跟 static 变量有对应的实现.

auto 变量

上面只是简单的 hello worldBlock, 现在来使用一个 Block 之外的 auto 变量, rewrite 后会发生什么.

1#import <stdio.h> 2 3int main(void) { 4 @autoreleasepool { 5 int number = 10; 6 void (^test)(void) = ^{ 7 printf("hello, world!, number = %d\n", number); 8 }; 9 test(); 10 } 11 return 0; 12}
1// ... 2 3struct __main_block_impl_0 { 4 struct __block_impl impl; 5 struct __main_block_desc_0* Desc; 6 int number; 7 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int flags=0) : number(_number) { 8 impl.isa = &_NSConcreteStackBlock; 9 impl.Flags = flags; 10 impl.FuncPtr = fp; 11 Desc = desc; 12 } 13}; 14static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 15 int number = __cself->number; // bound by copy 16 17 printf("hello, world!, number = %d\n", number); 18 } 19 20// ...

这次我们发现, 其他东西没啥变化, 不过 __main_block_impl_0__main_block_func_0 多了个跟 int 类型的 number, 其中还能看出 __main_block_impl_0 赋值给 __cself, 直接通过 __cself 使用 __main_block_impl_0number.

static 变量

再来看看 static 变量的情况

1#import <stdio.h> 2 3int main(void) { 4 @autoreleasepool { 5 int number = 10; 6 static int b = 10; 7 void (^test)(void) = ^{ 8 printf("hello, world!, number = %d, b = %d\n", number, b); 9 }; 10 test(); 11 } 12 return 0; 13}
1// ... 2struct __main_block_impl_0 { 3 // ... 4 int *b; 5 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int *_b, int flags=0) : number(_number), b(_b) { 6 impl.isa = &_NSConcreteStackBlock; 7 impl.Flags = flags; 8 impl.FuncPtr = fp; 9 Desc = desc; 10 } 11}; 12 13static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 14 int number = __cself->number; // bound by copy 15 int *b = __cself->b; // bound by copy 16 17 printf("hello, world!, number = %d, b = %d\n", number, (*b)); 18 } 19 20int main(void) { 21 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 22 int number = 10; 23 static int b = 10; 24 void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number, &b)); 25 ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test); 26 } 27 return 0; 28}

从代码中我们可以看出, 通过 & 操作符把 b 的地址传给 __main_block_impl_0 的构造函数, 同时 __main_block_impl_0 有一个 int *b 的成员, 同时在 __main_block_func_0 里进行解指针操作取值, 其实可以猜到一个行为, 如果在 block 调用之前修改 b, 最后取到的 b 是修改过的值, 因为它是通过 b 的指针进行取值.

全局变量

现在来看看全局变量的情况, 这种情况其实可以猜到, Block 直接使用全局变量, 不会在 struct 里添加成员. 现在来验证一下

1#import <stdio.h> 2 3int number_= 11; 4static int b_ = 11; 5 6int main(void) { 7 @autoreleasepool { 8 int number = 10; 9 static int b = 10; 10 void (^test)(void) = ^{ 11 printf("hello, world!, number = %d, b = %d, number_ = %d, b_ = %d\n", number, b, number_, b_); 12 }; 13 test(); 14 } 15 return 0; 16}
1static void __main_block_func_0(struct __main_block_impl_0 *__cself) { 2 int number = __cself->number; // bound by copy 3 int *b = __cself->b; // bound by copy 4 5 printf("hello, world!, number = %d, b = %d, number_ = %d, b_ = %d\n", number, (*b), number_, b_); 6 }

跟我们刚才猜得行为是一致的.

多参数 Block

继续尝试修改代码后再 rewrite

1#import <stdio.h> 2 3int main(void) { 4 @autoreleasepool { 5 int number = 10; 6 void (^test)(int a) = ^(int a) { 7 printf("hello, world!, number = %d, a = %d\n", number, a); 8 }; 9 test(11); 10 } 11 return 0; 12}
1// ... 2static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a) { 3 int number = __cself->number; // bound by copy 4 5 printf("hello, world!, number = %d, a = %d\n", number, a); 6 } 7 // ... 8int main(void) { 9 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 10 int number = 10; 11 void (*test)(int a) = ((void (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number)); 12 ((void (*)(__block_impl *, int))((__block_impl *)test)->FuncPtr)((__block_impl *)test, 11); 13 } 14 return 0; 15}

__main_block_func_0 参数改变了, 增加了一个 int a 的参数, 当然相应的调用的代码也要改变下, 至于其他的地方, 倒没啥变化.


现在来稍微总结一下, 等于讲 ClangBlock 转成 objc 的对象, 涉及捕获auto 变量时就给 struct 加个外部变量同名的成员, 涉及 static 变量, 就给 struct 加个同名的指针; 如果是访问全局变量, 则会直接在函数内部使用到; 涉及多参数的就给 __main_block_func_0 加更多形参.

关于 _NSConcreteStackBlock

我们再来看最初的 hello world

1#import <stdio.h> 2 3int main(void) { 4 @autoreleasepool { 5 void (^test)(void) = ^{ 6 printf("hello, world!\n"); 7 }; 8 test(); 9 } 10 return 0; 11}
1struct __block_impl { 2 void *isa; 3 int Flags; 4 int Reserved; 5 void *FuncPtr; 6}; 7 8struct __main_block_impl_0 { 9 struct __block_impl impl; 10 struct __main_block_desc_0* Desc; 11 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) { 12 impl.isa = &_NSConcreteStackBlock; 13 impl.Flags = flags; 14 impl.FuncPtr = fp; 15 Desc = desc; 16 } 17};

可以看到有个 isa 的指针, 给 isa 传得是 &_NSConcreteStackBlock, 由此可以看出 Block 是一个 objc 的对象, 同时它的 isa 可能是 _NSConcreteStackBlock.

通过 rewrite-objc 看到 Block 的类型是 _NSConcreteStackBlock, 此外还有另外两个 _NSConcreteGlobalBlock, _NSConcreteMallocBlock, 分别对应以下类型

类型class指定因素
__NSGlobalBlock___NSConcreteGlobalBlock没有访问 auto 变量时
__NSStackBlock___NSConcreteStackBlock访问了 auto 变量
__NSMallocBlock___NSConcreteMallocBlock__NSStackBlock__ 使用 copy

如果对 __NSGlobalBlock__ 使用 copy, 它还是 __NSGlobalBlock__, 并不会改变.
Block 使用 copy 后的结果

class源区域copy 结果
_NSConcreteGlobalBlockdata无动作
_NSConcreteStackBlockstackstack -> heap
_NSConcreteMallocBlockheap引用计数增加

既然 Blockobjc 对象, 那意味着我们可以

1#import <Foundation/Foundation.h> 2 3int main(void) { 4 @autoreleasepool { 5 void (^test)(void) = ^{ 6 printf("hello, world!\n"); 7 }; 8 NSLog(@"%@", [test class]); // __NSGlobalBlock__ 9 10 int a = 10; 11 NSLog(@"%@", [^{ 12 NSLog(@"hello world!, a = %d\n", a); 13 } class]); // __NSStackBlock__ 14 15 NSLog(@"%@", [[^{ 16 NSLog(@"hello world!, a = %d, b = %d\n", a); 17 } copy] class]); // __NSMallocBlock__ 18 } 19 return 0; 20}

然后对比 rewrite 后的代码就会发现, 第一条 NSLog 后出来的是 __NSGlobalBlock__, 说明其类型是 _NSConcreteGlobalBlock, 然而 rewrite-objc 出来的却是 _NSConcreteStackBlock, 第二第三条的 Block 也都是 _NSConcreteStackBlock, 很早之前的 Clang rewrite-objc 出来的内容不是这样的 (至少我 2014 年看到的不是这样的), 这里就不深究了, 以实际执行时的结果为准. 不过这也算是一个好事, 因为我们用 Rust 包装 Block 时只要处理 _NSConcreteStackBlock 就行啦!

其他

其实还有一些 MRCARC 相关的, 以及使用 objc 对象时的情况.

使用 Rust 包装

了解到上面关于 Block 的一些基本原理, 现在来尝试用 Rust 包装一下 Block, 内容来源 rust-block 这个 crate.
首先创建一个 Rust 项目, 直接

1cargo new block --lib

然后把 lib.rs 的内容删掉, 写上这玩意

1enum Class {} 2 3#[cfg_attr( 4 any(target_os = "macos", target_os = "ios"), 5 link(name = "System", kind = "dylib") 6)] 7#[cfg_attr( 8 not(any(target_os = "macos", target_os = "ios")), 9 link(name = "BlocksRuntime", kind = "dylib") 10)] 11extern "C" { 12 static _NSConcreteStackBlock: Class; 13}

这里主要是把 _NSConcreteStackBlock extern 出来, 至于 enum Class {} 是 Rust 的一个技巧, 这里是为了让编译通过, 不想用它可以直接用 (). 至于

1#[cfg_attr( 2 any(target_os = "macos", target_os = "ios"), 3 link(name = "System", kind = "dylib") 4)] 5#[cfg_attr( 6 not(any(target_os = "macos", target_os = "ios")), 7 link(name = "BlocksRuntime", kind = "dylib") 8)]

是预处理一下 extern 块, 前面一段适用于一般的 macOS/iOS 环境, 后面一段适用于带 BlocksRuntimeLinux 环境.

然后照着 rewrite 后的 Cpp 代码的样子写一下 Rust

1#[repr(C)] 2struct BlockBase<A, R> { 3 isa: *const Class, 4 flags: c_int, 5 _reserved: c_int, 6 invoke: unsafe extern "C" fn(*mut Block<A, R>, ...) -> R, 7}

这里 repr(C) 表示的是使用 C 的内存布局, 这里 A 跟 R 泛型表示的是参数类型跟返回结果, 接着我们要描述 Block

1#[repr(C)] 2struct ConcreteBlock<A, R, F> { 3 base: BlockBase<A, R>, 4 descriptor: BlockDescriptor<ConcreteBlock<A, R, F>>, 5} 6 7#[repr(C)] 8struct BlockDescriptor<B> { 9 _reserved: c_ulong, 10 block_size: c_ulong, 11 copy_helper: unsafe extern "C" fn(&mut B, &B), 12 dispose_helper: unsafe extern "C" fn(&mut B), 13}

copy 跟 dispose

这里多了两个叫 copy, dispose 的东西, 前面讲到的 Block 全是跟基础类型(譬如 int) 相关的行为, 如果跟 objc 对象打交道, rewrite-cpp 后就会生成 copydispose, 主要是为了管理 objc 对象的内存, 我们可以来验证一下

1#import <Foundation/Foundation.h> 2 3NS_ASSUME_NONNULL_BEGIN 4 5@interface Person : NSObject { 6@public 7 int _number; 8} 9 10@end 11 12NS_ASSUME_NONNULL_END 13 14@implementation Person 15 16@end 17 18int main(void) { 19 @autoreleasepool { 20 Person *person = [[Person alloc] init]; 21 person->_number = 10; 22 void (^test)(void) = ^{ 23 NSLog(@"%d", person->_number); 24 }; 25 test(); 26 } 27 return 0; 28}

然后做一下 rewrite 操作

1// ... 2static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);} 3 4static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);} 5 6static struct __main_block_desc_0 { 7 size_t reserved; 8 size_t Block_size; 9 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); 10 void (*dispose)(struct __main_block_impl_0*); 11} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0}; 12// ...

所以我们得在 Rust 这边加上这两个玩意, 由于这两个函数是 objc 管理的, 所以 Rust 这边主要是利用一下 drop 的行为

1unsafe extern "C" fn block_context_dispose<B>(block: &mut B) { 2 std::ptr::read(block); 3} 4 5unsafe extern "C" fn block_context_copy<B>(_dst: &mut B, _src: &B) {}

现在来定义一下 Block

1#[repr(C)] 2pub struct Block<A, R> { 3 _base: PhantomData<BlockBase<A, R>>, 4}

Block 内部是由 BlockBase 组成, 但其实并没有用到它, 所以直接用幽灵数据包裹一下, 接着写个 RcBlock 来包装一下 Block 结构体, 顺便把 _Block_copy _Block_release extern 出来, 在 RcBlock drop 时调用 _Block_release, 引用计数增加时调用 _Block_copy

1extern "C" { 2 // ... 3 fn _Block_copy(block: *const c_void) -> *mut c_void; 4 fn _Block_release(block: *const c_void); 5} 6 7pub struct RcBlock<A, R> { 8 ptr: *mut Block<A, R>, 9} 10 11impl<A, R> RcBlock<A, R> { 12 pub unsafe fn new(ptr: *mut Block<A, R>) -> Self { 13 RcBlock { ptr } 14 } 15 16 pub unsafe fn copy(ptr: *mut Block<A, R>) -> Self { 17 let ptr = _Block_copy(ptr as *const c_void) as *mut Block<A, R>; 18 RcBlock { ptr } 19 } 20} 21 22impl<A, R> Clone for RcBlock<A, R> { 23 fn clone(&self) -> Self { 24 unsafe { RcBlock::copy(self.ptr) } 25 } 26} 27 28impl<A, R> Deref for RcBlock<A, R> { 29 type Target = Block<A, R>; 30 fn deref(&self) -> &Self::Target { 31 unsafe { &*self.ptr } 32 } 33} 34 35impl<A, R> Drop for RcBlock<A, R> { 36 fn drop(&mut self) { 37 unsafe { 38 _Block_release(self.ptr as *const c_void); 39 } 40 } 41}

然后再来完善 ConcreteBlock, 主要是把 Rust 的闭包转换成 ConcreteBlock, 在此之前先弄个把参数抽象出来, 先弄个单个参数的, 比较好处理

1pub trait BlockArguments: Sized { 2 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R; 3} 4 5impl<A> BlockArguments for A { 6 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R { 7 let invoke: unsafe extern "C" fn(*mut Block<Self, R>, A) -> R = { 8 let base = block as *mut BlockBase<Self, R>; 9 mem::transmute((*base).invoke) 10 }; 11 let a = self; 12 invoke(block, a) 13 } 14}

然后可以考虑一下多个参数的怎么处理, 没有参数的又怎么处理. 只要把上面的 A 改成元组包装一下, 再用元组处理多个参数的情况

1impl<A> BlockArguments for (A,) { 2 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R { 3 let invoke: unsafe extern "C" fn(*mut Block<Self, R>, A) -> R = { 4 let base = block as *mut BlockBase<Self, R>; 5 mem::transmute((*base).invoke) 6 }; 7 let (a,) = self; 8 invoke(block, a) 9 } 10} 11 12impl<A, B> BlockArguments for (A, B) { 13 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R { 14 let invoke: unsafe extern "C" fn(*mut Block<Self, R>, A, B) -> R = { 15 let base = block as *mut BlockBase<Self, R>; 16 mem::transmute((*base).invoke) 17 }; 18 let (a, b) = self; 19 invoke(block, a, b) 20 } 21}

不过这样太无脑了, 假如有 12 个参数就要写 12 遍, 写个宏先

1macro_rules! block_args_impl { 2 ($($a:ident : $t:ident), *) => ( 3 impl<$($t),*> BlockArguments for ($($t,)*) { 4 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R { 5 let invoke: unsafe extern "C" fn(*mut Block<Self, R> $(, $t)*) -> R = { 6 let base = block as *mut BlockBase<Self, R>; 7 mem::transmute((*base).invoke) 8 }; 9 let ($($a,)*) = self; 10 invoke(block $(, $a)*) 11 } 12 } 13 ); 14} 15 16block_args_impl!(); 17block_args_impl!(a: A); 18block_args_impl!(a: A, b: B); 19block_args_impl!(a: A, b: B, c: C); 20block_args_impl!(a: A, b: B, c: C, d: D); 21block_args_impl!(a: A, b: B, c: C, d: D, e: E); 22block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F); 23block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G); 24block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); 25block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); 26block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J); 27block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K); 28block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L);

现在来定义个 IntoConcreteBlocktrait, 主要是把 Rust 闭包转化成 ConcreteBlock, 因为有多个参数的情况, 所以又要一对一式地实现对应个数的, 顺便先把解引用, 克隆之类的 trait 实现一下, copy 函数让 RcBlock 持有 block

1pub trait IntoConcreteBlock<A>: Sized 2where 3 A: BlockArguments, 4{ 5 type ReturnType; 6 fn into_concrete_block(self) -> ConcreteBlock<A, Self::ReturnType, Self>; 7} 8 9impl<A, R, F> ConcreteBlock<A, R, F> 10where 11 A: BlockArguments, 12 F: IntoConcreteBlock<A, ReturnType = R>, 13{ 14 pub fn new(closure: F) -> Self { 15 closure.into_concrete_block() 16 } 17} 18 19impl<A, R, F> ConcreteBlock<A, R, F> { 20 unsafe fn with_invoke(invoke: unsafe extern "C" fn(*mut Self, ...) -> R, closure: F) -> Self { 21 ConcreteBlock { 22 base: BlockBase { 23 isa: &_NSConcreteStackBlock, 24 flags: 1 << 25, 25 _reserved: 0, 26 invoke: mem::transmute(invoke), 27 }, 28 descriptor: Box::new(BlockDescriptor::new()), 29 closure, 30 } 31 } 32} 33 34impl<A, R, F> ConcreteBlock<A, R, F> 35where 36 F: 'static, 37{ 38 pub fn copy(self) -> RcBlock<A, R> { 39 unsafe { 40 let mut block = self; 41 let copied = RcBlock::copy(&mut *block); 42 mem::forget(block); 43 copied 44 } 45 } 46} 47 48impl<A, R, F> Deref for ConcreteBlock<A, R, F> { 49 type Target = Block<A, R>; 50 fn deref(&self) -> &Self::Target { 51 unsafe { &*(&self.base as *const _ as *const Block<A, R>) } 52 } 53} 54 55impl<A, R, F> DerefMut for ConcreteBlock<A, R, F> { 56 fn deref_mut(&mut self) -> &mut Block<A, R> { 57 unsafe { &mut *(&mut self.base as *mut _ as *mut Block<A, R>) } 58 } 59} 60 61impl<A, R, F> Clone for ConcreteBlock<A, R, F> 62where 63 F: Clone, 64{ 65 fn clone(&self) -> Self { 66 unsafe { ConcreteBlock::with_invoke(mem::transmute(self.base.invoke), self.closure.clone()) } 67 } 68}

参数相关的, 先把一个的情况写出来

1impl<A, R, X> IntoConcreteBlock<(A,)> for X 2where 3 X: Fn(A) -> R, 4{ 5 type ReturnType = R; 6 fn into_concrete_block(self) -> ConcreteBlock<(A,), R, X> { 7 unsafe extern "C" fn concrete_block_invoke_args1<A, R, X>( 8 block_ptr: *mut ConcreteBlock<A, R, X>, 9 a: A, 10 ) -> R 11 where 12 X: Fn(A) -> R, 13 { 14 let block = &*block_ptr; 15 (block.closure)(a) 16 } 17 let f: unsafe extern "C" fn(*mut ConcreteBlock<A, R, X>, a: A) -> R = 18 concrete_block_invoke_args1; 19 unsafe { ConcreteBlock::with_invoke(mem::transmute(f), self) } 20 } 21}

继续用宏处理

1macro_rules! concrete_block_impl { 2 ($f:ident) => ( 3 concrete_block_impl!($f,); 4 ); 5 ($f:ident, $($a:ident : $t:ident),*) => ( 6 impl<$($t,)* R, X> IntoConcreteBlock<($($t,)*)> for X 7 where X: Fn($($t,)*) -> R { 8 type ReturnType = R; 9 fn into_concrete_block(self) -> ConcreteBlock<($($t,)*), R, X> { 10 unsafe extern fn $f<$($t,)* R, X>( 11 block_ptr: *mut ConcreteBlock<($($t,)*), R, X> 12 $(, $a: $t)*) -> R 13 where X: Fn($($t,)*) -> R { 14 let block = &*block_ptr; 15 (block.closure)($($a),*) 16 } 17 let f: unsafe extern fn(*mut ConcreteBlock<($($t,)*), R, X> $(, $a: $t)*) -> R = $f; 18 unsafe { 19 ConcreteBlock::with_invoke(mem::transmute(f), self) 20 } 21 } 22 } 23 ); 24} 25 26concrete_block_impl!(concrete_block_invoke_args0); 27concrete_block_impl!(concrete_block_invoke_args0, a: A); 28concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B); 29concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C); 30concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D); 31concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E); 32concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F); 33concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G); 34concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H); 35concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I); 36concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J); 37concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K); 38concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K, l: L);

基本上已经用 RustBlock 包装好了. 了解 objc Block 原理, 再配合上 Rust 的代码风格. 现在就是试试在 objc 端调用 RustBlock 试试效果.

在 objc 项目中试用

先在 lib.rs 写上以下内容

1#[no_mangle] 2unsafe extern "C" fn sum(block: &Block<(i32, i32), i32>) -> i32 { 3 block.call((1, 2)) + 1 4}

主要是调用 block 后加 1

然后 Cargo.toml 加上

1[lib] 2name = "block" 3crate-type = ["staticlib", "cdylib"]

后执行

1cargo build --release

就能生成静态库, 为了简单起见, 直接写个 main.m 然后用 clang 编译同时链接静态库, 当然别忘了加上头文件, 内容如下

1// LLBlock.h 2#ifndef LLBlock_h 3#define LLBlock_h 4 5#import <Foundation/Foundation.h> 6 7int32_t sum(int32_t (^block)(int32_t, int32_t)); 8 9#endif /* LLBlock_h */
1// main.m 2#import "LLBlock.h" 3 4int main(int argc, const char * argv[]) { 5 @autoreleasepool { 6 NSLog(@"%d", sum(^int32_t(int32_t a, int32_t b) { 7 return a + b; 8 })); 9 } 10 return 0; 11}

然后用这个命令编译链接生成一个可执行文件

1cc ./main.m -framework Foundation ./libblock.a -o main && ./main

只要是在 macOS 环境下, 应该能看到数字 4 的输出
至此, 我们的任务完成了.


可以到我的个人博客查看。

点赞
收藏

评论区

加载中...

相关推荐

Oracle 统计表空间和对象历史增长量

最近7天内每天(某个)表空间的增长量colTS_NAMEfora15SELECTa.snap_id,a.rtime,c.tablespace_namets_name,round(a.tablespace_sizec.block_size/1024/1024/1024,

Spark Transformations之mapPartitions

mapPartitions(func)Similartomap,butrunsseparatelyoneachpartition(block)oftheRDD,sofuncmustbeoftypeIterator<TIterator<UwhenrunningonanRDDoftypeT.

.clear 万能清除浮动

htmlbodydiv.clear,htmlbodyspan.clear{background:none;border:0;clear:both;display:block;float:none;

Ruby中的each collect map inject

说明:each——连续访问集合的所有元素collect—从集合中获得各个元素传递给block,block返回的结果生成新的集合。map——同collect。inject——遍历集合中的各个元素,将各个元素累积成返回一个值。arr\1,2,3\1)arr2arr.each{|elem

OC中block使用相关

代码块本质上是和其他变量类似。不同的是,代码块存储的数据是一个函数体。使用代码块是,你可以像调用其他标准函数一样,传入参数数,并得到返回值。脱字符(^)是块的语法标记。按照我们熟悉的参数语法规约所定义的返回值以及块的主体(也就是可以执行的代码)。下图是如何把块变量赋值给一个变量的语法讲解:!(http://static.oschina.net

ASSM和PCTUSED

相信很多人都知道,当使用了ASSM(autosegmentspacemanagement),PCTUSED将不在有效。但是,有多少人告诉你,为什么在ASSM中PCTUSED被忽略。ASSM之前,Oracle使用freelist,freegroup来管理空闲的block。哪如何决定一个block是否可以加到freelist上?假设,如果block