Observable을 생성해보자! (RxSiwft)

Tabber·2021년 9월 2일
2

Reactive Extension

목록 보기
2/4
post-thumbnail
post-custom-banner

Observable이 뭐였더라?

일단 사용하기 전에 Observable이 뭐였는지부터 떠올려보자.

Rx가 어떻게 동작했는지에 대해서 떠올려보면 자연스레 이어질 것이다.

여러개의 작업들을 메인 스레드에서 진행하는 것이 아닌, 메인 스레드는 하던거 진행하면서 백그라운드에서 진행하던 작업들 중 완료가 된 작업들에 대해 그 순간 메인 스레드에 불러오는 것이 비동기 프로그램이었다.

여기서 Observable의 역할은 데이터의 진행 흐름을 볼 수 있는 곳 이라고 생각하면 된다.

위 사진을 기준으로 대충 흐름을 생각해보자.

작업 시작

처음 작업을 시작할때 부터 확인해보자.
작업을 시작할 때는 그 작업에 대해 감시할 감시자를 붙여줘야 한다.
이 감시자가 나중에 작업이 끝났을 경우에 데이터를 가져와야 하기 때문이다.
따라서 감시자(Observer)를 일단 작업이 진행될 Observable구독(Subscribe) 해야한다.
이미지 상에서는 메인 스레드의 시작부분이다.
그렇게 작업은 Observable에서 진행이 된다.

작업 진행 중

작업은 메인 스레드가 아닌 백그라운드에서 진행이 된다. 따라서 메인 스레드에서 일어나는 모든것과 관계가 없는 상황이고, 반대로 메인 스레드 역시 백그라운드에서 진행되는 모든것에 관계가 없는 상황이다.

서로서로 별 관심이 없는 상황이니, 각자 할 거 할 수 있다라는 소리다.

따라서 메인 스레드에서 어떤 활동을 해도 멈추는 일 없이 계속 진행된다.

작업 완료

작업이 완료됐을 때에, 후에 알아볼 Emitter Interface 를 통해 작업이 완료됐다라는 것을 Observable을 감시하고 있던 Observer에게 알려준다.
그럼 완료신호를 받은 Observer는 완료된 작업의 데이터를 메인 스레드에 가져오게 된다.

이렇게 작업이 완료된다.

작업이 진행되고 있는 걸 어떻게 확인해?

작업이 잘 진행되고 있는지, 완료가 됐는지, 에러가 났는지 등을 어떻게 확인을 할 수 있을까?
일단 작업이 진행되는 곳은 Observable이고, 감시하는 것은 Observer인것을 알아냈다.
근데 Observer에게 작업의 진행을 알려주는것은 뭔지 아직모른다.

Emitter Interface

작업의 진행 상황을 Observer에게 알려주는 인터페이스를 Emitter Interface 라고 한다.

  • onNext()
    작업의 데이터가 한번 발생했음을 알리는 이벤트
  • onCompleted()
    작업의 데이터 발생이 모두 완료되었음을 알리는 이벤트
  • onError()
    작업 중 오류가 발생했음을 알리는 이벤트

작업 종료의 경우는 항상 onCompleted(), onError() 두 가지의 상황으로 종료된다.

Observable 생성해보기

무작위 사진 서버에서 사진을 가져와 화면에 보여주는 앱을 예시로 만들어보겠다.

Create() 메서드


create() 메서드를 통해 Emitter를 이용해 데이터의 발생, 완료, 에러 시 행하는 이벤트를 직접 설정할 수 있다.

Observable.create { seal in
        asyncLoadImage(from: imageUrl) { image in
        seal.onNext(image)
        seal.onCompleted()
}

위 코드는 이미지를 비동기 방식으로 하나씩 불러와서 완료시키는 Observable을 생성시킨 모습이다.
각 interface에 해당하는 이벤트들을 설정해놓았다.

subscribe() 메서드

위에서 생성한 Observable을 구독하는 용도로 사용한다.
구독을 한 후, 데이터가 하나씩 발생했을 때, 완료됐을 때, 에러가 발생했을 때의 동작을 정의할 수 있다.

rxswiftLoadImage(from: LARGER_IMAGE_URL) // 아까 위에서 생성한 Observable 함수
            .subscribe({ result in
                switch result {
                case let .next(image):
                    self.imageView.image = image

                case let .error(err):
                    print(err.localizedDescription)

                case .completed:
                    break
                }
            })

이렇게 .next , .error , .completed 세가지로 각각의 행해야 할 것 들을 정의하고 있다.

ObserveOn() 메서드


ObserveOn() 메서드는 작업을 수행할 방향을 정해준다.
예를 들자면, 서버에서 이미지를 다운받는 수행은 백그라운드에서 진행하지만, 다운을 다 받고 난 뒤에 이미지를 보여주는 화면은 메인 스레드이다.
따라서 진행 방향을 개발자가 설정한 방향으로 바꿔주는 역할을 한다.

rxswiftLoadImage(from: LARGER_IMAGE_URL)
            .observeOn(MainScheduler.instance)

disposed() 메서드

subscribe의 리턴값은 dispose이다.
dispose는 작업시에 생성이 되는데, 이 dispose로 작업을 중단시키거나 진행되지 않게끔 만들 수 있다.
disposedBag 이란 , 여러개의 dispose들을 넣어놓을 수 있는 가방 역할을 한다.
disposed() 메서드는 생성되는 dispose를 disposedBag에 넣어주는 역할을 한다.

rxswiftLoadImage(from: LARGER_IMAGE_URL)
            .observeOn(MainScheduler.instance)
            .subscribe({ result in
                switch result {
                case let .next(image):
                    self.imageView.image = image

                case let .error(err):
                    print(err.localizedDescription)

                case .completed:
                    break
                }
            })
            // 이렇게 담아줄 수도 있다.
            .disposed(by: disposeBag)

여기서 부터는 Observable에서 사용하는 Operator에 대해 설명한다.

from() 메서드

입력된 데이터를 하나씩 리턴 시키는 메서드이다.
특히 배열형식으로 들어간 데이터를 하나하나씩 꺼내 쓸 때 유용하다.

Observable.from(["RxSwift", "In", "4", "Hours"])
            .subscribe(onNext: { s in 
            	print(s) 
            }, 
            onError: { err in
                print(err.localizedDescription)
            }, onDisposed: {
                // dispose 되는 경우는
                // 1. completed 되는 경우
                // 2. error 인 경우
                print("disposed")
            })
            .disposed(by: disposeBag)


마지막의 disposed는 모든 작업이 끝났을 경우 disposed로 상태가 넘어간다.
따라서 위 코드에서 disposed 될 시 출력하라고 설정해놨기 때문에 뜨는 것이다.

just() 메서드

just() 메서드는 입력받은 모든 데이터를 한번에 내보내는 역할을 한다.

Observable.just("Hello")
            .map { str in "\(str) RxSwift" }
            .subscribe(onNext: { str in
                print(str)
            })
            .disposed(by: disposeBag)

위 코드에서는 데이터를 배열로 넣었고, 배열 그대로 출력이 된 상황이다.

map() 메서드

map 메서드는 그림 예시와 같이 특정 조건을 넣어, 새로운 Observable이 되게끔 해주는 메서드이다.

Observable.just("Hello")
            .map { str in "\(str) RxSwift" }
            .subscribe(onNext: { str in
                print(str)
            })
            .disposed(by: disposeBag)

map() 메서드를 이용하여 위에서 입력받은 "Hello"를 "Hello RxSwift"로 변환 후 내보내는 작업을 수행했다.

filter() 메서드

filter() 메서드는 내려오는 데이터들에게 조건을 걸어, 조건에 True가 된 데이터들만 내려가게끔 해주는 메서드이다.

Observable.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
            .filter { $0 % 2 == 0 } // 이 과정 자체가 스트림 Stream
            .subscribe(onNext: { n in
                print(n)
            })
            .disposed(by: disposeBag)

subscribeOn() 메서드

subscribeOn()메서드는 Observable을 subscibe 할때부터 작업의 방향을 지정하는 메서드이다.
아까 ObserveOn() 은 실행된 다음부터 작업의 방향을 지정하는 메서드였다면,
subscribeOn() 은 생성 초기부터 지정하는 것이다.
따라서 코드가 어디에 있든지간에, 이 메서드가 존재만 한다면 방향이 정해진다.

Observable.just("800x600")
            .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default)) // 얘는 subscribe할때 적용하는 것이라 어디서 선언을 하든지간에 사용가능
            .map { $0.replacingOccurrences(of: "x", with: "/") } // "800/600"
            .map { "https://picsum.photos/\($0)/?random" }
            .map { URL(string: $0) } // URL
            .filter { $0 != nil } // nil인지 아닌지 확인 false 면 안내려감
            .map { $0! } // 강제 언래핑
            .map { try Data(contentsOf: $0) }
            .map { UIImage(data: $0) }
            .observeOn(MainScheduler.instance) // 서버 작업은 백그라운드에서 하고 사진 적용은 다시 메인 스레드에서
            .do(onNext: {image in
                print(image?.size)
            })
            .subscribe(onNext: { image in
                self.imageView.image = image
            })
            .disposed(by: disposeBag)
    }

subscribeOn()을 통해 백그라운드에서 실행 할 수 있게 설정해준 것이다.
중간에 observeOn() 을통해서 서버 작업이 끝나고 난 뒤에, 사진만 적용 시킬때 메인 스레드로 잠깐 복귀 시킨다.

오늘은 Observable의 정의와, 사용법 그리고 자주 사용되는 Operator에 대해 알아보았다.

profile
iOS 정복중인 Tabber 입니다.
post-custom-banner

0개의 댓글