티스토리 뷰

OpenSource/RxSwift

곰튀김님의 RXSwift 1교시

malrang-malrang 2022. 6. 26. 19:06

곰튀김님의 RXSwift 1교시

RxSwift는 Swift에 ReactiveX를 적용시켜 비동기 프로그래밍을 직관적으로 작성할 수 있도록 도와주는 라이브러리입니다.

RXSwift의 용도는 비동기로 처리되는 작업을 completionHandler(클로저)로 전달하는게 아니라 리턴값으로 전달하기위해 만들어진 유틸리티다!

RXSwift를 사용하기 위해서는 아래의 방법들을 익혀야한다!

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

Observavle

RxSwift에서 사용되는 Observavle 는 아래의 예시코드 형태로 구현되어있다.

위에서 설명한 비동기로 처리되는 작업을 클로저로 전달하지 않고 리턴값으로 전달하기 위해 사용된다!

예시코드를 보자!

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)
            }
        }
    }

@IBAction func onLoad() {
    editView.text = ""
    setVisibleWithAnimation(activityIndicator, true)

    downloadJson(MEMBER_LIST_URL) { json in
        self.editView.text = json
        self.setVisibleWithAnimation(self.activityIndicator, false)
        }
    }

Json파일을 다운로드하는 메서드와 onLoad 라는 버튼의 액션메서드 코드다.
버튼 누르면 onload() 메서드가 호출되어 동작하는 방식이다.

downloadJson() 에 파라미터로 completion(클로저)을 받아 내부의 글로벌 스레드 작업후 어떤 행동을 메인 스레드에서 동작하게 할지 넣어주어 사용했다!

요런방식이 기존에 사용하던 방식이다!

이제는 RXSwift의 Observable과 유사한 타입을하나만들어보자!
Observable 은 나중에 생기는 데이터라고 생각하면된다!

//나중에생기는데이터
class Observavle<T> {
    private let task: (@escaping (T) -> Void) -> Void

    init(task: @escaping (@escaping (T) -> Void) -> Void) {
        self.task = task
    }

    //나중에 데이터가 오면
    func subscribe(_ f: @escaping (T) -> Void) {
        task(f)
    }
}

위에서 만든 Observable을 사용해보자!

func downloadJson(_ url: String) -> Observable<String?> {
    return Observable() { 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(json)
            }
        }
    }
}

@IBAction func onLoad() {
    editView.text = ""
    setVisibleWithAnimation(activityIndicator, true)

    downloadJson(MEMBER_LIST_URL)
        .subscribe { json in
            self.editView.text = json              
            self.setVisibleWithAnimation(self.activityIndicator, false)
        }
}

요런 형태가 된다!
이제는 클로저를 사용하는것이아닌 리턴값을 활용하는 모습을 볼수있다!

그럼이제는 RXSwift에 정의되어있는 Observavle을 사용해보자!

func downloadJson(_ url: String) -> Observable<String?> {
    //Observable 을 생성할때는 create메서드를 사용한다.
    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 {
                //on.next 메서드를 사용해 클로저?를 실행한다.
                f.onNext(json)
            }
        }
        //create()메서드의 반환 타입은 Disposables이다.
        //그래서 비어있는 Disposables하나 만들어서 리턴해준것이다.
        return Disposables.create()
    }
}

@IBAction func onLoad() {
    editView.text = ""
    setVisibleWithAnimation(activityIndicator, true)

    downloadJson(MEMBER_LIST_URL)
    //subscribe 은 event를 처리할수있는데 next, error, completed 을 활용할수있다.
        .subscribe { event in
            switch event {
            case .next(let json):
                self.editView.text = json
                self.setVisibleWithAnimation(self.activityIndicator, false)
            case .error(_):
                break
            case .completed:
                break
            }
        }
}

직접 만들었던 Observable와 큰 차이는 없지만 좀더 옵션들이 추가된걸 볼수있다!

위에서 얘기한데로 RXSwift는 비동기로 처리되는 작업을 클로저로 전달하지 않고 리턴값으로 전달하기 위해 사용된다! 이런용도로 사용하기위해 만들어진 유틸리티 라고 볼수있다!

그외 다른 용도는 없다!

사용법은 Observable은 나중에 생기는 데이터인데 subscribe 즉 나중에 데이터가 오면 을 사용하면되고 그후 event가 오는데 event의 종류는 다음과 같다.

Observable event의 종류

  • next:
    • 데이터가 전달되었을때
  • completed:
    • 데이터가 전달되고 끝났을때
  • error:
    • 에러났을경우

위의 세가지경우 어떻게 행할것인지 작성해 넣어주면 된다!

위에 Observable의 create() 메서드를 사용한후 Disposable을 만들어 반환해주었는데 현재는 사용될 용도가 없기때문에 변수에 담아주지 않았다.

하지만 상수 혹은 변수에 담아주게되면 dispose()라는 메서드를 호출할수있게된다.

@IBAction func onLoad() {
    editView.text = ""
    setVisibleWithAnimation(activityIndicator, true)

    let disposable = downloadJson(MEMBER_LIST_URL)
    //subscribe 은 event를 처리할수있는데 next, error, completed 을 활용할수있다.
        .subscribe { event in
            switch event {
            case .next(let json):
                self?.editView.text = json
                self.setVisibleWithAnimation(self.activityIndicator, false)
            case .error(_):
                break
            case .completed:
                break
            }
        disposable.dispose()
    }
}

dispose()는 버린다는 뜻인데 dispose위에 작성된 switc구문의 작업이 끝나지 않았더라도 dispose()메서드를 호출하게되면 작업을 취소하게된다.

동작을 취소시킨다는뜻이다.

이제 아래의 예제 코드는 지금까지 방치해두었던 클로저의 값캡쳐로 인해 생기는 순환참조 문제를 해결하는 코드다!

[weak self] 키워드를 사용해 해결해도 되지만 다른방식으로 해결이 가능하다!

순환참조 문제가 발생하는 이유는 클로저의 값을 캡쳐하면서 RC(레퍼런스카운트)가 증가하게 되는데 캡쳐한 값을 다사용한후 제거해준다면 순환참조 문제를 해결할수 있게된다!

그럼 우리가 해주어야할것은 무엇인가?
Observable의 event종류에는 next, error, completed 가있었는데
completed를 사용해 사용이끝났다는것을 알려주면된다!

onNext()메서드 호출한후 onCompleted()메서드를 호출해주면된다!

그러면 클로저의 역할이 끝났다는것을 알게되고 클로저가 제거된다!

    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)
                    f.onCompleted()
                }
            }
            return Disposables.create()
        }
    }

그럼 맨처음 RXSwift를 사용하기위해 익혀야하는 것들에은 요녀석들이 있었는데!

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

위의 예시 코드들이 익혀야 하는 방법들이었다!

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

예시코드

기본적인 형태

func downloadJson(_ url: String) -> Observable<String?> {
 return Observable.create() { emitter in
    //여기 예시처럼 onNext를 2번 사용할경우 사이클이2번 돈다고 생각하면된다!
    //그러면 Observable로 오는 데이터를 처리할때 next의 값이 2번째 값으로 덮어 씌워지게된다!
     emitter.onNext()
     emitter.onNext()
     emitter.onCompleted()

     return Disposables.create()
 }   
}

위에서 사용한 예시 코드

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

    //요부분이 비동기로 생기는 데이터를 Observable로 감싸서 리턴하는 방법에 해당된다.
        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)
                    f.onCompleted()
                }
            }
            return Disposables.create()
        }
    }

Observable을 활용해 좀더 수정한 코드(URLSession의 dataTask메서드 사용)

    func downloadJson(_ url: String) -> Observable<String?> {
        return Observable.create { emitter in
            let url = URL(string: url)!
            let task = URLSession.shared.dataTask(with: url) { data, _, 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()
            }
        }
    }

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

기본적인 형태

@IBAction func onLoad() {
        editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)

        let disposable = downloadJson(MEMBER_LIST_URL)
            .subscribe { event in
                switch event {
                case .next:
                    break 
                case .error:
                    break
                case .completed:
                    break
                }
            }
    //필요에 따라 작업을 취소시킬수있다.
    disposable.dispose()
    }

위에서 사용한 예시 코드

@IBAction func onLoad() {
        editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)

        //요부분이 Observable로 오는 데이터를 받아서 처리하는 방법 에 해당된다.
        _ = downloadJson(MEMBER_LIST_URL)
            .subscribe { event in
                switch event {
                case .next(let json):
                    self.editView.text = json
                    self.setVisibleWithAnimation(self.activityIndicator, false)
                case .error(_):
                    break
                case .completed:
                    break
                }
            }
    }

Observable을 활용해 좀더 수정한 코드

@IBAction func onLoad() {
        editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)

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

Observable의 생명주기

  1. create
  2. subscribe
  3. onNext

--- 끝 ---
4. onCompleted / onError
5. disposed

순으로 이루어지며 동작시기는 2번 subscribe 에 동작하기 시작한다!
create를 사용해 만들어두었다 해도 실행되진 않는다!

사용이끝난 Observable 사용시 주의할점!

Observable을 사용시 onCompleted, onError, 혹은 dispose()메서드를 호출했다면 사용이끝난 subscribe는 재사용이 불가능하다!

새로운 subscribe을 만들어주어 사용하는것은 가능하다!

@IBAction func onLoad() {
    editView.text = ""
    setVisibleWithAnimation(activityIndicator, true)

    let ob = downloadJson(MEMBER_LIST_URL)

    let disp = ob.subscribe { event in
            switch event {
            case .next(let json):
                DispatchQueue.main.async {
                    self.editView.text = json
                    self.setVisibleWithAnimation(self.activityIndicator, false)
                }
            case .error(_):
                break
            case .completed:
                break
            }
        }
    disp.dispose()

    //요부분 문제생김!
    ob.subcribe {...}
}

Sugar API

지금까지 Observable의 개념과 사용법을 알아봤다!
하지만 너무길고 귀찮다..!
이를 해결하기위해 추가적인 API들이 더 존재한다!

기본적인 사용방법에서는 아래의 형태로 사용했었다.

func downloadJson(_ url: String) -> Observable<String?> {
 return Observable.create() { emitter in
     emitter.onNext("Hello World")
     emitter.onCompleted()
     return Disposables.create()
 }   
}

그치만 데이터 하나를 Observable로 감싸서 리턴 시켜주기엔 기본방법을 사용하면 많은 코드를 작성해야한다.

Observable에는 위의 코드를 함축하고있는 just()메서드가 존재하며 이를이용하면 훨씬더 간결하게 축약할수 있게된다.

요런녀석들을 Operator라고 부른다.

Operator

데이터를 Observable에서 subscribe로 데이터가 전달되는 중간에 데이터를받아 바꿔치기하는 그런애들을 Operator라고 한다.

Operator의 종류는 많다.

  • Creating Observables(생성관련 Operator)
  • Transforming Observables (데이터 변형 관련 Operator)

등등이 있다. 다른것들 찾아보려면 아래의 링크로!
오퍼레이터의 종류

Creating Observables Operator

just()

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

하지만 just는 데이터를 1개밖에 못보낸다.
그렇기때문에 데이터를 여러개 보내고싶다면 배열을이용하면된다!

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

배열에있는거 한개씩보내고싶을때는 from을 쓰면된다!

from()

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

요렇게 하면 배열에담긴것들 한개씩 보내준다!

여기서 데이터를 보내준다라는 의미는 아래의 코드에서 event의 next로 2번들어온다는뜻이다.

사이클 2번돌겠죠?

@IBAction func onLoad() {
        editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)

        let disposable = downloadJson(MEMBER_LIST_URL)
            .subscribe { event in
                switch event {
                case .next(let t):
                    print(t)
                case .error:
                    break
                case .completed:
                    break
                }
            }
    }

프린트 구문으로 찍어보면 요렇게 배열내부의 값을 한개씩 꺼내서 next로 넘겨주게된다!

사이클이 2번돌게되니까 next가2번!
즉 아래처럼 프린트구문이 찍히게된다!

subscribe()에서 onNext만 사용하게된다면 switch를 사용하지않고 좀더 간결하게 사용할수있다!

@IBAction func onLoad() {
        editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)

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

요렇게 사용하지않는애들은 제외하고 축약이 가능하다!

onNext말고 onCompleted도 사용하고싶은데? 그렇다면 요렇게!

```swift
@IBAction func onLoad() {
        editView.text = ""
        setVisibleWithAnimation(activityIndicator, true)

        let disposable = downloadJson(MEMBER_LIST_URL)
            .subscribe(onNext: { print($0) }, onCompleted: { print("Completed") })
    }

뒤에더 추가해주면된다!(에러도 똑같음)

observeOn()

아래는 기존의 코드를 위의 방식으로 수정했다.(onNext만 사용함)

@IBAction func onLoad() {
    editView.text = ""
    setVisibleWithAnimation(activityIndicator, true)

    downloadJson(MEMBER_LIST_URL)   
        .subscribe(onNext: { json in
            DispatchQueue.main.async {
                self.editView.text = json
                self.setVisibleWithAnimation(self.activityIndicator, false)
            }
        })
}

뷰에서 보여주는 부분은 메인 스레드에서 작업해줘야 하기 때문에 DispatchQueue를 사용했는데 중첩되서 거슬린다.

요것도 좀더 보기 좋게 변경할수있는 메서드가 있는데 observeOn()요녀석이다.

@IBAction func onLoad() {
    editView.text = ""
    setVisibleWithAnimation(activityIndicator, true)

    _ = downloadJson(MEMBER_LIST_URL)   
        //요녀석! 메인스레드에서 작업해줘! 라고 하는거다.
        .observeOn(MainScheduler.instance)
        .subscribe(onNext: { json in
            self.editView.text = json
            self.setVisibleWithAnimation(self.activityIndicator, false)
        })
}

observeOn() 요녀석을 사용하면 데이터를 (메인스레드에서 작업을하게한다던지?) 변경해주는데 이런 녀석들을 Operator(오퍼레이터) 라고 부른다.

데이터가 Observable에서 subscribe로 데이터가 전달되는 중간에 데이터를받아 바꿔치기하는 그런애들을 Operator라고 한다.

map과 filter를 사용하는 예제 코드

@IBAction func onLoad() {
    editView.text = ""
    setVisibleWithAnimation(activityIndicator, true)

    _ = downloadJson(MEMBER_LIST_URL)  
        //데이터를 받아서(현재는  String타입의 데이터)
        //count 즉 Int 값으로 변경해준다.
        .map { json in json?.count ?? 0 }

        //map을 거쳐 반환된값이 0보다 큰놈만 걸러줄거야!
        .filter { cnt in cnt > 0 }

        //위에서 걸러진놈을 String타입으로 변경할거야!
        .map { "\($0)" }

        //위의 데이터는 메인스레드에서 작업하게 할거야!
        .observeOn(MainScheduler.instance)

        //맨처음 즉 downloadJson메서드를 글로벌스레드 에서 작업하라고하는 Operator다.
        //위치가 어디에있든 상관없다.
        .subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default))
        .subscribe(onNext: { json in
            self.editView.text = json
            self.setVisibleWithAnimation(self.activityIndicator, false)
        })
}
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/07   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30 31
글 보관함