어제 제대로 집중하지 못했기 때문에, 평소보다 강의를 듣는 속도가 느리다. 오늘 기준으로 1-9까지 들었지만, 마지막 강의는 2시간이 넘기 때문에 내일까지 수강할 예정이다. 강의를 듣는 게 다가 아니라 RxSwift에 대해 이해하고 정리를 진행해야 과제도 진행할 수 있을텐데, 아직 정확하게 큰 그림이 그려지지는 않는다. 내일 오전 내로 강의를 다 듣고 정리하는 시간을 가질 예정이다. 과제는 아마 이후에 진행하지 않을까 생각한다.
사실 이번 강의에서 주된 실습으로 진행하는 NETFLIX CLONE Coding에서는 RxSwift를 사용하고 있는 이유에 대해서라고 하기엔 많이 부족하다고 생각한다. 기본적으로, RxSwift란 어떤 것인지에 대한 개념의 소개가 부족했다고 느꼈으며, RxSwift를 사용함으로써 얻게 되는 이점과 활용법에 대한 소개도 부족하다고 느껴 아쉽다고 생각한다. 물론, 강의 하나를 통해 RxSwift에 대해 모든 걸 알 수 있다고 생각하지는 않으며, 해당 내용은 광범위하기 때문에 이후 스스로 찾아서 공부 및 정리를 진행해야할 것이지만, 기본적인 개념에 대한 설명의 부재가 크게 다가왔다.
공통된 네트워크 호출 동작을 묶어서 사용하기 위해 싱글톤으로 NetworkManger를 생성하여 사용한다.
class NetworkManager {
static let shared = NetworkManager()
private init() {}
func fetch<T: Decodable>(url: URL) -> Single<T> {
return Single.create { observer in
let session = URLSession(configuration: .default)
session.dataTask(with: URLRequest(url: url)) { data, response, error in
if let error = error {
observer(.failure(error))
return
}
guard let data = data,
let response = response as? HTTPURLResponse,
(200..<300).contains(response.statusCode) else {
observer(.failure(NetworkError.dataFetchFail))
return
}
do {
let decodedData = try JSONDecoder().decode(T.self, from: data)
observer(.success(decodedData))
} catch {
observer(.failure(NetworkError.decodingFail))
}
}.resume()
return Disposables.create()
}
}
}
ViewModel에는 해당 앱의 비즈니스 로직을 구현하는 것에 중심을 둔다. 유사한 로직의 존재로 하나의 메서드와 Subject만을 설명하겠다.
View가 구독할 Subject를 선언해준다. 그리고 PopularMovie 데이터를 불러올 메서드 fetchPopularMovie를 생성해준다. 해당 네트워크의 fetch 결과는 Single Type이기 때문에 구독할 수 있으며 NetworkManger의 Single로부터 나온 데이터를 그대로 ViewModel의 subject로 내려보내고 있다. 그리고 View에서는 해당 subject를 구독하고 있다가 데이터가 발행 된 순간 그에 맞는 행동을 하도록 설계한다.
let popularMovieSubject = BehaviorSubject(value: [Movie]())
init() {
fetchPopularMovie()
}
func fetchPopularMovie() {
guard let url = URL(string: "https://api.themoviedb.org/3/movie/popular?api_key=\(apiKey)") else {
popularMovieSubject.onError(NetworkError.invalidUrl)
return
}
NetworkManager.shared.fetch(url: url)
.subscribe(onSuccess: { [weak self] (movieResponse: MovieResponse) in
self?.popularMovieSubject.onNext(movieResponse.results)
}, onFailure: { [weak self] error in
self?.popularMovieSubject.onError(error)
}).disposed(by: disposeBag)
}