본문 바로가기

카테고리 없음

iOS 프로그래밍 실무 10주차

(기말 80%)

5월 3일 기준 pretty print 적용

적용 X
네트워킹 4단계 정리

네트워크에서 무언가를 가져오는 경우

 

주소를 저장할 함수 getData

 

init?(string: String)

 

init(string:) | Apple Developer Documentation

Creates a URL instance from the provided string.

developer.apple.com

init?(string: String) 에서 ? 는 실패 가능한 초기화 구문(Failable Initializer) 이라는 뜻이야.

즉, 객체 생성이 성공할 수도 있고 실패할 수도 있다는 의미야.

예를 들어:

 
struct Person {
    let age: Int

    init?(age: Int) {
        if age < 0 {
            return nil
        }
        self.age = age
    }
}
 

사용:

 
let p1 = Person(age: 20)   // 성공
let p2 = Person(age: -1)   // nil
 

여기서 p2는 객체 생성이 실패해서 nil이 돼.


init 와 init? 차이 정리:

형태의미
init() 반드시 생성 성공
init?() 생성 실패 가능 (nil 반환 가능)

네가 본:

 
init?(string: String)
 

은 보통 문자열을 어떤 타입으로 변환할 때 많이 사용돼.

예:

 
let num = Int("123")   // 성공 → Optional(123)
let num2 = Int("abc")  // 실패 → nil
 

왜냐면 "abc" 는 숫자로 변환할 수 없으니까 객체 생성이 실패하는 거야.

그래서 Int의 생성자는 내부적으로 이런 느낌:

 
init?(string: String)
 

처럼 실패 가능하게 만들어져 있어.

핵심:

  • ? = 실패 가능
  • 실패하면 nil
  • 그래서 결과 타입이 Optional 이 됨 (Int?, Person?)

-> 옵셔널형으로 만들어진다.

 

 

 

1단계

URL(string:) -> 실패 가능한 초기화 구문(Failable Initializer) -> 객체가 옵셔널으로 나옴사용하려면 옵셔널을 풀어야함

옵셔널 ?로 되어있음

if let

guard let ->더 추천

url 인스턴스를 넣을때 주소가 없을 수도 있음으로 nil이 출력되어야헤서 옵셔널형으로 나옴 

1. if let 2. guard let중 더 가독성 좋은 guard let을 선호한다

 

 

2단계

init(configuration: URLSessionConfiguration)

 

init(configuration:) | Apple Developer Documentation

Creates a session with the specified session configuration.

developer.apple.com

 

.default 가장 많이 씀

 

3단계(*중요*)

datatask 함수안에 클로저가 들어감(후행 클로저 스타일)

 

후행클로저 스타일로 변경하기

enter를 누르면 자동변경

Data?, URLResponse?, Error?

3개 자료형 -> 옵셔널형

데이터를 받기전에 에러체크(에러시 return)

 

4단계

 

func getData(){

        guard let url =  URL(string: movieURL) else { return } //네트워킹 1단계 - 주소 만들기

        let session = URLSession(configuration: .default)//네트워킹 2단계 - 택배회사 정하기

        let task = session.dataTask(with: url) { data, response, error in

            if error != nil{

                print(error!)

                return

            }//네트워킹 3단계 - 배송 요청서 작성

            //dataTask -> 이 주소로 가서 데이터를 가져와라

            guard let JSONdata = data else{ return }

            print(JSONdata)

        }

        task.resume()//내트워킹 4단계 - 택배출발

    }

->

이 코드는 Swift에서 URLSession을 사용해서 인터넷에서 데이터를 가져오는 네트워킹 코드야.
주석도 잘 달아놨는데, 각 단계가 실제로 무엇을 하는지 흐름까지 자세히 설명해볼게.


전체 흐름

이 함수의 목적:

 
func getData()
 

👉 movieURL 주소로 이동해서
👉 서버 데이터를 받아오는 것

예를 들면:

  • 영화 API
  • 날씨 API
  • 로그인 서버
  • JSON 데이터

같은 걸 가져올 때 사용해.


코드 전체

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

    task.resume()
}
 

1단계 — URL 만들기

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

역할

문자열 주소를 실제 URL 객체로 변환

예:

 
let movieURL = "https://example.com/movie"
 

 
URL(string: movieURL)
 

 
URL 객체 생성
 

왜 guard let 사용?

URL(string:) 은 실패 가능 생성자야.

즉:

 
URL(string: "잘못된 주소")
 

이런 경우 실패할 수 있음.

그래서 반환형이:

 
URL?
 

(Optional)

이라서 안전하게 언래핑해야 해.


guard let 의미

 
guard let url = ... else { return }
 

의 뜻:

  • URL 생성 성공 → 계속 진행
  • 실패 → 함수 종료

2단계 — URLSession 만들기

 
let session = URLSession(configuration: .default)
 

역할

인터넷 통신 담당 객체 생성

쉽게 말하면:

“통신 회사 선택”


URLSession 이란?

애플이 제공하는 네트워크 관리자 객체

할 수 있는 것:

  • 데이터 다운로드
  • 파일 업로드
  • API 호출
  • 이미지 다운로드

등등


configuration: .default

기본 설정 사용

즉:

  • 일반 캐시 사용
  • 쿠키 사용
  • 기본 네트워크 정책 사용

가장 흔하게 사용되는 설정


3단계 — dataTask 생성

 
let task = session.dataTask(with: url) { data, response, error in
 

역할

실제 요청(Request) 만들기

쉽게 말하면:

“이 주소 가서 데이터 가져와”


dataTask란?

비동기 네트워크 작업

서버에 요청 보내고 결과를 받아옴.


클로저 부분 설명

 
{ data, response, error in
 

서버 응답이 오면 실행되는 코드

서버가 응답하면 자동 호출됨.


매개변수 3개

이름의미
data 실제 받은 데이터
response 서버 응답 정보
error 오류 정보

error 처리

 
if error != nil{
    print(error!)
    return
}
 

의미

네트워크 실패했는지 확인

예:

  • 인터넷 끊김
  • 주소 오류
  • 서버 다운

왜 return?

에러 발생 시 더 진행하면 안 되니까 함수 종료


더 안전한 코드

보통 이렇게 많이 씀:

 
if let error = error {
    print(error)
    return
}
 

강제 언래핑(!) 피할 수 있어서 더 안전함.


data 언래핑

 
guard let JSONdata = data else { return }
 

의미

서버가 실제 데이터를 보냈는지 확인

data 타입은:

 
Data?
 

Optional 이라 언래핑 필요


Data란?

바이트(binary) 데이터

예:

  • JSON
  • 이미지
  • 영상
  • 텍스트

모든 인터넷 데이터는 결국 Data 형태로 옴.


print(JSONdata)

 
print(JSONdata)
 

현재는 원시 데이터 출력

결과 예시:

 
876 bytes
 

같은 식으로 나옴.


실제 앱에서는 보통 이렇게 변환

JSON 문자열 보기:

 
let str = String(data: JSONdata, encoding: .utf8)
print(str)
 

또는

 
JSONDecoder()
 

로 모델 객체 변환


4단계 — task 시작

 
task.resume()
 

매우 중요

이거 안 쓰면 네트워크 시작 안 됨.


왜?

dataTask() 는 작업만 생성함.

아직 시작 안 한 상태.

즉:

 
let task = ...
 

“택배 예약만 함”

상태


resume()

 
task.resume()
 

해야 실제 요청 시작

즉:

“택배 출발”


전체 흐름 그림

1. URL 생성
   ↓
2. URLSession 생성
   ↓
3. dataTask 생성
   ↓
4. 서버 요청
   ↓
5. 서버 응답
   ↓
6. data / error 처리
 

중요한 개념 — 비동기(Async)

이 코드 핵심 중 하나

 
session.dataTask
 

는 비동기 작업임.

즉:

 
getData()
print("끝")
 

하면

출력 순서가:

끝
(나중에 서버 응답)
 

일 수 있음.

왜냐면 인터넷 응답은 시간이 걸리니까.


실제 앱에서 자주 추가하는 것

JSON 디코딩

 
let decoder = JSONDecoder()
let result = try decoder.decode(Movie.self, from: JSONdata)
 

메인스레드 UI 업데이트

네트워크는 백그라운드에서 실행됨.

그래서 UI 수정 시:

 
DispatchQueue.main.async {
    self.label.text = "완료"
}
 

필요함.


한 줄 요약

이 코드는:

URL 생성
→ 네트워크 세션 생성
→ 서버에 요청
→ 응답 받기
→ 데이터 출력
 

하는 Swift 기본 네트워킹 코드야.

 let dataString = String(data: JSONdata, encoding: .utf8)

            print(dataString!)을 통해 영화진흥위원회 데이터를 인코딩하여 datastring에 저장

 

 

https://app.quicktype.io/

 

Instantly parse JSON in any language | quicktype

 

app.quicktype.io

JSON파일을 넣음->파싱을 쉽게 해줌

// This file was generated from JSON Schema using quicktype, do not modify it directly.
// To parse the JSON, add this file to your project and do:
//
//   let welcome = try? JSONDecoder().decode(Welcome.self, from: jsonData)

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
}

 

AI활용 -> 파싱을 쉽게 하기 위한 MovieData형 구조체를 Swift로 만들어 줘

import Foundation

// 최상위
struct MovieData: Decodable {
    let boxOfficeResult: BoxOfficeResult
}

// boxOfficeResult
struct BoxOfficeResult: Decodable {
    let boxofficeType: String
    let showRange: String
    let dailyBoxOfficeList: [DailyBoxOffice]
}

// 영화 정보
struct DailyBoxOffice: Decodable {
    
    let rnum: String
    let rank: String
    let rankInten: String
    let rankOldAndNew: String
    
    let movieCd: String
    let movieNm: String
    let openDt: String
    
    let salesAmt: String
    let salesShare: String
    let salesInten: String
    let salesChange: String
    let salesAcc: String
    
    let audiCnt: String
    let audiInten: String
    let audiChange: String
    let audiAcc: String
    
    let scrnCnt: String
    let showCnt: String
}
 

그리고 이렇게 디코딩하면 돼:

 
do {
    let decoder = JSONDecoder()
    
    let result = try decoder.decode(MovieData.self, from: JSONdata)
    
    print(result.boxOfficeResult.dailyBoxOfficeList[0].movieNm)
    
} catch {
    print(error)
}
 

구조 이해 그림

MovieData
 └── boxOfficeResult
      └── dailyBoxOfficeList
            ├── 영화1
            ├── 영화2
            └── 영화3
 

실제 접근 예시

영화 제목 출력

 
let movies = result.boxOfficeResult.dailyBoxOfficeList

for movie in movies {
    print(movie.movieNm)
}
 

출력 결과

슈퍼 마리오 갤럭시
악마는 프라다를 입는다 2
살목지
...
 

왜 Decodable 사용?

 
Decodable
 

은:

JSON → Swift 객체
 

자동 변환 가능하게 해주는 프로토콜이야.


Codable로 써도 됨

실무에서는 보통:

 
struct MovieData: Codable
 

많이 사용해.

왜냐면:

프로토콜의미
Decodable JSON → 객체
Encodable 객체 → JSON
Codable 둘 다

참고 (시험/실무 팁)

현재 모든 값이 "문자열" 형태라서 전부 String 으로 만든 거야.

예:

 
"rank":"1"
 

숫자처럼 보여도 실제 JSON은 문자열임.

그래서:

 
let rank: String
 

으로 해야 디코딩 오류 안 남.

구조체는 상속 x codable은 프로토콜 

Codable 은 Swift 객체를
JSON 같은 데이터 형식으로 변환(인코딩) 하거나
JSON 데이터를 Swift 객체로 변환(디코딩) 하기 위해 사용해.

쉽게 말하면:

Swift 객체 ↔ JSON
 

자동 변환 기능이라고 보면 돼.

 

서버에서 받은 JSON:

 
{
   "movieNm":"슈퍼 마리오 갤럭시"
}
 

이걸 Swift 구조체로 바꾸고 싶음:

 
struct Movie {
    let movieNm: String
}
 

이때 Codable 이 필요해.


Codable 없으면?

Swift는:

JSON 데이터를
어떤 구조체에 넣어야 하는지 모름
 

그래서 자동 변환 불가능.


Codable 추가

 
struct Movie: Codable {
    let movieNm: String
}
 

하면 Swift가 자동으로:

JSON ↔ 구조체
 

변환 코드를 만들어줌.


Codable의 진짜 정체

사실:

 
Codable
 

은 합쳐진 이름이야.

 
Codable = Encodable + Decodable
 

각각 의미

프로토콜의미
Decodable JSON → 객체
Encodable 객체 → JSON
Codable 둘 다 가능

네 코드에서는 주로 Decodable 기능 사용

 
let result = try decoder.decode(MovieData.self, from: JSONdata)
 

여기서 하는 일:

JSONdata
↓
MovieData 구조체 변환
 

이거야.


내부적으로 Swift가 하는 일

이 JSON:

 
{
   "movieNm":"슈퍼 마리오 갤럭시"
}
 

을 보고

 
struct Movie: Codable {
    let movieNm: String
}
 

 
movieNm
 

이랑 이름 매칭해서 자동 저장함.


Codable 없으면 오류

 
struct Movie {
    let movieNm: String
}
 

이 상태에서:

 
decoder.decode(Movie.self, from: data)
 

하면 오류 발생.

왜냐면:

Movie는 Decodable이 아님
 

이라고 뜸.


실무에서 Codable 엄청 중요함

앱 개발 대부분이 서버 통신이라서:

  • 영화 API
  • 로그인 API
  • 쇼핑몰 데이터
  • 채팅 데이터

전부 JSON 사용함.

그래서 Codable 은 iOS에서 거의 필수 개념이야.


시험 스타일 핵심 정리

 
Codable
 
  • JSON 데이터를 Swift 객체로 변환 가능
  • Swift 객체를 JSON으로 변환 가능
  • Encodable + Decodable
  • 서버 통신에서 자주 사용

흐름 그림

서버 JSON 데이터
        ↓
JSONDecoder
        ↓
Codable 구조체
        ↓
Swift 객체 사용
 

예시 전체 흐름

 
struct Movie: Codable {
    let movieNm: String
}
 
 
let decoder = JSONDecoder()

let movie = try decoder.decode(Movie.self, from: data)
 

결과:

 
print(movie.movieNm)
 

출력:

슈퍼 마리오 갤럭시

 

 

class

예외처리 필요한 매소드

throws

do catch try 사용

1등의 데이터 출력 이름,누적관객수,해당 날짜 관객수,순위

클로저안에 쓰기 위해 self 사용(현재 객체(ViewController 등) 자신의 속성)

출력이 이상함
실행은 되지만 잘 동작을 안한다는 에러문
메인 쓰레드에서 동작하기 위해 사용

//
//  ViewController.swift
//  Moviekhw
//

import UIKit

// 테스트용 영화 이름 배열
// 현재는 실제 API 데이터를 사용하므로 사용하지 않음
let name = [
"1: 슈퍼 마리오 갤럭시",
"2: 악마는 프라다를 입는다 2",
"3: 살목지",
"4: 프로젝트 헤일메리",
"5: 짱구",
"6: 왕과 사는 남자",
"7: 란 12.3",
"8: 내 이름은",
"9: 사랑의 하츄핑 특별판",
"10: 극장판 반짝반짝 달님이- 싱어롱 파티"
]



// MARK: - JSON 전체 구조

// 가장 바깥 JSON 구조
// JSON:
// {
//    "boxOfficeResult": { ... }
// }

struct MovieData : Codable {
    let boxOfficeResult : BoxOfficeResult
}



// boxOfficeResult 내부 구조
// JSON:
// "boxOfficeResult": {
//      "dailyBoxOfficeList": [ ... ]
// }

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



// 실제 영화 데이터 하나의 구조
// JSON 배열 안의 각각 영화 정보

struct DailyBoxOfficeList : Codable {
    
    // 영화 제목
    let movieNm : String
    
    // 하루 관객 수
    let audiCnt : String
    
    // 누적 관객 수
    let audiAcc : String
    
    // 현재 순위
    let rank : String
}



// MARK: - ViewController

class ViewController: UIViewController,
                      UITableViewDelegate,
                      UITableViewDataSource {

    // 스토리보드의 TableView 연결
    @IBOutlet weak var table: UITableView!
    
    
    // 서버에서 받아온 영화 데이터를 저장할 변수
    // 처음에는 데이터가 없으므로 Optional(?)
    var movieData : MovieData?
    
    
    // 영화진흥위원회(KOBIS) API 주소
    // targetDt = 조회 날짜
    let movieURL =
    "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=d32b85027905503dcfff4af4097262af&targetDt=20260510"
    
    
    
    // MARK: - 테이블뷰 행 개수
    
    // 테이블에 몇 개의 셀(row)을 만들지 결정
    
    func tableView(_ tableView: UITableView,
                   numberOfRowsInSection section: Int) -> Int {
        
        // 현재는 10개 고정
        // 실제로는:
        // return movieData?.boxOfficeResult.dailyBoxOfficeList.count ?? 0
        // 처럼 많이 사용함
        
        return 10
    }
    
    
    
    // MARK: - 셀 내용 구성
    
    // 각 셀에 어떤 데이터를 넣을지 결정
    
    func tableView(_ tableView: UITableView,
                   cellForRowAt indexPath: IndexPath)
    -> UITableViewCell {
        
        
        // 재사용 가능한 셀 가져오기
        
        // dequeueReusableCell:
        // 셀을 새로 계속 만드는 것이 아니라
        // 기존 셀을 재사용해서 성능 향상
        
        
        let cell = tableView.dequeueReusableCell(
            withIdentifier: "myCell",
            for: indexPath
        ) as! MyTableViewCell
        
        
        // 다운캐스팅(as!)
        // UITableViewCell -> MyTableViewCell 변환
        
        
        
        // 서버에서 받아온 영화 제목 표시
        
        // movieData? :
        // Optional 안전 접근
        
        // indexPath.row :
        // 현재 몇 번째 셀인지
        
        
        cell.movieName.text =
        movieData?.boxOfficeResult
            .dailyBoxOfficeList[indexPath.row]
            .movieNm
        
        
        
        // 테스트용 배열 사용 코드
        // cell.movieName.text = name[indexPath.row]
        
        
        // 셀 반환
        return cell
    }
    
    
    
    // MARK: - 섹션 개수
    
    func numberOfSections(in tableView: UITableView) -> Int {
        
        // 섹션 1개
        
        return 1
    }
    
    
    
    // MARK: - 화면 로드 시 실행
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        
        // 테이블뷰의 delegate 연결
        
        // delegate:
        // 셀 선택, 높이 등 이벤트 처리
        
        table.delegate = self
        
        
        // dataSource:
        // 셀 데이터 공급
        
        table.dataSource = self
        
        
        // 서버 데이터 요청 시작
        getData()
    }
    
    
    
    // MARK: - 네트워크 함수
    
    func getData(){
        
        
        // =========================
        // 1단계 - URL 생성
        // =========================
        
        // 문자열 주소 -> URL 객체 변환
        
        guard let url = URL(string: movieURL)
        else { return }
        
        
        
        // =========================
        // 2단계 - URLSession 생성
        // =========================
        
        // 인터넷 통신 객체
        
        let session =
        URLSession(configuration: .default)
        
        
        
        // =========================
        // 3단계 - 데이터 요청
        // =========================
        
        let task = session.dataTask(with: url) {
            data, response, error in
            
            
            // error:
            // 네트워크 실패 정보
            
            
            // =========================
            // 에러 확인
            // =========================
            
            if error != nil{
                
                // 에러 출력
                
                print(error!)
                
                // 함수 종료
                return
            }
            
            
            
            // =========================
            // 데이터 안전 추출
            // =========================
            
            // data는 Optional(Data?)
            
            guard let JSONdata = data
            else { return }
            
            
            
            // 서버에서 받은 원본 데이터 크기 출력
            // print(JSONdata)
            
            
            
            // =========================
            // Data -> String 변환
            // =========================
            
            // JSON 내용을 문자열로 확인 가능
            
            let dataString =
            String(data: JSONdata,
                   encoding: .utf8)
            
            
            // 실제 JSON 확인
            // print(dataString!)
            
            
            
            
            // =========================
            // JSONDecoder 생성
            // =========================
            
            let decoder = JSONDecoder()
            
            
            
            // =========================
            // JSON 파싱
            // =========================
            
            do{
                
                // JSON -> MovieData 구조체 변환
                
                let decosedData =
                try decoder.decode(
                    MovieData.self,
                    from: JSONdata
                )
                
                
                
                // 첫 번째 영화 정보 출력
                
                print(
                    decosedData
                        .boxOfficeResult
                        .dailyBoxOfficeList[0]
                        .movieNm
                )
                
                
                print(
                    decosedData
                        .boxOfficeResult
                        .dailyBoxOfficeList[0]
                        .audiAcc
                )
                
                
                print(
                    decosedData
                        .boxOfficeResult
                        .dailyBoxOfficeList[0]
                        .audiCnt
                )
                
                
                print(
                    decosedData
                        .boxOfficeResult
                        .dailyBoxOfficeList[0]
                        .rank
                )
                
                
                
                // 현재 ViewController의
                // movieData 속성에 저장
                
                self.movieData = decosedData
                
                
                
                // =========================
                // 메인 스레드에서 UI 업데이트
                // =========================
                
                // 네트워크는 백그라운드 스레드
                
                // UI 수정은 반드시 메인 스레드
                
                DispatchQueue.main.async(){
                    
                    // 테이블뷰 새로고침
                    
                    // 다시
                    // numberOfRowsInSection
                    // cellForRowAt
                    // 호출됨
                    
                    self.table.reloadData()
                }
                
                
                
            }catch{
                
                // 디코딩 실패 시 에러 출력
                
                print(error)
            }
            
        }
        
        
        
        // =========================
        // 4단계 - 네트워크 시작
        // =========================
        
        // resume() 안 하면 실행 안 됨
        
        task.resume()
    }

}

주요 코드

추가예정

//
// ViewController.swift
// Moviekhw
//

import UIKit



// ======================================================
// MARK: - JSON 구조체
// ======================================================

// 서버에서 받아오는 JSON 구조와
// 동일한 형태로 구조체를 생성해야 함


// 가장 바깥 JSON 구조
//
// {
// "boxOfficeResult" : { ... }
// }

struct MovieData : Codable {

// boxOfficeResult 저장
let boxOfficeResult : BoxOfficeResult
}



// boxOfficeResult 내부 구조
//
// "boxOfficeResult": {
// "dailyBoxOfficeList": [ ... ]
// }

struct BoxOfficeResult : Codable {

// 영화 배열 저장
let dailyBoxOfficeList : [DailyBoxOfficeList]
}



// 영화 1개의 정보 구조
//
// [
// {
// "movieNm":"슈퍼마리오",
// "audiCnt":"1000"
// }
// ]

struct DailyBoxOfficeList : Codable {

// 영화 제목
let movieNm : String

// 하루 관객 수
let audiCnt : String

// 누적 관객 수
let audiAcc : String

// 현재 순위
let rank : String
}



// ======================================================
// MARK: - ViewController
// ======================================================

class ViewController: UIViewController,
UITableViewDelegate,
UITableViewDataSource {


// ==================================================
// IBOutlet
// ==================================================

// 스토리보드의 테이블뷰 연결

@IBOutlet weak var table: UITableView!



// ==================================================
// 영화 데이터 저장 변수
// ==================================================

// 서버에서 받아온 데이터를 저장

// Optional(?)인 이유:
// 앱 시작 직후에는 아직 데이터가 없음

var movieData : MovieData?



// ==================================================
// API 주소
// ==================================================

// 영화진흥위원회(KOBIS) API

// targetDt=
// 뒤에 날짜를 붙여서 사용

var movieURL =
"https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=d32b85027905503dcfff4af4097262af&targetDt="



// ==================================================
// 테이블뷰 행 개수
// ==================================================

// 몇 개의 셀(row)을 만들지 결정

func tableView(_ tableView: UITableView,
numberOfRowsInSection section: Int)
-> Int {


// 현재는 10개 고정

// 실제 앱에서는:
//
// return movieData?
// .boxOfficeResult
// .dailyBoxOfficeList.count ?? 0
//
// 많이 사용

return 10
}



// ==================================================
// 셀 내용 설정
// ==================================================

// 각 셀에 어떤 데이터를 넣을지 결정

func tableView(_ tableView: UITableView,
cellForRowAt indexPath: IndexPath)
-> UITableViewCell {


// 재사용 가능한 셀 가져오기

// withIdentifier:
// 스토리보드의 identifier 이름

let cell =
tableView.dequeueReusableCell(
withIdentifier: "myCell",
for: indexPath
) as! MyTableViewCell



// as!
// 다운캐스팅

// UITableViewCell
// ↓
// MyTableViewCell



// 현재 행(row)의 영화 제목 표시

// indexPath.row
// 현재 몇 번째 셀인지

cell.movieName.text =
movieData?
.boxOfficeResult
.dailyBoxOfficeList[indexPath.row]
.movieNm



// 완성된 셀 반환

return cell
}



// ==================================================
// 섹션 개수
// ==================================================

func numberOfSections(in tableView: UITableView)
-> Int {

// 섹션 1개

return 1
}



// ==================================================
// 화면 로드 시 실행
// ==================================================

override func viewDidLoad() {
super.viewDidLoad()


// delegate 연결

// 셀 선택 이벤트 등 처리

table.delegate = self


// dataSource 연결

// 테이블 데이터 공급

table.dataSource = self



// 어제 날짜 문자열 생성

// 예:
// 20260510

movieURL += makeYesterdayString()



// 최종 URL 예시
//
// https://.....&targetDt=20260510



// 서버 데이터 요청

getData()
}



// ==================================================
// 어제 날짜 문자열 생성 함수
// ==================================================

func makeYesterdayString() -> String {


// 현재 날짜에서 하루(-1일) 빼기

let y =
Calendar.current.date(
byAdding: .day,
value : -1,
to : Date()
)!



// DateFormatter 객체

// 날짜 형식 변환 담당

let dateF = DateFormatter()



// 원하는 날짜 형식

// yyyy = 년도
// MM = 월
// dd = 일

dateF.dateFormat = "yyyyMMdd"



// Date -> String 변환

let day = dateF.string(from: y)



// 문자열 반환

return day
}



// ==================================================
// 네트워크 함수
// ==================================================

func getData(){


// ==============================================
// 1단계 - URL 생성
// ==============================================

// 문자열 -> URL 객체 변환

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



// ==============================================
// 2단계 - URLSession 생성
// ==============================================

// 인터넷 통신 객체

let session =
URLSession(configuration: .default)



// ==============================================
// 3단계 - 데이터 요청
// ==============================================

let task =
session.dataTask(with: url) {
data, response, error in


// data
// 서버에서 받은 데이터

// response
// 서버 응답 정보

// error
// 오류 정보



// ==========================================
// 에러 체크
// ==========================================

if error != nil{

// 오류 출력

print(error!)


// 함수 종료

return
}



// ==========================================
// 데이터 안전 추출
// ==========================================

// data는 Optional(Data?)

guard let JSONdata = data
else { return }



// Data -> String 변환

// JSON 확인 가능

let dataString =
String(
data: JSONdata,
encoding: .utf8
)


// JSON 문자열 출력

// print(dataString!)




// ==========================================
// JSONDecoder 생성
// ==========================================

let decoder = JSONDecoder()



// ==========================================
// JSON 파싱
// ==========================================

do{


// JSON 데이터를
// MovieData 구조체로 변환

let decosedData =
try decoder.decode(
MovieData.self,
from: JSONdata
)



// 첫 번째 영화 제목 출력

print(
decosedData
.boxOfficeResult
.dailyBoxOfficeList[0]
.movieNm
)


// 누적 관객 수 출력

print(
decosedData
.boxOfficeResult
.dailyBoxOfficeList[0]
.audiAcc
)


// 하루 관객 수 출력

print(
decosedData
.boxOfficeResult
.dailyBoxOfficeList[0]
.audiCnt
)


// 순위 출력

print(
decosedData
.boxOfficeResult
.dailyBoxOfficeList[0]
.rank
)



// 현재 ViewController의
// movieData 속성에 저장

self.movieData = decosedData



// ==========================================
// 메인 스레드 UI 업데이트
// ==========================================

// 네트워크는 백그라운드 스레드

// UI 수정은 반드시 메인 스레드

DispatchQueue.main.async(){


// 테이블뷰 새로고침

// 다시
// numberOfRowsInSection
// cellForRowAt
// 호출됨

self.table.reloadData()
}



}catch{


// JSON 파싱 실패

print(error)
}

}



// ==============================================
// 4단계 - 네트워크 시작
// ==============================================

// resume() 안 하면 실행 안 됨

task.resume()
}

}