Rust wrapping objc Block

Introduction to Blocks

When using objc to develop apps , Block is often used . This syntactic sugar is an extension of Clang to C language. Block can be compiled into C language code.
If you have an idea, you can directly read Clang 's official documentation on Block . -ABI-Apple

rewrite-objc generates cpp code

Let's use Clang to generate an ordinary objc file into Cpp code, and see what the C language code generated by Block looks like . First write a simple hello world program

#import <stdio.h>

int main(void) {
  @autoreleasepool {
    void (^test)(void) = ^{
      printf("hello, world!\n");
    };
    test();
  }
  return 0;
}
复制代码

Then use the clang program to generate the above code into cpp code

clang -rewrite-objc ./test.m
复制代码

Then a bunch of code will be generated, and we will find the key content inside

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;
}
复制代码

It is almost certain from the code

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

      printf("hello, world!");
    }
复制代码

means that

^{
  printf("hello, world!");
};
复制代码

Because __main_block_impl_0contains __block_implthis structure, so

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;
  }
};
复制代码

Then look at the mainfunction , and use the __main_block_impl_0constructor to point to it with a pointer

// 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 结果
_NSConcreteGlobalBlock data 无动作
_NSConcreteStackBlock stack stack -> heap
_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 = %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;
}
复制代码

这里主要是把 _NSConcreteStackBlock extern 出来, 至于 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);
复制代码

Now let 's define an IntoConcreteBlocktrait , mainly to convert Rust closures into ConcreteBlocks . Because there are multiple parameters, we need to implement the corresponding number one-to-one. By the way, first dereference, clone, etc. traitRealize , the copyfunction lets RcBlock holdblock

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()) }
  }
}
复制代码

Parameter related, first write one case

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) }
  }
}
复制代码

continue processing with macros

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);
复制代码

Basically , the Block has been wrapped in Rust . Understand the principle of objc Block , and then cooperate with the Rust code style. Now it is time to try calling Rust 's Block on the objc side to try the effect.

Try it out in objc project

First write the following in lib.rs

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

Mainly add 1 after calling block

Then Cargo.toml adds

[lib]
name = "block"
crate-type = ["staticlib", "cdylib"]
复制代码

post execution

cargo build --release
复制代码

A static library can be generated. For the sake of simplicity, write a main.m directly and then use clang to compile and link the static library. Of course, don't forget to add the header file, the content is as follows

// 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;
}
复制代码

Then use this command to compile and link to generate an executable file

cc ./main.m -framework Foundation ./libblock.a -o main && ./main
复制代码

As long as it is in the macOS environment, you should be able to see the output of the number 4


So far, our task is complete.

おすすめ

転載: juejin.im/post/7090204739429728292