iOS Class 1

공공데이터 포털
국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Dataset)와 Open API로 제공하는 사이트입니다.
Documentation

Optional이란 무엇인가요? 값이 있을 수도 있고 없을(nil) 수도 있음을 나타내는 Swift의 타입 래퍼입니다.

Optional을 안전하게 언랩하는 방법은? if let, guard let, nil 병합 연산자(??), 옵셔널 체이닝(?.) 등을 사용합니다.

guard let과 if let의 차이는? guard let은 조건 불충족 시 early exit하며 이후 스코프에서 언랩된 값을 사용할 수 있고, if let은 해당 블록 안에서만 사용 가능합니다.

struct와 class의 차이는? struct는 값 타입으로 복사되고 class는 참조 타입으로 같은 인스턴스를 공유하며, class만 상속과 deinit을 지원합니다.

Value Type과 Reference Type의 차이는? Value Type은 할당·전달 시 값이 복사되고, Reference Type은 메모리 주소가 공유되어 한쪽 변경이 다른 쪽에도 영향을 줍니다.

ARC는 무엇이며 어떻게 동작하나요? Automatic Reference Counting의 약자로, 참조 카운트가 0이 되면 자동으로 메모리를 해제하는 Swift의 메모리 관리 방식입니다.

weak과 unowned의 차이는? weak은 참조 대상이 해제되면 자동으로 nil이 되는 옵셔널이고, unowned는 항상 값이 있다고 가정하므로 대상이 먼저 해제되면 크래시가 발생합니다.

closure에서 캡처 리스트가 필요한 이유는? 클로저가 외부 참조 타입을 강하게 캡처하면 순환 참조(retain cycle)가 발생할 수 있으므로, [weak self] 등으로 이를 방지하기 위해 필요합니다.

escaping closure와 non-escaping closure의 차이는? escaping closure는 함수 실행이 끝난 후에도 호출될 수 있어 @escaping 키워드가 필요하고, non-escaping은 함수 내에서만 실행되고 종료 전에 소멸됩니다.

protocol의 역할은? 특정 기능에 필요한 메서드·프로퍼티의 청사진을 정의하여, 타입 간 공통 인터페이스를 보장하고 다형성을 구현하게 해줍니다.

delegate 패턴을 왜 사용하나요? 객체 간 결합도를 낮추면서 한 객체의 이벤트나 동작을 다른 객체에 위임하여 처리할 수 있게 해주기 때문입니다.

async/await의 장점은? 비동기 코드를 동기 코드처럼 순차적으로 읽고 쓸 수 있어 콜백 지옥을 없애고 가독성과 에러 처리를 크게 개선합니다.

actor란 무엇이며 어떤 역할을 하나요? 동시성 환경에서 내부 상태에 대한 접근을 자동으로 직렬화하여 data race를 방지하는 참조 타입입니다.

computed property와 stored property의 차이는? stored property는 값을 메모리에 직접 저장하고, computed property는 호출 시마다 계산을 통해 값을 반환합니다.

Swift의 enum이 강력한 이유는? 연관 값(associated value), 메서드 정의, 프로토콜 채택, 패턴 매칭 등이 가능하여 단순 상수 나열을 넘어 풍부한 모델링을 할 수 있기 때문입니다.


추가 트렌드 질문: SwiftUI와 UIKit의 차이를 설명하고, 신규 프로젝트에서 어떤 기준으로 선택하시겠습니까?

https://www.kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json

import Foundation

// MARK: - Models

struct BoxOfficeResponse: Codable {
    let boxOfficeResult: BoxOfficeResult
}

struct BoxOfficeResult: Codable {
    let boxofficeType: String
    let showRange: String
    let dailyBoxOfficeList: [DailyBoxOffice]
}

struct DailyBoxOffice: Codable {
    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
}

// MARK: - 편의 computed properties

extension DailyBoxOffice {
    /// 당일 관객수 (Int)
    var audienceCount: Int { Int(audiCnt) ?? 0 }
    /// 누적 관객수 (Int)
    var accumulatedAudience: Int { Int(audiAcc) ?? 0 }
    /// 당일 매출액 (Int)
    var salesAmount: Int { Int(salesAmt) ?? 0 }
    /// 순위 변동 (Int, 양수=상승)
    var rankChange: Int { Int(rankInten) ?? 0 }
}

// MARK: - 파싱 예시

func parseBoxOffice(from jsonData: Data) {
    let decoder = JSONDecoder()

    do {
        let response = try decoder.decode(BoxOfficeResponse.self, from: jsonData)
        let result = response.boxOfficeResult

        print("📊 \(result.boxofficeType) (\(result.showRange))")
        print(String(repeating: "-", count: 50))

        for movie in result.dailyBoxOfficeList {
            print("\(movie.rank)위 | \(movie.movieNm)")
            print("   관객: \(movie.audienceCount.formatted())명 (누적 \(movie.accumulatedAudience.formatted())명)")
            print("   매출: \(movie.salesAmount.formatted())원")
            print()
        }
    } catch {
        print("파싱 실패: \(error)")
    }
}

// MARK: - 네트워크 호출 예시 (async/await)

func fetchDailyBoxOffice(date: String, apiKey: String) async throws -> BoxOfficeResponse {
    let urlString = "https://kobis.or.kr/kobisopenapi/webservice/rest/boxoffice/searchDailyBoxOfficeList.json?key=\(apiKey)&targetDt=\(date)"

    guard let url = URL(string: urlString) else {
        throw URLError(.badURL)
    }

    let (data, _) = try await URLSession.shared.data(from: url)
    return try JSONDecoder().decode(BoxOfficeResponse.self, from: data)
}

// 사용 예시:
// Task {
//     let result = try await fetchDailyBoxOffice(date: "20260308", apiKey: "YOUR_API_KEY")
//     for movie in result.boxOfficeResult.dailyBoxOfficeList {
//         print("\(movie.rank)위 - \(movie.movieNm)")
//     }
// }