곰튀김 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 처리용으로 드라이버 만들었다?
*** 반응형 / 함수형 차이?
'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 |