React Native iOS 백그라운드 API 요청 끊김 현상과 해결 방법

ChanghyeonO·2024년 11월 21일

리액트 네이티브로 앱 개발 중에 발생한 문제에 대해 정리한다.
현재 최초 회원가입 시 카카오 지갑 인증하도록 되어있어서 카카오톡을 갔다 와야하는데,
기존 리액트 웹처럼 구현했을 경우 IOS에서는 API 요청이 끊겨버린다.

이는 iOS의 앱 생명주기(App Lifecycle) 관리 정책 때문인데, iOS는 배터리 최적화와 시스템 리소스 관리를 위해 백그라운드로 전환된 앱의 실행을 제한한다.

처음에는 아무리 찾아봐도, 백그라운드 상태가 될 경우 api를 호출하는 라이브러리는 있어도 끊기지 않도록 해주는 라이브러리는 발견하지 못했다. (있었는데 그냥 내가 못찾았다...)

그래서 이전에 백엔드에서 혹시나 요청이 끊길 경우 성공 여부 확인을 위해 확인 api를 별도로 만들어 둔 게 있었는데,
백그라운드를 갔다가 다시 앱으로 돌아오게 되면 확인용 api를 요청하게 만들었다가 타이밍 문제가 발생했다.

백엔드에서 카카오 인증 response를 redis에 저장하는데 걸리는 시간 보다 api 요청 보내는 시간이 더 빨라서 발생한 문제.
그래서 setTimeout으로 앱으로 복귀 후 2초 뒤 확인 api를 쏘도록 구현했는데, 완전한 해결책이 아니었다.
저장 시간이 2초보다 더 걸리는 경우가 있었던 것.
그렇다고 시간을 무작정 늘릴 수도 없는 노릇이었고, 차라리 폴링 방식을 사용하기로 했다.

useEffect(() => {
  const subscription = AppState.addEventListener('change', nextAppState => {
    const checkLogin = async () => {
      const authCount = await AsyncStorage.getItem('AuthCount');
      
      // 앱 활성화, 인증 안된 상태, 첫 시도
      if (nextAppState === 'active' && !isAuthSuccess && !authCount) {
        await AsyncStorage.setItem('AuthCount', '1');
        await makeLoginRequest();  // 최초 로그인 요청
        return;
      } 
      // 2. 앱 활성화, 인증 안된 상태, 재시도
      else if (nextAppState === 'active' && !isAuthSuccess && authCount) {
        await checkLoginStatus();  // 인증 상태 체크
        return;
      }
    };
    checkLogin();
  });

  return () => {
    subscription.remove();
  };
}, [checkLoginStatus, isAuthSuccess, makeLoginRequest]);

일단 최초 앱 진입 시에는 기존 인증 요청 로직 쏘고 카운트를 async 스토리지에 추가한다.
카카오 지갑 인증 이후 앱으로 복귀 시 AuthCount 숫자가 올라가 있는걸로 최초 api 요청했음을 확인.
인증이 정상적으로 이루어졌는지 확인용 api 호출을 진행한다.
처음에는 여기 setTimeout을 넣어놨으나 타이밍 문제가 발생함에 따라,

const checkLoginStatus = useCallback(
  async (retryCount = 0) => {  // 재시도 횟수를 파라미터로 받음
    try {
      // 인증 상태 체크 API 호출
      const response = await axios.post(...);

      // 성공한 경우
      if (response.data.errYn === 'N') {
        handleKakaoAuthResponse(response.data);
        return;
      }

      // 실패했고 아직 재시도 가능한 경우 (5번까지)
      if (retryCount < 5) {
        setTimeout(() => {
          checkLoginStatus(retryCount + 1);
        }, 2000);
      }
    } catch (error) {
      console.log('실패:', error);
    }
  },
  [getAuthPayload, handleKakaoAuthResponse] 
);

폴링 방식을 적용하여 요청 후 실패하면 성공할 때까지 2초 간격으로 최대 5번까지 재시도하고 중간에 성공할 경우에는 요청을 중단한다.

이렇게 하니까 결국 해결하긴 했다,,,ㅎㅎ

그러나 이렇게 했을 경우 결국 api를 무조건 최소 2번씩 요청해야한다는 문제와 불필요한 api 요청이 늘어난다는 단점이 있었고, 분명 다른 방법이 있을 것이라 생각했다.

그러다 찾은 react-native-background-timer 라이브러리.

react-native-background-timer는 iOS에서 앱이 백그라운드 상태일 때도 타이머를 계속 실행할 수 있게 해주는 라이브러리다.

특징
iOS 백그라운드 실행 제한을 우회
네이티브 모듈을 통해 시스템 레벨의 타이머 제공

if (Platform.OS === 'ios') {
  // 백그라운드 타이머 시작
  BackgroundTimer.runBackgroundTimer(() => {}, 1000);
  // 빈 함수를 1초마다 실행하면서 백그라운드 실행을 유지
}

try {
  // API 요청 수행
  const response = await axios.post(...);
} finally {
  if (Platform.OS === 'ios') {
    // 작업 완료 후 타이머 중지
    BackgroundTimer.stopBackgroundTimer();
  }
}

위처럼 구현하면 1초마다 빈 함수를 계속 실행하면서 백그라운드에서 끊기지 않고 실행을 유지한다.
덕분에 API 요청 중간 백그라운드로 이동하더라도 API 요청이 중단되지 않고, 돌아올 때까지 유지할 수 있는 것이다.
물론 1초마다 빈 함수가 계속 요청 된다는 단점은 존재하나, 2초마다 백엔드에 api를 쏘면서 확인하는 불필요한 과정 및 비용을 줄일 수 있게 되었다.

참 앱은 어렵다....하면 할수록 너무 어렵다...

profile
현 2년차 개발자 -> 기관사로 이직 준비 중

0개의 댓글