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

React Native background 작업: 화면이 없어도 일을 어떻게 이어 가는가

업로드를 메모리와 타이머에만 둬 Reload 뒤 잃는 실패를 재현하고, 끝나지 않은 작업을 앱 재시작 뒤에도 읽을 목록·재실행 안전성·Android와 iOS의 서로 다른 예약 책임을 구분한다.

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

30초 요약

영속 작업 대기열은 아직 끝나지 않은 작업의 최소 입력을 앱 재시작 뒤에도 읽도록 저장한 목록이다. 화면이 background로 갔다는 사실은 JavaScript가 계속 실행된다는 뜻이 아니다. 이 글에서 scheduler는 network·전원 같은 조건이 맞을 때 실행 기회를 주는 운영체제 예약 장치이고, worker는 그 기회에 영속 저장된 작업을 읽어 처리하는 코드다. 잃으면 안 되는 작업은 현재 React state나 타이머가 아니라 영속 작업 대기열에 먼저 기록하고, 그 뒤 scheduler에 실행 기회를 요청한다.

사용자 행동       최소 작업 입력과 고정 작업 ID를 영속 저장
scheduler 등록    network·전원 같은 실행 조건과 함께 운영체제에 요청
worker 시작       새 프로세스여도 영속 대기열에서 입력을 다시 읽음
중단·재실행       같은 작업 ID로 다시 실행해도 서버 효과는 한 번
완료              서버 확인 뒤에만 로컬 대기 항목 제거

반복해서 기억할 문장은 이것이다.

작업을 먼저 저장하고 실행을 나중에 요청하며, worker는 다시 실행돼도 안전하게 만든다.

이 글을 관통하는 상황: 영수증 업로드를 background 전환 때 시작했다

사용자가 receipt-42 저장을 확정한 직후 Home으로 이동한다. 잘못된 구현은 업로드 항목을 React state에만 넣고 AppState의 background callback이나 setTimeout에서 전송을 시작한다.

이 글의 질문은 하나다.

화면과 현재 JavaScript 실행이 사라져도 업로드 의도를 잃지 않으면서, 재실행돼도 중복 효과 없이 어떻게 이어 가는가?

background 상태와 background 실행 권한은 다르다

AppState change          상태 전환을 관찰
Promise·setTimeout       현재 JavaScript 실행 안에서 나중 작업을 예약
운영체제 예약 장치         조건이 맞을 때 앱·worker에 실행 기회를 제공
영속 작업 대기열          새 프로세스가 읽을 작업 입력을 보관

background callback은 카메라를 멈추거나 보이는 동안의 연결을 정리하는 데 유용하다. 하지만 “이제부터 10초 동안 업로드를 끝낼 수 있다”는 허가가 아니다. 프로세스가 끝나기 직전 callback에서만 작업을 저장하는 것도 9편에서 확인한 종료 cleanup 오판을 반복한다.

실행 기준선을 만든다

9편에서 @react-native-async-storage/[email protected]을 설치해 배송 메모 복원을 확인한 같은 React Native 프로젝트 루트를 이어 쓴다. 그 프로젝트의 App.tsx를 교체한다. 이 글의 고정 영수증과 서버 기록은 권한 없는 로컬 fixture다. 실제 사용자 정보, 영수증 내용, 운영 요청 주소나 인증값은 저장하거나 출력하지 않는다.

worker 실행 대역 버튼은 운영체제 scheduler가 worker를 시작한 사건을 손으로 재현한다. 실제 WorkManager나 Background Tasks가 등록됐다는 증거가 아니다.

UploadJobServerFixture라는 두 type은 각각 저장할 작업과 가짜 서버 기록의 모양을 TypeScript에 알린다. JSON.parse 결과는 검사하기 전에는 믿지 않는 값인 unknown으로 받고, 뒤의 속성 검사는 손상된 fixture 문자열을 작업으로 쓰지 않게 거른다.

App.tsx를 다음 완결 코드로 교체한다. PERSIST_BEFORE_SCHEDULE = false가 잘못된 기준선이다.

import React, { useState } from 'react'
import { Button, SafeAreaView, StyleSheet, Text, View } from 'react-native'
import AsyncStorage from '@react-native-async-storage/async-storage'
 
const PERSIST_BEFORE_SCHEDULE = false
const SIMULATE_STOP_AFTER_SERVER_APPLY = false
const OUTBOX_KEY = '@background/receipt-upload'
const SERVER_KEY = '@fixture/applied-receipt-uploads'
 
type UploadJob = {
  id: string
  receiptId: string
}
 
type ServerFixture = {
  appliedJobIds: string[]
  applyCount: number
}
 
const FIXED_JOB: UploadJob = {
  id: 'receipt-42-upload-v1',
  receiptId: 'receipt-42',
}
 
function parseUploadJob(raw: string | null): UploadJob | null {
  if (raw === null) {
    return null
  }
 
  try {
    const input: unknown = JSON.parse(raw)
 
    if (
      typeof input === 'object' &&
      input !== null &&
      'id' in input &&
      typeof input.id === 'string' &&
      'receiptId' in input &&
      typeof input.receiptId === 'string'
    ) {
      return { id: input.id, receiptId: input.receiptId }
    }
  } catch {
    return null
  }
 
  return null
}
 
function parseServerFixture(raw: string | null): ServerFixture {
  if (raw === null) {
    return { appliedJobIds: [], applyCount: 0 }
  }
 
  try {
    const input: unknown = JSON.parse(raw)
 
    if (
      typeof input === 'object' &&
      input !== null &&
      'appliedJobIds' in input &&
      Array.isArray(input.appliedJobIds) &&
      input.appliedJobIds.every((id) => typeof id === 'string') &&
      'applyCount' in input &&
      typeof input.applyCount === 'number'
    ) {
      return {
        appliedJobIds: input.appliedJobIds,
        applyCount: input.applyCount,
      }
    }
  } catch {
    return { appliedJobIds: [], applyCount: 0 }
  }
 
  return { appliedJobIds: [], applyCount: 0 }
}
 
async function applyOnFixtureServer(job: UploadJob): Promise<ServerFixture> {
  const current = parseServerFixture(await AsyncStorage.getItem(SERVER_KEY))
 
  if (current.appliedJobIds.includes(job.id)) {
    return current
  }
 
  const next = {
    appliedJobIds: [...current.appliedJobIds, job.id],
    applyCount: current.applyCount + 1,
  }
 
  await AsyncStorage.setItem(SERVER_KEY, JSON.stringify(next))
  return next
}
 
export default function App() {
  const [status, setStatus] = useState('아직 실행하지 않음')
  const [memoryJob, setMemoryJob] = useState<UploadJob | null>(null)
  const [persistedJob, setPersistedJob] = useState(false)
  const [serverApplyCount, setServerApplyCount] = useState(0)
  const [busy, setBusy] = useState(false)
 
  async function inspectFixture() {
    const [jobRaw, serverRaw] = await Promise.all([
      AsyncStorage.getItem(OUTBOX_KEY),
      AsyncStorage.getItem(SERVER_KEY),
    ])
 
    setPersistedJob(parseUploadJob(jobRaw) !== null)
    setServerApplyCount(parseServerFixture(serverRaw).applyCount)
  }
 
  async function run(action: () => Promise<void>) {
    setBusy(true)
 
    try {
      await action()
    } catch {
      setStatus('fixture 작업 오류')
    } finally {
      setBusy(false)
    }
  }
 
  async function resetFixture() {
    await run(async () => {
      await Promise.all([
        AsyncStorage.removeItem(OUTBOX_KEY),
        AsyncStorage.removeItem(SERVER_KEY),
      ])
      setMemoryJob(null)
      await inspectFixture()
      setStatus('fixture 초기화 완료')
    })
  }
 
  async function enqueueUpload() {
    await run(async () => {
      if (PERSIST_BEFORE_SCHEDULE) {
        await AsyncStorage.setItem(OUTBOX_KEY, JSON.stringify(FIXED_JOB))
      }
 
      setMemoryJob(FIXED_JOB)
      await inspectFixture()
      setStatus('scheduler 요청 완료')
    })
  }
 
  async function runWorkerFixture() {
    await run(async () => {
      const job = parseUploadJob(await AsyncStorage.getItem(OUTBOX_KEY))
 
      if (job === null) {
        await inspectFixture()
        setStatus('worker: 영속 대기 작업 없음')
        return
      }
 
      await applyOnFixtureServer(job)
 
      if (SIMULATE_STOP_AFTER_SERVER_APPLY) {
        await inspectFixture()
        setStatus('worker: 서버 적용 뒤 중단 대역')
        return
      }
 
      await AsyncStorage.removeItem(OUTBOX_KEY)
      await inspectFixture()
      setStatus('worker: 업로드 확인 뒤 대기 작업 제거')
    })
  }
 
  return (
    <SafeAreaView style={styles.screen}>
      <Text style={styles.title}>background 업로드 대기열</Text>
      <Text>
        mode: {PERSIST_BEFORE_SCHEDULE ? 'durable' : 'memory-only'}
      </Text>
      <Text>상태: {status}</Text>
      <Text>메모리 작업: {memoryJob === null ? '없음' : memoryJob.id}</Text>
      <Text>영속 대기 작업: {persistedJob ? '있음' : '없음'}</Text>
      <Text>서버 적용 횟수: {serverApplyCount}</Text>
      <View style={styles.actions}>
        <Button
          title="fixture 초기화"
          disabled={busy}
          onPress={() => void resetFixture()}
        />
        <Button
          title="영수증 업로드 등록"
          disabled={busy}
          onPress={() => void enqueueUpload()}
        />
        <Button
          title="영속 상태 확인"
          disabled={busy}
          onPress={() => void run(inspectFixture)}
        />
        <Button
          title="worker 실행 대역"
          disabled={busy}
          onPress={() => void runWorkerFixture()}
        />
      </View>
    </SafeAreaView>
  )
}
 
const styles = StyleSheet.create({
  screen: { flex: 1, padding: 16, gap: 12 },
  title: { fontSize: 22, fontWeight: '700' },
  actions: { gap: 8 },
})

실행 전에 예측한다.

  1. 영수증 업로드 등록 직후 메모리와 영속 저장소에는 각각 무엇이 있을까?
  2. Reload로 JavaScript 메모리를 새로 만든 뒤 worker는 어떤 입력을 읽을까?
  3. scheduler 요청 문구가 보이면 업로드가 끝났다고 볼 수 있을까?

처음에는 다음 순서로 실패를 확인한다.

  1. fixture 초기화를 누른다.
  2. 영수증 업로드 등록을 누른다.
  3. 아직 Reload하지 않고 다음 등록 직후 값을 확인한다.
mode: memory-only
상태: scheduler 요청 완료
메모리 작업: receipt-42-upload-v1
영속 대기 작업: 없음
서버 적용 횟수: 0
  1. 개발자 메뉴의 Reload 또는 앱 재시작으로 현재 JavaScript 메모리를 없앤다.
  2. 영속 상태 확인을 누르면 메모리와 영속 대기 작업이 모두 없음을 확인할 수 있다.
  3. worker 실행 대역을 누른다.
mode: memory-only
상태: worker: 영속 대기 작업 없음
메모리 작업: 없음
영속 대기 작업: 없음
서버 적용 횟수: 0

화면이 background가 되는 정확한 순간을 기다리지 않아도 실패 원인은 재현된다. scheduler를 불렀다고 가정했지만 새 실행이 읽을 작업 입력이 없으므로 worker는 아무것도 할 수 없다.

실행을 예약하기 전에 작업 의도를 저장한다

여기서는 대기 항목이 하나뿐이라 key 하나를 쓴다. 실제 앱에서는 여러 항목의 상태·생성 시각·재시도 횟수·마지막 오류를 다루는 영속 데이터 저장 구조나 대기열이 필요할 수 있다. 민감한 작업 내용을 그대로 넣지 않고 서버에서 다시 조회할 식별값처럼 최소 입력을 선택한다.

첫 실패 뒤 상수 하나만 바꾼다.

const PERSIST_BEFORE_SCHEDULE = true

fixture 초기화부터 같은 순서를 반복한다. 등록 직후에는 메모리와 영속 대기열에 같은 작업이 보인다.

mode: durable
상태: scheduler 요청 완료
메모리 작업: receipt-42-upload-v1
영속 대기 작업: 있음
서버 적용 횟수: 0

Reload한 뒤 영속 상태 확인을 누르면 현재 JavaScript 메모리만 사라졌음을 확인할 수 있다.

mode: durable
상태: 아직 실행하지 않음
메모리 작업: 없음
영속 대기 작업: 있음
서버 적용 횟수: 0

이제 worker 실행 대역을 누른다.

mode: durable
상태: worker: 업로드 확인 뒤 대기 작업 제거
메모리 작업: 없음
영속 대기 작업: 없음
서버 적용 횟수: 1

Reload 직후 영속 상태 확인에서는 메모리 작업은 없고 영속 대기 작업은 있다. worker가 서버 적용 성공을 확인한 뒤에만 대기 항목을 지우므로 마지막 결과에서는 둘 다 없어지고 적용 횟수는 1이다.

잘못된 순서  scheduler 요청 → 메모리에만 작업 생성
교정 순서    영속 대기열 쓰기 성공 → scheduler 요청
worker 순서  영속 입력 읽기 → 서버 적용 확인 → 로컬 완료 기록·대기 항목 삭제

worker는 중단과 재실행에 안전해야 한다

fixture에서도 이 빈틈을 관찰한다. PERSIST_BEFORE_SCHEDULE = true인 상태에서 다음 상수를 바꾼다.

const SIMULATE_STOP_AFTER_SERVER_APPLY = true

fixture 초기화영수증 업로드 등록worker 실행 대역 순서로 누른다.

상태: worker: 서버 적용 뒤 중단 대역
영속 대기 작업: 있음
서버 적용 횟수: 1

서버는 작업을 받았지만 로컬 항목은 남았다. 이제 상수를 false로 되돌리고 Reload한 뒤 worker 실행 대역을 다시 누른다.

상태: worker: 업로드 확인 뒤 대기 작업 제거
영속 대기 작업: 없음
서버 적용 횟수: 1

두 번째 실행도 같은 receipt-42-upload-v1을 보냈지만 fixture server가 이미 적용한 작업 ID를 확인해 효과를 늘리지 않았다. 실제 HTTP API도 서버 중복 키나 자원의 현재 상태처럼 서버가 권위 있게 중복을 판단하는 계약을 가져야 한다. Async Storage에 둔 로컬 완료 표시만으로 다른 기기·재설치·서버 성공을 증명하지 않는다.

Android와 iOS는 같은 예약 API를 공유하지 않는다

이 글의 업로드 대기열을 Android에 연결할 때는 network 연결 조건을 가진 한 번 실행할 작업 요청을 만들고, 예를 들어 receipt-upload-drain이라는 고유 작업 이름과 KEEP 정책으로 등록할 수 있다. KEEP은 같은 이름의 미완료 작업이 있으면 새 중복 요청을 추가하지 않는 선택이다. worker는 scheduler 입력에 전체 영수증을 복사하지 않고 영속 대기열을 읽어 비어 있을 때까지 처리한다.

따라서 이 글의 단일 receipt-42 동기화 fixture만으로 BGProcessingTaskRequest를 고르면 안 된다. 실제 전송 크기, 사용자가 기다리는지, 얼마나 미뤄도 되는지를 먼저 분류한다.

iOS의 실제 요구먼저 검토할 API이 글에서 기억할 경계
화면에서 시작한 짧고 중요한 작업을 잠시 더 마침beginBackgroundTask제한된 추가 시간일 뿐, 나중 실행 예약이 아님
시간이 걸리는 파일 업로드·다운로드를 앱 정지 뒤에도 이어 감background URLSession전송별 오류와 HTTP 응답을 확인한 성공에서만 영속 대기열을 완료 처리
저활동 시간까지 미뤄도 되는 무거운 대기열 처리BGProcessingTaskRequest등록한 작업 이름으로 처리 코드를 연결하고, 시작·만료 시각은 시스템이 결정

Android WorkManager와 iOS에서 선택한 API가 공유하는 것은 앱이 정한 고정 작업 ID와 영속 대기열까지다. iOS의 세 API에서 이름과 식별값이 하는 일은 서로 다르다.

  • beginBackgroundTask에 붙인 이름은 개발 중 실행을 멈추고 살피는 도구의 표시용이다. 현재 실행을 잠시 연장하며, 반환된 식별값을 endBackgroundTask에 넘겨 각각 끝낸다.
  • background URLSession은 설정 식별값으로 앱 재실행 뒤 같은 전송 묶음을 다시 연결한다. 전송별 완료 함수에서 오류와 HTTP 응답을 확인해 성공한 앱 작업만 대기열에서 지운다. 별도의 세션 수준 함수는 보류된 모든 사건이 앱에 전달됐음을 알리며, 이때 운영체제가 맡긴 완료 함수를 호출한다. 이 신호 자체가 개별 업로드 성공을 뜻하지는 않는다.
  • BGProcessingTaskRequest는 미리 등록한 작업 식별값으로 나중에 부를 처리 코드와 연결하며, 처리 뒤 성공 여부를 보고한다.
공통 질문Android WorkManageriOS에서 선택한 API
중복 예약·처리고유 작업 이름과 정책실행 연장·파일 전송·지연 처리마다 다른 식별값 계약 + 앱 작업 ID
시작 시각시스템과 실행 조건이 결정선택한 API와 시스템 정책이 결정
중단실행 조건 변화·시스템 제한을 처리제한 시간·만료·전송 callback을 처리
완료 기준작업 결과와 영속 대기열 상태를 보고API별 완료 결과와 영속 대기열 상태를 보고

정확한 시각이 제품 요구라면 일반 background sync 문제와 분리해 플랫폼이 허용하는 알람·사용자 가시 작업인지 다시 판단한다. 단순 타이머나 임의의 장시간 실행으로 플랫폼 제한을 우회하지 않는다.

Headless JS는 Android scheduler 자체가 아니다

Headless JS를 사용해도 누가 네이티브 service를 시작하는지, 제한 시간과 재시도 규칙을 어떻게 정하는지, 새 프로세스에서 입력을 어디서 읽는지는 별도다. WorkManager worker가 플랫폼 코드로 대기열을 처리할지, React Native 실행 환경을 올려 Headless JS worker를 호출할지는 앱의 의존성·시작 비용·공유 로직에 따라 선택한다. iOS에는 같은 Headless JS 계약이 없으므로 공통 JavaScript 함수 하나만 등록해 양 플랫폼 scheduler가 완성됐다고 보지 않는다.

가상 fixture와 실제 운영체제 증거를 나눈다

검증 단계Android emulatoriOS Simulator지원 실기기
Async Storage 대기열·중복 ID fixture확인확인회귀 확인
운영체제 예약 등록·오류통제된 플랫폼 테스트 도구와 앱 기록Xcode 설정·등록 오류 확인양 플랫폼 최종 확인
worker 강제 시작·중단WorkManager 테스트 API 또는 통제된 개발 명령정확한 실제 시각을 기다려 검사하지 않음Apple의 실기기 전용 개발 함수로 시작·만료 점검
재부팅·network·배터리 제약emulator에서 일부 자동화Simulator가 실제 기기 정책을 대신하지 않음실제 전원·network·앱 전환·재시작 조건 확인
장기 지연·제조사 제한대표 Android 버전해당 없음지원 기기와 운영 실행 기록으로 관찰

Apple의 Background Tasks 개발 문서가 제시하는 강제 시작·만료 함수는 개발 중 물리 기기에서 실행을 멈추고 상태를 살피는 도구로만 사용한다. App Store 제출 코드에는 넣지 않는다. 평상시 실행은 여러 시간 지연될 수 있으므로 테스트가 정확한 실제 시각을 기다리게 만들지 않는다. 실제 운영에서는 예약·시작·성공·재시도·만료를 작업 ID와 함께 기록하되 작업 내용이나 인증값은 기록에 넣지 않는다.

pull request에서 달라져야 할 행동

background 작업 코드를 검토할 때 다음 표를 먼저 채운다.

작업 의도       어떤 사용자·서버 사건이 작업을 만들었는가
영속 입력       새 프로세스에 필요한 최소 ID와 상태는 어디에 기록하는가
저장 순서       대기열 쓰기 성공 뒤 scheduler를 요청하는가
플랫폼 선택     Android WorkManager / iOS의 작업 성격별 API
실행 조건       네트워크, 전원, 배터리, 사용자 가시성, 지연 허용 범위
중복 계약       작업 ID, 서버 중복 키, 고유 작업 이름, 동시 worker
중단·재시도     제한 시간, 만료, 재시도 간격을 점차 늘리는 규칙, 취소, 다음 실행
완료 조건       서버 확인 뒤 로컬 대기 항목을 언제 지우는가
관찰            대기·시작·성공·재시도·만료 기록과 민감 정보 가림
검증 환경       fixture, emulator·Simulator, 지원 실기기, 운영 지연

검토자는 다음을 확인한다.

  • AppState background callback이나 JavaScript 타이머를 실행 시간 보장으로 취급하지 않는가?
  • scheduler 요청 전에 작업 입력이 영속 저장됐는가?
  • worker가 React component·현재 화면 state 없이도 입력을 읽을 수 있는가?
  • 서버 성공과 로컬 완료 기록 사이 중단 뒤 같은 작업이 다시 실행돼도 안전한가?
  • Android와 iOS의 시작·중단·완료 계약을 하나의 추상 함수 뒤에서 잊지 않았는가?
  • 정확한 시각이 필요 없는 작업에 알람이나 임의의 장시간 실행을 남용하지 않는가?
  • 실제 scheduler와 기기 정책을 수동 fixture 성공으로 대체하지 않는가?

한 장으로 다시 보기

문제       영수증 업로드 입력이 React state에만 있어 새 실행에서 사라짐
오판       background callback·Promise·타이머가 끝날 때까지 앱을 살려 둠
관찰       Reload 뒤 영속 대기 작업 없음, worker 적용 0회
원인       실행 요청과 복원 가능한 작업 입력을 분리하지 않음
행동       영속 대기열 쓰기 → 플랫폼 예약 API 선택·등록 → worker가 대기열 읽기
재실행     고정 작업 ID와 서버 중복 제거로 적용 효과 1회 유지
플랫폼     Android WorkManager, iOS 작업 성격별 API의 다른 시작·중단 계약
경계       fixture는 작업 함수만 증명하고 실제 scheduler·기기 정책은 별도 검증

background 작업의 핵심은 코드를 오래 살려 두는 기술이 아니다. 현재 실행이 언제 끝나도 다음 실행이 작업 의도를 찾고, 운영체제가 준 짧은 기회마다 안전하게 진전시키며, 완료한 효과를 중복하지 않는 계약이다.

스스로 확인할 질문

  1. AppState의 background 값과 background 실행 시간은 왜 다른 계약인가?
  2. scheduler 등록보다 영속 작업 대기열 쓰기가 먼저여야 하는 이유는 무엇인가?
  3. 서버 적용 뒤 로컬 완료 기록 전에 worker가 중단되면 어떤 식별값이 필요한가?
  4. Android WorkManager와 iOS의 세 가지 background 실행 선택은 어떤 조건에서 갈리는가?

Active recall

기억에서 꺼내 보기

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

  1. 01AppState가 background 전환을 알려 주면 그 callback에서 시작한 JavaScript 업로드는 끝날 때까지 계속 실행될까?

    정답

    보장되지 않는다. AppState는 현재 앱 상태를 알리는 관찰 입구일 뿐이고, 운영체제는 프로세스 실행을 일시 중단하거나 종료할 수 있다. 잃으면 안 되는 작업은 사용자 행동 시점에 먼저 영속 대기열에 기록한다.

    왜 그런가

    상태 변화 알림과 실행 시간 허가는 서로 다른 계약이다.

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

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

  2. 02영수증 업로드를 운영체제 worker에 등록하면 작업 입력을 React state에만 둬도 될까?

    정답

    아니다. worker가 새 프로세스에서 시작할 수 있으므로 업로드 식별값과 필요한 최소 입력을 먼저 영속 저장하고, 저장 성공 뒤 scheduler에 등록한다.

    왜 그런가

    실행 기회만 예약하고 입력을 메모리에 두면 worker가 시작될 때 처리할 항목이 없다.

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

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

  3. 03업로드 성공 응답을 받은 직후 프로세스가 끝나 완료 표시를 못 남겼다면 같은 작업을 다시 보내도 될까?

    정답

    같은 작업 ID를 재사용하고 서버가 중복 ID를 한 번의 효과로 처리할 때 안전하다. 서버 적용 뒤 로컬 완료 기록 전에는 결과를 모르므로 worker는 재실행될 수 있다는 전제로 만든다.

    왜 그런가

    중단 가능한 실행에서는 네트워크 성공과 로컬 완료 기록 사이의 빈틈을 없앨 수 없으므로 중복 효과를 통제해야 한다.

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

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

  4. 04영속 대기열에 업로드가 있으면 iOS에서는 항상 BGProcessingTaskRequest를 선택하면 될까?

    정답

    아니다. 화면에서 시작한 짧은 작업의 제한된 마무리, 시간이 걸리는 파일 전송, 저활동 시간까지 미뤄도 되는 무거운 처리는 각각 beginBackgroundTask, background URLSession, BGProcessingTaskRequest를 먼저 검토한다. 어떤 API도 일반 타이머처럼 정확한 시작 시각을 보장하지 않는다.

    왜 그런가

    iOS background API는 업로드라는 이름이 아니라 작업 시간·전송 형태·지연 허용 범위로 선택한다.

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

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

출처와 검증 범위

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

  1. AppStateReact Native · 공식 문서 · 확인 2026-08-09
  2. Headless JSReact Native · 공식 문서 · 확인 2026-08-09
  3. Processes and app lifecycleAndroid Developers · 공식 문서 · 확인 2026-08-09
  4. Background WorkAndroid Developers · 공식 문서 · 확인 2026-08-09
  5. Task schedulingAndroid Developers · 공식 문서 · 확인 2026-08-09
  6. Managing workAndroid Developers · 공식 문서 · 확인 2026-08-09
  7. WorkManager releasesAndroid Developers · 공식 문서 · 확인 2026-08-09
  8. Managing your app's life cycleApple Developer · 공식 문서 · 확인 2026-08-09
  9. Background TasksApple Developer · 공식 문서 · 확인 2026-08-09
  10. Choosing Background Strategies for Your AppApple Developer · 공식 문서 · 확인 2026-08-09
  11. BGProcessingTaskRequestApple Developer · 공식 문서 · 확인 2026-08-09
  12. BGProcessingTaskApple Developer · 공식 문서 · 확인 2026-08-09
  13. earliestBeginDateApple Developer · 공식 문서 · 확인 2026-08-09
  14. expirationHandlerApple Developer · 공식 문서 · 확인 2026-08-09
  15. Extending your app's background execution timeApple Developer · 공식 문서 · 확인 2026-08-09
  16. beginBackgroundTask(withName:expirationHandler:)Apple Developer · 공식 문서 · 확인 2026-08-09
  17. URLSessionConfiguration identifierApple Developer · 공식 문서 · 확인 2026-08-09
  18. urlSessionDidFinishEvents(forBackgroundURLSession:)Apple Developer · 공식 문서 · 확인 2026-08-09
  19. urlSession(_:task:didCompleteWithError:)Apple Developer · 공식 문서 · 확인 2026-08-09
  20. Starting and Terminating Tasks During DevelopmentApple Developer · 공식 문서 · 확인 2026-08-09
  21. React Native Async StorageReact Native Async Storage · 공식 문서 · 확인 2026-08-09
  22. Using Async StorageReact Native Async Storage · 공식 문서 · 확인 2026-08-09
  23. Async Storage ChangelogReact Native Async Storage · 공식 문서 · 확인 2026-08-09
  24. RFC 9110 - Idempotent MethodsInternet Engineering Task Force · RFC · 확인 2026-08-09
  25. Fast RefreshReact Native · 공식 문서 · 확인 2026-08-09
  26. Debugging — Accessing the Dev MenuReact Native · 공식 문서 · 확인 2026-08-09
이 문서의 마지막까지 읽었습니다.