RxSwift 2탄 !! Observable에 대해 알아보쟈

2020. 5. 23. 03:31iOS/RxSwift

앞서 RxSwift 맛만보자!! 포스팅에서 비동기 작업을 도와주는 RxSwift의 간단한 동작을 보았습니다.

이제 이 RxSwift의 기능 2가지

  1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
  2. Observable로 오는 데이터를 받아서 처리하는 방법

를 한번 자세히 알아보도록 하겠습니다.

먼저 원하는 데이터를 Observable로 감싸기 위해서는 Observable.create()를 호출해주어야 합니다. 그리고 우리는 이제 이 Obervable로 감싼 데이터를 리턴해 주도록 하겠습니다.

    func downloadJson(_ url: String) -> Observable<String?>{

        //1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
        return Observable.create() // Obervable 객체 생성
    }

그리고 completion으로 원하는 데이터를 이 Obervable안에 emitter.onNext()의 인자로 넣어주게 됩니다. 그 다음 emitter.onCompleted()로 데이터 전송이 끝났음을 알려줍니다.

    func downloadJson(_ url: String) -> Observable<String?>{

        //1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
        return Observable.create() { emitter in // emitter를 통해 전송할 데이터를 관리할 수 있다.
            emitter.onNext("Hello") // onNext 를 통해 데이터 전송
            emitter.onNext("World")
            emitter.onCompleted()    // 데이터 전송이 끝남을 알려준다.

            return Disposables.create()
        }
    }

이제 Observable로 오는 데이터를 받아서 처리해주는 방법입니다.subscribe 함수는 데이터가 받아와 질때 실행되는 함수 입니다.

    // 2. Observable로 오는 데이터를 받아서 처리하는 방법
    @IBAction func onLoad() {
        self.editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)


        downloadJson(MEMBER_LIST_URL)
            .subscribe { event in
                switch event {
                case .next(let data):
                    self.editView.text = data
                    self.setVisibleWithAnimation(self.activityIndicator, false)
                case .completed:
                    break
                case .error(_):
                    break
                }
        }
    }

이 subscribe는 이제 event 값으로 데이터 처리 방법을 분기처리를 시작합니다. .next로 앞서 전송한 데이터는 이 분기처리에서 .next(let data) 로 도달하게 됩니다.

앞서 next로 "Hello" 와 "World" 데이터를 전송해 주었습니다. 따라서 이 .next로 두번 도달하게 됩니다.

또한 이 이벤트에는 error와 completed가 있는데 앞서 emitter.completed()를 호출해 주지 않으면 데이터 전송이 끝나도 completed가 호출되지 않습니다. 따라서 이 이벤트는 클로져가 종료가 되지않고 남아있으면서 순환참조 문제가 발생할 수 있습니다. 따라서 데이터 전송이 끝났을 때 completed를 호출해주어야 합니다.

이제 이런 이벤트 처리를 URL Session으로 직접 사용해 보면서 적용시켜 보겠습니다.

func downloadJson(_ url: String) -> Observable<String?>{

        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!) // 에러가 난다면 .onError()를 호출
                    return
                }
                
                if let dat = data, let json = String(data: dat, encoding: .utf8) {
                    emitter.onNext(json) // 데이터를 잘 받아와진 경우엔 .onNext()를 호출
                }
                
                emitter.onCompleted() // 데이터 전송이 완료되면 .onCompleted() 호출
            }
            
            task.resume()
            
            return Disposables.create() { // 이 객체는 이 Observable로 처리하는 작업을 중단 시킬수 있는 객체이다.
                task.cancel() // 따라서 이 작업이 중단될 경우 URLSession 작업 또한 중지시킨다.
            }
        }
        
    }

이렇듯 Observable은 하나의 생명주기를 가지게 됩니다.

1. Create 2. Subscribe 3. onNext 4. onCompleted / onError 5. Disposed

Create가 된다고 해서 데이터를 받아오기 시작하지 않으며 Subscribe에서 데이터를 받아오는 작업이 실행됩니다. 데이터를 전달해 주기 위해선 onNext를 통해 전달할 수 있고 전달 결과에 따라 onCompleted 또는 onError 로 처리가 됩니다.그리고 모든 작업이 완료되거나  정지시킬 경우 해당 Observable은 Disposed 상태가 됩니다.

이렇게 Disposed 상태가 되면 다시 실행시킬 수 없고, subscribe 함수를 다시 호출하여 데이터를 받아오는 작업을 실행시킬 수 있습니다.

    @IBAction func onLoad() {
        self.editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)
        
        
        let disp = downloadJson(MEMBER_LIST_URL)
            .subscribe { event in
                switch event {
                case .next(let data):
                    DispatchQueue.main.async { 
// 앞서 URLSession을 통해 completion을 넘겨주었기 때문에 해당 작업 또한 URLSession 쓰레드에서 작업이 실행된다.
// 따라서 main 쓰레드로 UI 변경 작업을 해주어야 한다.
                        self.editView.text = data
                        self.setVisibleWithAnimation(self.activityIndicator, false)
                    }
                    
                case .completed:
                    print("completed")
                    break
                case .error(_):
                    break
                }
        }
        //disp.dispose() subscribe를 통해 수행되는 작업을 직접 중지시키는 함수
    }
}

추가적으로 이러한 과정을 print 해주는 함수가 있습니다. 바로 debug()함수 입니다.

    @IBAction func onLoad() {
        self.editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)
        
        let disp = downloadJson(MEMBER_LIST_URL)
            .debug() //
            .subscribe { event in
                switch event {
                case .next(let data):
                	break
                case .completed:
                    break
                case .error(_):
                    break
                }
        }
    }
}

이렇게 create와 subscribe 사이에 debug() 함수를 호출해주면 다음과 같은 데이터가 콘솔창에 뜨게 됩니다.

2020-05-23 03:23:26.082: ViewController.swift:80 (onLoad()) -> subscribed
2020-05-23 03:23:27.086: ViewController.swift:80 (onLoad()) -> Event next(Optional("받아오는 데이터"))
2020-05-23 03:23:27.135: ViewController.swift:80 (onLoad()) -> Event completed
completed
2020-05-23 03:23:27.135: ViewController.swift:80 (onLoad()) -> isDisposed

이렇게 Observable 객체가 어떻게 사용이 되는지 이 데이터는 어떠한 생명 주기를 갖는지 알아보았습니다. 복잡한 구조를 갖고 있지만 하나씩 코드를 직접 처보면서 보다보면 충분히 이해가 가리라 생각됩니다 !!