13 个版本 (7 个稳定版)
1.0.6 | 2023年8月6日 |
---|---|
1.0.5 | 2023年4月10日 |
1.0.3 | 2023年3月18日 |
1.0.0 | 2023年2月28日 |
0.1.0 | 2021年10月20日 |
#88 in Rust 模式
16,731 每月下载量
用于 56 个 crate (2 个直接使用)
49KB
667 行
swift-rs
轻松从 Rust 调用 Swift 函数!
设置
将 swift-rs
添加到项目的 dependencies
和 build-dependencies
[dependencies]
swift-rs = "1.0.5"
[build-dependencies]
swift-rs = { version = "1.0.5", features = ["build"] }
接下来,需要进行一些设置工作
- 确保您的 Swift 代码已组织成 Swift 包。这可以通过在 XCode 中选择 File -> New -> Project -> Multiplatform -> Swift Package 并导入现有代码来完成。
- 将
SwiftRs
添加为 Swift 包的依赖项,并将构建类型设置为.static
。
let package = Package(
dependencies: [
.package(url: "https://github.com/Brendonovich/swift-rs", from: "1.0.5")
],
products: [
.library(
type: .static,
),
],
targets: [
.target(
// Must specify swift-rs as a dependency of your target
dependencies: [
.product(
name: "SwiftRs",
package: "swift-rs"
)
],
)
]
)
- 如果项目根目录中没有,则在项目根目录下创建一个
build.rs
文件。 - 在
build.rs
文件中使用SwiftLinker
来链接 Swift 运行时和您的 Swift 包。包名应与您的Package.swift
文件中指定的名称相同,路径应指向相对于 crate 根目录的 Swift 项目的根目录。
use swift_rs::SwiftLinker;
fn build() {
// swift-rs has a minimum of macOS 10.13
// Ensure the same minimum supported macOS version is specified as in your `Package.swift` file.
SwiftLinker::new("10.13")
// Only if you are also targetting iOS
// Ensure the same minimum supported iOS version is specified as in your `Package.swift` file
.with_ios("11")
.with_package(PACKAGE_NAME, PACKAGE_PATH)
.link();
// Other build steps
}
完成这些步骤后,您应该可以开始从 Rust 使用 Swift 代码了!
如果您在使用 Tauri 与 swift-rs
时遇到错误 dyld[16008]: Library not loaded: @rpath/libswiftCore.dylib
,请确保您已在您的 tauri.config.json
中将 Tauri 最低系统版本 设置为 10.15
或更高。
调用基本函数
要允许从 Rust 调用 Swift 函数,它必须遵循一些规则
- 它必须是全局的
- 它必须使用
@_cdecl
进行标记,以便可以从C调用 - 它只能使用在Objective-C中可以表示的类型,因此只有继承自
NSObject
的类,以及Int和Bool这样的标量。这排除了字符串、数组、泛型(尽管可以通过变通方法发送这些)和结构体(结构体是严格禁止的)。
在这个例子中,我们将使用一个简单的平方数字的函数
public func squareNumber(number: Int) -> Int {
return number * number
}
到目前为止,这个函数满足了第1和第3项要求:它是全局的和公共的,并且只使用了Int类型,这是Objective-C兼容的。但是,它没有使用@_cdecl
进行标记。为了修复这个问题,我们必须在函数声明之前调用@_cdecl
,并指定函数暴露给Rust的名称作为其唯一的参数。为了遵循Rust的命名约定,我们将以snake case的形式导出这个函数,名称为square_number
。
@_cdecl("square_number")
public func squareNumber(number: Int) -> Int {
return number * number
}
现在squareNumber
已经正确地暴露给Rust,我们可以开始与它交互。这可以通过使用swift!
宏来实现,其中Int
类型有助于提供类似的函数签名
use swift_rs::swift;
swift!(fn square_number(number: Int) -> Int);
最后,您可以从常规的Rust函数中调用此函数。请注意,所有对Swift函数的调用都是不安全的,并且需要在unsafe {}
块或unsafe fn
中包装。
fn main() {
let input: Int = 4;
let output = unsafe { square_number(input) };
println!("Input: {}, Squared: {}", input, output);
// Prints "Input: 4, Squared: 16"
}
请查看文档以获取所有可用的辅助类型。
从Swift返回对象
假设我们想让我们的squareNumber
函数不仅返回结果,还返回原始输入。在Swift中,这是一种标准的做法,使用结构体
struct SquareNumberResult {
var input: Int
var output: Int
}
然而,我们不能这样做,因为结构体不能在Objective-C中表示。相反,我们必须使用一个扩展了NSObject
的类
class SquareNumberResult: NSObject {
var input: Int
var output: Int
init(_ input: Int, _ output: Int) {
self.input = input;
self.output = output
}
}
是的,这个类可以包含平方逻辑,但这对本例来说并不重要
然后可以从squareNumber
返回这个类的实例
@_cdecl("square_number")
public func squareNumber(input: Int) -> SquareNumberResult {
let output = input * input
return SquareNumberResult(input, output)
}
如你所见,从Swift返回NSObject
并不困难。然而,Rust实现则不然。squareNumber
实际上并没有返回包含input
和output
的结构体,而是返回存储在内存中某个地方的SquareNumberResult
的指针。此外,这个值包含的数据不仅仅是input
和output
:由于它是一个NSObject
,它还包含额外的数据,这些数据在使用Rust时必须予以考虑。
这听起来可能令人畏惧,但实际上并不是一个问题,多亏了SRObject<T>
。这个类型内部管理指针,并接受一个可以访问数据的结构体的泛型参数。让我们看看如何在Rust中实现SquareNumberResult
use swift_rs::{swift, Int, SRObject};
// Any struct that is used in a C function must be annotated
// with this, and since our Swift function is exposed as a
// C function with @_cdecl, this is necessary here
#[repr(C)]
// Struct matches the class declaration in Swift
struct SquareNumberResult {
input: Int,
output: Int
}
// SRObject abstracts away the underlying pointer and will automatically deref to
// &SquareNumberResult through the Deref trait
swift!(fn square_number(input: Int) -> SRObject<SquareNumberResult>);
然后,使用新的返回值就像直接使用SquareNumberResult
一样简单
fn main() {
let input = 4;
let result = unsafe { square_number(input) };
let result_input = result.input; // 4
let result_output = result.output; // 16
}
在Rust中创建对象然后将其传递给Swift是不支持的。
可选类型
swift-rs
也支持Swift的nil
类型,但仅限于返回可选NSObject
的函数。返回可选原语类型的函数无法在Objective C中表示,因此不受支持。
假设我们有一个返回可选SRString
的函数
@_cdecl("optional_string")
func optionalString(returnNil: Bool) -> SRString? {
if (returnNil) return nil
else return SRString("lorem ipsum")
}
得益于 Rust 的空指针优化,SRString?
的可选性质可以通过将 SRString
包装在 Rust 的 Option<T>
类型中来表示!
use swift_rs::{swift, Bool, SRString};
swift!(optional_string(return_nil: Bool) -> Option<SRString>)
空指针实际上是无法在 C 中表示返回可选原语函数的原因。如果支持这一点,如何区分 nil
和数字?这是不可能的!
复杂数据类型
到目前为止,我们只看了使用原语类型和结构体/类,但这忽略了某些最重要的数据结构:数组(SRArray<T>
)和字符串(SRString
)。然而,这些类型必须谨慎处理,并且不如 Swift & Rust 的本地对应类型灵活。
字符串
字符串可以通过 SRString
在 Rust 和 Swift 之间传递,它可以从两种语言中的本地字符串创建。
作为参数
import SwiftRs
@_cdecl("swift_print")
public func swiftPrint(value: SRString) {
// .to_string() converts the SRString to a Swift String
print(value.to_string())
}
use swift_rs::{swift, SRString, SwiftRef};
swift!(fn swift_print(value: &SRString));
fn main() {
// SRString can be created by simply calling .into() on any string reference.
// This will allocate memory in Swift and copy the string
let value: SRString = "lorem ipsum".into();
unsafe { swift_print(&value) }; // Will print "lorem ipsum" to the console
}
作为返回值
import SwiftRs
@_cdecl("get_string")
public func getString() -> SRString {
let value = "lorem ipsum"
// SRString can be created from a regular String
return SRString(value)
}
use swift_rs::{swift, SRString};
swift!(fn get_string() -> SRString);
fn main() {
let value_srstring = unsafe { get_string() };
// SRString can be converted to an &str using as_str()...
let value_str: &str = value_srstring.as_str();
// or though the Deref trait
let value_str: &str = &*value_srstring;
// SRString also implements Display
println!("{}", value_srstring); // Will print "lorem ipsum" to the console
}
数组
原语数组
正确表示数组很棘手,因为我们不能像 Swift 遵循规则 3 使用泛型作为参数或返回值。相反,swift-rs
提供了一个泛型 SRArray<T>
,它可以嵌入到扩展了非泛型 NSObject
的另一个类中,但这仅限于单个元素类型。
import SwiftRs
// Argument/Return values can contain generic types, but cannot be generic themselves.
// This includes extending generic types.
class IntArray: NSObject {
var data: SRArray<Int>
init(_ data: [Int]) {
self.data = SRArray(data)
}
}
@_cdecl("get_numbers")
public func getNumbers() -> IntArray {
let numbers = [1, 2, 3, 4]
return IntArray(numbers)
}
use swift_rs::{Int, SRArray, SRObject};
#[repr(C)]
struct IntArray {
data: SRArray<Int>
}
// Since IntArray extends NSObject in its Swift implementation,
// it must be wrapped in SRObject on the Rust side
swift!(fn get_numbers() -> SRObject<IntArray>);
fn main() {
let numbers = unsafe { get_numbers() };
// SRArray can be accessed as a slice via as_slice
let numbers_slice: &[Int] = numbers.data.as_slice();
assert_eq!(numbers_slice, &[1, 2, 3, 4]);
}
为了简化 Rust 侧的事情,我们实际上可以去掉 IntArray
结构体。因为 IntArray
只有一个字段,其内存布局与 SRArray<usize>
相同,因此我们的 Rust 实现可以在牺牲与 Swift 代码等价性的情况下简化。
// We still need to wrap the array in SRObject since
// the wrapper class in Swift is an NSObject
swift!(fn get_numbers() -> SRObject<SRArray<Int>>);
NSObject 数组
如果我们想返回一个 NSObject
数组,Swift 侧有两种选择
- 继续使用
SRArray
和自定义包装类型,或者 - 使用
SRObjectArray
,这是swift-rs
提供的包装类型,可以接受任何NSObject
作为其元素。这可能比继续创建包装类型更容易,但牺牲了一些类型安全性。
还有适用于 Rust 的 SRObjectArray<T>
,它与任何单个元素 Swift 包装类型(以及当然 Swift 中的 SRObjectArray
)兼容,并自动将元素包装在 SRObject<T>
中,因此几乎没有不使用它的理由,除非你真的很喜欢自定义包装类型。
在 Swift 和 Rust 中使用 SRObjectArray 并与一个基本的自定义类/结构体一起使用,可以这样做
import SwiftRs
class IntTuple: NSObject {
var item1: Int
var item2: Int
init(_ item1: Int, _ item2: Int) {
self.item1 = item1
self.item2 = item2
}
}
@_cdecl("get_tuples")
public func getTuples() -> SRObjectArray {
let tuple1 = IntTuple(0,1),
tuple2 = IntTuple(2,3),
tuple3 = IntTuple(4,5)
let tupleArray: [IntTuple] = [
tuple1,
tuple2,
tuple3
]
// Type safety is only lost when the Swift array is converted to an SRObjectArray
return SRObjectArray(tupleArray)
}
use swift_rs::{swift, Int, SRObjectArray};
#[repr(C)]
struct IntTuple {
item1: Int,
item2: Int
}
// No need to wrap IntTuple in SRObject<T> since
// SRObjectArray<T> does it automatically
swift!(fn get_tuples() -> SRObjectArray<IntTuple>);
fn main() {
let tuples = unsafe { get_tuples() };
for tuple in tuples.as_slice() {
// Will print each tuple's contents to the console
println!("Item 1: {}, Item 2: {}", tuple.item1, tuple.item2);
}
}
复杂数据类型可以包含您喜欢的任何原语和 SRObject<T>
的组合,但请记住遵循 3 个规则!
额外奖励
SRData
SRArray<T>
的包装类型,用于存储 u8
- 实质上就是一个字节数据缓冲区。
使用 autoreleasepool!
紧密控制内存
如果你从 Objective-C 背景转向 Swift,你很可能知道 @autoreleasepool
块的实用性。《swift-rs》也为你提供了支持,只需将代码块用 autoreleasepool!
包裹起来,那么这个代码块现在将执行自己的 autorelease pool!
use swift_rs::autoreleasepool;
for _ in 0..10000 {
autoreleasepool!({
// do some memory intensive thing here
});
}
限制
目前,可以从 Rust 创建的类型只有数字类型、布尔值、SRString
和 SRData
。这是因为这些类型易于在栈上或通过调用 swift 在堆上分配内存,而其他类型则不行。虽然这可能会在未来实现。
跨 Swift 和 Rust 修改值目前不是这个库的目标,它纯粹是为了提供参数和返回值。此外,这会违反 Rust 的编程模型,可能会允许对值的多个共享引用,而不是通过类似 Mutex 的方式实现内部可变性。
许可证
根据您的选择,许可如下
- Apache 许可证 2.0 版本,(LICENSE-APACHE 或 https://apache.ac.cn/licenses/LICENSE-2.0)
- MIT 许可证 (LICENSE-MIT 或 http://opensource.org/licenses/MIT)
。
贡献
除非您明确声明,否则您提交的任何旨在包含在作品中的贡献,根据 Apache-2.0 许可证的定义,应按上述方式双许可,不附加任何额外条款或条件。
依赖项
~0.8–1.6MB
~36K SLoC