동기 비동기

동기 : 하나를 하면 다른 거는 못하게 막는 것

ex) 타이머가 돌아가고 있는데 사진이 나오게 하면 사진이 나올 때까지 타이머가 멈춤

비동기 : 상관없이 돌아가는 것

스크린샷 2022-08-02 오후 2.26.27.png

DispatchQueue를 넣으면 동기가 비동기가 됨

DispatchQueue에는 concurrency Queue(global)와 serial Queue가 있고 각각은 동기와 비동기로 나뉠 수 있다

UI건드는 거는 main스레드에서 돌려야 되니까 main에 넣음

⇒ DispatchQueue로 처리 하기가 너무 귀찮으니까!! 다양한 라이브러리가 존재 : promiseKit, boltKit, rxswift등등

promiseKit

스크린샷 2022-08-02 오후 2.30.04.png

rxSwift - 비동기 처리를 쉽게 하기 위함, promiseKit와 동일하지만 거기에 operator가 있어서 더 특별함을 가진다고 볼 수 있음.

스크린샷 2022-08-02 오후 2.30.49.png

subscribe를 하면 disposable가 리턴됨 → 그걸로 취소가 가능

스크린샷 2022-08-02 오후 2.35.29.png

이 취소 작업을 dispatchQueue를 하면 flag로 하거나 하는 귀찮은 작업이 필요하므로 이러한 utility를 사용하면 더 쉬워짐

DIsposeBag

disposable을 담는 백

작업을 도중에 취소할 수 있다!

스크린샷 2022-08-02 오후 2.39.28.png

disposeBag = disposeBag()

insert로 담을 수 있지만 한꺼번에 버리는 함수가 없기 때문에 위의 문장처럼 새로 선언해주면 들어있던 disposable이 모두 사라진다

스크린샷 2022-08-02 오후 2.40.04.png

← 같은 코드를 간단하게 표현하는 방법

Operator

이전에는 observable을 생성해서 seal에 onNext로 데이터를 넘겨줬었는데 귀찮아서 데이터를 직접받게 해주는 메소드가 존재함 : just

Just : 그냥 바로 내려보냄

스크린샷 2022-08-02 오후 2.45.06.png

From : 배열 하나씩 내려보냄

스크린샷 2022-08-02 오후 2.47.52.png

Map & Filter

스크린샷 2022-08-02 오후 2.48.01.png

스크린샷 2022-08-02 오후 2.50.35.png

스크린샷 2022-08-02 오후 2.50.40.png

→ Operator에는 생성(observable을 생성, create, just, from), 변환(map), 필터링(filter),결합(zip), 오류처리, 조건과 불린 연산자, 수학과 집계 연산자, 역압 연산자 등등 존재 : Docs Operators에 들어가면 다 있음, 사이트에 트리가 있어서 약간 읽어가면서 맞는 걸 선택할 수 있음!!

Marble Diagram

구슬은 데이터, 화살표는 스트림, 짝대기는 completed을 의미하며 complete되면 disposebag에 들어간다

엑스는 error를 의미!

ex)

스크린샷 2022-08-02 오후 2.59.18.png

스크린샷 2022-08-02 오후 2.59.32.png

  • 다르게 생긴 구슬은 움직일 수 있다

    스크린샷 2022-08-02 오후 3.00.51.png

  • 생성은 스트림이 아닌 걸 넣으면 스트림이 나오는 거고 map같은 애는 stream을 넣어서 Stream이 나오는 형태 → 따라서 map은 just로 스트림을 만든 후에 map을 사용할 수 있음!!, 처음부터 map을 사용할 수 없음, filter도 마찬가지!!

스크린샷 2022-08-02 오후 3.07.41.png

map은 데이터를 넣으면 데이터가 나오는데 flatMap은 데이터를 넣으면 stream이 나온다

https://rxmarbles.com/ ← 이사이트로 마블을 그릴 수 있어서 테스트 가능

Next, Error, Completed

작대기 : completed

전달되는 것 : next

엑스 : Error

operator로 만든 최종 얻은 데이터를 받아서 사용하려면 subscribe를 하며 된다!!

  1. subscribe()
  2. subscribe(event)

스크린샷 2022-08-02 오후 3.14.19.png

operator는 대부분 stream을 리턴하지만(Observable) subscribe는 리턴타입이 disposable임!

stream은 에러나 complete되는 경우 종료된다

  1. subscribe(?,?,?,?)
    • switch문으로 작성하는 경우 모든 케이스를 작성해야만 한다는 단점이 존재하므로 subscribe 인자에 원하는 case만 작성해줘도 된다! optional이기 때문!

    스크린샷 2022-08-02 오후 3.19.34.png

dispose가 불리는 경우는 complete 혹은 error난 경우 불림

Scheduler

exMap3는 main스레드에서 map, filter를 너무 많이 해서 스크롤이 멈춘다는 문제가 있음

→ .observeOn(ConcurrentDispatchQueueScheduler(qos:.default))를 넣으면 concurrent스레드에서 가고 보여주기 직전에는 ui 는 메인스레드에서 해야하기 때문에 .observeOn(MainScheduler.instance)로 돌아가서 해야 한다

스크린샷 2022-08-02 오후 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.를 아웃렛에 넣어주면 그걸 비동기 처리하겠다는 의미가 됨!!

스크린샷 2022-08-02 오후 4.25.35.png

  • 리팩토링해보자!
    • input : 아이디 입력, 비번 입력
    • output : 불빛, 로그인버튼 활성화

스크린샷 2022-08-02 오후 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, 끝난 시점에 가장 마지막 꺼를 줌

스크린샷 2022-08-02 오후 5.04.15.png

스크린샷 2022-08-02 오후 5.04.38.png

cf. 내가 추가한 빈칸일 때에도 불빛이 꺼지게 하는 코드

스크린샷 2022-08-02 오후 6.08.16.png


cf. subscribe 대신에 bind를 사용하여 간단하게 줄인 코드, 들어온 값을 단순히 세팅하는 경우에는 bind가 더 쉽다!

스크린샷 2022-08-04 오전 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
    }
}