Danny의 iOS 컨닝페이퍼
article thumbnail
Published 2023. 3. 6. 20:38
[iOS/RxSwift] Observable 연산자 RxSwift

Observable의 연산자

Observable 관련 연산자의 종류는 이와 같습니다.

 

생성 관련 연산자

just, of, from, range, create

 

제거 관련 연산자

empty, never, disposable

 

지연 관련 연산자

deferred

 

 

생성 관련 연산자

생성 관련 연산자 just, of, from, range, create에 대하여 알아봅시다.

 

들어가기 앞서 RxSwift 사용방법을 모르시는 분은

[iOS/RxSwift] Observable, Subscribe 개념 및 사용 방법을 참고해 주세요

 

 

just

오직 하나의 Observable sequence만을 생성 및 방출

let justObservable = Observable.just("Hello World")

justObservable.subscribe(onNext: { element in
    print("Current value: \(element)")
})

// Current value: Hello World

이렇게 observer를 따로 만들어서 사용도 가능합니다.

let justObservable = Observable.just("Hello World")

let observer: (String) -> Void = { value in
    print("Current value: \(value)")
}

justObservable.subscribe(onNext: observer)

// Current value: Hello World

 

 

of

여러 개의 element를 받아 순차적으로 Observable을 생성합니다.

of 내부의 element의 타입은 동일해야 합니다.

let ofObservable: Observable<Int> = Observable.of(1, 2, 3)

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

// 1
// 2
// 3

 

만약에 of의 값이 배열이라면?

각 배열을 한 개의 element로 취급 후 순차적으로 Observable을 생성합니다.

let ofObservableArr: Observable<[Int]> = Observable.of([1, 2, 3], [4, 5])

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

// [1, 2, 3]
// [4, 5]

 

 

from

배열의 element 값을 하나하나씩 순차적으로 Observable를 생성합니다.

(방출 이벤트 : array -> element)

let fromObservable = Observable.from([1, 2, 3])

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

// 1
// 2
// 3

 

참고

from으로도 범위를 표현할 수 있습니다.

다만 의미를 명확하게 전달하기 위해선 아래의 range를 사용하자.

let fromRangeObservable = Observable.from(1...5)

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

// 1
// 2
// 3
// 4
// 5

 

 

range

범위를 지정하여 Observable을 생성합니다.

let rangeObservable = Observable.range(start: 1, count: 5)

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

// 1
// 2
// 3
// 4
// 5

 

 

create

가장 많이 사용하는 생성 방법으로 사용가자 직접 커스텀 Observable을 생성합니다.

 

들어가기 전에

 [iOS/RxSwift] Observable, Subscribe 개념 및 사용 방법을 참고하시면 이해가 더 쉽습니다.

 

기본적인 틀은 이와 같습니다.

Observable<T>.create()

create의 구독 이벤트(onNext, onError, onCompleted, onDisposed)를 직접

escape closure를 통하여 정의해 주면 되죠.

 

참고로 구독(subsribe)을 하게 되면 리턴값으로 Disposable(일회성 리소스)를 리턴합니다.

설명은 밑에 Disposable을 참고하세요.

 

그러므로 커스텀 Observable를 생성 시

Disposable(일회성 리소스)를 필수적으로 Disposables.create()를 통해 구현해줘야 하죠.

 let createObservable = Observable<String>.create { observer in
     return Disposables.create()
 }

 

커스텀 생성 시 알아야 할 내용은 끝났고

이제 클로저 내부에서 이벤트를 처리해 봅시다.

// "Hello World"를 전달하고 완료 및 에러 이벤트 처리

let createObservable = Observable<String>.create { observer in
    observer.onNext("Hello World")
    observer.onCompleted()
    observer.onError(NSError(domain: "GG", code: 404))
    return Disposables.create()
}

 

이제 구독(subscribe)을 해봅시다.

잘 나오는 것이 확인이 되네요.

createObservable.subscribe { element in
    print(element)
} onCompleted: {
    print("Complete")
}

// Hello World
// Complete

간단하게 create 생성법에 대하여 알아봤습니다.

 

 

제거 관련 연산자

제거 관련 연산자 empty, never, disposable에 대하여 알아봅시다.

 

 

empty

Observable을 초기화하고 싶을 때 사용합니다.

(빈 Observable을 만든다)

 

정상적으로 completed, disposed가 되지만 배출되는 element는 없습니다.

let emptyObservable = Observable<Any>.empty()

emptyObservable.subscribe {
    print($0)
} onError: {
    print($0)
} onCompleted: {
    print("onCompleted")
} onDisposed: {
    print("onDisposed")
}
    
// onCompleted
// onDisposed

 

 

never

Observable이 아무런 이벤트도 방출시키지 않도록 한다. (단! onDisposed 제외)

empty와 never는 주로 테스트를 위해 사용됩니다.

let neverObservable = Observable<Any>.never()

neverObservable.subscribe {
    print($0)
} onError: {
    print($0)
} onCompleted: {
    print("onCompleted")
} onDisposed: {
    print("onDisposed")
}.dispose()

// onDisposed

 

 

Disposable

지금까지 위의 예제를 따라 해 보셨다면

 Result of call to 'subscribe(onNext:onError:onCompleted:onDisposed:)' is unused 

이런 경고가 생길 거예요.

 

그 이유는 Observable의 이벤트를 처리하기 위해서 구독(subscribe)을 사용합니다.

구독(subscribe)의 정의를 살펴보면 리턴값으로 Disposable을 리턴해주고 있죠.

(이 Disposable에 대한 처리를 안 해줘서 경고가 발생하는 것이죠)

 

그럼 Disposable이 뭘까요?

Disposable은 일회성 리소스입니다. 즉, 사용 후 버려지는 리소스이죠.

그래서 구독(subscribe)은 한번 사용되면 다시 재사용이 불가능하게 되고 새로 구독을 만들어줘야 하죠.

여기서 남은 이벤트인 onNext, onError, onCompleted, onDisposed를 더 이상 사용하지 않으므로

메모리 낭비를 막기위해 필수적으로 정리해줘야 합니다.

 

그럼 여기서 구독 후 남은 찌꺼기들을 어떻게 처리해줘야 할까요?

바로 dispose() 메서드를 사용하는 겁니다.

(이름만 봐도 "폐기하다"라는 의미가 있는 메서드죠)

간단히 생각하면 메모리 관리를 위한 구독 취소라고 생각하시면 됩니다.

 

그럼 구독 취소 방법에 대해서 알아봅시다

 

이와 같이 직접 dispose를 호출해 구독취소가 가능합니다.

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

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

 

만약 구독이 여러 개가 있다고 가정해 봅시다.

let subscribe1 = observable.subscribe { _ in }
let subscribe2 = observable.subscribe { _ in }
let subscribe3 = observable.subscribe { _ in }

 

일일이 각각의 해제 시점을 고려해 시퀀스를 끊어줘야 하죠.

subscribe1.dispose()
subscribe2.dispose()
subscribe3.dispose()

 

disposeBag

dispose에서 불편함을 느껴 "Disposable을 주머니에 담아서 한 번에 해제시키자"라고

생각해 만들어진 것이 disposeBag입니다.

 

disposeBag의 생성방법은 간단합니다.

전역변수로 disposeBag을 만들어 준 뒤 disposed(by:) 메서드에 Disposable를 담아주기만 하면 되죠.

 

 disposed(by:)의 정의는 이와 같습니다. bag에 Disposable 자신을 넣어줍니다.

extension Disposable {
    public func disposed(by bag: DisposeBag) {
        bag.insert(self)
    }
}

 

그럼 disposeBag을 사용해 봅시다.

let disposeBag = DisposeBag()

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

let subscribe1 = observable.subscribe { _ in }
let subscribe2 = observable.subscribe { _ in }

만들어둔 disposeBag를 disposed(by:)에 넣어주면 끝!

subscribe1.disposed(by: disposeBag)
subscribe2.disposed(by: disposeBag)

 

"어? 그러면 Disposable를 disposeBag에 담아주긴 했는데 해제는 언제 되는 거지?"

라는 의문을 가질 수 있습니다.

 

disposeBag의 해제 시점은

기본적으로 사용되고 있는 컨트롤러가 deinit 됐을 때 자동으로 dispose를 시켜줍니다.

다른 방법으로는 객체 disposeBag에 임의로 nil 값을 할당했을 때 dispose가 호출됩니다.

(disposeBag = nil, 이때는 대신 옵셔널로 선언해 줘야겠죠)

 

간단히 disposeBag - dispose의 내부 구현부를 보면 동작방식을 살펴봅시다.

// dispose가 되면 for-in문을 통해 기존의 Disposable을 새로운 Disposable로 변환시켜 줍니다.

private func dispose() {
    let oldDisposables = self._dispose()
    
    for disposable in oldDisposables {
        disposable.dispose()
    }
}

 

 

지연 관련 연산자

deferred

무언가를 미룬다는 의미로 사용되는 연산자로 Observable 생성을 연기할 수 있습니다.

 

즉, deferred는 구독(subscribe)되기 전까지 Observable의 생성을 미뤄주는 역할을 합니다.

 

글만으로는 이해가 안 되니 바로 예제를 봅시다.

 

이와 같이 메인스레드에서 동작하는

어떤 오래 걸리는 작업이 있다고 생각해 봅시다.

func heavyWork() {
    DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
        print("Take a long time to work")
    }
}

 

이제 just로 Observable을 생성해 봅시다.

 

구독(subscribe)을 하지도 않았는데 위의 5초 동안 하는 긴 작업이 호출되고 있습니다.

(just 안쪽에서 함수를 미리 실행시키기 때문)

of, from도 마찬가지로 미리 실행을 시킵니다.

let observable = Observable.just(heavyWork())

// 5초 후
// Take a long time to work 출력

 

위와 같이 메인스레드에서 5초가 걸리는 무거운 작업을 하게 된다면

그 시간 동안 UI를 그리는 메인스레드를 방해하고 있으므로 5초 동안 화면이 멈춘 거 같이 보일 겁니다.

 

이건 비효율적이겠죠?

 

그래서 RxSwift에서는 구독(subscribe)이 호출되기 전까지 작업을 미뤄주는 역할을 하는

deferred 연산자가 있습니다.

let deferredObservable = Observable.deferred {
    // Observable을 리턴해 줍니다.
    return Observable.just(heavyWork())
}

// 동작을 하지 않고 구독(subscribe)을 기다림

 

구독을 하는 순간에 heavyWork() 작업이 동작

deferredObservable.subscribe { _ in
    // ...
}

// 5초 후 
// Take a long time t work 출력

 

deferred를 사용하게 되면

구독이 호출되는 순간 실행하기 때문에 쓸데없는 작업을 막고 필요한 시점에서만 작업을 수행할 수 있습니다.

즉, 무거운 작업(오래 걸리는 작업) 시 스레드 낭비를 막을 수 있습니다.

 

구독되는 시점에 동작하므로

주로 구독과 동시에 어떠한 업데이트가 필요할 때 deferred로 감싸서 사용합니다.

 

 

부록

OpenAPI 호출 예제

(AlamoFire + Observable 생성)

// 모델
struct ArticleResponse: Codable {
    let articles: [Article]
}

struct Article: Codable {
    let title: String
}

// Observable 생성
func getNews() -> Observable<[Article]> {
    return Observable.create { observer in
        
        let urlString = "https://newsapi.org/v2/everything?q=tesla&sortBy=publishedAt&apiKey=123456789"
        
        AF.request(urlString)
            .validate(statusCode: 200...299)
            .responseDecodable(of: ArticleResponse.self) { response in
                switch response.result {
                case .success(let data):
                    observer.onNext(data.articles)
                    observer.onCompleted()
                case .failure(let error):
                    observer.onError(error)
                }
            }
            
        return Disposables.create()
    }
}

// Observable 호출
getNews().subscribe { event in
    switch event {
    case .next(let articles):
        print(articles)
    case .error(let err):
        print(err.localizedDescription)
    case .completed:
        print("complete")
    }
}.disposed(by: disposeBag)

 

 

부족한 설명이지만, 조금은 이해 가셨나요?

틀린 내용이 있다면 언제든지 지적해 주시면 감사히 받겠습니다. 🫠
읽어주셔서 감사합니다 😃

 

 

반응형
profile

Danny의 iOS 컨닝페이퍼

@Danny's iOS

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!