-
(RxSwift)API 호출 시점이 많을 때, 효율적으로 관리하기iOS/Swift 2022. 2. 23. 17:59
안녕하세요! 프로젝트를 하다보면 API를 여러 시점에서 호출해야 하는 상황이 생기는데요,
이런 상황을 어떻게 효율적으로 관리할 수 있을지 정리해보았습니다.
더 좋은 방법이 있다면 피드백 부탁드립니다!!
문제점
프로젝트를 하면서, 서버에 요청하여 유저 목록을 가져와야하는데, 호출해야 하는 시점이 매우 다양하다.
- 화면 진입 시
- 주변 사람 / 받은 요청 탭을 전환 할 때
- empty view에서 새로고침 버튼을 눌렀을 때
- 카드뷰를 아래로 당겨서 새로고침 할 때
- 카드뷰의 상세 화면을 닫았을 경우
- 앱이 백그라운드 → 포그라운드 상태가 됐을 때, 앱의 화면이 새싹 찾기 화면일 경우
여기서 또 고려해야 할 점이, 호출 시점이 많아 너무 과도하게 호출될 수 있어 마지막 호출이 일어난 지 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의 과도한 호출을 막을 수 있게 구현해두었다!!!
'iOS > Swift' 카테고리의 다른 글
SwiftUI List Background Color(iOS 16.0+) (0) 2022.09.29 SwiftUI 제스처가 작동하지 않을 때 (0) 2022.07.10 UICollectionView Compositional Layout (0) 2022.02.17 RxDataSources + 컬렉션 뷰(Swift) (0) 2022.02.16 Firebase Auth 적용하기(전화번호 인증, Swift) (0) 2022.02.04