본문 바로가기
iOS앱개발

iOS실무 10주차

by 김경래 2026. 5. 12.

 

 

 

 

 

 

 

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()
    }

 

 

 

https://app.quicktype.io/

 

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