곰튀김 RxSwift 영상을 보면서 왜 RxSwift가 사용되는지에 대해서 정리해 보았다.
아무래도 영상을 한번만 보고 모든 것을 이해하기엔 너무 어렵기에 계속해서 복습하고 앞으로도 Observable, Observer, Subject에 관하여 또 정리하고 출간할 생각이다.
RxSwift를 사용하지 않고도 버튼을 클릭했을 때 비동기적으로 처리하면 애니메이션으로 만들 수 있으며 데이터를 전달할 수 있다.
하지만 어째서인지 RxSwift를 많이들 사용한다. 왜 그럴까?
URL로부터 데이터를 받아서 JSON을 파싱하는 간단한 작업을 먼저 살펴보자.
// JSON 을 간단하게 String으로 변환하여 화면에 띄어주는 작업
func downloadJson(_ url : String, completion : @escaping (String?) -> Void) {
DispatchQueue.global().async {
let url = URL(string: url)!
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
DispatchQueue.main.async {
completion(json)
}
}
}
@IBAction func onLoad() {
downloadJson(MEMBER_LIST_URL) { jsonData in
self.editView.text = jsonData
self.setVisibleWithAnimation(self.activityIndicator, false)
}
}
먼저 비동기를 사용해서 함수를 호출하고 컴플리션을 통해 화면에 데이터를 전달해주었다.
가장 큰 단점은 return 을 이용할 수 없다는 점이다. 또한 데이터를 통신하는 일이 중첩되게 된다면 함수가 계속해서 들여쓰기가 생기게 되어서 코드는 엄청나게 지저분해지는 단점이 생길 수 있다.
그러면 이제 RxSwift의 근간이 되는 데이터를 클래스로 감싸는 방법으로 반응형 프로그래밍을 만들어보자.
class 나중에생기는데이터<T> {
private let task : (@escaping (T) -> Void) -> Void
init(task: @escaping (@escaping (T) -> Void) -> Void) {
self.task = task
}
func 나중에오면(_ f : @escaping (T) -> Void) {
task(f)
}
}
버튼을 누르면 반응한다는 예로 생각해보자. 버튼이 눌렸을 때 그에 상응하는 동작은 task 프로퍼티에 정의된다. 동작은 클로저가 될 것이다.
보다시피 위에 보면 task 는 나중에생기는데이터 가 초기화될 때 파라미터를 통해서 전달되는 것을 볼 수 있다. 즉 나중에생기는데이터 인스턴스가 생긴다는 것은 버튼이 눌렸을 때 그에 상응하는 동작을 클래스를 통해 만들어 둔 것이다.
그러면 버튼이 눌렸을 때 실제로 어떤 작업을 해줘야할까? 바로 나중에오면 메소드를 실행해주면 된다.
func downloadJson(_ url : String) -> 나중에생기는데이터<String?> {
return 나중에생기는데이터 { f in
DispatchQueue.global().async {
let url = URL(string: url)!
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
DispatchQueue.main.async {
f(json)
}
}
}
}
@IBAction func onLoad {
downloadJson(MEMBER_LIST_URL)
.나중에오면 { json in
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}
}
나중에생기는데이터 인스턴스를 생성할 때 task 프로퍼티에는 다음과 같은 클로저가 저장되어 있을 것이다.

나중에오면 의 파라미터에 다음과 같은 함수를 넣어서 실행했다고 생각해보자.

나중에오면 메소드에서 task(f) 를 실행시키기 때문에 나중에오면 메소드의 파라미터(클로저)가 task 의 파라미터로 전달된다.
task 의 클로저에서 DispatchQueue.main.async 부분에서 다시 task 가 실행될 때 받은 파라미터를 실행하고 있기 때문에 나중에오면 을 실행하면서 보냈던 파라미터가 다시 실행된다.
이러한 로직이 바로 RxSwift 의 근간이 되는 로직이다.
하지만 RxSwift는 이러한 로직에서 메모리 누수 방지, 에러 처리와 같은 더 효과적인 기능을 제공해주기 때문에 많은 사람들이 RxSwift를 사용하고 있다.
func downloadJson(_ url : String) -> Observable<String?> {
return Observable.create { emitter in
let url = URL(string : url)!
let task = URLSession.shared.dataTask(with: url) { data, response, error in
guard error == nil else {
emitter.onError(error!)
return
}
if let data = data, let json = String(data : data, encoding : .utf8) {
emitter.onNext(json)
}
emitter.onCompleted()
}
task.resume()
return Disposables.create { task.cancel() }
}
}
@IBAction func onLoad() {
let disposable = observable.subscribe { event in
switch event {
case .next(let json):
DispatchQueue.main.async {
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}
case .completed:
break
case .error(let error):
break
}
}
}
RxSwift 를 적용한 코드를 보면 쉽지 않겠지만 onNext , onCompleted , onError 와 같은 함수가 존재한다. 상황에 따라서 어떤 동작을 실행할 것인지 선택한다고 보면 된다.
이런 유연한 방법이 사람들이 RxSwift 를 적용하는 이유다. 그러면 이제 앞으로 더 깊이 탐구해보고 배워보자.