RXSwift 4시간에 끝내기를 공부 하며

cheshire0105·2024년 1월 15일

iOS

목록 보기
4/45
post-thumbnail

RXSwift 4시간에 끝내기 정리, 공부

RXSwift 4시간에 끝내기

1. RxOptional

  • RxSwift에서 옵셔널을 다루기 위한 확장 라이브러리.
  • 팟에 rxoptional을 추가하여 사용 가능.
  • 옵셔널 값에 대한 다양한 연산자나 메서드를 제공.

2. RxViewController

  • ViewController의 생명주기 이벤트를 RxSwift 스타일로 처리.
  • viewWillAppear, viewWillDisappear 등의 이벤트를 Observable로 변환.
  • 특정 생명주기 이벤트에서 한 번만 작업을 수행하려면 take 1 연산자 사용.

3. RxExtension

  • RxSwift의 기능을 확장하여 제공하는 라이브러리 모음.
  • KVO, UIKit 등과 관련된 확장 기능 포함.
  • rx.bounds, rx.center 등의 UIKit 확장 기능을 통해 UI 요소의 변화 감지.

4. RxCommunity

  • Rx와 관련된 다양한 라이브러리와 확장 기능 모음.
  • 55개 이상의 레퍼지토리 포함.
  • rxnavigate, rxdatasource, rxpromise 등 다양한 라이브러리 포함.

5. RxGesture

  • 제스처 이벤트를 RxSwift 스타일로 처리하는 라이브러리.
  • tabgesture, when, recognized 메서드를 통해 제스처 인식 및 처리.

6. 기타 내용

  • RxSwift는 비동기 처리를 위한 라이브러리로, 다양한 오퍼레이터와 확장 기능 제공.
  • RxSwift는 비동기 처리를 위한 라이브러리로, 프라미스나 볼트와 같은 다른 라이브러리들도 있지만 RxSwift의 다양한 오퍼레이터와 활성화된 커뮤니티가 큰 장점.

RxSwift and RxCocoa Hands-On

RxSwift and RxCocoa Hands-On

  • RXSwift의 핵심 개념
    1. Observable: 관찰 할 수 있는 ~
      • 비유: 물의 흐름이나 강물처럼 생각해보세요. 강물은 계속해서 물을 흘려보내며, 이 물의 흐름이 바로 Observable이 방출하는 이벤트라고 볼 수 있습니다.
      • 설명: Observable은 시간에 따라 여러 값을 방출할 수 있는 "데이터 스트림"입니다.
    2. Observer: 관찰자
      • 비유: 강물 가장자리에 앉아서 물 속에 무엇이 떠다니는지 관찰하는 사람처럼 생각해보세요. 이 사람이 바로 Observer입니다.
      • 설명: Observer는 Observable에서 방출되는 이벤트들을 "구독"하고, 해당 이벤트에 반응합니다.
    3. Operators: 조작자
      • 비유: 댐이나 수문처럼 생각해보세요. 댐은 강물의 흐름을 조절하거나, 특정 조건에 따라 물을 흘려보내거나 막을 수 있습니다. 이렇게 강물의 흐름을 변화시키는 것이 Operators의 역할입니다.
      • 설명: Operators는 Observable의 데이터 스트림을 변환, 필터링, 결합하는 등의 작업을 수행합니다.
    4. DisposeBag:
      • 비유: 사용한 커피 잔이나 빈 병을 담는 쓰레기 봉투처럼 생각해보세요. 사용이 끝난 후에는 쓰레기 봉투를 버리면, 그 안에 담긴 모든 쓰레기도 함께 사라집니다.
      • 설명: DisposeBag은 RxSwift에서 구독이 끝날 때 메모리 해제를 도와주는 역할을 합니다. Observable의 구독이 끝나면, 해당 구독과 관련된 모든 리소스가 DisposeBag 내부에서 자동으로 해제됩니다.
    5. 구독 (Subscription)
      • 비유: 물고기 낚시를 생각해보세요. 강물에 물고기가 있을 때, 낚시꾼이 낚싯대를 통해 물고기를 잡으려고 시도합니다. 낚시꾼이 낚싯대를 투척하여 물고기를 기다리는 것, 즉 낚시를 시작하는 행위를 '구독'이라고 볼 수 있습니다. 물고기가 물릴 때마다 낚시꾼이 반응하게 되는 것처럼, Observer는 Observable의 이벤트에 반응합니다.
      • 설명: '구독'은 Observer가 Observable의 이벤트에 관심을 가지고 시작하는 행위입니다. 이때, Observable은 실제로 이벤트를 방출하기 시작합니다. Observer는 이 이벤트들을 수신하고 반응합니다. 구독이 종료되면, Observer는 더 이상 이벤트를 수신받지 않습니다.

RXSwift 란 무엇인가

  • 반응형 프로그래밍에서는 모든 것이 데이터 스트림이 될 수 있습니다.
  • 장치, 위치, API에서 얻은 객체 목록, 시스템 시간, 사용자 정보 같은 정보들의 실시간으로 변경 되고 사라지는 데이터들을 추적 할 수 있고 반영 할 수 있습니다. 그것을 데이터 스트림 이라고 합니다.
  • observable이 변경 되었을 때, 그건 데이터를 가져오는 것이 아니고 반응형 프로그래밍에선 데이터를 push 해서 그 push 된 것을 받아온다고 생각 하면 됩니다.

그렇다면 어떤 이점이 있을까

  • 반응형 프로그래밍을 사용 해야 하는 이유는 무엇일까요
  • 반응형 프로그래밍의 비동기식 작업 방식은 사용자가 상호 작용 할 수 있는 부드러운 반응성을 제공합니다.
  • 또한 코드 관리가 쉬워지고, 개별 데이터 스트림을 관리 하는 것에서 많은 편의성을 줍니다.
  • 또한 쓰레드를 관리 하기 편해지며, 콜백 즉, complte handler를 사용 하지 않아도 됩니다.
  • 코드도 가독성이 높아지며, 다른 언어와 달라도 RX를 사용 한다면 같은 개념을 공유하기 때문에 다른 플랫폼에서도 대응 하기 쉬워집니다.

observables과 observers의 개념

  • rx의 핵심 개념입니다. 데이터 스트림과 동일한 개념이기도 하며, 데이터가 흘러가고 흘러오고 하는 개념이라고 생각 하면 됩니다.
  • observer는 subscriber와 비슷한 개념이라고 생각 하면 됩니다. observer은 observable의 데이터 스트림을 받고, subscriber 또한 observables을 구독 하며 데이터의 변화와 결과를 계속 추적 하기 때문입니다.
  • observable이 변경 알림을 보내거나 할 때 즉, 데이터를 방출 할 때, observer는 그걸 구독 하고 observable이 변경 되면 알람을 받게 됩니다. 그렇게 데이터를 가져오고 사용 하게 되는 것입니다.
  • 예를 들어 여러개의 observe가 observable을 수신 할 수 있습니다. 그렇기에 observable이 변경 되면 모든 항목에 이를 알리게 됩니다.
  • observable은 여러개의 값을 방출 할 수도 있고 하나의 값을 방출 할 수도 있습니다. 오류를 내보낼 수도, 완료 이벤트를 내보낼 수도 있습니다. 그런 결과들은 메서드를 통해서 방출 합니다.
  • 구체적인 메서드들은 onNext, onCompleted, onError를 이용 하여 배출 하게 됩니다.
  • observables이 항목을 내보낼 때 마다 onNext를 호출 하며 그 뒤에 onCompleted 메서드 ( 오류가 발생 하지 않았을 때 ) 와 에러가 발생 한다면 onError를 호출 하게 됩니다.
  • 또 다른 방법은 observer에 observable을 구독 해서 값을 받아오는 방법입니다.
  • 예를 들어 just로 observable을 생성 하고 subscribe로 observable을 구독 하며 onNext와 onCompleted 메서드를 사용 하는 방법입니다.
  • 옵져버와 옵져버블은 rx의 핵심 개념 입니다.

마블

  • RX에서는 마블이라는 개념을 통해 데이터 스트림을 시각적으로 보여줍니다.
  • 화살표는 시간을 나타내며, 원은 observable이 방출 하는 항목을 나타낸다. 수직선은 observable이 성공적으로 완료 되었다는걸 알려줍니다. x는 에러가 생겼다는 표현 입니다.
  • 반응형 프로그래밍 즉, 리액티브 프로그래밍은 여러 스트림을 결합하고 변경 하는 것에서 장점이 있습니다.
  • 반응형 프로그램 개발자들 사이에선 “체인을 끊지 마시오” 라는 말이 있을 정도 입니다.
  • 데이터를 변환 하거나 맵핑 해서 다양한 데이터 스트림을 나타낼 수 있습니다.

RXSwift Code

  • 코드로 실제 RXSwift를 적용해 봅시다.
  • just를 통해 시퀀스를 관찰해 봅시다. 그리고 정의해 봅시다.
  • observable은 데이터를 방출 하는 역할을 하기 때문에 observer가 observable을 구독 해야 합니다. 구독이라는건 observer가 observable에게 관심을 가지고 데이터를 수신한다와 같은 의미라고 생각 할 수 있습니다.

Observable을 생성 하는 연산자

just : 단일 요소의 observable을 생성 합니다.

import UIKit
import RxSwift
import RxCocoa
import RxRelay

let os1 = Observable.just("Hi there!")
// 데이터를 방출 하는 observable 입니다. 
let sub1 = os1.subscribe(onNext: {
// 그리고 그 observable을 구독 하는 구독 객체 입니다.
	event in
	print(event)
},onError: {
	print($0)
}, onCompleted: {
	print("completed")
}, onDisposed: {
	print("disposed")
})

let disoseBag = DisposeBag()
sub1.disposed(by: disposeBag)

// 결과 값들 입니다.
// Hi there!
// completed
// disposed

from : 배열이나, 리스트 같은 여러 시퀀스를 포함 하는 observable을 생성 합니다.

let os2 = Observable.from([1,2,3])
// 데이터를 방출 하는 observable 입니다.

let sub2 = os2.subscribe(onNext: {
	print($0)
}).disposed(by: disposeBag)
// observable을 구독 하는 객체 입니다.

// 순서대로 1, 2, 3 을 방출 합니다.

observable을 간소화 및 바로 생성 하는 방법

Observable.of("apple", "pears", "banana")
	.subscribe(onNext: {
		print($0)
})
.disposed(by: disposeBag)

// apple
// pears
// banana

Subjects and Traits

스크린샷 2023-10-12 오후 2.22.57.png

Subject ( 서브젝트 )

  • 서브젝트는 observable과 observer의 두가지 역할 모두 할 수 있는 객체 입니다.
  • 이벤트를 방출 할 수도 다른 이벤트들을 구독 하고 반응 할 수도 있습니다.
  • observable에 새로운 값을 추가 해야 할 경우 유용 합니다.
  • 런타임에서 Observable에 새로운 값을 추가하는 데 유용합니다.
  • 비유
    • 라디오 방송국입니다. 라디오 방송국은 음악을 방송하면서 (데이터를 방출하는 Observable 역할), 동시에 청취자들의 요청곡을 받아들일 수 있습니다 (데이터를 수신하는 Observer 역할).
  • 종류
    • PublishSubject: 방송 시작 후에 들어온 요청곡만 방송합니다.
      • 구독한 후 발생하는 이벤트만 새로운 관찰자에게 방출합니다.
    • BehaviorSubject: 가장 최근의 요청곡, 또는 특정 노래를 방송 시작할 때 무조건 틀어줍니다.
      • 새로운 관찰자에게 이벤트를 방출할 때, 가장 최근에 방출된 이벤트부터 시작합니다.
    • ReplaySubject: 방송 시작 전에 들어온 몇몇 요청곡들을 처음부터 다시 방송해줍니다.
      • 구독 시간에 관계없이 새로운 관찰자에게 모든 이벤트를 방출합니다.

Relays ( 릴레이 )

  • 서브젝트를 감싸지만, 오직 다음 이벤트만을 수락하고 방출합니다.
  • 오류나 완료 이벤트를 추가할 수 없기 때문에 UI 작업에 적합합니다.
  • PublishRelay
    • PublishSubject와 유사하게 동작하지만, 종료되거나 오류를 방출할 수 없습니다.
  • BehaviorRelay
    • BehaviorSubject와 유사하게 동작하지만, 종료되거나 오류를 방출할 수 없습니다.

Trait ( 트레잇 ) : 특징

  • 일반 Observable보다 행동이 제한적입니다.
  • 특별한 observable 입니다. 어떤 규칙이나 계약을 준수 하기 위해 설계 되었습니다.
  • 기존의 observable로도 수행 할 수 있지만 코드에 추가로 역할을 줄 때 사용 합니다.
  • 트레잇은 코드의 명확성을 제공합니다.
  • 주로 UI 작업에 적합합니다.
  • RXCocoa의 트레잇은 메인 스레드에서 동작하며 오류를 방출하지 않습니다.
  • 기타 특징 및 메서드
    • Side Effects: 트레잇을 사용할 때 사이드 이펙트를 공유하지 않아야 합니다.
    • 변환: asObservable 메서드를 사용하여 트레잇을 일반 Observable로 변환할 수 있습니다.
    • 다른 트레잇으로의 변환: asSingle(), asCompletable(), asMaybe() 등의 메서드를 사용하여 Observable을 해당 트레잇 형태로 변환할 수 있습니다.
  • 비유
    • 주문 방식입니다. 어떤 주문은 음식만, 어떤 주문은 서비스만, 또 어떤 주문은 음식 혹은 서비스 둘 중 하나만 요구합니다.
  • 종류
    • Single: 하나의 음식만 주문합니다. (한 가지 결과만 기대합니다.)
      • 예를 들어 GET과 POST의 결과를 주고 받을 수 있습니다.
      • Single은 이름에서 알 수 있듯이 단 하나의 값을 방출하거나 오류를 방출합니다.
      • 용도: 단일 요소 또는 오류 이벤트만을 방출하기 원할 때 사용합니다.
        • API 호출을 통해 데이터를 가져오는 경우, 데이터를 가져오거나 실패 메시지를 반환하는 경우에 사용됩니다.
      • 특징: 오직 하나의 요소 또는 오류를 방출합니다. 여러 개의 요소를 방출하는 일반적인 Observable과는 다르게, Single은 정확히 하나의 결과만을 방출하거나 오류를 방출합니다.
    • Completable: 서비스 완료 여부만 알려줍니다. (결과의 세부 내용은 관심 없습니다.)
      • 결과 값 없이 작업이 성공적으로 완료되었는지, 아니면 오류가 발생했는지만 알려줍니다.
      • 용도: 값의 방출 없이 작업의 완료 또는 실패만을 알리고 싶을 때 사용합니다.
        • 파일을 저장하는 작업이 성공적으로 완료되었는지 확인하는 경우에 사용됩니다.
      • 특징: 어떠한 값을 방출하지 않습니다. 작업이 성공적으로 완료되었음을 알리는 완료 이벤트 또는 오류 이벤트만을 방출합니다.
    • Maybe: 음식을 주문하거나, 아무것도 주문하지 않거나, 서비스 완료 여부만 알려줄 수 있습니다. (여러 가지 상황에 대응합니다.)
      • 용도: SingleCompletable의 중간 형태로, 하나의 요소를 방출하거나 방출 없이 완료/오류 이벤트만을 방출하고 싶을 때 사용합니다.
        • 캐시에서 데이터를 가져올 때, 데이터가 있으면 반환하고, 없으면 아무것도 반환하지 않거나 오류를 반환하는 경우에 사용됩니다.
      • 특징: 최대 하나의 요소를 방출하거나, 값을 방출하지 않고 완료 또는 오류 이벤트만을 방출합니다.
  • RXCocoa
    • Trait은 RXCocoa와 연관 되어서도 유용성이 많습니다.
    • Driver
      • 주로 UI를 업데이트하는 데 사용됩니다.
      • 항상 메인 스레드에서 동작하므로 UI 업데이트에 안전합니다.
      • 오류를 방출하지 않습니다.
    • Signal
      • Driver와 유사하지만, 새로운 구독자에게 마지막 이벤트를 재생하지 않습니다.
      • UI 이벤트에 대한 반응으로 주로 사용됩니다.
    • ControlProperty
      • UI 요소의 속성을 반응적으로 표현합니다 (예: 텍스트 필드의 텍스트).
      • UI 컨트롤이 deallocated될 때 시퀀스가 완료됩니다.
      • 항상 메인 스레드에서 동작합니다.
    • ControlEvent
      • UI 요소의 이벤트를 반응적으로 표현합니다 (예: 버튼 탭).
      • 초기 값을 방출하지 않습니다.
      • UI 컨트롤이 deallocated될 때 시퀀스가 완료됩니다.

Subjects and Traits의 코드 사용 예시

Trait - Single

let os1 = observable.just("e1")
// 하나의 observable을 생성 합니다. 
// just는 단일이기 때문에 하나의 스트림만 생성 합니다.

let disB = DisposeBag()
// 메모리 해제를 위해서 disB 변수를 생성 합니다.

ob1.asSingle()
	.subscribe(onSuccess: {
		print($0)
}.onError: {
		print($0)
})
.disposed(by: disB)

// e1
let os1 = observable.just("e1", "e2")
  • 만약 이렇게 두개를 생성 한다면 에러가 나게 됩니다. single로 선언 하기 때문입니다.

Trait - Completable

Completable.empty()
	// RX의 특별한 observable 중 하나 입니다. 
	// 값을 방출 하지 않고 성공적으로 완료 되었는지만 알려줍니다.
	.subscribe(onCompleted: {
			print("completed")
}, onError: {
			print($0)
})

// completed

Trait - asMaybe

let os2 = Observable.of("e2")
	// 단일 값을 방출 하는 Observable을 생성 합니다.
os2.asMaybe()
	// asMaybe() 메서드는 Observable을 Maybe라는 특별한 타입의 Observable로 변환합니다.
	.subscribe(onSuccess: {
		print($0)
	}, onError: {
		print($0)
	}, onCompleted: {
		print("complete")
	// Maybe가 성공적으로 완료됐을 때 실행됩니다. 여기서는 "complete"라는 문자열을 출력합니다.
	})

// e2
let os2 = Observable<Any>.empty()
// 아무런 값을 방출하지 않고 즉시 완료 되는 Observable입니다.
// 아무런 값이 없기 때문에 어느 요소도 출력 되지 않고, 에러도 나지 않기 때문에 complete가 출력 됩니다. 

// complete

RXCocoa - Driver

let os3 = Observable.of("e3", "e4")
// "e3"와 "e4" 두 개의 값을 순서대로 방출하는 Observable을 생성합니다.

os3.asDriver(onErrorJustReturn: "default")
// asDriver 메서드는 Observable을 Driver라는 특별한 타입의 Observable로 변환합니다.
// Driver는 항상 메인 스레드에서 동작하며 오류를 방출하지 않습니다. 오류가 발생하면 제공된 기본 값("default")을 대신 방출합니다.
	.drive(onNext: {
		print($0)
	}, onCompleted: {
		print("drive complete")
	}, onDisposed: {
		print("driver disposed")
	})
	.disposed(by: dispB)

// e3
// e4
// drive complete
// driver disposed

연산자 - Operator

  • RX에서 연산자는 크게 세가지로 나눌 수 있습니다.
  • 필터링, 변환, 결합
  • 연산자를 이해 하는 가장 쉬운 방법은 RX 마블을 보면서 직관적으로 이해 하는 것 입니다.

RxMarbles: Interactive diagrams of Rx Observables

필터 연산자(Filter Operators)

  • filter: 특정 조건을 만족하는 항목만 통과시킵니다.
    Observable.of(8, 12, 9, 11)
    .filter { $0 > 10 }
    .subscribe(onNext: { print($0) })
    // 출력: 12, 11
  • skip: 앞에서부터 특정 수의 항목을 건너뜁니다.
    Observable.of(1, 2, 3, 4)
    .skip(2)
    .subscribe(onNext: { print($0) })
    // 출력: 3, 4
  • takeWhile: 특정 조건이 만족될 때까지만 항목을 취합니다.
    Observable.of(8, 9, 12, 11)
    .takeWhile { $0 < 12 }
    .subscribe(onNext: { print($0) })
    // 출력: 8, 9

변환 연산자(Transforming Operators)

  • map: 각 항목을 변환합니다.
    Observable.of(1, 2, 3)
    .map { $0 * 2 }
    .subscribe(onNext: { print($0) })
    // 출력: 2, 4, 6
  • flatMap: 항목을 Observable로 변환하고 결과를 병합합니다.
    Observable.of(1, 2, 3)
    .flatMap { Observable.of($0, $0*10) }
    .subscribe(onNext: { print($0) })
    // 출력: 1, 10, 2, 20, 3, 30

결합 연산자(Combining Operators)

  • startWith: 시퀀스가 시작될 때 특정 항목을 먼저 내보냅니다.
    Observable.of(2, 3, 4)
    .startWith(1)
    .subscribe(onNext: { print($0) })
    // 출력: 1, 2, 3, 4
  • concat: 두 Observable을 연결합니다.
    let first = Observable.of(1, 2)
    let second = Observable.of(3, 4)
    first.concat(second)
    .subscribe(onNext: { print($0) })
    // 출력: 1, 2, 3, 4
  • merge: 두 Observable의 항목을 병합합니다.
    let first = Observable.of(1, 3)
    let second = Observable.of(2, 4)
    Observable.merge(first, second)
    .subscribe(onNext: { print($0) })
    // 출력: 1, 3, 2, 4 (순서는 예시일 뿐, 실제로는 어떤 Observable이 먼저 값을 방출하는지에 따라 다를 수 있습니다.)
  • combineLatest: 두 Observable의 최신 항목을 조합합니다.
    let first = Observable.of(1, 2)
    let second = Observable.of(3, 4)
    Observable
    .combineLatest(first, second) { $0 + $1 }
    .subscribe(onNext: { print($0) })
    // 출력: 4, 6

연산자의 사용 예시

filterskip

let disB = DisposeBag()
// 메모리 관리를 위해서 DisposeBag을 우선 생성 합니다.
// 구독이 끝나면 자동으로 메모리를 해제 할 수 있습니다.

let os1 = Observable.of("a1", "b1", "a2")
// observable을 통해서 데이터를 생성 합니다.

os1.filter({
// 연산자를 이용해서 observable의 데이터 속에서 사용할 데이터를 골라냅니다.
	elem in
	elem.starts(with: "a")
// a로 시작 하는 요소만 선택 합니다.
})
.subscribe(onNext: {
	print($0)
// subscribe를 통해 os1을 구독 하고 그 결과를 print 합니다.
})
.disposed(by: dispB)

os1.skip(2)
// observable에서 처음 두개의 요소는 건너 뜁니다.
.subscribe(onNext: {
	print($0)
// 그러면 "a2"만 남게 됩니다. 그리고 그 결과를 출력 합니다.
})
.disposed(by: dispB)

// a1
// a2
// a2 
os1.elementAt(1)
  • skipelementAt(1)로 변경 하면 1번의 요소를 선택 하게 되는 것이기 때문에 “b1”이 출력 됩니다. 컴퓨터는 0,1,2의 순서로 세기 때문입니다.

flatMapflatMapLatest

os1.flatMap({
// os1 Observable에서 각 요소(elem)에 대해 flatMap이 호출됩니다.
	elem in
	return Observable.of("c", "d")
	// flatMap 내부에서 새로운 Observable을 생성합니다: Observable.of("c", "d").
	.map({
		value in
		"" + elem + value
		// 새로운 Observable의 각 요소(value)는 map 연산자를 사용하여 변환됩니다. 
		// 변환은 elem과 value를 결합하는 것으로 이루어집니다.
		// os1의 첫 번째 요소 "a1"에 대해 "a1c"와 "a1d"가 방출됩니다.
		// os1의 두 번째 요소 "b1"에 대해 "b1c"와 "b1d"가 방출됩니다.
		// os1의 세 번째 요소 "a2"에 대해 "a2c"와 "a2d"가 방출됩니다.
	})
})
.disposed(by: dispB)

flatMap:

  • flatMap은 원본 Observable에서 방출되는 모든 요소에 대해 새로운 Observable을 생성하고, 이 새로운 Observable에서 방출되는 모든 요소를 결과 Observable로 병합합니다.
  • 즉, 원본 Observable에서 여러 요소가 방출되면, 그에 해당하는 여러 Observable이 생성되고, 이러한 모든 Observable의 요소들이 결과 Observable로 병합됩니다.
os1.flatMapLatest({

flatMapLatest:

  • flatMapLatest는 원본 Observable에서 방출되는 각 요소에 대해 새로운 Observable을 생성하는 것은 flatMap과 동일합니다.
  • 그러나, 새로운 요소가 원본 Observable에서 방출될 때마다, 이전에 생성된 Observable의 방출을 무시하고 최신 Observable의 방출만을 결과 Observable로 전달합니다.
  • 즉, "최신" Observable만이 결과 Observable로 요소를 방출하게 됩니다.
  • 사용자 입력에 따라 API 요청을 실행하는 경우, 최신의 입력만을 기반으로 요청을 처리하고 이전 요청의 결과를 무시하려면 flatMapLatest를 사용할 수 있습니다.

zip

Observable.zip(os1, os2)
	.subscribe(onNext: {
		print($0)
	})
	.disposed(by: dispB)

// ("a1", 1)
// ("b1", 2)
// ("a2", 3) 

zip:

  • zip은 두 개 이상의 Observable을 "짝지어" 방출합니다.
  • 각 Observable에서 요소가 방출될 때마다, zip은 그 요소들을 짝지어서 튜플로 묶어 결과 Observable로 방출합니다.
  • 만약 하나의 Observable이 다른 Observable보다 더 많은 요소를 방출하면, 남은 요소는 방출되지 않고 기다립니다. 다른 Observable에서 요소가 방출될 때까지 말이죠.
  • 예를 들어, ObservableA[1, 2, 3]을 방출하고 ObservableB[A, B]를 방출한다면, 결과는 [(1, A), (2, B)]가 됩니다. 3은 짝지어지지 않았기 때문에 결과에 포함되지 않습니다.
  • zip은 여러 Observable의 방출을 짝지어서 튜플로 묶어 결과 Observable로 방출합니다.

merge

Observable.merge(os1, Observable.of("c","d")
  • merge는 두 개 이상의 Observable의 방출을 하나의 Observable로 합칩니다.
  • 원본 Observable 중 하나에서 요소가 방출되면, 해당 요소는 즉시 결과 Observable로 방출됩니다.
  • 순서는 Observable의 방출 순서에 따라 결정됩니다.
  • 예를 들어, ObservableA1 -> 2 -> 3 순서로 방출하고, ObservableBA -> B 순서로 방출한다면, 결과 Observable의 가능한 방출 순서는 1 -> A -> 2 -> B -> 3 또는 1 -> 2 -> A -> 3 -> B 등 다양하게 될 수 있습니다.
  • merge는 여러 Observable의 방출을 그대로 합쳐 결과 Observable로 방출합니다.

scan

let os2 = Observable.of(1,2,3,4)

os2.scan(Int()) {
// scan 연산자는 Observable의 각 요소에 대해 누적 연산을 수행하는 연산자입니다.
// scan 연산자는 초기값(Int(), 즉 0)과 함께 제공된 클로저를 사용하여 Observable에서 방출되는 각 요소에 대한 누적된 결과를 생성합니다. 
// 클로저의 첫 번째 인자는 누적값(sum)이고, 두 번째 인자는 Observable에서 방출된 현재의 요소(elem)입니다.
	sum, elem in 
// 첫 번째 인자는 sum 이고 두번째 인자는 elem 입니다.
// 그리고 sum과 elem을 계속 누적 하면서 결과를 리턴 합니다.
	return sum + elem
}
.subscribe(onNext: {
	print($0)
// 첫 번째 요소 1이 scan에 전달되면, 0(초기값) + 1 = 1이 출력됩니다.
// 두 번째 요소 2가 scan에 전달되면, 1(이전의 누적값) + 2 = 3이 출력됩니다.
// 세 번째 요소 3이 scan에 전달되면, 3(이전의 누적값) + 3 = 6이 출력됩니다.
// 네 번째 요소 4가 scan에 전달되면, 6(이전의 누적값) + 4 = 10이 출력됩니다.
})
.disposed(by: dispB)

Handling errors and debugging

오류 처리

  • onError: 구독 시 오류 처리 방법.
  • do(onError:): 오류를 부수 효과로 개별적으로 처리.
  • catch: 오류 복구 지정.
  • retry: 오류 발생 시 작업을 다시 시도
let observable = Observable.of("a", "b", "c")
// Observable.of는 주어진 인수들로부터 Observable을 생성합니다. 
// 따라서 이 Observable은 순차적으로 "a", "b", "c" 값을 방출합니다.
		.map { value -> String in
		// map 연산자는 Observable이 방출하는 각 요소에 대해 적용되는 함수를 통해 이 요소들을 변환합니다.
		// 여기서는 "b"라는 값이 나오면 SomeError()라는 오류를 발생시킵니다. 그렇지 않으면 그대로 값을 반환합니다.
    if value == "b" {
        throw SomeError()
    }
    return value
}

observable
    .catchError { error in
        return Observable.just("d")
		// catchError 연산자는 Observable에서 오류가 발생할 경우 이를 처리하도록 도와줍니다.
		// 이 경우, 오류가 발생하면 새로운 Observable을 반환하여 "d"라는 값을 방출합니다.
    }
    .subscribe(onNext: { print($0) })
		// .subscribe를 사용하여 Observable을 구독하고, Observable이 방출하는 각 값을 출력합니다.

// 결과적으로, 이 코드는 "a", "d", "c"를 순서대로 출력합니다. 
// "b" 대신 "d"가 출력되는 이유는 "b"에서 오류가 발생하고 catchError에 의해 "d"로 대체되었기 때문입니다.

디버깅

  • debug: 모든 이벤트를 출력하여 디버깅에 도움을 줍니다.
let observable = Observable.of("a", "b", "c")
observable.debug("Observable values").subscribe()

메모리 누수 디버깅

  • 디버그 모드 활성화 및 Resources.total 사용하여 리소스 추적.
  • RxSwift에서는 메모리 누수를 감지하고 디버깅하는 데 도움이 되는 몇 가지 도구를 제공합니다. 메모리 누수는 앱의 성능 문제를 일으킬 수 있기 때문에 중요한 주제입니다. RxSwift에서는 특히 DisposeBag을 사용하여 Observable 구독을 관리하므로, 올바른 메모리 관리가 중요합니다.
  • 디버그 모드 활성화:
    • RxSwift는 디버그 모드에서 리소스 추적을 활성화할 수 있습니다. 이렇게 하려면 빌드 설정에서 TRACE_RESOURCES 플래그를 활성화해야 합니다. 이렇게 하면 RxSwift는 모든 할당된 리소스를 추적할 수 있습니다.
  • Resources.total:
    • Resources.total는 디버그 모드에서 현재 할당된 리소스의 총 수를 나타내는 전역 변수입니다. 메모리 누수가 의심될 때 이 값을 모니터링하여 증가하는지 확인할 수 있습니다.
    • 예를 들어, 앱의 특정 부분을 탐색한 후 Resources.total 값이 증가하면 해당 부분에서 메모리 누수가 발생할 수 있습니다.
  • 사용 방법:
    1. 앱을 실행하기 전에 TRACE_RESOURCES를 활성화합니다.
    2. 앱에서 여러 작업을 수행합니다.
    3. 작업 후 Resources.total 값을 확인하여 이전에 비해 증가했는지 확인합니다.
    4. 값이 계속 증가하면 해당 영역에서 메모리 누수가 발생할 가능성이 있습니다. 이 경우, DisposeBag 및 다른 Rx 리소스가 올바르게 해제되었는지 확인해야 합니다.

RxSwift와 RxCocoa에서 사용 가능한 스케줄러 탐구

큐(Queue)와 스케줄러(Scheduler)

  • 큐(Queue)
    • 작업이나 태스크를 순차적 또는 동시에 실행하기 위해 대기시키는 구조입니다.
    • GCD에서는 직렬 큐와 동시 큐가 있습니다.
    • 사람들이 줄을 서서 기다리는 것처럼, 컴퓨터 작업들이 순서대로 대기하는 줄입니다.
  • 스케줄러(Scheduler)
    • RxSwift에서는 작업을 어디와 어떻게 실행할지 결정하는 역할을 합니다.
    • 스케줄러는 RxSwift 작업의 실행 방식과 위치를 추상화하는 데 사용됩니다.
    • RxSwift의 스케줄러는 내부적으로 GCD의 큐나 다른 메커니즘을 사용할 수 있습니다.
    • 어느 줄에서 어떤 작업을 할지를 결정하는 지휘자 같은 역할입니다.
  • 큐와 스케줄러
    • RxSwift의 스케줄러는 GCD의 큐 개념을 추상화하여 더 간결하고 직관적으로 멀티스레딩 작업을 수행할 수 있게 도와줍니다.
    • GCD만 사용할 때는 작업을 어떤 큐에 배치할지, 어떻게 동기화할지 등의 세부 사항을 관리해야 합니다. 그러나 RxSwift의 스케줄러를 사용하면 이러한 세부 사항을 더 간단하게 처리할 수 있습니다.
    • 스케줄러를 사용하면 어느 스레드에서 코드를 실행할지 쉽게 지정할 수 있으며, subscribeOnobserveOn 연산자를 통해 연산의 흐름을 명확하게 제어할 수 있습니다.
  • 요약
    • 예를 들어, 테마파크에서 여러 놀이기구를 타려고 줄을 서 있을 때, 각 놀이기구마다 줄이 있습니다. 이 각각의 줄을 "큐"라고 생각하면 됩니다. 그리고 어느 놀이기구 줄에 서야 할지, 어느 놀이기구를 먼저 탈 것인지를 결정해주는 안내원이 있다면, 그 안내원이 "스케줄러"와 유사한 역할을 합니다.

기본 제공 스케줄러

  • RxSwift에서는 기본적으로 동일한 스레드에서 모든 작업이 수행됩니다. 스레드를 수동으로 변경하지 않으면 현재 스레드에서 모든 것이 처리됩니다. 스레드를 변경하기 위해서는 스케줄러를 사용합니다.
  • 직렬 스케줄러 (Serial Queue)
    • 작업이 순차적으로 실행됩니다.
    • 한 번에 하나의 작업만 실행되며, 이전 작업이 완료되면 다음 작업이 시작됩니다.
    • GCD에서의 동기 작업과 유사한 방식으로 동작합니다.
    • CurrentThreadScheduler
      • 현재 스레드에서 스케줄링합니다. 기본 스케줄러로도 사용됩니다.
      • 요리를 시작한 주방에서 그대로 모든 요리 과정을 마무리하는 것입니다. 만약 당신이 주방에서 스테이크를 구워서 시작했다면, 그 주방에서 스테이크를 완성할 것입니다.
    • MainScheduler
      • 메인 스레드에서 스케줄링합니다. 모든 UI 작업이 이곳에서 이루어집니다.
      • 이것은 주방의 메인 요리대를 상상해보세요. 여기서는 주로 완성된 음식을 장식하거나, 손님이 볼 수 있는 오픈 키친에서 요리하는 곳입니다. 모든 중요한 마무리 작업이 이곳에서 이루어집니다.
    • SerialDispatchQueueScheduler
      • 특정 큐에서 스케줄링합니다.
      • 여러 단계로 요리가 진행되는 레시피를 따라 요리할 때, 한 단계가 끝날 때까지 다음 단계로 넘어가지 않는 것입니다. 예를 들면, 스파게티를 만들 때 먼저 소스를 만들고 나서 스파게티 면을 삶는 순서로 진행합니다.
  • 동시 스케줄러 (Concurrent Queue)
    • 여러 작업이 동시에 실행될 수 있습니다.
    • 작업 간의 순서는 보장되지 않습니다.
    • GCD에서의 비동기 작업과 유사한 방식으로 동작합니다.
    • ConcurrentDispatchQueueSchedule
      • 특정 큐에서 스케줄링합니다.
      • 여러 요리를 동시에 시작하는 것입니다. 예를 들어, 스테이크를 구우면서 동시에 샐러드와 감자튀김을 준비하는 것입니다.
    • OperationQueueScheduler
      • 특정 큐에서 스케줄링합니다.
      • 요리를 순서대로 준비하는 큐를 상상해보세요. 스테이크를 먼저 구우고, 그 다음에 샐러드를 만들고, 마지막으로 감자튀김을 준비하는 순서대로 진행되는 것입니다.

실제 앱 개발 상황에서의 고려

  1. (Queue)
    • Background Queue (백그라운드 큐):
      • 큰 사진 파일을 서버에 업로드하는 작업은 시간이 오래 걸릴 수 있으므로, 이 작업을 백그라운드에서 처리합니다.
    • Main Queue (메인 큐):
      • 사용자에게 UI 업데이트 (예: '업로드 완료!' 메시지 표시)를 해주는 작업은 메인 큐에서 처리합니다. 왜냐하면 UI 관련 작업은 항상 메인 스레드에서 이루어져야 하기 때문입니다.
  2. 스케줄러(Scheduler)
    • 앱이 실행될 때, RxSwift의 스케줄러가 어느 큐에서 어떤 작업을 해야 할지 지휘합니다.
let uploadButton = UIButton() // 사진 업로드 버튼

uploadButton.rx.tap
    .flatMapLatest {
        uploadPhotoToServer() // 큰 사진 파일을 서버에 업로드하는 함수
            .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .background)) // 백그라운드 큐에서 업로드 작업을 수행
            .observeOn(MainScheduler.instance) // 메인 큐에서 결과를 관찰
    }
    .subscribe(onNext: { _ in
        // '업로드 완료!' 메시지 표시
        showMessage("업로드 완료!")
    })
    .disposed(by: disposeBag)
  • 이 코드에서는 subscribeOn을 사용하여 사진 업로드 작업을 백그라운드 큐에서 수행하도록 지정하였고, observeOn을 사용하여 그 작업의 결과 (업로드 완료)를 메인 큐에서 관찰하도록 하였습니다.

subscribeOnobserveOn 연산자

  • subscribeOn: Observable이 작동할 스케줄러를 지정합니다.
  • observeOn: 관찰자가 이 Observable을 관찰할 스케줄러를 지정합니다.

사용자 정의 스케줄러

  • 기본 제공 스케줄러가 요구 사항을 충족시키지 못하는 경우 사용자 정의 스케줄러를 작성할 수 있습니다.
  1. ImmediateScheduler 프로토콜을 구현해야 합니다.
  2. 스케줄러 유형을 선택하고 Scheduler 또는 Periodic Scheduler 프로토콜 중 하나를 구현해야 합니다.

코드 예시

let observable = Observable.of(1, 2, 3)
// 1, 2, 3의 세 개의 요소를 발행하는 Observable을 생성합니다.

observable
    .subscribeOn(SerialDispatchQueueScheduler)
		// 이 메서드는 Observable이 어떤 스케줄러에서 작동할지 지정합니다. 
		// 여기서는 SerialDispatchQueueScheduler를 사용하여 Observable의 작업이 직렬 디스패치 큐 스케줄러에서 수행되도록 지정합니다. 
		// 이것은 주로 백그라운드 스레드에서 실행됩니다.
    .observeOn(MainScheduler)
		// 이 메서드는 관찰자가 어떤 스케줄러에서 Observable을 관찰할지 지정합니다. 
		// 여기서는 MainScheduler를 사용하여 관찰자가 메인 스레드에서 결과를 관찰하도록 지정합니다. 
		// 일반적으로 UI 업데이트와 같은 작업은 메인 스레드에서 수행되어야 하므로 이 메서드는 그러한 경우에 유용합니다.
    .subscribe(onNext: { value in
        print(value)
    })

// 1
// 2
// 3

0개의 댓글