mcpField
S
swift-language

swift-language

github.com/dpearson2699开发1.2k4.0k 次安装13 周前更新
代码仓库

关于 swift-language

面向 Swift 开发者的语言技能,用于代码现代化与审查。内容涵盖新语法和常见模式,并强调保持行为与求值顺序;专门主题则交由相关技能处理。

主要功能

  1. 01if 与 switch 表达式
  2. 02类型化 throws 与结果构建器
  3. 03属性包装器与类型选择
  4. 04guard 模式与 Never 语义
  5. 05正则表达式构建器与集合 API
  6. 06现代化审查清单

适用场景

  1. 01在不改变行为的前提下改写条件逻辑
  2. 02选择 some 与 any 类型
  3. 03检查 Swift 代码中的常见问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/dpearson2699/swift-ios-skills --skill swift-language

安装后在 Claude Code 中输入/swift-language确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

evals/
evals.json
references/
swift-attributes-interop.md
swift-patterns-extended.md

详细介绍

Swift 语言模式

应用当前的 Swift 语言语法,同时不改变行为或求值顺序。 将复杂解码交给 swift-codable,格式化交给 swift-formatstyle,命名交给 swift-api-design-guidelines,并发交给 swift-concurrency,SwiftUI 状态和视图工作交给 swiftui-patterns。

目录

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。

常见错误

  1. 能用 some 时却使用 any。 返回类型和参数默认使用 some,但每个 -> some P 分支都必须返回相同的具体类型。
  2. 使用手动循环或 .filter { }.count,而不是集合 API。 条件计数使用 count(where:),并使用 contains(where:)、compactMap 和 flatMap,避免额外迭代或创建数组。
  3. 使用 DateFormatter,而不是 FormatStyle。 .formatted() 更简单、类型安全,并且会自动处理本地化。
  4. 对 Codable 解码结果强制解包。 对可选或缺失的键使用 decodeIfPresent 并提供默认值。
  5. 现代化时重新排列前置条件。 使用 guard,不要在验证前移动规范化或转换操作。
  6. @c 签名无效。 说明 UnsafeBufferPointer 是 Swift 结构体/值包装器,然后排除 String、Array、闭包和泛型占位符。
  7. 忽略类型化 throws。 当函数只有一种明确的错误类型时,类型化 throws 可让调用方进行穷尽式 switch,而无需强制转换。
  8. 过度使用属性包装器。 如果不需要复用或投影值,计算属性更简单。
  9. 对 Never 的说明不够充分。 对于 Result<T, Never> 或 throws(Never),明确说明注意事项:Never 不会隐式遵循任意协议,无法满足任意 T: P 约束,并且仅在有效的表达式/推断上下文中类似底类型。
  10. 负责实现同级技能的内容。 指明负责的技能并停止。避免提供 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 转交给同级技能

参考资料

评论

支持 Markdown