Skip to main content
네이티브
입문네이티브React Native 플랫폼 · 9

운영체제의 프로세스 종료: 메모리 초안은 왜 복원되지 않는가

앱이 화면 뒤에 있는 동안 프로세스가 종료될 수 있다는 전제에서, React state와 실행 중 상태를 다시 만들고 사용자 초안만 영속 저장소에서 복원하는 기준을 익힌다.

마지막 검증 재검증 정책: 제품 버전 의존: 새 주요 버전마다 재검증
목차
표준·구현·측정·해석 표시는 무엇인가요?
  • 표준웹 표준이나 언어 명세가 정한 동작
  • 구현특정 기술이나 브라우저가 실제로 구현한 동작
  • 측정명시한 환경에서 직접 실행해 관찰한 결과
  • 해석앞선 근거에서 도출한 설계 판단
  • 미확인아직 공식 근거나 재현 결과를 확인하지 못한 내용

30초 요약

앱이 background에 있다는 말은 프로세스가 계속 살아 있다는 보장이 아니다. 운영체제가 프로세스를 종료하면 React state, JavaScript 변수, 타이머와 구독도 함께 사라진다. 다음 실행에서는 화면과 연결을 새로 만들고, 사용자가 잃으면 안 되는 최소 입력만 영속 저장소에서 읽어 복원한다.

다시 만든다  컴포넌트, 실행 중 표시, 구독, 카메라·네트워크 연결
저장한다      사용자가 작성한 초안, 복원할 항목의 식별값
다시 가져온다 서버가 원본인 데이터
저장하지 않는다 이전 실행의 loading·running 같은 순간 상태

마지막 cleanup에서 저장하려고 기다리지 않는다. 사용자가 저장을 확정한 시점에 쓰기가 성공했는지 확인하고, 새 실행에서는 복원이 끝나기 전 빈 화면을 최종값처럼 취급하지 않는다.

이 글을 관통하는 상황: 배송 메모가 재실행 뒤 비어 있다

배송 메모 화면은 사용자가 적은 문자열을 React state에만 둔다. TextInput은 사용자가 글을 입력하는 React Native 요소이고, onChangeText는 현재 입력 문자열을 함수에 전달한다.

import { useState } from 'react'
import { Text, TextInput, View } from 'react-native'
 
const runId = Date.now()
 
export default function App() {
  const [draft, setDraft] = useState('')
 
  return (
    <View style={{ padding: 24, gap: 12 }}>
      <Text>현재 실행 ID: {runId}</Text>
      <TextInput
        accessibilityLabel="배송 메모"
        value={draft}
        onChangeText={setDraft}
        placeholder="예: 현관 앞에 놓아주세요"
      />
      <Text>현재 초안: {draft || '비어 있음'}</Text>
    </View>
  )
}

먼저 결과를 예측한다.

  1. 현관 앞에 놓아주세요를 입력하고 Home으로 갔다가 바로 돌아오면 값이 남을 수 있는가?
  2. 현재 앱 프로세스를 종료하고 새로 실행하면 runId는 같은가?
  3. 새 JavaScript 실행이 이전 useState('')의 값을 어디에서 다시 찾을 수 있는가?

Home으로 갔다가 곧바로 돌아오는 동안 프로세스가 유지되면 초안도 남는다. 그러나 Android 기기· 에뮬레이터에 명령을 보내는 Android Debug Bridge(adb)의 force-stop으로 현재 Android 앱 실행을 강제로 끝내거나, iOS 개발 도구인 Xcode의 Stop으로 현재 iOS 앱 실행을 끝낸 뒤 앱을 삭제하거나 다시 설치하지 않고 재실행하면 다음 결과를 관찰한다.

종료 전  실행 ID=첫 값, 현재 초안=현관 앞에 놓아주세요
재실행  실행 ID=새 값, 현재 초안=비어 있음

이 실패는 TextInput이 값을 잃은 문제가 아니다. 새 프로세스에는 이전 React state를 가진 JavaScript 메모리가 없고, 코드도 복원할 저장 위치를 제공하지 않았다.

background에 있던 프로세스는 사라질 수 있다

8편의 AppState 구독은 살아 있는 실행 안에서 activebackground 전환에 반응했다. 프로세스가 종료되면 그 구독을 실행하던 JavaScript 자체가 사라진다. 다음 실행에서는 Effect setup과 외부 연결을 처음부터 다시 만든다.

종료 cleanup을 마지막 저장 기회로 삼지 않는다

사용자가 저장을 눌렀다면 저장 작업의 성공을 화면에서 확인한 뒤에만 “보존됐다”고 판단한다. 자동 저장이 필요한 제품은 의미 있는 입력 변경 시점에 저장하되, 저장 빈도와 동시 쓰기 정책을 따로 설계한다. 종료 순간에 아직 끝나지 않은 Promise가 마무리될 것이라고 가정하지 않는다.

복원에 필요한 최소 입력만 저장한다

화면 state 전체가 아니라 새 실행에서 화면을 다시 만들 입력을 분류한다.

상태처리이유
배송 메모 초안저장사용자가 다시 입력해야 하면 손실이다
편집 중인 주문 ID필요하면 저장원본 주문을 다시 가져올 위치다
서버의 주문 상세다시 요청서버가 원본이며 오래된 사본일 수 있다
저장 중 표시다시 계산이전 실행의 작업은 새 실행에서 계속되지 않는다
카메라 running다시 연결실제 자원 상태를 새 실행에서 확인해야 한다
인증 비밀값이 저장소에 저장하지 않음보안 저장소 경계는 17~18편에서 분리한다

문자열 초안을 저장하고 새 실행에서 복원한다

이 실습은 민감하지 않은 짧은 배송 메모에 비암호화 영속 키-값 저장소인 React Native Async Storage 3.1.1을 사용한다. 앱 프로젝트 루트에서 JavaScript 패키지를 설치한다. 이어서 ios 디렉터리에서 iOS 네이티브 의존성 설치 도구인 CocoaPods의 pod install을 실행하고 프로젝트 루트로 돌아온 뒤 양쪽 앱을 재빌드한다.

pnpm add @react-native-async-storage/[email protected]
cd ios
pod install
cd ..

App.tsx를 다음 완결 코드로 바꾼다. 복원 중에는 입력과 저장을 막고 초안 값도 아직 표시하지 않아, 읽기 전의 빈 문자열을 사용자의 최종 초안처럼 다루지 않는다. 코드의 void restoreDraft()에서 함수 호출은 복원 작업을 시작하고, JavaScript의 void 연산자는 반환된 Promise를 이 자리에서 사용하지 않는다는 뜻으로 undefined로 버린다. 저장 완료가 표시된 뒤에만 프로세스 종료 실험을 시작한다.

import { useEffect, useState } from 'react'
import { Pressable, Text, TextInput, View } from 'react-native'
import { createAsyncStorage } from '@react-native-async-storage/async-storage'
 
const draftStorage = createAsyncStorage('delivery-draft')
const DRAFT_KEY = 'delivery-note'
const runId = Date.now()
 
export default function App() {
  const [draft, setDraft] = useState('')
  const [status, setStatus] = useState('복원 중')
 
  useEffect(() => {
    let cancelled = false
 
    async function restoreDraft() {
      try {
        const stored = await draftStorage.getItem(DRAFT_KEY)
        if (cancelled) return
 
        if (stored !== null) setDraft(stored)
        setStatus(stored === null ? '저장된 초안 없음' : '초안 복원 완료')
      } catch {
        if (!cancelled) setStatus('초안 복원 실패')
      }
    }
 
    void restoreDraft()
 
    return () => {
      cancelled = true
    }
  }, [])
 
  const isRestoring = status === '복원 중'
  const isBusy = isRestoring || status === '저장 중'
 
  async function saveDraft() {
    setStatus('저장 중')
    try {
      await draftStorage.setItem(DRAFT_KEY, draft)
      setStatus('저장 완료')
    } catch {
      setStatus('저장 실패')
    }
  }
 
  function changeDraft(nextDraft: string) {
    setDraft(nextDraft)
    setStatus('저장되지 않은 변경')
  }
 
  return (
    <View style={{ padding: 24, gap: 12 }}>
      <Text>현재 실행 ID: {runId}</Text>
      <TextInput
        accessibilityLabel="배송 메모"
        editable={!isBusy}
        value={draft}
        onChangeText={changeDraft}
        placeholder="예: 현관 앞에 놓아주세요"
      />
      <Pressable disabled={isBusy} onPress={() => void saveDraft()}>
        <Text>초안 저장</Text>
      </Pressable>
      <Text>상태: {status}</Text>
      <Text>
        현재 초안: {isRestoring ? '복원 중' : draft || '비어 있음'}
      </Text>
    </View>
  )
}

cancelled는 저장 작업을 취소하는 기능이 아니다. 복원 Promise가 끝나기 전에 이 컴포넌트가 제거됐을 때 지난 결과로 state를 바꾸지 않게 막는 표시다.

결정적 재시작과 실제 운영체제 종료를 구분한다

먼저 앱을 삭제하거나 다시 설치하지 않은 상태에서 다음 공통 순서를 실행한다.

  1. 현관 앞에 놓아주세요를 입력한다.
  2. 초안 저장을 누르고 저장 완료를 확인한다.
  3. 실행 ID와 초안을 기록한다.
  4. 현재 앱 실행을 끝내고 같은 설치의 앱을 다시 연다.
  5. 실행 ID가 바뀌고 상태가 초안 복원 완료, 초안이 이전 문자열인지 확인한다.

Android Emulator에서는 application ID(운영체제가 Android 앱 설치를 구분하는 패키지 식별자)를 실제 값으로 바꿔, 앱 프로젝트 루트의 터미널에서 다음 명령을 실행한 뒤 런처에서 앱을 다시 연다.

adb shell am force-stop com.example.receiptfixture

iOS Simulator에서는 Xcode 도구 막대의 Stop으로 실행을 끝낸 뒤 Run으로 다시 연다. 두 방법 모두 저장·복원 계약은 확인하지만 낮은 메모리, 기기별 백그라운드 체류 시간, 실제 운영체제 선택 정책은 증명하지 않는다. 그 조건이 제품 합격 기준이면 지원 실기기에서도 같은 저장→종료→재실행 결과를 확인하고, 운영 중에는 복원 실패와 저장 실패를 별도 신호로 관찰한다.

기대 결과는 다음과 같다.

첫 실행     새 입력 → 저장 완료
실행 종료   현재 JavaScript 메모리와 React state 소멸
다음 실행   새 실행 ID → 초안 복원 완료 → 현관 앞에 놓아주세요

단순히 문자열이 보인다는 것만으로 충분하지 않다. 두 번째 검증에서는 현관 앞에 놓아주세요를 저장해 저장 완료를 확인한 뒤, 끝에 !를 더 입력한다. 화면 상태가 저장되지 않은 변경으로 바뀐 것을 확인하고 저장 버튼을 다시 누르지 않은 채 종료한다. 다음 실행에는 !가 없는 마지막 저장 완료 값만 복원돼야 한다. 이 차이로 메모리 값과 영속 값을 구분한다.

같은 상황을 다시 진단한다

문제       프로세스 종료 뒤 배송 메모가 비어 있음
잘못된 판단 background에서도 React state가 다음 실행까지 유지됨
관찰       실행 ID가 바뀌고 useState의 초기 빈 문자열이 표시됨
원인       사용자 초안을 메모리에만 두고 복원 입력을 저장하지 않음
행동       사용자가 확정한 초안을 영속 저장소에 쓰고 새 실행에서 먼저 읽음
검증       저장 완료 값은 복원되고 저장하지 않은 최신 메모리 값은 복원되지 않음

풀 리퀘스트에서는 다음을 확인한다.

  • AppState background와 프로세스 종료 뒤 새 실행을 같은 사건으로 취급하지 않았는가?
  • 사용자 손실로 이어지는 최소 입력과 다시 계산할 실행 상태를 분리했는가?
  • 종료 cleanup이 아니라 의미 있는 사용자 행동 시점에 저장 완료를 확인하는가?
  • 복원 전 빈 기본값으로 저장 데이터를 덮어쓰거나 최종 화면을 먼저 보여 주지 않는가?
  • 저장 오류와 복원 오류를 사용자·관측 신호에서 구분하는가?
  • 민감한 인증 정보를 비암호화 일반 저장소에 넣지 않았는가?
  • 결정적 프로세스 재시작과 실제 운영체제 자원 회수 조건의 증명 범위를 구분했는가?

기억할 문장은 짧다.

프로세스는 다시 만들고, 사용자가 잃으면 안 되는 최소 입력만 미리 저장한다.

다음 10편에서는 저장한 데이터의 형식이 앱 업그레이드로 바뀔 때 기존 값을 어떻게 읽고 새 형식으로 옮길지 다룬다.

Active recall

기억에서 꺼내 보기

답을 쓰지 않아도 됩니다. 먼저 머릿속으로 답한 뒤 펼쳐서 정답·이유·흔한 오해를 비교하세요.

  1. 01앱이 background에 있는 동안 운영체제가 프로세스를 종료하면 React state는 그대로 남아 있을까?

    정답

    아니다. 새 프로세스와 JavaScript 실행이 시작되므로 메모리 state와 실행 중 작업은 다시 만들어야 한다.

    왜 그런가

    background 복귀와 프로세스 종료 뒤 새 실행은 화면 모양이 비슷해도 다른 경계다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  2. 02사용자가 입력한 중요한 초안을 프로세스 종료 cleanup에서만 저장해도 될까?

    정답

    아니다. 종료 시 마지막 cleanup 기회에 의존하지 말고, 의미 있는 사용자 행동이 끝난 시점에 저장 완료를 확인해야 한다.

    왜 그런가

    운영체제 종료에서는 React cleanup이나 플랫폼 종료 callback이 항상 현재 JavaScript 작업을 끝낼 기회를 주지 않는다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  3. 03배송 메모 화면에서 무엇을 저장하고 무엇을 다시 계산해야 할까?

    정답

    사용자가 잃으면 안 되는 초안 문자열은 저장하고, 저장 중 표시·구독·카메라 세션 같은 실행 상태는 새 실행에서 다시 계산하거나 연결한다.

    왜 그런가

    모든 화면 state를 그대로 저장하기보다 복원에 필요한 최소 입력을 저장해야 오래된 실행 상태를 되살리는 오류가 줄어든다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

  4. 04에뮬레이터의 force-stop이나 Xcode Stop 성공만으로 실제 메모리 압박 종료 정책까지 검증했다고 볼 수 있을까?

    정답

    아니다. 현재 프로세스를 없앤 뒤 저장·복원되는 계약은 검증하지만, 운영체제가 언제 종료 대상을 고르는지는 재현하지 않는다.

    왜 그런가

    결정적 재시작 실험과 실제 기기 운영 조건은 증명 범위가 다르다.

    관련 설명 다시 읽기
    지금 어느 정도 기억났나요?

    선택한 상태와 다음 복습일은 이 브라우저에만 저장됩니다.

출처와 검증 범위

아래 날짜는 링크를 마지막으로 열어 본 날입니다. 문서 상단의 검증일은 글의 설명과 적용 범위를 다시 확인한 날입니다.

  1. Processes and app lifecycleAndroid Developers · 공식 문서 · 확인 2026-08-09
  2. Save UI statesAndroid Developers · 공식 문서 · 확인 2026-08-09
  3. Android Debug Bridge (adb)Android Developers · 공식 문서 · 확인 2026-08-09
  4. Managing your app's life cycleApple Developer · 공식 문서 · 확인 2026-08-09
  5. Preserving your app's UI across launchesApple Developer · 공식 문서 · 확인 2026-08-09
  6. Running your app on simulated or physical devicesApple Developer · 공식 문서 · 확인 2026-08-09
  7. React Native Async StorageReact Native Async Storage · 공식 문서 · 확인 2026-08-09
  8. Using Async StorageReact Native Async Storage · 공식 문서 · 확인 2026-08-09
  9. Async Storage ChangelogReact Native Async Storage · 공식 문서 · 확인 2026-08-09
이 문서의 마지막까지 읽었습니다.