본문 바로가기

iOS_RxSwift

RxSwift ActivityIndicator GitHub 코드 정리

네트워크 요청이 있는 경우 응답을 받기 까지 화면이 멈춰있으면 안되고 뭔가 일련의 작업을 수행중이라는 로딩바 호출이 프론트 개발에는 필수적이다. 이것을 좀 있어보이게 ActivityIndicator 라 부르는 것 같다(?)

프로젝트에서 RxSwift 를 사용했을때 썼던 코드가 있어 정리해둔다. (검색해보니 GitHub 오픈소스인것으로 보인다.)

 

import RxSwift
import RxCocoa

private struct ActivityToken<E>: ObservableConvertibleType, Disposable {
  private let _source: Observable<E>
  private let _dispose: Cancelable

  init(source: Observable<E>, disposeAction: @escaping () -> Void) {
      _source = source
      _dispose = Disposables.create(with: disposeAction)
  }

  func dispose() {
      _dispose.dispose()
  }

  func asObservable() -> Observable<E> {
      return _source
  }
}

/**
Enables monitoring of sequence computation.

If there is at least one sequence computation in progress, `true` will be sent.
When all activities complete `false` will be sent.
*/
public class ActivityIndicator: SharedSequenceConvertibleType {
  public typealias Element = Bool
  public typealias SharingStrategy = DriverSharingStrategy

  private let _lock = NSRecursiveLock()
  private let _relay = BehaviorRelay(value: 0)
  private let _loading: SharedSequence<SharingStrategy, Bool>

  public init() {
    _loading = _relay.asDriver()
      .map { $0 > 0 }
      .distinctUntilChanged()
  }

  fileprivate func trackActivityOfObservable<Source: ObservableConvertibleType>(_ source: Source) -> Observable<Source.Element> {
    return Observable.using({ () -> ActivityToken<Source.Element> in
      self.increment()
      return ActivityToken(source: source.asObservable(), disposeAction: self.decrement)
    }) { t in
      return t.asObservable()
    }
  }

  private func increment() {
    _lock.lock()
    _relay.accept(_relay.value + 1)
    _lock.unlock()
  }

  private func decrement() {
    _lock.lock()
    _relay.accept(_relay.value - 1)
    _lock.unlock()
  }

  public func asSharedSequence() -> SharedSequence<SharingStrategy, Element> {
    return _loading
  }
}

extension ObservableConvertibleType {
  public func trackActivity(_ activityIndicator: ActivityIndicator) -> Observable<Element> {
    return activityIndicator.trackActivityOfObservable(self)
  }
}

UIViewController 에서 ActivityIndicator를 호출할 경우 연동하여 사용하는 법도 같이 정리해둔다.

 

import UIKit
import RxSwift
import RxCocoa

/*
 공통 UIViewController
 */
class BaseViewController: UIViewController {
  var disposeBag = DisposeBag()
  var activity = ActivityIndicator()
  var errorTracker = ErrorTracker()
  
  private var loadingView: LoadingView? // 로딩바가 돌아가는 UIView
  
  var isLoading: Bool = true {
    didSet {
      if isLoading {
        guard loadingView == nil else { return }
        loadingView = LoadingView.fromNib()
        
        if var topController = UIApplication.shared.keyWindow?.rootViewController {
          while let presentedViewController = topController.presentedViewController {
            topController = presentedViewController
          }
          loadingView!.frame = topController.view.bounds
          topController.view.addSubview(loadingView!)
          topController.view.bringSubviewToFront(loadingView!)
        }
      } else {
        loadingView?.removeFromSuperview()
        loadingView = nil
      }
    }
  }
  
  override func viewDidLoad() {
    super.viewDidLoad()
    setupRx()
  }
  
  func setupRx() {
    activity.distinctUntilChanged()
      .asObservable()
      .bind(to: rx.isLoading)
      .disposed(by: disposeBag)
  }
}

BaseViewController 를 상속받은 UIViewController 에서는

아래 코드와 같이 trackActivity 할 경우 ActivityIndicator가 동작하게 된다.

 

apiService.exampleLogin() // RxSwift Observable 요청
  .trackActivity(self.activity)
  .trackError(self.errorTracker)
  .subscribe(onNext: { [weak self] _ in
  
  }).disposed(by: disposeBag)