15 个不稳定版本 (4 个破坏性版本)
0.5.1 | 2024 年 5 月 21 日 |
---|---|
0.4.0 | 2023 年 12 月 3 日 |
0.3.0 | 2023 年 7 月 31 日 |
0.2.0-alpha.8 | 2023 年 2 月 7 日 |
0.2.0-alpha.1 | 2021 年 11 月 22 日 |
#4 在 macOS 和 iOS API 中
449,616 每月下载量
用于 675 个 crate (69 直接使用)
780KB
13K SLoC
block2
Rust 中 Apple C 语言 blocks 扩展。
这个 crate 提供了与 C blocks 交互的功能,这是 Rust 闭包的 C 等价物。
虽然技术上它们不仅限于在 Objective-C 中使用,但实际上你很可能会在唯一的地方遇到它们。
请参阅 文档 获取更全面的概述。
这个 crate 是 objc2
项目 的一部分,请参阅该项目获取相关 crate。
lib.rs
:
Apple C 语言 blocks 扩展
C Blocks 是捕获其环境的函数,即 Rust 的 [Fn
] 闭包的 C 等价物。由于它们最初是由 Apple 开发的,因此通常用于 Objective-C 代码中。这个 crate 提供了以易于使用、以 Rust 为中心的方式创建、管理和调用这些 blocks 的功能。
在较高层次上,这个 crate 包含四种类型,每种类型代表不同种类的 blocks 和不同种类的所有权。
block2 类型 |
等效 Rust 类型 |
---|---|
&Block<dynFn() + 'a> |
&dynFn() + 'a |
RcBlock<dynFn() + 'a> |
Arc<dynFn() + 'a> |
StackBlock<'a, (), (), implFn() + 'a> |
impl Fn() + 'a |
GlobalBlock<dynFn()> |
fn 项目 |
有关 block 实现的详细信息,请参阅 C 语言规范 和 ABI 规范。
使用 blocks 的外部函数
要声明接收块的外部函数或方法,可以使用以下语法:&Block<dyn Fn(Params) -> R>
或 Option<&Block<dyn Fn(Args) -> R>>
,其中 Params
是参数类型,而 R
是返回类型。
在接下来的几个示例中,我们将使用一个名为 check_addition
的函数,该函数接收一个执行两个整数相加的块,并检查加法是否正确。
这样的函数可以像下面这样用 C 语言编写。
#include <cassert>
#include <stdint.h>
#include <Block.h>
void check_addition(int32_t (^block)(int32_t, int32_t)) {
assert(block(5, 8) == 13);
}
该函数的外部声明可能如下所示:extern "C" { ... }
use block2::Block;
extern "C" {
fn check_addition(block: &Block<dyn Fn(i32, i32) -> i32>);
}
这也可以在用 objc2::extern_methods!
声明的外部方法内部完成。
use block2::Block;
use objc2::extern_methods;
#
#
extern_methods!(
unsafe impl MyClass {
#[method(checkAddition:)]
pub fn checkAddition(&self, block: &Block<dyn Fn(i32, i32) -> i32>);
}
);
如果函数/方法允许传递 NULL
块,则类型将是 Option<&Block<dyn Fn(i32, i32) -> i32>>
。
调用块
我们也可以在 Rust 中定义外部函数并将其暴露给 Objective-C。为此,我们可以在函数内部使用 Block::call
来调用块。
use block2::Block;
#[no_mangle]
extern "C" fn check_addition(block: &Block<dyn Fn(i32, i32) -> i32>) {
assert_eq!(block.call((5, 8)), 13);
}
注意 call
方法中的额外括号,因为必须将参数作为元组传递。
创建块
要传递给 Objective-C 的块可以使用 RcBlock
或 StackBlock
来创建,这取决于你是否希望将块移动到堆上,还是让调用者决定是否需要这样做。
要调用这样的函数/方法,我们可以使用 RcBlock::new
从闭包创建一个新的块。
use block2::RcBlock;
#
let block = RcBlock::new(|a, b| a + b);
check_addition(&block);
这将在堆上创建块。如果调用外部函数时它不会复制块,那么直接使用 StackBlock
创建块(使用 StackBlock::new
)可能会更高效。
请注意,这要求闭包是 Clone
,因为外部代码将来可能允许将块复制到堆上。
use block2::StackBlock;
#
let block = StackBlock::new(|a, b| a + b);
check_addition(&block);
作为优化,如果您的闭包没有捕获任何变量(如上述示例所示),您可以使用 global_block!
宏创建一个静态块。
use block2::global_block;
#
global_block! {
static BLOCK = |a: i32, b: i32| -> i32 {
a + b
};
}
check_addition(&BLOCK);
生命周期
处理块时,需要注意的生命周期可能有很多。
其中最重要的是块数据的生命周期,即块中闭包包含的数据的生命周期。这个生命周期可以在 &Block<dyn Fn() + 'f>
中指定为 'f
。
请注意,没有指定生命周期的 &Block<dyn Fn()>
可能会有些令人困惑,因为它的默认值取决于类型的位置。在函数/方法签名中,它默认为 'static
,但作为例如 let
绑定的类型,生命周期可能被推断为更小,有关详细信息,请参阅 参考。如果有疑问,可以添加 + 'static
或 + '_
以强制创建逃逸或非逃逸块。
另一个生命周期是当前持有的指针的生命周期,即 'b
在 &'b Block<dyn Fn()>
中。这个生命周期可以使用 Block::copy
安全地扩展,因此应该不会造成太多麻烦(当然,生命周期仍然不能超过捕获数据的生命周期)。
最后,块的参数和返回类型也可以包含生命周期,如 'a
和 'r
在 &Block<dyn Fn(&'a i32) -> &'r u32>
中。不幸的是,由于 Rust 特征对高秩特征边界的限制,这些生命周期目前存在问题且不受支持。如果您在处理接受或返回引用的块时遇到问题,请考虑使用与ABI兼容的 NonNull<T>
,或将它转换为一个 'static
生命周期。
线程安全
在 block2
中尚不能表示线程安全的块,因此任何需要线程安全块的函数必须标记为 unsafe
。
可变性
块通常被认为是可共享的,因此很少会使其可变。特别是,没有很好的方法来防止重入。
您可能需要使用内部可变性。
指定运行时
存在不同的运行时实现,它们的行为略有不同(并且有多个不同的辅助函数),最重要的方面是库的名称不同,因此在链接时我们必须考虑到这一点。
您可以通过使用相关的Cargo功能标志来选择所需的运行时,请参阅以下部分(您可能需要首先禁用默认的"apple"
功能)。
苹果的libclosure
- 功能标志:
apple
。
这是最常见且最复杂的运行时,并且它比规范要求的功能要多得多。
所需的最小操作系统版本如下(尽管在实际中,Rust 本身需要比这更高的版本)
- macOS:
10.6
- iOS/iPadOS:
3.2
- tvOS:
1.0
- watchOS:
1.0
这是默认使用的,因此如果您在这些平台之一上使用此crate,则无需指定运行时。
LLVM compiler-rt
的libBlocksRuntime
- 功能标志:
compiler-rt
。
这是苹果旧版本(大约是macOS 10.6)运行时的副本,现在也用于Swift的libdispatch
和Swift的Foundation。
可以使用libblocksruntime-dev
软件包在许多Linux系统上安装运行时和相关头文件。
使用此运行时可能无法与objc2
crate一起工作。
GNUStep的libobjc2
- 功能标志:
gnustep-1-7
、gnustep-1-8
、gnustep-1-9
、gnustep-2-0
和gnustep-2-1
,具体取决于您使用的版本。
GNUStep有点奇怪,因为它将blocks支持捆绑到其Objective-C运行时中。这意味着我们必须链接到libobjc
,这是通过依赖于objc2
crate来完成的。虽然有点不寻常,但它确实可行。
源代码
微软的WinObjC
- 功能标志:
unstable-winobjc
。
不稳定:尚未在Windows上测试!
基于GNUStep的libobjc2
版本1.8的分支。
ObjFW
- 功能标志:
unstable-objfw
。
不稳定:目前无法使用!
C编译器配置
据我们所知,只有Clang支持blocks。要使用这些功能编译C或Objective-C源代码,您应该设置-fblocks
标志。