Hot Reload — 실행 중인 모바일 애플리케이션의 코드를 다시 시작하지 않고 현재 상태를 잃지 않고 업데이트할 수 있는 기술입니다. 개발자가 소스 코드를 변경하면 1초 이내에 변경 사항이 기기 또는 에뮬레이터 화면에 표시됩니다. 이는 Flutter와 React Native의 핵심 기능으로, 개발 반복을 근본적으로 가속화합니다: 편집-보기 주기 시간이 5~10초(재빌드)에서 300~500밀리초로 단축됩니다. Flutter Documentation, 2025에 따르면, hot reload는 변경된 코드의 증분 컴파일을 수행하고 Dart VM에 업데이트를 전송합니다.
핵심 요점
Hot Reload는 소스 코드를 수정하고 애플리케이션을 중지하지 않고 실행 중인 애플리케이션에 적용하는 개발 메커니즘입니다. 개발자가 파일을 편집하고 저장하면 0.3~2초 내에 업데이트된 인터페이스가 화면에 나타납니다. 애플리케이션 상태(카운터, 스크롤 위치, 입력된 데이터)가 유지되므로 개발자는 컨텍스트를 잃지 않습니다.
hot reload의 개념은 초기 웹 도구(LiveReload, 2010)에서 시작되어 Flutter(2017)와 React Native(2015)에 의해 모바일 개발에 적용되었습니다. 오늘날 hot reload는 디버그 구성 및 프로파일링과 함께 현대 모바일 프레임워크의 필수 기능입니다. hot reload 없이는 UI 개발이 비효율적인 것으로 간주됩니다: 각 변경 검토에 10~30초의 재빌드 및 시작 시간이 필요합니다.
기술적으로 hot reload는 세 단계로 구성됩니다: 변경 감지(파일 감시자), 변경된 코드 컴파일(증분 컴파일러), 적용(핫 패칭). 각 프레임워크는 이러한 단계를 다르게 구현하지만 결과는 동일합니다: 편집과 표시 사이의 최소 지연.
Flutter에서의 Hot Reload는 Dart VM 아키텍처와 JIT 컴파일을 기반으로 합니다. 개발자가 IDE에서 “Hot Reload”를 누르거나 파일을 저장하면 Flutter는 변경된 Dart 라이브러리를 커널 파일(.dill)로 증분 컴파일합니다. Dart VM은 이 파일들을 로드하고 실행 중인 애플리케이션에서 변경된 함수의 구현을 교체합니다.
// Hot reload 중에 상태가 유지되는 Flutter stateful 위젯
class CounterWidget extends StatefulWidget {
@override
State createState() => _CounterState();
}
class _CounterState extends State {
int _counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
Text('카운터: $_counter'),
ElevatedButton(
onPressed: () => setState(() => _counter++),
child: Text('증가'),
),
],
);
}
}
예제에서 StatefulWidget CounterWidget은 hot reload 중에 _counter 필드를 유지합니다. Dart VM은 reassemble()을 호출하여 상태(State)를 다시 만들지만 _counter는 재설정하지 않습니다 — 위젯이 완전히 다시 생성되지 않는 한 값이 유지됩니다. Flutter는 모든 State 객체에 대해 reassemble()을 호출하고 build()는 업데이트된 코드와 유지된 상태로 다시 실행됩니다.
hot reload가 작동하지 않는 경우: 정적 초기화 변수(static const), 전역 변수, main(), enum/mixin 클래스 선언 또는 @override initState()의 코드가 변경된 경우입니다. 이러한 경우 Hot Restart가 필요합니다. Flutter Team(2025)에 따르면 hot reload는 85~90%의 경우 성공하며, 10~15%의 변경은 완전한 재시작이 필요합니다.
Dart VM은 디버그 모드에서 JIT 컴파일러로 작동합니다: 커널 형식(바이트코드와 유사)을 통해 Dart 코드를 해석합니다. Hot reload는 새 커널 파일을 로드하고 이전 함수 정의를 교체합니다. VM은 격리(isolate)를 다시 시작하지 않으며 모든 비동기 작업(Future, Stream)은 계속 실행됩니다. 릴리스 모드에서는 Dart가 AOT(dart2native)로 컴파일되며 hot reload를 사용할 수 없습니다.
Fast Refresh(이전 명칭 Hot Reloading)는 React Native에서 Metro bundler를 사용합니다 — 파일 변경을 모니터링하는 JavaScript 모듈 번들러입니다. 개발자가 파일을 저장하면 Metro는 변경된 모듈만(HMR — Hot Module Replacement) 컴파일하고 WebSocket을 통해 실행 중인 애플리케이션에 업데이트를 전송합니다.
// Hot reload 중 상태를 유지하는 React Native 컴포넌트
import React, { useState } from 'react';
import { View, Text, Button } from 'react-native';
const Counter = () => {
const [count, setCount] = useState(0);
return (
<View>
<Text>카운터: {count}Text>
<Button title="증가"
onPress={() => setCount(c => c + 1)} />
View>
);
};
Fast Refresh는 모듈 업데이트 시 React 상태(useState, useReducer)를 유지합니다. Metro HMR은 변경된 모듈의 diff만 전송하며 전체 번들은 전송하지 않습니다. React Native는 React 팀(Dan Abramov, 2019)이 개발한 React Fast Refresh를 사용합니다: 컴포넌트에 대한 새 렌더를 생성하지만 컴포넌트 시그니처가 변경되지 않은 경우 hook 상태와 props를 유지합니다.
Fast Refresh는 다음 변경 시 작동하지 않습니다: 컴포넌트 내보내기, hooks(useEffect, useMemo), 모듈 종속성 및 네이티브 모듈(Java/Objective-C) 변경. 이러한 변경에는 Reload(전체 JS 번들 다시 로드) 또는 Rebuild(네이티브 코드 재빌드)가 필요합니다. Fast refresh 시간은 200~800ms, 전체 reload는 2~5초입니다.
Hot Reload와 Hot Restart는 다른 사용 시나리오를 가진 두 가지 코드 업데이트 모드입니다. Hot Reload는 클래스 구조와 상태 유형이 변경되지 않는 UI 변경(스타일, 레이아웃, 색상, 텍스트)에 적합합니다. Hot Restart는 메서드 시그니처 변경, 루트 트리에 새 위젯/컴포넌트 추가, initState 및 네이티브 모듈 변경 시 필요합니다.
| 특성 | Hot Reload | Hot Restart |
|---|---|---|
| 속도 | 0.3~2초 | 2~10초 |
| 상태 유지 | 예 (변수, state, 탐색 스택) | 아니요 (애플리케이션 새로 시작) |
| 컴파일 | 증분 (변경만) | 전체 Dart/JS 재컴파일 |
| 사용 시기 | UI 조정, 스타일, 텍스트, 레이아웃 | 구조적 변경, 새 모듈, 네이티브 코드 |
| Flutter | Hot Reload (R) | Hot Restart (Shift + R) |
| React Native | Fast Refresh | Reload (Cmd + R) |
권장 전략: hot reload로 시작합니다. 변경 사항이 적용되지 않는 경우(IDE에 “Reload needed” 표시) — hot restart를 수행합니다. Flutter에서 버튼 아이콘이 변경됩니다: 번개(⚡)는 hot reload, 취소선 번개는 restart가 필요한 경우입니다. 전체 재빌드와 비교하여 hot reload를 사용한 개발 효율성은 40~60% 더 높습니다(JetBrains Developer Survey 2024 데이터).
코드 주입(code injection)은 모든 프레임워크에서 사용되는 hot reload의 일반적인 메커니즘입니다. 세 단계로 구성됩니다. 첫 번째 — 변경 감지: 파일 감시자(IDE에 내장) 또는 파일 시스템(FSNotify)이 .dart, .js, .tsx 파일의 변경을 감지합니다. 두 번째 — 컴파일: 증분 컴파일러가 변경된 파일만 중간 표현(Dart의 경우 커널 .dill, JS의 경우 HMR 모듈)으로 변환합니다. 세 번째 — 적용: 새 코드가 기기로 전송되고 실행 중인 애플리케이션의 메모리에서 이전 정의를 교체합니다.
핫 패칭(hot patching)은 런타임이 가상 메서드 테이블의 함수 포인터를 교체하는 기술입니다. Dart VM은 ClassTable — 로드된 모든 클래스를 포함하는 내부 구조를 사용합니다. Hot reload 중에 VM은 ClassTable에서 클래스를 찾고 커널 파일의 새 함수 정의로 해당 함수 정의를 교체합니다. 기존 클래스 인스턴스는 모두 자동으로 새 동작을 얻습니다.
// Flutter: hot reload 후 상태 관리를 위한 reassemble 콜백
class MyWidget extends StatefulWidget {
@override
State createState() => _MyState();
}
mixin ReloadAware on State {
@override
void reassemble() {
super.reassemble();
// Hot reload 후 캐시 또는 데이터 재설정
clearCache();
}
}
예제에서 ReloadAware 믹스인은 reassemble() 메서드를 재정의하며, Dart VM은 hot reload 후 각 State 객체에서 이를 호출합니다. 개발자는 캐시를 재설정하거나, 리소스를 다시 초기화하거나, 상태 마이그레이션을 수행할 수 있습니다. 이 메서드가 없으면 이전 데이터가 캐시에 남아 위젯 업데이트 후 불일치를 일으킬 수 있습니다.
핫 패칭은 새 필드에 대한 메모리 재할당, 클래스의 변수 유형 변경, StatefulWidget에 새 필드 추가, enum 값 또는 제네릭 매개변수 변경이 필요한 변경에는 작동하지 않습니다. 이러한 변경은 메모리의 기존 객체와 호환되지 않습니다 — Dart VM은 이미 할당된 객체의 필드를 “섞을” 수 없습니다. 이러한 경우 hot restart 또는 전체 재빌드가 필요합니다.
네이티브 Android 및 iOS 개발에는 전통적으로 완전한 hot reload가 없습니다. Android Studio는 Android 11+ 및 AGP 4.2+와 함께 Apply Changes를 지원합니다: 애플리케이션을 다시 시작하지 않고 코드 업데이트. Apply Changes는 Android Runtime(ART)을 통해 작동하며 실행 중에 dex 파일의 메서드 구현을 교체합니다. 그러나 Apply Changes는 제한적입니다: 리소스 변경(layout.xml, drawable), 매니페스트 및 네이티브 라이브러리에는 작동하지 않습니다.
Apple은 Xcode 15(2023)에서 Previews(SwiftUI Preview)를 도입했습니다 — 이는 고전적인 의미의 hot reload가 아닙니다. Previews는 기본 애플리케이션과 별도로 미리보기 섹션을 컴파일하고 Xcode 캔버스에 결과를 표시합니다. 파일 저장 시 미리보기는 1~3초 내에 업데이트되지만 애플리케이션 상태는 유지되지 않습니다. UIKit 프로젝트의 경우 hot reload는 타사 도구(InjectionIII(John Holdsworth), SwiftHotReload)를 통해 사용할 수 있습니다.
Kotlin Multiplatform(KMP)은 2024년부터 JetBrains의 실험적 hot reload 지원을 받았습니다. 메커니즘은 객체 파일(.klib)에서 함수 교체와 함께 Kotlin/Native 런타임을 기반으로 합니다. JetBrains Compose Multiplayer는 Flutter와 유사한 자체 hot reload 구현을 사용합니다: Kotlin/Native 런타임에서 증분 컴파일 및 클래스 교체. 속도는 1~3초이며 UI 변경에만 사용할 수 있습니다.
Apply Changes는 ART 런타임 API를 사용하는 Android Studio의 메커니즘입니다. 코드 저장 시 Android Studio는 어떤 클래스가 변경되었는지 확인하고 adb를 통해 해당 dex 파일을 기기로 전송합니다. ART는 중지하지 않고 실행 중인 애플리케이션의 메서드 구현을 교체합니다. Apply Changes는 세 가지 모드로 작동합니다: Instant Run(빠른 메서드 교체), Swap(인스턴스 재생성과 함께 클래스 교체), Restart Activity(변경 사항이 현재 상태와 호환되지 않는 경우).
자주 묻는 질문
Hot Reload는 애플리케이션을 다시 시작하지 않고 코드를 업데이트하고 상태를 유지합니다. Live Reload는 파일 변경 시 전체 애플리케이션 또는 웹 페이지를 다시 로드합니다. Live Reload는 구현이 간단하지만 느리고 상태를 잃습니다. Flutter와 React Native는 hot reload를 사용하고, 웹 도구는 live reload를 사용합니다.
Hot Reload는 메모리 재할당이 필요한 변경(새 클래스 필드), 정적 상수(static const) 변경, 위젯 이름 변경, enum 또는 제네릭 매개변수 변경 시 작동하지 않습니다. 이러한 변경은 Dart VM 또는 JavaScript 런타임 메모리의 기존 객체와 호환되지 않습니다.
네, hot reload는 실제 기기와 에뮬레이터 모두에서 작동합니다. Flutter는 USB(adb forward) 또는 Wi-Fi를 통해 기기에 커널 파일을 전송합니다. React Native는 Metro bundler를 통해 WebSocket을 사용합니다. 실제 기기에서의 지연은 일반적으로 에뮬레이터보다 10~30% 더 높습니다.
Xcode Previews(2021년 이후)는 SwiftUI용 hot reload의 유사 기능이지만 제한 사항이 있습니다: 미리보기가 별도로 컴파일되고, 앱 탐색 및 복잡한 상태를 지원하지 않습니다. Apple은 iOS용 공식 hot reload를 제공하지 않습니다. 타사 도구: InjectionIII와 SwiftHotReload는 코드 주입에 Objective-C Runtime을 사용합니다.
hot reload 후 UI가 잘못 표시되면: hot restart를 수행합니다. 데이터에 문제가 있는 경우 — Flutter의 reassemble() 콜백 또는 React Native의 useEffect cleanup을 확인합니다. 지속적인 문제의 경우 Flutter Clean 또는 Reset Metro Cache를 사용합니다. 버그가 reload 후에만 재현되는 경우 — 이는 기존 상태와 변경 사항의 비호환성 신호입니다.
요약
턴키 방식의 모바일 애플리케이션을 개발해 드립니다
IT Sectr는 2017년부터 스타트업과 기업을 위한 iOS 및 Android 애플리케이션을 만듭니다. 저희가 상담해 드리고 최적의 솔루션을 제안하겠습니다.