[RxSwift] 왜 사용할까?

강대훈·2024년 11월 16일

RxSwift

목록 보기
1/5
post-thumbnail

곰튀김 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 를 적용하는 이유다. 그러면 이제 앞으로 더 깊이 탐구해보고 배워보자.


참고자료 - https://www.youtube.com/watch?v=iHKBNYMWd5I&t=10131s

0개의 댓글