RxSwift 입문기

영점·2022년 1월 1일
0

RxSwift

목록 보기
1/1

노션에 따로 기록해두었던 RxSwift의 개념을 Velog에 옮기기로 했다.

시작하기 전에..

영상과 여러 자료를 바탕으로 정리해두긴 했지만, 틀린 내용이 있을 수도 있습니다.
굉장히 중구난방으로 순서가 되어있습니다.. 피드백 환영합니다 🙂

솔직히 자신이 영어 독해를 잘한다 싶으면 공식문서만 보셔도 이해는 가능합니다.
못한다고 해도 공식문서에 있는 그림은 이해를 할 수 있어야 한다.

RxSwift? 그게 뭔데? 🤔

RxSwift란 비동기적으로 생기는 데이터를 completion 같은 closure로 전달하는게 아니라 return 값으로 전달하기 위해서 만들어진 유틸리티 이다.

여기서 말하는 비동기란..
동기 방식은 서버에서 요청을 보냈을 때 응답이 돌아와야 다음 동작을 수행할 수 있는 것을 말하고,
비동기 방식은 요청을 보냈을 때 응답 상태와 상관없이 다음 동작을 수행 할 수 있는 것을 말한다.

//optional 일 경우는 default 이므로 명시안해도 된다. 
//((String?) -> Void)?)라면 @escaping이 필요없다는것이다.
func downloadJson(_ url : String, _ completion: @escaping (String?) -> Void) {
	DispatchQueue.global().async {
            let url = URL(string: MEMBER_LIST_URL)!
            let data = try! Data(contentsOf: url)
            let json = String(data: data, encoding: .utf8)
            DispatchQueue.main.async {
                completion(json)
            }
        }
 }

👉🏻 해당 방식이 RX가 없는 DispatchQueue를 이용한 비동기 처리 방식이다.
👉🏻 비동기로 생긴 데이터를 어떻게 리턴값으로 만들지? 라는 고민을 해결해준게 RX이다.

//예시 ( 주석값은 RxSwift를 적용했을 때를 나타낸다 )
class 나중에생기는데이터<T> { //-> class Observable<T>
	private let task: (@escaping (T) -> Void) -> Void
    init(task: @escaping (@escaping (T) -> Void) -> Void) {
    	self.task = task
    }
    func 나중에오면(_ f: @escaping (T) -> Void){ //-> func subscribe
    	tast(f)
    }
}

...

func downloadJson(_ url : String -> 나중에생기는데이터<String?>) 
	return 나중에생기는데이터(){ f in
    	f(json)
    }

👉🏻 이런식으로 한다면 원하는 동작을 그대로 처리할 수 있으면서 나중에 생기는 데이터를 completion을 쓰지않고 리턴 값으로 처리하는 방식이 된다.

본격적으로 예제 접해보기~ Observable!

먼저 코드부터 ~

...
//RxSwift : Observavle, onNext
//1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
func downloadJson(_ url : String)-> Observable<String?> {
        return Observable.create() { f in
            DispatchQueue.global().async {
                let url = URL(string: MEMBER_LIST_URL)!
                let data = try! Data(contentsOf: url)
                let json = String(data: data, encoding: .utf8)
                
                DispatchQueue.main.async {
                    f.onNext(json)
                }
            }
            return Disposables.create()
        }
    }

//RxSwift : .subscribe, event
@IBAction func onLoad() {
        editView.text = ""
        self.setVisibleWithAnimation(self.activityIndicator, true)
        
	//2. Observable로 오는 데이터를 받아서 처리하는 방법
        downloadJson(MEMBER_LIST_URL)
            .subscribe { event in
                switch event{
                case let .next(json):
                    self.editView.text = json
                    self.setVisibleWithAnimation(self.activityIndicator, false)
                    break
                case .completed:
                    break
                case .error(_):
                    break
                }
        }
    }
//데이터가 전달 될때는 .next
//데이터가 다 전달 됬을 때는 .completed

위의 한글로 설정해둔 클래스명으로 정리를 해보자면 이렇다.

Observable : 나중에 생기는 데이터 class
Observable.create() : 나중에 생기는 데이터 만들때
Subscribe : 나중에 오면
onNext : f에 바로 전달하는게 아닌 onNext로 전달
event : subscribe 하면 나중에 event 가 온다.
Disposable 로 작업 취소(create 했으면 Disposable 를 리턴 해야함) disposable.dispose()로 작업을 바로 취소할 수 있다. onLoad()에서 버튼을 누르면 다운을 받아라!했는데 바로 취소하는 셈이다.

그리고 Observable의 순서(생명주기)는 이렇다.

  1. Create ( Create를 만들어놔도 Subscribe가 없으면 실행되지 않는다 )
  2. Subscribe
  3. onNext / onError ( -> 여기서 에러가 나도 종료 )
  4. onCompleted ( -> 종료 )
  5. Disposed ( -> 취소로 인한 종료 )

⚠️ 동작이 끝난 Observavle은 재사용이 불가능하다.

위에서 주석처리 한 것처럼 RxSwift의 사용법은 두가지 이다.
1. 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법
2. Observable로 오는 데이터를 받아서 처리하는 방법

첫번째로, 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법은 이렇다.
1. Observable.create() 한다.
2. onNext로 데이터를 받는다.
3. 데이터 다 받으면 onCompleted()
4. 마지막에 Disposables.create()

func downloadJson(_ url : String)-> Observable<String?> {
        
        return Observable.create(){ emitter in //emitter : 데이터
            emitter.onNext("Hello") //데이터를 받는다. 여러개를 받을 수 있음!!
            emitter.onNext("World")
            emitter.onCompleted() //다받으면 onCompleted()
            
            return Disposables.create() //그냥 Disponsable말고 Disposables.create()
        }
    }

//실행하면 Hello, World가 각각 아래의 event로 들어간다.

두번째로, Observable로 오는 데이터를 받아서 처리하는 방법은 이렇다.
1. observable 을 만든다.
2. subscribe로 이벤트를 받는다.
3. 이벤트의 next, error, complete 을 처리한다
4. 필요에 따라 취소시킨다. ( → disposable.dispose() )

let observable = downloadJson(MEMBER_LIST_URL)
        let disposable = observable.subscribe{ event in
            switch event{
            case .next(let json):
                self.editView.text = json //1차로 Hello, 2차로 World가 들어온다.
								self.setVisibleWithAnimation(self.activityIndicator, false)
            case .error(let err):
                break
            case .completed:
                break
            }
            
        }
        disposable.dispose() // 로 필요에 따라 취소시킬 수 있다.

//최종적으로는 World만 찍힌다. 덮어씌워진것이므로 마지막에 들어온것만 뜸!!

위 두개는 이해를 쉽게 하기 위한 예제이다.
본격적으로 만들게 되면 이렇게 나온다.

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, _ ,error) in

                //error는 nil 이여야 하닌데 아닐땐 onError
                guard error == nil else{
                    emitter.onError(error!)
                    return
                }

                //데이터가 제대로 왔다면 onNext로 Json 접근
                if let data = data , let json = String(data: data, encoding: .utf8){
                    emitter.onNext(json)
                }

                //다 받았을 경우 끝 ( Observavle의 끝 )
                emitter.onCompleted()

            }
            task.resume()
            
            //중간에 cancel 하면 task를 Cancel 한다.
            return Disposables.create() {
                task.cancel()
            }
        }
    }
    
let observable = downloadJson(MEMBER_LIST_URL)
        let disposable = observable.subscribe{ event in
            switch event{
            case .next(let json):
                DispatchQueue.main.sync {
			self.editView.text = json 
										  self.setVisibleWithAnimation(self.activityIndicator, false)
                                          }
            case .error(let err):
                break
            case .completed:
                break
            }
            
        }
        disposable.dispose()

예제로 본 것처럼 event에는 next, error, completed가 있다.
( 여기서 switch-case문이 순환참조상태이다. 순환참조의 설명은 생략. )

Operator!

하지만 위의 예제는 길다. 해당 코드를 더 짧게 쓸 수 있는? 기본 사용법에 귀찮은 것을 없애주는 친구가 있는데, 그게바로 Opreator이다.
처음에 설명했듯이, ReactiveX 공식문서에 Opreator의 모든 종류들에 대한 설명이 전부 세세하게 적혀있다. 🙂
링크텍스트

⚠️ Operator는 순서가 굉장히 중요하다. 순서에따라 출력 결과가 달라지기 때문이다. ⚠️
쉬운 이해를 위해 아까 작성했던 우리의 영원한 친구 Hello world에서 이렇게 줄일 수 있다.

func downloadJson(_ url : String)-> Observable<String?> {
        
     return Observavle.just("Hello World")
}

just는 데이터를 하나밖에 보내지 못한다. 만약 두개를 보내고 싶으면 배열을 이용한다.

func downloadJson(_ url : String)-> Observable<[String?]> {
        
     return Observavle.just(["Hello", "World"])
}

만약 하나씩 하나씩 보내고 싶다면 from을 사용한다.

func downloadJson(_ url : String)-> Observable<String?> {
     
		 //Hello 한 번 내려가고 World 한 번 내려가고 ( 하나씩 내려감 )
     return Observavle.from(["Hello, World"]) 
}

코드 줄이기

아까 subscribe로 다시 돌아가보면, switch-case문에 있는 error나 completed는 해당 코드에서 사용할 일이 없는 것을 알 수 있다.

let observable = downloadJson(MEMBER_LIST_URL)
        _ = observable.subscribe { event in
            switch event{
            case .next(let json):
                DispatchQueue.main.sync {
			self.editView.text = json 
										  self.setVisibleWithAnimation(self.activityIndicator, false)
								}
            case .error(let err):
                break
            case .completed:
                break
            }
            
        }
        disposable.dispose()

안쓰는데 왜 적어야해?
그래서! onNext인 경우, 이렇게 한 줄로 작성이 가능하다!

_ = downloadJson(MEMBER_LIST_URL) {
	.subscribe(onNext : {print($0)})
} 

//만약 completed도 받고싶다면
_ = downloadJson(MEMBER_LIST_URL) {
	.subscribe(onNext : {print($0)}, onCompleted : {print("Com")})
} 

//error도 받고싶다면
_ = downloadJson(MEMBER_LIST_URL) {
	.subscribe(onNext : {print($0)}, 
    		onCompleted : {print("Com")}, 
		onError : { error in print(error)})
} 

Disposable

앞서 설명했던 disposable.dispose()의 그 disposable이다.
disposable은 리소스를 해제할 때 사용한다. 자동차로 비유하자면 브레이크 느낌이랄까?

//취소 ( observer 로직 끝 )
disposable.dispose()

//취소 ( viewdidAppear 내부 )
disposable.forEach{$0.dispose()}

//화면에 여러개의 취소시켜야 하는 작업이 있을 경우
 var disposable : [Disposable] = []
 let d = Observable.zip(jsonObservable,helloObservable){$1 + "\n" + $0!}
            .observeOn(MainScheduler.instance) // 메인쓰레드로 바뀌고
            .subscribe ( onNext: {json  in
                self.editView.text = json
                self.setVisibleWithAnimation(self.activityIndicator, false)
        })
 disposable.append(d)

화면에 여러개 취소도 sugar api가 있다. DisposeBag()이라고 담는가방이라고 생각하면 된다.
DisposeBag를 사용하면..!

...
var disposable = DisposeBag()

let d = Observable.zip(jsonObservable,helloObservable){$1 + "\n" + $0!}
            .observeOn(MainScheduler.instance) // 메인쓰레드로 바뀌고
            .subscribe ( onNext: {json  in
                self.editView.text = json
                self.setVisibleWithAnimation(self.activityIndicator, false)
        })
        
disposable.insert(d)

//만약 마지막 코드도 귀찮다! 싶으면 이렇게도 가능하다.
...
var disposeBag = DisposeBag()

Observable.zip(jsonObservable,helloObservable){$1 + "\n" + $0!}
            .observeOn(MainScheduler.instance) // 메인쓰레드로 바뀌고
            .subscribe ( onNext: {json  in
                self.editView.text = json
                self.setVisibleWithAnimation(self.activityIndicator, false)
        })
        .disposed(by: disposeBag)

노션에 정리해두었던 개념은 이렇다.
일단 옮겨놓기만 했고, 코드도..좀 줄간격이 이상한데.. 일단 올리고 내일해야겠다.
정교한 정리와 퀄리티업은 자료를 더 찾으면서 하기로.. 🥲

profile
일단 배운내용은 적어두기

0개의 댓글