...
본문 바로가기

iOS

RxSwift, RxCocoa 정리

우선..

 

곰튀김님의 RxSwift 3시간 강의연상 정말 추천합니다.

( 저도 지인분을 통해서 추천받고 들었는데 정말 대박입니다 👍 ) 

( 왜 RxSwift가 필요하고, 왜 쓰이는지, 왜 그토록 많은 기업에서 사용하는지 이유를 알 수 있을 거에요! ) 

 

곰튀김님 강의 영상 

 


  • 이 글은 곰튀김님의 강의 영상을 보면서 정리한 글 및 추가적으로 제가 RxSwift, RxCocoa를 사용하면서 제가 필요한 내용들만 적은 글입니다. 

 

RxSwift github

 

GitHub - ReactiveX/RxSwift: Reactive Programming in Swift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com


 

RxSwift 사용하는 이유

  • RxSwift를 사용하는 이유는, 비동기적으로 발생하는 결과값을 completion 클로저로 전달하지 않고, return값으로 전달하고자 위함입니다.  그럼으로써 좀 더 코드를 동기적으로 작성할 수 있고, 코드가 더 깔끔해진다는 장점이 있습니다. 
  •  Swift만 사용하여 completion 클로저로 전달하지 않고 return값으로 보내는 타입 생성하기  ( 곰튀김님 영상에 있습니다 ) 
    •  
    • class Observable<T> { var task: (@escaping(T) -> Void) -> Void init(_ task: @escaping(@escaping(T) -> Void) -> Void) { self.task = task } func subscribe(_ completion: @escaping(T) -> Void ) { task(completion) } }​
  • 기본적인 RxSwift의 사용방법 
    • func loadMembers() -> Observable<[Member]> {
          return Observable.create { emitter in
              let task = URLSession.shared.dataTask(with: URL(string: MEMBER_LIST_URL)!) { data, _, error in
                  if let error = error {
                      emitter.onError(error)
                      return
                  }
                  guard let data = data,
                      let members = try? JSONDecoder().decode([Member].self, from: data) else {
                      emitter.onCompleted()
                      return
                  }
      
                  emitter.onNext(members)
                  emitter.onCompleted()
              }
              task.resume()
              return Disposables.create {
                  task.cancel()
              }
          }
      }
      
      loadMembers()
          .observeOn(MainScheduler.instance)
          .subscribe(onNext: { [weak self] members in
              self?.data = members
              self?.tableView.reloadData()
          })
          .disposed(by: disposeBag)​
       

 

클로저내에서 참조하는 객체가 메모리누수가 되지 않도록 하기 위해서는... 

  • subscribe내에서 self를 사용한다면?
    • Observable에서 Error or Completed 를 호출한다. 
      • Error or Completed를 호출하면 스트림이 끊기게 되므로, 신경 쓰지 않아도 된다. 
    • 호출하지 않는다면, weak self로 바꾸고, DisposeBag에 담아야한다. 또는 dispose를 호출해야한다. 
      • 왜 weak self로 바꾸어야 하냐면, 참조 횟수가 2 이상이 되어버리니, self가 메모리에서 해제되지 않고, DisposeBag도 사라지지 않기 때문이다. 
  • subscribe내에서 weak self를 사용한다면? 
    • Observable에서 Error or Completed 를 호출한다.
    • 호출하지 않는다면 DisposeBag에 담아야한다. 또는 dispose를 호출해야한다.
  • ( 하지만 곰튀김님의 말씀으로는 너무 RxSwift에 의존하기보다는 weak self를 사용하는 것을 추천한다고 하셨다. ) 

 


RxSwift의 유용한 Operator

  •  Reactivex.xo 에서 상단  - docs에서 확인할 수 있습니다. 
  • 기본적인 문법에서 더 간략하게 사용할 수 있는 sugar api들이 많이 있습니다. 
    • subscribe에서 event의 분기 처리를 하는 것이 귀찮으니, 특정 event만 고려하겠다 - (onNext: {} ) 
      •  
      • just() .subscribe(onNext: { print($0 )} )
  • 특정 큐에서만 subscribe 하겠다. - observeOn
    •  
    • // main queue just() .observeOn(MainScheduler.instance) .subscribe(onNext: { print($0 )} )
    •  
    • // background queue just() .observeOn(ConcurrentDispatchQueueScheduler(qos: .default)) .subscribe(onNext: { print($0 )} )​

 

 


 

Subject

  • 기본 Observable은 만들때 부터 이미 전달할 값이 정해져있다. 그러므로 외부에서 따로 Observable에게 특정값을 전달하라고 지시할 수가 없다.  그러므로 외부에서도 값을 전달할 수 있는 Observable이면서, Subject라는 타입이 있다.
    •  
    • var menuObservable: BehaviorSubject<[Menu]> = BehaviorSubject<[Menu]>(value: []) // 값을 Subject에 전달하기 viewModel.menuObservable.onNext([ Menu(name: "asdf", price: Int.random(in: 100..<200), count: Int.random(in: 0..<10)), Menu(name: "asdf", price: Int.random(in: 100..<200), count: Int.random(in: 0..<10)), ])

 


 

RxCocoa

  • iOS, macOS, watchOS, tvOS의 개발에서 사용되는 유용한 기능들을 제공합니다. 
  • UIButton를 손쉽게 addTarget을 추가할 수 있습니다. 
    •  
    • button.rx.tap .bind { print("clicked") } .disposed(by: disposeBag)
  • tableview를 dataSource를 구현하지 않고 손쉽게 사용할 수 있습니다.
    • viewModel.allMenus
          .bind(to: tableView.rx.items(cellIdentifier: MenuItemTableViewCell.identifier,
                                               cellType: MenuItemTableViewCell.self)) { 
              indexPath, item, cell in
              cell.updateView(by: item)
          }
          .disposed(by: disposeBag)​
  • 마찬가지로 손쉽게 UILabel의 text에도 subscribe할 수 있습니다.
    • viewModel.totalSelectedCountText
          .bind(to: itemCountLabel.rx.text)
          .disposed(by: disposeBag)

 

 


subscribe, bind, drive  차이 

셋 다 결과값을 처리하는 방법에 사용되는데, 

 

subscribe는 가장 기본적인 결과값을 처리하는 방법입니다. onNext, onError, onCompleted 를 처리할 수 있습니다. 

 

bind는 에러처리를 할 필요가 없는 경우에 사용됩니다.  또한 위에서 봤듯이 ( to: ) 구문으로 UIKit과 손쉽게 바인딩할 수 있습니다 

  • 주석에서도, 에러가 발생할 경우 디버깅 모드에서는 fatalError가 발생하고, release모드에서는 error가 출력됩니다. 

 

drive는 내부적으로 mainThread에서만 결과값을 받아오고, 처리하는 방법입니다.

  • 주석에서도 나와있듯이, drive에서는 에러처리를 할 수가 없습니다.