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のライフサイクルは明確で、いつキーボードが表示されるかを正確に制御できる。

設計判断:Time-to-Text 500ms以下という非機能要件を確実に満たすため、UIKitを採用。実機計測で200〜300msを安定して達成。

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での詳細な分析を可能にしている。

計測結果:目標 500ms以下 → 実機実測値 200〜300msで安定動作。ユーザーがアプリアイコンをタップしてから、テキスト入力が可能になるまでわずか0.2〜0.3秒。

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は当面維持する方針だ。

よくある質問

Q. SwiftUIでは500ms以下の起動は無理ですか?
不可能ではないが、UIKitのほうが確実にミリ秒単位で速い。@StateObject初期化やbody再評価のオーバーヘッドがある。最速を追求するなら、UIKitの決定論的なライフサイクルが有利。
Q. Storyboardを使わないデメリットは?
UIの可視化がコードベースになる。Interface Builderでのドラッグ&ドロップ設計ができなくなる。ただしメモアプリのUIはシンプルなので、コードベースのUI構築で問題にならない。
Q. 実機での計測結果は?
目標500ms以下に対し、実機で200〜300ms台で安定。os_signpostでナノ秒単位で計測し、Instrumentsで詳細分析を実施している。
Q. 今後SwiftUIに移行する予定は?
クリティカルパス(起動→入力→送信)はUIKitを維持する。設定画面などの非クリティカルな画面については、SwiftUIの採用を検討する余地はある。

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.

Design Decision: UIKit was chosen to reliably meet the non-functional requirement of Time-to-Text under 500ms. Real device measurements consistently achieve 200-300ms.

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.

Measurement Results: Target 500ms or less → Actual device measurement: stable at 200-300ms. From the moment a user taps the app icon to text input readiness takes only 0.2-0.3 seconds.

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

Q. Can't SwiftUI achieve sub-500ms launch?
It's not impossible, but UIKit is reliably faster by milliseconds. There's overhead from @StateObject initialization and body re-evaluation. For maximum speed, UIKit's deterministic lifecycle has the advantage.
Q. What are the downsides of not using Storyboard?
UI visualization becomes code-based. You lose Interface Builder's drag-and-drop design capability. However, for a memo app with a simple UI, code-based UI construction presents no issues.
Q. What are the real device measurements?
Against the target of under 500ms, real device measurements are stable in the 200-300ms range. Measured at nanosecond precision with os_signpost and analyzed in detail with Instruments.
Q. Plans to migrate to SwiftUI?
The critical path (launch → input → send) will remain UIKit. Non-critical screens like settings may be considered for SwiftUI adoption.

参考文献 References