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〜約1秒。

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の採用を検討する余地はある。