[TIL] RxSwift 4시간에 끝내기
RxSwift 4시간에 끝내기를 참고하였습니다.
✅⠀RxSwift란?
- Swift에 ReactiveX를 적용시켜 비동기 프로그래밍을 직관적으로 작성할 수 있도록 도와주는 라이브러리.
- ReactiveX(An API for asynchronous programming with observable streams) 시리즈 중 하나 MS에서 만들었다고 한다.
RxSwift를 활용하지 않고 서버에서 이미지 가져오기
- 동기
guard let url = URL(string: imageUrl) else { return nil }
guard let data = try? Data(contentsOf: url) else { return nil }
let image = UIImage(data: data)
imageView.image = image
- 비동기
DispatchQueue.global().async{
guard let url = URL(string: imageUrl) else { return nil }
guard let data = try? Data(contentsOf: url) else { return nil }
let image = UIImage(data: data)
DispatchQueue.main.async {
self.imageView.image = image
}
}
RxSwift를 활용하여 서버에서 이미지 가져오기
지금은 이미지만 가져오는 간단한 작업이라 코드가 간결해보이지 않지만 복잡한 작업을 할 때 유용하다고 한다.
rxswiftLoadImage(from: LARGER_IMAGE_URL)
.observeOn(MainScheduler.instance)
.subscribe({ result in
switch result {
case let .next(image):
self.imageView.image = image
case let .error(err):
print(err.localizedDescription)
case .completed:
break
}
}).disposed(by: disposeBag)
func rxswiftLoadImage(from imageUrl: String) -> Observable<UIImage?> {
return Observable.create { seal in
asyncLoadImage(from: imageUrl) { image in
seal.onNext(image)
seal.onCompleted()
}
return Disposables.create()
}
}
subscribe
작업의 시작을 알림. subscribe은 Disposable을 반환한다.
func subscribe(_ on: @escaping (Event<UIImage?>) -> Void) -> Disposable
Dispose
- Disposable을 직접 취소
var disposable: Disposable?
let disposable = rxswiftLoadImage(from: LARGER_IMAGE_URL)
.observeOn(MainScheduler.instance)
.subscribe({ result in
switch result {
case let .next(image):
self.imageView.image = image
case let .error(err):
print(err.localizedDescription)
case .completed:
break
}
})
disposable?.dispose() // cancel
- DisposeBag을 활용한 취소
var disposable: Disposable?
var disposeBag: DisposeBag = DisposeBag()
// DisposeBag에 넣을 수 있는 방법
// 1. insert를 사용해 넣어준다.
let disposable = rxswiftLoadImage(from: LARGER_IMAGE_URL)
.observeOn(MainScheduler.instance)
.subscribe({ result in
switch result {
case let .next(image):
self.imageView.image = image
case let .error(err):
print(err.localizedDescription)
case .completed:
break
}
})
disposeBag.insert(disposable) // 해당 disposable을 넣어줌
// 2. disposed(by: )를 사용하여 1번보다 더 간편하게 넣어준다.
rxswiftLoadImage(from: LARGER_IMAGE_URL)
.observeOn(MainScheduler.instance)
.subscribe({ result in
switch result {
case let .next(image):
self.imageView.image = image
case let .error(err):
print(err.localizedDescription)
case .completed:
break
}
}).disposed(by: disposeBag) // <---
disposeBag = DisposeBag() //disposeBag을 새로 만들어주면 기존에 disposeBag에 있는 작업들이 다 dispose 된다.
✅⠀Operators
Creating Observables
Just
- create an Observable that emits a particular item
- 통으로 나온다.
Observable.just("Hello World")
.subscribe(onNext: { str in
print(str)
})
.disposed(by: disposeBag)
// Hello World 출력
Observable.just(["Hello", "World"])
.subscribe(onNext: { arr in
print(arr)
})
.disposed(by: disposeBag)
// ["Hello", "World"] 출력
From
- convert various other objects and data types into Observables
- Just와 달리 하나하나 나온다.
Observable.from(["Hello", "World"])
.subscribe(onNext: { str in
print(str)
})
.disposed(by: disposeBag)
// ** 출력 **
// Hello
// World
Observable.from(["Hello", "World"])
.map { $0.count }
.subscribe(onNext: { str in
print(str)
})
.disposed(by: disposeBag)
// ** 출력 **
// 5
// 5
Transforming Observables
Map
Observable.from([1, 2, 3])
.map { $0 * 10 }
.subscribe(onNext: { str in
print(str)
})
.disposed(by: disposeBag)
// ** 출력 **
// 10
// 20
// 30
Filtering Observables
Filter
Observable.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.filter { $0 % 2 == 0 }
.subscribe(onNext: { n in
print(n)
})
.disposed(by: disposeBag)
// ** 출력 **
// 2
// 4
// 6
// 8
// 10
First
Observable.from([1, 2, 3, 4, 5, 6, 7, 8, 9, 10])
.first()
.subscribe(onNext: { n in
print(n)
})
.disposed(by: disposeBag)
// ** 출력 **
// 1
Single
Observable.from([1])
.single()
.subscribe(onNext: { n in
print(n)
})
.disposed(by: disposeBag)
// ** 출력 **
// 1
Combining Observables
CombineLatest
Observable.combineLatest( idField.rx.text.orEmpty
.map(checkEmailValid),
pwField.rx.text.orEmpty
.map(checkPasswordValid),
resultSelector: {s1, s2 in s1 && s2})
.subscribe(onNext: { b in
self.loginButton.isEnabled = b
}).disposed(by: disposeBag)
✅⠀Scheduler
어느 Scheduler에서 실행시킬지 결정
observeOn
순서에 영향을 받는다. 즉, 중간중간에 스케쥴러를 바꿔도 됨.
Observable.just("800x600")
.observeOn(ConcurrentDispatchQueueScheduler.init(qos: .default)) // ** 여기서부터 ConcurrentDispatchQueueScheduler에서 처리하겠다.
.map { $0.replacingOccurrences(of: "x", with: "/") }
.map { "https://picsum.photos/\($0)/?random" }
.map { URL(string: $0) }
.filter { $0 != nil }
.map { $0! }
.map { try Data(contentsOf: $0) }
.map { UIImage(data: $0) }
.observeOn(MainScheduler.instance) // ** UI관련 작업은 메인쓰레드에서 처리
.subscribe(onNext: { image in
self.imageView.image = image
})
.disposed(by: disposeBag)코드를 입력하세요
// 만약 observeOn에 순서를 바꾼다면?
Observable.just("800x600")
.map { $0.replacingOccurrences(of: "x", with: "/") }
.map { "https://picsum.photos/\($0)/?random" }
.map { URL(string: $0) }
.filter { $0 != nil }
.map { $0! }
.map { try Data(contentsOf: $0) }
// ** Data를 가져오는 부분보다 뒤에서 비동기처리를 하니 데이터를 가져오는 것 까진 메인쓰레드에서 동기 처리.
// ** 즉, 데이터를 받아올 때 까지 아무것도 못한다.
.observeOn(ConcurrentDispatchQueueScheduler.init(qos: .default))
.map { UIImage(data: $0) }
.observeOn(MainScheduler.instance) // ** UI관련 작업은 메인쓰레드에서 처리
.subscribe(onNext: { image in
self.imageView.image = image
})
.disposed(by: disposeBag)
subscribeOn
observeOn과 달리 순서에 영향을 받지 않음. subscribe 쓰는 순간 부터 처리함.
Observable.just("800x600")
.map { $0.replacingOccurrences(of: "x", with: "/") }
.map { "https://picsum.photos/\($0)/?random" }
.map { URL(string: $0) }
.filter { $0 != nil }
.map { $0! }
.map { try Data(contentsOf: $0) }
.map { UIImage(data: $0) }
.subscribeOn(ConcurrentDispatchQueueScheduler.init(qos: .default))
.observeOn(MainScheduler.instance)
.subscribe(onNext: { image in
self.imageView.image = image
})
.disposed(by: disposeBag)
✅⠀subscribe 후 이벤트 처리
- subscribe(_ on: (Event) -> Void )
Observable.just(["Hello", "World"])
.subscribe{ event in
switch event {
case .next(let str):
print("next : \(str)")
break
case .error(let err):
print("error : \(err.localizedDescription)")
break
case .completed:
print("completed")
break
}
}.disposed(by: disposeBag)
// ** 출력 **
// next : Hello
// next : World
// completed
Observable.just(["Hello", "World"])
.single()
.subscribe{ event in
switch event {
case .next(let str):
print("next : \(str)")
break
case .error(let err):
print("error : \(err.localizedDescription)")
break
case .completed:
print("completed")
break
}
}.disposed(by: disposeBag)
// ** 출력 **
// next : Hello
// error: The operation couldn't be completd.
// single은 하나만 들어와야되는데 여러개가 들어오니 에러
- .subscribe(onNext: , onError: , onCompleted: , onDisposed: )
Observable.just(["Hello", "World"])
.subscribe(onNext: { s in
print(s)
}, onError: { err in
print(err.localizedDescription)
}, onCompleted: {
print("completd")
}, onDisposed: {
print("disposed")
}).disposed(by: disposeBag)
// ** 출력 **
// next : Hello
// next : World
// completd
// disposed
✅⠀Side effect
말 그대로 외부에 영향을 주는 부분을 담당. subscribe와 do에서만 처리하자.
- subscribe
.subscribe(onNext: { image in
self.imageView.image = image // 외부에 있는 imageView의 image를 바꿀 때
})
- do
.do(onNext: { image in
print(image?.size)
})
✅⠀RxCocoa
textField의 입력될 때마다 호출되는 부분을 Delegate를 사용하지 않고 RxCocoa를 활용하여 구현.
@IBOutlet var idField: UITextField!
// .rx : 비동기로 받겠다.
// orEmpty : .filter{ $0 != nil}.map{$0!} nil값을 체크
idField.rx.text.orEmpty
.map(checkEmailValid)
.subscribe(onNext: { b in
self.idValidView.isHidden = b
})
.disposed(by: disposeBag)
// 따로 분리해서 쓸 수도 있음
let idInputOb : Observable<String> = idField.rx.text.orEmpty.asObservable()
let idValidOb = idInputOb.map(checkEmailValid)
idValidOb.subscribe(onNext: { b in
self.idValid.onNext(b)
self.idValidView.isHidden = b
})
.disposed(by: disposeBag)
✏️⠀오늘은...
말로만 듣던 Rx 오늘 처음 공부해봤는데 생각보다 굉장히 유용한것 같다. gesture 부분을 rx로 간편하게 구현할 수 있는거 보고 감탄의 감탄을 👏🏻⠀전세계 사람들 다 코드 길게 치기 귀찮아서 오픈소스 열심히 만드나 보다. 감사합니다 ㅎㅎ 내일은 곰튀김님의 RxSwift 시즌2를 들어야겄다..
Author And Source
이 문제에 관하여([TIL] RxSwift 4시간에 끝내기), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@sainkr/TIL-2021.05.10저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)