ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • (RxSwift)API 호출 시점이 많을 때, 효율적으로 관리하기
    iOS/Swift 2022. 2. 23. 17:59

    안녕하세요! 프로젝트를 하다보면 API를 여러 시점에서 호출해야 하는 상황이 생기는데요,

    이런 상황을 어떻게 효율적으로 관리할 수 있을지 정리해보았습니다.

    더 좋은 방법이 있다면 피드백 부탁드립니다!!


     

    문제점

    프로젝트를 하면서, 서버에 요청하여 유저 목록을 가져와야하는데, 호출해야 하는 시점이 매우 다양하다.

    1. 화면 진입 시
    2. 주변 사람 / 받은 요청 탭을 전환 할 때
    3. empty view에서 새로고침 버튼을 눌렀을 때
    4. 카드뷰를 아래로 당겨서 새로고침 할 때
    5. 카드뷰의 상세 화면을 닫았을 경우
    6. 앱이 백그라운드 → 포그라운드 상태가 됐을 때, 앱의 화면이 새싹 찾기 화면일 경우

    여기서 또 고려해야 할 점이, 호출 시점이 많아 너무 과도하게 호출될 수 있어 마지막 호출이 일어난 지 5초가 지나지 않았다면 호출하지 않아야 한다는 것이다.

     

     

    의식의 흐름

    일단 처음으로 생각해 본 것은 호출하는 시점들을 모두 Merge로 묶고 난 뒤, 묶어둔 이벤트가 하나라도 실행될 경우 호출하는 방법이다.

     

    Merge는 여러 이벤트에서 발생하는 시퀸스들을 하나의 시퀸스로 묶어주는 연산자다.

    이 연산자에 서버를 호출하는 이벤트들을 묶어두는 것이 알맞겠다고 생각했다.

     

     

    그리고 호출에 제한을 두기 위한 방법으로는 Debounce, Throttle을 사용하는 방법을 떠올렸다.

    먼저 Debounce 연산자는 이벤트가 들어온 뒤 일정 시간이 지나도록 다른 이벤트가 들어오지 않으면 실행되는 연산자다.

    마블을 보면 맨 처음 1이라는 이벤트가 들어간 뒤, Debounce에 설정한 시간동안 아무런 이벤트가 발생하지 않아 1이라는 이벤트가 실행되는 것을 볼 수 있다.

    다음 2라는 이벤트가 들어갔지만, Debounce에서 설정한 시간 이전에 연달아서 3, 4, 5라는 이벤트가 들어왔기 때문에 실행되지 않았고, 맨 마지막에 들어간 5만 그 이후에 아무런 이벤트가 들어오지 않아 실행되는 모습이다.

     

     

    그 다음은 Throttle이다.

     

    Debounce와 비슷하지만 살짝 다르다.

     

    throttle은 설정한 시간 이후에만 이벤트를 받을 수 있고, 그 타이머 동안에 들어오는 이벤트는 무시한다.

    a라는 이벤트가 들어온 뒤 타이머가 작동하고, 이후 들어오는 x,y 이벤트는 타이머가 끝나지 않았기 때문에 무시된다. 그리고 타이머가 끝난 뒤 b가 들어오면 이벤트를 실행하는 모습이 보인다.

     

     

     

    호출하는 방법을 사용자가 의도한 방법과 의도하지 않은 방법 두가지로 나누어서 생각해보았다.

    새로고침 버튼을 눌렀을 때, 혹은 당겨서 새로고침 기능을 사용할 때 사용자는 잠재적으로 곧 새로운 데이터로 새로고침 되겠구나 라고 생각할 수 있다.

     

    Debounce로 처리하는 경우, 사용자가 멈추지 않고 이벤트를 발생시키면, 매번 타이머가 초기화되기때문에 이벤트 발생을 멈추기 전까지는 아무일도 일어나지 않는다. 그래서 이번 경우에는 Throttle이 적합하다고 생각했다.

     

     

    그런데 Throttle로 이 호출을 처리할 경우에는 설정한 Timer가 끝나지 않았을 때 이벤트가 무시되므로 사용자가 불편을 느낄 수도 있다. 나는 새로고침 버튼을 눌렀는데 왜 아무런 일도 일어나지 않는거지?? 하는..

    그래서 Throttle의 타이머에 관련된 Flag를 세우고, 사용자가 의도적으로 이벤트를 발생시키는 방법(새로고침 버튼 누름, 당겨서 새로고침 기능)에 관한 이벤트가 발생했을 경우, Toast 메세지 같은 방법으로 잠시 뒤 다시 시도해달라는 메세지를 전달하는 것도 괜찮을 것 같다.

    해결 방법

    Throttle에는 latest라는 매개변수가 존재하는데, latest == true 일 경우 타이머가 동작하는 도중 마지막 이벤트를 타이머가 끝나고 실행할 수 있다! 이 속성을 사용하면 될 것 같다.

    Observable.merge(input.willAppear, input.cardViewClosed, input.refreshButtonClicked, input.refreshControlValueChanged)
                .throttle(.seconds(5), latest: true, scheduler: MainScheduler.instance)
                .withUnretained(self)
                .do(onNext: { owner, _ in owner.output.activating.accept(true) })
                .flatMap { owner, _ in owner.fetchFriends(position: UserInfo.mapPosition) }
                .do(onNext: { [weak self]_ in self?.output.activating.accept(false) })
                .bind(with: self) { owner, friends in
                    owner.output.friendsValue.accept(friends.fromQueueDB)
                }
                .disposed(by: disposeBag)
    

    이렇게 호출 이벤트들을 merge로 묶고, throttle로 API의 과도한 호출을 막을 수 있게 구현해두었다!!!

    댓글

Designed by Tistory.