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系统安装orld
的 Block, 现在来使用一个 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_0
的 number
.
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
的参数, 当然相应的调用的代码也要改变下, 至于其他的地方, 倒没啥变化.
现在来稍微总结一下, 等于讲 Clang 把 Bloc变量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 | 引用计数增加 |
既然 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 = %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
就行啦!
其他
其实还有一些 MRC 跟 ARC 相关的, 以及使用 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
extern
出macosmojave来, 至于 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面一段适用于带 BlocksRuntime 的 Linux 环境.
然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
后就会生成 copy
跟 dispose
, 主要是为了管理 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);
现在来定义个 IntoConcreteBlock 的 trait
, 主要是把 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);
基本上已经用 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
就能生成静态库, 为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此, 我们的任务完成了.
评论(0)