用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
直接命令不会经过审查 Prompt;运行前请先检查来源。
npx skills add https://github.com/tomevault-io/skills-registry --skill swift-strict命令会保持在同一行。复制前请横向滚动并检查完整内容。
想先保存到本地?可下载 SkillsMP 当前能够提供的文件。
| Use when this capability is needed.
> Use when this capability is needed.
Review architecture and API design for the vfs-s3 project. Use when the user mentions @architect, asks to review an issue's design, discuss module boundaries, API shape, or architectural decisions for vfs-s3. Also trigger when the user wants to create an ADR (Architecture Decision Record) or evaluate a technical approach for the project. Intended for dispatch from Codex automation or Claude routines; GitHub trigger phrase: @vfs-s3-bot please prepare design doc Use when this capability is needed.
基于 SOC 职业分类
正在显示 SKILL.md
| name | swift-strict |
| description | > Use when this capability is needed. |
Rules extracted from 3 production SwiftUI iOS apps.
// BAD
let name = user!.name
let url = URL(string: urlString)!
let day = calendar.date(byAdding: .day, value: -1, to: date)!
// GOOD
guard let name = user?.name else { return }
guard let url = URL(string: urlString) else {
throw AppError.invalidURL(urlString)
}
guard let day = calendar.date(byAdding: .day, value: -1, to: date) else { return }
Exceptions (very rare, must justify):
fatalError() in required init?(coder:) for programmatic-only views@IBOutlet connections (but prefer programmatic UI)guard let for early returns, if let for scoped binding// GOOD: guard for early exit (preferred pattern)
func listNotes(in folder: String) -> [Note] {
guard let root = cloud.stikRoot else { return [] }
guard let files = try? fm.contentsOfDirectory(at: root) else { return [] }
return files.filter { ... }
}
// GOOD: if let when value only needed in branch
if let fileName = removed.photoFilename {
let url = Self.photosDir.appendingPathComponent(fileName)
try? fm.removeItem(at: url)
}
Default to guard. Use if-let only when the value is needed for one branch.
try? for critical operations// BAD: silently swallows error
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
// GOOD: handle or propagate
do {
try FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
} catch {
logger.error("Failed to create directory: \(error)")
throw AppError.fileSystemError(error)
}
// ACCEPTABLE: truly optional operations
try? fm.removeItem(at: tempFile) // Cleanup, failure is OK
enum AppError: LocalizedError {
case invalidFolderName
case containerUnavailable
case networkUnavailable
case unauthorized
case serverError(Int)
var errorDescription: String? {
switch self {
case .invalidFolderName: String(localized: "error_invalid_folder")
case .containerUnavailable: String(localized: "error_container")
case .networkUnavailable: String(localized: "error_network")
case .unauthorized: String(localized: "error_unauthorized")
case .serverError(let code): String(localized: "error_server \(code)")
}
}
}
func handleAppleSignIn(result: Result<ASAuthorization, Error>) async {
switch result {
case .success(let auth):
await processAppleCredential(auth)
case .failure(let error):
lastError = .unknown(error)
}
}
// BAD: old pattern
class ViewModel: ObservableObject {
@Published var items: [Item] = []
@Published var isLoading = false
}
// Requires @StateObject in view
// GOOD: modern pattern
@Observable
final class ViewModel {
var items: [Item] = []
var isLoading = false
}
// Uses @State in view (simpler)
@ObservationIgnored for non-reactive internals:
@Observable final class EditorViewModel {
var content: String = ""
@ObservationIgnored
private var autosaveTask: Task<Void, Never>?
}
struct HomeView: View {
@State private var viewModel = HomeViewModel() // View owns it
@Environment(\.dismiss) private var dismiss // System injection
var body: some View { ... }
}
Never use @StateObject in new code targeting iOS 17+.
@Observable final class SavedStore {
static let shared = SavedStore()
private(set) var savedQuoteIds: [Int] = [] // Read-only externally
private(set) var folders: [SavedFolder] = []
private init() { load() }
func addToSaved(_ id: Int) {
savedQuoteIds.append(id)
persist()
}
}
@Observable
@MainActor
final class AuthController {
private(set) var isAuthenticated = false
private(set) var lastError: AuthError?
func signIn() async {
// Already on MainActor: no dispatch needed
isLoading = true
defer { isLoading = false }
// ...
}
}
actor APIClient {
static let shared = APIClient()
private let session = URLSession.shared
func fetch<T: Decodable>(_ endpoint: String) async throws -> T {
let (data, _) = try await session.data(from: url)
return try JSONDecoder().decode(T.self, from: data)
}
}
// BAD: function is already @MainActor
@MainActor func update() async {
await MainActor.run { isLoading = true } // REDUNDANT
}
// GOOD
@MainActor func update() async {
isLoading = true // Already on MainActor
}
private func scheduleAutosave() {
autosaveTask?.cancel()
autosaveTask = Task { @MainActor [weak self] in
try? await Task.sleep(for: .seconds(1.5))
guard !Task.isCancelled else { return }
self?.saveIfDirty()
}
}
Always:
Task.isCancelled after sleep/await[weak self] to prevent retain cycles@Observable final class EditorViewModel {
var content: String = "" // Internal: views in same module need it
private(set) var isDirty = false // Readable externally, writable internally only
private var autosaveTask: Task<Void, Never>? // Implementation detail
private func scheduleAutosave() { ... } // Internal helper
func save() throws { ... } // Public API
}
Rules:
private for implementation details (helpers, tasks, caches)private(set) for state that views read but shouldn't mutatepublic only for framework/library APIsfinal on classes by default// GOOD: prevents unintended subclassing
@Observable final class HomeViewModel { ... }
// Only omit final when subclassing is intentional
class BaseViewController: UIViewController { ... }
// BAD: potential retain cycle
NotificationCenter.default.addObserver(forName: .didChange, object: nil, queue: .main) { _ in
self.reload() // Strong capture
}
// GOOD
NotificationCenter.default.addObserver(forName: .didChange, object: nil, queue: .main) { [weak self] _ in
self?.reload()
}
// GOOD: auto-cancelled on disappear
List(viewModel.items) { item in ItemRow(item: item) }
.task { await viewModel.loadItems() }
// BAD: manual Task management in onAppear
.onAppear {
Task { await viewModel.loadItems() } // Not cancelled on disappear!
}
is, has, can, should prefixvar isLoading = false
var isDirty = false
var isSynced = true
var hasCompletedOnboarding = false
var canEdit: Bool { ... }
// Methods
func save() throws { ... }
func loadItems() async { ... }
func handleAppleSignIn(result:) async { ... }
// Properties
var folders: [String] = []
var selectedFolder: String?
var errorMessage: String?
.task { } modifier, not onAppear + Task// BAD
.onAppear { Task { await vm.load() } }
// GOOD
.task { await vm.load() }
// BAD
UserDefaults.standard.set(apiKey, forKey: "api_key")
// GOOD: Keychain
try keychain.set(apiKey, key: "api_key")
// ACCEPTABLE: non-sensitive preferences only
@AppStorage("hasCompletedOnboarding") private var hasCompletedOnboarding = false
guard let url = URL(string: input), url.scheme == "https" else {
throw AppError.invalidURL(input)
}
Never add NSAllowsArbitraryLoads to Info.plist unless connecting to local dev servers.
Swift 6 strict concurrency requires types crossing actor boundaries to be Sendable.
// Value types: derive automatically
struct UserSession: Sendable {
let id: UUID
let token: String
}
// Reference types: declare and enforce immutability
final class Config: Sendable {
let endpoint: URL
let timeout: TimeInterval
init(endpoint: URL, timeout: TimeInterval) {
self.endpoint = endpoint
self.timeout = timeout
}
}
// When mutation is needed, use an actor
actor TokenStore {
private var tokens: [String: Token] = [:]
func get(_ key: String) -> Token? { tokens[key] }
}
@unchecked Sendable only with a written justification (e.g. internal lock).
some P for return position, any P for storage// GOOD: opaque, no boxing, generic specialization
func makeView() -> some View { Text("hello") }
// GOOD: existential, needed for heterogeneous storage
var providers: [any AuthProvider] = []
// BAD: existential where opaque would do (boxes for nothing)
func makeView() -> any View { Text("hello") }
Default to some. Reach for any only when the concrete type must vary at runtime.
Add PrivacyInfo.xcprivacy listing tracking domains, required reason APIs, and collected data types. Apple rejects submissions missing required-reason API declarations (UserDefaults, FileTimestamp, SystemBootTime, DiskSpace).
// BAD: untyped, callers must handle any Error
func parse(_ data: Data) throws -> Config { ... }
// GOOD: typed, exhaustive at the call site
enum ConfigError: Error {
case invalidJSON
case missingField(String)
case versionMismatch(found: Int, expected: Int)
}
func parse(_ data: Data) throws(ConfigError) -> Config { ... }
do {
let cfg = try parse(data)
} catch .missingField(let f) {
// Compiler knows the cases, no fallback needed
}
Reach for typed throws on library boundaries and parsers. Stay untyped at top-level UI handlers where any error can bubble up.
// Package.swift
.target(
name: "MyLib",
swiftSettings: [
.swiftLanguageMode(.v6),
.enableUpcomingFeature("StrictConcurrency"),
.unsafeFlags(["-warnings-as-errors"], .when(configuration: .release)),
]
)
Swift 6 language mode + StrictConcurrency catches data races at compile time. New code starts here.
Swift 6.2 (released 2025) is the current stable. Highlights worth adopting:
InlineArray<N, T>: stack-allocated fixed-size arrays, no heap allocation, compile-time bounds. Use for small fixed buffers (RGB triples, fixed-width tokens, register sets).nonisolated synchronous code on the calling actor by default. Reduces incidental awaits without giving up data-race safety.!) in production codetry? on critical operations (file creation, auth, data save)@Observable + final@MainActor or on MainActor.run.task { } work checks cancellationNSAllowsArbitraryLoads in Info.plistSource: 0xMassi/claude-skills — distributed by TomeVault.