func downloadJson(_ url: String, _ completion: @escaping (String?) -> Void){
// downloadJson이 끝나고 나중에 실행되는 함수라는걸 알려줄려고 escaping을 적어줌
// ((String?) -> Void)? 이렇게 옵셔널인 경우 escaping이 default라 안적어줘도 됨
DispatchQueue.global().async {
let url = URL(string: url)!
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
DispatchQueue.main.async {
// ** 여기서 completion 사용
completion(json)
}
}
}
@IBAction func onLoad() {
downloadJson(MEMBER_LIST_URL){ json in // completion 구현
self.editView.text = json
}
}
class 나중에생기는데이터<T>{
private let task: (@escaping (T) -> Void) -> Void
init(task: @escaping (@escaping (T) -> Void) -> Void){
self.task = task
}
func 나중에오면(_ f: @escaping (T) -> Void){
task(f)
}
}
func 다운로드받는함수(_ url: String) -> 나중에생기는데이터<String>{
return 나중에생기는데이터() { f in // ** task
DispatchQueue.global().async {
let url = URL(string: url)!
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
DispatchQueue.main.async {
f(json)
}
}
}
}
@IBAction func onLoad() {
// ** 1. json에 나중에생기는데이터<String>을 담는다.
let json: 나중에생기는데이터<String> = 다운로드받는함수(MEMBER_LIST_URL)
// ** 2. 나중에오면을 실행시켜 task를 실행시킨다.
json.나중에오면{ json in // ** f
self.editView.text = json
self.setVisibleWithAnimation(self.activityIndicator, false)
}
}
RxSwift의 Observable의 기본 원리는 위와 같이 구현된다고한다. Obsevable은 아마 아래와 같이 생기지 않았을까 .. ?
class Obsevalble<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)
}
}
func rx사용(_ url: String) -> Observable<String?>{
return Observable.create{f in // ** 위에서의 task
DispatchQueue.global().async {
let url = URL(string: url)!
let data = try! Data(contentsOf: url)
let json = String(data: data, encoding: .utf8)
DispatchQueue.main.async {
f.onNext(json)
f.onCompleted()
}
}
// static func create(_ subscribe: @escaping (AnyObserver<String?>) -> Disposable) -> Observable<String?>
// create안에 있는 클로져의 반환 값이 Disposable이기 때문에 Dispoable을 return 해준다.
return Disposables.create()
}
}
rx사용(MEMBER_LIST_URL)
.debug() // debug()를 통해 받아온 데이터, 생명 주기 확인 가능
.subscribe{ event in // ** 위에서의 f
switch event{
case .next(let json):
self.editView.text = json
case .error:
break
case .completed:
break
}
}
옵저버가 BehaviorSubject를 구독하기 시작하면, 옵저버는 소스 Observable이 가장 최근에 발행한 항목(또는 아직 아무 값도 발행되지 않았다면 맨 처음 값이나 기본 값)의 발행을 시작하며 그 이후 소스 Observable(들)에 의해 발행된 항목들을 계속 발행한다.
PublishSubject는 구독 이후에 소스 Observable(들)이 배출한 항목들만 옵저버에게 배출한다.
주의할 점은, PublishSubject는 (이를 막지 않는 이상) 생성 시점에서 즉시 항목들을 배출하기 시작할 것이고 이런 특성 때문에 주제가 생성되는 시점과 옵저버가 이 주제를 구독하기 시작하는 그 사이에 배출되는 항목들을 잃어 버릴 수 있다는 단점이 있다. 이 경우 PublishSubject 대신 ReplaySubject를 사용해야 한다.
viewModel.totalPrice
.map{ $0.currencyKR() }
.observeOn(MainScheduler.instance)
.subscribe(onNext: { [weak self] text in
self?.totalPrice.text = text
}).disposed(by: disposeBag)
override func viewWillDisappear(_ animated: Bool) {
disposeBag = DisposeBag()
}
// subscribe 사용한 경우
viewModel.totalPrice
.map{ $0.currencyKR() }
.observeOn(MainScheduler.instance)
.subscribe(onNext: {
self.totalPrice.text = $0
}).disposed(by: disposeBag)
// bind 사용한 경우
viewModel.totalPrice
.map{ $0.currencyKR() }
.bind(to: totalPrice.rx.text)
.disposed(by: disposeBag)
_ = menuObservable
.map{ menus in
menus.map { m in
Menu(id: m.id,name: m.name, price: m.price, count: 0)
}
}
.take(1) // 한 번만 수행하기 계속 수행하면 스트림이 계속 생기기 때문
.subscribe(onNext: {
self.menuObservable.accept($0)
})
// Driver 사용 전
viewModel.itemsCount
.map{"\($0)"}
.observeOn(MainScheduler.instance)
.bind(to: itemCountLabel.rx.text)
.disposed(by: disposeBag)
// Driver 사용 후
viewModel.itemsCount
.map{"\($0)"}
.asDriver(onErrorJustReturn: "") // error가 나면 .. ""로 리턴하겠다.
.drive(itemCountLabel.rx.text)
.disposed(by: disposeBag)
// observeOn() 사용해서 메인쓰레드로 굳이 안바꿔줘도 됨
전 시즌에 비해 기초가 더 탄탄해졌다. 개념을 더 정확히 잡을 수 있어 더 좋았던 것 같다. RxCocoa 실습도 더 강화됐는데 과부화 걸려서 천천히 이해하면서 정리해봐야겠다 🤯⠀아직은 너무 어려운 Rx ... 그래도 오랜만에 새로운걸 공부하니 재미있었다 😊