
Swift에서 원격 API의 데이터를 가져오는 과정은 크게 데이터 모델 설계, URL 생성, 네트워크 요청, 데이터 파싱의 4단계로 나뉜다
1단계: 데이터 모델 만들기 (Codable)
먼저 API가 주는 JSON 데이터의 구조와 일치하는 Swift struct를 만듭니다. Codable 프로토콜을 채택해야 JSON과 객체 사이의 변환이 가능합니다.
struct Post: Codable {
let userId: Int
let id: Int
let title: String
let body: String
}
2단계: URL 및 Request 설정
접속할 API의 주소를 확인하고 URL 객체를 생성합니다. 필요하다면 헤더(Header)나 메서드(GET, POST 등)를 설정하기 위해 URLRequest를 사용합니다.
guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts/1") else {
print("Invalid URL")
return
}
3단계: 네트워크 요청 보내기 (URLSession)
URLSession의 data(from:) 메서드를 사용하여 비동기적으로 데이터를 호출합니다. async/await를 사용하면 코드가 동기식처럼 깔끔해집니다.
// 비동기 함수 내에서 실행
let (data, response) = try await URLSession.shared.data(from: url)
// HTTP 상태 코드 확인 (200번대인지 체크)
guard (response as? HTTPURLResponse)?.statusCode == 200 else {
throw URLError(.badServerResponse)
}
4단계: 데이터 디코딩 (JSONDecoder)
받아온 바이너리 데이터(Data)를 1단계에서 만든 모델 객체로 변환합니다.
let decoder = JSONDecoder()
let post = try decoder.decode(Post.self, from: data)
print("제목: \(post.title)")

옵셔널임


tab키를 누른 뒤 enter를 누르면 후행클로저 스타일로 자동 변경
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 {return}
guard let JSONdata = data else{return}
print(JSONdata)
}
task.resume()
}
Instantly parse JSON in any language | quicktype
app.quicktype.io
복잡한 JSON을 구조체로 바꿔줌
import Foundation
// MARK: - Welcome
struct Welcome {
let boxOfficeResult: BoxOfficeResult
}
// MARK: - BoxOfficeResult
struct BoxOfficeResult {
let boxofficeType, showRange: String
let dailyBoxOfficeList: [DailyBoxOfficeList]
}
// MARK: - DailyBoxOfficeList
struct DailyBoxOfficeList {
let rnum, rank, rankInten: String
let rankOldAndNew: RankOldAndNew
let movieCD, movieNm, openDt, salesAmt: String
let salesShare, salesInten, salesChange, salesAcc: String
let audiCnt, audiInten, audiChange, audiAcc: String
let scrnCnt, showCnt: String
}
enum RankOldAndNew: String {
case old
}
LLM(gemini) 활용
import Foundation
// 1. 최상위 루트 객체
struct MovieData: Codable {
let boxOfficeResult: BoxOfficeResult
}
// 2. 중간 계층 객체
struct BoxOfficeResult: Codable {
let boxofficeType: String
let showRange: String
let dailyBoxOfficeList: [DailyBoxOfficeList]
}
// 3. 실제 영화 상세 정보 객체
struct DailyBoxOfficeList: Codable {
let rank: String // 순위
let movieNm: String // 영화명
let openDt: String // 개봉일
let audiAcc: String // 누적 관객수
let movieCd: String // 영화 코드 (상세 정보 조회용)
// JSON의 키값이 복잡하다면 필요한 것만 골라 담아도 됩니다.
}


import UIKit
// MARK: - 1. 데이터 모델 (JSON 구조와 일치해야 함)
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: - 2. 메인 화면 컨트롤러
class ViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
// 스토리보드에서 연결된 테이블 뷰
@IBOutlet weak var table: UITableView!
// 서버에서 받아온 데이터를 담아둘 변수 (처음엔 데이터가 없으니 옵셔널 ?)
var movieData: MovieData?
// API 기본 주소 (마지막에 날짜만 붙이면 됨)
var movieURL = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=eaa1c89ff79f8f288c9366ac031f7510&targetDt="
override func viewDidLoad() {
super.viewDidLoad()
// 테이블뷰에게 "일 시킬 사람(대리자)은 나야"라고 알려줌
table.dataSource = self
table.delegate = self
// 1. 어제 날짜 문자열을 생성해서 URL 뒤에 붙임
movieURL += makeYesterdayString()
// 2. 서버로 데이터 요청 시작
getData()
}
// MARK: - 3. 유틸리티 함수 (날짜 계산)
func makeYesterdayString() -> String {
// 현재 날짜(Date())에서 하루(.day, -1)를 뺀 날짜 계산
let yesterday = Calendar.current.date(byAdding: .day, value: -1, to: Date()) ?? Date()
// 날짜 형식을 yyyyMMdd(예: 20260511)로 지정
let formatter = DateFormatter()
formatter.dateFormat = "yyyyMMdd"
return formatter.string(from: yesterday)
}
// MARK: - 4. 네트워크 통신 함수
func getData() {
// 주소 문자열을 실제 URL 객체로 변환
guard let url = URL(string: movieURL) else { return }
// 통신을 수행할 세션 객체 생성
let session = URLSession(configuration: .default)
// 데이터를 가져오는 작업(Task) 정의
let task = session.dataTask(with: url) { data, response, error in
// 에러가 있다면 종료
if error != nil { return }
// 서버가 준 데이터가 있다면 JSONdata에 저장
guard let JSONdata = data else { return }
// JSON 데이터를 MovieData 구조체로 변환 시도
let decoder = JSONDecoder()
do {
let decodedData = try decoder.decode(MovieData.self, from: JSONdata)
// 성공적으로 파싱된 데이터를 클래스 변수에 저장
self.movieData = decodedData
// 중요: UI 관련 작업(테이블 갱신)은 반드시 '메인 스레드'에서 처리
DispatchQueue.main.async {
self.table.reloadData() // 테이블 뷰 다시 그리기
}
} catch {
print("파싱 에러: \(error)")
}
}
// 일시정지 상태인 task를 실행(시작)시킴
task.resume()
}
// MARK: - 5. 테이블 뷰 프로토콜 (화면 구성)
// 몇 줄을 보여줄 것인가? (보통 데이터 배열의 개수만큼)
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return movieData?.boxOfficeResult.dailyBoxOfficeList.count ?? 0
}
// 각 줄(셀)에 어떤 내용을 넣을 것인가?
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
// 재사용 가능한 셀을 가져와서 MyTableViewCell로 변환
let cell = tableView.dequeueReusableCell(withIdentifier: "myCell", for: indexPath) as! MyTableViewCell
// 현재 순서(indexPath.row)에 맞는 영화 데이터를 가져옴
let movie = movieData?.boxOfficeResult.dailyBoxOfficeList[indexPath.row]
// 셀에 있는 라벨에 영화 제목 표시
cell.movieName.text = movie?.movieNm
return cell
}
// 셀을 클릭했을 때 실행되는 코드
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
print("\(indexPath.row + 1)위를 선택했습니다.")
}
}


.self를 붙이는 이유
타입 자체(구조체나 클래스의 타입)를 인스턴스처럼 쓰려 할 때 .self를 써서 명확히 타입임을 알려야 함 여기서 MovieData는 구조체 타입(자료형)이지 인스턴스가 아님
'iOS앱개발' 카테고리의 다른 글
| iOS실무 12주차 (0) | 2026.05.26 |
|---|---|
| iOS실무 11주차 (0) | 2026.05.19 |
| iOS실무 9주차 (0) | 2026.04.28 |
| iOS실무 7주차 (0) | 2026.04.14 |
| iOS실무 6주차 (0) | 2026.04.07 |