swift について
Swift 開発者向けのプログラミングスキルで、設定、ログ、テレメトリー、テスト、デバッグ、言語の実践方法を案内します。テーマ別のリファレンスを使い、テスト移行やクロスプラットフォーム実装などに対応します。
主な機能
- 01設定とシークレット管理の指針
- 02Swift Log による構造化ログの実践
- 03OpenTelemetry の設定とエクスポート
- 04Swift Testing の手法と XCTest 移行
- 05メモリー安全性とプラットフォーム別の規約
活用シーン
- 01設定とシークレット処理の導入
- 02サーバーアプリへのログや OpenTelemetry の追加
- 03Swift テストの作成や XCTest からの移行
- 04クロスプラットフォームとメモリー安全性の確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ wendylabsinc/ claude-skills --skill swiftインストール後、Claude Code で/swiftを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/debugging.mdswift-configuration.mdswift-log.mdswift-otel.mdswift-testing.md詳細
Swift
Swift はモダンな汎用プログラミング言語です。
参照ファイル
特定のトピックについては、必要に応じて以下のファイルを読み込んでください。
references/swift-configuration.md- Swift の設定:環境変数、ファイル、CLI 引数からの設定の読み込み、プロバイダーの階層、名前空間、ホットリロード、シークレットの取り扱いreferences/swift-log.md- Swift Log のロギング API:ログレベル、構造化ロギング、ライブラリのベストプラクティス、メタデータ、カスタムハンドラーreferences/swift-otel.md- Swift OTel:サーバーアプリ向けの OpenTelemetry バックエンド(Linux では推奨)、ログ、メトリクス、トレースの OTLP エクスポート、フレームワークとの統合references/swift-testing.md- Swift Testing フレームワーク:@Test マクロ、#expect/#require アサーション、特性、パラメーター化テスト、テストスイート、並列実行、XCTest からの移行references/debugging.md- デバッグのヒント:Linux のターミナル UI(代替画面バッファー)、GitHub Actions のログ分析
Swift Testing のテスト名
テストの説明が関数名に含まれるように、バッククォートで囲んだ文章形式のテスト関数名を優先してください。
@Test
func `does something very special in a certain edge case`() async throws {
// ...
}
個別の表示名が特に必要な場合にのみ、@Test("...") の表示名を使用してください。
アクセス修飾子
外部から使用するために公開する必要がない限り、型と関数は internal のままにしてください。これにより、実装の詳細を誤って公開するのを防ぎ、アクセスレベルのエラーも修正しやすくなります。
メンバーの整理
メンバーは、次の順序で可視性ごとに並べてください。
- Public
- Internal
- Private
Public メンバーは先頭に配置し、Apple API リファレンスのグループのように読める // MARK: - ... セクションを使ってトピックごとにまとめてください。各 // MARK: - ... 見出しの上下には空行を入れてください。次のように、機能を表す現在進行形の動名詞句を優先してください。
// MARK: - Working with Child Items
public func addChild(_ child: Child) { ... }
// MARK: - Managing Life-Cycle
public func start() async throws { ... }
非公開セクションには可視性マーカーを使用してください。
// MARK: - Internal
internal func prepareStorage() { ... }
// MARK: - Private
private func rebuildIndex() { ... }
internal または private のヘルパーを public のトピックセクションに混在させないでください。Public API に明確なトピックが 1 つしかない場合は、internal/private セクションの前に // MARK: - <Topic> を置く必要はありません。
プロトコル準拠の整理
プロトコルの意味に基づいて、準拠を記述する場所を選んでください。
- プロトコルが 「〜である」関係を表す場合は、型宣言に準拠をインラインで記述してください。
- 例:
public enum BlahError: Error { ... }
- 例:
- プロトコルが型の能力を表し、通常
-ableで終わる場合は、そのプロトコルと同じファイルの末尾にある extension に準拠を記述してください。- extension の直前に
// MARK: - <ProtocolName>見出しを置いてください。
- extension の直前に
public struct Blah { ... }
// MARK: - Codable
extension Blah: Codable {
// Codable-specific implementation
}
Foundation を避ける方針
可能な限り、コアライブラリのコードでは Foundation を避けてください。
- 次を対象とするライブラリの公開 API では、Foundation の型(
Data、Date、UUIDなど)を避けてください。- Embedded Swift
- クロスプラットフォームでの一貫性
- バイナリサイズの削減(FoundationEssentials は 15~40MB)
- 代わりに Swift 標準ライブラリの型を使用してください。
- バイトバッファーには
Dataではなく[UInt8] DateではなくContinuousClock.Instantまたはカスタム型UUID文字列ではなく、バイトベースのイニシャライザー
- バイトバッファーには
- 常に
internal import Foundationまたはinternal import FoundationEssentialsを使用し、public importは使用しないでください。
#if canImport(FoundationEssentials)
internal import FoundationEssentials
#else
internal import Foundation
#endif
InternalImportsByDefault 機能
Package.swift で InternalImportsByDefault を使用すると、明示的に public import と指定しない限り、すべての import はデフォルトで internal になります。
public import を使用する場合:
- インポートしたモジュールの型が公開 API(戻り値の型、パラメーター、プロトコル準拠)に公開される場合
- 例:公開型が
ServiceLifecycle.Serviceに準拠する場合はpublic import ServiceLifecycle public import Foundationは絶対に使用しないでください。Foundation は internal に保ってください。
プラットフォーム固有のファイル構成
プラットフォーム固有の実装には、+platform サフィックスの規則を使用します。
PlatformDeviceDiscovery+macos.swift- macOS の実装PlatformDeviceDiscovery+linux.swift- Linux の実装PlatformDeviceDiscovery+default.swift- その他のプラットフォーム向けのフォールバック
プロトコルにメソッドを追加する場合は、適合性を維持するためにすべてのプラットフォームファイルを更新する必要があります。
Linux C ライブラリのサポート
Linux との互換性のため、Glibc と Musl の両方をサポートします。
#if os(macOS) || os(iOS) || os(tvOS) || os(watchOS)
import Darwin
#elseif canImport(Glibc)
import Glibc
#elseif canImport(Musl)
import Musl
#endif
選択ロジックでのコードの繰り返しを避ける
優先順位を付けて複数の選択肢から選ぶ場合は、複数の条件ブロックではなく、ソートを使用します。
悪い例 - 繰り返しが多い例:
if !preferBluetooth {
for interface in interfaces {
if case .lan(let device) = interface {
return .lan(device)
}
}
}
for interface in interfaces {
if case .bluetooth(let device) = interface {
return .bluetooth(device)
}
}
if preferBluetooth {
for interface in interfaces {
if case .lan(let device) = interface {
return .lan(device)
}
}
}
良い例 - 一度だけソートし、一度だけ反復する例:
let sorted = interfaces.sorted { a, b in
if preferBluetooth {
return a.type == "Bluetooth" && b.type != "Bluetooth"
} else {
return a.type == "LAN" && b.type != "LAN"
}
}
for interface in sorted {
switch interface {
case .lan(let device): return .lan(device)
case .bluetooth(let device): return .bluetooth(device)
default: continue
}
}
メモリ安全性のパターン(Swift 6.2 以降)
Swift 6.2 では、Package.swift の .strictMemorySafety() によるオプトイン方式の厳密なメモリ安全性チェックが導入されています。
Span のライフタイム制約:
Span<T>はライフタイムに依存します。バッキングストレージのメモリを借用します- 非同期境界を越えられません
- クロージャのスコープから外に出せません
- 非同期コールバックに渡せません
解決策:非対称な API 設計 Span はパース用(読み取り専用、同期、借用)に、[UInt8] は書き込み用(所有され、境界を越えられる)に使用します。
public struct Characteristic<Value: Sendable>: Sendable {
// Parsing uses Span - borrowed, synchronous access
internal let parse: @Sendable (borrowing Span<UInt8>) throws -> Value
// Writing uses [UInt8] - owned, can cross closure boundaries
public typealias WithBytes = ([UInt8]) -> Void
internal let write: @Sendable (Value) -> (WithBytes) -> Void
}
バイトから安全に整数を読み込む:
// UNSAFE: unsafeLoad
return span.bytes.unsafeLoad(as: UInt64.self)
// SAFE: Manual byte-by-byte assembly
var value: UInt64 = 0
for i in 0..<8 {
value |= UInt64(span[i]) << (i * 8)
}
return value
_read/_modify を使った Span ベースの計算プロパティ
LifetimeDependence の実験的機能を使うと、計算プロパティは _read と _modify のアクセサーを使用して、RawSpan や MutableRawSpan などのエスケープできない型を返せます。
// Enable in Package.swift:
swiftSettings: [
.enableExperimentalFeature("LifetimeDependence"),
]
// Read-only span access
public var bytes: RawSpan {
_read {
var mapInfo = GstMapInfo()
guard mapBuffer(&mapInfo) else { fatalError("Failed to map") }
defer { unmapBuffer(&mapInfo) }
yield RawSpan(_unsafeStart: mapInfo.data, byteCount: Int(mapInfo.size))
}
}
// Mutable span access with Copy-on-Write
public var mutableBytes: MutableRawSpan {
_read {
fatalError("Cannot read mutableBytes")
}
_modify {
// Ensure unique ownership before write (CoW)
if !isKnownUniquelyReferenced(&storage) {
storage = storage.copy()!
}
var mapInfo = GstMapInfo()
guard mapBuffer(&mapInfo) else { fatalError("Failed to map") }
defer { unmapBuffer(&mapInfo) }
var span = MutableRawSpan(_unsafeStart: mapInfo.data, byteCount: Int(mapInfo.size))
yield &span
}
}
既知のコンパイラーの問題(Swift 6.2.3): 特定のコンテキストで span.withUnsafeBytes を使用すると、LifetimeDependenceScopeFixup パスがクラッシュすることがあります。回避策:Span アクセサーを経由しない、C との相互運用向けのクロージャベースのメソッドを別途用意します。
非コピー可能な型
複製すべきではないムーブ専用の型には、~Copyable を使用します。
public struct ResourceHandle: ~Copyable {
// Can only be moved, not copied
}
public struct ServiceRegistration: @unchecked Sendable, ~Copyable { ... }