
桌面应用【免费下载链接】tinycastTinycast — a tiny, fully native macOS launcher, hotkeys, and clipboard history.项目地址https://gitcode.com/GitHub_Trending/ti/tinycast点击查看免费下载Tinycast 是一个完全原生的 macOS 启动器、热键与剪贴板历史工具仓库根目录 README.md。本文基于 docs/architecture.md 展开结合仓库源码深入讲解其整体架构从 PURE / EFFECT / OBSERVABLE STATE / VIEW 四层分层、AppCore单一所有者核心到入口与窗口管理、Observation 模型、Swift 6 并发纪律与目录树组织。读完本文你将理解这个代码库如何被装配在一起以及每一层、每一个类型、每一扇窗口在该架构中承担的确切职责。架构文档声明每个功能子系统的内部细节见 docs/features/ 下的分功能文档编写新代码的约定见 docs/standards.md。四层分层PURE → EFFECT → OBSERVABLE STATE → VIEW与文件夹树无关每一个成熟子系统都收敛到了相同的四层结构而Tests/下的独立 harness测试载体是让这四层保持分离的机制。原文档用一张分层图概括了全部四层┌─ PURE ─────────────────────────────────────────────────────────────────────┐ │ Foundation only. No AppKit, no clock, no network, no filesystem. Every │ │ environment fact is an injected parameter. │ │ ⇒ Compiled verbatim by a harness, so it cannot drift. │ │ │ │ SearchRelevance · EntryNaming · ScriptRomanization · LauncherOrder · │ │ SearchScopes · LauncherRankingStore · FileSearch{Query,Result,Scope} · │ │ Calculator/* · EmojiCatalog · EmojiGridGeometry · SystemAction · │ │ VolumeLevel · │ │ WindowCommand · WindowPlacementEngine · WindowActionMemory · WindowLayout/* · │ │ CustomWindowSize{,Store} · │ │ PaletteRowIndex · │ │ Uninstall{Target,SearchRoot,Rules,Protection,Plan} · │ │ Quicklink{,Destination,Store,Archive} · AppleShortcut · Notes/Model/* · │ │ Snippets/Model/* · │ │ ShellCommandRunner · DoubleTap{Modifier,Detector} · ClipboardStore · │ │ RaycastDecoder · Scrypt · AppSettingsKey · SettingsBackupCoverage │ │ MeetingLink · MeetingEvent · UpcomingWindow · MeetingDay · MenuBarSummary │ │ AutoJoinPolicy · EventDraft · SupportReminderSchedule · │ │ MenuSearch{Item,Shortcut,Query,TreeNode,SnapshotPolicy,Target} · │ │ WindowSwitch{Entry,Order,Query} │ └──────────────────────────────────┬─────────────────────────────────────────┘ │ consumed by ┌─ EFFECT ─────────────────────────▼─────────────────────────────────────────┐ │ All platform I/O, one folder per feature. │ │ AppIndex · SpotlightNames · FileSearchService · SettingsPaneScanner · │ │ AXWindowAccess · AXScreens · WindowInventory · WindowLayoutRunner · │ │ IconCache · WindowMover · UninstallScanner · UninstallRunner · │ │ SystemActionRunner · QuicklinkLauncher · TextInjector · │ │ SnippetKeywordListener · NotesRepository · CurrencyRateStore · Paster · │ │ HotKeyCenter · HyperKeyTap · DoubleTapMonitor · RunningAppsMonitor · │ │ CalendarStore · MeetingLauncher · MeetingClock · CameraSession · │ │ SupportReminderStore · AXMenuAccess · WindowZOrder · WindowSwitchSweep · │ │ AppleShortcutRunner │ └──────────────────────────────────┬─────────────────────────────────────────┘ │ published through ┌─ OBSERVABLE STATE ───────────────▼─────────────────────────────────────────┐ │ 39 MainActor Observable stores, sessions, indices and State types │ └──────────────────────────────────┬─────────────────────────────────────────┘ │ rendered by ┌─ VIEW ───────────────────────────▼─────────────────────────────────────────┐ │ SwiftUI screens, views and each features coordinator — declarative, thin │ └────────────────────────────────────────────────────────────────────────────┘四层的含义分别是PURE纯逻辑层只依赖 Foundation不碰 AppKit、不读时钟、不联网、不碰文件系统所有环境事实都以注入参数的方式传入。该层的文件会被 harness 原样编译而非复制一份因此它不可能漂移——任何一次让 harness 编译失败都意味着有决策泄漏进了效果层。EFFECT效果层所有平台 I/O每个功能一个文件夹。AXUIElement调用、CGEventTap、NSWorkspace.open、URLSession请求、FileManager遍历、CoreAudio 读取全部住在这里。该层做事情。OBSERVABLE STATE可观察状态层39 个MainActor Observable的 store、session、index 与 State 类型由效果层发布、被视图层渲染。VIEW视图层SwiftUI 屏幕、视图以及每个功能的 coordinator声明式、轻薄、不持有任何策略。在目录树中这四层对应为Model/、Service/、UI/与Settings/——可观察状态住在拥有它的那两层之一通常是 Model 或 Service中。Model / Service / UI / Settings 的职责划分在文件夹树中四层被物化为每个功能目录下的四个子目录小功能则保持扁平Model/— 纯逻辑。只依赖 Foundation数据需求确实必要时才引入 SQLite3 或 CoreGraphics。环境中的一切都被注入CalcEngine接收now/calendar/ratesLauncherRankingStore接收now和它的文件 URLWindowActionMemory把now作为参数接收UninstallRules拿到的是目录名称而不是 URLQuicklinkStore拿到的是主目录。这是决策的层。Service/— 效果。Store、monitor、runner、scanner 与 AppKit 胶水。每一个AXUIElement调用、CGEventTap、NSWorkspace.open、URLSession请求、FileManager遍历和 CoreAudio 读取都住在这里。这是执行的层。UI/与Settings/— 视图加上功能的 coordinator。声明式、轻薄、不持有任何策略。一条可检查的规则Model 层不得 import AppKit 或 SwiftUI这是整个架构最关键、也最可验证的一条规则Model/下的文件不得 import AppKit 或 SwiftUI因为 harness 编译的是发布版源码而不是副本详见 docs/testing.md 对 harness 与纯度检查的说明。一个停止编译的 harness就是决策泄漏进了效果层或效果泄漏进了决策层的信号。边界如何把效果挡在决策之外源码中有两个典型证据CalcEngine.evaluate被传入一个已经完成的CurrencyRates?而不是自己去取汇率——这使它保持 Foundation-only 且可测对应 Tinycast/Features/Calculator/Model/CalcEngine.swift 与 Tests/calc-test.swift。确认门你确定吗住在 coordinator 里绝不在 runner 里——这正是ShellCommandRunner与SystemActionRunner能被 harness 编译同时确认步骤又不可能被绕过的原因对应 Tinycast/Features/CustomCommands/Service/ShellCommandRunner.swift。另外有两个东西被刻意放在功能文件夹之外Tinycast/Features/PaletteRowIndex.swift 因为扁平选择索引归 palette 而非某个具体功能所有DesignSystem/Tinycast/DesignSystem/与Platform/Tinycast/Platform/是每个功能都依赖的共享原语与系统 shim。这两者都不得依赖任何功能。单一所有者核心AppCoreAppCore.sharedTinycast/App/AppCore.swift是一个MainActor单例拥有应用中每一个长生命周期的事物store 群AppIndex、ClipboardStore、SnippetsStore、QuicklinkStore、CustomCommandStore、FavoritesStore、VisibilityStore、AliasStore、LauncherRankingStore、CalculatorHistoryStore、CurrencyRateStore、FrequentEmojiStore、CalendarStoremanager / monitor / clock 群ClipboardManager、可选的ClipboardTextIndexer、HotKeyManager、HyperKeyTap、RunningAppsMonitor、SnippetKeywordListener共享状态AppSettings、PaletteState、FileSearchSession、MenuSearchSession、UninstallSession、CustomCommandArgumentSession、MeetingClock以及NotesStore、二十个功能 coordinator 与各窗口控制器。从 AppCore.swift 的 start() 方法可以看到start()是一屏就能读完的整个启动序列注册工具提示延迟默认值、设置.accessory激活策略、应用外观、启动appIndex、按顺序applyEnabled()各 coordinator、装配热键回调闭包、在首次启动时展示 Onboarding。而 AppDelegate.swift 中applicationDidFinishLaunching只做一件事——调用AppCore.shared.start()。这是唯一的装配点。功能动作住在 coordinator 上架构规则是功能动作位于该功能的 coordinator 上视图绝不能绕过 coordinator 直接去 mutate store。AppCore只持有连接热键 → coordinator的闭包装配。视图通过Environment注入AppCore并把它当作 coordinator 的locator使用——例如core.quicklinkCoordinator.deleteQuicklink(…)这种形状另一种做法是单独注入十五个 coordinator但毫无收益。从AppCore上读取 store 来渲染是允许的用 store 来做决策才是规则禁止的。showNotice、confirm、reportFailure、showMessage、pickVolume等都是AppCore自身的转发器见 AppCore.swift 的 Dialogs 段因此DialogController与MessageHUDController保持单一所有者。新长生命周期状态应放在AppCore上并在start()中装配不要创建与之竞争的第二个单例——这是单例不是容器。唯一离开进程的功能剪贴板文本识别剪贴板文本识别OCR是唯一离开进程的功能。AppCore拥有 indexerClipboardTextIndexer无状态的ClipboardTextWorker对每条项目运行一个随应用捆绑的ClipboardTextHelper位于Contents/Helpers用完即回收。因此 Vision 与 PDFKit 的内存分配属于一个退出即释放的进程——helper 没有数据库、剪贴板或设置访问权只被交给一个输入路径然后通过管道用有界文本作答。对应实现可见 Tinycast/Features/Clipboard/Service/ClipboardTextWorker.swift 与 ClipboardTextIndexer。入口与窗口谁创建了哪扇窗口TinycastAppTinycast/App/TinycastApp.swiftmain只声明两个MenuBarExtrascene——Tinycast 自己的菜单栏项与日历的菜单栏项各自由一个偏好项插入、彼此独立其余所有可见内容都由 AppKit 命令式驱动。命令面板Command palette——一个无边框浮动NSPanelTinycast/Palette/PalettePanel.swift通过NSHostingView承载 SwiftUI由PaletteWindowControllerTinycast/Palette/PaletteWindowController.swift管理。它通过调整窗口尺寸在紧凑条与完整启动器之间切换。控制器独占地拥有 frame每次显示解析一次顶部左侧锚点使面板向下生长hosting view 设置sizingOptions []让 SwiftUI 永远不会驱动窗口尺寸——否则 hosting view 会按内容调整面板大小导致紧凑↔展开切换时顶边漂移。面板在windowDidResignKey时自动消失见 PaletteWindowController.swift 的 windowDidResignKey。详见 docs/features/palette.md。设置与 Onboarding——带标题的NSWindow各有一个 Windows/AppWindowController.swift分别由SettingsCoordinator与OnboardingCoordinator拥有。对 accessory 应用而言 SwiftUI 的Settings与Windowscene 不可靠所以这是刻意选择。两者的生命周期与 palette 双向独立。Notes——一个持久的、带标题、非激活的NotesPanel由NotesWindowController管理。尺寸由用户决定AppKit 自动保存 frame其 TextKit 2 编辑器在字面源文本之上渲染 Markdown在本地 Markdown 文件间切换失焦仍保持可见。显示的字符串就是规范的源文件内容——不存在源/显示映射。详见 docs/features/notes.md。主菜单——由TinycastApp的.commands塑造TinycastApp.swift其中把 ⌘Q 重绑定为Close Settings。它只在一扇带标题窗口打开时才出现在屏幕上所以它是 Settings 的菜单栏且必须保持声明式。对话框Dialogs——无边框DialogPanel由DialogControllerTinycast/Windows/Dialog/DialogController.swift驱动是应用中唯一用于确认、失败报告和取值提示的 presenter。展示是async的因此不会阻塞主 actorpresenter 在有一个对话框显示期间拒绝第二个——挡住按住热键叠加对话框的正是这一点而不是某个 flag见 present 方法 中基于continuation的互斥。Support——带标题的AppWindowController窗口归SupportCoordinator所有尺寸按内容测量高度。每一条进入它的路径——palette 的菜单圆环、Settings → About、菜单栏、启动器、30 天提醒——都落在showSupport()上这也是提醒锚点移动的原因。详见 docs/features/support.md。相机表面Camera surfaces——无边框、非激活的CameraPanel位于.floating一个CameraSession上两种形态CameraPreviewController归CalendarCoordinator所有为加入会议把关兼任自动加入的确认CameraCoordinator归AppCore所有是独立的 Open Camera 命令。详见 docs/features/camera.md 与 docs/features/calendar.md。HUD是单独的因为对话框是询问而 HUD 是报告MessageHUDController胶囊与VolumeHUDController音量框两者共用HUDPresenter由后者持有同一时刻只有一个、自动消失、淡出的策略。详见 docs/ui.md#dialogs--hud。NSAlert从不被使用这一点是承重结构外观是一个设置项AppCore.applyAppearance()从AppSettings.appearance赋值NSApp.appearance.system则赋nil让 AppKit 自行跟随 macOS应用中没有任何其他地方设置外观见 AppCore.swift 的 applyAppearance。观察模型Observation39 个类型是MainActor Observable。代码库不用ObservableObject或Published视图通过Environment而不是EnvironmentObject读取状态。关于这套模型有三个容易踩坑的点原文档与 docs/standards.md 都专门强调memo 缓存与惰性构建的协作者必须标ObservationIgnored。不标的话读取 memo 会注册依赖视图会在自己的缓存填充时重渲染。AppCore的所有 coordinator 都是ObservationIgnored private(set) lazy正是这个原因例如 AppCore.swift 第 72-196 行。绝不要给Environment标注类型针对Observable值。宏是按类型解析无 key 重载的显式标注会改变所选重载。编译器看不到漏掉的注入点。一个视图从没人注入过的层级里读取Environment(AppSettings.self)能编译通过但会在运行时 trap——所以添加 hosting view 时务必检查注入。AppCore.track是在视图之外响应设置变更的范式见 AppCore.swift 的 track 方法withObservationTracking的onChange是willSet钩子——它在写入落地之前触发且是一次性的——所以闭包必须把重新读取推迟进一个Task并在那里重新武装 tracking。两个半边缺一不可移除Task会读到旧值。并发模型Swift 6 语言模式工程以Swift 6 语言模式构建数据竞争违规是硬错误。几乎一切都是MainActor跨 actor 的模型类型是Sendable。重活与 IO 密集工作——应用扫描、图片解码、设置面板扫描、shell 执行、汇率拉取——通过Task.detached驱动的nonisolated static函数推离主线程。整个项目刻意只有一个 actor。针对尖锐边角的居家惯例原文档列出均可在源码中核实块观察者的生命周期走 RAII 的NotificationTokenTinycast/Platform/NotificationToken.swift而不是在deinit里移除——token 在deinit中自动removeObserver且可在 nonisolated 析构中执行。ClipboardStore使用isolated deinit做 SQLite 清理在它的 actor 上拆除资源是值得照抄的惯用法。原始 Carbon 与 C 指针在跨入 actor 代码之前先解码为普通值参见hotKeyCarbonEventHandler。HealthTickerTinycast/Platform/HealthTicker.swift是周期健康检查共享的唯一定时器1 秒 watchdog弱订阅者无订阅者时不创建 timer因此各 event tap 不必各自持有一个——AppCore.start()中 hyperKeyTap / doubleTapMonitor / snippetListener 都注入同一个 healthTicker。目录树把分层变成可导航的文件夹文件夹布局就是把上面的分层做成可导航——每个功能一个文件夹容纳该功能拥有的一切Tinycast/ App/ main, AppDelegate, AppCore — the composition root DesignSystem/ Theme (the token source), KeyCapChip, Tooltip, SymbolImage, VisualEffectView, PopoverMenu, SettingsComponents, Scrolling/, Interaction/ Platform/ system shims: Permissions, LaunchAtLogin, InputSourceSwitcher, ScreenTarget, AppDisplayName, NotificationToken, AppPaths, Signposts, HealthTicker, Memo, ActivationPolicy, Images/, Compression/ Resources/ RaycastRuntime.generated.js, the embedded extension runtime Palette/ the palette shell: PalettePanel, PaletteWindowController, RootPaletteView, the PaletteScreen protocol, PaletteCoordinator, PaletteState, PaletteMode Windows/ the non-palette AppKit surfaces: AppWindowController, Dialog/, HUD/, About/ Assets.xcassets/ the app icon and the bundled image sets some catalog symbols resolve to Features/ PaletteRowIndex.swift the flat selection index — palette-owned, so it sits at the top Launcher/ Clipboard/ Calculator/ Calendar/ Emoji/ FileSearch/ MenuSearch/ Notes/ Quicklinks/ Snippets/ Uninstall/ SystemActions/ CustomCommands/ HotKeys/ Backup/ WindowManagement/ Onboarding/ Updates/ Support/ AI/ Settings/ Extensions/ Model/ pure — the harness inputs Service/ effects — stores, monitors, runners, AppKit glue UI/ screens, views, and the features coordinator Settings/ the features own panes Settings/ the Settings shell only: SettingsCoordinator, the sidebar/detail/toolbar and navigation types, SettingsTab, AppSettings, AppSettingsKey, and Panes/ for the two panes no feature owns Tests/ the standalone harnesses, one Swift file each Scripts/ run-tests.sh, the two data generators, packaging, formatting, editor setup较大的功能拆成全部四个子文件夹小的保持扁平正如Onboarding/那样。HotKeys/没有Settings/因为它的 Shortcuts 面板属于 Settings 外壳而非该功能。Resources/RaycastRuntime.generated.js是嵌入的扩展运行时其生成脚本在 Scripts/raycast-runtime/。每个SettingsTab映射到一个…SettingsView且每个都是.formStyle(.grouped)的标准Form见 docs/ui.md#settings。面板随功能走只有无主的面板General、Permissions才住在Settings/Panes/。四个启动器类别的面板——Applications、System Settings、System Actions、Commands——是共享LauncherItemsSection的薄包装Apple Shortcuts 把自己的功能开关与同一个LauncherItemsList配对。值得一提的实现细节SettingsTab与SettingsSection都以 case 本身而非索引标识。可选择的List会把 section 与 row 的 ID 拍平进同一个命名空间重叠的IntID 会让 SwiftUI 丢掉整个侧栏分组——Tests/settings-history-test.swift正是把这两个命名空间钉死分开的 harness。分层如何被验证架构不是纸上谈兵而是被机械地检查harness 编译发布版源码Tests/下的每个文件是一个独立 harnessTests/ 中 80 个单文件如 Tests/calc-test.swift、Tests/settings-history-test.swift由 Scripts/run-tests.sh 驱动。它们按原样编译Model/层源码所以Model/不得 import AppKit/SwiftUI这条规则一旦被违反harness 立刻编译失败。纯度 grep、格式与 lint、干净构建这些机械门槛统一列在 docs/testing.md 的 Definition of Done 里避免写两遍导致漂移。总结Tinycast 的架构可以用三句话概括四层分层把决策与执行分离且用编译 harness 机械保证AppCore是唯一的所有者与装配点功能动作统一走 coordinatorSwift 6 并发 Observation 让几乎一切保持主 actor 与显式注入。如果要在该代码库中新增一个功能正确的顺序是在Features/Name/Model/写纯逻辑并让 harness 编译它在Service/写效果在UI/Settings/写视图与 coordinator最后在AppCore.start()里装配——然后阅读 docs/standards.md 与 docs/testing.md 确认约定与检查门槛。赞分享桌面应用【免费下载链接】tinycastTinycast — a tiny, fully native macOS launcher, hotkeys, and clipboard history.项目地址https://gitcode.com/GitHub_Trending/ti/tinycast点击查看免费下载相关推荐深入SkyWalking架构四层架构解析与核心组件深入SkyWalking架构四层架构解析与核心组件 本文详细解析了SkyWalking的四层架构体系包括Probe层的数据采集机制、Platform Bac可观测性APM链路追踪指标监控日志分析微服务iii 核心概念深度解析Worker、Trigger、Function 与 Engine 的四层协作模型iii 核心概念深度解析Worker、Trigger、Function 与 Engine 的四层协作模型 本文是 iii 项目“理解 iii”系列的核心讲解文后端流程编排任务调度可观测性claudian 核心层架构解析src/core 的依赖边界、状态所有权与模型路由契约claudian 核心层架构解析src/core 的依赖边界、状态所有权与模型路由契约 claudian 是一个将 Claude Code / Codex 嵌AI 应用代码智能体交互助手人工智能AI Agent创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考