[TIL] RxSwift 4시간에 끝내기2

승아·2021년 5월 11일
0

RxSwift 4시간에 끝내기 시즌2를 참고하였습니다.

✅⠀비동기로 데이터 받기

completion을 이용한 방법

func downloadJson(_ url: String, _ completion: @escaping (String?) -> Void){
    // downloadJson이 끝나고 나중에 실행되는 함수라는걸 알려줄려고 escaping을 적어줌
    // ((String?) -> Void)? 이렇게 옵셔널인 경우 escaping이 default라 안적어줘도 됨
    DispatchQueue.global().async {
        let url = URL(string: url)!
        let data = try! Data(contentsOf: url)
        let json = String(data: data, encoding: .utf8)
        DispatchQueue.main.async {
            // ** 여기서 completion 사용
            completion(json)
        }
    }
}

@IBAction func onLoad() {
    downloadJson(MEMBER_LIST_URL){ json in // completion 구현
        self.editView.text = json
    }
}

받아온 값을 return 받는 방법

class 나중에생기는데이터<T>{
    private let task: (@escaping (T) -> Void) -> Void
    
    init(task: @escaping (@escaping (T) -> Void) -> Void){
        self.task = task
    }
    
    func 나중에오면(_ f: @escaping (T) -> Void){
        task(f)
    }
}

func 다운로드받는함수(_ url: String) -> 나중에생기는데이터<String>{
    return 나중에생기는데이터() { f in // ** task
        DispatchQueue.global().async {
            let url = URL(string: url)!
            let data = try! Data(contentsOf: url)
            let json = String(data: data, encoding: .utf8)
            DispatchQueue.main.async {
                f(json)
            }
        }
    }
}

@IBAction func onLoad() {
    // ** 1. json에 나중에생기는데이터<String>을 담는다.
    let json: 나중에생기는데이터<String> = 다운로드받는함수(MEMBER_LIST_URL) 
    // ** 2. 나중에오면을 실행시켜 task를 실행시킨다.
    json.나중에오면{ json in // ** f
        self.editView.text = json
        self.setVisibleWithAnimation(self.activityIndicator, false)
    }
}

RxSwift의 Observable의 기본 원리는 위와 같이 구현된다고한다. Obsevable은 아마 아래와 같이 생기지 않았을까 .. ?

class Obsevalble<T>{
    private let task: (@escaping (T) -> Void) -> Void
    
    init(task: @escaping (@escaping (T) -> Void) -> Void){
        self.task = task
    }
    
    func subscribe(_ f: @escaping (T) -> Void){
        task(f)
    }
}

RxSwift를 이용한 방법

func rx사용(_ url: String) -> Observable<String?>{
    return Observable.create{f in // ** 위에서의 task
        DispatchQueue.global().async {
            let url = URL(string: url)!
            let data = try! Data(contentsOf: url)
            let json = String(data: data, encoding: .utf8)
            DispatchQueue.main.async {
                f.onNext(json)
                f.onCompleted()
            }
        }
        // static func create(_ subscribe: @escaping (AnyObserver<String?>) -> Disposable) -> Observable<String?>
        // create안에 있는 클로져의 반환 값이 Disposable이기 때문에 Dispoable을 return 해준다.
        return Disposables.create() 
    }
}

rx사용(MEMBER_LIST_URL)
    .debug() // debug()를 통해 받아온 데이터, 생명 주기 확인 가능 
    .subscribe{ event in // ** 위에서의 f
        switch event{
        case .next(let json):
            self.editView.text = json
        case .error:
            break
        case .completed:
            break
        }
    }

✅⠀Obsevable의 생명주기

  1. Create
  2. Subscribe : subscribe한 시점 부터 옵저버블이 실행된다.
  3. onNext
  4. onCompleted or onError
  5. Disposed

✅⠀Subject

  • Subject는 옵저버이기 때문에 하나 이상의 Observable을 구독할 수 있으며 동시에 Observable이기도 하기 때문에 항목들을 하나 하나 거치면서 재배출하고 관찰하며 새로운 항목들을 배출할 수도 있다.
  • Observable은 Unicast, Subjcet는 Multicast
  • 강의에서 외부의 값을 이용하기 위해 Obsevable대신 Subject를 사용하였음

BehaviorSubejct


옵저버가 BehaviorSubject를 구독하기 시작하면, 옵저버는 소스 Observable이 가장 최근에 발행한 항목(또는 아직 아무 값도 발행되지 않았다면 맨 처음 값이나 기본 값)의 발행을 시작하며 그 이후 소스 Observable(들)에 의해 발행된 항목들을 계속 발행한다.

PublishSubject


PublishSubject는 구독 이후에 소스 Observable(들)이 배출한 항목들만 옵저버에게 배출한다.

주의할 점은, PublishSubject는 (이를 막지 않는 이상) 생성 시점에서 즉시 항목들을 배출하기 시작할 것이고 이런 특성 때문에 주제가 생성되는 시점과 옵저버가 이 주제를 구독하기 시작하는 그 사이에 배출되는 항목들을 잃어 버릴 수 있다는 단점이 있다. 이 경우 PublishSubject 대신 ReplaySubject를 사용해야 한다.

✅⠀Relay

  • Subject는 한 번 error나면 스트림이 끊기지만 Relay는 error가 나도 스트림이 끊어지지 않는다.
  • onNext(), onError()을 accept()로 표현해줘야 한다.

✅⠀순환 참조 관리

  • weak 사용
viewModel.totalPrice
    .map{ $0.currencyKR() }
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { [weak self] text in
        self?.totalPrice.text = text
    }).disposed(by: disposeBag)
  • disposeBag 사용
override func viewWillDisappear(_ animated: Bool) {
    disposeBag = DisposeBag()
}

✅⠀그 외 함수들

  • bind() : subscribe()을 더 간편하게 사용. MainThread에서 실행
// subscribe 사용한 경우
viewModel.totalPrice
    .map{ $0.currencyKR() }
    .observeOn(MainScheduler.instance)
    .subscribe(onNext: { 
        self.totalPrice.text = $0
    }).disposed(by: disposeBag)
// bind 사용한 경우
viewModel.totalPrice
    .map{ $0.currencyKR() }
    .bind(to: totalPrice.rx.text)
    .disposed(by: disposeBag)
  • take(n) : n번만 호출.
_ = menuObservable
    .map{ menus in
        menus.map { m in
            Menu(id: m.id,name: m.name, price: m.price, count: 0)
        }
    }
    .take(1) // 한 번만 수행하기 계속 수행하면 스트림이 계속 생기기 때문
    .subscribe(onNext: {
        self.menuObservable.accept($0) 
    })
  • Dirver
// Driver 사용 전
viewModel.itemsCount
    .map{"\($0)"}
    .observeOn(MainScheduler.instance) 
    .bind(to: itemCountLabel.rx.text)
    .disposed(by: disposeBag)
    
// Driver 사용 후
viewModel.itemsCount
    .map{"\($0)"}
    .asDriver(onErrorJustReturn: "") // error가 나면 .. ""로 리턴하겠다.
    .drive(itemCountLabel.rx.text)
    .disposed(by: disposeBag)
    
// observeOn() 사용해서 메인쓰레드로 굳이 안바꿔줘도 됨

✏️⠀오늘은

전 시즌에 비해 기초가 더 탄탄해졌다. 개념을 더 정확히 잡을 수 있어 더 좋았던 것 같다. RxCocoa 실습도 더 강화됐는데 과부화 걸려서 천천히 이해하면서 정리해봐야겠다 🤯⠀아직은 너무 어려운 Rx ... 그래도 오랜만에 새로운걸 공부하니 재미있었다 😊

0개의 댓글