티스토리 뷰

🍎 iOS

RxSwift(1)

yeding 2024. 7. 30. 10:53

ReactiveX


 

An API for asynchronous programming with observable streams

asynchronous programming


Rx가 얘기하는 비동기 프로그래밍이란?

  • 앱의 전체적인 사용관점에서 비동기라고 한다.
    → 코드의 실행 순서가 정해져있지 않다.
    → 사용자의 입력 등 다양한 외부적인 요인에 따라서 코드는 다르게 실행 될 수 밖에 없음: Line by line 으로 실행되는 것이 불가능하다.

Rx에서는 아래의 기능들을 포함하여 비동기 프로그래밍이라고 한다.

  • Event - Touch Up Inside
  • Closure
  • GCD
  • Delegate Pattern
  • Notification Center

Observable Streams(Observable Sequence)

Observable, Observer

RxSwift는 전달을 하고 전달을 받는 형식으로 이루어져있음

<계산기>

  1. 버튼을 클릭했을 때 3이라는 숫자를 전달한다.(이벤트를 전달한다)
  2. → 사용자가 하는 행위들
  3. 3이라는 숫자를 보여준다. (이벤트를 받아서 처리한다.)
  4. → 입력에 대한 처리들

Rx의 가장 큰 개념!!

방을 어지르는 사람과 치우는 사람은 따로있듯이

이벤트를 전달한다(어지르는 사람) → Observable

이벤트를 받아서 처리한다(치우는 사람) → Observer

계산기를 다시 보면..

<계산기>

  1. 버튼을 클릭했을 때 3이라는 숫자를 전달한다. → Observable
  2. 3이라는 숫자를 보여준다. → Observer

Stream

코드가 시간순서에 따라 다르게 흘러가면 데이터들의 모양이 바뀌게 되는데, 아래와 같은 데이터의 흐름을 Stream이라고 한다.

let result = array
            .filter{ $0 % 2 == 0 } // [2,4,6,8]
            .map{ $0 * 2 } //[4,8,12,16]
            .map{ "\\($0)일" } //[4일,8일,12일,16일]
							
print(result)

Rx에서는 이런 흐름을 Observable Sequence, Stream 등 으로 부른다.

Observable/Observer

Observable은 계속 이벤트를 전달하고

Observer은 계속 관찰하고 있어야한다.

둘은 상생관계

만약 Observable에게 Observer이 없다면?

→ 계산기 앱에서 숫자를 계속 누르지만 아무 일도 일어나지 않는 것과 같다.

Observable에 Observer이 없다가, 나중에 Observer이 생긴다면?

Ex) 4번째 숫자가 들어왔을 경우 숫자 텍스트에 콤마를 찍는다.

→ 4번째가 들어왔을때 Observer이 생겨 그 이후부터 처리하게 되는 것과 같음

Subscribe

그러면 Observable이 Observer의 처리를 받는 방법은?

Subscribe(구독)을 통해 연결시켜준다

Dispose

Observable에 Observer이 나중에 없어진다면?

→ 처리해왔던 이벤트들을 앞으로는 처리하지 못함 (Dispose)

Observable, Observer, Subscribe, Dispose 단어를 기억해두자!

ex) A유튜버가 올리는 영상이 올라올 때(Observable) 알림을 받으려면,

→ A유튜버의 구독버튼을 눌러 구독(Subscribe)해야한다!

→ 구독 이후 시점부터 알림(Observer)을 받을 수 있다.

→ A유튜버의 구독을 취소(Dispose)했을 경우 영상을 올려도 더이상 알림을 받지 못함

Observable Sequence

Infinite observable sequence

  • 끝이 없는 이벤트 전달(무한 시퀀스)
  • ex) 천번을 흔들던 만번을 흔들던 핸드폰의 가로모드는 적용이 됨

→ UI와 관련된 이벤트에 해당한다 == 이벤트를 방출 한다

Finite observable sequence

  • 끝이 있는 이벤트 전달(유한 시퀀스)→ 점진적으로 실행
  • → 점진적으로 실행되기 때문에 여러 상황이 일어날 수 있음( 네트워크 오류, 등등..)
  • ex) NASA API를 사용해서 사진을 받아왔을 때

Observable의 이벤트 전달(Next, Complete, Error)

NASA API를 통해 사진 정보를 받아올때와 Observable Sequence를 비교한다면

next → 점진적 다운로드/최신데이터 전달

complete → 다운로드 완료

error → 디코딩 실패/상태코드 오류/네트워크 연결 유실

이렇게 Rx에서는 세가지의 이벤트 전달 방법이 있다.

무한시퀀스의 경우에는 next이벤트만 사용한다.

→ 화면을 전환하면서 UI적으로 Error가 나거나 성공 여부가 필요 없기 때문

→ next를 방출(emit) 할 뿐

유한 시퀀스의 경우에는 실패/성공 여부가 필요하기 때문에

→ 방출 뿐 아니라, complete와 error 이벤트 전달(notification)이 필요하다

이벤트를 잘 전달하려면 전달할 요소가 필요한데 ,이것을 Operator라고 한다.

just/from/interval

func testJustKeyword(){
        //나 Int배열로 일 벌릴래~
        Observable
            .just([1,2,3,4,5]) //Finite Observable Sequence
            .subscribe { arr in              //이렇게 전달할게~
                print("next: \\(arr)")
            } onError: { error in
                print(error)
            } onCompleted: {
                print("completed")
            } onDisposed: {
                //구독 취소가 되었을때 동작하는 클로저라 Dispose는 아님
                print("dispose")
            }
            .disposed(by: disposeBag)
    }
    
    func testFromKeyword(){
        //나 Int배열에 원소 하나씩 일 벌릴래~
        Observable
            .from([1,2,3,4,5]) //Finite Observable Sequence
            .subscribe { arr in              //이렇게 전달할게~
                print("next: \\(arr)")
            } onError: { error in
                print(error)
            } onCompleted: {
                print("completed")
            } onDisposed: {
                //구독 취소가 되었을때 동작하는 클로저라 Dispose는 아님
                print("dispose")
            }
            .disposed(by: disposeBag)
    }
    
    func testIntervalKeyword(){
        //나 특정 간격으로 일 벌릴래~
        Observable<Int>
            .interval(.seconds(1), scheduler: MainScheduler.instance)   //Infinite Observable Sequence(언제 끝날지 모름)
            .subscribe { num in              //이렇게 전달할게~
                print("next: \\(num)")
            } onError: { error in
                print(error)
            } onCompleted: {
                print("completed")
            } onDisposed: {
                //구독 취소가 되었을때 동작하는 클로저라 Dispose는 아님
                print("dispose")
            }
            .disposed(by: disposeBag)
    }

 

 

button.addTarget(self, action: #selector(buttonTapped), for: .touchUpInside)을 Rx로 바꿔보자!

//1.
//Observable Stream
//button - Infinite Observable Sequence (error와 completed는 실행되지 않음)
button                              //UIButton
  .rx                             //Reactive<>
  .tap                            //ControlEvent<Void>
  .subscribe { _ in               //Observer
      self.label.text = "버튼을 클릭했어요."
      print("next")
  } onError: { error in
      print(error)
  } onCompleted: {
      print("completed")
  } onDisposed: {
      print("disposed")
  }
  .disposed(by: disposeBag)

//2.
//subscribe의 next, onCompleted, onDisposed는 nil이라 생략해도 된다.
button                              //UIButton
  .rx                             //Reactive<>
  .tap                            //ControlEvent<Void>
  .subscribe { _ in               //Observer
      self.label.text = "버튼을 클릭했어요."
  } onDisposed: {
      print("disposed")
  }
  .disposed(by: disposeBag)

 

 

self 를 약하게!

//3.leak
button.rx.tap
  .subscribe { [weak self] _ in
      self?.label.text = "버튼을 클릭했어요."
  }
  .disposed(by: disposeBag)

//4.withUnretained
button.rx.tap
  .withUnretained(self)   //weak self 대신
  .subscribe {  _ in
      self.label.text = "버튼을 클릭했어요."
  }
  .disposed(by: disposeBag)

//5.withUnretained
button.rx.tap
  .subscribe(with: self, onNext: { owner, _ in
      owner.label.text = "버튼을 클릭했어요."
  }, onDisposed: { owner in
      print("disposed")
  })
  .disposed(by: disposeBag)

 

 

MainThread에서 동작시켜보자

//6. MainThread
//subscribe: background thread에서 동작할 수 있음
//보라색 오류 뜰 수 있음
//ex) 네트워크 통신과 결합되어 있을 경우
button.rx.tap
  .subscribe(with: self, onNext: { owner, _ in
      DispatchQueue.main.async {
          owner.label.text = "버튼을 클릭했어요."
      }
  }, onDisposed: { owner in
      print("disposed")
  })
  .disposed(by: disposeBag)

//7. MainThread
button.rx.tap
  .observe(on: MainScheduler.instance)    //MainThread에서 동작하게 만들게!
  .subscribe(with: self, onNext: { owner, _ in
      owner.label.text = "버튼을 클릭했어요."
  }, onDisposed: { owner in
      print("disposed")
  })
  .disposed(by: disposeBag)

//8. RxCocoa
//메인스레드에서 동작 + 애초에 error 안받는 친구로 -> UI에 적절한 친구로 만들어줘!
button.rx.tap
  .bind(with: self, onNext: { owner, _ in
      owner.label.text = "버튼을 클릭했어요."
  })
  .disposed(by: disposeBag)

 

 

응용

//9.
button.rx.tap
  .map{ "버튼을 클릭했어요" }      // Observable<String>
  .bind(to: label.rx.text)    //다이렉트로 꽂아줌
  .disposed(by: disposeBag)

 

왜 label.rx.text는 subscribe가 없지?

RxCocoa - TableView

데이터 연결

//이벤트 전달: Observable -> 어디에 보여질지는 수습하는 Observer 마음이다.
let items = Observable.just([
    "ggamdoo",
    "taaaaan",
    "yeji"
])

//Observer: 테이블 뷰에 보여주는 형태로 처리하고있다.(클로저 부분에 해당)
//구독은 누가? bind! (Subscribe 키워드에서 발전해온 것)
//bind는 UI에 사용 RxCocoa가 import되어있지 않다면 사용할 수 없음
items
.bind(to: tableView.rx.items) { (tableView, row, element) in
    let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
    cell.textLabel?.text = "\\(element) @ row \\(row)"
    return cell
}
.disposed(by: disposeBag) //subscribe가 취소되는 부분인가?

 

테이블뷰 이벤트

//didSelectRowAt - indexPath
tableView.rx.itemSelected
    .bind{ print($0) }
    .disposed(by: disposeBag) //[0,0]

//didSelectRowAt - 값
tableView.rx.modelSelected(String.self)
    .bind{ print($0) }
    .disposed(by: disposeBag) //ggamdoo
    
//zip을 통해 하나로 합칠 수 있음
Observable
		.zip(tableView.rx.itemSelected, tableView.rx.modelSelected(String.self)) //operator
		.bind { print($0) }
		.disposed(by: disposeBag)  //([0, 0], "ggamdoo")

'🍎 iOS' 카테고리의 다른 글

RxSwift vs Combine  (0) 2024.09.10
Compositional Layout - CollectionView(System설정)  (0) 2024.07.19
Method Dispatch  (0) 2024.07.15
Design Pattern  (0) 2024.07.14
MVVM Pattern(1)  (0) 2024.07.14
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/02   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
글 보관함