Crashlytics를 이용해서 iOS앱을 개선시켜보기

이건준·2024년 5월 15일
0

1. specialized Interceptor.retry(_:for:dueTo:completion:)

Crash 로그정보

Crashed: org.alamofire.session.rootQueue
0  Haram                          0x11aa64 UserManager.reissuanceAccessToken() + 4366690916 (<compiler-generated>:4366690916)
1  Haram                          0x100e88 specialized Interceptor.retry(_:for:dueTo:completion:) + 28 (Interceptor.swift:28)
//... 생략

첫번째 시도

  • Interceptor의 retry함수에서 비정상 종료가 7명 정도가 있어 코드자체의 문제가 있다고 판단함

  • 401 상태코드로 인한 토큰 재발급시에 재발급 이후에 retry를 하게되는데 무한정으로 retry를 시도하고있다 판단

if request.retryCount < self.retryLimit {
        UserManager.shared.reissuanceAccessToken()
          .subscribe(onSuccess: { _ in
            // 재발급을 성공했다면 기존에 발생했던 요청 재시도
            completion(.retry)
          }
  • 재시도 횟수를 제한해주는 상태값을 이용해 retry시도하게끔 로직을 변경

2. LoginViewModel.loginMember(userID:password:)

Crash 로그정보

Crashed: com.apple.main-thread
0  Haram                          0x7a534 LoginViewModel.loginMember(userID:password:) + 12 (NetworkManager.swift:12)
1  Haram                          0x44768 LoginViewController.didTappedLoginButton() + 224 (LoginViewController.swift:224)
//... 생략

LoginViewModel코드문제로 추정되는 부분

authRepository.loginMember(
      request: .init(
        userID: userID,
        password: password,
        uuid: UserManager.shared.uuid!,
//... 생략

첫번째 시도

if !UserManager.shared.hasUUID {
      UserManager.shared.set(uuid: UUID().uuidString)
    }
  • 성서알리미 로그인 시 하나의 계정을 여러 디바이스에서 로그인하는 것을 방지하기위해 UUID를 이용하여 이를 구분하고자 함

  • AppDelegate에서 UUID를 생성해주었기에 강제언래핑을 통해 값을 가져와도 된다 판단했지만 딱 보이기에 앱이 비정상 종료가 될만한 코드가 저 부분이라 위처럼 UUID를 가지고있지않을 시 생성해주는 코드를 삽입

3. 홈 정보업데이트 다수호출 문제

1번 retry문제를 개선하다가 코드자체에서의 로직에 대한 문제점을 발견

기존 HomeViewModel & HomeViewController 문제 코드

// HomeViewModel
private let newsModelRelay   = BehaviorRelay<[HomeNewsCollectionViewCellModel]>(value: [])
private let bannerModelRelay = BehaviorRelay<[HomebannerCollectionViewCellModel]>(value: [])
private let shortcutModelRelay = BehaviorRelay<[HomeShortcutCollectionViewCellModel]>(value: [])
  
owner.newsModelRelay.accept(news)
owner.bannerModelRelay.accept(banners)
owner.noticeModelRelay.accept(notices)

// HomeViewController
Driver.combineLatest(
  viewModel.newsModel,
  viewModel.bannerModel,
  viewModel.noticeModel,
  viewModel.isAvailableSimpleChapelModal
)
  • 홈 데이터를 가지는 타입을 BehaviorRelay를 사용하여 초기값을 가지고있는 상황에서 HomeViewController에 combineLatest를 통해 홈 화면에 데이터바인딩시키고있는 로직

  • 위와 같은 이유로 인해 홈 정보 API를 한번 호출함에도 데이터바인딩을 3번돌려 reload하는 코드이기때문에 불필요한 리소스낭비된다고 판단

  • 홈에 채플정보에 대한 UI를 특정시간(오전 10시 ~ 오후 1시)만 띄워주기위해 viewWillAppear함수에 구현해주었기때문에 위 코드는 해당 시간에 굳이 변경하지않아도 될 홈 정보를 reload해주는 불필요한 코드

기존 HomeViewModel & HomeViewController 문제 개선코드

/// HomeViewModel
private let newsModelRelay   = PublishRelay<[HomeNewsCollectionViewCellModel]>()
private let bannerModelRelay = PublishRelay<[HomebannerCollectionViewCellModel]>()
private let shortcutModelRelay = PublishRelay<[HomeShortcutCollectionViewCellModel]>()
  
/// HomeViewController
Driver.zip(
  viewModel.newsModel,
  viewModel.bannerModel,
  viewModel.noticeModel
)
  • BehaviorRelay -> PublishRelay로 바꿔줌으로써 초기값을 가지고있지않기때문에 섹션 1개만 변경해도 전체가 reload되는 문제점 해결

  • 홈 정보 API를 호출할때 공지, 바로가기, 뉴스레터 총 3개의 섹션에 대한 데이터를 한번에 받기때문에 combineLatest -> zip으로 변경(사실 combineLatest로 둬도 무관하지만 zip연산자가 의도를 분명하게 함)

// HomeViewController
viewModel.isAvailableSimpleChapelModal
      .drive(with: self) { owner, modalModel in
		//... 생략
  • 홈에 띄워주는 채플정보에 대한 바인딩 코드를 다른 데이터바인딩코드와 따로 배치함으로써 호출되어도 채플에 대한 UI만 reload되어 코드 개선

4. 잦은 retry함수 호출

  • [성서알리미]앱에서는 402(refreshToken만료) 499(다른 디바이스에서 한 계정 접속)할때에 로그인 화면으로 돌아가 로그아웃하는 기능이 존재

  • 너무 자주 로그인화면으로 이동한다는 사용자의 피드백을 따라 로그를 찍어본 결과 402, 499코드가 떨어져 이미 로그인화면에 이동했음에도 retry가 호출되는 문제점 발견

// Interceptor
else if statusCode == 402 || statusCode == 499 {
      // 상태코드 402은 refreshToken 만료, 499는 다른 uuid를 이용해 로그인 시 기존 로그인이 취소되었음을 알림
      UserManager.shared.clearAllInformations()
      
      DispatchQueue.main.async {
        let vc = LoginViewController()
        (UIApplication.shared.connectedScenes.first as? UIWindowScene)?.windows.first?.rootViewController = vc
        if statusCode == 499 {
          AlertManager.showAlert(title: "로그아웃 알림", message: "다른 기기에서 로그인되었습니다", viewController: vc, confirmHandler: nil)
        }
      }
      completion(.doNotRetryWithError(error))
    }
  • 전부 처리하였다고 생각했는데 402, 499 상태코드가 떨어졌을 시 completion을 이용해 retry를 이제 하지않겠다는 콜백을 던져줬어야했는데 추가하지못해 해당 문제 발생

  • 이전 Swift Concurrency에서 콜백함수의 문제점 중 에러에 대한 처리를 하지않아도 컴파일러는 이를 모르기에 개발자의 의도에 따라 달렸다고 공부했는데 이번 경험을 통해 한번 더 인지하게 됨

2024.05.15 기준

기존 v1.0.1버전 사용자에 대해 비정상종료 대상자비율이 v1.0.2버전 사용자에 대비해 줄어든 모습

0개의 댓글