关于 swift-macos
swift-macos 是面向 macOS 开发者的 Swift 6.3、SwiftUI 与 SwiftData 开发参考技能,涵盖从界面和数据模型到并发、测试及发布的内容。它还提供 Foundation Models、屏幕采集和 AppKit 互操作等主题的指南。
主要功能
- 01SwiftUI 场景、窗口与原生界面
- 02SwiftData 查询、关系与迁移
- 03Swift 并发与数据安全类型
- 04Foundation Models 集成参考
- 05测试、构建与应用分发
- 06ScreenCaptureKit 与 AppKit 互操作
适用场景
- 01构建原生 macOS 应用界面
- 02设计 SwiftData 模型和迁移
- 03处理异步任务与并发安全
- 04测试并分发 macOS 应用
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ tenequm/ skills --skill swift-macos安装后在 Claude Code 中输入/swift-macos确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
CHANGELOG.mdLICENSE.txtreferences/actors-isolation.mdapp-lifecycle.mdappkit-interop.mdapproachable-concurrency.mdarchitecture.mdasync-patterns.mdcloudkit-sync.mdcontainer-context.mdcore-audio-tap.mddistribution.mdfall-2026-releases.mdfoundation-models.mdmigration-guide.mdmigrations.mdmodels-schema.mdrelationships-predicates.mdscreen-capture-audio.mdsendable-safety.mdspm-build.mdstructured-concurrency.mdswiftui-macos.mdsystem-integration.mdtesting.md详细介绍
macOS App 开发 - Swift 6.3
使用 Swift 6.3(最新版本:6.3.3,随 Xcode 26.6 捆绑,2026 年 6 月)、SwiftUI、SwiftData 和 macOS 26 Tahoe(当前版本为 26.6.2)构建原生 macOS 应用。目标版本为 macOS 14+(支持 SwiftData/@Observable)、macOS 15+(支持最新 SwiftUI),以及 macOS 26(支持 Liquid Glass 和 Foundation Models)。请注意,Xcode 26.6 仍捆绑 macOS 26.5 SDK,因此目前还无法构建仅适用于 26.6 的 API。如需了解 WWDC 2026 Beta 技术栈(macOS 27、Xcode 27、Swift 6.4,计划于 2026 年秋季发布),请参阅 references/fall-2026-releases.md。
快速入门
import SwiftUI
import SwiftData
@Model
final class Project {
var name: String
var createdAt: Date
// Named ProjectTask, not Task - `Task` would shadow `Swift.Task`
@Relationship(deleteRule: .cascade) var tasks: [ProjectTask] = []
init(name: String) {
self.name = name
self.createdAt = .now
}
}
@Model
final class ProjectTask {
var title: String
var isComplete: Bool
var project: Project?
init(title: String) {
self.title = title
self.isComplete = false
}
}
@main
struct MyApp: App {
var body: some Scene {
WindowGroup("Projects") {
ContentView()
}
.modelContainer(for: [Project.self, ProjectTask.self])
.defaultSize(width: 900, height: 600)
#if os(macOS)
Settings { SettingsView() }
MenuBarExtra("Status", systemImage: "circle.fill") {
MenuBarView()
}
.menuBarExtraStyle(.window)
#endif
}
}
struct ContentView: View {
@Query(sort: \Project.createdAt, order: .reverse)
private var projects: [Project]
@Environment(\.modelContext) private var context
@State private var selected: Project?
var body: some View {
NavigationSplitView {
List(projects, selection: $selected) { project in
NavigationLink(value: project) {
Text(project.name)
}
}
.navigationSplitViewColumnWidth(min: 200, ideal: 250)
} detail: {
if let selected {
DetailView(project: selected)
} else {
ContentUnavailableView("Select a Project",
systemImage: "sidebar.left")
}
}
}
}
场景与窗口
| 场景 | 用途 |
|---|---|
WindowGroup | 可调整大小的窗口(多个实例) |
Window | 单实例实用工具窗口 |
Settings | 偏好设置(Cmd+,) |
MenuBarExtra | 菜单栏,采用 .menu 或 .window 样式 |
DocumentGroup | 基于文稿的应用 |
打开窗口:@Environment(\.openWindow) var openWindow; openWindow(id: "about")
完整的场景生命周期说明,请参阅 references/app-lifecycle.md。
菜单与命令
.commands {
CommandGroup(replacing: .newItem) {
Button("New Project") { /* ... */ }
.keyboardShortcut("n", modifiers: .command)
}
CommandMenu("Tools") {
Button("Run Analysis") { /* ... */ }
.keyboardShortcut("r", modifiers: [.command, .shift])
}
}
表格(macOS 原生)
Table(items, selection: $selectedIDs, sortOrder: $sortOrder) {
TableColumn("Name", value: \.name)
TableColumn("Date") { Text($0.date, format: .dateTime) }
.width(min: 100, ideal: 150)
}
.contextMenu(forSelectionType: Item.ID.self) { ids in
Button("Delete", role: .destructive) { delete(ids) }
}
有关表单、弹出框、工作表、检查器和 macOS 修饰符,请参阅 references/swiftui-macos.md。
@Observable
@Observable
final class AppState {
var projects: [Project] = []
var isLoading = false
func load() async throws {
isLoading = true
defer { isLoading = false }
projects = try await ProjectService.fetchAll()
}
}
// Use: @State var state = AppState() (owner)
// Pass: .environment(state) (inject)
// Read: @Environment(AppState.self) var state (child)
SwiftData
@Query 与 #Predicate
@Query(filter: #Predicate<Project> { !$0.isArchived }, sort: \Project.name)
private var active: [Project]
// Dynamic predicate
func search(_ term: String) -> Predicate<Project> {
#Predicate { $0.name.localizedStandardContains(term) }
}
// FetchDescriptor (outside views)
var desc = FetchDescriptor<Project>(predicate: #Predicate { $0.isArchived })
desc.fetchLimit = 50
let results = try context.fetch(desc)
let count = try context.fetchCount(desc)
关系
@Model final class Author {
var name: String
@Relationship(deleteRule: .cascade, inverse: \Book.author)
var books: [Book] = []
init(name: String) { self.name = name }
}
@Model final class Book {
var title: String
var author: Author?
@Relationship var tags: [Tag] = [] // many-to-many
init(title: String) { self.title = title }
}
删除规则:.cascade、.nullify(默认)、.deny、.noAction。
每个 @Model 类都需要显式初始化器——宏不会合成初始化器,省略时会报错:@Model requires an initializer be provided for '<Type>'。
架构迁移
enum SchemaV1: VersionedSchema { /* ... */ }
enum SchemaV2: VersionedSchema { /* ... */ }
enum MigrationPlan: SchemaMigrationPlan {
static var schemas: [any VersionedSchema.Type] { [SchemaV1.self, SchemaV2.self] }
static var stages: [MigrationStage] {
[.lightweight(fromVersion: SchemaV1.self, toVersion: SchemaV2.self)]
}
}
// Apply: .modelContainer(for: Model.self, migrationPlan: MigrationPlan.self)
CloudKit 同步
启用 iCloud 功能后,.modelContainer(for: Model.self) 会自动同步。限制:所有属性都必须有默认值或为可选属性;不支持唯一性约束;关系必须为可选关系。
有关模型属性、后台上下文、批量操作、撤销/重做和测试,请参阅下方的 SwiftData 参考资料。
并发(Swift 6.2+)
默认 MainActor 隔离
将整个模块设为主 actor 隔离——默认情况下,所有代码都在主 actor 上运行:
// Package.swift
.executableTarget(name: "MyApp", swiftSettings: [
.defaultIsolation(MainActor.self),
])
也可以在 Xcode 中设置:Build Settings > Swift Compiler > Default Isolation > MainActor。
@concurrent
将函数标记为在后台执行:
@concurrent
func processFile(_ url: URL) async throws -> Data {
let data = try Data(contentsOf: url)
return try compress(data) // runs off main actor
}
// After await, automatically back on main actor
let result = try await processFile(fileURL)
适用于 CPU 密集型工作、I/O,以及任何不涉及 UI 的任务。
Actors
actor DocumentStore {
private var docs: [UUID: Document] = [:]
func add(_ doc: Document) { docs[doc.id] = doc }
func get(_ id: UUID) -> Document? { docs[id] }
nonisolated let name: String
}
// Requires await: let doc = await store.get(id)
结构化并发
// Parallel with async let
func loadDashboard() async throws -> Dashboard {
async let profile = fetchProfile()
async let stats = fetchStats()
return try await Dashboard(profile: profile, stats: stats)
}
// Dynamic with TaskGroup
func processImages(_ urls: [URL]) async throws -> [NSImage] {
try await withThrowingTaskGroup(of: (Int, NSImage).self) { group in
for (i, url) in urls.enumerated() {
group.addTask { (i, try await loadImage(url)) }
}
var results = [(Int, NSImage)]()
for try await r in group { results.append(r) }
return results.sorted { $0.0 < $1.0 }.map(\.1)
}
}
Sendable
struct Point: Sendable { var x, y: Double } // value types: implicit
final class Config: Sendable { let apiURL: URL } // final + immutable
actor SharedState { var count = 0 } // mutable: use actors
// Enable strict mode: .swiftLanguageMode(.v6) in Package.swift
AsyncSequence 与 Observations
// Stream @Observable changes (macOS 26+ / iOS 26+, SE-0475)
// Observations uses a closure init, not Observations(of:).
let progresses = Observations { manager.progress }
for await p in progresses { print(p) }
// Typed NotificationCenter (macOS 26+)
struct DocSaved: NotificationCenter.MainActorMessage {
typealias Subject = Document
static var name: Notification.Name { .init("DocSaved") }
let id: UUID
}
NotificationCenter.default.post(DocSaved(id: document.id), subject: document)
let token = NotificationCenter.default.addObserver(of: document, for: DocSaved.self) { msg in
refresh(msg.id)
}
如需深入了解并发,请参阅下方的并发参考资料。
Foundation Models(macOS 26+)
设备端约 30 亿参数的大模型。免费、可离线使用且注重隐私:
import FoundationModels
let session = LanguageModelSession()
let response = try await session.respond(to: "Summarize: \(text)")
print(response.content) // respond() returns Response<Content>, not Content
// Structured output
@Generable struct Summary { var title: String; var points: [String] }
let result = try await session.respond(to: prompt, generating: Summary.self)
let summary: Summary = result.content
有关工具调用、流式传输和会话,请参阅 references/foundation-models.md。
测试
import Testing
@Suite("Project Tests")
struct ProjectTests {
@Test("creates with defaults")
func create() {
let p = Project(name: "Test")
#expect(p.name == "Test")
}
@Test("formats sizes", arguments: [(1024, "1 KB"), (0, "0 KB")])
func format(bytes: Int, expected: String) {
#expect(formatSize(bytes) == expected)
}
}
// SwiftData testing
let container = try ModelContainer(
for: Project.self,
configurations: ModelConfiguration(isStoredInMemoryOnly: true)
)
let ctx = ModelContext(container)
ctx.insert(Project(name: "Test"))
try ctx.save()
有关退出测试、附件和 UI 测试,请参阅 references/testing.md。
分发
| 方式 | 沙盒 | 公证 | 审核 |
|---|---|---|---|
| App Store | 必需 | 自动 | 是 |
| Developer ID | 建议 | 必需 | 否 |
| Ad-Hoc | 否 | 否 | 仅本地 |
xcodebuild archive -scheme MyApp -archivePath MyApp.xcarchive
xcodebuild -exportArchive -archivePath MyApp.xcarchive \
-exportPath ./export -exportOptionsPlist ExportOptions.plist
xcrun notarytool submit ./export/MyApp.dmg \
--apple-id [email protected] --team-id TEAM_ID \
--password @keychain:AC_PASSWORD --wait
xcrun stapler staple ./export/MyApp.dmg
完整的分发指南请参阅 references/distribution.md。
SPM
// swift-tools-version: 6.3
let package = Package(
name: "MyApp",
platforms: [.macOS(.v14)],
targets: [
.executableTarget(name: "MyApp", swiftSettings: [
.swiftLanguageMode(.v6),
.defaultIsolation(MainActor.self),
]),
.testTarget(name: "MyAppTests", dependencies: ["MyApp"]),
]
)
有关构建插件、宏和 Swift Build,请参阅 references/spm-build.md。
Liquid Glass(macOS 26)
使用 Xcode 26 SDK 重新构建的应用会自动应用 Liquid Glass 样式。使用 .glassEffect() 创建自定义玻璃表面,使用 GlassEffectContainer 创建自定义层级结构。选择退出(仅限 Xcode 26):在 Info.plist 中设置 UIDesignRequiresCompatibility = YES,即可保留旧版视觉样式,作为临时迁移辅助措施。使用 Xcode 27(Beta)重新构建的应用将无法再选择退出;该键会被忽略,且必须使用 Liquid Glass(请参阅 references/fall-2026-releases.md)。
ScreenCaptureKit
捕获屏幕内容、应用音频和麦克风(macOS 12.3+):
import ScreenCaptureKit
let content = try await SCShareableContent.excludingDesktopWindows(false, onScreenWindowsOnly: true)
guard let display = content.displays.first else { return }
// Filter: specific apps only
let filter = SCContentFilter(display: display, including: [targetApp], exceptingWindows: [])
// Configure
let config = SCStreamConfiguration()
config.capturesAudio = true
config.sampleRate = 48000
config.channelCount = 2
config.excludesCurrentProcessAudio = true
// Audio-only: throttle the video pipeline (it always runs). minimumFrameInterval is a
// MINIMUM gap between frames - 1/Int32.max is ~0s, i.e. native refresh rate (60+ fps
// of discarded frames and a full-screen recomposite each). Use 1 fps.
config.width = 2; config.height = 2
config.minimumFrameInterval = CMTime(value: 1, timescale: 1)
let stream = SCStream(filter: filter, configuration: config, delegate: self)
try stream.addStreamOutput(self, type: .screen, sampleHandlerQueue: nil)
try stream.addStreamOutput(self, type: .audio, sampleHandlerQueue: audioQueue)
try await stream.startCapture()
macOS 15+:使用 SCRecordingOutput 简化文件录制,使用 config.captureMicrophone 捕获麦克风。macOS 14+:使用 SCContentSharingPicker 显示系统选择器 UI,使用 SCScreenshotManager 捕获单帧。
完整的 API 参考、音频写入(AVAssetWriter/AVAudioFile)、权限和示例,请参阅 references/screen-capture-audio.md。
AppKit 互操作
struct WebViewWrapper: NSViewRepresentable {
let url: URL
func makeNSView(context: Context) -> WKWebView { WKWebView() }
func updateNSView(_ v: WKWebView, context: Context) {
v.load(URLRequest(url: url))
}
}
有关在 AppKit 中托管 SwiftUI 和高级桥接的信息,请参阅 references/appkit-interop.md。
架构
| 模式 | 最适用场景 | 复杂度 |
|---|---|---|
| SwiftUI + @Observable | 小型到中型、个人项目 | 低 |
| MVVM + @Observable | 中型、团队项目 | 中 |
| TCA | 大型、严格测试 | 高 |
所有模式及示例请参阅 references/architecture.md。
参考资料
| 文件 | 阅读时机 |
|---|---|
references/fall-2026-releases.md | WWDC 2026 beta 技术栈:macOS 27、Xcode 27、Swift 6.4、下一代 Foundation Models、Core AI、Spatial Preview、强制使用 Liquid Glass |
| SwiftUI 与 macOS | |
references/app-lifecycle.md | 窗口管理、场景、DocumentGroup、MenuBarExtra 注意事项、异步终止(以及从主队列终止导致卡住的问题)、LSUIElement 问题 |
references/swiftui-macos.md | 侧边栏、检查器、表格、表单、弹出框、工作表、搜索 |
references/appkit-interop.md | NSViewRepresentable、托管控制器、NSHostingSceneRepresentation、尺寸调整/场景桥接选项、AppKit Liquid Glass(NSGlassEffectView)、粘贴板隐私、NSPanel/浮动 HUD |
references/screen-capture-audio.md | ScreenCaptureKit、SCStream 注意事项、SCStream 拆除风险、AVAudioEngine 双管线、AVAssetWriter 崩溃安全、非交错立体声陷阱、TCC 注意事项、重新安装后 CDHash 降级状态、SpeechAnalyzer 转录 |
references/core-audio-tap.md | 按进程捕获音频的 CATap:仅 Tap 聚合设备(兼容 HFP)、漂移补偿、速率更改反模式、交错立体声帧数陷阱、IO proc 隔离 |
references/system-integration.md | 键盘快捷键、拖放、文件访问、App Intents(实体、Spotlight、摘要片段)、小组件与 Control Center、进程监控、AXUIElement、CoreAudio 按进程 API、登录项、XPC、LSUIElement、os.Logger 与 signpost、隐私用途说明 |
references/foundation-models.md | 设备端 AI:引导式生成、工具调用、流式处理 |
references/architecture.md | MVVM、TCA、依赖注入、项目结构 |
references/testing.md | Swift Testing、退出测试、附件、UI 测试、迁移 XCTest |
references/distribution.md | App Store、Developer ID、公证注意事项、嵌套 Bundle 签名、xcodebuild 签名陷阱(静默采用临时签名构建)、裸 CLI 二进制文件的稳定签名标识符、沙盒、通用二进制文件 |
references/spm-build.md | Package.swift、Swift Build、插件、宏、插件/宏验证关卡、Metal 工具链下载、手动组装 .app Bundle、混合 ObjC 目标、CLT 测试 |
| 并发 | |
references/approachable-concurrency.md | 默认 MainActor 隔离、@concurrent、nonisolated async、运行时陷阱 |
references/actors-isolation.md | Actor 模型、全局 Actor、自定义执行器、可重入性 |
references/structured-concurrency.md | Task、TaskGroup、async let、取消、优先级、命名任务 |
references/sendable-safety.md | Sendable 协议、数据竞争安全、@unchecked Sendable + 串行队列、@preconcurrency import |
references/async-patterns.md | AsyncSequence、AsyncStream、Observations、continuations、Clock |
references/migration-guide.md | 从 GCD 迁移到 async/await、从 Combine 迁移到 AsyncSequence、Swift 6 迁移 |
| SwiftData | |
references/models-schema.md | @Model、@Attribute 选项、Codable、可转换类型、外部存储 |
references/relationships-predicates.md | 高级关系、反向规则、复合谓词 |
references/container-context.md | ModelContainer、ModelContext、后台上下文、撤销/重做、批量操作 |
references/cloudkit-sync.md | CloudKit 设置、冲突解决、共享、同步调试 |
references/migrations.md | VersionedSchema、轻量级/自定义迁移、Core Data 迁移 |