1. 왜 2026년에 UIKit인가요

Apple은 매년 WWDC에서 SwiftUI의 진화를 강조하고 있으며, 선언적인 UI의 편리성은 이제 의심의 여지가 없습니다.

하지만, 이 앱에는 양도할 수 없는 요구사항이 있습니다. 그것은 Time-to-Text(시작부터 텍스트 입력이 가능해질 때까지의 시간)를 500ms 이하로 만드는입니다.

SwiftUI에서는 @StateObject의 초기화, body의 재평가, .onAppear의 타이밍 제어 등 선언적 UI 프레임워크 고유의 오버헤드가 존재합니다. 이것들은 일반적인 앱에서는 무시할 수 있는 정도이지만, 밀리초 단위의 최적화를 추구하는 상황에서는 무시할 수 없습니다.

한편, UIKit의 viewDidAppear에서 becomeFirstResponder()을 호출하는 경로는결정론적으로 고속이다. ViewController의 라이프사이클은 명확하며, 언제 키보드가 표시되는지를 정확하게 제어할 수 있습니다.

설계 판단:Time-to-Text 500ms 이하라는 비기능 요구 사항을 충족하기 위해 UIKit을 채택했습니다.

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의 파싱 시간 0, Segue 해결 시간 0을 구현하고 있습니다. 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 이하입니다. 아이콘 탭 후의 모습을 화면 녹화로 측정한 기록(2026년 8월 11일, iPhone 16e·웜 실행)에서는 입력란에 대한 포커스가 0.17초, 키보드 표시 완료가 0.4초였습니다.

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 이하의 실행이 불가능한가요?
불가능하지 않습니다. 다만, SwiftUI에서는@StateObject의 초기화 및body의 재평가만큼 처리가 추가됩니다. 가장 빠른 속도를 목표로 하는 이 개발에서는 라이프사이클을 세밀하게 제어할 수 있는 UIKit을 선택했습니다(두 가지를 동일한 조건으로 측정하고 비교한 결과는 아닙니다).
Q. Storyboard를 사용하지 않는 단점은 무엇인가요?
UI의 시각화가 코드베이스가 됩니다. Interface Builder에서 드래그 앤 드롭 설계가 불가능해집니다. 다만 메모 앱의 UI는 단순하므로 코드 기반 UI 구축에 문제가 되지 않습니다.
Q. 실제 기기에서의 측정 결과는?
목표는 500ms 이하입니다. 기록이 남아 있는 측정은 아이콘 탭에서의 모습을 화면 녹화로 측정한 것입니다(2026년 8월 11일, iPhone 16e·웜 실행)이며, 입력란에 대한 포커스는 0.17초, 키보드 표시 완료는 0.4초입니다. 개발 중에는os_signpost으로 구간을 기록하고, Instruments로 분석하고 있습니다.
Q. 앞으로 SwiftUI로 전환할 예정인가요?
크리티컬 경로(시작→입력→전송)는 UIKit을 유지합니다. 설정 화면 등 비크리컬한 화면에 대해서는 SwiftUI 도입을 검토할 여지가 있습니다.