일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
- 애니메이션
- swiftUI
- Xcode
- ribs
- Observable
- combine
- UITextView
- Clean Code
- clean architecture
- RxCocoa
- uitableview
- ios
- Refactoring
- HIG
- rxswift
- UICollectionView
- swift documentation
- uiscrollview
- tableView
- collectionview
- 스위프트
- 리펙터링
- Protocol
- SWIFT
- 클린 코드
- map
- Human interface guide
- MVVM
- 리펙토링
- 리팩토링
- Today
- Total
김종권의 iOS 앱 개발 알아가기
[RxSwift] 8. Transforming Operators 실전(RESTful API) 본문
1. URL API REQ
1) map을 이용하여 원소에 접근하여 최종적으로 URLRequest획득
// viewDidLoad().swoft
DispatchQueue.global(qos: .default).async { [weak self] in
let response = Observable.from([repo])
.map { urlString -> URL in
return URL(string: "https://api.github.com/repos/\(urlString)/events")!
}.map { url -> URLRequest in
return URLRequest(url: url)
}
}
2) flatMap을 이용하여 새로운 Observable생성
*map이 아닌 flatMap을 사용하는 이유
- 비동기적으로 작동하는 observable을 통해 효과적으로 "대기"할수 있도록 함(==그 동안 다른 연결들은 계속 작동되게끔)
(웹의 response를 기다리기 위함)
- 문자열 또는 숫자 array들의 observable 같이 일시적으로 요소를 방출하고 완료된 observable들을 flatten
※ map vs flatMap : map은 이벤트를 변경, flatMap은 새로운 Observable을 생성하여 변경, 자세한 내용은 여기 참고
.flatMap { request -> Observable<(response: HTTPURLResponse, data: Data)> in
return URLSession.shared.rx.response(request: request)
}
- RxCocoa의 URLSession객체 내에 있는 response(request:)를 이용(RES를 서버로 부터 받으면 .next이벤트를 한 번 발생)
- response(request:)의 반환 값 : 앱이 웹 서버를 통해 full response를 받을 때 마다 Observable<(response: HTTPURLResponse, data:
Data)>)를 반환
- flatMap이 REQ이고 그의 반환값이 RES라고 생각
3) .share(replay:scope:)
- share이 없을경우 문제 : 위의 코드로 RES를 받게되면 .nextf를 emit할 때 1회성이므로 새로운 subscribed observer들은 .next이벤트를 받지 않음
- replay : replay로 emit한 요소를 가지고 있다가 새로운 subscriber가 생길 때 이를 제공
- scope : .whileConnected (n/w response buffer를 아무도 subscribe하고 있지 않을때까지 가지고 있는 것)
.forever (n/w response buffer를 계속 가지고 있는 것, 새로운 subscriber는 buffer response를 갖음)
.share(replay: 1, scope: .whileConnected)
※ .share은 complete할 것으로 예상되는 sequence에 사용해야함(observable이 다시 생성되는 것을 방지할 수 있기 때문)
* REQ & RES, full code : map과 flatMap으로 표현
DispatchQueue.global(qos: .default).async { [weak self] in
let response = Observable.from([repo])
.map { urlString -> URL in
return URL(string: "https://api.github.com/repos/\(urlString)/events")!
}.map { url -> URLRequest in
return URLRequest(url: url)
}.flatMap { request -> Observable<(response: HTTPURLResponse, data: Data)> in
return URLSession.shared.rx.response(request: request)
} .share(replay: 1, scope: .whileConnected)
}
※ REQ에 대한 오류처리는 14챕터에서 확인
2. RES
1) response observable에 대한 구독을 만들어서 리스폰스 데이터를 객체로 변환
response
.filter { response, _ in
return 200 ..< 300 ~= response.statusCode
}
2) 서버로 부터 받은 JSON 형식의 데이터를 [Event]로 변환
- [[String:Any]]
- 데이터 디코드 : JOSNDecoder이용
.map { _, data -> [Event] in
let decoder = JSONDecoder()
let events = try? decoder.decode([Event].self, from: data)
return events ?? []
}
3) 이벤트 객체가 존재하는 것만 filter
.filter { objects in
return !objects.isEmpty
}
4) 구독 요청
- self?.processEvents(newEvents)메소드는 아직 구현 x
.subscribe(onNext: { [weak self] newEvents in
self?.processEvents(newEvents)
})
.disposed(by: self?.bag ?? DisposeBag())
3. Reponse된 데이터 처리
- processEvents(newEvent:)처리
// ActivityController.swift, processEvents(_:)
// private let events = BehaviorRelay<[Event]>(value: [])
var updatedEvents = events.value + newEvents
if updatedEvents.count > 50 {
updatedEvents = [Event](updatedEvents.prefix(upTo: 50))
}
events.accept(updatedEvents)
// tableView.reloadData()는 오직 메인쓰레드에서만 가능
DispatchQueue.main.async {
self.tableView.reloadData()
}
4. refresh 설정 (새로고침)
UITableView를 구현한 클래스에서, self.refreshControl사용
1) refreshControl 객체
// viewDidLoad()
self.refreshControl = UIRefreshControl()
let refreshControl = self.refreshControl!
refreshControl.backgroundColor = UIColor(white: 0.98, alpha: 1.0)
refreshControl.tintColor = UIColor.darkGray
refreshControl.attributedTitle = NSAttributedString(string: "Pull to refresh")
refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
refresh() // fetchData하는 메소드
2) 업데이트가 된 경우 애니메이션 삭제
- endRefreshhing()
// processEvents()
// self.tableView.reloadData() 밑에다 바로 적을 것
self.refreshControl?.endRefreshing()
5. .plist에 이벤트에 대한 정보 저장
1) .plist를 생성하는 함수 정의
// ActivityController.swift
func cachedFileURL(_ fileName: String) -> URL {
return FileManager.default
.urls(for: .cachesDirectory, in: .allDomainsMask)
.first!
.appendingPathComponent(fileName)
}
2) 파일 생성
// heap 영역
var eventsFileURL : URL {
return self.cachedFileURL("events.json")
}
3) JSON을 Data형 변환한 후 disk에 저장
- processEvents(_:)에 추가
let encoder = JSONEncoder()
if let eventsData = try? encoder.encode(updatedEvents) {
try? eventsData.write(to: eventsFileURL, options: .atomicWrite)
}
6. 수정된 시간정보를 header에 넣어서 REQ
1) modified 변수
// heap
var modifiedFileURL : URL {
return cachedFileURL("modified.txt")
}
let lastModified = BehaviorRelay<String?>(value: nil)
<chachedFileURL>
// ActivityController.swift
func cachedFileURL(_ fileName: String) -> URL {
return FileManager.default
.urls(for: .cachesDirectory, in: .allDomainsMask)
.first!
.appendingPathComponent(fileName)
}
2) lastModified에 이벤트 추가
// viewDidLoad()
if let lastModifiedString = try? String(contentsOf: modifiedFileURL, encoding: .utf8)
lastModified.accept(lastModifiedString)
}
// refresh() 호출 바로 위
3) fetchEvents()안의 response에 필터 및 observable.just
response
.filter { response, _ in
return 200..<400 ~= response.statusCode
}
4) flatMap을 통해 Response 받음 (예전과 동일)
.flatMap { response, _ -> Observable<String> in
guard let value = response.allHeaderFields["Last-Modified"] as? String else {
return Observable.empty()
}
return Observable.just(value)
}
5) 구독 요청
.subscribe(onNext: { [weak self] modifiedHeader in
guard let self = self else { return }
self.lastModified.accept(modifiedHeader)
try? modifiedHeader.write(to: self.modifiedFileURL, atomically: true, encoding: .utf8)
})
.disposed(by: self?.bag ?? DisposeBag())
6) request에 modifiedHeader정보 추가
- 1번을 2번으로 교체
// fetchEvents(repo:)
// 1번
.map { url -> URLRequest in
return URLRequest(url: url)
}
// 2번
.map { [weak self] url -> URLRequest in
var request = URLRequest(url: url)
if let modifiedHeader = self?.lastModified.value {
request.addValue(modifiedHeader,
forHTTPHeaderField: "Last-Modified")
}
return request
}
* 이점
- 최신 데이터에만 관심(request, response), 최신 데이터만 update
'RxSwift > RxSwift 기본' 카테고리의 다른 글
[RxSwift] 10. share Operators (0) | 2020.06.24 |
---|---|
[RxSwift] 9. Combining Operators (0) | 2020.06.05 |
[RxSwift] 7.Transforming Operators (0) | 2020.06.01 |
[RxSwift] 6.Filtering Operators 실습 (0) | 2020.05.29 |
[RxSwift] 5.Filtering Operators (0) | 2020.05.26 |