brunch

매거진 iOS 작업실

You can make anything
by writing

C.S.Lewis

앱 진입로직 개선(feat. RxSwift)

안녕하세요!

카카오헤어샵 iOS 개발자 austin 입니다.

저는 이번에 앱 진입로직과 관련된 버그 해결과정을 공유하고자 합니다.


앱 진입로직이란?


저희 카카오헤어샵 앱에는 홈 화면에 진입하기 전에 체크해야 하는 항목들이 있는데요. 

체크 항목을 모두 통과해야만 홈 화면에 진입이 가능하고, 하나라도 실패하면 에러 핸들링 처리하는 로직을 앱 진입로직이라고 정의하였습니다.


체크 항목으로는 8가지 정도가 있습니다.

무결성 검사, 탈옥 감지 등 보안 체크사항도 있고, 저희 서비스가 꼭 로그인을 해야만 홈에 진입할 수 있는 서비스다 보니 로그인 여부 체크사항, 휴먼 계정 체크사항, 앱 강제 업데이트 체크사항 등 모든 체크항목을 통과해야지만 홈에 진입이 가능하죠. 반면에 이 중 하나라도 에러가 발생한다면 해당 에러를 핸들링하고 홈에 진입합니다(예를 들어 로그인한 계정이 휴먼 계정인 경우 해당 계정을 활성화하거나 다른 계정으로 로그인해야만 홈에 진입이 가능합니다).


장애 발생


올해 8월 중순쯤 서버 장애가 30분간 발생한 적이 있었는데요.

서버 장애라 api 요청 시 응답값이 오지 않거나 500대 에러가 나는 것은 맞지만, 저희 iOS 앱에서 앱 진입 시 네트워크 에러 팝업을 띄우지 않고, 홈 진입 전 스플래시 화면에서 대기 중인 버그도 같이 발견했습니다.

안드로이드는 네트워크 에러 팝업이 잘 나오고 있는 반면, iOS 에서는 팝업 없이 스플래시 화면에서 마냥 대기 중인 버그였어요.
iOS 도 원래는 장애 시 네트워크 팝업이 잘 나왔으나, 팝업이 나오지 않는 버그를 발견하고 어떤 이슈가 있는지 확인을 바로 해보았습니다.


왼: 안드로이드, 오: iOS


기존코드 분석


RxSwift의 zip operator

기존 코드를 분석해 보니 체크항목들을 체크해서 모두 완료인 경우 홈에 진입하는 로직으로 RxSwift의 zip operator를 사용하고 있었습니다. 
zip operator를 간략히 설명드리자면, Combining Operator 중 하나로서, 방출된 아이템을 짝을 맞춰 메서드로 가공한 결과를 새로운 Observable로 리턴하는 Operator입니다. 

따라서 결합한 Observable들이 하나씩 값을 방출해야지만 새로운 Observable로 값이 방출이 되고, 이 중 하나라도 아이템이 방출되지 않는다면 새로운 Observable로 값이 방출이 되지 않습니다.

위 그림으로 설명드리자면 "1"과 "A"가 짝을 지어서 "1A"라는 값이 새로운 Observable로 아이템이 방출되고, 또 이어서 "2"와 "B"가 짝을 지어 "2B"라는 값이 새로 방출됨을 알 수 있습니다. 반면에 마지막 "5"는 값이 방출되었음에도 또 다른 Observable에서 아이템이 방출되지 않아 zip operator로 값이 방출되지 않음을 알 수 있습니다. 


체크항목을 8가지를 모두 체크한 후에만 홈으로 넘어가는 로직으로 zip operator를 사용한 건 매우 적절하다고 생각했지만, 현 코드를 보니 3가지 이슈가 있었습니다.


이슈 1. 체크항목 Observable 들 중에 서버의 응답값이 성공인 경우만을 바라보는 항목이 있었습니다. 따라서 서버의 응답값이 에러인 경우에 해당 항목은 값을 방출하지 않기 때문에, 결합된 Observable 이 모두 한 번씩 아이템을 방출해야 새로운 Observable로 값을 방출하는 zip operator 특성상 값이 방출되지 않고 마냥 기다리는 이슈가 있었습니다. 그래서 네트워크 에러 팝업이 노출되지 않았던 거죠.


이슈 2. 성공인 경우만을 바라보는 체크항목을 실패도 바라보게끔 고쳐도 이슈는 있습니다. zip operator 특성상 8가지 체크항목을 모두 기다려야 하기 때문에 한 체크항목이 에러를 방출했음에도 다른 항목들을 기다리게 됩니다. 따라서 에러를 뱉어도, 서버의 응답값을 기다리는 다른 체크항목이 있는 경우엔 네트워크 에러 팝업이 아주 나중에 나타나게 됩니다. 


이슈 3. URLRequest의 timeInterval이 따로 정해져 있지 않았습니다. 따라서 서버 응답값이 내려오지 않는 경우, 어떠한 조치도 없이 스플래시 화면에서 대기하는 버그가 발생합니다.


해결책 1: 서버 응답값이 실패인 경우도 바라보게 버그 Fix


당연한 조치인데, 체크항목 중 서버 응답값이 성공인 경우만을 바라보는 체크항목을 실패인 경우도 같이 바라보도록 코드 수정해 주었습니다. 따라서 (서버로부터 응답값이 잘 내려오기만 한다면) 8가지 체크항목 Observable 이 모두 값을 방출할 수 있게 되어 zip operator를 통해 값이 잘 넘어올 수 있게 되었습니다.


해결책 2: URLRequest의 timeInterval 설정


서버 응답값을 필요로 하는 체크항목들이 서버 요청을 보낼 때, URLRequest의 timeInterval을 10초로 설정해 주었습니다. 따라서 서버 응답값이 내려오지 않는 경우가 있어도 10초가 지나면 에러가 발생하고, 이후 네트워크 팝업을 띄울 수 있게 되었습니다.


해결책 3: 성공 체크로직은 zip operator 사용, 실패 체크로직은 merge operator 사용


성공인 경우 zip, 실패인 경우 merge 을 사용.

사실상 가장 공을 많이 드린 부분입니다.

성공을 체크하는 로직은 기존대로 zip operator를 사용하는 반면, 실패 체크하는 로직은 RxSwift의 merge operator을 사용해 체크항목이 통과가 실패한 경우 바로 에러 핸들링되도록 조치해 주었습니다.

merge operator를 간략히 설명드리자면, merge 도 zip처럼 여러 Observable을 합쳐 새로운 Observable을 만드는 Combining Operator 중 하나입니다. 하지만 merge는 zip과 달리 하나의 Observable에서 값이 방출되면 merge operator을 통해 만든 새로운 Observable로 값이 바로 넘어갑니다(위 그림을 보시면 값 20이 방출되자마자 merge operator로 20이 넘어감을 알 수 있습니다).

따라서 merge operator을 통해 에러가 방출된 경우 이제는 다른 체크항목을 기다릴 필요 없이 바로 에러 핸들링을 할 수 있게 되었습니다~! 짝짝짝 


회고

(1) RxSwift 지식 상승: RxSwift에 대해 더 깊게 알게 된 경험이었습니다. Observable, Observer 에서부터 share 메서드, Subject와 각 operator에 대해서도 얕게만 아는 게 아니라 내부 원리들도 덕분에 잘 알아가고 공부하게 된 좋은 기회가 되었습니다.

(2) 테스트 코드 작성 필요: 작업 진행할 때, 앱 체크항목들을 모두 QA 직접 진행하며 작업 진행하였습니다. 때문에 생각보다 시간이 오래 걸렸던 같은데, 다음부터는 테스트 코드도 같이 작성해 제가 작성한 코드가 잘 돌아감을 QA가 아닌 코드단에서 확신을 얻고 작업을 진행해야 할 것 같네요!


끝까지 읽어주셔서 감사합니다 :)

브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari