인터넷에서 소스를 다운로드 했을 때


image
Trsut and Open을 클릭해서 Open한다.

image
실행을 해도 아무것도 보이지 않을 수 있다.

image
Xcode 버전이 맞지 않아서 그런 것이니 Minimum Deployment를 낮추면 정상적으로 보인다.s

네트워킹


image

1단계 URL 만들기

guard let url = URL(string: movieURL) else {return}

failurble initializer가 사용됨 -> optional이 리턴됨

따라서 guard let으로 언래핑함

image

2단계 URLSession 만들기

let session = URLSession(configuration: .default)

3단계 URlSession 인스턴스에게 task 주기

let task = session.dataTask(with: url)  { data, response, error in
    if error != nil {
        print(error!)
        return
    }
    guard let JSONdata = data else { return }
    print(JSONdata)
}

소스를 수정하고 실행해도 동작을 하지 않는다.

4단계 task를 resume()

task.resume()

새로 초기화된 작업은 일시 중단된 상태에서 시작되므로, 이 메서드를 호출하여 작업을 시작해야함

data를 String으로 찍어보기

guard let JSONdata = data else { return }
let dataString = String(data: JSONdata, encoding: .utf8)
print(dataString!)

예외 처리

예외 처리를 하지 않아 오류 발생 image

image 수정한 사진

cell에 data 적용

func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
    cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm
    //print(indexPath.description)
    return cell
}

table읋 reload 해줘야한다!!

image

사진 처럼 수정하니 main thread에서 동작을 해야한다는 오류가 발생했다.

DispatchQueue.main.async {
    self.table.reloadData()
}

이렇게 수정해서 main thread에서 처리하면 된다.

완성된 getData()

func getData() {
    guard let url = URL(string: movieURL) else {return}
    let session = URLSession(configuration: .default)
    let task = session.dataTask(with: url)  { data, response, error in
        if error != nil {
            print(error!)
            return
        }
        guard let JSONdata = data else { return }
        print(JSONdata)
        let decoder = JSONDecoder()
        do {
            let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
            self.movieData = decodedData
            DispatchQueue.main.async {
                self.table.reloadData()
            }
        }catch{
            
        }        }
    task.resume()
}

어제의 날짜를 기준으로 자동으로 조회하는 소스를 추가한 버젼


//
//  ViewController.swift
//  MovieCMU
//
//  Created by Induk-cs  on 2024/05/02.
//

import UIKit

// 영화 진흥위원회 API 응답 데이터를 저장할 구조체들 정의
struct MovieData : Codable {
    let boxOfficeResult : BoxOfficeResult
}

struct BoxOfficeResult : Codable {
    let dailyBoxOfficeList : [DailyBoxOfficeList]
}

struct DailyBoxOfficeList : Codable {
    let movieNm : String
    let audiCnt : String
    let audiAcc : String
    let rank : String
}

// ViewController 클래스는 UITableViewDelegate, UITableViewDataSource 프로토콜을 준수합니다.
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {

    // 스토리보드에서 연결된 UITableView 인스턴스
    @IBOutlet weak var table: UITableView!

    // API 응답 데이터를 저장할 MovieData 구조체 인스턴스
    var movieData : MovieData?

    // 영화 진흥위원회 API URL 문자열
    var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=d9dfbf5b0a8b138015b48bd3b9ecfe2b&targetDt="

    override func viewDidLoad() {
        super.viewDidLoad()

        // 테이블 뷰의 데이터소스와 델리게이트를 ViewController 인스턴스로 설정
        table.dataSource = self
        table.delegate = self

        // API URL에 전날 날짜 추가
        movieURL += makeYesterdayString()

        // API 데이터 가져오기
        getData()
    }

    // 전날 날짜를 "yyyyMMdd" 형식의 문자열로 반환하는 함수
    func makeYesterdayString() -> String {
        let today = Date()
        let calendar = Calendar.current
        let yesterday = calendar.date(byAdding: .day, value: -1, to: today)!
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMdd"
        let yesterdayString = formatter.string(from: yesterday)
        return yesterdayString
    }

    // API 데이터 가져오기
    func getData() {
        // API URL 인스턴스 생성
        guard let url = URL(string: movieURL) else {return}
        let session = URLSession(configuration: .default)

        // API 요청 작업 생성 및 실행
        let task = session.dataTask(with: url)  { data, response, error in
            if error != nil {
                print(error!)
                return
            }

            // 응답 데이터 처리
            guard let JSONdata = data else { return }
            print(JSONdata)
            let decoder = JSONDecoder()
            do {
                // JSON 데이터 디코딩
                let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
                self.movieData = decodedData

                // 메인 큐에서 테이블 뷰 다시 로드
                DispatchQueue.main.async {
                    self.table.reloadData()
                }
            }catch{
            }
        }
        task.resume()
    }

    // 테이블 뷰 섹션당 행의 개수 반환 (하드코딩된 값 10)
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 10
    }

    // 테이블 뷰 셀 구성
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell

        // 영화 제목 설정 (indexPath.row를 사용하여 dailyBoxOfficeList 배열에 접근)
        cell.movieName.text = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row].movieNm

        return cell
    }

    // 테이블 뷰 섹션 개수 반환
    func numberOfSections(in tableView: UITableView) -> Int {
        return 1
    }

    // 행 선택 시 호출되는 메서드 (현재는 아무 작업도 하지 않음)
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
    }
}

이 소스코드는 Swift와 UIKit 프레임워크를 사용하여 영화 진흥위원회의 API를 호출하여 전날 박스오피스 순위 데이터를 가져와서 UITableView에 표시하는 iOS 앱의 ViewController 클래스입니다. 코드를 자세히 설명하면 다음과 같습니다.

  1. MovieData, BoxOfficeResult, DailyBoxOfficeList 구조체는 API 응답 데이터를 디코딩하기 위한 Codable 프로토콜을 준수하는 구조체입니다. 각 구조체는 API 응답 JSON 데이터의 구조와 일치하도록 정의되어 있습니다.

  2. ViewController 클래스는 UITableViewDelegate, UITableViewDataSource 프로토콜을 준수하며, 다음과 같은 프로퍼티와 메서드를 포함합니다.

    • table: UITableView 인스턴스를 참조하는 IBOutlet 변수입니다.
    • movieData: API 응답 데이터를 저장하는 MovieData 구조체 인스턴스입니다.
    • movieURL: 영화 진흥위원회 API의 URL 문자열입니다. makeYesterdayString() 함수를 호출하여 전날 날짜를 추가합니다.
    • viewDidLoad(): 뷰 컨트롤러의 생명주기 메서드로, 테이블 뷰의 데이터소스와 델리게이트를 설정하고 getData() 메서드를 호출하여 API 데이터를 가져옵니다.
    • makeYesterdayString(): 전날 날짜를 “yyyyMMdd” 형식의 문자열로 반환하는 함수입니다.
    • getData(): API 데이터를 가져오기 위해 URLSession을 사용하여 API 요청을 보내고, 응답 데이터를 디코딩하여 movieData 프로퍼티에 저장합니다. 그리고 메인 큐에서 테이블 뷰를 다시 로드합니다.
    • tableView(_:numberOfRowsInSection:): 테이블 뷰에 표시할 행의 개수를 10으로 반환합니다.
    • tableView(_:cellForRowAt:): 테이블 뷰 셀을 구성합니다. MyTableViewCell 클래스의 인스턴스를 생성하고 movieNm 프로퍼티 값을 설정합니다.
    • numberOfSections(in:): 테이블 뷰의 섹션 개수를 1로 반환합니다.
    • tableView(_:didSelectRowAt:): 행이 선택되었을 때 호출되는 메서드입니다. 현재는 아무 작업도 하지 않습니다.
  3. 이 코드는 전날 박스오피스 순위 데이터를 가져와서 테이블 뷰에 표시합니다. 그러나 현재 테이블 뷰에 10개의 행만 표시되도록 하드코딩되어 있으며, 실제 API 응답 데이터의 개수와 일치하지 않습니다. 또한 tableView(_:cellForRowAt:) 메서드에서 indexPath.row를 사용하여 dailyBoxOfficeList 배열에 접근하고 있지만, 인덱스가 배열의 범위를 벗어날 경우 런타임 오류가 발생할 수 있습니다.

  4. 이 코드를 개선하려면 tableView(_:numberOfRowsInSection:) 메서드에서 실제 API 응답 데이터의 개수를 반환하도록 수정해야 합니다. 또한 tableView(_:cellForRowAt:) 메서드에서 인덱스 범위 검사를 추가하거나 옵셔널 체이닝을 사용하여 안전하게 데이터에 접근할 수 있도록 해야 합니다.

코드 리팩토링

import UIKit

// MARK: - Structs
struct MovieData: Codable {
    let boxOfficeResult: BoxOfficeResult
}

struct BoxOfficeResult: Codable {
    let dailyBoxOfficeList: [DailyBoxOfficeList]
}

struct DailyBoxOfficeList: Codable {
    let movieNm: String
    let audiCnt: String
    let audiAcc: String
    let rank: String
}

// MARK: - ViewController
class ViewController: UIViewController {
    
    @IBOutlet private weak var tableView: UITableView!
    
    private var movieData: MovieData?
    private let apiURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=d9dfbf5b0a8b138015b48bd3b9ecfe2b&targetDt="
    
    override func viewDidLoad() {
        super.viewDidLoad()
        configureTableView()
        fetchMovieData()
    }
    
    private func configureTableView() {
        tableView.dataSource = self
        tableView.delegate = self
    }
    
    private func fetchMovieData() {
        guard let url = URL(string: apiURL + makeYesterdayString()) else { return }
        
        URLSession.shared.dataTask(with: url) { [weak self] data, _, error in
            if let error = error {
                print(error)
                return
            }
            
            guard let data = data else { return }
            
            do {
                let decodedData = try JSONDecoder().decode(MovieData.self, from: data)
                self?.movieData = decodedData
                DispatchQueue.main.async {
                    self?.tableView.reloadData()
                }
            } catch {
                print(error)
            }
        }.resume()
    }
    
    private func makeYesterdayString() -> String {
        let calendar = Calendar.current
        let yesterday = calendar.date(byAdding: .day, value: -1, to: Date())
        let formatter = DateFormatter()
        formatter.dateFormat = "yyyyMMdd"
        return formatter.string(from: yesterday ?? Date())
    }
}

// MARK: - UITableViewDataSource
extension ViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return movieData?.boxOfficeResult.dailyBoxOfficeList.count ?? 0
    }
    
    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
        
        guard let movieData = movieData else { return cell }
        let movie = movieData.boxOfficeResult.dailyBoxOfficeList[safe: indexPath.row]
        cell.movieName.text = movie?.movieNm
        
        return cell
    }
}

// MARK: - UITableViewDelegate
extension ViewController: UITableViewDelegate {
    func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        // 행 선택 시 실행할 코드
    }
}

// MARK: - Array Extension
extension Array {
    subscript(safe index: Int) -> Element? {
        return indices.contains(index) ? self[index] : nil
    }
}








출처


  • Smile Han의 iOS 프로그래밍 실무, 한성현(출판 예정), PPT로 제공
  • Do it! 스위프트로 아이폰 앱 만들기 입문(개정 7판)(이지스퍼블리싱,송호정, 이범근, 2023.1)
  • 핵심만 골라 배우는 SwiftUI 기반의 iOS 프로그래밍(제이펍, 닐 스미스, 2023.12)
    • https://www.techotopia.com/index.php/IOS_iPhone_iPad_eBooks
  • 스위프트 프로그래밍(Swift 5) 3판(한빛미디어, 야곰, 2019.10)
  • 꼼꼼한 재은 씨의 스위프트 기본편 (루비페이퍼, 이재은, 2018.05)
  • 꼼꼼한 재은 씨의 스위프트 실전편 (Swift) (루비페이퍼, 이재은, 2018.08)
  • 꼼꼼한 재은 씨의 Swift 문법편 (루비페이퍼, 이재은, 2017.12)