1년전 rx를 사용해야만 하는 프로젝트가 있어서 그때 잠깐 공부하고, 작년 7월쯤 한번 더 공부했지만 그때는 rx를 왜 사용해야하는지, 장점을 찾아봐도 이해는 되지만 크게 와닿지 않아서 조금 공부하다가 자연스럽게 사용하지 않게 되었다.
취업준비를 하면서 기존에 만들어 배포했던 다이어리 앱 코드의 심각성을 느껴 리팩토링과 기능을 추가하는 단계에서 연습용 프로젝트로 했던것 보다 좀 더 복잡한 비동기처리를 해야할 일이 생겼는데 @escaping으로 벗어나고 중괄호 안으로 타고타고 들어가는 코드를 보니 뭔가 마음에 들지 않아 좀 더 찾아보니 이런 부분을 rx로 해결할 수 있다는 것을 알게 되었다. 그때부터 rx를 배워야겠다고 생각하고 취업을 한 후 회사 코드를 보다보니 복잡한 비동기 순서때문에 @escaping이 많아지고 복잡한 코드를 보니 rx의 필요성을 느끼고 비동기를 공부해보고자 한다.
rx가 워낙 유명하고 사용하는 곳이 많다보니 복잡한 비동기 처리를 하려면 rx를 배워야하는 것인가라는 생각도 했었는데 그렇지는 않다.
@escaping을 이용한 콜백 함수를 통해 복잡하지만 처리할 수 있고, GCD, Notification Center, Async/Await, PromiseKit, Combine 등등 다양한 방법이 있지만 코드 가독성, 성능 등을 고려하여 적절한 방법을 찾는것이 중요하다고 생각한다.
저는 인프런 앨런의 동시성프로그래밍 강의를 듣고 기초를 공부하였고,
해당 포스팅은 제 머리와 구글, 곰튀김 Rx강의를 듣고 참고하여 작성하였습니다.
Rx란?
영문 그대로 해석하자면 관측가능한 스트림이 있는 비동기 프로그래밍을 위한 API 이다.
뭔가 말이 친근하지 않은데...
이벤트 발생을 관찰하는 주체(Observable)과 이를 구독하는 구독자(Subscriber) 간에 관계를 맺어 변화를 감지하고 이벤트를 처리한다.
고 이해했다!
Rx 예시
func myFunction(completion: @escaping (String?) -> ()) {
print("Hello")
completion("Hi")
print("world")
}
myFunction() { response in
print(response) // Hi
}
콜백함수를 이용하면 위와같이 작성할 수 있는데, return이 없지만 myFunction함수를 벗어나 값을 전달하고 있다. rx를 사용한다면 비동기적으로 발생하는 데이터를 return을 통해 값을 전달할 수 있다.
func myFunction() -> Observable<String?> {
// 1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
return Observable.create() { response in
DespatchQueue.global().async {
let newData = "newData"
DispatchQueue.main.async {
response.onNext(newData)
response.onComplete() // 클로저 실행 종료 => rc 사라짐
}
}
return Disposables.create()
}
}
// 2. Observable로 오는 데이터를 받아서 처리하는 방법
myFunction()
.subscribe { event in
switch event {
case let .next(response):
print(response)
case .completed:
break
case .error:
break
}
}
위 코드처럼 return 값을 Observable로 감싸주면 비동기적으로 생성되는 나중에 생기는 데이터라는것을 알 수 있고, 아래 subscribe를 통해 나중에 데이터가 오면 그때 호출할 수 있다.
switch-case문에서 next는 성공, error는 실패이며 next로 성공하면 complete를 만나야 종료되고, error를 만나면 complete를 만나지 않아도 바로 종료된다.
하지만 위 코드와 같이 작성하면 "Result of call to 'subscribe' is unused"라는 노란 경고가 발생하는데, 이는 myFunction의 .subscribe()를 실행한 후 return 값을 사용하지 않았기 때문이다.
subscribe()를 살펴보면 다음과 같이 선언되어 있다.
return값으로 Disposable을 반환하고 있는데, 이는 다음과 같이 사용할 수 있다.
let disposable = myFunction()
.subscribe { event in
switch event {
case let .next(response):
print(response)
case .completed:
break
case .error:
break
}
}
disposable.dispose()
return으로 받은 Disposable를 통해 dispose()를 실행할 수 있다.
dispose()를 실행하면 작업이 취소된다.
subscribe는 리턴값으로 주어지는 Disposable를 가지고 있다가 원하는 시점에 dispose()를 실행하여 작업을 취소할 수 있다.
var disposable: Disposable?
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
disposable?.dispose()
}
func onLoad() {
disposable = .subscribe(onNext: ...
}
위 코드와 같이 onLoad()를 통해 무언가 다운로드와 같은 동작을 하던 중 화면이 Disappear되면 dispose()를 실행해 작업을 중간에 취소할 수 있다.
여러개의 subsribe가 있을 경우는 disposable를 배열로 만들어 저장하여 for문을 통해 dispose시키면 된다.
그러나 여러개의 Disposable를 DisposeBag로 한번에 관리할 수 있다.
var disposeBag: DisposeBag?
func onLoad() {
disposable = .subscribe(onNext: ...
.disposed(by: disposeBag)
}
이와같이 작성하면 보다 쉽게 관리할 수 있고, 멤버변수이기 때문에 클래스가 사라질때 함께 사라진다.
func exampleFunc() -> Observable<String?> {
return Observable.create() { emitter in
emitter.onNext("Hello")
emitter.onNext("World")
emitter.onCompleted()
return Disposables.create()
}
1. Observable.create() 생성
2. 그 안에 emitter와 같은 값 하나 만들기
3. onNext를 통해 값 전달 (여러개 가능)
4. onCompleted()를 통해 종료
5. Displsables.create()리턴 (메모리 누수 예방)
6. 이걸 리턴
* Observable의 생명주기
1. Create
2. subscribe => Create 후 구독되었을때 동작
3. onNext
4. onCompleted / onError
5. Disposed
Operators
func exampleFunc() -> Observable<String?> {
return Observable.create() { emitter in
emitter.onNext("Hello World")
emitter.onCompleted()
return Disposables.create()
}
위 예시 코드에서는 Hello World라는 String을 넘기기 위해 이와같이 작성했다.
그러나 하나의 데이터만을 전달하는데 코드가 너무 길다는 단점을 Operator를 사용하여 보완할 수 있다.
func exampleFunc() -> Observable<String?> {
return Observable.just("Hello World")
}
이처럼 just라는 Operator를 사용해 짧게 작성할 수 있다.
여러개의 onNext를 작성하지 않고 Operator를 통해 쉽게 데이터를 가공하여 전달할 수 있다.
+) ObserveOn
exampleFunc()
.subscribe(onNext: { data in
DispatchQueue.main.async {
self.label.text = data
}
})
exampleFunc()
.ObserveOn(MainScheduler.instance)
.subscribe(onNext: { data in
self.label.text = data
})
observeOn을 통해 main스레드에서 실행하는 코드를 아래 코드와 같이 코드 한줄로 작성할 수 있다.
'iOS' 카테고리의 다른 글
[iOS] WKWebView 쿠키 데이터 가져오기 (자동로그인) (0) | 2023.06.08 |
---|---|
[iOS] PageViewController 사용하기 (0) | 2023.05.18 |
[iOS] 뷰를 push, present 하는 과정에서 이전 navigation controller에 접근해 뷰 push하기 (0) | 2023.04.20 |
[iOS] 코드로 화면 구성하기 (0) | 2023.04.20 |
[iOS] xcode Vary for Traits (0) | 2023.03.28 |