본문 바로가기

iOS

[RxSwift] RxSwift 기본 (1편 - Observable, Observer, Subject)

RxSwift: Reactive Programming with Swift

 

RxSwift: Reactive Programming with Swift

<p>Leverage the power of RxSwift in your reactive apps!</p> <p>This book is for iOS developers who already feel comfortable with iOS and Swift, and want to dive deep into development with RxSwift.</p> <p>Start with an introduction to the reactive programmi

www.raywenderlich.com

전반적으로 위 교재를 참고하여 작성했습니다.


1️⃣ Rx Swift가 뭘까요?

https://github.com/ReactiveX/RxSwift

 

GitHub - ReactiveX/RxSwift: Reactive Programming in Swift

Reactive Programming in Swift. Contribute to ReactiveX/RxSwift development by creating an account on GitHub.

github.com

📌 어원

RxSwift는 Reactive eXtension + Swift의 합성어로 이루어진 용어로,

Swift에 ReactiveX를 적용시켜 비동기 프로그래밍을 직관적으로 작성할 수 있도록 도와주는 라이브러리입니다.

📌 정의

📚 Rx is a generic abstraction of computation expressed through Observable<Element> interface, which lets you broadcast and subscribe to values and other events from an Observable stream. RxSwift is the Swift-specific implementation of the Reactive Extensions standard.

위 글을 해석해보면 “ RX는 Observable<Element>이라는 인터페이스로 표현되는 computation(프로그래밍, 코딩, 계산)의 제네릭 추상화이다. 이를 통해서 Observable(관측가능한) 흐름으로부터 값이나 다른 이벤트들을 구독하고 송출할 수 있게 해준다. “ 라고 하네요!

쉽게 풀어쓰면, RxSwift는 비동기 프로그래밍을 관찰 가능한 흐름으로 지원해주는 API입니다. 옵저버 패턴과 이터레이터 패턴, 그리고 함수형 프로그래밍을 조합한 반응형 프로그래밍 익스텐션인 것이죠~!

이래도 뭐라는지 모르겠네요

어쨌건! RxSwift는 스위프트 특화적인 반응형 프로그래밍을 가능하게 해준다고 합니다!

📌 RxSwift를 사용하는 이유?

[RxSwift] RxSwift와 비동기 프로그래밍

 

[RxSwift] RxSwift와 비동기 프로그래밍

안녕하세요! 제인입니다 :) 요즘 비동기적 처리에 대한 필요성을 느끼고, 아키텍처 공부도 시작하다 보니 자연스럽게 RxSwift에 관심이 생겨 공부를 해보고 있는데요! RxSwift의 기본 개념부터 차근

janechoi.tistory.com

가독성 : 여러 쓰레드를 넘나 들고 클로저를 넘겨서 이벤트를 처리하기는 기존의 방식을 Rx를 이용하면 가독성이 좋게 만들 수 있다.

간결성 : 코드가 깔끔해진다.

비동기 처리 : Observable 타입을 이용해 비동기 처리를 할 수 있다.

📌 비동기 처리 방식

RxSwift는 데이터의 변화를 구독(Observe)할 수 있기 때문에, 데이터의 변화에 순차적으로 반응하여 비동기 처리를 쉽게 도와준다고 합니다.

 

참고 : 애플에서 제공하는 기본적인 비동기 API

  • NotificationCenter
  • The delegate pattern
  • Grand Central Dispatch
  • Closures
  • Combine

2️⃣ Rx의 주인공들 : Observable, Observer, Subject

📌 Observable

..? 옵저버블이요..? 그게 뭔데요 갑자기...

 

✳️ Observable(관측가능한)

옵저버블은 관찰가능한 이벤트의 sequence를 만들어 줍니다! 관찰가능한 친구이니까, 누군가가 관찰해서 그 이벤트에 따른 행동을 할 수 있게 하는... 그런 RxSwift의 심장과도 같은 녀석입니다.

 

✅ Observable 타입에 대한 설명

  • 데이터의 스냅샷을 전달할 수 있는 이벤트 시퀀스를 비동기적으로 생성하는 기능을 담당합니다.
  • RxSwift는 Observable을 통해 이벤트 값을 방출(emit)할 수 있고, 이 값을 구독하여 관찰하고 반응할 수 있습니다.
  • 하나 이상의 관찰자(Observer)가 실시간으로 어떤 이벤트에 반응하고 UI를 업데이트하거나 들어오는 데이터를 처리하고 활용할 수 있게 합니다.

아직 어떤 방법인지는 모르겠지만, 위와 같이 Observable<Int>가 event들을 만들어(방출해) 줍니다.

그러면 이 Observable을 구독(관측)하고 있는 Observer들이 이 이벤트들의 발생을 감지합니다!

이벤트를 감지한다면, 이벤트 감지 시점에 옵저버는 특정 행위를 취할 수 있겠죠?

이 Observer들은 같은 Observable에서 같은 Event 값이 와도 다른 행동을 하도록 정의할 수 있습니다.

예를 들어 받은 Int값을 라벨 텍스트에 반영한다던지...! Int값을 어떤 프로퍼티에 더해준다던지...!

위처럼 버튼의 tap이벤트를 감지할 수도 있습니다~!

 

✅ Observable의 Event

 

observable은 세 가지 이벤트만 방출할 수 있습니다. 아래를 보시면 Event에 제네릭 타입으로 element를 받아주고 있네요! Int, String, UIImage 등등...

 

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
}
  • next : 말 그대로 다음 값을 전송하는 이벤트. 값은 next event를 통해서만 전달될 수 있다.
  • error : observable 타입이 값을 배출하는 과정에서 에러가 발생하면 error를 방출하고 종료시키는 이벤트
  • completed : 성공적으로 이벤트 시퀀스를 종료시키는 이벤트이다. Observable 타입이 더 이상 값을 배출하지 않는다.

여기서 주목할 점! error와 completed 타입 모두 옵저버블의 활동을 종료시켜버립니다! 더이상 어떠한 이벤트도 발동하지 않게 되어버리는 ㅠㅠ

📌 Observable을 생성하는 방법

 

let observable: Observable<Int> = Observable<Int>.just(1)

 

just는 오직 하나의 요소를 포함하는 Observable Sequence를 생성합니다.

 

let observables = Observable.of(1, 2, 3)

 

of는 주어진 값들에서 Observable Sequence를 생성합니다.

 

let observables2 = Observable.of([1, 2, 3])

 

of의 인자로 array를 넣게되면 [1, 2, 3]을 단일요소로 가지게 됩니다

observables2 의 타입은 Observable<[Int]>

 

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

 

from을 사용하게 되면 array의 요소들로 Observable Sequence 생성

observables3 의 타입은 Observable<Int>

 

📌 Observable의 Subscribing(구독)

지금까지는 Observable이 어떤 이벤트를 가지는지 정의를 했을 뿐, 이 이벤트에 대한 구독을 하지 않았습니다! Observable을 정의만 하고 구독을 하지 않으면 아무런 일도 일어나지 않아요!!

 

✅ 구독은 어떻게 할까?

 

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

observable.subscribe({ (event) in
  print(event)
})

/* Prints:
 next(1)
 next(2)
 next(3)
 completed
*/

 

prints를 보면 Observable을 구독했을 때, Observable이 가진 특정 event 자체를 인자로 받아서, 그 인자에 대한 여러가지 처리를 할 수 있겠네요! 출력형태는 event의 형태로 나타납니다.

그런데 저희가 관심이 있는 것은 event 자체가 아닌 event에 실려 오는 element의 값이기 때문에!

 

observable.subscribe { event in
  if let element = event.element {
    print(element)
  }
}
/* Prints:
1
2
3
*/

 

위와 같이 element의 값을 받아올 수 있기는 한데... 이것보다는

아래처럼 next event에 대한 행동만 정의할 수도 있습니다! onNext의 Handler에는 element를 인자로 받기 때문에 더욱 편리하게 사용할 수 있네요!

 

observable.subscribe(onNext: { element in
  print(element)
})
/* Prints:
1
2
3
*/

 

✅ subscribe 메서드의 정의를 살펴볼까요?

 

저희가 subscribe를 사용해서 옵저버블을 구독했으니..한번 이 친구에 대해서 알아봅시다!

 

func subscribe(_ on: @escaping (Event<Element>) -> Void) -> Disposable

 

우선 반환 타입은 Disposable입니다. Disposable? 예.. 버릴 수 있는 친구라네요

버리긴 뭘 버리냐구요? 나중에 봅시다 어떻게 버리는지(이 부분은 중요하니까 다시 다룰게요!)

 

그리고 on 뒤에 있는 클로저를 보면, Event 타입을 받아서 어떠한 일을 할 수 있도록 만들어줬네요!!

 

그래서 저희는! 각각의 이벤트 타입 .next, .error, .completed 에 대해 어떤 일을 해줄지 정의해줄 수 있습니다!!

아래는 .next와 .completed에 이벤트가 관찰되었을 때 어떤 행동을 할지에 대해 정해주고 있네요!!

 

Observable.of(1,2,3)
    .subscribe(onNext: { num in
        print(num)
    }, onCompleted: {
        print("completed")
    })

📌 Disposing, 구독을 취소하자

근데 저희 위에서 봤자나요.. Disposable... 버릴 수 있는... 처리할 수 있는...뭘 버리지??

쉽게 말하면 구독을 취소할 수 있는 클래스라는 의미입니다!

subscribing은 Observable이 이벤트들을 방출하도록 해주는 방아쇠 역할을 하고,

→ 이벤트를 고이 간직만 하고 있던 옵저버블에 subscribe 메서드를 사용하면

→ 리턴값으로 Disposable한 객체를 만들어줍니다!

이는 언제든 구독을 취소할 수 있는 가능성이 열려있다는 뜻이고

Disposable한 객체이기 때문에 저희는 dispose() 를 통해서 가차없이 버립니다. 이 구독을.

 

✅ dispose()

 

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

let subscription = observable.subscribe({ num in
   print(num)
})

/* Prints:
1
2
3
*/

subscription.dispose()

 

위처럼 disposable한 인스턴스를 생성해서, 원하는 시점에 dispose해버릴 수도 있고!

 

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

observable.subscribe({ num in
     print(num)
}).dispose()

/* Prints:
1
2
3
*/

 

따로 인스턴스를 생성하지 않고 그냥 구독하고 이벤트 다 봤으면 방출해버리는...이런 무자비한 방법도 있습니다

 

⁉️ 어...그런데..왜 버렸더라...?

Observable은 관측가능한 event의 흐름을 내보내는 친구이고, 이 흐름을 구독하여 특정 이벤트에 대한 처리를 해줄 수 있습니다!

그런데 말 그대로, 구독을 하고 있는 상태이기 때문에, 구독만 하고 취소하지 않으면 메모리 누수가 발생하겠죠? 이를 방지하기 위해 dispose 해주는 겁니다~!

 

✅ Disposebag, 한번에 다 버리자

 

근데 이거 언제 하나씩 적절한 시점에 다 구독취소해주냐... 귀찮은 작업이 되겠죠?

그래서 있는 친구가 쓰레기통 == DisposeBag() 입니다!

 

let disposeBag = DisposeBag()

Observable.of("A", "B", "C")
  .subscribe { print($0) }
  .disposed(by: disposeBag)

Observable.of("A", "B", "C")
  .subscribe { print($0) }
  .disposed(by: disposeBag)

Observable.of("A", "B", "C")
  .subscribe { print($0) }
  .disposed(by: disposeBag)

위처럼 disposeBag에 Dispoasable한 친구들을 disposed(by:) 메서드로 넣어주면...

disposeBag이 메모리에서 해제될 때 모든 구독이 취소됩니다!

 

예를 들어, 뷰컨트롤러에 disposeBag을 선언하고, 여러 가지 옵저버블에 대한 disposable을 disposebag에 넣어주면, 뷰컨트롤러가 메모리에서 해제될 때 구독도 모두 취소되는거죠~~

📌 Observer

✅ 근데 아까 Observer가 Observable을 관측한다면서요..Observer는 어디?

 

아 ㅋㅋㅋ 저 여깄음

 

Observable<String>.create { observer in

}

 

오 ㅋㅋㅋ ㅎㅇ...어 근데 observer로 뭐하지...?

 

일단 정의부터 살펴보실게요!

 

static func create(_ subscribe: @escaping (AnyObserver<Element>) -> Disposable) 
-> Observable<Element>

 

일단 create 메서드를 Observable에 사용하면 Observabl 이 다시 리턴되는데 아까 만난 다른 메서드들(of, just) 등과는 다른 점이, Event<> 를 클로저 인자로 받는게 아니라 AnyObserver<String> 를 인자로 받네요?

AnyObserver<String> 로 뭘 할 수 있는지 한번 봅시다!

 

✅ Observer에 이벤트 보내주기(알려주기)

 

Observable<String>.create { observer in
  // 1
  observer.onNext("1")
  // 2
  observer.onCompleted()
// 3
  observer.onNext("?")
// 4
  return Disposables.create()
}

 

아아아.. 옵저버에는 이벤트를 바로 보내줄 수 있네요..! 왜냐! 옵저버는 관찰자니까, 관찰자한테 이런 이벤트가 있다고 바로 넣어주는거죠~!

사실 이렇게 생성한 결과물은, 똑같은 Observable일 뿐이고...

 

Observable<String>.create { observer in
	// 1
	observer.onNext("1")	
	// 2
	observer.onCompleted()
	// 3
	observer.onNext("?")
	// 4
	return Disposables.create()
}.subscribe(onNext: { t in
  print(t)
}).dispose()

/* prints
1
*/

구독하고 dispose해주는 것은 똑같습니다..예..

 

⁉️  물론! AnyObserver 타입 인스턴스를 직접 생성할 수도 있습니다! 그렇게도 활용되는 예제들이 제법 있구요~ 하지만 개인적으로 Subject 타입을 사용하는 것이 더 편하다고 느껴져서... 더이상 다루지는 않겠습니다. 직접 해보시길!!


📌 Subject : Observable이면서 Observer인 것

Rx에서 정말 많이 쓰이는 친구... Subject가 등장했습니다!

Observable이면서, Observer라고 하는데 어떤 의미일까요??

스스로 이벤트를 방출할 수도 있으면서....다른 옵저버블을 구독할 수도 있다!

먼저 정의를 봅시다!

 

public final class PublishSubject<Element>: Observable<Element>

 

오오..확실히 Observable이긴 하군요! 그렇다면 Observer란 말은??

예제를 보시면 이해가 될 것 같습니다!!

 

let subject = PublishSubject<String>() // subject 생성

// Observable의 역할
let subscriptionOne = subject
    .subscribe(onNext: { string in
        print(string)
    })

// Observer의 역할
subject.on(.next("Is anyone listening?"))
subject.onNext("안녕")

 

예제를 보시면, 아까 Observable에서 봤듯이 subject를 구독할 수 있다는 것을 알 수 있구요

subject에 직접 Event를 추가하는 것으로 보아 Observer의 역할도 하고 있는 것으로 보입니다!

 

✅ Subject의 활용

 

그러니까.. Subject를 쓰면 아래와 같이 아아아주우우우우 유용한 방식이 가능해집니다.

 

final class AuthTextField: UITextField {
    
    var clearButtonTapped = PublishSubject<Bool>()
    
    private lazy var clearButton: UIButton = {
        let bt = UIButton()

        bt.addAction(UIAction(handler: { _ in
            self.text = ""
            self.clearButtonTapped.onNext(false)
        }), for: .touchUpInside)
        
        return bt
    }()
}

final class LoginVC {
	var disposeBag = DisposeBag()
    private let emailTextField: AuthTextField()
    
	.
    .

	func bind() {
    emailTextField.clearButtonTapped
            .asDriver(onErrorJustReturn: false)
            .drive(onNext: { [weak self] isEnabled in
                self?.loginButton.isEnabled = isEnabled
            })
            .disposed(by: disposeBag)
    }
}

➡️  커스텀 텍스트필드 안에 clearButtonTapped라는 PublishSubject<Bool>() 프로퍼티 선언

➡️  버튼 클릭 시 PublishSubject에 onNext로 bool타입 false 값을 전달

➡️  뷰컨에서 커스텀텍스트필드에 있는 PublishSubject를 구독하고, onNext 이벤트가 생길 시 어떤 행위를 할 지 설정해줌 : 로그인버튼의 isEnabled 상태를 false로 변경시켜준다.

 

이처럼 두 객체간의 정보를 전달할 때에 아주 유용하게 사용할 수 있습니다. 그래서 RxSwift가 아키텍쳐의 확장에 효율적이라고 하는 것이구요~~! 기존에는 NotificationCenter나 delegate Pattern, 클로저를 통해서만 할 수 있었던 일을 RxSwift를 통해서 간편하게 해결할 수 있죠!

 

  • 자가피드백..
    let subject = PublishSubject<Void>()
        
    let subscriptionOne = subject
      .subscribe(onNext: { _ in
        print("하이")
      })
    
    subject.onNext(())
    ..어차피 false값으로만 바꿔줄건데, 굳이 Bool타입 subject로 선언할 필요가 없었다. 탭 액션을 인식하기만 하면 되는것이기 때문에...

✅ Subject의 종류!

subject는 무려 4종류나 있다고 하는데요~ 한 번 살펴봅시다!

1️⃣  PublishSubject : 빈 값으로 시작해서 오직 새로운 하나의 이벤트만 구독자에게 전달한다

2️⃣  BehaviorSubject: 초기값으로 시작해서 초기 이벤트를 방출하거나, 그 이후의 이벤트를 구독자에게 전달한다.

3️⃣  ReplaySubject: 특정 크기만큼의 이벤트를 저장하고, 이후에 구독한 구독자에게 이벤트들을 돌려준다(replay)

4️⃣  AsyncSubject: 시퀀스의 마지막 이벤트만 방출하거나, completed 이벤트만 방출한다. 잘 사용할 일이 없는 친구라고 하네요~

 

✨  PublishSubject

let subject = PublishSubject<String>()

let subscriptionOne = subject
  .subscribe(onNext: { string in
    print(string)
  })

subject.onNext("1")

/* prints
1
*/

let subscriptionTwo = subject
  .subscribe(onNext: { string in
    print("2)", string)
  })

subject.onNext("3")

/* prints
3
2) 3
*/

subscriptionOne.dispose()
subject.onNext("4")

/* prints
2) 4
*/

✨ BehaviorSubject

let subject = BehaviorSubject(value: "초기값")
let disposeBag = DisposeBag()

subject
  .subscribe(onNext: { string in
    print("1)", string)
  })
  .disposed(by: disposeBag)

/* prints
1) 초기값
*/

subject.onNext("X")

/* prints
1) X
*/

subject
  .subscribe(onNext: { string in
    print("2)", string)
  })
  .disposed(by: disposeBag)

/* prints
2) X
*/

✨ ReplaySubject

let subject = ReplaySubject<String>.create(bufferSize: 2)
let disposeBag = DisposeBag()

subject.onNext("1")
subject.onNext("2")
subject.onNext("3")

subject
  .subscribe(onNext: { string in
    print("1)", string)
  })
  .disposed(by: disposeBag)

/* prints
1) 2
1) 3
*/

3️⃣ Operator

📌 Operator : Observable이 생성한 값을 이리저리 바꿔서 전달하자!

  • ObservableType과Observable 클래스에는 복잡한 논리를 구현하기 위해 많은 메서드가 포함되어 있습니다. 이 메서드들을 Operator라고 불러요~!
  • Operator는 비동기 입력을 받아 출력만 생성하기 때문에 쉽게 결합이 가능합니다.

📌 예시 : .filter와 .map

UIDevice.rx.orientation
  .filter { $0 != .landscape }
  .map { _ in
    return "portrait 입니다."
  }
  .subscribe(onNext: { msg in
    print(msg)
  })
  • 첫번째, filter는 .landscape가 아닌 값만 통과시킨다.
  • 두번째, .portrait 값이 들어온다면 map은 “portrait 입니다.” 출력으로 변환합니. (Orientation -> String)
  • 마지막으로, Subscribe를 통해 next이벤트를 구현. 앞에서 받아온 String 타입 msg 값을 받아 print합니다.

정말 정말 정말 많은 오퍼레이터들이 있지만... 이 부분은 추후에 하나씩 연재하겠습니다.

 

다음 편에서는 RxSwift의 Copanion 라이브러리인 RxCocoa를 이용하여 실제 프로젝트에 RxSwift를 적용하는 방법에 대해 알아보겠습니다!