🍎 iOS/RxSwift

[RxSwift] 곰튀김 RxSwift + MVVM 정리

dev_zoe 2023. 1. 16. 15:19
반응형

1. 왜 비동기 프로그래밍을 하는가?

생활 예로, 마트에서 물건을 살 때 계산 줄을 서는 것을 생각하면 쉽다.

먼저 온 순서대로 한사람 한사람씩 계산되는 과정이 '동기적 프로그래밍'이다.

여기서, 어떤 사람이 물건을 엄청 많이 샀다던가, 계산 중에 다른 물건을 집어온다거나 할 때, 뒤에 서있는 고객들은 그 문제의 사람이 계산을 마칠 때까지 기다리는 수밖에 없다.

 

이와 같이, 동기적으로 프로그래밍을 한다면 한 작업이 끝날 때까지 그 뒤의 어떤 작업도 진행되지 않기에, 화면 로딩, 통신 연결 등의 비효율을 높이고, 사용성 또한 급격히 떨어진다.

 

코드가 한 페이지에서 동기적으로만 일을 하게 된다면, 아마 페이지 최상단에 있는 것부터 하나씩 또 하나씩 로딩이 될 것이며, 설상가상 시작하면 중간에 물릴 수도 없으니, 화면을 로딩하는 중간에 다른 명령을 수행할 수도 없을 것이다.

만약 동영상 재생 한 페이지에서 하나씩하나씩 따로 로딩해서 동영상이 돌아가는데 꽤 오랜 시간이 걸리고, 그 시간 동안 다른 건 아무것도 클릭하지 못한다면 매우 불편할 것이다.

거기에 대한 해답이 바로 '비동기 프로그래밍'이다. 비동기적으로 코드를 실행하면 더 유동적으로, 더 효율적으로 많은 일을 할 수 있다!

동기적 일처리 방식 : 순차적으로 일을 스스로 끝내 나가는 방식 (한줄 한줄 쳐낸다)
비동기적 일처리 방식 : 해야 할 일을 위임하고 기다리는 방식 (동시다발적으로 업무를 수행하고 완료되는대로 loading되는 방식)

다시 정리하면, 유동적으로, 효율적으로 움직이게 하기 위해서는 비동기적 프로그래밍이 필요하다.

 

2. RxSwift 란

iOS의 비동기 프로그래밍에서, 나중에 들어오는 return값(Observable)나중에 데이터가 return해서 들어오면 처리(Subscribe) 하는 것을 간결하고 효율적으로 도와주는 프레임워크이다.

 

- 나중에생기는데이터<타입> = Observable<타입>

- 나중에생기면 = subscribe

 

3. RxSwift 사용 방법

1) 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법

    func downloadJson(_ url:String) -> Observable<String?> {
        //1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
        return Observable.create() { emitter in
            let url = URL(string: url)!
            let task = URLSession.shared.dataTask(with: url) { (data, _, err) in
                guard err == nil else {
                    emitter.onError(err!) //에러 호출
                    return
                }
                
                if let dat = data, let json = String(data: dat, encoding: .utf8){
                    emitter.onNext(json) //데이터 전달
                }
                emitter.onCompleted() //종료 -> Observable의 끝
            }
                
            task.resume()
                
            return Disposables.create() {
                task.cancel() //observable을 취소할 때 하는 동작들 정의
            }
        }
    }

 

2) Observable로 오는 데이터를 받아서 처리하는 방법

    @IBAction func onLoad() {
        editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)
        
        //* observable의 생명주기
        //create -> subscribe -> next -> complete / error -> disposed
        let ob = downloadJson(MEMBER_LIST_URL)

        let disposable = ob.subscribe { event in
            switch event {
            case let .next(json):
                DispatchQueue.main.async {
                    self.editView.text = json
                    self.setVisibleWithAnimation(self.activityIndicator, false)
                }
            case .completed:
                break
            case .error:
                break
            }
        }
        disposable.dispose() //Observable이 시행중이든 아니든 즉시 작업 취소
        
    }

 

* observable의 생명주기

  1. create
  2. subscribe
  3. onNext (데이터 전달)
  4. onCompleted / onError (실행 종료)
  5. disposed (동작이 끝난 Observable은 재사용할 수 없음 - 일회용임. 다시 subscribe해 동작을 시작해야 함)

 

* observable이 종료되는 시점

.completed, .error, .disposed

 

4. Sugar API - 연산자 (Operator)

3번의 긴 코드를 간결하게 만들어주는 친구!

        _ = downloadJson(MEMBER_LIST_URL)
		.subscribe { event in
            switch event {
            case let .next(json):
                DispatchQueue.main.async {
                    self.editView.text = json
                    self.setVisibleWithAnimation(self.activityIndicator, false)
                }
            case .completed: //완료되면 멈춤
                break
            case .error: //에러가 발생하면 멈춤
                break
            }
        }

위 코드를, 아래와같이 간결하게 작성할 수 있음

        
        //sugar API -> 위처럼 case를 다 나누지 않아도 onNext, onError만 가져와서 사용
        _ = downloadJson(MEMBER_LIST_URL)
            .subscribe(onNext: { json in
                DispatchQueue.main.async {
                    self.editView.text = json
                    self.setVisibleWithAnimation(self.activityIndicator, false)
                }
            })

 

다 외울수는 없으니, 그때그때 찾아보면 좋음

연산자 모아놓은 문서 : https://reactivex.io/documentation/operators.html

 

1) 자주 쓰는 연산자 모음

  • Observable.just("데이터") : 데이터 하나 전달할 때 사용하는 연산자
  • .from(["hello", "world"]) : 여러 데이터를 하나씩 전달할 때 사용하는 연산자
  • .subscribe(onNext: { ... } ) : next, completed, error, dispose 중 필요한 것만 파라미터로 사용 한다.
  • .observeOn() : 다음 줄부터 실행할 스레드를 지정
    • downstream(아래로) 영향을 준다
    • DispatchQueue.main.async { ... }를 사용하지 않고 한 줄로 해결
    • 매개변수로는 Scheduler타입을 넣는다.
    • 예를 들어 MainScheduler.instance, ConcurrentDispatchQueueScheduler(qos: .defalut) ..
  • .subscribeOn() : 맨 처음 시작할 스레드를 지정
    • 어디에서 호출하든 맨 처음 스레드를 지정함
    • upstream(위로) 영향을 준다
  • .map { ... },.filter { ... } ...
  • .buffer : 여러 데이터가 있을 때 지정된 갯수만큼 묶어서 내려보내줌
  • .scan : 직전 데이터와 새 데이터를 가지고 연산을 수행
  • .take(횟수) : 지정 횟수만큼만 수행
  • .debug() : Observable내에서 어떤 데이터가 전달되는지를 보여줌
  • merge : 데이터 타입이 같은 여러개의 Observable을 하나의 observable로 만듦
  • zip : 두 Observable의 데이터를 합쳐 쌍으로 만들어 보냄 ex. 1 2 3 4 5.. observable과 a b c d e observable -> 1a 2b 3c... observable
  • combinelatest : zip과 비슷하나, 쌍을 만들 데이터가 없을 경우 이전 데이터를 가지고 쌍을 만들어서 내려줌

 

2) 마블 다이어그램

  • 동그라미 = 데이터 
  • 네모 = 연산자
    • 색깔 : 쓰레드가 달라짐을 의미함
  • 가로 화살표 = Observable
  • 세로줄 = complete

 

ex) 

- array 데이터가 from operator를 거치면, Observable이 나오고, 데이터가 순서대로 전달이 되고나서 complete된다.

 

5. Subject

 Observable처럼 subscribe해 값을 받아올 수 있고, 외부에서 값을 컨트롤할 수도 있는 객체

https://reactivex.io/documentation/subject.html

 

1) 왜 쓰는가?

  • create 시점 뿐 아니라, 이미 만들어진 subject에 대해서도 값 변경 가능
  • Observable과의 차이점 : 생성된 이후에도 외부에서 값을 바꿀 수 있다. (데이터 주입 가능)
  • PublishSubjectBehaviorSubject를 가장 많이 사용한다.

2) Subject 종류

  • PublishSubject()
    • 자신을 subscribe한 객체에게 데이터를 내려줌
    • 이후 subscribe한 객체에게는 그 이후의 데이터를 내려줌
  • BehaviorSubject(기본값)
    • subscribe하면 기본값을 일단 내려주고 시작함
    • 이후 subscribe한 객체에게는 가장 최근의 데이터 + 그 이후의 데이터를 내려줌
  • AsyncSubject
    • subscribe한 객체들이 있어도 데이터를 내려주지 않다가
    • 자신이 complete되는 시점에 맨 마지막 데이터만 subscribe한 객체들에게 내려주고 끝남
  • ReplaySubject
    • 새롭게 subscribe한 객체에게 그동안 발생한 데이터 중 bufferSize 만큼의 최근 값들을 내려줌

💡 Stream 이란?

Observable의 input이 있고, subscribe하면서 output이 나오는 모든 일련의 흐름들을 스트림이라고 한다.

 

6. Rxcocoa란?

- RxSwift를 UIKit의 요소에 접목하여, RxSwift의 사용을 용이하게 할 수 있도록 도와주는 프레임워크

- UIKit요소.rx로 사용 가능

  • .bind(to: 바인더) : subscribe(onNext: { ... } ) 대신 사용
    • [weak self]를 사용하지 않아도 알아서 순환참조를 예방해줌
  .bind(to: totalPrice.rx.text)
  .bind(to: tableView.rx.items(cellIdentifier: cellID, cellType: MenuItemTableViewCell.self)){ index, item ,cell in                cell.price.text = "\(item.price)"            }
                cell.count.text = "\(item.count)"
                cell.title.text = item.name

 

 💡 RxDataSource

- 위와 같이 구현할 때

1) 데이터가 추가/수정/삭제로 인해 바뀔 때마다 reloadData를 하기때문에 데이터가 사라지는 애니메이션을 주지 못해 UX적으로 좋지 않음

2) section을 다루기 까다로움

- 위 2가지 불편한 점을 해결해주는 아이! 애니메이션도 관리할 수 있고, section을 관리할 수도 있음

 => 이 친구는 별도의 글로 따로 다뤄보는걸로!

 

 

reference 

https://limjs-dev.tistory.com/136

 

반응형