Apple公式Swift Configurationを試してみた
目次
1. はじめに
本記事では、2025年12月に1.0が正式リリースされたApple公式の設定管理ライブラリ「Swift Configuration」を紹介します。基本的な使い方から応用機能までを解説し、最後に自分のユースケース向けに作ったラッパーライブラリについても触れます。
なお、本記事はサーバーサイドSwiftでiOSアプリ開発をどこまで効率化できるかを検証するシリーズの第1回です。私は2025年12月頃から、Vaporを使ったバックエンド構築とiOSアプリとのコード共有を試しており、その過程で開発したSwiftパッケージについて本シリーズで解説していきます。
2. Swift Configurationとは
Swift Configurationは、Appleが開発した設定管理ライブラリです。2025年10月にベータ版が公開され、12月11日に1.0が正式リリースされました。
解決する課題
Swift開発では、設定値を環境変数、JSONファイル、コマンドライン引数など複数のソースから読み込む必要があります。従来はこれらを手動で組み合わせる必要がありました。
// 従来のアプローチ(手動で組み立てる)
let projectId = ProcessInfo.processInfo.environment["GCP_PROJECT_ID"] ?? "default-project"
let port = Int(ProcessInfo.processInfo.environment["SERVER_PORT"] ?? "") ?? 8080
Swift Configurationは、この課題を統一的なAPIで解決します。
設計思想:リーダーとプロバイダーの分離
Swift Configurationの核心は、設定を読み取るAPI(リーダー)と設定の供給元(プロバイダー)を分離するという設計思想です。
アプリやライブラリはConfigReaderを通じて設定を読み取り、実際のプロバイダー(環境変数、JSONファイルなど)はエントリーポイントで一度だけ設定します。これにより、ライブラリは特定の設定ソースに依存せずに設定を受け取れます。
3. 基本的な使い方
ConfigReaderとプロバイダー
最もシンプルな使い方は、EnvironmentVariablesProviderを使って環境変数から設定を読み込むパターンです。
import Configuration
let config = ConfigReader(provider: EnvironmentVariablesProvider())
let httpTimeout = config.int(forKey: "http.timeout", default: 60)
print("タイムアウト: \(httpTimeout)秒")
プロバイダー階層による優先度解決
複数のプロバイダーを組み合わせることで、優先度順に値を解決できます。
let config = ConfigReader(providers: [
EnvironmentVariablesProvider(),
try await FileProvider<JSONSnapshot>(filePath: "/etc/config.json")
])
// 1. 環境変数に値があればそれを使用
// 2. なければJSONファイルの値を使用
// 3. どちらにもなければデフォルト値を使用
let timeout = config.int(forKey: "http.timeout", default: 60)
例えば、環境変数HTTP_TIMEOUT=15が設定されていれば15、なければJSONファイルの"http": { "timeout": 30 }から30、どちらもなければデフォルトの60が返ります。
キー変換規則
EnvironmentVariablesProviderは、ドット区切りのキーを自動的に大文字のアンダースコア区切りに変換します。
| キー | 環境変数名 |
|---|---|
http.timeout |
HTTP_TIMEOUT |
gcp.project.id |
GCP_PROJECT_ID |
firebase.emulator |
FIREBASE_EMULATOR |
スコープ付きリーダー
scoped(to:)メソッドを使うと、特定のプレフィックスを持つキーだけを扱うリーダーを作成できます。
let config = ConfigReader(provider: EnvironmentVariablesProvider())
// "emulator" スコープ付きリーダーを作成
let emulatorConfig = config.scoped(to: "emulator")
// "emulator.firestore.host" → 環境変数 EMULATOR_FIRESTORE_HOST を参照
let firestoreHost = emulatorConfig.string(forKey: "firestore.host", default: "localhost")
これにより、関連する設定をグループ化して管理できます。
4. 応用的な機能
Swift Configurationは基本的な設定読み込みだけでなく、本番環境で必要な機能を幅広くサポートしています。
3つのアクセスパターン
設定値へのアクセス方法として、3つのパターンが用意されています。
| パターン | 用途 | 特徴 |
|---|---|---|
| 同期(get) | 起動時の設定読み込み | 即座に値を返す |
| 非同期(fetch) | リモート設定の取得 | async/awaitで待機 |
| 監視(watch) | 設定変更のリアルタイム検知 | AsyncSequenceで値の変更を受け取る |
サーバーアプリケーションでは、起動時に同期パターンで設定を読み込み、実行中はwatchパターンで設定変更を監視するといった使い分けができます。
ホットリロード
ReloadingFileProviderを使うと、設定ファイルの変更を自動的に検知し、サービスを再起動せずに設定を更新できます。
// Package.swiftでReloadingトレイトを有効化
// traits: [.defaults, "Reloading"]
let config = ConfigReader(providers: [
EnvironmentVariablesProvider(),
try await ReloadingFileProvider<JSONSnapshot>(filePath: "/etc/config.json")
])
watchパターンと組み合わせることで、設定ファイルの変更をリアルタイムに反映できます。
型変換
基本型(String、Int、Double、Bool)に加えて、以下の型への変換をサポートしています。
- URL、UUID: 文字列からの自動変換
- Date: ISO8601形式からの変換
- FilePath: ファイルパス型への変換
-
列挙型:
RawRepresentable<String>に準拠した列挙型 -
カスタム型:
ExpressibleByConfigStringプロトコルに準拠した任意の型
シークレット保護
機密性の高い設定値(APIキー、パスワードなど)を安全に扱うための機能があります。isSecretフラグを設定することで、アクセスログへの出力時に自動的にマスキングされ、意図しない露出を防ぎます。
アクセスログ
AccessLoggerを使うと、どの設定値がいつ読み込まれたかをSwiftLogに出力できます。デバッグや監査に役立ちます。
// Package.swiftでLoggingトレイトを有効化
// traits: [.defaults, "Logging"]
アクセスイベントには、要求されたキー、呼び出し元、取得された値、使用されたプロバイダーなどの情報が含まれます。
利用可能なプロバイダー
Swift Configurationには、以下のプロバイダーが組み込まれています。
| プロバイダー | 用途 |
|---|---|
EnvironmentVariablesProvider |
環境変数 |
CommandLineArgumentsProvider |
コマンドライン引数(opt-in) |
FileProvider<JSONSnapshot> |
JSONファイル |
FileProvider<YAMLSnapshot> |
YAMLファイル(opt-in) |
ReloadingFileProvider |
ホットリロード対応ファイル(opt-in) |
DirectoryFilesProvider |
ディレクトリ内の複数ファイル |
InMemoryProvider |
テスト用のインメモリストレージ |
KeyMappingProvider |
キー名の変換 |
また、ConfigProviderプロトコルに準拠することで、カスタムプロバイダーを実装することもできます。
5. swift-envで目指したAPI設計
自分のプロジェクトでSwift Configurationを使う中で、Swiftマクロを使って宣言的に書けるラッパーswift-envを作りました。
自分が欲しかったAPI
私が欲しかったのは、プロパティにキーとデフォルト値を宣言するだけで設定構造体を定義できるAPIでした。
@Env
struct GCPConfig {
@Value("gcp.project.id", default: "my-project")
var projectId: String
@Value("firebase.emulator", default: false)
var useEmulator: Bool
}
@Envと@Valueマクロを使うことで、Keys enum、Defaults enum、initメソッドを自動生成します。使う側は宣言した通りの設定構造体が生成され、ConfigReaderを渡すだけで初期化できます。
let config = ConfigReader(provider: EnvironmentVariablesProvider())
let gcp = GCPConfig(config: config)
print(gcp.projectId) // 環境変数 GCP_PROJECT_ID または "my-project"
スコープ対応
@Env(scope:)でスコープを指定すると、内部でscoped(to:)が自動的に呼び出されます。
@Env(scope: "emulator")
struct EmulatorConfig {
@Value("firestore.host", default: "localhost")
var firestoreHost: String
@Value("firestore.port", default: 8090)
var firestorePort: Int
}
// 使用時はルートのconfigを渡すだけ
let emulator = EmulatorConfig(config: config)
// 環境変数 EMULATOR_FIRESTORE_HOST, EMULATOR_FIRESTORE_PORT を参照
設定構造体のグループ化
複数の設定構造体をまとめたい場合は、@EnvGroupマクロを使います。
@EnvGroup
struct AppConfig {
let gcp: GCPConfig
let emulator: EmulatorConfig
let server: ServerConfig
}
// 一括読み込み
let appConfig = AppConfig.load()
print(appConfig.gcp.projectId)
print(appConfig.emulator.firestoreHost)
ここで重要なのは、AppConfig自体はユーザーが定義する通常の構造体ですが、@EnvGroupマクロを付けることでload()メソッドが自動生成される点です。このメソッドは内部でConfigReaderを作成し、すべての子設定構造体を一括で初期化します。
位置付け
swift-envは、Swift Configurationの機能をそのまま活用しつつ、自分のユースケースに合わせて宣言的なAPIを提供するラッパーです。環境変数からの読み込みに特化しており、Swift Configurationが持つJSON/YAMLプロバイダーやホットリロードなどの豊富な機能をすべてサポートしているわけではありません。
公式ライブラリが提供するAPIをそのまま使うか、自分好みにラップするかは、プロジェクトの要件次第です。
6. まとめ
Swift Configuration 1.0の正式リリースにより、Swiftエコシステムに統一的な設定管理APIが提供されました。
- 環境変数、JSON、YAMLなど複数ソースからの設定読み込みを統一
- プロバイダー階層による柔軟な優先度解決
- 同期・非同期・監視の3つのアクセスパターン
- ホットリロード、シークレット保護、アクセスログなど本番環境向け機能
VaporやHummingbirdなど主要なサーバーサイドSwiftフレームワークでも統合が進んでおり、今後のSwiftエコシステムにおける設定管理の標準になっていくと思われます。
次回は、SwiftUIの状態管理をマクロで自動化するライブラリ「swift-statable」について解説します。
参考リンク
公式ドキュメント
- Swift Configuration - GitHub
- Swift Configuration 1.0 released - Swift.org
- Introducing Swift Configuration - Swift Forums
本記事で紹介したライブラリ
- swift-env - Swift Configurationのマクロベースラッパー
Discussion