RxSwift: Reactive Programming with Swift
전반적으로 위 교재를 참고하여 작성했습니다.
1️⃣ Rx Swift가 뭘까요?
https://github.com/ReactiveX/RxSwift
📌 어원
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를 사용하는 이유?
가독성 : 여러 쓰레드를 넘나 들고 클로저를 넘겨서 이벤트를 처리하기는 기존의 방식을 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를 통해서 간편하게 해결할 수 있죠!
- 자가피드백..
..어차피 false값으로만 바꿔줄건데, 굳이 Bool타입 subject로 선언할 필요가 없었다. 탭 액션을 인식하기만 하면 되는것이기 때문에...let subject = PublishSubject<Void>() let subscriptionOne = subject .subscribe(onNext: { _ in print("하이") }) subject.onNext(())
✅ 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를 적용하는 방법에 대해 알아보겠습니다!