적당한 고통은 희열이다

- 댄 브라운 '다빈치 코드' 중에서

Swift iOS 앱 개발/Swift 튜토리얼

곰튀김 RxSwift 시즌 2

hongssup_ 2023. 1. 31. 01:15
반응형

곰튀김 RxSwift 시즌 2 : https://youtu.be/iHKBNYMWd5I

Github : https://github.com/iamchiwon/RxSwift_In_4_Hours

 

 

멀티쓰레드 이용한 데이터 다운로드 및 indicator 표시 비동기 처리

 

downloadJson 함수를 따로 빼서 그것만 멀티 쓰레드로 처리하도록. 

but 그러면 리턴값을 받을 수 가 없다? 

-> @escaping 클로저를 사용해서 결과 값을 전달

completion: @escaping (String?) -> Void
…
completion(json)

Single argument function types require parentheses

그런데 만약 결과값이 옵셔널인 경우 escaping 디폴트이기 때문에 생략 가능

completion: ((String?) -> Void)?)
…
completion?(json)

이것이 기존에 사용하던 swift에서의 비동기 처리방식.

 

결과 값을 completion으로 전달하지 말고, 사용하기 편하게 리턴 값으로 전달할 순 없을까?
비동기로 생긴 데이터를 어떻게 리턴값으로 만들지? 

 

RxSwift의 목적 / 용도

: 비동기적으로 생기는 데이터를 completion 같은 클로저를 통해 전달하는 것이 아니라 리턴 값으로 전달하기 위해 만들어진 유틸리티. 

 

비동기로 나중에 생기는 데이터를 Observable로 감싸서 리턴하도록.

생성할 때는 create(), 값 전달할 때는 onNext() 사용해서 리턴하고, subscribe를 통해서 값을 사용.

 

Observable 만드는 방법

Observable create를 사용해서 emitter로 onNext 혹은 onCompleted 를 전달 혹은 error를 보내는 방식

 

*** URLSession 사용하는 이유..?

error 처리까지 깔끔하게 하기 위해서 ? data 처리 뿐 아니라? 

urlsession 자체가 main 스레드가 아니라 다른 스레드에서 처리됨

 

error 처리 혹은 onNext로 데이터 전달 혹은 onCompleted로 종료를 시킨다. 

+ 취소 되었을 해야하는 행동 cancel()

 

Observable의 생명주기

1. Create

2. Subscribe - Observable은 Subscribe가 되면 그 때 실행된다. 

3. onNext

—— 동작 끝 —— 

4. onCompleted / onError

5. Disposed

 

동작이 끝난 Observable 재사용이 불가. 

 

Observable & subscribe 기본 사용법

 

Sugar API

just / from : 생성을 제공해주는 sugar

create으로 만들어줘도 되지만, create, onNext, onCompleted, disposables 과정을 .just를 통해 한줄로 간단하게 사용할 수 있음.  

return Observable.just(“Hello World”) just는 데이터 하나만 보낼 때 혹은 하나의 배열 내에 여러개 보낼수도.

return Observable.from([“Hello”, “World”]) 배열에 들어있는 하나씩 전달.

 

observable에서 subscribe 로 데이터 전달되는 중간에 데이터를 변경하는 sugar는 operator

observeOn 으로 스레드 변경 .observeOn(MainScheduler.instance)

subscribeOn 은 맨 처음 시작 스레드에 영향 주는 친구. 어디에 있든 위치와 상관없이. 

.subscribeOn(ConcurrentDispatchQueueScheduler(qos: .default)) 이런식으로 수있음

 

.last : complete 됐을 가장 마지막 데이터

 

*** closure - reference counting? 순환참조 ? 

 

기존 UI update 방식 : viewDidLoad, 버튼 터치 등 업데이트 해야하는 곳마다 updateUI 함수 호출 해줘야

var totalPrice: Int = 10000

-> Observable<Int> 이용해서 자동으로 업데이트 반영 되도록

var totalPrice: Observable<Int> = Observable.just(10000)

-> Subject 사용해서 옵저버블 밖에서도 값을 통제하고 업데이트 가능하도록 (subject를 사용하면 옵저버블 밖에서도 데이터를 컨트롤 할 수 있다)

 

네 가지 종류의 subject 중에서 publish와 behavior subject 가 가장 많이 쓰임. 위에 사용한 것이 바로 publish subject 

옵저버블 사용하면 한번만 선언해주면 후로 값이 바뀔때마다 자동으로 텍스트 업데이트

 

lazy var menuObservable = Observable.just(menus)

-> lazy var menuObservable = PublishSubject<[Menu]>()

외부에서도 값을 변경할 수 있도록 subject로 변경

 

메뉴 값이 바뀔 때 마다 itemCount, totalPrice,  자동으로 바뀌도록 옵저버블 설정

연결관계(Stream)만 만들어주면 자동으로 바뀌도록 

 

.rx 는 RxCocoa 에서 제공해주는 친구들. 

Binder 

.subscribe(onNext: { [weak self] in 

    self?.totalPrice.text = $0

})

-> bind 사용해서 순환참조 없이 한줄로 만들어줄 수 있음

.bind(to: totalPrice.rx.text)

 

*** 궁금증 .map { String($0) } .map { “\($0)” } 성능 차이 있나..?  - 캐스팅 실패되도 예를들어 nil 뜰수있도록?

 

viewModel.menuObservable

            .bind(to: tableView.rx.items(cellIdentifier: cellId, cellType: MenuItemTableViewCell.self)

설정해주면 tableview datasource delegate 사용 안해도댐. 

오잉 근데 build fail 뜨네? 

Failure converting from Optional … to UITableViewDataSource

https://github.com/ReactiveX/RxSwift/issues/1587

storyboard에 연결되어있는 dataSource outlet을 연결해제해주어야 한다. 

오 이제 빌드는 잘 되는데 테이블 뷰 값이 하나도 안들어오네? 

publish subject는 subscribe 한 이후에 값이 들어오면 그 때 적용됨. 

-> menuObservable을 초기값을 가지는 BehaviorSubject 로 설정을 해주어야 tableView에 초기 값이 잘 들어간다. 

lazy var menuObservable = PublishSubject<[Menu]>()

-> var menuObservable = BehaviorSubject<[Menu]> 

 

clearAllItemSelections()

stream을 한 번 연결해주면 다음번에 변경이 일어나도 다시 계속 실행됨. 

따라서 한 번만 실행이 되도록 .take(1) 해줘서 더 이상 수행이 되지 않도록 설정해주기

 

cell 에서 아이템 + - 설정 해줄려면 

cell 내에서 viewModel 설정해주고 호출하는 방법 or viewController 내에서 viewModel 호출하는 방법

+ tableview.rx.items bind에서 간단하게 콜백으로 처리하는 방법도. 

Missing return in closure expected to return 'Menu' ??

 

callback 으로 메뉴 count + - 설정

count - 넘어가지 않도록 count: max(m.count + increase, 0))

 

view는 화면 처리만 담당하고, 모든 데이터에 대한 처리는 viewModel에서 

 

API 호출할 때 rx 사용하는 법 

원래는 @escaping 클로저로 리턴되는 타입이 <Data, Error> 였지만, rx로 하면 onError로 받아올 수 있으니까 Data만 리턴하면 됨.

기존 api 호출하는 레거시 코드를 rx로 감싸서 fetch하도록 코드 리팩토링..? 

 

서버에서 받아올 MenuItem 구조체에는 id, count 가 따로 없기 때문에 Menu 구조체에 extension을 사용하여 id와 count를 추가해주는 메서드 fromMenuItems 를 선언해준다. 

 

APIService 사용할 때

서버에서 제공하는 스펙에 따라 모델을 만들고, 사용할 방식으로 변환 

 

 

viewModel : 아키텍처로의 뷰모델, view에서 사용할 model

 

아키텍처

UIKit(특정 플랫폼)에 종속되면 test가 힘들어짐. 

MVC 패턴에서 Model은 종속되지 않으니 testable, but UIViewController & UIView 는 UIKit에 종속되기 떄문에 테스트가 힘들어짐. 

=> 컨트롤러의 역할을 제한하자. MVP Model View Presenter : test 하기 힘든 view에서는 테스트 필요없는 것들만 모아두고, 모든 판단과 로직은 Presenter에게 맡기도록(test할 수 있게).  but view, presenter 1:1 관계라 비효율적..?

 

야매로 하긴했는데 코드 비효율적이다? + - 할 때마다 메뉴를 계속 새로 생성하는 건 비효율적.

리팩토링 해보기! (Rx + MVVM 코드 참고)

 

옵저버블은 정해져있는? stream.

옵저버블 밖에서 데이터를 주입시켜 줄 수 있는 것이 필요. 데이터를 넣어줄수도 있고 subscribe로 구독할 수 있는 양방향성을 가진 subject

 

UI 작업할 때는 무조건 UI thread (Main thread) 에서 작업을 해야 ! 

=> .observeOn(MainScheduler.instance) 는 항상 매번 들어가야 함. 

그리고 UI 는 stream이 끊어지면 안됨. .catchErrorJustReturn(“”) 같은 걸 사용해서 에러 나도 빈 텍스트라도 내려주도록 

이 두가지를 합쳐서? .asDriver(onErrorJustReturn: “”)

드라이버는 observeOn 안해줘도 항상 Main thread에서 돌아감. + 에러가 나도 끊어지지 않도록 UI 처리용으로 드라이버 만들었다? 

 

*** 반응형 / 함수형 차이? 

 

 

728x90
반응형

'Swift iOS 앱 개발 > Swift 튜토리얼' 카테고리의 다른 글

[Swift iOS] Realm  (0) 2022.11.21
RxSwift 곰튀김  (0) 2022.10.11
[Swift iOS] Face Detection  (0) 2021.12.22
[Swift iOS] Core Data 이용한 ToDoList 예제  (0) 2021.06.12
[Stanford iOS] Lecture 12. Core Data  (0) 2021.04.13