인터넷에서 소스를 다운로드 했을 때
Trsut and Open을 클릭해서 Open한다.
실행을 해도 아무것도 보이지 않을 수 있다.
Xcode 버전이 맞지 않아서 그런 것이니 Minimum Deployment를 낮추면 정상적으로 보인다.s
네트워킹
1단계 URL 만들기
guard let url = URL(string: movieURL) else {return}
failurble initializer가 사용됨 -> optional이 리턴됨
따라서 guard let으로 언래핑함
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!)
예외 처리
예외 처리를 하지 않아 오류 발생
수정한 사진
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 해줘야한다!!
사진 처럼 수정하니 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 클래스입니다. 코드를 자세히 설명하면 다음과 같습니다.
-
MovieData
,BoxOfficeResult
,DailyBoxOfficeList
구조체는 API 응답 데이터를 디코딩하기 위한 Codable 프로토콜을 준수하는 구조체입니다. 각 구조체는 API 응답 JSON 데이터의 구조와 일치하도록 정의되어 있습니다. -
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:)
: 행이 선택되었을 때 호출되는 메서드입니다. 현재는 아무 작업도 하지 않습니다.
-
이 코드는 전날 박스오피스 순위 데이터를 가져와서 테이블 뷰에 표시합니다. 그러나 현재 테이블 뷰에 10개의 행만 표시되도록 하드코딩되어 있으며, 실제 API 응답 데이터의 개수와 일치하지 않습니다. 또한
tableView(_:cellForRowAt:)
메서드에서indexPath.row
를 사용하여dailyBoxOfficeList
배열에 접근하고 있지만, 인덱스가 배열의 범위를 벗어날 경우 런타임 오류가 발생할 수 있습니다. -
이 코드를 개선하려면
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)