ios dev kangwook.

2. RxSwift Observables 본문

RxSwift

2. RxSwift Observables

kangwook 2022. 12. 9. 00:15

What is an Observable?

  • Observable은 Rx의 핵심
  • Observable은 일정 기간 동안 이벤트를 생성하며 그 과정에서 방출을 한다.
  • 이벤트는 숫자 또는 사용자 지정 타입의 인스턴스와 같은 값을 포함하거나 탭과 같은 인식된 제스쳐일 수 있다.
  • 이를 개념화하는 가장 좋은 방법 중 하나는 타임 라인에 표시된 marble diagram

  • 왼쪽에서 오른쪽 화살표는 시간을 나타내고 번호가 새겨진 원은 시퀀스 요소를 나타낸다.
  • 1이 방출되고 시간이 지나면 2와 3이 차례로 방출
  • Observable의 생명 주기동안 어느 시점에나 있을 수 있다.

Lifecycle of an Observable

  • 끝에 수직으로 된 막대가 있다는 것은 끝을 나타낸다.
  • 이 Observable은 세 개의 탭 이벤트를 내보낸 후 종료
  • 이 이벤트는 종료되었기 때문에 완료된 이벤트라고 한다.
  • 이 때, Observable은 더 이상 이벤트를 방출하지 않는다.D

  • 이 다이어그램에서는 오류가 발생
  • 빨간색 X로 표시
  • 이 또한 Observable이 정상적으로 종료되었을 때와 마찬가지로 더 이상 다른 이벤트를 방출하지 않는다.

 

  • Observable은 아이템을 포함하는 다음 이벤트를 내보낸다.
  • 종료 이벤트가 발생할 때까지 이 작업을 계속할 수 있다.(onError or onCompleted)
  • Observable이 종료되면 더 이상 이벤트를 생성할 수 없다.
// Event<Element>
public enum Event<Element> {
    // Next element is produced.
    case next(Element)
 
    // Sequence terminated with an error.
    case error(Swift.Error)
 
    // Sequence completed successfully.
    case completed
}

Creating Observables

// Create Observable

// SupportCode.swift
public func example(of description: String, action: () ->Void) {
    print("\n--- Example of:", description, "---")
    action()
}
 
// RxSwiftPlayground
example(of: "just, of, from") {
    let one = 1
    let two = 2
    let three = 3
 
    let observable = Observable<Int>.just(one)
    let observable2 = Observable.of(one, two, three)        // [Int] 타입이 아닌 Observable<Int> 타입
    let observable3 = Observable.of([one, two, three])      // Observable<[Int]> 타입
    let observable4 = Observable.from([one, two, three])    // Observable<Int> 타입
}
  • 하나의 정수로 just 메소드를 사용하여 Int 타입의 Observable 시퀀스를 만든다.
  • "just"는 단 하나의 아이템만 포함하는 Observable 시퀀스를 만드든 것으로 Observable 타입 메소드
  • Rx에서는 메소드를 Operator라고 한다.
  • "of" Operator는 가변 매개 변수를 취하고 Swift는 이를 기반으로 Observable의 타입을 추론할 수 있다.
  • Observable 배열을 만들고 싶다면 배열을 of에 전달할 수 있다.
  • "from" Operator는 타입이 지정된 아이템에서 개별 아이템의 Observable을 만든다.
  • "from" Operator는 배열만 받는다.

Subscribing to Observable

  • RxSwift는 Observable과는 다른 옵저버에게 알림을 브로드 캐스트
// UIKeyboardDidChangeFrame

let observer = NotificationCenter.default.addObserver(forName: UIResponder.keyboardDidChangeFrameNotification,
                                                      object: nil,
                                                      queue: nil) { notification in
        // Handle receiving notification
}
  • RxSwift Observable을 구독하는 것은 상당히 유사
  • Observable을 observing 한다고 한다.
  • addObserver() 대신 subscribe() 를 사용
  • Observable은 실제로 시퀀스 정의
  • Observable을 구독하는 것은 Swift 표준 라이브러리의 Iterator에서 next()를 호출하는 것과 매우 유사
// Iterator next()

let sequence = 0..<3
 
var iterator = sequence.makeIterator()
 
while let n = iterator.next() {
    print(n)
}
//0
//1
//2

 

  • 하지만 Observable subscribe()은 이보다 더 간단
// Observable subscribe()

example(of: "subscribe") {
    let one = 1
    let two = 2
    let three = 3
 
    let observable = Observable.of(one, two, three)
 
    observable.subscribe { event in     // Observable 구독
        print(event)
    }
}

  • Observable은 각 아이템에 대해 .next 이벤트를 내보낸 후 .completed 이벤트를 내보내고 종료
// Observable subscribe() - 2

observable.subscribe { event in
    if let element = event.element {
        print(element)
    }
}

  • event에는 element 속성이 있다.
  • .next 이벤트에만 element가 있으므로 옵셔널 값
  • 따라서 옵셔널 바인딩을 사용하여 nil이 아닌 경우 element 래핑을 해제
  • 이것은 좋은 패턴이고 자주 사용되어 RxSwift에 적용되어 있다.
// Observable subscribe() - 3

observable.subscribe(onNext: { element in
    print(element)
})

 

  • 값 범위에서 Observable을 생성하는 것도 가능
  • 시작 정수 값 및 생성할 정수의 개수를 사용하는 범위 연산자를 사용하여 Observable을 생성
  • 방출된 각 원소에 대해 n번째 피보나치 수를 계산하고 출력
// Observable range

example(of: "range") {
    let observable = Observable<Int>.range(start: 1, count: 10)
 
    observable.subscribe(onNext: { i in
        let n = Double(i)
        let fibonacci = Int(((pow(1.61803, n) - pow(0.61803, n)) / 2.23606).rounded())
        print(fibonacci)
    })
}

Disposing and Terminating

  • Observable은 구독을 받기 전까지는 아무것도 하지 않는다.
  • .error 또는 .completed 이벤트를 생성하고 종료될 때까지 새로운 이벤트를 생성하는 Observable의 작업을 트리거 하는 구독
  • 구독을 취소하여 Observable을 수동으로 종료할 수 있다.
// dispose

example(of: "dispose") {
    let observable = Observable.of("A", "B", "C")           // Observable<String> 생성
 
    let subscription = observable.subscribe { event in      // Observable 구독
        print(event)                                        // 출력
    }
 
    subscription.dispose()                                  // 구독을 명시적으로 취소 -> Observable 이벤트 생성 중지
}

 

  • 각 구독을 개별적으로 관리할 수 있지만 RxSwift에는 DisposeBag 타입이 포함
  • DisposeBag이 할당 해제될 때 DisposeBag 안에 있는 각각에 대해 dispose()를 호출
// DisposeBag

example(of: "DisposeBag") {
    let disposeBag = DisposeBag()       // disposeBag 생성
 
    Observable.of("A", "B", "C")        // Observable 생성
        .subscribe {                    // Observable 구독
            print($0)
    }.disposed(by: disposeBag)          // subscribe의 반환 값을 disposeBag에 추가
}
  • DisposeBag에 구독을 추가하는 것을 잊었거나 구독을 마쳤을 때 dispose를 수동으로 호출하거나 다른 방식으로 Observable이 어느 시점에서 종료된다고 하면 메모리 누수가 발생할 수 있다.
  • Swift 컴파일러는 사용하지 않는 disposable에 대해 경고를 한다.

 

  • create 연산자를 사용하는 것은 Observable이 subscriber에게 방출할 모든 이벤트를 지정하는 또 다른 방법
  • create 연산자는 subscribe라는 단일 매개 변수를 사용
  • subscriber에게 보낼 모든 이벤트를 정의
  • subscribe 매개 변수는 AnyObserver를 취하고 Disposable을 리턴하는 이스케이프 클로저
  • AnyObserver는 Observable 시퀀스에 값을 추가하는 것을 용이하게 하는 일반 타입으로 subscriber에게 방출
// create

example(of: "create") {
    let disposeBag = DisposeBag()
 
    Observable<String>.create { observer in
        observer.onNext("1")                // Observer에 .next 이벤트를 추가
 
        observer.onCompleted()              // Observer에 .completed 이벤트를 추가
 
        observer.onNext("2")                // Observer에 다른 .next 이벤트를 추가
 
        return Disposable.create()          // disposable 반환
    }.subscribe(
        onNext: { print($0) },
        onError: { print($0) },
        onCompleted: { print("Completed") },
        onDisposed: { print("Disposed") }
    ).disposed(by: disposeBag)
}

두 번째 .next 이벤트는 .completed 이벤트를 생성하고 시퀀스에 추가되기 전에 종료되었기 때문에 출력되지 않는다.


Creating Observable Factories

  • subscriber를 기다리는 Observable을 만드는 대신 각 subscriber에게 새로운 Observable을 제공하는 Observable 팩토리를 만드는 것이 가능
// deferred

example(of: "deferred") {
    let disposeBag = DisposeBag()
 
    var flip = false                                        // create Bool flag
 
    let factory: Observable<Int> = Observable.deferred {  // deferred 연산자를 사용하여 Int 팩토리의 Observable을 생성
        flip.toggle()                                       // 팩토리를 subscribe 할 때마다 전환
 
        if flip {                                           // flip 이라는 flag에 따라 다른 Observable을 반환
            return Observable.of(1, 2, 3)
        } else {
            return Observable.of(4, 5, 6)
        }
    }
 
    for _ in 0...3 {
        factory.subscribe(onNext: {
            print($0, terminator: "")
        })
        .disposed(by: disposeBag)
        print()
    }
}


Using Traits

  • Trait은 일반 Observable보다 행동 집합이 더 좁은 Observable
  • Trait의 목적은 코드를 읽는 사람이나 API를 사용하는 사람에게 의도를 보다 명확하게 전달할 수 있는 방법을 제공하는 것
  • RxSwift에는 Single, Maybe 및 Completable의 세 가지 Trait이 있다.

 

  • Single은 .success(value) 또는 .error 이벤트를 내보낸다
  • .success(value)는 실제 .next 및 .completed 이벤트의 조합
  • 이는 데이터를 다운로드하거나 디스크에서 로드하는 것과 같이 성공하고 값을 산출하거나 실패하는 일회성 프로세스에 유용

 

  • Completable은 .completed 또는 .error 이벤트만 내보낸다.
  • 어떤 값도 방출하지 않는다.
  • 파일 쓰기와 같이 작업이 성공적으로 완료되거나 실패한 경우에만 Completable을 사용할 수 있다.

 

  • Maybe는 Single과 Completable의 mashup
  • .success(value), .completed 또는 .error를 방출할 수 있다.
  • 성공하거나 실패할 수 있는 작업을 구현하고 선택적으로 성공시 값을 반환해야 하는 경우 Maybe 사용
// Single

example(of: "Single") {
    let disposeBag = DisposeBag()
 
    enum FileReadError: Error {
        case fileNotFound, unreadable, encodingFailed
    }
 
    func loadTxt(from name: String) -> Single<String> {
        let disposable = Disposables.create()
 
        guard let path = Bundle.main.path(forResource: name, ofType: "txt") else {
            single(.failure(FileReadError.fileNotFound))
            return disposable
        }
 
        guard let data = FileManager.default.contents(atPath: path) else {
            single(.failure(FileReadError.unreadable))
            return disposable
        }
 
        guard let contents = String(data: data, encoding: .utf8) else {
            single(.failure(FileReadError.encodingFailed))
            return disposable
        }
 
        single(.success(contents))
        return disposable
    }
 
    loadTxt(from: "copyright")
        .subscribe {
            switch $0 {
            case .success(let string)
                print(string)
            case .failure(let error)
                print(error)
            }
    }.disposed(by: disposeBag)
}

'RxSwift' 카테고리의 다른 글

6. Transforming Operators  (0) 2022.12.22
5. Filtering Operators  (0) 2022.12.19
4. Observables & Subjects in Practice  (1) 2022.12.13
3. RxSwift Subjects  (0) 2022.12.12
1. RxSwift Introduction  (1) 2022.12.07
Comments