동기 비동기
동기 : 하나를 하면 다른 거는 못하게 막는 것
ex) 타이머가 돌아가고 있는데 사진이 나오게 하면 사진이 나올 때까지 타이머가 멈춤
비동기 : 상관없이 돌아가는 것
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.26.27.png)
DispatchQueue를 넣으면 동기가 비동기가 됨
DispatchQueue에는 concurrency Queue(global)와 serial Queue가 있고 각각은 동기와 비동기로 나뉠 수 있다
UI건드는 거는 main스레드에서 돌려야 되니까 main에 넣음
⇒ DispatchQueue로 처리 하기가 너무 귀찮으니까!! 다양한 라이브러리가 존재 : promiseKit, boltKit, rxswift등등
promiseKit
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.30.04.png)
rxSwift - 비동기 처리를 쉽게 하기 위함, promiseKit와 동일하지만 거기에 operator가 있어서 더 특별함을 가진다고 볼 수 있음.
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.30.49.png)
subscribe를 하면 disposable가 리턴됨 → 그걸로 취소가 가능
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.35.29.png)
이 취소 작업을 dispatchQueue를 하면 flag로 하거나 하는 귀찮은 작업이 필요하므로 이러한 utility를 사용하면 더 쉬워짐
DIsposeBag
disposable을 담는 백
작업을 도중에 취소할 수 있다!
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.39.28.png)
disposeBag = disposeBag()
insert로 담을 수 있지만 한꺼번에 버리는 함수가 없기 때문에 위의 문장처럼 새로 선언해주면 들어있던 disposable이 모두 사라진다
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.40.04.png)
← 같은 코드를 간단하게 표현하는 방법
Operator
이전에는 observable을 생성해서 seal에 onNext로 데이터를 넘겨줬었는데 귀찮아서 데이터를 직접받게 해주는 메소드가 존재함 : just
Just : 그냥 바로 내려보냄
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.45.06.png)
From : 배열 하나씩 내려보냄
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.47.52.png)
Map & Filter
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.48.01.png)
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.50.35.png)
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.50.40.png)
→ Operator에는 생성(observable을 생성, create, just, from), 변환(map), 필터링(filter),결합(zip), 오류처리, 조건과 불린 연산자, 수학과 집계 연산자, 역압 연산자 등등 존재 : Docs Operators에 들어가면 다 있음, 사이트에 트리가 있어서 약간 읽어가면서 맞는 걸 선택할 수 있음!!
Marble Diagram
구슬은 데이터, 화살표는 스트림, 짝대기는 completed을 의미하며 complete되면 disposebag에 들어간다
엑스는 error를 의미!
ex)
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.59.18.png)
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_2.59.32.png)
-
다르게 생긴 구슬은 움직일 수 있다
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.00.51.png)
-
생성은 스트림이 아닌 걸 넣으면 스트림이 나오는 거고 map같은 애는 stream을 넣어서 Stream이 나오는 형태 → 따라서 map은 just로 스트림을 만든 후에 map을 사용할 수 있음!!, 처음부터 map을 사용할 수 없음, filter도 마찬가지!!
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.07.41.png)
map은 데이터를 넣으면 데이터가 나오는데 flatMap은 데이터를 넣으면 stream이 나온다
https://rxmarbles.com/ ← 이사이트로 마블을 그릴 수 있어서 테스트 가능
Next, Error, Completed
작대기 : completed
전달되는 것 : next
엑스 : Error
operator로 만든 최종 얻은 데이터를 받아서 사용하려면 subscribe를 하며 된다!!
- subscribe()
- subscribe(event)
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.14.19.png)
operator는 대부분 stream을 리턴하지만(Observable) subscribe는 리턴타입이 disposable임!
stream은 에러나 complete되는 경우 종료된다
- subscribe(?,?,?,?)
- switch문으로 작성하는 경우 모든 케이스를 작성해야만 한다는 단점이 존재하므로 subscribe 인자에 원하는 case만 작성해줘도 된다! optional이기 때문!
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.19.34.png)
dispose가 불리는 경우는 complete 혹은 error난 경우 불림
Scheduler
exMap3는 main스레드에서 map, filter를 너무 많이 해서 스크롤이 멈춘다는 문제가 있음
→ .observeOn(ConcurrentDispatchQueueScheduler(qos:.default))를 넣으면 concurrent스레드에서 가고 보여주기 직전에는 ui 는 메인스레드에서 해야하기 때문에 .observeOn(MainScheduler.instance)로 돌아가서 해야 한다
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_3.24.37.png)
observeOn을 건 다음부터 걸게 된다는 것 따라서 데이터를 로드하기 전에 다른 쓰레드로 가는 것이 좋음!
그렇다면 just에 바로 데이터를 가져오게 한다면 observeOn은 다음줄부터니까 안되자나!!
→ .subscribeOn으로 scheduler를 지정하면 얘는 어디에 두든 subscribe를 할 때 바로 동작하게 되어서 상관없게 된다!
cf. network queue를 따로 만들어서 사용한다면 다른 애들은 dispatchqueue로 하면 되니까 시간을 더 줄일 수 있다!
SideEffect
operator들은 전부 sideEffect이 없지만 subscribe과 do라는 애만 sideEffect를 가짐
do : subscribe를 지나가기 전에 event를 감지하는 곳이라고 보면 됨 좀 더 자세하게 나눠서 작성 가능함
RxCocoa(Rxswift 응용해보기)
Uikit를 다룰 때 편한 것들이 들어있음
ex) 이메일, 비번 잘 작성하면 버튼 활성화되도록 하는 예제
기존 : delegate 위임해서 action으로 확인해서 둘다 만족하면 버튼을 활성화시킴
→ rxswift는 비동기에 사용되는 것!! 데이터를 나중에 주기 때문에 ui에 대한 이벤트도 비동기로 처리 가능!!
stream에다가 event를 넣어주면 비동기로 ui를 처리할 수 있게 된다!!
- rxCocoa를 이용해서 rx.를 아웃렛에 넣어주면 그걸 비동기 처리하겠다는 의미가 됨!!
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.25.35.png)
- 리팩토링해보자!
- input : 아이디 입력, 비번 입력
- output : 불빛, 로그인버튼 활성화
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_4.36.17.png)
Subject
- asyncSubject, bahaviorSubject, publishSubject, replaySubject가 존재
- observable의 역할을 하면서 동시에 observable은 외부에서 이미 있는 데이터를 넣어줘야 하는데 subject는 외부의 데이터를 받아오면서 observable도 하는 것
- 외부에서 통제하는 observable
BehaviorSubject
- default값을 지정하여 선언
- subscribe하면 default값을 내려주고 데이터가 발생하면 전달해주고 만약에 다른 애가 behaviorsubject를 subsribe하면 그 때의 최신값을 주고 계속 한다
- behaviorSubject가 다른 애를 subscribe할 수 있다는 것은 observable이라는 소리인데 스스로 데이터를 발생할 수 있음!
- observable은 just로 데이터를 갖고 있어서 준건데 subject는 데이터가 나중에 발생하면 외부에서 넣어줄 수 있는 것!!
- 넣어줄 수도 있고 observable할 수 있는 것!!
PublishSubject
behaviorSubject는 디폴트 값이 없고 나중에 데이터가 생성되면 줌
replaySubject
subsribe하고 나서 데이터 들어오면 보내지만 또subscribe하면 앞에꺼 다 줌
asyncSubject
끝이 나야 전달이 되는 subject, 끝난 시점에 가장 마지막 꺼를 줌
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_5.04.15.png)
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_5.04.38.png)
cf. 내가 추가한 빈칸일 때에도 불빛이 꺼지게 하는 코드
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-02_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%2592%25E1%2585%25AE_6.08.16.png)
cf. subscribe 대신에 bind를 사용하여 간단하게 줄인 코드, 들어온 값을 단순히 세팅하는 경우에는 bind가 더 쉽다!
%2065fc89b920fb4b929f0abad0c5713607/%25E1%2584%2589%25E1%2585%25B3%25E1%2584%258F%25E1%2585%25B3%25E1%2584%2585%25E1%2585%25B5%25E1%2586%25AB%25E1%2584%2589%25E1%2585%25A3%25E1%2586%25BA_2022-08-04_%25E1%2584%258B%25E1%2585%25A9%25E1%2584%258C%25E1%2585%25A5%25E1%2586%25AB_10.25.37.png)
cf.로그인 앱에서 viewModel 적용해보기(input.output 적용해서)
- viewController에서 받은 입력(observable)을 그대로 viewModel에 input으로 가져온다
- 그 후, output은 input을 이용하여 만든다. 이 때 observable(input)을 구독하여 subject에 결과를 넣은(방출한) 뒤 subject를 observable로 변환하여 output을 만든다
- viewModel은 output(observable)으로 view를 bind(구독)하여 변경해준다.
func bindUsingViewModel1(){
let input = ViewModel.Input(email: idField.rx.text.orEmpty.asObservable(), pw: pwField.rx.text.orEmpty.asObservable())
viewModel = ViewModel(input: input)
let output = viewModel.calculateOutput(input: input)
output.isEmailValid
.drive(idValidView.rx.isHidden)
.disposed(by: disposeBag)
output.isPasswordValid
.drive(pwValidView.rx.isHidden)
.disposed(by: disposeBag)
//1.UI를 가지고 결정하는 거니까 viewController에 있어도 무방하여 여기서 결정하는 방법
// Driver.combineLatest(output.isPasswordValid,output.isEmailValid ){b1,b2 in b1 && b2}
// .drive(loginButton.rx.isEnabled)
// .disposed(by: disposeBag)
//2. viewModel에게 위임하는 방법
output.isLoginValid
.drive(loginButton.rx.isEnabled)
.disposed(by: disposeBag)
}
import Foundation
import RxSwift
import RxCocoa
protocol ViewModelType {
associatedtype Input
associatedtype Output
var disposeBag: DisposeBag { get set }
func calculateOutput(input: Input) -> Output
}
class ViewModel : ViewModelType {
var disposeBag: DisposeBag = DisposeBag()
// MARK: - Logic
let input: Input
// let output: Output
struct Input {
//input : 두개의 입력, observer
let email : Observable<String>
let pw : Observable<String>
}
struct Output {
//output : 출력, obserable
let isEmailValid : Driver<Bool>
let isPasswordValid : Driver<Bool>
let isLoginValid : Driver<Bool>
}
init(input : Input){
self.input = input
}
func calculateOutput(input : Input) -> Output {
//observer를 사용하기 위한 subject
let emailSubject = BehaviorRelay(value: false)
let pwSubject = BehaviorRelay(value: false)
input.email.subscribe(onNext: {
email in
emailSubject.accept(self.checkEmailValid(email))
}).disposed(by: disposeBag)
input.pw.subscribe(onNext: {pw in
pwSubject.accept(self.checkPasswordValid(pw))
}).disposed(by: disposeBag)
let loginDriver = Driver.combineLatest(emailSubject.asDriver(),pwSubject.asDriver() ){b1,b2 in b1 && b2}.asDriver()
return Output(isEmailValid: emailSubject.asDriver(), isPasswordValid: pwSubject.asDriver(),isLoginValid: loginDriver)
}
private func checkEmailValid(_ email: String) -> Bool {
// return email.contains("@") && email.contains(".")
return email.isEmpty || (email.contains("@") && email.contains("."))
}
private func checkPasswordValid(_ password: String) -> Bool {
// return password.count > 5
return password.isEmpty || password.count > 5
}
}