ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Swift MVVM 패턴 Network -> Decode -> View
    개발/Swift 2021. 1. 12. 14:46

    네트워킹과 받아온 데이터를 모델로 디코딩 후에 테이블 뷰에 보여주는 간단한 예제이다.

    먼저 모델을 만든다.

    각 뉴스의 내용을 담을 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 에 접근할수있다.

    댓글

Designed by Tistory.