본문 바로가기

iOS

[iOS] TableView에 뉴스 제목뿌리기(feat. JSON, News API)

위 두 게시글에서 이어지는 작업이다.
JSON을 사용하여 News API정보를 JSON으로 변환하여 Foundation데이터로 정보를 추출하여 TableView에 뉴스 title내용을 출력시키는 작업이다.

news API JSON 코드 접은 모습
news API JSON 코드 펼친모습

아래 첨부한 전체코드를 살펴보면 JSON데이터를 Foundation데이터로 변환하여 값을 다룰때 다운캐스팅을 굉장히 많아하는것을 볼 수 있다. 이는 JSON을 다룰 때 흔히 사용되는건지 잘 모르겠지만,,, 코드를 읽는데 많이 복잡했따. 다운캐스팅한걸 또하고 또하니 머리속에 정리가 되질않아 많이 형편없지만 그림으로 그려보았다. 그래서 그리면서 이해하는데 도움이 된것같아 다행이다.. 나중엔 더 예쁘게 그려볼게요..

News API내 JSON정보를 카테고리로 종이에 적어봄...좀 엉망..
22

 

 

# 구현 순서

1.  HTTP 통신을 시도한다. -> URLSession

2.  URL정보내 JSON데이터를 Foundation 데이터로 변환한다. -> JSONSerialization

3.  변환된 데이터를 TableView에 데이터 매칭을 시킨다. -> tableView메서드내 다운캐스팅으로 값에 접근

   3-1.  데이터를 가져왔으니 뿌려줘야함 -> DispatchQueue.main.sync { TableViewProperty.reloadData() }

 

# 전체코드

# URL

enum NewsApi {
    static let url = "https://newsapi.org/v2/top-headlines?country=kr&apiKey=cc0f1ef9a14347e38975fecc11158ce5"
}

# TableViewMain

class TableViewMain: UITableViewCell {
    @IBOutlet weak var label: UILabel!
}

# ViewController

//  ViewController.swift
//  NewsAPITableView
//  Created by LIMGAUI on 2022/04/10

import UIKit

class ViewController: UIViewController {
    @IBOutlet weak var TableViewMain: UITableView!
    // TableView에 테이터 매칭을 하기위해서 변수에 할당을 해줘야함
    // tableView내에 중복코드가 발생하기떄문에 변수에 담아서 관리해주는게 좋음
    var newsData: [[String: Any]]?
    
    override func viewDidLoad() {
        super.viewDidLoad()
        setTableViewMainOwnerToSelf()
        getNews()
    }
}

// MARK: method
extension ViewController: UITableViewDelegate, UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        if let news = newsData {
            return news.count
        } else {
            return .zero
        }
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        // 1. TableView Cell개체를 반환하고 테이블에 추가한다.
        let cell = TableViewMain.dequeueReusableCell(withIdentifier: "TableViewMain", for: indexPath) as! TableViewMain
            
            let indexPathRow = indexPath.row
            // 2. getNews메서드에서 추출한 Foudation news데이터로 접근한다.
            if let news = newsData {
                let rowData = news[indexPathRow]
                
                if let row = rowData as? [String: Any] {
                    
                    if let title = row["title"] as? String {
                        cell.label.text = title
                    }
                }
            }
        return cell
    }
    
    func setTableViewMainOwnerToSelf() {
        TableViewMain.delegate = self
        TableViewMain.dataSource = self
    }
    
    func getNews() {
        // 1. URL정보를 가져온다
        guard let url = URL(string: NewsApi.url) else { return }
        // 2. URL정보 가져오기 성공시 dataTask메서드를 실행한다.
        let task = URLSession.shared.dataTask(with: url) { data, response, error in
            
            if let newsData = data {
                // 3-1. JSONSerialization.jsonObject메서드를 사용할때 변환을 실패할 수 있기때문에 do catch문내에서 작업해준다.
                do {
                    // 3. JSON Parsing 작업을 시도한다. JSON -> Foundation데이터로 변환
                    guard let json = try JSONSerialization.jsonObject(with: newsData, options: []) as? [String: Any] else { return }
                    
                    // 4. Parsing을 완료한 데이터로 읽어와야하는 값을 Dictionary Key로 접근하여 정보를 읽어온다.
                    guard let articles = json["articles"] as? [[String: Any]] else { return }
                    // 5. 정보를 외부 전역변수에 할당
                    self.newsData = articles
                    // 6. TableView정보의 값을 변경했으니 reloadData메서드를 TableView의 행과 섹션을 다시 로드합니다.(업데이트 작업)
                    //  tableViewMain에 통보를 해줘야한다. 
                    //  그러면 TableViewMain은 화면에 그리기 시작한다. 
                    //  이때 중요한 개념이 하나나오는데 네트워크의 기본 원칙이 존재한다. 
                    //   모바일은 네트워크를 통신하게되면 일하는 쓰레드 즉,일꾼이 존재한다. 
                    //   그 일꾼들은 background: network에서 일하는데 현재 그려야하는 위치가 UI: Main에 해야하는것이다. 
                    //  그래서 Main에 그려라라는 메서드인 DispatchQueue.main.async {} 
                    //  비동기로 바로 작업을 해주라는 코드를 작성해주어야한다.
                    // 6-1. 위와같은 설명에 이유때문에 DispatchQueue내부에 넣어준다.
                    DispatchQueue.main.sync {
                        self.TableViewMain.reloadData()
                    }
                } catch {}
            }
        }
        // 7. 작업이 일시 중단된 경우 작업을 다시 시작한다.
        task.resume()
    }
}

중요한 내용인데 reloadData()메서드를 실행시킬때 모바일로 네트워크 통신을 할경우에는 background인 즉 내부에서 일꾼들이 일을 하다보니 내부에서 보여지는 작업이랑 UI에서 표시되는거랑은 상반된다고 한다. 예를들면 오리가 호수에서 발을 동동구르면 밖에서는 오리발이 보이지않는 것과 같은 현상 처럼 말이다. 그래서 UI에 노출을 시켜줄 수 있어야하는데 이때 사용하는게 DispatchQueue.main.sync 이다. 그래서 이 메서드 내에서 사용하게되면 사용자 UI에 데이터를 뿌려주는 역할이기때문에 정상적으로 업데이트되는걸 볼 수 있다.

 

만일 DispatchQueue.main.sync 를 사용하지않고 실행하게되면 하단의 사진처럼 보라색에러를 만나볼 수있다.

reloadData()메서드를 background에 호출했을때 발생하는 에러

에러 메세지를 보면 must be used main thread only라고 말하고있다. main 즉, UI를 말하는것이다. background가 networt이고 main은 UI를 가르킨다고 볼 수 있다.

 

# 실행화면

 

 

- Reference
- https://newsapi.org/s/south-korea-news-api
- https://www.youtube.com/watch?v=5ejngRFNy_k