Rust 包装 objc Block

limit
• 阅读 584

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 结果
_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);

现在来定义个 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 包装好了. 了解 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

就能生成静态库, 为了简单起见, 直接写个 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 的输出
至此, 我们的任务完成了.


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

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
Oracle 统计表空间和对象历史增长量
最近7天内每天(某个)表空间的增长量colTS_NAMEfora15SELECTa.snap_id,a.rtime,c.tablespace_namets_name,round(a.tablespace_sizec.block_size/1024/1024/1024,
Stella981 Stella981
3年前
Spark Transformations之mapPartitions
mapPartitions(func)Similartomap,butrunsseparatelyoneachpartition(block)oftheRDD,sofuncmustbeoftypeIterator<TIterator<UwhenrunningonanRDDoftypeT.
Wesley13 Wesley13
3年前
.clear 万能清除浮动
htmlbodydiv.clear,htmlbodyspan.clear{background:none;border:0;clear:both;display:block;float:none;
Stella981 Stella981
3年前
Ruby中的each collect map inject
说明:each——连续访问集合的所有元素collect—从集合中获得各个元素传递给block,block返回的结果生成新的集合。map——同collect。inject——遍历集合中的各个元素,将各个元素累积成返回一个值。arr\1,2,3\1)arr2arr.each{|elem
Stella981 Stella981
3年前
OC中block使用相关
代码块本质上是和其他变量类似。不同的是,代码块存储的数据是一个函数体。使用代码块是,你可以像调用其他标准函数一样,传入参数数,并得到返回值。脱字符(^)是块的语法标记。按照我们熟悉的参数语法规约所定义的返回值以及块的主体(也就是可以执行的代码)。下图是如何把块变量赋值给一个变量的语法讲解:!(http://static.oschina.net
Wesley13 Wesley13
3年前
ASSM和PCTUSED
相信很多人都知道,当使用了ASSM(autosegmentspacemanagement),PCTUSED将不在有效。但是,有多少人告诉你,为什么在ASSM中PCTUSED被忽略。ASSM之前,Oracle使用freelist,freegroup来管理空闲的block。哪如何决定一个block是否可以加到freelist上?假设,如果block
Stella981 Stella981
3年前
Block的循环引用
在ios常见的循环引用中曾经提到过block:!(http://static.oschina.net/uploads/space/2016/0830/112327_c1yY_1463495.png)看看上面最基本的block循环应用,self包含block,block包含了self中的变量val,所以形成了循环应用,编译器给出了循环引用的警告,当
Wesley13 Wesley13
3年前
MySQL Disk
/sys/block/sda/queue/nr\_requests磁盘队列长度。默认只有128个队列,可以提高到512个.会更加占用内存,但能更加多的合并读写操作,速度变慢,但能读写更加多的量/sys/block/sda/queue/iosched/antic\_expire等待时间。读取附近产生的新请时等待多长时间/sys/bl
Wesley13 Wesley13
3年前
mysql之mysql数据在磁盘的储存方式
mysql的数据在磁盘上的存储:   数据块:       由多个磁盘block组成的块,存储引擎负责管理数据块。       磁盘是block块设备,数据在磁盘上的存放也是按照块存放的。        mysql读取表到内存的时候,也必许按照一块一块
block yandex bot
是Yandex搜索引擎的爬虫。在这篇文章中,我总结了所有的Useragent,通过那篇文章,我们可以看出有些Yandexbot遵守,有些不遵守robots.txt协议。我们需要屏蔽yandexbot(blockyandexbot)可以通过robots.txt和IP的方式屏蔽,下面分别来讲。通过robots.txt
limit
limit
Lv1
临渊羡鱼,不如天天摸鱼。
文章
3
粉丝
1
获赞
3