RxSwift
강의 자료
iamchiwon/RxSwift_In_4_Hours
[1교시] 개념잡기 - RxSwift를 사용한 비동기 프로그래밍
-
onLoad()
- LOAD 버튼을 누르면 상단 시간이 멈추고, 버튼도 눌린 상태에서 멈췄다가 json을 모두 다운로드하면 시간이 다시 흘러간다.→ 👋왜 멈출까? 동기로 동작하기 때문, 비동기로 바꿔줘야 한다.
→ 해당 함수에서 서버 호출 부분을
DispatchQueue.global().async{}
로 감싸 준다.→ 👋오류가 발생한다. iOS에서 UI 변경은 main Thread에서 해야 한다. 코드 중 UI를 변경하는 부분을 main Thread에서 하도록 변경한다.
→ 시간도 멈추지 않고, indicator도 떴다가 사라진다.
비동기 프로그래밍
-
다른 쓰레드를 사용해서 현재 하고 있는 작업은 작업대로 하고 다른 스레드에서 내가 원하는 작업을 동시에 수행하는 것
-
iOS에서는
DispatchQueue
,OperationQueue
가 비동기 프로그래밍을 하기 위한 대표적인 메소드이다. -
DispatchQueue
부분을 함수로 분리하기func downloadJson(_ url: String, _ completion: @escaping (String?) -> Void) { 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(json) } } }
→ 👋함수로 분리해서 사용하는 부분은 간결해지긴 했지만, completion 구문 등등.. 또 비동기 호출이 많아지면?? 코드가 점점 복잡해진다. 그냥 String을 리턴할 수 없을까?
→ 아래와 같이 수정할 수 있다.
→
나중에생기는데이터<T>
와 같은 기능을 갖는 유틸리티 - PromiseKit, Bolt, RxSwift 등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 downloadJson(_ url: String) -> 나중에생기는데이터<String?> { return 나중에생기는데이터() { f in 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) } } } }
-
RxSwift로 바꿔 보기
나중에생기는데이터<T>
→Observable<T>
나중에오면
→subscribe
RxSwift는 비동기적으로 발생한 데이터를 completion 같은 클로저로 전달하는 것이 아니라 리턴 값으로 전달하기 위해 만들어진 유틸리티이다._ = downloadJson(MEMBER_LIST_URL) .subscribe { event in // 클로저 범위는 여기부터 switch event { case .next(let json): self.editView.text = json self.setVisibleWithAnimation(self.activityIndicator, false) case .completed: break case .error: break } } // 여기까지 }
-
👋순환 참조
-
위 구문에서 순환 참조가 발생함
→ 클로저가
subscribe { }
부분인데, 클로저가 self를 캡쳐하면서 RC가 증가하기 때문에 순환 참조가 발생한다. RC가 감소될 때는 클로저가 없어질 때이다.→ 그럼 클로저는 언제 없어지나?
complete
,error
일 때 -
순환 참조 해결 방법 1 -
[weak self]
추가 -
순환 참조 해결 방법 2 -
onCompleted()
추가→
onNext()
이후onCompleted()
추가 (처리가 끝남을 알림), 그러면onLoad()
함수의 switch 문에서 next를 실행하고 complete 가 실행하고 클로저가 사라져 클로저가 가지고 있는 self에 대한 RC도 사라진다.
-
-
Observable의 생명주기
- Create (만들었다고 실행되는 건 아님)
- Subscribe (Observable은 subscribe 됐을 때 동작한다)
- onNext
- —— 끝 —— →동작이 끝난 observable은 재사용 불가, 새로운 subscribe이 있어야 동작함
- onCompleted / onError
- Disposed
-
But, 지금까지 만들어진 코드도 길다. 추가적인 귀찮음을 없애보자
Sugar API (→ Operator) -
Observable.create
를 이용하면 Hello World 한 문장을 출력하는 데도 4줄의 코드가 필요하다. -
just
,from
사용하기// Hello World 한 문장을 출력하는 데도 4줄의 코드가 필요하다. return Observable.create { emitter in emitter.onNext("Hello World") emitter.onCompleted() return Disposables.create() } // 1줄로 바꾸기 return Observable.just("Hello World") // Optional("Hello World") // 2개 이상 값 보내기 return Observable.just(["Hello"], ["World"]) // [Optional("Hello") Optional("World")] return Observable.from(["Hello"], ["World"]) // Optional("Hello") // Optional("World")
-
subscribe
_ = downloadJson(MEMBER_LIST_URL) .subscribe(onNext: { print($0) }, onError: { err in print(err) }, onCompleted: { print("Com") })
-
observeOn
을 사용해서DispatchQueue
구문을 줄일 수 있다.observeOn
은 RxSwift가OperationQueue
메소드를 래핑한 함수다.
-
데이터를 전달되는 중간에 가로채서 바꿔치기 하는 역할을 가짐, 종류가 매우 많다. (마블 그림에 함수의 설명이 나오니 그림을 이해할 줄 알면 동작을 이해할 수 있다.)
-
http://reactivex.io/documentation/operators.html
_ = downloadJson(MEMBER_LIST_URL) .observeOn(MainScheduler.instance) // operator .subscribe(onNext: { json in self.editView.text = json self.setVisibleWithAnimation(self.activityIndicator, false) })
[2교시] RxSwift 활용하기 - 쓰레드의 활용과 메모리 관리-
share
-
combine
,merge
- 여러 데이터 합치기,zip
- 하나씩 쌍으로 만들어서 전달, 쌍이 없으면 전달 못함 -
**observeOn
vssubscribeOn
차이점**observeOn
- 바로 밑의 쓰레드에 영향을 줌, downstream에 영향subscribeOn
- 위치에 상관없이 맨 처음 쓰레드에 영향을 줌, upstream에 영향
[3교시] RxSwift 활용범위 넓히기 - UI 컴포넌트와의 연동-
Subject
는 observable 밖에서 데이터를 컨트롤해서 새로운 값을 집어넣을 수 있다. -
양방향성을 가진다.
-
Subject의 종류Observable.create
했을 때 받았던 emitter와 같은 역할을 하는데 emitter와 달리 observable 밖에서 사용 가능참고 - https://github.com/fimuxd/RxSwift/blob/master/Lectures/03_Subjects/Ch3. Subjects.md
- (AsyncSubject, BehaviorSubject, PublishSubject, ReplaySubject)
-
AsyncSubject
-
여러 개가 구독해도 데이터를 내려보내지 않고 complete됐을 때 데이터를 내려 보낸다.
-
-
BehaviorSubject
-
기본 값을 하나 가지고 시작하고 subscribe가 되자마자 앞에서 설정한 기본 값을 내려 준다. 구독 이후에는 데이터가 생기면 내려 보낸다.
-
추가로 subscribe이 되면 최근에 만들어진 값을 내려보내고 이후 값이 생성되면 이어서 값을 내려 보낸다.
-
-
PublishSubject
-
Subject에 누군가 subscribe할 수 있다. 내부에서 데이터가 생성이 되면 데이터를 그대로 내려 보낸다.
-
-
ReplaySubject
-
처음 동작은 PublishSubject와 동일하고 다음 구독이 생성되면 이전에 발생한 데이터를 한꺼번에 내려 보낸다. (replay)
-
-
- Subject (http://reactivex.io/documentation/subject.html)
- Stream의 분리 및 병합
예제를 그림으로 이해해 보자
/// [Menu]를 받는 observable
var menuObservable = BehaviorSubject<[Menu]>(value: [])
/// menuObservable의 값이 바뀌면 아이템 갯수가 다시 계산된다
lazy var itemsCount = menuObservable.map {
$0.map { $0.count }.reduce(0, +)
}
/// 메뉴의 count가 바뀌면, 즉 menuObservable의 값이 바뀌면 총합을 다시 계산한다.
lazy var totalPrice = menuObservable.map {
$0.map { $0.price * $0.count }.reduce(0, +)
}
init() {
let menus: [Menu] = [
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0),
Menu(name: "튀김1", price: 100, count: 0)
]
menuObservable.onNext(menus)
}
RxCocoa
- UI 처리 (
UIKit.rx
) - UI 작업의 특징
→ 항상 UI Thread 에서만 돌아가야 함, 그래서observeOn
을 항상 쓴다.
→ UI는 작업을 처리하다 오류가 나면 작업이 끝나고 이 스트림은 다시 사용, 처리할 수 없다. 그래서 에러가 나도 스트림이 끊어지지 않도록 처리해야 한다. (ex:catchErrorJustReturn("")
,asDriver()
,drive()
) - Observable / Driver (UI 용)
→ driver는 항상 메인 쓰레드에서 동작한다.observeOn
이 필요없다. - Subject / Relay (UI 용)
→ Subject와 같지만 에러가 나도 끊어지지 않는 다는 점이 다르다.
→ 무조건 끝나기 때문에onNext()
가 아닌accept()
를 사용한다.
[4교시] RxSwift 를 활용한 아키텍쳐 - 프로젝트에 MVVM 적용하기
MVC
Input을 Controller가 받는다. 입력에 대한 출력도 Controller가 한다. (View, Model Update)
View와 Controller는 UIKit
에 종속되어 테스트가 불가능하지만 Model은 종속되지 않으므로 테스트가 가능하다.
MVP
Controller의 역할을 제한하자. ViewController의 역할을 View에 넘기고, VC에서 했던 로직만 따로 빼어 Presenter로 한다. 모든 판단과 로직은 Presenter가 하게 만든다.
View ↔ Presenter는 1:1 관계를 갖는다.
MVVM
ViewModel이 화면에 어떤 걸 그리라고 지시하지 않는다. View가 ViewModel을 바라보면서 값이 바뀌면 스스로 갱신한다.
MVP나 MVVM은 아이디어는 비슷하고 처리하는 방식이 조금 다르다.
'Programming > iOS' 카테고리의 다른 글
[iOS] Apple Login(1) Authenticating Users with Sign in with Apple (0) | 2020.07.02 |
---|---|
ReactorKit으로 단방향 반응형 앱 만들기 내용 정리 (0) | 2020.04.26 |
[iOS/Swift5] Universal Links 예제 프로젝트 (2) | 2020.04.16 |
Fastlane 으로 iOS 배포를 쉽게 하자! - Fastlane with Slack (0) | 2019.10.27 |
Fastlane 으로 iOS 배포를 쉽게 하자! - Beta Deployment (2) | 2019.10.21 |