Cacao
这个库为macOS上的AppKit
(测试版,相当可用)和iOS/tvOS上的UIKit
(内测版,详见仓库)提供了安全的Rust绑定。
它尝试以一种方式实现,如果你之前用Swift或Objective-C编程过这个框架,会感到熟悉。由于Rust的所有权模型,这在Rust中很棘手,但一些创造性的编码和假设可以让我们走得很远。
这个库存在于crates.io上,部分原因是为了让项目得到更广泛的使用,从而为开发提供参考。话虽如此,这个库目前还处于早期阶段,可能存在bug - 使用它的风险由你自己承担。然而,只要你遵循规则(关于内存/所有权),它已经可以用于一些应用程序了。核心仓库有大量示例可以帮助你入门。
重要
如果你从0.2版本迁移到0.3版本,你应该在
Cargo.toml
中选择appkit
或uikit
作为特性。这个改变是为了支持不仅仅是macOS/iOS/tvOS的平台(例如,gnustep, airyx)。这些特性中的一个是必需的;为了便于开发,默认选择了appkit
。
请注意,这个crate依赖于Objective-C运行时。与运行时交互需要unsafe代码块;这个crate为你处理了那些不安全的交互,并提供了一个安全的包装器,但使用这个crate意味着你理解
unsafe
的使用是必然的,并且在包装的控件中会相当普遍。这并不意味着你不能评估、审查或质疑unsafe的使用 - 只是要知道它正在发生,而且在很大程度上它不会消失。仅仅涉及unsafe存在的问题将被关闭,不予评论。
如果你想在本地机器上构建这个库的文档,由于特性标志与cargo doc
的工作方式,你需要使用以下命令:
RUSTDOCFLAGS="--cfg docsrs" cargo +nightly doc --all-features --open
Hello World
use cacao::appkit::{App, AppDelegate};
use cacao::appkit::window::Window;
#[derive(Default)]
struct BasicApp {
window: Window
}
impl AppDelegate for BasicApp {
fn did_finish_launching(&self) {
self.window.set_minimum_content_size(400., 400.);
self.window.set_title("Hello World!");
self.window.show();
}
}
fn main() {
App::new("com.hello.world", BasicApp::default()).run();
}
更多详细示例,请查看examples/
文件夹。
如果你对更全面的"厨房水槽"示例感兴趣,可以通过以下命令查看todos_list:
cargo run --example todos_list
初始化
由于AppKit和UIKit程序通常的工作方式,我们鼓励你从AppDelegate
的did_finish_launching()
方法开始进行大部分工作。这确保了应用程序有时间初始化并在后台进行任何必要的内务处理。
当前支持
就大部分可工作的部分而言,下表展示了对各种功能的支持级别。这个列表并不详尽,仅仅是因为更新文档是一件麻烦事 - 所以我们鼓励你查看代码生成的文档以获取更多信息:
注意,虽然iOS有绿色对勾,但一些组件仍然没有很好地定义(例如,Views/ViewControllers在那里仍然处于非常初级的阶段)。
提供AppKit形式或替代品的非苹果平台可能能够使用这个库中的大部分AppKit支持。
组件 | 描述 | AppKit | iOS | tvOS |
---|---|---|---|---|
App | 初始化和事件 | ✅ | ✅ | ❌ |
Window | 构建、处理、事件 | ✅ | ✅ | ❌ |
View | 构建、样式、事件 | ✅ | ✅ | ❌ |
ViewController | 构建、生命周期事件 | ✅ | ✅ | ❌ |
Color | 系统支持的颜色、主题 | ✅ | ✅ | ❌ |
ListView | 可重用列表,带缓存行 | ✅ | ❌ | ❌ |
Button | 样式、事件、工具栏支持 | ✅ | ❌ | ❌ |
Label/TextField | 文本渲染和输入 | ✅ | ❌ | ❌ |
Image/ImageView | 加载、绘制等 | ✅ | ✅ | ❌ |
Toolbar | 基本原生工具栏 | ✅ | ❌ | ❌ |
SplitViewController | 分割视图(兼容Big Sur) | ✅ | ❌ | ❌ |
WebView | WKWebView的包装器 | ✅ | ❌ | ❌ |
UserDefaults | 持久化小数据 | ✅ | ✅ | ❌ |
Autolayout | 适应不同屏幕的视图布局 | ✅ | ✅ | ❌ |
可选特性
以下是可以启用或禁用的Cargo特性列表。
appkit
:链接AppKit.framework
。uikit
:链接UIKit.framework
(仅限iOS/tvOS)。cloudkit
:链接CloudKit.framework
并提供一些CloudKit功能的包装器。目前功能尚不完整。color_fallbacks
:为较旧的系统提供不存在systemColor
类型的后备颜色。这个特性非常少见,你可能不需要它。quicklook
:链接QuickLook.framework
并提供生成文件预览图像的方法。user-notifications
:链接UserNotifications.framework
并提供在macOS和iOS上发送通知的功能。注意,这要求你的应用程序进行代码签名,否则无法工作。webview
:链接WebKit.framework
并提供由WKWebView
支持的WebView
控件。这个特性在tvOS上不支持,因为该平台没有webview控件。由于WKWebView控件和非苹果平台上的不同支持,这个特性可能也只支持macOS/iOS。webview-downloading-macos
:通过私有接口启用从WebView
下载文件的功能。这不是一个App Store安全的特性,所以在启用之前要注意这一点。这个特性在iOS上不支持(用户会以非常不同的方式处理下载),在tvOS上也不支持(那里根本没有网络浏览器)。
一般说明
为什么不扩展现有的cocoa-rs crate?
这是一个好问题。归根结底,我认为那个crate(如果我理解错了,请纠正我)在某种程度上与Servo绑定,而我想尝试在Rust中表示Cocoa UI模型的最佳方法。这个crate也并非完全忽视了他们的工作 - 内部使用并重新导出了core_foundation
和core_graphics
以供一般使用。
为什么我应该使用Rust编程,而不是X语言?
就我而言,我希望能够为我的设备(以及我喜欢为之构建产品的平台)编写原生应用程序,而不必局限于使用苹果特定的语言...也不必使用C/C++或JavaScript(注:指的是工具链,而非语言本身 - ES6/TypeScript是可以的)。我之所以想这样做,是因为我厌倦了每次想要将应用程序移植到其他生态系统时都要面对大量工作。我认为Rust提供了一个(正在成长但已相当重要的)可行模式,可以在不牺牲性能的情况下跨平台和生态系统共享代码。
(这里通常会引发互联网上关于Electron、Qt等的激烈讨论 - 我们在此不再赘述,因为这个话题已经被讨论得很透彻了)
这个crate对那些不需要完全投入苹果生态系统,但希望相对轻松地将其工作移植到该平台的人来说是有用的。我们并不期望所有人都会突然想用Rust重写他们的macOS/iOS/tvOS应用。
Objective-C是否已经过时了? 是,也不是。
诚然,苹果确实更青睐Swift,这是有充分理由的(我作为一个毫不掩饰的Objective-C爱好者也这么说)。话虽如此,我会感到惊讶如果我们没有至少5年以上的支持;苹果虽然很快就会弃用旧技术,但完全移除Objective-C运行时需要大量的时间和精力。也许SwiftUI会终结它,谁知道呢。围绕这些内容的包装器应该可以在将来需要更换底层UI后端时更容易地进行切换。
需要注意的一点是,苹果已经开始发布仅支持Swift的框架。对于需要使用这些框架的情况,应该可以通过某种链接和桥接的组合来实现 - 这将为未来某个时候如何切换底层UI后端提供参考。
有些人可能会谴责Objective-C速度慢。对此,我想指出以下几点:
- 你的UI引擎可能不是性能瓶颈。
- Swift通常更好,因为它修复了Objective-C无法捕获的一类bug;大多数情况下,它仍然建立在现有的Cocoa框架之上(尽管这种说法可能很快就会过时)。
- Objective-C中的消息分发比你编写的大部分代码都要优化得更好,对大多数情况来说已经足够快了。
简而言之,它可能已经足够好了,而且你可以使用Rust来满足性能需求。
为什么不直接包装UIKit,然后依赖Catalyst? 我还没有见过一个使用Catalyst感觉良好的应用程序。不过,这个目标是好的,如果它发展到看起来就是未来的方向(比如,苹果直接终止AppKit),那当然是一个选择。
你不可能在这里包装所有平台特定的行为...
没错!每个UI控件都包含一个objc
字段,你可以将其用作逃生舱 - 如果控件不支持某些功能,你可以自由地降级到Objective-C运行时并自行处理。
为什么你不使用绑定来自动生成这些内容? 出于初步探索的目的,我主要是手动完成这些工作的,因为我想在投入绑定生成之前找到一种适合Rust模型的方法。现在我已经让东西"运行"得足够好了,这可能是我下一步要关注的重点。
这与Swift项目Cacao有关吗? 没有。这个问题中提到的项目旨在将Cocoa和UIKit的部分内容映射到Linux上运行,但已经有一段时间没有活动了(它确实很酷!)。
2020年的开源项目命名就像试图购买一个.com
域名:所有好名字都被占用了。幸运的是,多个项目可以共享一个名称...所以这里就会出现这种情况。
这是不是有点欺骗Rust的对象模型? 这取决于你如何看待它。我个人并不太在意 - 对于某些类型的产品来说,这些平台的GUI层是一个硬性要求,放弃它们也意味着放弃了经过实战检验的工具,用于处理诸如无障碍性和更深层次的操作系统集成等问题。话虽如此,内部确实在努力尝试让事物尊重Rust的工作模型。
你可以把这看作类似于gtk-rs。如果你想支持或尝试一个更"纯粹"的模型,可以去看看Druid之类的项目。:)
许可证
采用MIT/MPL-2.0双重许可。有关更多信息,请参阅本仓库中的相应文件。Apple、AppKit、UIKit、Cocoa和其他商标是Apple, Inc.的版权。