Block 简介

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

rewrite-objc 生成linux虚拟机 cpp 代码

先来用 Clang 把一个普通的linux虚拟机 objc 文件变量类型有哪些生成到 Cpp 代码, 看看 Block 生成的 C 语言代码长啥样. 先macos10136怎么升级写个简单的 hello worldios应用商店 程序

#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;
}

从代码上基本可以macos系统下载肯定

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 构造appetite函数用指针指向它

// 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));

结构体appear的构appear造函数执行后把 fp 指针传给 FuncPtr, fp 指针就是 __ios下载main_block_fmacos是什么意思unc_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 变量有对应的实现.

autios是什么意思o 变量

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

#import <stdio.h>
int main(void) {
  @autoreleasepool {
    int number = 10;
    void (^test)(void) = ^{
      printf("hello, world!, number = %dn", 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 = %dn", number);
    }
// ...

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

static 变量

再来看看 statiiOSc 变量的情况

#import <stdio.h>
int main(void) {
  @autoreleasepool {
    int number = 10;
    static int b = 10;
    void (^test)(void) = ^{
      printf("hello, world!, number = %d, b = %dn", 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 = %dn", 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 的地址传application__main_block_impl_0 的构造函数, 同时 __maiios16n_block_impl_0 有一个 int *b 的成员, 同时在macosx __main_block变量英语_func_0 里进行解指针操作取值, 其实可以猜到一个行为, 如果在 block 调用之前修改 b, 最后取到的 b 是修改过的值, 因为它是通过 b 的指针进行取值.

全局变量

现在ios下载来看看全局变量的情况, 这种情况其实可以猜到, 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_ = %dn", 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_ = %dn", 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 = %dn", 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 = %dn", 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 参数改变了,变量的定义 增加了linux命令一个 int a 的参数, 当然相应的调用的代码也要改变下, 至于其他的地方, 倒没啥变化.


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

关于 _NSConcreteSappearancetackBlock

我们再来看最初的 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, 由此可以看出 BlAPPock 是一个 objc 的对象, 同时它的 isa 可能是 _NSConapprovecreteStackBlock.

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

类型 class 指定因素
__NSGlobalBlock__ _NSCoapprovencreteGlobalBlock 没有访问 auto 变量时
__NSStackBlock__ _NSConcreteStackBlock 访问了 auto 变量
__NSMallocBlock__ _NSConcreteMallocBlock __NSStackBlock__ 使用 copy

如果对 __NSGlobalBlock__ 使用 copy, 它还是macosx是什么文件夹 __NSGlobalBlock__ios15, 并不会改变.
Blockapprove 使用 copy 后的结果

capplicationlass 源区域 copy 结果
_NSConcreteGlobalBlock data 无动作
_NSConcreteStackBlock stack stack -> heappointmentap
_NSConcreteMallocBlock heap 引用计数增加

既然 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 = %dn", a);
    } class]); // __NSStackBlock__
    NSLog(@"%@", [[^{
      NSLog(@"hello world!, a = %d, b = %dn", a);
    } copy] class]); // __NSMallocBlock__
  }
  return 0;
}

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

其他

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

使用 Rust 包装

了解到上面关于 Block 的一些基本原理, 现在来尝试用 Rust 包装一下 Blmacos montereyock, 内容来源 rusios16t-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;
}

这里主要是把 _NSConcreteStackBlock externmacosmojave来, 至于 enum Class {} 是 Rust 的一个ios下载技巧, 这里linux是什么操作系统是为了linux常用命令让编译通过, 不想用它可以直接用 (). 至于

#[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 环境, 后macos monterey面一段适用于带 BlocksRuntimeLinux 环境.

linux重启命令后照着 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 跟macOS 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 对象appreciate打交道, rewrite-cpp 后就会生成 copydispose, 主要是为了管理 objc 对象的内存, 我们可以来验证一下macos10136怎么升级

#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 组成, 但其实并没有用到它, 所以直接用幽灵数据包裹一下linux常用命令, 接着写个 RcBlock 来包装一下 Block 结构体, 顺便把 _Block_copy _Block_release extern 出来, 在 RcBlock drop 时调用 _Block_release, 引用计数增加时调用 _Block_coios系统py

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)
  }
}

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

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 闭包转化成 Clinux重启命令oncreteBlock, 因为有多个参数的情况, 所以又要ios系统一对一式地实现对应个数的, 顺便先把解引用, 克隆之类的 trait 实现一macOS下, 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 包装好了. 了解 objc Block 原理, 再配合上 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

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

// 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 的输出


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