Block 简介
使用 objc 开发 App 时, 经常会使用到 Block, 这个语法糖是 Clang 给 C 语言实现的一个拓展. 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 world
的 Block, 现在来使用一个 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_0
的 number
.
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
的参数, 当然相应的调用的代码也要改变下, 至于其他的地方, 倒没啥变化.
现在来稍微总结一下, 等于讲 Clang 把 Block 转成 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 | 引用计数增加 |
既然 Block 是 objc 对象, 那意味着我们可以
#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
就行啦!
其他
其实还有一些 MRC 跟 ARC 相关的, 以及使用 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 环境, 后面一段适用于带 BlocksRuntime 的 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 跟 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
后就会生成 copy
跟 dispose
, 主要是为了管理 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);
现在来定义个 IntoConcreteBlock 的 trait
, 主要是把 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);
基本上已经用 Rust 把 Block 包装好了. 了解 objc Block 原理, 再配合上 Rust 的代码风格. 现在就是试试在 objc 端调用 Rust 的 Block 试试效果.
在 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 的输出
至此, 我们的任务完成了.
可以到我的个人博客查看。