Swift MVVM 패턴 Network -> Decode -> View
iOS Clean Architecture & MVVM: RxSwift 완전 정복 강의 | 덤벨로퍼 - 인프런
덤벨로퍼 | Clean Architecture와 MVVM 패턴을 실무에서 적용할 수 있도록 설명하며, RxSwift, Concurrency 등 필수 기술을 다룹니다., iOS Clean Architecture & MVVM: RxSwift 완전 정복현업에서 Clean
www.inflearn.com
네트워킹과 받아온 데이터를 모델로 디코딩 후에 테이블 뷰에 보여주는 간단한 예제이다.
먼저 모델을 만든다.
각 뉴스의 내용을 담을 Article, Article의 리스트 형태인 ArticleList 이다
struct ArticleList : Codable {
let articles :[Article]
}
struct Article : Codable {
let title :String
let description : String?
}
1. Codable은 추후에 디코딩을 위해 필요하다. 서버로 보낼 데이터가 아니라면 Decodable로 사용해도된다.
2. description 은 옵셔널 String 타입으로 되어있다. 이는 나중에 에러가 발생해서 수정했던 내용이다. 먼저 얘기 하자면 desctiption에 null값이 들어갈수 있기 때문이다.
네트워크
func getArticles(url:URL,completion: @escaping ([Article]?)->()){
URLSession.shared.dataTask(with: url){ data , response, error in
if let error = error {
print(error.localizedDescription)
completion(nil)
}else if let fetchedData = data {
let articleList = try? JSONDecoder().decode(ArticleList.self,from:fetchedData)
if let articleList = articleList {
completion(articleList.articles)
}
print(articleList?.articles)
}
}.resume()
}
getArticles 라는 API 호출 메소드를 만들었다. 이 메소드는 view에서 실행할 것이다.
view 에서 getArticle() 함수를 사용하면서 콜백함수를 사용할것이다. 이럴때 @escaping을 사용해준다.
completion: @escaping ([Article]?)->()
에러처리를 우선 해준다음
data 가 들어오면 디코딩 작업을해준다.
let articleList = try? JSONDecoder().decode(ArticleList.self,from:fetchedData)
try? 의경우 에러가 발생해도 에러가 나지않고 articleList는 nil이 들어간다.
try! 를 사용하면 에러발생시 에러가 나고 코드가 진행되지않는다.
여기서 이런 에러가 발생했다.
에러 발생
Swift.DecodingError.valueNotFound(Swift.String, Swift.DecodingError.Context(codingPath: [CodingKeys(stringValue: "articles", intValue: nil), _JSONKey(stringValue: "Index 11", intValue: 11), CodingKeys(stringValue: "description", intValue: nil)], debugDescription: "Expected String value but found null instead.", underlyingError: nil)
쉽게는 "description" 에 null 값이 들어가서 에러가 난 경우이다.
그래서 아까처럼 모델을 옵셔널 타입으로 변경했다.
ArticleList 모델로 디코딩이 완료되면 모델을 넘겨주는 completion이라는 콜백 함수를 실행한다.
실행하는 view는 잠시후에 보고 viewModel 먼저 보자
viewModel
struct ArticleViewModel {
private let article : Article
}
extension ArticleViewModel{
init(_ article:Article) {
self.article = article
}
}
extension ArticleViewModel{
var title :String {
return self.article.title
}
var description :String? {
return self.article.description
}
}
ㄹ먼저 Article 모델의 뷰모델이다.
간단하게 init함수 와 필드 get 변수 들이있다.
extension 하지않고 struct 안에 해도 상관없다.
struct ArticleListViewModel {
let articles :[Article]
}
extension ArticleListViewModel{
var numberOfSection:Int{
return 1
}
func numberOfRowInSetion() -> Int{
return self.articles.count
}
func articleAtIndex(index:Int) -> ArticleViewModel{
let viewModel = ArticleViewModel.init(self.articles[index])
return viewModel;
}
}
ㅁArticle 리스트 형태인 필드와
추후에 TableView에서 사용할 메소드들을 넣었다.
viewController
let url = URL(string: "fetch 할 url 주소")!
Webservice().getArticles(url: url) { articles in
if let articles = articles{
self.articlesViewModel = ArticleListViewModel(articles:articles)
}
//reload table
DispatchQueue.main.async {
self.tableView.reloadData()
}
}
getArticle함수를 사용하면 콜백으로 articles가 리턴될것이다.
articles 는 여기서 뷰 모델로 바꾼후 글로벌로 저장한다.
DispatchQueue.main.async 를 사용해서 이후에 view를 리로드 해준다.
override func numberOfSections(in tableView: UITableView) -> Int {
return self.articlesViewModel == nil ?0: self.articlesViewModel.numberOfRowInSetion()
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return self.articlesViewModel == nil ? 0 : self.articlesViewModel.numberOfRowInSetion()
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(withIdentifier: "ArticleTableViewCell", for: indexPath) as? ArticleTableViewCell else {
fatalError("not found")
}
let articleVm = articlesViewModel.articleAtIndex(index: indexPath.row)
cell.titleLabel.text = articleVm.title
cell.descriptionLabel.text = articleVm.description
return cell
}
테이블 뷰를 위한 오버라이딩 메소드이다.
아까 뷰모델에서 article 개수와 섹션갯수 , article 객체 가져오는 메소드를 만들어놔서 사용했다.
마지막 에사용하는 ArticleTableViewCell 은 따로 만들어놨고 as ArticleTableViewCell 를 사용해 그안에있던
label text 에 접근할수있다.