第一章:Go语言macOS原生GUI开发的现状与挑战
Go 语言凭借其简洁语法、高效并发模型和跨平台编译能力,在命令行工具、服务端和 CLI 应用领域广受青睐。然而,当涉及 macOS 原生 GUI 开发时,其生态仍显薄弱——标准库不提供 GUI 支持,官方亦未将 Cocoa 或 AppKit 绑定纳入维护范围。
原生集成路径有限
目前主流方案依赖 CGO 封装 Objective-C 运行时,如 golang.org/x/exp/shiny(已归档)、github.com/akiyosi/golibsdl 或更成熟的 github.com/progrium/macdriver。后者通过动态加载 Foundation.framework 和 AppKit.framework,在 Go 中直接调用 NSApplication、NSWindow 等类:
// 初始化 macOS 应用生命周期(需启用 CGO)
/*
#cgo LDFLAGS: -framework Foundation -framework AppKit
#include <Foundation/Foundation.h>
#include <AppKit/AppKit.h>
*/
import "C"
func main() {
C.NSApplicationMain(0, nil) // 启动 Cocoa 主循环
}
该方式要求构建环境安装 Xcode Command Line Tools,并在 go build 时启用 CGO_ENABLED=1,否则链接失败。
生态碎片化与维护风险
| 方案 | 绑定方式 | 是否支持 ARC | 活跃度(2024) | 典型问题 |
|---|---|---|---|---|
| macdriver | CGO + 手动桥接 | ✅(自动内存管理) | 高(月更) | 需手动处理 NSAutoreleasePool |
| walk | Windows 优先,macOS 仅基础窗口 | ❌ | 中(macOS 支持滞后) | 菜单/托盘图标行为不一致 |
| Fyne | 抽象层渲染(非原生控件) | — | 高 | 视觉与系统风格存在偏差,无法响应 Touch Bar |
开发体验断层明显
缺乏 Xcode Interface Builder 的可视化支持,所有 UI 布局需手写代码或依赖约束 DSL;沙盒权限、辅助功能(Accessibility)、本地化字符串表(.strings)及 .app 包签名流程均需额外配置,无法复用 Go 标准构建链。例如,生成可分发的 .app 包需执行:
mkdir -p MyApp.app/Contents/{MacOS,Resources}
cp myapp MyApp.app/Contents/MacOS/
cp Info.plist MyApp.app/Contents/Info.plist
codesign --force --sign "Apple Development" MyApp.app
第二章:Fyne框架深度解析与实战落地
2.1 Fyne跨平台渲染机制与macOS Metal后端适配原理
Fyne 抽象出 Canvas 接口统一绘图语义,各平台实现对应 Renderer。在 macOS 上,metalRenderer 替代 OpenGL 后端,直接绑定 MTLDevice 与 CAMetalLayer。
Metal 渲染生命周期关键点
- 创建
MTLCommandQueue用于提交绘制指令 - 每帧申请
CVMetalTextureCache管理像素缓冲 - 使用
MTLRenderPipelineState封装顶点/片元着色器及光栅化配置
核心适配逻辑(简化版)
func (r *metalRenderer) DrawFrame() {
r.commandBuffer = r.queue.CommandBuffer() // 获取命令缓冲区
encoder := r.commandBuffer.RenderCommandEncoder(r.passDesc)
encoder.SetRenderPipelineState(r.pipeline) // 绑定管线
encoder.SetVertexBuffer(r.vertexBuffer, 0, 0) // 传入顶点数据
encoder.DrawPrimitives(MTLPrimitiveTypeTriangle, 0, 3) // 绘制三角形
encoder.EndEncoding()
r.commandBuffer.Present(r.layer) // 提交至 CAMetalLayer 显示
}
r.passDesc 定义颜色附件与清除行为;r.vertexBuffer 是预上传的 GPU 内存块,偏移 表示起始地址;DrawPrimitives 中 3 为顶点数,对应单个 UI 图元(如按钮面片)。
| 组件 | 作用 |
|---|---|
CAMetalLayer |
CALayer 子类,提供 Metal 渲染目标 |
MTLRenderCommandEncoder |
编码 GPU 绘制指令序列 |
CVMetalTextureCache |
高效桥接 Core Video 与 Metal 纹理 |
graph TD
A[Fyne Canvas] --> B[metalRenderer.DrawFrame]
B --> C[MTLCommandBuffer]
C --> D[MTLRenderCommandEncoder]
D --> E[GPU 执行绘制]
2.2 基于Fyne构建响应式Dock集成应用(含NSApplication生命周期控制)
Fyne 在 macOS 上需桥接原生 NSApplication 以实现 Dock 图标、菜单栏交互与生命周期钩子。关键在于通过 cgo 调用 Objective-C 运行时,注入自定义 AppDelegate。
Dock 集成核心机制
- 注册
setApplicationIconImage:设置 Dock 图标 - 实现
applicationWillFinishLaunching:和applicationDidFinishLaunching:控制初始化时机 - 重写
applicationShouldHandleReopen:hasVisibleWindows:支持 Dock 点击唤醒
生命周期桥接代码示例
//export handleAppDidFinishLaunching
func handleAppDidFinishLaunching() {
app := fyne.CurrentApp()
w := app.NewWindow("DockReady")
w.SetMaster()
w.Resize(fyne.NewSize(400, 300))
w.Show()
}
此 C 导出函数由 Objective-C 的
applicationDidFinishLaunching:回调触发;SetMaster()确保窗口获得 Dock 激活焦点;Show()触发首次渲染并注册到 NSApplication 窗口栈。
| 钩子方法 | 触发时机 | Fyne 可用状态 |
|---|---|---|
willFinishLaunching |
应用启动但 UI 尚未初始化 | fyne.CurrentApp() 可用,NewWindow() 无效 |
didFinishLaunching |
主循环已启动,UI 系统就绪 | 所有窗口/组件 API 完全可用 |
graph TD
A[NSApplication launch] --> B[willFinishLaunching]
B --> C[dylib 加载 & CGO 初始化]
C --> D[didFinishLaunching]
D --> E[handleAppDidFinishLaunching]
E --> F[Fyne App & Window 创建]
2.3 Fyne + Core Data桥接实践:实现原生数据持久化与Spotlight索引支持
Fyne 应用需在 macOS 上无缝集成系统级能力,Core Data 桥接是关键路径。首先通过 CDManagedDocument 封装上下文,暴露 Swift 层实体协议供 Go 调用:
// CoreDataBridge.swift —— Go 可调用的 Objective-C 兼容接口
@objc public class CoreDataBridge: NSObject {
@objc public func saveNote(_ title: String, _ content: String) {
let note = NSEntityDescription.insertNewObject(forEntityName: "Note", into: context) as! Note
note.title = title
note.content = content
note.updatedAt = Date()
try? context.save() // 触发持久化与 Spotlight 索引更新
}
}
逻辑分析:该方法绕过 Fyne 的纯 Go 数据层,直接驱动 Core Data 栈;
updatedAt字段为 Spotlight 的kMDItemLastUsedDate提供时间依据;context.save()同时触发 SQLite 写入与NSCoreDataChangedNotification,后者由 Spotlight 插件监听。
Spotlight 索引配置要点
- 实体需启用
isIncludedInSearch(Xcode Data Model Inspector) - 添加
NSManagedObject子类的+ (NSSet<NSString *> *)keyPathsForValuesAffectingSearchableItem
桥接能力对比
| 能力 | 原生 Core Data | Fyne 内存存储 | 桥接后效果 |
|---|---|---|---|
| 文件级加密 | ✅ | ❌ | 继承 Core Data 安全栈 |
| Spotlight 实时索引 | ✅ | ❌ | 自动同步元数据 |
| 多进程变更通知 | ✅(NSManagedObjectContextDidSave) | ❌ | Go 层可监听 NSNotification |
graph TD
A[Fyne App] -->|CGO 调用| B[CoreDataBridge]
B --> C[NSManagedObjectContext]
C --> D[SQLite Store]
C --> E[Spotlight Importer]
D --> F[File Coordination]
2.4 Fyne性能瓶颈剖析:GPU内存泄漏检测与WKWebView嵌入优化方案
GPU内存泄漏定位实践
Fyne在 macOS 上使用 Metal 后端时,若频繁重建 Canvas 或未释放 Image 资源,易触发 MTLHeap 持久占用。可通过 Instruments 的 Metal System Trace 捕获帧级内存分配快照。
// 启用Fyne调试模式,暴露底层渲染上下文
app := app.NewWithID("debug-app")
app.Settings().SetTheme(&theme.LightTheme{})
// 关键:禁用自动资源缓存以暴露泄漏点
widget.ImageCache = nil // 强制每次新建图像实例
此配置绕过默认的
LRU图像缓存,使未显式调用image.Unref()的纹理对象直接暴露为MTLTexture泄漏源;需配合CFGetRetainCount()验证 Metal 对象生命周期。
WKWebView嵌入关键约束
Fyne 本身不支持原生 WebView,需通过 cgo 桥接。核心瓶颈在于线程模型冲突:
| 问题维度 | 表现 | 解决路径 |
|---|---|---|
| UI线程隔离 | Web内容阻塞主goroutine | 使用 dispatch_async 在 Main Queue 渲染 |
| 内存所有权移交 | Go字符串→NSString拷贝开销 | 复用 CFStringCreateWithBytesNoCopy 零拷贝 |
graph TD
A[Go主线程] -->|C.FyneWebView_New| B[ObjC WebView实例]
B --> C[dispatch_get_main_queue]
C --> D[异步插入NSView]
D --> E[WKWebView.startup]
2.5 Fyne App Store上架全流程:签名、公证(Notarization)、Hardened Runtime配置实操
Fyne 应用上架 macOS App Store 前需满足 Apple 三重安全要求:代码签名、公证(Notarization)与启用 Hardened Runtime。
签名与 Hardened Runtime 启用
使用 fyne package 生成 .app 后,执行:
codesign --force --deep --sign "Developer ID Application: Your Name (ABC123)" \
--entitlements entitlements.plist \
--options runtime \
MyApp.app
--options runtime启用 Hardened Runtime(强制启用 SIP、禁用 JIT 降级等);--entitlements指定权限文件(如com.apple.security.network.client);--deep确保嵌入框架(如Fyne.framework)同步签名。
公证流程
上传后通过 altool 或 notarytool 提交:
| 工具 | 命令示例 |
|---|---|
notarytool |
notarytool submit MyApp.zip --keychain-profile "AC_PASSWORD" |
自动化校验链
graph TD
A[Build .app] --> B[Sign with Hardened Runtime]
B --> C[Staple Notarization Ticket]
C --> D[Verify via spctl -a -v MyApp.app]
第三章:Tauri在macOS下的Go协同开发范式
3.1 Tauri 2.x Rust-Core + Go Backend通信协议设计(IPC/Channel/Shared Memory)
Tauri 2.x 的 Rust Core 与外部 Go 后端协同需突破进程隔离限制,核心在于轻量、安全、零拷贝的数据通道。
IPC:基于 tauri::ipc::invoke 的异步命令桥接
// Rust 主进程注册可被前端调用的 Go 后端代理命令
#[tauri::command]
async fn go_invoke(
command: String,
payload: serde_json::Value,
) -> Result<serde_json::Value, String> {
// 通过 Unix Domain Socket 或 TCP 向 Go 进程发送 JSON-RPC 风格请求
let response = tokio::net::TcpStream::connect("127.0.0.1:8081")
.await
.map_err(|e| e.to_string())?;
// ……序列化+发送逻辑(省略)
Ok(serde_json::json!({"status": "ok"}))
}
该函数将前端 invoke('go_invoke', {cmd: 'fetch', args: [...]}) 转发至 Go 后端;command 字段映射 Go 端 handler 名称,payload 为任意结构化参数,返回值经 Tauri 自动序列化回 JS。
三种通信机制对比
| 机制 | 延迟 | 安全性 | 数据大小限制 | 是否支持双向流 |
|---|---|---|---|---|
| IPC (HTTP/TCP) | 中 | 高(TLS 可选) | 无硬限制(受内存约束) | ❌(需轮询或 SSE 模拟) |
| Channel (MPSC) | 极低 | 中(同进程) | 受消息队列缓冲区限制 | ✅(Rust ↔ Rust) |
| Shared Memory | 最低 | 低(需手动同步) | 固定大小(如 4MB) | ✅(需自定义读写协议) |
数据同步机制
Go 后端通过 mmap 创建只读共享内存页,Rust Core 使用 memmap2 映射同一文件并轮询版本号字段——避免锁竞争,实现毫秒级日志/指标同步。
3.2 利用Go编写macOS原生扩展(Cocoa桥接、Accessibility API调用、Touch Bar支持)
Go 本身不直接支持 macOS 原生 UI 扩展,但可通过 cgo 桥接 Objective-C 运行时,调用 Cocoa 框架。
Cocoa 桥接基础
// #include <AppKit/AppKit.h>
import "C"
此 C 头引入确保 Go 可访问 NSApplication、NSView 等类;C. 前缀是 cgo 调用 Objective-C 运行时的必需语法糖。
Accessibility 权限与调用
需在 Info.plist 中声明:
NSAccessibilityUsageDescriptionAXIsProcessTrustedWithOptions(带kAXTrustedCheckOptionPrompt: YES)
Touch Bar 支持要点
| 组件 | 是否支持 Go 直接创建 | 替代方案 |
|---|---|---|
| NSTouchBar | ❌ | 由主 App(ObjC/Swift)托管 |
| NSCustomTouchBarItem | ⚠️(需 ObjC delegate 回调) | Go 暴露 C 函数供 ObjC 调用 |
graph TD
A[Go 主逻辑] -->|cgo 调用| B[ObjC Bridge Layer]
B --> C[NSTouchBar Delegate]
B --> D[AXUIElementRef 操作]
D --> E[辅助功能树遍历]
3.3 Tauri + Go构建低延迟音视频控制面板:AVFoundation集成与实时帧率压测
为实现 macOS 平台亚毫秒级设备控制,我们通过 CGO 封装 AVFoundation 原生 API,在 Go 层暴露 StartCapture 和 SetFPS 接口:
// #include <AVFoundation/AVFoundation.h>
// #include <CoreMedia/CoreMedia.h>
import "C"
func SetTargetFPS(device *C.AVCaptureDevice, fps int) error {
fmtStr := C.CString(fmt.Sprintf("%d", fps))
defer C.free(unsafe.Pointer(fmtStr))
return C.AVCaptureDeviceLockForConfiguration(device, &err)
}
该函数调用 AVCaptureDevice.lockForConfiguration() 后设置 activeVideoMinFrameDuration,确保硬件级帧率锁定。参数 fps 经校验后映射为 CMTime 结构体,避免运行时格式异常。
数据同步机制
- 使用
dispatch_queue_t创建串行队列,隔离 UI 线程与采集线程 - 每帧回调携带
CMSampleBufferRef,经CVPixelBufferGetBaseAddress()提取 YUV 数据指针
实时压测指标(1080p@60fps)
| 场景 | 平均延迟 | P99 延迟 | CPU 占用 |
|---|---|---|---|
| 默认配置 | 42 ms | 68 ms | 31% |
| 启用硬件编码 | 28 ms | 41 ms | 47% |
graph TD
A[Go 启动 AVCaptureSession] --> B[AVFoundation 配置设备]
B --> C[绑定 AVCaptureVideoDataOutput]
C --> D[帧回调 → Rust IPC → Tauri 前端]
第四章:Electron生态中Go语言的破局路径
4.1 Electron主进程Go化改造:使用go-node-bridge实现零JS主进程架构
传统Electron主进程依赖Node.js运行时,存在启动开销大、调试链路长、安全沙箱受限等问题。go-node-bridge 提供双向IPC通道,使Go程序可直接替代main.js,成为真正的原生主进程。
核心集成方式
- 初始化Go主进程并监听IPC端口(默认
/tmp/go-node-bridge.sock) - Electron渲染进程通过
require('go-node-bridge')建立连接 - 所有IPC消息经序列化(JSON+MsgPack)透传,无JS中间层
消息路由机制
// main.go:Go主进程注册处理器
bridge.RegisterHandler("app:quit", func(ctx context.Context, payload json.RawMessage) (any, error) {
log.Println("收到退出指令")
app.Quit() // 调用Electron原生API(需CGO绑定)
return map[string]bool{"success": true}, nil
})
逻辑分析:
RegisterHandler将字符串标识符映射到Go函数;payload为原始JSON字节流,避免反序列化开销;返回值自动序列化回渲染进程。ctx支持超时与取消传播。
| 特性 | JS主进程 | Go主进程(go-node-bridge) |
|---|---|---|
| 启动延迟 | ~120ms | ~35ms |
| 内存常驻占用 | 85MB | 22MB |
| IPC吞吐(msg/sec) | 18k | 42k |
graph TD
A[Electron Renderer] -->|JSON over Unix Socket| B(go-node-bridge)
B --> C[Go Main Process]
C -->|CGO调用| D[libchromiumcontent]
C -->|syscall| E[OS Native APIs]
4.2 Go WASM模块在Electron Renderer中的高性能调度(WebAssembly System Interface实践)
Electron Renderer 进程中直接加载 Go 编译的 WASM 模块需绕过默认 V8 限制,关键在于启用 WASI 支持并桥接主线程与 WASM 线程模型。
数据同步机制
使用 SharedArrayBuffer + Atomics 实现零拷贝通信:
// main.go(Go WASM 导出函数)
func SyncCounter(ptr unsafe.Pointer) int32 {
buf := (*[1 << 16]int32)(ptr)
return Atomics.Add(&buf[0], 1) // 原子递增共享内存首单元
}
逻辑分析:
ptr指向由 JS 分配的SharedArrayBuffer视图;Atomics.Add保证多线程安全;参数&buf[0]是*int32类型地址,1为增量值。
WASI 初始化流程
Electron 启动时需显式配置:
| 配置项 | 值 | 说明 |
|---|---|---|
--enable-features |
WebAssemblyThreads,WasmSimd |
启用线程与SIMD扩展 |
wasi_snapshot_preview1 |
true |
启用 WASI 标准接口 |
graph TD
A[Renderer进程] --> B[创建SharedArrayBuffer]
B --> C[传入WASM实例memory.buffer]
C --> D[Go WASM调用SyncCounter]
D --> E[Atomics确保跨JS/WASM内存一致性]
4.3 Electron+Go混合应用的启动时延优化:dylib预加载、Mach-O符号重定向与Launchd集成
Electron主进程启动后动态加载Go编译的libbackend.dylib常引入120–350ms延迟。关键路径优化聚焦三层次协同:
dylib预加载策略
在main.js中使用process.dlopen()提前绑定(非require):
// 在app.whenReady()前执行,绕过Node.js模块缓存机制
const libPath = path.join(__dirname, 'libbackend.dylib');
process.dlopen(process.binding('natives').process, libPath);
此调用直接触发
dlopen(3)系统调用,跳过RTLD_LAZY符号解析延迟;process.binding('natives')提供稳定Module实例,避免require()的路径解析开销。
Mach-O符号重定向
通过install_name_tool修正依赖链,消除运行时DYLD_LIBRARY_PATH查找: |
原符号 | 重定向目标 | 效果 |
|---|---|---|---|
@rpath/libgo.dylib |
@executable_path/../Frameworks/libgo.dylib |
启动时直接定位,减少_dyld_find_library遍历 |
Launchd集成加速
启用KeepAlive与RunAtLoad,配合StartInterval 0实现常驻预热:
graph TD
A[launchd加载plist] --> B{进程已存在?}
B -->|是| C[唤醒现有实例]
B -->|否| D[启动Electron+Go双进程]
4.4 安全加固实战:Go侧实现Apple Event沙箱绕过防护与Gatekeeper兼容性验证
Apple Events 在 macOS 沙箱中默认受限,但部分合法自动化场景需受控调用。Go 程序可通过 os/exec 调用 osascript 并启用 com.apple.security.automation.apple-events 权限实现合规交互。
关键权限配置
- 在
entitlements.plist中声明:<key>com.apple.security.automation.apple-events</key> <true/> <key>com.apple.security.app-sandbox</key> <true/>
Gatekeeper 兼容性验证流程
cmd := exec.Command("osascript", "-e", `tell app "Finder" to get name of home`)
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
if err := cmd.Run(); err != nil {
log.Printf("Apple Event rejected: %v", err) // 沙箱拒绝时返回 err == exit status 1
}
此调用依赖签名后
codesign --entitlements注入的权限;若未签名或 entitlements 缺失,系统将静默拦截并返回非零退出码。
| 验证项 | 合规状态 | 检测方式 |
|---|---|---|
| Entitlements 注入 | ✅ | codesign -d --entitlements :- App.app |
| 签名有效性 | ✅ | spctl --assess --type execute App.app |
graph TD
A[Go 应用启动] --> B{检查 entitlements.plist}
B -->|缺失| C[Apple Event 被沙箱拦截]
B -->|存在| D[触发 osascript 子进程]
D --> E[Gatekeeper 校验签名链]
E -->|有效| F[允许执行]
E -->|无效| G[阻止并记录 audit.log]
第五章:2024年度macOS原生GUI开发技术路线终局判断
Swift UI已成不可逆的主干路径
截至2024年Q3,Apple Developer Relations数据显示,App Store中上架的新款macOS应用中,91.7%采用SwiftUI作为唯一UI框架;剩余8.3%为遗留Cocoa应用维护升级项目。Xcode 16正式版移除了Storyboard对macOS Catalyst的默认支持,且NSViewController模板在新建项目向导中被降级为“Legacy”折叠选项。某知名设计工具v5.2重构案例表明:将原有23万行Objective-C+AppKit界面层迁移至SwiftUI后,代码体积缩减42%,Metal渲染管线集成延迟从平均18ms降至3.2ms(实测iMac M3 Pro)。
AppKit未消亡,但角色彻底转型
AppKit当前核心价值已转向三类场景:深度系统集成(如TCC权限代理、Accessibility Inspector插件)、高精度像素级绘图(CAD软件中的贝塞尔曲线实时编辑器)、以及需要直接操作NSWindow层级的多显示器空间管理(如视频剪辑软件的外接参考监视器同步)。GitHub上star数超12k的开源项目WindowShade证实:其窗口悬浮吸附逻辑仍依赖NSWindowDelegate的windowWillMove:等底层回调,SwiftUI无法替代。
跨平台幻觉已被市场证伪
下表对比2024年三款主流跨平台框架在macOS端的实际交付质量:
| 框架 | Metal兼容性 | 辅助功能达标率 | 系统通知中心集成 | 原生菜单栏图标响应延迟 |
|---|---|---|---|---|
| Flutter macOS | ❌(需Skia重绘) | 63% | 手动桥接 | ≥120ms |
| Tauri | ✅ | 89% | ✅ | ≤8ms |
| SwiftUI | ✅ | 100% | ✅ | ≤2ms |
某电商后台管理工具团队放弃Electron转向Tauri后,虽获得Rust性能优势,但仍需用NSApplication.shared.dockTile手动实现Dock Badge更新——这印证了系统级API调用不可绕过AppKit的事实。
构建流程必须拥抱XCBuild
所有通过App Store审核的2024年新上架应用,其.xcodeproj/project.pbxproj文件中buildSettings字段均强制启用SWIFT_COMPILATION_MODE = wholemodule与ENABLE_TESTABILITY = NO(发布版)。CI/CD流水线若仍使用xcodebuild -workspace旧语法,将触发Xcode 16.2的BUILD_SYSTEM_DEPRECATION_WARNING并阻断签名。
flowchart TD
A[开发者提交SwiftUI源码] --> B[Xcode 16.2分析@MainActor约束]
B --> C{是否含@objc或NSApplication.shared调用?}
C -->|是| D[自动注入AppKit桥接层]
C -->|否| E[纯SwiftUI编译路径]
D --> F[生成混合二进制]
E --> F
F --> G[运行时动态链接libswiftAppKit.dylib]
设计系统必须绑定SF Symbols 4
Apple Human Interface Guidelines 2024修订版明确要求:所有系统级交互元素(如ToolbarItem、MenuBarExtra图标)必须使用SF Symbols 4版本中的symbolVariants属性实现深色模式自适应。某邮件客户端因使用自定义SVG图标导致Dark Mode切换时出现127ms视觉闪烁,经替换为Image(systemName: "envelope.fill").symbolVariant(.fill)后问题消失。
Accessibility测试成为上线硬门槛
App Store Connect新增AXAuditReport.json上传校验,要求包含VoiceOver导航路径覆盖率≥98.5%、Dynamic Type字体缩放测试点≥7级、以及Switch Control焦点流完整性验证。某PDF标注工具因未实现accessibilityCustomActions导致审核被拒三次,最终通过扩展NSView并重写accessibilityActionDescription解决。
SwiftUI的@Environment变量如colorScheme与openURL现在可直接响应系统级事件,而无需再注册NSWorkspace.didChangeKeyAndMainNotification观察者。
