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)
    }
}