관리 메뉴

김종권의 iOS 앱 개발 알아가기

[RxSwift] 핵심 개념 본문

RxSwift

[RxSwift] 핵심 개념

jake-kim 2020. 6. 25. 16:33

1. Observable

1) 용어

- Observable : 관측(subscribe)되는 대상

- Observer (=subscriber) : Observable을 구독(subscribe)하는 주체

- emit : Observable이 이벤트를 방출하는 것

 

2) Observable에 이벤트 생성

- .just(value)

- .of(value1, value2, ...)

- .from([Element]) : 오직 array타입만 가능, 원소 모두 하나씩 emit

- .create() : 클로저 형식, 다양한 값 생성 가능

Observable<Int>.create { observer in
	observer.onNext(1)
    observer.dispose()
}

3) Subscribe method

Observer가 Observable에 연결하는 것(Observer입장에서 Observable의 emit을 발생하게끔 하는 것)

(딱 3가지)

- onNext : Observable의 최신 값을 emit

- onError : 이벤트 발생 종료 (더 이상 onNext, onCompleted부르지 않음)

- onCompleted : 정상 종료

 

4) Observable의 Traits

- Single :  .success, error (파일 쓰기, 파일 다운로드, 데이터 로딩)

- Maybe : .success, completed, error

- Completable : .completed, error (일의 끝, 오류 판단)

 

* success와 completed차이 : 파라미터 유무

 .success(value)

 .completed

 

* disposeBag : 이 변수가 dealloc되는 여기에 넣었던 Observable역시도 같이 해제

2. Operator

1) 용어

operators : emit된 이벤트들에 대해서 변형&처리&반응 할 수 있는 방법

Chaining Operators : 이전의 연산자와 함께 "체인"형식으로 접근이 간능(독립적이지 않고 모두 종속적으로 발생)

 

2) Transforming operator

- toArray : 개별 원소 -> 하나의 배열

- map : 배열 순회하면서 1대1매핑

- flatMap

3) Combine operator

- .concat([Observable])

let left = Observable.of(9, 8, 7)
let right = Observable.of(1, 2, 3)

let concatTest = Observable.concat([left, right])
concatTest
	.subscribe(
    	onNext: {print($0)}
    )
// 9 8 7 1 2 3

- .merge: 무조건 선착순

let source = Observable.of(left, right)
left observable = source.merge()
observable.subscribe(
	onNext:{print($0)}
)

// 9 8 1 7 2 3

- combineLatest : 여러개의 seqeunce들의 event들을 하나의 sequence에 동시에 저장

Observable.combineLatest(v1,v2)

- zip : combineLatest와 유사하지만 짝을 기다림

4) Trigger operator : 특정 시점에 emit하게끔 설정

- 옵저버1.withLatestFrom(옵저버2) : "옵저버1"이 onNext될 때, "옵저버2"의 최신 event와 같이 emit함

- 옵저버1.sample(옵저버2) : "옵저버2"가 onNext될 때, "옵저버1"이 emit함, 단 하나의 값만 발동

 

5) left.amb(right) : left와 right중 먼저 발생한 event에 속하는 subscriber만 수신

 

6) seqenece 내 요소들간의 결합

- reduce(초기값, accumulator클로저) : 하나의 값으로 압축

- scan(초기값, accumulator클로저) : reduce와 동일하지만 누적하며 emit

 

7) shared연산자

 자원을 공유해서 효율적으로 observable을 여럿이서 subscribe할 때 사용

 

3. Subject

1) 개념 : Observable과 Observer간의 연결(특별한 제약사항을 걸어둔 Observable)

2) 종류

- PublishSubject : empty시작 / 최신 요소만 emit

- BehaviorSubject : 초기값으로 시작 ,,, BehaviorSubject(value: "init val")

- ReplaySubject : 초기화된 버퍼 사이즈로 시작(정한 갯수만큼 다수 emit) ,,, ReplaySubject<String>.create(bufferSize: 2)

- Relay : 절대 끝나지 않음, 오직 .next이벤트만 가능 (PublishRelay, BehaviorRelay)

- combineLatest : 여러개의 이벤트를 저장 ,,, Oservable.combineLatest(left, right)

ex)

Images = BehaviorRelay<[UIImage]>(value: [])
let newImages = images.value
+ [UIImage(named: "IMG_1907.jpg")!] 
images.accept(newImages)  // accept()의미 : 이벤트 수락(등록)

3) subscribe 방법 : capture list이용

images.subscribe(
	onNext: { [weak imagePreview] photos in 
  	  guard let preview = imagePreview else { return }
	preview.image = photos.collage(size: preview.frame.size) }
    ) 
	.disposed(by: bag) 

 

4. Scheduler

Scheduler : 스케줄러는 쓰레드가 아님

  • observeOn(scheduler) : Observable이 Observer에게 알리는 스케줄러를 지정
  • SubscribeOn(scheduler) : Observable이 동작하는 스케줄러 지정

(쉽게 다시 정리)

RxSwift

 

핵심은 비동기 이벤트들을 stream에 담아서 코드로 처리하겠다는 목표

 

Scheduler

    • 동시에 실행 : ConcurrentDispatchQueueScheduler(qos: .default)
    • 메인실행 : MainSCheduler.instance
  • observeOn(MainScheduler.instance) : 선언한 밑 부분부터 적용
  • subscribeOn(ConcurrentDispatchQueueScheduler(sos: .default))  : subscribe하는 시점부터 실행 (아무곳에 선언해도 가능)

 

비동기를 잡아서 밑 순서대로 진행한다고 생각,

map은 해당 원소에 접근하는 것이라 생각

 

Observable.Just|(“800x600”)

 .map{$0.replacingOccurrences(9f: “x”, with: “/“)} // 800/600

 .subscribeOn(ConcurrentDispatchWueueScheduler(sos: .default)) 

 .observeOn(MainScheduler.instance)

 .subscribe(onNext: self.imageView.image = $0 }

 

 

Side effect

  • self(외부 블럭에 영향을 미칠 수 있는 곳) —> 딱 두 곳에만 가능하게끔 약속
    • Do
    • subscribe

(지나가면서 do를 실행)

 

 

—————————————————————subject——————————————————— 

subject란?

subscribe도 가능하고 이벤트도 추가해줄 수 있는 애

BehaviorSubject

PublishSubject

ReplaySubject : 버퍼 사이즈만큼 전달

 

 

————————— RxCocoa ———————————————————————————————— 

 

textField.rx —> 얘네들을 비동기로 받을 것이다. 선언. 단, 모든 값을 Optional로 처음에 받음

  • 다음과 같이 filter와 map에서 unwrap해줄 수 있지만, “orEmpty”로 옵셔널 바인딩 가능
  • 주의할 점 : map에서 다른 메소드를 호출 할 경우, 메소드 이름만 적음

 

Optional binding

주의 -> map해서 나오는 값은 checkEmailValid함수에서 반환된 값 (boolean)

 

textField.rx.text

.subscribe(onNext {print($0)}

.disposed(by: db)

textField.rx —> Reactive 

textField.rx.text 타입 —> ControlProperty … rx로 컴포넌트에 바인딩을 할 때 사용되어짐

textField.rx.text.asObservable() —> Observable<String?>

 

  • 개념 … 개념 더 찾아보기 —> 이 개념을 알아야 Observable이라는 개념을 이해할 수 있음
    • Reactive : Use Reactive proxy as customization point for constrained protocol extensions.
    • ControlProperty : Trait for Observable/ObservableType that represents property of UI element.
    • Binder : Observer that enforces interface binding rules:

 

.combineLatest(source1, source2, resultSelector: ) : 두 값을 받아서 결정하여 하나를  return

  • 두 텍스트필드의 유효에 대한 것을 검정 한 후 , 

 

.zip : 두 개를 주면, 둘 다 데이터가 만들어진 경우 전달 (한쪽의 데이터가 바뀌고 다른쪽은 안바뀐 경우 아직 전달 x)

combineLatest : 두 개의 스트림에서, 한쪽의 데이터만 들어와도 최신의 값들을 두 스트림에서 비교함

Merge : 두 개의 스트림을 받아서, 들어오는 대로 하나씩 받음 (한 스트림에서 두 개 비교하게 될 수 있음)

 

Bind : Subject를 통해 input과 output으로 나눔

idValid: BehaviorSubject<Bool> = BehaviorSubject(value: false)

 

— input

idValidOb = idField.rx.text.orEmpty

 

 idValidOb.map(checkPasswordValid).subscribe(onNext: {self.idValid.onNext($0)})

idValidOb.map(checkPasswordValid).bind(to: idValid) // subscroibe의 onNext와 같은 것

// bind의 리턴값은 Disposable이므로, .dispose해줄 것

 

— output

idValid.subscribe(onNext: {b in

self.idValidView.isHidden = b

})

.disposed(by: disposeBag)

 

drive()

: UI에서 처리할 때 mainThread로 돌려줘야 하는데, 아래와같이 쓰는게 귀찮아서 씀 

.observeOn(MainScheduler.instance) —> 쓰는게 귀찮아서 씀

 

.asDriver()

.drive(onNext: {idValidView.isHidden $0})

.bind(to: idValidView.rx.isHidden)

 

 

Memory leak 방지

안쓰면 ViewController가 pop되어도, self에 대한 Reference count가 남아있기 때문에 없어지지 않는 위험 방지 —> 캡쳐리스트로 count 증가시켜주지 않게함

—> UI에 관한 것은 complete되지 않기 때문에 캡쳐리스트를 사용해야함 (disposeBag이 사라질때 초기화됨)

—> complete되면 삭제됨

 (complete 명확한 애들 : .next 추가한 이벤트들 …)

Comments