
Swift Ios Migration
- 3 installs
- 3 repo stars
- Updated June 5, 2026
- xtone/ai_development_tools
Migration guide for Swift and iOS covering ObservableObject to @Observable, Swift 6 strict concurrency, and iPadOS 26 windowing changes.
About
A migration guide for Swift and iOS breaking changes, covering @Observable adoption, Swift 6 concurrency, and iPadOS 26 windowing. A developer uses it when upgrading Swift/iOS code across version boundaries.
- ObservableObject to @Observable and Swift 6 concurrency migration
- Reference files for iOS 17, Swift 6.2/Xcode 26, and iPadOS 26
Swift Ios Migration by the numbers
- 3 all-time installs (skills.sh)
- +1 installs in the week ending Jul 27, 2026 (Skillselion tracking)
- Ranked #887 of 1,039 Mobile Development skills by installs in the Skillselion catalog
- Data as of Jul 27, 2026 (Skillselion catalog sync)
npx skills add https://github.com/xtone/ai_development_tools --skill swift-ios-migrationAdd your badge
Show developers this skill is listed on Skillselion. Paste this into your README.
| Installs | 3 |
|---|---|
| repo stars | ★ 3 |
| Last updated | June 5, 2026 |
| Repository | xtone/ai_development_tools ↗ |
What it does
Migration guide for Swift and iOS covering ObservableObject to @Observable, Swift 6 strict concurrency, and iPadOS 26 windowing changes.
Files
Swift/iOS マイグレーションガイド
既存コードの移行作業やバージョンアップに伴う破壊的変更への対応ガイド。
ディレクトリ構成
swift-ios-migration/
├── SKILL.md (このファイル)
└── references/
├── ios17-observable.md # @Observable移行パターン
├── swift6-concurrency.md # Swift 6並行処理対応
├── swift62-changes.md # Swift 6.2 / Xcode 26
└── ipados26-windowing.md # iPadOS 26ウィンドウシステムリファレンスファイル
references/ios17-observable.md
iOS 17+ @Observableマクロへの移行ガイド:
- 移行パターン: ObservableObject → @Observable
- @Bindable: @Published/@Bindingの代替
- @Environment変更: 環境値の新しい渡し方
- パフォーマンス向上: プロパティ単位の監視
- 注意点: 互換性、既存コードとの共存
references/swift6-concurrency.md
Swift 6並行処理対応ガイド:
- @MainActor: ViewModel全体への適用、UI保護
- Sendable: データモデル設計、アクター境界
- 移行チェックリスト: Strict Concurrency Checking有効化
- よくある警告と解決策:
- "Non-sendable type captured"
- "Actor-isolated property cannot be mutated"
- Actor: カスタムActorでスレッドセーフ保証
references/swift62-changes.md
Swift 6.2 / Xcode 26 移行ガイド:
- Approachable Concurrency: より使いやすい並行処理
- Default Actor Isolation: MainActorをデフォルトに
- @concurrent: 明示的なバックグラウンド実行
- Xcode 26新機能: AI Coding Tools、Playground強化
- デバッグ改善: LLDB強化、並行処理デバッグ
references/ipados26-windowing.md
iPadOS 26 ウィンドウシステム移行ガイド:
- UIRequiresFullScreen廃止: Info.plistからの削除、Auto Layout対応
- シーンジオメトリ: UIWindowScene.Geometry、effectiveGeometry、リサイズ検知
- サイズ制限: UISceneSizeRestrictions、minimumSize/maximumSize設定
- 方向ロック: prefersInterfaceOrientationLocked、方向変更監視
- メニューバー対応: SwiftUI Commands、UIMenuBuilder、キーボードショートカット
- マルチウィンドウ: UIApplicationSupportsMultipleScenes、新規ウィンドウ作成
- Trait Collection: サイズクラス対応、自動トラッキング
使用方法
iOS 17 @Observable移行時
1. references/ios17-observable.mdで移行パターン確認 2. 新規コードから@Observableを使用開始 3. 既存の小さなViewModelから段階的に移行 4. 大きなViewModelは慎重に移行
Swift 6移行時
1. references/swift6-concurrency.mdでチェックリスト確認 2. Strict Concurrency Checkingを有効化 3. ViewModelに@MainActorを追加 4. データモデルをSendableに対応 5. 警告を順次解消
Swift 6.2移行時
1. references/swift62-changes.mdで新機能確認 2. Xcode 26にアップデート 3. Approachable Concurrencyを有効化 4. Default Actor IsolationをMainActorに設定 5. 必要な箇所に@concurrentを追加 6. 既存のSwift 6コードを簡略化
iPadOS 26ウィンドウシステム対応時
1. references/ipados26-windowing.mdで移行チェックリスト確認 2. UIRequiresFullScreenをInfo.plistから削除 3. Auto Layoutでサイズ適応レイアウトを実装 4. UISceneSizeRestrictionsで最小サイズを設定 5. メニューバー対応(外部キーボード使用時)
移行優先順位
Swift 6.2(推奨順)
1. Build SettingsでApproachable Concurrencyを有効化 2. Default Actor IsolationをMainActorに設定 3. 必要な箇所に@concurrentを追加 4. 既存のSwift 6コードを簡略化
Swift 6(推奨順)
1. ViewModelに@MainActorを追加 2. データモデルをSendableに対応 3. 非同期処理をasync/awaitに統一
iOS 17 @Observable(推奨順)
1. 新規コードから@Observableを使用 2. 既存の小さなViewModelから移行 3. 大きなViewModelは慎重に移行
iPadOS 26(推奨順)
1. Xcode 26でビルド・テスト 2. UIRequiresFullScreenを削除 3. Auto Layout対応を確認 4. メニューバー実装
バージョン対応表
| 機能 | Swift 5.9 | Swift 6 | Swift 6.2 |
|---|---|---|---|
| Strict Concurrency | Opt-in | Default | Default |
| @MainActor | ✅ | ✅ | ✅ |
| Default Actor Isolation | - | - | ✅ |
| @concurrent | - | - | ✅ |
| Approachable Concurrency | - | - | ✅ |
| 機能 | iOS 16 | iOS 17 | iOS 18 | iOS 26 |
|---|---|---|---|---|
| @Observable | - | ✅ | ✅ | ✅ |
| iPadOS新ウィンドウシステム | - | - | - | ✅ |
関連スキル
- swiftui-components: UIコンポーネントカタログ
- swiftui-coding-guidelines: 基本的なベストプラクティス
- swiftui-ssot: 状態管理の設計
- swiftui-accessibility: アクセシビリティ実装
iOS 17+ @Observable マクロガイド
概要
iOS 17で導入された@Observableマクロは、SwiftUIの状態管理を大幅に簡素化し、パフォーマンスを向上させる。
移行パターン
基本的な変更
| iOS 13-16 | iOS 17+ |
|---|---|
ObservableObject + @Published | @Observable(@Published不要) |
@StateObject | @State |
@ObservedObject | ラッパー不要(直接渡す) |
@EnvironmentObject | @Environment(MyType.self) |
Before: ObservableObject(iOS 13-16)
class ArticleViewModel: ObservableObject {
@Published var articles: [Article] = []
@Published var isLoading = false
@Published var errorMessage: String?
func fetch() async {
isLoading = true
defer { isLoading = false }
// ...
}
}
struct ArticleListView: View {
@StateObject private var viewModel = ArticleViewModel()
var body: some View {
ChildView(viewModel: viewModel)
}
}
struct ChildView: View {
@ObservedObject var viewModel: ArticleViewModel
// ...
}After: @Observable(iOS 17+)
@Observable
class ArticleViewModel {
var articles: [Article] = []
var isLoading = false
var errorMessage: String?
func fetch() async {
isLoading = true
defer { isLoading = false }
// ...
}
}
struct ArticleListView: View {
@State private var viewModel = ArticleViewModel()
var body: some View {
ChildView(viewModel: viewModel)
}
}
struct ChildView: View {
var viewModel: ArticleViewModel // ラッパー不要
// ...
}@Bindableの使用
子ビューで双方向バインディングが必要な場合は@Bindableを使用。
@Observable
class FormData {
var name = ""
var email = ""
}
struct FormView: View {
@State private var formData = FormData()
var body: some View {
FormContent(formData: formData)
}
}
struct FormContent: View {
@Bindable var formData: FormData
var body: some View {
Form {
TextField("Name", text: $formData.name)
TextField("Email", text: $formData.email)
}
}
}Environment経由の注入
// iOS 13-16
struct ContentView: View {
@EnvironmentObject var settings: AppSettings
}
// iOS 17+
@Observable
class AppSettings {
var theme: Theme = .light
}
struct ContentView: View {
@Environment(AppSettings.self) var settings
var body: some View {
// 双方向バインディングが必要な場合
@Bindable var settings = settings
Toggle("Dark Mode", isOn: $settings.isDarkMode)
}
}
// 注入
ContentView()
.environment(AppSettings())パフォーマンス向上
選択的な監視
@Observableは使用されるプロパティのみを監視し、不要な再描画を防止。
@Observable
class ViewModel {
var name = "" // NameViewのみが監視
var count = 0 // CountViewのみが監視
var items: [Item] = [] // ItemsViewのみが監視
}
struct NameView: View {
var viewModel: ViewModel
var body: some View {
// nameが変更された時のみ再描画
// countやitemsの変更では再描画されない
Text(viewModel.name)
}
}
struct CountView: View {
var viewModel: ViewModel
var body: some View {
// countが変更された時のみ再描画
Text("\(viewModel.count)")
}
}ObservableObjectとの比較
// ObservableObject: すべての@Publishedプロパティの変更で全ビューが再描画
class OldViewModel: ObservableObject {
@Published var name = ""
@Published var count = 0
}
// @Observable: 使用しているプロパティの変更のみで再描画
@Observable
class NewViewModel {
var name = ""
var count = 0
}注意点
@State vs @StateObject の違い
// ⚠️ 注意: @Stateは@StateObjectと異なる動作をする可能性
// @StateObject: ビュー再生成時も同じインスタンスを維持
@StateObject private var viewModel = ViewModel()
// @State with @Observable: 条件付きビューでは再初期化の可能性
@State private var viewModel = ViewModel()Jesse Squiresの記事より:
@Stateで@Observableオブジェクトを使用する場合、ビューの条件付き表示で予期しない再初期化が起こる可能性- 複雑なナビゲーションやモーダル表示では注意が必要
監視の除外
特定のプロパティを監視対象から除外する場合:
@Observable
class ViewModel {
var trackedProperty = ""
@ObservationIgnored
var ignoredProperty = "" // 変更しても再描画されない
}移行チェックリスト
1. [ ] ObservableObjectを@Observableに変更 2. [ ] @Publishedを削除 3. [ ] @StateObjectを@Stateに変更 4. [ ] @ObservedObjectを削除(直接渡す) 5. [ ] @EnvironmentObjectを@Environment(Type.self)に変更 6. [ ] 双方向バインディングが必要な箇所に@Bindableを追加 7. [ ] @ObservationIgnoredで監視不要なプロパティをマーク 8. [ ] 条件付きビューでの状態維持を確認
後方互換性
iOS 17未満をサポートする場合:
#if swift(>=5.9)
@Observable
class ViewModel {
var items: [Item] = []
}
#else
class ViewModel: ObservableObject {
@Published var items: [Item] = []
}
#endifまたは、iOS 17+のみで@Observableを使用し、iOS 16以下では従来のパターンを維持。
iPadOS 26 ウィンドウシステム
iPadOS 26では、iPadが本格的なウィンドウシステムを採用し、macOSに近いマルチタスク体験を提供するようになりました。このドキュメントでは、新しいウィンドウシステムへの対応方法を解説します。
主要な変更点
UIRequiresFullScreen の廃止
iPadOS 26より、UIRequiresFullScreen Info.plistキーとその関連する互換モードは非推奨となり、将来のリリースで無視されるようになります。
重要: このキーに依存しているアプリは、システムがマルチタスクシナリオに対応するためにシーンをリサイズする際に、以下の問題が発生する可能性があります:
- レイアウトの崩れ
- UI要素の位置ずれ
- コンテンツの切り詰め
新しいウィンドウモード
iPadOS 26では、より柔軟なウィンドウ管理が可能になりました:
- 自由なウィンドウサイズ変更
- 複数ウィンドウの重なり表示
- すべてのiPadモデルでStage Managerが利用可能
- メニューバーのサポート強化
---
移行手順
1. UIRequiresFullScreen の削除
リサイズ可能なシーンをサポートするために、アプリが以下の条件を満たすことを確認してください:
1. Launch Storyboardの提供 - Info.plistで起動画面を指定 2. すべてのサイズクラスのサポート - Compact/Regularの両方に対応 3. UIRequiresFullScreen キーの削除 - Info.plistまたはビルド設定から削除
<!-- 削除すべきエントリ -->
<key>UIRequiresFullScreen</key>
<true/>2. Auto Layoutへの移行
固定サイズのレイアウトから、制約ベースのレイアウトに移行してください。
// ❌ 避けるべき: 固定フレーム
view.frame = CGRect(x: 0, y: 0, width: 1024, height: 768)
// ✅ 推奨: Auto Layout制約
NSLayoutConstraint.activate([
contentView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
contentView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
contentView.bottomAnchor.constraint(equalTo: view.safeAreaLayoutGuide.bottomAnchor)
])---
シーンジオメトリの監視
UIWindowSceneDelegate でのジオメトリ変更検知
windowScene(_:didUpdateEffectiveGeometry:) を使用して、シーンのジオメトリ変更を監視します。
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var previousSceneSize = CGSize.zero
func windowScene(
_ windowScene: UIWindowScene,
didUpdateEffectiveGeometry previousGeometry: UIWindowScene.Geometry
) {
let geometry = windowScene.effectiveGeometry
let sceneSize = geometry.coordinateSpace.bounds.size
// インタラクティブリサイズ中かどうかを確認
if !geometry.isInteractivelyResizing && sceneSize != previousSceneSize {
previousSceneSize = sceneSize
// リサイズ完了後の処理
updateLayoutForSize(sceneSize)
}
}
private func updateLayoutForSize(_ size: CGSize) {
// レイアウト更新処理
}
}UIWindowScene.Geometry プロパティ
| プロパティ | 型 | 説明 |
|---|---|---|
systemFrame | CGRect | システム座標でのシーンフレーム |
coordinateSpace | UICoordinateSpace | シーンの座標空間 |
interfaceOrientation | UIInterfaceOrientation | 現在のインターフェース方向 |
isInterfaceOrientationLocked | Bool | 方向がロックされているか |
isInteractivelyResizing | Bool | リサイズ操作中かどうか |
minimumSize | CGSize | 最小サイズ |
maximumSize | CGSize | 最大サイズ |
resizingRestrictions | UIWindowSceneResizingRestrictions | リサイズ制限 |
---
シーンサイズ制限の設定
UISceneSizeRestrictions
UISceneSizeRestrictions を使用して、ウィンドウの最小・最大サイズを指定します。
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
func scene(
_ scene: UIScene,
willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions
) {
guard let windowScene = scene as? UIWindowScene else { return }
// 最小サイズの設定
windowScene.sizeRestrictions?.minimumSize = CGSize(width: 500, height: 400)
// 最大サイズの設定(オプション)
windowScene.sizeRestrictions?.maximumSize = CGSize(width: 1200, height: 900)
// フルスクリーン許可の設定
windowScene.sizeRestrictions?.allowsFullScreen = true
}
}SwiftUI での設定
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
.frame(minWidth: 500, maxWidth: 1200,
minHeight: 400, maxHeight: 900)
}
.windowResizability(.contentMinSize)
}
}windowResizability オプション
| 値 | 説明 |
|---|---|
.automatic | システムが自動的に決定 |
.contentSize | コンテンツサイズに基づく |
.contentMinSize | コンテンツの最小サイズを使用 |
---
インターフェース方向のロック
特定の状況で方向をロックする必要がある場合(例:ゲーム、カメラアプリ):
class MyViewController: UIViewController {
var isDriving: Bool = false {
didSet {
if isDriving != oldValue {
setNeedsUpdateOfPrefersInterfaceOrientationLocked()
}
}
}
override var prefersInterfaceOrientationLocked: Bool {
return isDriving
}
}方向ロック状態の監視
func windowScene(
_ windowScene: UIWindowScene,
didUpdateEffectiveGeometry previousGeometry: UIWindowScene.Geometry
) {
let wasLocked = previousGeometry.isInterfaceOrientationLocked
let isLocked = windowScene.effectiveGeometry.isInterfaceOrientationLocked
if wasLocked != isLocked {
// 方向ロック状態が変化した
handleOrientationLockChange(isLocked: isLocked)
}
}---
SwiftUI でのリサイズ対応
onInteractiveResizeChange
struct ContentView: View {
@State private var isResizing = false
var body: some View {
GeometryReader { geometry in
MainContent(size: geometry.size)
.overlay {
if isResizing {
ResizeOverlay()
}
}
}
.onInteractiveResizeChange { isResizing in
self.isResizing = isResizing
}
}
}GeometryReader でのサイズ適応
struct AdaptiveLayout: View {
var body: some View {
GeometryReader { geometry in
if geometry.size.width > 600 {
HStack {
Sidebar()
MainContent()
}
} else {
VStack {
MainContent()
}
}
}
}
}---
メニューバー対応
iPadOS 26では、外部キーボード接続時にメニューバーが表示されるようになりました。
SwiftUI でのメニューバー構築
@main
struct MyApp: App {
var body: some Scene {
WindowGroup {
ContentView()
}
.commands {
// サイドバーコマンド
SidebarCommands()
// カスタムメニュー
CommandMenu("Actions") {
Button("Run", systemImage: "play.fill") {
// アクション
}
.keyboardShortcut("R")
Button("Stop", systemImage: "stop.fill") {
// アクション
}
.keyboardShortcut(".")
}
// 既存メニューの拡張
CommandGroup(after: .newItem) {
Button("New from Template...") {
// アクション
}
}
}
}
}UIKit でのメニュー構築
class AppDelegate: UIResponder, UIApplicationDelegate {
override func buildMenu(with builder: UIMenuBuilder) {
super.buildMenu(with: builder)
guard builder.system == .main else { return }
// カスタムメニューの追加
let actionsMenu = UIMenu(
title: "Actions",
children: [
UIKeyCommand(
title: "Run",
action: #selector(runAction),
input: "R",
modifierFlags: .command
),
UIKeyCommand(
title: "Stop",
action: #selector(stopAction),
input: ".",
modifierFlags: .command
)
]
)
builder.insertSibling(actionsMenu, afterMenu: .view)
}
@objc func runAction() { }
@objc func stopAction() { }
}---
マルチウィンドウ対応
複数シーンのサポート有効化
Info.plist で UIApplicationSupportsMultipleScenes を true に設定:
<key>UIApplicationSceneManifest</key>
<dict>
<key>UIApplicationSupportsMultipleScenes</key>
<true/>
<key>UISceneConfigurations</key>
<dict>
<key>UIWindowSceneSessionRoleApplication</key>
<array>
<dict>
<key>UISceneConfigurationName</key>
<string>Default Configuration</string>
<key>UISceneDelegateClassName</key>
<string>$(PRODUCT_MODULE_NAME).SceneDelegate</string>
</dict>
</array>
</dict>
</dict>新規ウィンドウの作成
// UIKit
func createNewWindow() {
let activity = NSUserActivity(activityType: "com.example.newWindow")
UIApplication.shared.requestSceneSessionActivation(
nil,
userActivity: activity,
options: nil
)
}
// SwiftUI
@Environment(\.openWindow) private var openWindow
Button("New Window") {
openWindow(id: "secondary")
}---
Trait Collection による適応
自動トラッキング
iOS 17以降では、トレイトの自動トラッキングが利用可能です:
class MyViewController: UIViewController {
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// 自動的にトレイトの変更を検知
updateLayoutForTraits()
}
private func updateLayoutForTraits() {
let horizontalClass = traitCollection.horizontalSizeClass
let verticalClass = traitCollection.verticalSizeClass
switch (horizontalClass, verticalClass) {
case (.compact, .regular):
// iPhone縦向き / iPad Split View狭幅
configureCompactLayout()
case (.regular, .regular):
// iPad全画面 / iPad Split View広幅
configureRegularLayout()
default:
configureDefaultLayout()
}
}
}トレイト変更の手動登録
override func viewDidLoad() {
super.viewDidLoad()
registerForTraitChanges([
UITraitHorizontalSizeClass.self,
UITraitVerticalSizeClass.self
]) { (self: Self, previousTraitCollection) in
self.updateLayoutForTraits()
}
}---
移行チェックリスト
必須対応
- [ ]
UIRequiresFullScreenをInfo.plistから削除 - [ ] Launch Storyboardを設定
- [ ] すべてのサイズクラスでのレイアウトをテスト
- [ ] Auto Layoutまたはサイズ適応レイアウトを使用
推奨対応
- [ ]
windowScene(_:didUpdateEffectiveGeometry:)でジオメトリ変更を監視 - [ ]
UISceneSizeRestrictionsで適切な最小サイズを設定 - [ ] メニューバー対応(外部キーボード使用時)
- [ ] マルチウィンドウシナリオのテスト
テスト項目
- [ ] Split View(50/50、75/25、25/75)
- [ ] Slide Over
- [ ] Stage Manager(対応デバイス)
- [ ] 外部ディスプレイ接続
- [ ] 画面回転
- [ ] フルスクリーン⇔ウィンドウ切り替え
---
関連リソース
Apple ドキュメント
- TN3192: Migrating your iPad app from the deprecated UIRequiresFullScreen key
- Multitasking on iPad, Mac, and Apple Vision Pro
- Supporting multiple windows on iPad
- UIWindowScene.Geometry
- UISceneSizeRestrictions
- Building and customizing the menu bar with SwiftUI
WWDC25 セッション
- WWDC25 Session 282: UIKit scenes and container view controllers - シーンとコンテナビューコントローラの活用
- WWDC25 Session 284: Updating UIKit apps for the new design - UIKitアプリの新デザイン対応
Human Interface Guidelines
Swift 6 並行処理対応ガイド
Swift 6では並行処理の安全性(Data Race Safety)がコンパイラによって厳密に保証される。SwiftUIアプリの設計に直接的な影響を与える。
@MainActor によるUI保護
ViewModelへの適用(推奨パターン)
// ✅ 推奨: ViewModel全体に@MainActorを付与
@MainActor
class ArticleListViewModel: ObservableObject {
@Published var articles: [Article] = []
@Published var isLoading = false
@Published var error: Error?
private let service: ArticleServiceProtocol
init(service: ArticleServiceProtocol = ArticleService()) {
self.service = service
}
func fetchArticles() async {
isLoading = true
defer { isLoading = false }
do {
// バックグラウンドで実行されるが、
// 結果の代入は自動的にメインスレッドで行われる
articles = try await service.fetch()
} catch {
self.error = error
}
}
}メリット:
- プロパティ更新が自動的にメインスレッドで実行
DispatchQueue.main.asyncが不要- コンパイラが安全性を保証
iOS 17+ @Observable での適用
@MainActor
@Observable
class ArticleListViewModel {
var articles: [Article] = []
var isLoading = false
var error: Error?
func fetchArticles() async {
isLoading = true
defer { isLoading = false }
do {
articles = try await service.fetch()
} catch {
self.error = error
}
}
}部分的な@MainActor適用
特定のメソッドのみUIに関連する場合:
class DataProcessor {
private var cache: [String: Data] = [:]
// バックグラウンドで実行可能
func processData(_ input: Data) async -> ProcessedData {
// 重い処理
await heavyComputation(input)
}
// UI更新を伴うメソッドのみ@MainActor
@MainActor
func updateUI(with result: ProcessedData) {
// UI更新処理
}
}Sendable プロトコル
アクター境界を超えるデータ
// ✅ 値型(Struct)は自動的にSendable
struct Article: Sendable, Identifiable {
let id: UUID
let title: String
let content: String
let publishedAt: Date
}
// ✅ Enumも自動的にSendable
enum LoadingState: Sendable {
case idle
case loading
case loaded([Article])
case error(Error) // ErrorはSendableではないので注意
}
// ⚠️ Errorを含む場合
enum LoadingState<T: Sendable>: Sendable {
case idle
case loading
case loaded(T)
case error(String) // Errorの代わりにStringを使用
}クラスをSendableにする
// 方法1: finalクラス + 不変プロパティ
final class ImmutableConfig: Sendable {
let apiKey: String
let baseURL: URL
init(apiKey: String, baseURL: URL) {
self.apiKey = apiKey
self.baseURL = baseURL
}
}
// 方法2: @unchecked Sendable(手動で安全性を保証)
final class ThreadSafeCache: @unchecked Sendable {
private let lock = NSLock()
private var storage: [String: Data] = [:]
func get(_ key: String) -> Data? {
lock.lock()
defer { lock.unlock() }
return storage[key]
}
func set(_ key: String, value: Data) {
lock.lock()
defer { lock.unlock() }
storage[key] = value
}
}⚠️ `@unchecked Sendable` は慎重に使用
- コンパイラのチェックを無効化
- 手動でスレッドセーフティを保証する責任
非同期タスクのパターン
View内でのTask使用
struct ArticleListView: View {
@StateObject private var viewModel = ArticleListViewModel()
var body: some View {
List(viewModel.articles) { article in
ArticleRow(article: article)
}
.task {
// ViewModelが@MainActorなら安全
await viewModel.fetchArticles()
}
.refreshable {
await viewModel.fetchArticles()
}
}
}明示的なアクター切り替え
@MainActor
class ViewModel: ObservableObject {
@Published var result: String = ""
func process() async {
// メインアクターから離脱してバックグラウンドで実行
let processed = await Task.detached(priority: .userInitiated) {
await self.heavyComputation()
}.value
// 自動的にメインアクターに戻る(ViewModelが@MainActorのため)
result = processed
}
// nonisolatedでメインアクター外で実行可能に
nonisolated func heavyComputation() async -> String {
// CPU集約的な処理
return "result"
}
}Swift 6移行チェックリスト
1. Strict Concurrency Checkingを有効化
Build Settings → Swift Compiler - Upcoming Features → Strict Concurrency Checking を Complete に設定
2. ViewModel
- [ ]
@MainActorを付与 - [ ]
@Publishedプロパティの更新がメインスレッドで行われることを確認 - [ ] 非同期メソッドが適切に
asyncマークされている
3. データモデル
- [ ] 値型(Struct/Enum)を優先使用
- [ ]
Sendable準拠を確認 - [ ] アクター境界を超えるデータの安全性確認
4. サービス/リポジトリ層
- [ ] API通信などは
async関数として実装 - [ ] 結果型は
Sendable準拠
5. 警告の解消
// ⚠️ 警告: Capture of 'self' with non-sendable type
Task {
await self.doSomething() // 警告
}
// ✅ 解決: @MainActorを付与するか、Sendable準拠
@MainActor
class ViewModel: ObservableObject {
func start() {
Task {
await self.doSomething() // OK
}
}
}よくある警告と解決策
1. "Non-sendable type captured"
// ❌ 警告
class MyClass {
func process() {
Task {
print(self) // Non-sendable type captured
}
}
}
// ✅ 解決策1: @MainActorを付与
@MainActor
class MyClass { ... }
// ✅ 解決策2: Sendable準拠
final class MyClass: Sendable { ... }
// ✅ 解決策3: 必要な値だけキャプチャ
class MyClass {
let id: String
func process() {
let id = self.id // Sendableな値をキャプチャ
Task {
print(id)
}
}
}2. "Actor-isolated property cannot be mutated"
// ❌ エラー
class ViewModel: ObservableObject {
@Published var data: String = ""
func update() async {
data = await fetchData() // エラー: メインアクター外から更新
}
}
// ✅ 解決: @MainActorを付与
@MainActor
class ViewModel: ObservableObject {
@Published var data: String = ""
func update() async {
data = await fetchData() // OK
}
}3. "Call to main actor-isolated method in a synchronous context"
// ❌ エラー
@MainActor
class ViewModel {
func doUIWork() { }
}
func somewhere() {
let vm = ViewModel()
vm.doUIWork() // エラー
}
// ✅ 解決: asyncコンテキストで呼び出し
func somewhere() async {
let vm = await ViewModel()
await vm.doUIWork()
}Actor の活用
カスタムActorでスレッドセーフを保証
actor ImageCache {
private var cache: [URL: UIImage] = [:]
func image(for url: URL) -> UIImage? {
cache[url]
}
func setImage(_ image: UIImage, for url: URL) {
cache[url] = image
}
func clearCache() {
cache.removeAll()
}
}
// 使用
class ImageLoader {
private let cache = ImageCache()
func loadImage(from url: URL) async -> UIImage? {
// actorのメソッドはawaitが必要
if let cached = await cache.image(for: url) {
return cached
}
guard let image = await downloadImage(from: url) else {
return nil
}
await cache.setImage(image, for: url)
return image
}
}Swift 6.2 / Xcode 26 移行ガイド
概要
Swift 6.2(Xcode 26同梱)では「Approachable Concurrency」が導入され、Swift 6の厳格な並行処理チェックをより使いやすくする機能が追加された。
---
Approachable Concurrency
背景
Swift 6のStrict Concurrencyは安全だが、多くのボイラープレートと急な学習曲線が問題だった。Swift 6.2では以下のアプローチで解決:
1. シングルスレッドコードから開始 - デフォルトでMainActorに分離 2. 必要に応じて並行処理を導入 - 明示的にオプトイン 3. `@concurrent`で明示的にバックグラウンド実行
---
デフォルトアクター分離
Build Settings設定
// Xcode 26 Build Settings
Swift Compiler - Upcoming Features:
- Approachable Concurrency: Yes
- Default Actor Isolation: MainActor効果
// Swift 6.2 + Default Actor Isolation = MainActor
// @MainActorを明示的に書く必要がない
class ArticleViewModel: ObservableObject {
@Published var articles: [Article] = []
// 暗黙的に@MainActor
func refresh() async {
articles = await fetchArticles()
}
}従来のSwift 6との比較
// Swift 6: 明示的な@MainActorが必要
@MainActor
class ArticleViewModel: ObservableObject {
@Published var articles: [Article] = []
func refresh() async {
articles = await fetchArticles()
}
}
// Swift 6.2: Default Actor IsolationがMainActorなら省略可能
class ArticleViewModel: ObservableObject {
@Published var articles: [Article] = []
func refresh() async {
articles = await fetchArticles()
}
}---
@concurrent
バックグラウンドで実行したい関数に明示的にマーク。
基本的な使用法
// バックグラウンドで実行
@concurrent
func processData(_ data: Data) async -> ProcessedData {
// 重い処理
return heavyComputation(data)
}
// 呼び出し側
func updateUI() async {
let result = await processData(rawData)
// 結果はMainActorに自動的に戻る(Default Actor IsolationがMainActorの場合)
displayResult(result)
}ユースケース
class ImageProcessor {
// 画像処理はバックグラウンドで実行
@concurrent
func processImage(_ image: UIImage) async -> UIImage {
// フィルター適用、リサイズなど
}
// UI更新はMainActorで(デフォルト)
func applyFilter() async {
let processed = await processImage(originalImage)
self.displayedImage = processed // MainActorで実行
}
}---
移行チェックリスト
Swift 6 → Swift 6.2
1. [ ] Xcode 26にアップデート 2. [ ] Build SettingsでApproachable Concurrencyを有効化 3. [ ] Default Actor IsolationをMainActorに設定 4. [ ] 既存の@MainActorを確認(削除可能な場合あり) 5. [ ] バックグラウンド処理が必要な関数に@concurrentを追加 6. [ ] 既存のSwift 6コードをレビュー・簡略化
新規プロジェクト
1. [ ] Xcode 26で新規プロジェクト作成 2. [ ] Approachable Concurrencyがデフォルトで有効 3. [ ] 必要に応じて@concurrentを使用
---
Xcode 26 新機能
AI Coding Tools
- ChatGPT統合: Xcode内でChatGPTを直接使用
- サードパーティLLM対応: APIキーで他のLLMも利用可能
- ローカルモデル実行: オンデバイスでのAI処理
- コード生成・バグ修正・ドキュメント生成
Playground強化
- インラインPlayground: 通常プロジェクト内にPlaygroundコードブロックを埋め込み
- ライブインタラクション: Previewのような即時フィードバック
- APIテストとデモ作成に最適
パフォーマンス改善
- プロジェクト読み込み40%高速化
- モジュラー設計: 必要な部分のみダウンロード
- Voice Control改善: Swiftコードのディクテーション精度向上
---
デバッグ改善
LLDB強化
- 非同期関数内でスレッド切り替え後も実行継続
- コンパイルモジュールの再利用で評価高速化
並行処理デバッグ
- アクター境界を超えるデータフローの可視化
- デッドロック検出の改善
---
その他のSwift 6.2新機能
Subprocess パッケージ
スクリプティング用のプロセス実行API。
import Subprocess
let result = try await Subprocess.run(
executing: .init(at: "/bin/ls"),
arguments: ["-la"]
)
print(result.standardOutput)Swift Testing強化
- カスタムアタッチメント: テスト結果に追加データを添付
- Exit tests: 終了コードの検証
@Test
func testProcess() async throws {
await #expect(exitsWith: .success) {
// プロセス終了をテスト
}
}相互運用性の改善
- C/C++/Objective-C/Java相互運用の改善
- より多くの型の自動ブリッジング
---
互換性
対応環境
- Xcode 26以降
- iOS 26 / iPadOS 26 / macOS 26 / watchOS 26 / tvOS 26 / visionOS 26
- 下位バージョンへのデプロイも可能(ただし一部機能制限あり)
既存コードとの互換性
- Swift 6コードはそのまま動作
- Approachable Concurrencyは段階的に採用可能
- 既存の
@MainActorは引き続き有効
---