티스토리 뷰
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는 전달을 하고 전달을 받는 형식으로 이루어져있음
<계산기>
- 버튼을 클릭했을 때 3이라는 숫자를 전달한다.(이벤트를 전달한다)
- → 사용자가 하는 행위들
- 3이라는 숫자를 보여준다. (이벤트를 받아서 처리한다.)
- → 입력에 대한 처리들
Rx의 가장 큰 개념!!
방을 어지르는 사람과 치우는 사람은 따로있듯이
이벤트를 전달한다(어지르는 사람) → Observable
이벤트를 받아서 처리한다(치우는 사람) → Observer
계산기를 다시 보면..
<계산기>
- 버튼을 클릭했을 때 3이라는 숫자를 전달한다. → Observable
- 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 |