티스토리 뷰

TIL

[TIL] 2022 / 10 / 24 - Rx

희철 2022. 10. 24. 23:32

Collection타입들은 sequence 프로토콜을 채택하고 있음

-> 한 번에 하나씩 단계별로 진행할 수 있는 값 목록 

-> 반복문 생각하면될듯

 

Observable과 Observer

 

옵저버블은 이벤트를 보내고, 옵저버는 이벤트를 처리한다고 생각

 

youtube를 예시로 들어보면, 영상을 아무리 많이 올려도 구독하는 사람이 없으면 볼 사람이 없음

 

처리하는 이벤트가 영상 시청이라고 생각하면 될듯

 

 

Infinite Observable Sequences, Finite Observable Sequences

 

말그대로 무한과 유한

 

무한은 UI같은거 생각하면될듯.

-> 데이터에 따라 UI변경이 일어나는건 계속해서 일어날 수 있음

 

유한은 파일 다운로드 생각하면됨.

-> 1기가 파일을 다운받을때, 한 번에 다운되는게 아니라 점진적으로 다운이 됨(10% - 20% - ...)

-> 그리고 다운이 끝나면 해당 다운 이벤트는 끝나는 거임

 

 

Observable

 

 

rx에선 세 가지 생각

 

next -> 이벤트를 emit. 예를 들어, 점진적인 다운로드 등

completed -> 성공. 다운 완료 => 이미지뷰 이미지 바꾸기 등의 것들을 하면 될듯

error -> 실패. 에러 => alert같은걸로 실패했다고 띄우기

 

complete와 error는 지속적으로 emit되지않고 한 번만

 

complete와 error는 항상 마지막에 호출

 

complete와 error는 동시에 호출될 수 없음

 

complete와 error가 실행될 수 있다면 유한한 시퀀스라고 생각해도됨.

 

시퀀스가 종료되는 시점에 dispose

 

옵저버와 옵저버블 연결하는 걸 subscribe라고 생각하면됨

-> subscribe가 없으면 이벤트가 아무리 emit되어도 처리할 수가 없음.

 

rx에는 다양한 오퍼레이터가 존재하는데 다 너무 많아서 자주 쓰는 것만 알면될듯

 

 

Disposable

 

 

subscribe중인 Stream을 원하는 시기에 처리할 수 있게 도와줌

 

모든 Observable은 Disposable을 return 하는데, Stream을 종료하고 실행되던 시퀀스를 동료

-> rx코드에서 마지막에 dispose있음

 

next이벤트가 무한히 emit된다면 dispose되지 않음

 

메모리 정리에도 도움이됨.

 

 

 

Rx 사용

 

 

TableView

 

        simpleTableView.register(UITableViewCell.self, forCellReuseIdentifier: "Cell")
        
        let items = Observable.just([
            "First Item",
            "Second Item",
            "Third Item"
        ])
        
        items
            .bind(to: simpleTableView.rx.items) { (tableView, row, element) in
                let cell = tableView.dequeueReusableCell(withIdentifier: "Cell")!
                cell.textLabel?.text = "\(element) @ row \(row)"
                return cell
            }
            .disposed(by: disposeBag)
        
        //modelSelected는 didselected라고 생각
        simpleTableView.rx.modelSelected(String.self) //이상태에서는 에러나 컴플리트 발생할 수 없음
        //섭스크라이브는 성공한 케이스, onNext라는 매개변수가 생략된거, dispose는 메모리 삭제할때
        //            .subscribe { value in
        //                print(value)
        //            } onError: { error in
        //                print("error")
        //            } onCompleted: {
        //                print("completed")
        //            } onDisposed: {
        //                print("disposed")
        //            }
        //            .disposed(by: disposeBag)
            .map { data in
                "\(data)를 클릭"
            }
            .bind(to: simpleLabel.rx.text) //onNext이벤트밖에 없는거
            .disposed(by: disposeBag)

내부에 다 구현이 되어있어서 따로 프로토콜을 채택해서 구현하지 않아도 됐음

 

just로 특정 값을 emit

 

보통 subscribe와 bind를 사용하는데, 무조건 성공하고 에러가 없는 경우(버튼 클릭 등)은 bind로 사용하는 듯

-> subScribe에서 onNext만 쓰는 것과 동일하다고 생각하면됨

-> 맨 위의 클로저가 onNext인데 생략된거임.

 

disposed를 통해 메모리를 관리

-> 항상 마지막에 씀

 

modelSelected는 기존의 didSelectRowAt을 생각하면됨

-> items의 요소를 사용할건데 String이므로 String.self가 들어간 것

 

map으로 데이터를 변화시킴

-> 이부분이 없으면 눌렀을때 그냥 items의 요소만 출력될텐데, map으로 인해서 '를 클릭'부분까지 추가

 

마지막 bind로 simpleLabel의 text에 표시

-> simpleLabel.text와 simpleLabel.rx.text는 다름. 그래서 rx를 사용할떈 신경써주기

 

 

 

PickerView

 

    func setPickerView() {
        let items = Observable.just([
            "영화",
            "애니메이션",
            "드라마",
            "기타"
        ])
        
        items
            .bind(to: simplePickerView.rx.itemTitles) { (row, element) in
                return element
            }
            .disposed(by: disposeBag)
        
        simplePickerView.rx.modelSelected(String.self)
        //bind로 했을때는 배열로 들어가는걸 확인해야함
//            .map { "\($0)"}
//            .subscribe(onNext: { value in
//                print(value)
//            })
//            .disposed(by: disposeBag)
            .map { $0.description }
            .bind(to: simpleLabel.rx.text)
            .disposed(by: disposeBag)
    }

 

마찬가지로 just로 값을 emit하는 형태

 

items의 bind에서 row가 아닌 pickerView에는 items 요소들의 string을 띄울 것이므로 element bind

 

테이블뷰와 같이 modelSelected로 pick되면 레이블에 텍스트를 띄움

 

 

 

Switch

 

    func setSwitch() {
        //just와 of으 ㅣ결과는 같음
        //just와 of는 오퍼레이턴데 비교하기좋음
        Observable.of(false) // 빌드시 값 false로 주는거
            .bind(to: simpleSwitch.rx.isOn)
            .disposed(by: disposeBag)
    }

just와 of는 같다고 봐도됨

 

근데 of는 여러개의 값을 줄 수 있는데, 하나의 값인 경우에는 just와 같음

 

그래서 위의 경우엔 switch의 초기값을 주는 코든데 하나의 값이 false만 주므로 just를 사용하는게 나은듯

 

 

 

텍스트필드

 

 

    func setSign() {
        //ex. 텍스트필드1, 2 -> 레이블에 보여줌(옵저버블은 텍스트필드, 옵저버는 레이블) 두 가지를 관찰해서 하나의 레이블에 표시. 레이블은 실패할일없으니까 bind
        Observable.combineLatest(signName.rx.text.orEmpty, signEmail.rx.text.orEmpty) { value1, value2 in //rx에서 알려주느 text는ㄴ 옵셔널타입. orEmpty는 옵셔널해제되고 nil일때는 emptyString으로 반환
            "name은 \(value1)이고, 이메일은 \(value2)입니다."
        } //소스중에 하나라도 바뀌면 이벤트 감지, collection에는 소스 넣으면되는듯
        .bind(to: simpleLabel.rx.text)
        .disposed(by: disposeBag)
        
        signName.rx.text.orEmpty //여기까진 string. 데이터의 흐름 stream을 바꾼다 => UITextField -> Reactive -> String? -> String -> Int -> bool
            .map { $0.count } //int형으로 변환
            .map { $0 < 4 } //Bool타입으로
            .bind(to: signEmail.rx.isHidden, signButton.rx.isHidden) // isHidden이 bool타입이라. 옵저버 여러개 가능
            .disposed(by: disposeBag)
        
        signEmail.rx.text.orEmpty
            .map { $0.count > 4 }
            .bind(to: signButton.rx.isEnabled)
            .disposed(by: disposeBag)
        
        signButton.rx.tap //tap은 touchupinside라고 생각
            .subscribe { _ in //bind가 아닌이유는 탭이라고 호출하면 컨트롤이벤트가 Void라서 bind해줄만한건 따로없음. 즉, 데이터 스트림을 바꾸지않는이상 bind를 해주지않음
                self.showAlert()
            }
            .disposed(by: disposeBag)
    }

 

combineLatest로 받은 파라미터중 하나라도 변하면 이벤트 감지

 

orEmpty -> 옵셔널 해제하고, nil인 경우엔 empty string 반환

 

signName의 텍스트도 map으로 타입을 바꿔주는 것

UITextField -> Reactive(rx) -> String? -> String -> Int -> Bool

 

bind로 이메일 텍스트필드의 isHidden관리

 

button에서의 tap은 touchUpInside라고 생각하면됨

-> 여기서 bind를 안쓴건 탭에 대한 컨트롤이벤트가 Void라서 딱히 만질 데이터가 없음

 

 

repeat

 

 

특정 요소를 반복

 

take를 이용해 횟수를 정할 수도 있음

-> 네트워킹 같은 로직에서 몇 번 실패하면 alert띄우기 등 가능

        Observable.repeatElement("123") //특정 요소 반복. 빌드하지말기. take로 반복수 조절가능
            .take(1) //약간 몇번 시도하고 안되면 에러로 보내는등 행동할수있음
            .subscribe { value in
                print("repeat - \(value)")
            } onError: { error in
                print("repeat - \(error)")
            } onCompleted: {
                print("repeat completed")
            } onDisposed: {
                print("repeat disposed")
            }
            .disposed(by: disposeBag)

 

 

Interval

 

 

//화면전환돼도 얘는 계속 살아있기떄문에 수동으로 dispose가 동작하게 해야함. complete나 error가 뜨면 자동으로 호출되는데 안뜨면 수동으로해야함
        let intervalOb = Observable<Int>.interval(.seconds(1), scheduler: MainScheduler.instance)
            .subscribe { value in
                print("interval - \(value)")
            } onError: { error in
                print("interval - \(error)")
            } onCompleted: {
                print("interval completed")
            } onDisposed: {
                print("interval disposed")
            }
            //.disposed(by: disposeBag)
        
        DispatchQueue.main.asyncAfter(deadline: .now() + 5) {
            intervalOb.dispose()
        }

위의 코드는 1초마다 print가 실행되는 코드

 

근데 dispose되지않으면 계속해서 살아있음

-> 수동으로 dispose가 동작하게해야함

 

 

just, of, from

 

 

let itemsA = [3.3, 4.0, 5.0, 2.0, 3.6, 4.8]
        let itemsB = [2.3, 2.0, 1.3]
        
        Observable.just(itemsA) //element에 대한걸 하나만 받을 수 있음
            .subscribe { value in
                print("just - \(value)")
            } onError: { error in
                print("just - \(error)")
            } onCompleted: {
                print("just completed")
            } onDisposed: {
                print("just disposed")
            }
            .disposed(by: disposeBag)
        Observable.of(itemsA, itemsB) //of는 여러개를 묶어서 전달가능. 그래서 객체가 하나면 just와 차이업승ㅁ
            .subscribe { value in
                print("of - \(value)")
            } onError: { error in
                print("of - \(error)")
            } onCompleted: {
                print("of completed")
            } onDisposed: {
                print("of disposed")
            }
            .disposed(by: disposeBag)
        
        Observable.from(itemsA) //하나씩 순환
            .subscribe { value in
                print("from - \(value)")
            } onError: { error in
                print("from - \(error)")
            } onCompleted: {
                print("from completed")
            } onDisposed: {
                print("from disposed")
            }
            .disposed(by: disposeBag)

 

말했듯이 of는 여러 개를 묶어서 전달할 수 있음

 

그래서 객체가 하나면 just와 차이없음.

 

from은 하나씩

 

 

 

range

 

    func rangeTest() {
        Observable.range(start: 1, count: 10)
            .subscribe { print($0) }
            .disposed(by: disposeBag)
    }

 

for문이라고 생각해도 될듯

 

 

generate

 

    func generateTest() {
        Observable.generate(
                initialState: 10,
                condition: { $0 < 15 },
                iterate: { $0 + 1 }
            )
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
    }

while문이랑 동일함

 

initialState의 값부터 condition이 false인 경우까지 iterate에 해당하는 코드를 실행

-> 위의 코드는 10부터 15미만까지 1씩 증가해서 출력

 

 

deffered

 

    func defferedTest() {
        var count = 1
        
        let deferredSequence = Observable<String>.deferred {
            print("Creating \(count)")
            count += 1
            
            return Observable.create { observer in
                print("Emitting...")
                observer.onNext("🐶")
                observer.onNext("🐱")
                observer.onNext("🐵")
                return Disposables.create()
            }
        }
        
        deferredSequence
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
        
        deferredSequence
            .subscribe(onNext: { print($0) })
            .disposed(by: disposeBag)
    }

옵저버블의 생성을 지연

 

subscribe되기전까지 생성하지않음

 

언제 쓸 지는 모르겠음.

 

 

 


 

과제하면서 알게된거

 

 

subscribe에서 onNext가 있는 상태에서 작성한거랑 없는 상태에서 클로저 작성한거랑 다름

-> 생략된게 아니라 다른 메서드

-> 없는 걸로 하면 Next까지 같이  출력

 

pickerView에서 int와 item이 있는데 int는 정확히 뭔지 모르겠음.

row같은데 제대로 출력되지도 않음

'TIL' 카테고리의 다른 글

[TIL] 2022 / 10 / 26 - Rx  (0) 2022.10.26
[TIL] 2022 / 10 / 25 - Rx  (0) 2022.10.25
[TIL] 2022 / 10 / 13 - Realm Migration  (1) 2022.10.13
[TIL] 2022/ 10 / 12 - Remote Notification, Method Swizzling  (0) 2022.10.12
[TIL] 2022 / 09 / 06  (0) 2022.09.06
댓글
최근에 올라온 글
Total
Today
Yesterday