15 个不稳定版本 (4 个破坏性版本)

0.5.1 2024 年 5 月 21 日
0.4.0 2023 年 12 月 3 日
0.3.0 2023 年 7 月 31 日
0.2.0-alpha.82023 年 2 月 7 日
0.2.0-alpha.12021 年 11 月 22 日

#4macOS 和 iOS API

Download history 78051/week @ 2024-05-03 84478/week @ 2024-05-10 92750/week @ 2024-05-17 102050/week @ 2024-05-24 113500/week @ 2024-05-31 116016/week @ 2024-06-07 121951/week @ 2024-06-14 130567/week @ 2024-06-21 126850/week @ 2024-06-28 114449/week @ 2024-07-05 124871/week @ 2024-07-12 116063/week @ 2024-07-19 118609/week @ 2024-07-26 106715/week @ 2024-08-02 113201/week @ 2024-08-09 89773/week @ 2024-08-16

449,616 每月下载量
用于 675 个 crate (69 直接使用)

MIT 许可证

780KB
13K SLoC

block2

Latest version License Documentation CI

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 的块可以使用 RcBlockStackBlock 来创建,这取决于你是否希望将块移动到堆上,还是让调用者决定是否需要这样做。

要调用这样的函数/方法,我们可以使用 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-rtlibBlocksRuntime

  • 功能标志:compiler-rt

这是苹果旧版本(大约是macOS 10.6)运行时的副本,现在也用于Swift的libdispatch和Swift的Foundation

可以使用libblocksruntime-dev软件包在许多Linux系统上安装运行时和相关头文件。

使用此运行时可能无法与objc2 crate一起工作。

GNUStep的libobjc2

  • 功能标志:gnustep-1-7gnustep-1-8gnustep-1-9gnustep-2-0gnustep-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标志

依赖项