iOS Development/RxSwift
[RxSwift] RxSwift로 네트워크 통신 구현하기
se0m
2024. 4. 12. 21:32
RxSwift와 RxCocoa를 이용해서 네트워크 통신을 하는 fetch 함수를 구현하고
(GitHub API를 사용하여 organization 이름으로 GitHub에 업로드된 레포지토리 목록을 불러옴)
UITableViewController의 UIRefreshControl에서 fetch 함수를 호출하여 당겨서 새로고침 기능을 구현할 수 있다.
1. Repository 타입의 데이터 모델 생성
2. BehaviorSubject와 DisposeBag 생성
private let repos = BehaviorSubject<[Repository]>(value: [])
private let disposeBag = DisposeBag()
3. fetch 함수 정의
- String → URL → URLRequest → Observable<(response: HTTPURLResponse, data: Data)> → Data → [[String: Any]] → [Repository] → Observable<Repository>로 변환
private func fetch(of organization: String) {
Observable.from([organization])
// 1. URL 생성
.map { organization -> URL in
return URL(string: "https://www.api.github.com/orgs/\(organization)/repos")!
}
// 2. URLRequest 생성
.map { url -> URLRequest in
var request = URLRequest(url: url)
request.httpMethod = "GET"
return request
}
// 3. URLResponse 생성
.flatMap { urlRequest -> Observable<(response: HTTPURLResponse, data: Data)> in
return URLSession.shared.rx.response(request: urlRequest) // .rx: Swift에서 기본적으로 제공하는 인자들을 rx로 변환
}
// 4. 올바른 응답만 처리
.filter { response, _ in
return 200..<300 ~= response.statusCode
}
// 5. json 데이터 파싱
.map { _, data -> [[String: Any]] in
guard let json = try? JSONSerialization.jsonObject(with: data),
let result = json as? [[String: Any]]
else { return [] }
return result
}
// 6. 값이 있는 데이터만 처리
.filter { result in
result.count > 0
}
// 7. Respository로 형 변환
.map { objects in
return objects.compactMap { dic -> Repository? in // compactMap은 자동적으로 nil 값은 제거해서 반환
guard let id = dic["id"] as? Int,
let name = dic["name"] as? String,
let description = dic["description"] as? String,
let starGazersCount = dic["stargazers_count"] as? Int,
let language = dic["language"] as? String
else { return nil }
return Repository(
id: id,
name: name,
description: description,
starGazersCount: starGazersCount,
language: language
)
}
}
// 8. 구독
// 구독을 하기 전까지는 아무런 역할을 하지 않음
.subscribe(
onNext: { [weak self] newRepos in
self?.repos.onNext(newRepos) // repos는 초기값이 아닌 newRepos 값을 받음
// UI Binding이 아닌 DispatchQueue를 사용하는 방식
DispatchQueue.main.async {
self?.tableView.reloadData()
self?.refreshControl.endRefreshing()
}
}
)
.disposed(by: disposeBag)
}
4. UITableViewController의 refresh 설정
refreshControl = UIRefreshControl()
let refreshControl = refreshControl!
refreshControl.backgroundColor = .systemBackground
refreshControl.tintColor = .darkGray
refreshControl.attributedTitle = NSAttributedString(string: "당겨서 새로고침") // 인디케이터 아래에 있는 글자
refreshControl.addTarget(self, action: #selector(refresh), for: .valueChanged)
5. Refresh의 target 메서드 정의
@objc private func refresh() {
DispatchQueue.global(qos: .background).async { [weak self] in
guard let self = self else { return }
self.fetchRepositories(of: organization)
}
}