关于 swift-language
面向 Swift 开发者的语言技能,用于代码现代化与审查。内容涵盖新语法和常见模式,并强调保持行为与求值顺序;专门主题则交由相关技能处理。
主要功能
- 01if 与 switch 表达式
- 02类型化 throws 与结果构建器
- 03属性包装器与类型选择
- 04guard 模式与 Never 语义
- 05正则表达式构建器与集合 API
- 06现代化审查清单
适用场景
- 01在不改变行为的前提下改写条件逻辑
- 02选择 some 与 any 类型
- 03检查 Swift 代码中的常见问题
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ dpearson2699/ swift-ios-skills --skill swift-language安装后在 Claude Code 中输入/swift-language确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
evals/evals.jsonreferences/swift-attributes-interop.mdswift-patterns-extended.md详细介绍
Swift 语言模式
应用当前的 Swift 语言语法,同时不改变行为或求值顺序。
将复杂解码交给 swift-codable,格式化交给 swift-formatstyle,命名交给 swift-api-design-guidelines,并发交给 swift-concurrency,SwiftUI 状态和视图工作交给 swiftui-patterns。
目录
- If/Switch 表达式
- 类型化抛错
- 结果构建器
- 属性包装器
- 不透明类型和存在类型
- Guard 模式
- Never 类型
- 正则表达式构建器
- Codable 最佳实践
- 现代集合 API
- FormatStyle
- 字符串插值
- 常见错误
- 审查清单
- 参考资料
If/Switch 表达式
进行现代化改造时,先固定当前行为和求值顺序,进行一次语义重写,编译受影响的模块,并运行针对性测试夹具/测试。发现任何变化都先修复,再继续;重复此过程,直到行为得到保留。
Swift 5.9+ 允许将 if 和 switch 用作返回值的表达式。可直接用它们赋值、返回值或初始化。
// Assign from if expression
let icon = if isComplete { "checkmark.circle.fill" } else { "circle" }
// Assign from switch expression
let label = switch status {
case .draft: "Draft"
case .published: "Published"
case .archived: "Archived"
}
// Works in return position
func badgeText(for priority: Priority) -> String {
switch priority {
case .high: "High"
case .medium: "Medium"
case .low: "Low"
}
}
规则:
- 每个分支都必须产生相同类型的值。
- 不允许多语句分支——每个分支只能是一个表达式。
- 用作函数参数时,请用括号括起来以避免歧义。
类型化抛错
Swift 6+ 允许指定函数抛出的错误类型。
enum ValidationError: Error {
case tooShort, invalidCharacters, alreadyTaken
}
func validate(username: String) throws(ValidationError) -> String {
guard username.count >= 3 else { throw .tooShort }
guard username.allSatisfy(\.isLetterOrDigit) else { throw .invalidCharacters }
return username.lowercased()
}
// Caller gets typed error -- no cast needed
do {
let name = try validate(username: input)
} catch {
// error is ValidationError, not any Error
switch error {
case .tooShort: print("Too short")
case .invalidCharacters: print("Invalid characters")
case .alreadyTaken: print("Taken")
}
}
规则:
- 只有在调用方能从穷尽式错误处理中受益时,才使用
throws(SomeError)。对于错误来源混杂的情况,请使用非类型化的throws。 - 将仅包含一个本地错误枚举的辅助函数现代化时,优先使用
throws(ErrorEnum),并注明 Swift 6+。 throws(Never)表示函数在语法上会抛错,但实际上永远不会抛错——这在泛型上下文中很有用。- 类型化抛错会传播:调用
throws(A)和throws(B)的函数自身必须抛出涵盖这两种类型的类型(或使用非类型化的throws)。
结果构建器
@resultBuilder 支持 DSL 风格语法。SwiftUI 的 @ViewBuilder 是最常见的示例,但你也可以为任何领域创建自定义构建器。
@resultBuilder
struct ArrayBuilder<Element> {
static func buildBlock(_ components: [Element]...) -> [Element] {
components.flatMap { $0 }
}
static func buildExpression(_ expression: Element) -> [Element] { [expression] }
static func buildOptional(_ component: [Element]?) -> [Element] { component ?? [] }
static func buildEither(first component: [Element]) -> [Element] { component }
static func buildEither(second component: [Element]) -> [Element] { component }
static func buildArray(_ components: [[Element]]) -> [Element] { components.flatMap { $0 } }
}
func makeItems(@ArrayBuilder<String> content: () -> [String]) -> [String] { content() }
let items = makeItems {
"Always included"
if showExtra { "Conditional" }
for name in names { name.uppercased() }
}
构建器方法: buildBlock(组合语句)、buildExpression(单个值)、buildOptional(不带 else 的 if)、buildEither(if/else)、buildArray(for..in)、buildFinalResult(可选的后处理)。
属性包装器
自定义的 @propertyWrapper 类型可封装存储和访问模式。
@propertyWrapper
struct Clamped<Value: Comparable> {
private var value: Value
let range: ClosedRange<Value>
var wrappedValue: Value {
get { value }
set { value = min(max(newValue, range.lowerBound), range.upperBound) }
}
var projectedValue: ClosedRange<Value> { range }
init(wrappedValue: Value, _ range: ClosedRange<Value>) {
self.range = range
self.value = min(max(wrappedValue, range.lowerBound), range.upperBound)
}
}
// Usage
struct Volume {
@Clamped(0...100) var level: Int = 50
}
var v = Volume()
v.level = 150 // clamped to 100
print(v.$level) // projected value: 0...100
设计规则:
wrappedValue是主要的 getter/setter。projectedValue(通过$property访问)提供元数据或绑定。- 属性包装器可以组合:
@A @B var x会先应用外层包装器。 - 简单的计算属性足以满足需求时,不要使用属性包装器。
不透明类型和存在类型
some Protocol(不透明类型)
调用方不知道具体类型,但编译器知道。-> some P 返回的类型在所有返回分支中都具有同一个固定的底层具体类型。
func makeCollection() -> some Collection<Int> {
[1, 2, 3] // Always returns Array<Int> -- compiler knows the concrete type
}
在以下情况下使用 some:
- 作为返回类型,以隐藏实现细节,同时保留类型标识。
- 作为参数类型(Swift 5.7+):
some P是未命名泛型参数的简写形式,例如<T: P>。
any Protocol(存在类型)
一种存在类型盒子,可在运行时容纳任何符合协议的类型。它使用动态派发;如果值无法放入内联缓冲区,可能会进行内存分配。
func process(items: [any StringProtocol]) {
for item in items {
print(item.uppercased())
}
}
何时选择
使用 some | 使用 any |
|---|---|
| 隐藏具体类型的返回类型 | 异构集合 |
| 函数参数(替代简单泛型) | 需要动态类型擦除 |
| 性能更好(静态分发) | 要擦除的协议具有 Self 或关联类型要求 |
经验法则: 默认使用 some。只有在需要异构集合或运行时类型灵活性时,才使用 any。
Guard 模式
guard 用于强制执行前置条件并支持提前退出。它能让正常执行路径保持左对齐,并减少嵌套。
func processOrder(_ order: Order?) throws -> Receipt {
// Unwrap optionals
guard let order else { throw OrderError.missing }
// Validate conditions
guard order.items.isEmpty == false else { throw OrderError.empty }
guard order.total > 0 else { throw OrderError.invalidTotal }
// Boolean checks
guard order.isPaid else { throw OrderError.unpaid }
// Pattern matching
guard case .confirmed(let date) = order.status else {
throw OrderError.notConfirmed
}
return Receipt(order: order, confirmedAt: date)
}
最佳实践:
- 前置条件使用
guard,分支逻辑使用if。 - 合并相关的守卫条件:
guard let a, let b else { return }。 else代码块必须退出作用域:使用return、throw、continue、break或fatalError()。- 使用简写解包:
guard let value else { ... }(Swift 5.7+)。
Never 类型
Never 是一种无法构造值的类型,用于永远不会产生值的代码路径。它只会在可以使用或推断值表达式的场景中表现得像 Swift 的底类型;它不是通用的类型见证,不会隐式遵循任意协议,也无法满足 T: P 之类的泛型约束,除非该约束本身对 Never 有效。
// Function that terminates the program
func crashWithDiagnostics(_ message: String) -> Never {
let diagnostics = gatherDiagnostics()
logger.critical("\(message): \(diagnostics)")
fatalError(message)
}
enum Result<Success, Failure: Error> {
case success(Success)
case failure(Failure)
}
// Result<String, Never> -- a result that can never fail
// Exhaustive switch: no default needed since Never has no cases
func handle(_ result: Result<String, Never>) {
switch result {
case .success(let value): print(value)
// No .failure case needed -- compiler knows it's impossible
}
}
正则表达式构建器
Swift 5.7+ 的 Regex 构建器 DSL 可提供编译时检查、易读的模式。
import Foundation
import RegexBuilder
// Parse "2024-03-15" into components
let dateRegex = Regex {
Capture { /\d{4}/ }; "-"; Capture { /\d{2}/ }; "-"; Capture { /\d{2}/ }
}
if let match = "2024-03-15".firstMatch(of: dateRegex) {
let (_, year, month, day) = match.output
_ = (year, month, day)
}
// TryCapture with transform
let priceRegex = Regex {
"$"
TryCapture { OneOrMore(.digit); "."; Repeat(.digit, count: 2) }
transform: { Decimal(string: String($0)) }
}
何时使用构建器而非字面量:
- 构建器:复杂模式、可复用组件、捕获值的强类型。
- 字面量(
/pattern/):简单模式、熟悉正则表达式语法。 - 两者可以混用:在构建器代码块中嵌入
/.../字面量。
Codable 最佳实践
简单重命名使用 CodingKeys,仅在载荷形状或转换确实不匹配时才自定义解码。阅读扩展 Swift 模式,查看简明的语言示例;使用 swift-codable 进行实现和验证。
现代集合 API
优先使用这些现代 API,而不是手动循环:
let numbers = [1, 2, 3, 4, 5, 6, 7, 8]
// count(where:) -- use instead of .filter { }.count
let evenCount = numbers.count(where: { $0.isMultiple(of: 2) })
// contains(where:) -- short-circuits on first match
let hasNegative = numbers.contains(where: { $0 < 0 })
// first(where:) / last(where:)
let firstEven = numbers.first(where: { $0.isMultiple(of: 2) })
// String replacing() -- Swift 5.7+, returns new string
let cleaned = rawText.replacing(/\s+/, with: " ")
let snakeCase = name.replacing("_", with: " ")
// compactMap -- unwrap optionals from a transform
let ids = strings.compactMap { Int($0) }
// flatMap -- flatten nested collections
let allTags = articles.flatMap(\.tags)
// Dictionary(grouping:by:)
let byCategory = Dictionary(grouping: items, by: \.category)
// reduce(into:) -- efficient accumulation
let freq = words.reduce(into: [:]) { counts, word in
counts[word, default: 0] += 1
}
FormatStyle
基础显示使用 .formatted() 和 Text(_:format:)。将样式选择、解析、本地化测试和可复用格式化器设计交由 swift-formatstyle 处理。
字符串插值
扩展 DefaultStringInterpolation,以支持特定领域的格式化。多行字符串使用 """(缩进相对于闭合的 """)。自定义插值示例请参阅 references/swift-patterns-extended.md。
常见错误
- 能用
some时却使用any。 返回类型和参数默认使用some,但每个-> some P分支都必须返回相同的具体类型。 - 使用手动循环或
.filter { }.count,而不是集合 API。 条件计数使用count(where:),并使用contains(where:)、compactMap和flatMap,避免额外迭代或创建数组。 - 使用
DateFormatter,而不是 FormatStyle。.formatted()更简单、类型安全,并且会自动处理本地化。 - 对 Codable 解码结果强制解包。 对可选或缺失的键使用
decodeIfPresent并提供默认值。 - 现代化时重新排列前置条件。 使用
guard,不要在验证前移动规范化或转换操作。 @c签名无效。 说明UnsafeBufferPointer是 Swift 结构体/值包装器,然后排除String、Array、闭包和泛型占位符。- 忽略类型化 throws。 当函数只有一种明确的错误类型时,类型化 throws 可让调用方进行穷尽式 switch,而无需强制转换。
- 过度使用属性包装器。 如果不需要复用或投影值,计算属性更简单。
- 对
Never的说明不够充分。 对于Result<T, Never>或throws(Never),明确说明注意事项:Never 不会隐式遵循任意协议,无法满足任意T: P约束,并且仅在有效的表达式/推断上下文中类似底类型。 - 负责实现同级技能的内容。 指明负责的技能并停止。避免提供
CodingKeys、解码器、格式化器、SwiftUI 或并发的代码片段。
审查清单
- 仅当每个不透明返回值分支都具有一种具体类型时,才使用
some - 前置条件使用
guard;使用count(where:),而非手动计数或.filter { }.count - 使用
.formatted(),而非DateFormatter/NumberFormatter - Codable 类型使用
CodingKeys映射 API;可选字段使用带默认值的decodeIfPresent - 条件赋值使用 if/switch 表达式;属性包装器有明确的复用理由
- 复杂模式使用 Regex builder(简单模式可使用字面量)
- 单一的本地错误域使用类型化 throws,并注明 Swift 6+ 兼容性
-
@c修正将UnsafeBufferPointer称为 Swift 结构体/值包装器,并按名称列出不支持的仅限 Swift 类型 -
Never说明使用“无值可构造”和“类似底类型”,并指出其不会隐式遵循任意协议或满足任意泛型约束 - 深入的 Codable 内容转交给
swift-codable;FormatStyle API 转交给swift-formatstyle;市场/本地化显示 QA 转交给ios-localization;命名/并发/SwiftUI 转交给同级技能
参考资料
- 扩展模式和 Codable 示例:references/swift-patterns-extended.md
- 特性和 C 互操作:references/swift-attributes-interop.md