UIKit vs SwiftUI:起動速度のためにUIKitを選んだ理由 UIKit vs SwiftUI: Why We Chose UIKit for Launch Speed
1. なぜ2026年にUIKitなのか
2026年現在、新規iOSアプリの多くはSwiftUIで開発されている。Appleも毎年のWWDCでSwiftUIの進化を強調し、宣言的UIの利便性はもはや疑いようがない。
しかし、このアプリには譲れない要件がある。それはTime-to-Text(起動からテキスト入力可能になるまでの時間)を500ms以下にすることだ。
SwiftUIでは、@StateObjectの初期化、bodyの再評価、.onAppearのタイミング制御など、宣言的UIフレームワーク固有のオーバーヘッドが存在する。これらは一般的なアプリでは無視できる程度だが、ミリ秒単位の最適化を追求する場面では無視できない。
一方、UIKitのviewDidAppearからbecomeFirstResponder()を呼ぶパスは決定論的に高速だ。ViewControllerのライフサイクルは明確で、いつキーボードが表示されるかを正確に制御できる。
2. Storyboardを完全に排除した理由
UIKitを選んだだけでは不十分だ。Storyboardにも起動パフォーマンスに影響するオーバーヘッドがある。
- XML解析コスト:Storyboardは内部的にXMLファイルであり、起動時にパースが必要
- Segue解決コスト:画面遷移のためのSegue設定の解決に時間がかかる
- 不必要な複雑性:シンプルなメモアプリにStoryboardの視覚的設計ツールは過剰
代わりに、SceneDelegateで直接ViewControllerを生成する方式を採用した。
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
PerformanceLogger.shared.signpostBegin(name: "SceneConnect")
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let composeVC = ComposeViewController()
let navController = UINavigationController(rootViewController: composeVC)
window.rootViewController = navController
window.makeKeyAndVisible()
PerformanceLogger.shared.signpostEnd(name: "SceneConnect")
}
このアプローチにより、Storyboardのパース時間ゼロ、Segue解決時間ゼロを実現している。SceneDelegateから直接ComposeViewControllerを生成することで、起動パスから不要な処理を完全に排除した。
3. viewDidAppearの1行がすべてを完結させる
UIKitの最大の強みは、ViewControllerのライフサイクルが明確に定義されていることだ。viewDidAppear(_:)が呼ばれた時点で、ビューは完全に画面に表示されている。この確実なタイミングでbecomeFirstResponder()を呼ぶことで、キーボード表示までの遅延を最小化できる。
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
activateTextInput()
}
private func activateTextInput() {
textView.becomeFirstResponder()
PerformanceLogger.shared.endTimeToText()
}
os_signpostによる計測をナノ秒精度で実施している。PerformanceLogger.shared.endTimeToText()がSignpostの終了イベントを記録し、Instrumentsでの詳細な分析を可能にしている。
4. パフォーマンス計測の仕組み
「速い」という感覚的な判断ではなく、定量的なデータに基づいて設計判断を行っている。
- PerformanceLogger:
os_signpostをラップしたカスタムロガー。起動・送信・UI更新の各フェーズを計測 - Instruments連携:Xcodeのos_signpost Instrumentでナノ秒精度のトラッキングが可能
- 主要メトリクス:Time-to-Text(起動→入力可能)、Send-to-Reset(送信ボタン→UI初期化)
- 継続的モニタリング:開発中のビルドすべてでパフォーマンス計測を実施し、リグレッションを即座に検出
パフォーマンスは機能である。ユーザーは「速さ」を明示的に要求しなくても、遅いアプリは無意識のうちに使わなくなる。だからこそ、パフォーマンスを数値で管理し続けることが重要だ。
5. SwiftUIを使う場面はあるか
UIKit一択というわけではない。アプリ内には、SwiftUIが適している場面もある。
- 設定画面:起動速度に影響しない非クリティカルな画面ではSwiftUIの宣言的UIが生産性を高める
- クリティカルパスの定義:起動 → メモ入力 → 送信というコアフローはUIKitで維持する
- ハイブリッドの複雑性:UIKitとSwiftUIを混在させると、UIHostingControllerの管理やデータフローの一貫性に注意が必要
- 現時点の判断:メモアプリのUIはシンプルであり、全画面UIKitで統一するほうがコードベースの一貫性が保たれる
将来的にSwiftUIのパフォーマンスがさらに改善されれば再評価の余地はあるが、クリティカルパスのUIKitは当面維持する方針だ。
よくある質問
@StateObject初期化やbody再評価のオーバーヘッドがある。最速を追求するなら、UIKitの決定論的なライフサイクルが有利。os_signpostでナノ秒単位で計測し、Instrumentsで詳細分析を実施している。1. Why UIKit in 2026
In 2026, the majority of new iOS apps are built with SwiftUI. Apple emphasizes SwiftUI's evolution at every WWDC, and the convenience of declarative UI is beyond question.
However, this app has a non-negotiable requirement: Time-to-Text (the time from launch to text input readiness) must be under 500ms.
SwiftUI introduces overhead inherent to declarative UI frameworks: @StateObject initialization, body re-evaluation, and .onAppear timing control. While negligible for most apps, these become significant when optimizing at the millisecond level.
In contrast, UIKit's viewDidAppear to becomeFirstResponder() path is deterministically faster. The ViewController lifecycle is explicit, giving precise control over when the keyboard appears.
2. Why We Completely Eliminated Storyboard
Choosing UIKit alone wasn't enough. Storyboards also introduce overhead that impacts launch performance.
- XML Parsing Cost: Storyboards are XML files internally, requiring parsing at launch time
- Segue Resolution Cost: Resolving segue configurations for screen transitions takes time
- Unnecessary Complexity: A simple memo app doesn't need Storyboard's visual design tools
Instead, we instantiate ViewControllers directly in SceneDelegate.
func scene(_ scene: UIScene, willConnectTo session: UISceneSession,
options connectionOptions: UIScene.ConnectionOptions) {
PerformanceLogger.shared.signpostBegin(name: "SceneConnect")
guard let windowScene = (scene as? UIWindowScene) else { return }
let window = UIWindow(windowScene: windowScene)
let composeVC = ComposeViewController()
let navController = UINavigationController(rootViewController: composeVC)
window.rootViewController = navController
window.makeKeyAndVisible()
PerformanceLogger.shared.signpostEnd(name: "SceneConnect")
}
This approach achieves zero Storyboard parse time and zero segue resolution. By instantiating ComposeViewController directly from SceneDelegate, all unnecessary processing is eliminated from the launch path.
3. One Line in viewDidAppear Completes Everything
UIKit's greatest strength is its explicitly defined ViewController lifecycle. When viewDidAppear(_:) is called, the view is fully rendered on screen. Calling becomeFirstResponder() at this precise timing minimizes the delay to keyboard display.
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
activateTextInput()
}
private func activateTextInput() {
textView.becomeFirstResponder()
PerformanceLogger.shared.endTimeToText()
}
Measurement is performed at nanosecond precision using os_signpost. PerformanceLogger.shared.endTimeToText() records the signpost end event, enabling detailed analysis in Instruments.
4. Performance Measurement Infrastructure
Design decisions are based on quantitative data, not subjective impressions of "feeling fast."
- PerformanceLogger: A custom logger wrapping
os_signpost, measuring each phase of launch, send, and UI updates - Instruments Integration: Nanosecond-precision tracking via Xcode's os_signpost Instrument
- Key Metrics: Time-to-Text (launch to input ready), Send-to-Reset (send button to UI reset)
- Continuous Monitoring: Performance measurement on every development build for immediate regression detection
Performance is a feature. Users don't explicitly demand "speed," but they unconsciously stop using slow apps. That's why continuously managing performance through numbers matters.
5. Are There Cases for Using SwiftUI?
It's not UIKit-or-nothing. There are areas within the app where SwiftUI could be appropriate.
- Settings Screens: Non-critical screens that don't affect launch speed could benefit from SwiftUI's declarative productivity
- Critical Path Definition: The core flow of launch → compose → send must remain UIKit
- Hybrid Complexity: Mixing UIKit and SwiftUI requires careful management of UIHostingController and data flow consistency
- Current Decision: The memo app's UI is simple enough that a pure UIKit approach maintains codebase consistency
If SwiftUI's performance improves further in the future, there's room for re-evaluation. But UIKit on the critical path is the policy for the foreseeable future.
Frequently Asked Questions
@StateObject initialization and body re-evaluation. For maximum speed, UIKit's deterministic lifecycle has the advantage.os_signpost and analyzed in detail with Instruments.参考文献 References
- Apple Developer — UIKit Documentation — SimpleMemoのUI構築に採用したフレームワークの公式リファレンスOfficial reference for the framework chosen to build SimpleMemo's UI
- Apple Developer — SwiftUI Documentation — 比較検討した宣言的UIフレームワーク。パフォーマンス測定の対象Declarative UI framework evaluated as an alternative and measured for performance
- Apple Developer — os_signpost / OSSignposter — Time-to-Text計測に使用したInstruments連携のパフォーマンス測定APIPerformance measurement API integrated with Instruments, used for Time-to-Text measurement
- WWDC 2023 — Analyze hangs with Instruments — UIレスポンス最適化の参考にしたWWDCセッションWWDC session referenced for UI responsiveness optimization