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 程序

#import <stdio.h>
int main(void) {
 @autoreleasepool {
 void (^test)(void) = ^{
 printf("hello, world!\n");
 };
 test();
 }
 return 0;
}

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

clang -rewrite-objc ./test.m

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

struct __block_impl {
 void *isa;
 int Flags;
 int Reserved;
 void *FuncPtr;
};
struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 printf("hello, world!\n");
 }
static struct __main_block_desc_0 {
 size_t reserved;
 size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(void) {
 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
 void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
 ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
 }
 return 0;
}

从代码上基本可以肯定

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

表示的是

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

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

struct __main_block_impl_0 {
 struct __block_impl impl;
 /*
 void *isa;
 int Flags;
 int Reserved;
 void *FuncPtr;
 */
 struct __main_block_desc_0* Desc;
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
};

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

// void (*test)(void) = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
void (*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 代码.

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

使用外部变量的 Block

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

局部变量

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

auto 变量

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

#import <stdio.h>
int main(void) {
 @autoreleasepool {
 int number = 10;
 void (^test)(void) = ^{
 printf("hello, world!, number = %d\n", number);
 };
 test();
 }
 return 0;
}
// ...
struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
 int number;
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int flags=0) : number(_number) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 int number = __cself->number; // bound by copy
 printf("hello, world!, number = %d\n", number);
 }
// ...

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

static 变量

再来看看 static 变量的情况

#import <stdio.h>
int main(void) {
 @autoreleasepool {
 int number = 10;
 static int b = 10;
 void (^test)(void) = ^{
 printf("hello, world!, number = %d, b = %d\n", number, b);
 };
 test();
 }
 return 0;
}
// ...
struct __main_block_impl_0 {
 // ...
 int *b;
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _number, int *_b, int flags=0) : number(_number), b(_b) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 int number = __cself->number; // bound by copy
 int *b = __cself->b; // bound by copy
 printf("hello, world!, number = %d, b = %d\n", number, (*b));
 }
int main(void) {
 /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
 int number = 10;
 static int b = 10;
 void (*test)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, number, &b));
 ((void (*)(__block_impl *))((__block_impl *)test)->FuncPtr)((__block_impl *)test);
 }
 return 0;
}

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

全局变量

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

#import <stdio.h>
int number_= 11;
static int b_ = 11;
int main(void) {
 @autoreleasepool {
 int number = 10;
 static int b = 10;
 void (^test)(void) = ^{
 printf("hello, world!, number = %d, b = %d, number_ = %d, b_ = %d\n", number, b, number_, b_);
 };
 test();
 }
 return 0;
}
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
 int number = __cself->number; // bound by copy
 int *b = __cself->b; // bound by copy
 printf("hello, world!, number = %d, b = %d, number_ = %d, b_ = %d\n", number, (*b), number_, b_);
 }

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

多参数 Block

继续尝试修改代码后再 rewrite

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

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


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

关于 _NSConcreteStackBlock

我们再来看最初的 hello world

#import <stdio.h>
int main(void) {
 @autoreleasepool {
 void (^test)(void) = ^{
 printf("hello, world!\n");
 };
 test();
 }
 return 0;
}
struct __block_impl {
 void *isa;
 int Flags;
 int Reserved;
 void *FuncPtr;
};
struct __main_block_impl_0 {
 struct __block_impl impl;
 struct __main_block_desc_0* Desc;
 __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
 impl.isa = &_NSConcreteStackBlock;
 impl.Flags = flags;
 impl.FuncPtr = fp;
 Desc = desc;
 }
};

可以看到有个 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 对象, 那意味着我们可以

#import <Foundation/Foundation.h>
int main(void) {
 @autoreleasepool {
 void (^test)(void) = ^{
 printf("hello, world!\n");
 };
 NSLog(@"%@", [test class]); // __NSGlobalBlock__
 int a = 10;
 NSLog(@"%@", [^{
 NSLog(@"hello world!, a = %d\n", a);
 } class]); // __NSStackBlock__
 NSLog(@"%@", [[^{
 NSLog(@"hello world!, a = %d, b = %d\n", a);
 } copy] class]); // __NSMallocBlock__
 }
 return 0;
}

然后对比 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 项目, 直接

cargo new block --lib

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

enum Class {}
#[cfg_attr(
 any(target_os = "macos", target_os = "ios"),
 link(name = "System", kind = "dylib")
)]
#[cfg_attr(
 not(any(target_os = "macos", target_os = "ios")),
 link(name = "BlocksRuntime", kind = "dylib")
)]
extern "C" {
 static _NSConcreteStackBlock: Class;
}

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

#[cfg_attr(
 any(target_os = "macos", target_os = "ios"),
 link(name = "System", kind = "dylib")
)]
#[cfg_attr(
 not(any(target_os = "macos", target_os = "ios")),
 link(name = "BlocksRuntime", kind = "dylib")
)]

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

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

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

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

#[repr(C)]
struct ConcreteBlock<A, R, F> {
 base: BlockBase<A, R>,
 descriptor: BlockDescriptor<ConcreteBlock<A, R, F>>,
}
#[repr(C)]
struct BlockDescriptor<B> {
 _reserved: c_ulong,
 block_size: c_ulong,
 copy_helper: unsafe extern "C" fn(&mut B, &B),
 dispose_helper: unsafe extern "C" fn(&mut B),
}

copy 跟 dispose

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

#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
@interface Person : NSObject {
@public
 int _number;
}
@end
NS_ASSUME_NONNULL_END
@implementation Person
@end
int main(void) {
 @autoreleasepool {
 Person *person = [[Person alloc] init];
 person->_number = 10;
 void (^test)(void) = ^{
 NSLog(@"%d", person->_number);
 };
 test();
 }
 return 0;
}

然后做一下 rewrite 操作

// ...
static 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*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __main_block_desc_0 {
 size_t reserved;
 size_t Block_size;
 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
 void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
// ...

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

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

现在来定义一下 Block

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

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

extern "C" {
 // ...
 fn _Block_copy(block: *const c_void) -> *mut c_void;
 fn _Block_release(block: *const c_void);
}
pub struct RcBlock<A, R> {
 ptr: *mut Block<A, R>,
}
impl<A, R> RcBlock<A, R> {
 pub unsafe fn new(ptr: *mut Block<A, R>) -> Self {
 RcBlock { ptr }
 }
 pub unsafe fn copy(ptr: *mut Block<A, R>) -> Self {
 let ptr = _Block_copy(ptr as *const c_void) as *mut Block<A, R>;
 RcBlock { ptr }
 }
}
impl<A, R> Clone for RcBlock<A, R> {
 fn clone(&self) -> Self {
 unsafe { RcBlock::copy(self.ptr) }
 }
}
impl<A, R> Deref for RcBlock<A, R> {
 type Target = Block<A, R>;
 fn deref(&self) -> &Self::Target {
 unsafe { &*self.ptr }
 }
}
impl<A, R> Drop for RcBlock<A, R> {
 fn drop(&mut self) {
 unsafe {
 _Block_release(self.ptr as *const c_void);
 }
 }
}

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

pub trait BlockArguments: Sized {
 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R;
}
impl<A> BlockArguments for A {
 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R {
 let invoke: unsafe extern "C" fn(*mut Block<Self, R>, A) -> R = {
 let base = block as *mut BlockBase<Self, R>;
 mem::transmute((*base).invoke)
 };
 let a = self;
 invoke(block, a)
 }
}

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

impl<A> BlockArguments for (A,) {
 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R {
 let invoke: unsafe extern "C" fn(*mut Block<Self, R>, A) -> R = {
 let base = block as *mut BlockBase<Self, R>;
 mem::transmute((*base).invoke)
 };
 let (a,) = self;
 invoke(block, a)
 }
}
impl<A, B> BlockArguments for (A, B) {
 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R {
 let invoke: unsafe extern "C" fn(*mut Block<Self, R>, A, B) -> R = {
 let base = block as *mut BlockBase<Self, R>;
 mem::transmute((*base).invoke)
 };
 let (a, b) = self;
 invoke(block, a, b)
 }
}

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

macro_rules! block_args_impl {
 ($($a:ident : $t:ident), *) => (
 impl<$($t),*> BlockArguments for ($($t,)*) {
 unsafe fn call_block<R>(self, block: *mut Block<Self, R>) -> R {
 let invoke: unsafe extern "C" fn(*mut Block<Self, R> $(, $t)*) -> R = {
 let base = block as *mut BlockBase<Self, R>;
 mem::transmute((*base).invoke)
 };
 let ($($a,)*) = self;
 invoke(block $(, $a)*)
 }
 }
 );
}
block_args_impl!();
block_args_impl!(a: A);
block_args_impl!(a: A, b: B);
block_args_impl!(a: A, b: B, c: C);
block_args_impl!(a: A, b: B, c: C, d: D);
block_args_impl!(a: A, b: B, c: C, d: D, e: E);
block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F);
block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G);
block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J);
block_args_impl!(a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I, j: J, k: K);
block_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

pub trait IntoConcreteBlock<A>: Sized
where
 A: BlockArguments,
{
 type ReturnType;
 fn into_concrete_block(self) -> ConcreteBlock<A, Self::ReturnType, Self>;
}
impl<A, R, F> ConcreteBlock<A, R, F>
where
 A: BlockArguments,
 F: IntoConcreteBlock<A, ReturnType = R>,
{
 pub fn new(closure: F) -> Self {
 closure.into_concrete_block()
 }
}
impl<A, R, F> ConcreteBlock<A, R, F> {
 unsafe fn with_invoke(invoke: unsafe extern "C" fn(*mut Self, ...) -> R, closure: F) -> Self {
 ConcreteBlock {
 base: BlockBase {
 isa: &_NSConcreteStackBlock,
 flags: 1 << 25,
 _reserved: 0,
 invoke: mem::transmute(invoke),
 },
 descriptor: Box::new(BlockDescriptor::new()),
 closure,
 }
 }
}
impl<A, R, F> ConcreteBlock<A, R, F>
where
 F: 'static,
{
 pub fn copy(self) -> RcBlock<A, R> {
 unsafe {
 let mut block = self;
 let copied = RcBlock::copy(&mut *block);
 mem::forget(block);
 copied
 }
 }
}
impl<A, R, F> Deref for ConcreteBlock<A, R, F> {
 type Target = Block<A, R>;
 fn deref(&self) -> &Self::Target {
 unsafe { &*(&self.base as *const _ as *const Block<A, R>) }
 }
}
impl<A, R, F> DerefMut for ConcreteBlock<A, R, F> {
 fn deref_mut(&mut self) -> &mut Block<A, R> {
 unsafe { &mut *(&mut self.base as *mut _ as *mut Block<A, R>) }
 }
}
impl<A, R, F> Clone for ConcreteBlock<A, R, F>
where
 F: Clone,
{
 fn clone(&self) -> Self {
 unsafe { ConcreteBlock::with_invoke(mem::transmute(self.base.invoke), self.closure.clone()) }
 }
}

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

impl<A, R, X> IntoConcreteBlock<(A,)> for X
where
 X: Fn(A) -> R,
{
 type ReturnType = R;
 fn into_concrete_block(self) -> ConcreteBlock<(A,), R, X> {
 unsafe extern "C" fn concrete_block_invoke_args1<A, R, X>(
 block_ptr: *mut ConcreteBlock<A, R, X>,
 a: A,
 ) -> R
 where
 X: Fn(A) -> R,
 {
 let block = &*block_ptr;
 (block.closure)(a)
 }
 let f: unsafe extern "C" fn(*mut ConcreteBlock<A, R, X>, a: A) -> R =
 concrete_block_invoke_args1;
 unsafe { ConcreteBlock::with_invoke(mem::transmute(f), self) }
 }
}

继续用宏处理

macro_rules! concrete_block_impl {
 ($f:ident) => (
 concrete_block_impl!($f,);
 );
 ($f:ident, $($a:ident : $t:ident),*) => (
 impl<$($t,)* R, X> IntoConcreteBlock<($($t,)*)> for X
 where X: Fn($($t,)*) -> R {
 type ReturnType = R;
 fn into_concrete_block(self) -> ConcreteBlock<($($t,)*), R, X> {
 unsafe extern fn $f<$($t,)* R, X>(
 block_ptr: *mut ConcreteBlock<($($t,)*), R, X>
 $(, $a: $t)*) -> R
 where X: Fn($($t,)*) -> R {
 let block = &*block_ptr;
 (block.closure)($($a),*)
 }
 let f: unsafe extern fn(*mut ConcreteBlock<($($t,)*), R, X> $(, $a: $t)*) -> R = $f;
 unsafe {
 ConcreteBlock::with_invoke(mem::transmute(f), self)
 }
 }
 }
 );
}
concrete_block_impl!(concrete_block_invoke_args0);
concrete_block_impl!(concrete_block_invoke_args0, a: A);
concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B);
concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C);
concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D);
concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E);
concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F);
concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G);
concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H);
concrete_block_impl!(concrete_block_invoke_args0, a: A, b: B, c: C, d: D, e: E, f: F, g: G, h: H, i: I);
concrete_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);
concrete_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);
concrete_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 包装好了. 了解 objcBlock 原理, 再配合上 Rust 的代码风格. 现在就是试试在 objc 端调用 RustBlock 试试效果.

在 objc 项目中试用

先在 lib.rs 写上以下内容

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

主要是调用 block 后加 1

然后 Cargo.toml 加上

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

后执行

cargo build --release

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

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

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

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

只要是在 macOS 环境下, 应该能看到数字 4 的输出


至此, 我们的任务完成了.

作者:飲馬江南

%s 个评论

要回复文章请先登录注册