Gemini API(generateContent)를 Swift에서 사용하기 위한 JSON 모델을 설계
Request Model
import Foundation
// MARK: - Request
struct GeminiRequest: Codable {
let contents: [Content]
let systemInstruction: Content?
let generationConfig: GenerationConfig?
let safetySettings: [SafetySetting]?
enum CodingKeys: String, CodingKey {
case contents
case systemInstruction = "system_instruction"
case generationConfig = "generationConfig"
case safetySettings = "safetySettings"
}
}
struct Content: Codable {
let role: String?
let parts: [Part]
}
struct Part: Codable {
let text: String?
let inlineData: InlineData?
enum CodingKeys: String, CodingKey {
case text
case inlineData = "inline_data"
}
}
struct InlineData: Codable {
let mimeType: String
let data: String
enum CodingKeys: String, CodingKey {
case mimeType = "mime_type"
case data
}
}
struct GenerationConfig: Codable {
let temperature: Double?
let topP: Double?
let topK: Int?
let maxOutputTokens: Int?
let responseMimeType: String?
enum CodingKeys: String, CodingKey {
case temperature
case topP = "topP"
case topK = "topK"
case maxOutputTokens = "maxOutputTokens"
case responseMimeType = "responseMimeType"
}
}
struct SafetySetting: Codable {
let category: String
let threshold: String
}
Response Model
import Foundation
// MARK: - Response
struct GeminiResponse: Codable {
let candidates: [Candidate]?
let usageMetadata: UsageMetadata?
let modelVersion: String?
}
struct Candidate: Codable {
let content: Content
let finishReason: String?
let index: Int?
let safetyRatings: [SafetyRating]?
}
struct SafetyRating: Codable {
let category: String
let probability: String
}
struct UsageMetadata: Codable {
let promptTokenCount: Int?
let candidatesTokenCount: Int?
let totalTokenCount: Int?
}

정렬하는 방법 시험에 출제

subclass of: 부모 컨트롤러를 작성해야함
//
// GeminiViewController.swift
// movie_yjy
//
// Created by comsoft on 2026/06/02.
//
import UIKit // iOS UI 관련 기능 가져오기
// 요청(Request)에 사용할 구조체들
// API에 보낼 JSON 구조를 Swift 구조체로 표현
// Encodable: Swift 객체 → JSON 변환 가능하게 해줌
// 최상위 요청 구조: { "contents": [...] }
struct GeminiRequest: Encodable {
let contents: [RequestContent] // contents 배열
}
// contents 배열의 각 항목: { "parts": [...] }
struct RequestContent: Encodable {
let parts: [RequestPart] // parts 배열
}
// parts 배열의 각 항목: { "text": "..." }
struct RequestPart: Encodable {
let text: String // 실제 프롬프트 텍스트
}
// API가 보내주는 JSON 구조를 Swift 구조체로 표현
// Decodable: JSON → Swift 객체 변환 가능하게 해줌
// 최상위 응답 구조: { "candidates": [...] }
struct GeminiResponse: Decodable {
let candidates: [Candidate] // candidates 배열
}
// candidates 배열의 각 항목: { "content": {...} }
struct Candidate: Decodable {
let content: ResponseContent // content 객체
}
// content 객체: { "parts": [...] }
struct ResponseContent: Decodable {
let parts: [ResponsePart] // parts 배열
}
// parts 배열의 각 항목: { "text": "..." }
struct ResponsePart: Decodable {
let text: String // AI가 생성한 실제 텍스트
}
class GeminiViewController: UIViewController {
@IBOutlet weak var textField: UITextField!
@IBOutlet weak var textView: UITextView!
// Gemini API 키 (Google AI Studio에서 발급)
let apiKey = "api-key" //자신의 키 입력
@IBAction func buttonTapped(_ sender: UIButton) {
// 입력값 비어있으면 종료
guard let mood = textField.text, !mood.isEmpty else { return }
// 로딩 메시지 표시
textView.text = "추천 중…"
// 키보드 내리기
view.endEditing(true)
// 영화 추천 받아오기 시작
getData(mood: mood)
}
// Gemini API를 호출해서 영화 추천을 받아오는 함수
func getData(mood: String) {
let geminiURL =
"https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=\(apiKey)"
// 문자열 주소를 URL 객체로 변환 (실패하면 함수 종료)
guard let url = URL(string: geminiURL) else { return }
// AI에게 보낼 명령(프롬프트) 작성
// \(mood)에는 사용자가 입력한 기분이 들어감
let prompt = "기분이 '\(mood)'일 때 볼만한 영화 3편을 한국어로 추천해줘. 각 영화마다 제목, 장르, 추천이유를 간단히 적어줘."
// 보낼 요청 본문을 구조체로 만들기
// GeminiRequest > RequestContent > RequestPart 순서로 중첩
let requestBody = GeminiRequest( contents:
[RequestContent(parts: [RequestPart(text: prompt)])] )
// HTTP 요청 객체 만들기 (수정 가능하도록 var)
var request = URLRequest(url: url)
// 요청 방식을 POST로 설정 (데이터를 "보내는" 방식), GET이 기본
request.httpMethod = "POST"
// 헤더에 "JSON 데이터를 보낸다"고 알려줌
request.setValue("application/json", forHTTPHeaderField:
"Content-Type")
// 구조체를 JSON Data로 바꿔서 요청 본문에 담기
// try?: 실패하면 nil (간단히 처리)
request.httpBody = try? JSONEncoder().encode(requestBody)
// 통신을 담당할 URLSession 객체 생성 (기본 설정 사용)
let session = URLSession(configuration: .default)
// 통신 작업(task)을 만들기
// 응답이 도착하면 { } 안의 코드가 실행됨
// [self]: 클로저 안에서 self를 사용할 수 있게 캡처
let task = session.dataTask(with: request) { [self]
(data, response, error) in
// 에러가 있으면 콘솔에 출력하고 함수 종료
if error != nil {
print(error!)
return
}
// 받은 데이터가 없으면 함수 종료
guard let JSONdata = data else { return }
if let httpResponse = response as? HTTPURLResponse {
print("statusCode =", httpResponse.statusCode)
}
if let jsonString = String(data: JSONdata, encoding: .utf8) {
print("response =", jsonString)
}
// JSON을 Swift 객체로 변환할 디코더 객체 생성
let decoder = JSONDecoder()
// 디코딩은 실패할 수 있으므로 do-catch로 감싸기
do {
// 받은 JSONdata를 GeminiResponse 구조체로 변환 시도
let decodedData = try decoder.decode(GeminiResponse.self,
from: JSONdata)
// 변환된 구조체에서 실제 추천 텍스트 꺼내기
// candidates배열의 첫번째 > content > parts배열의 첫번째 > text
// 만약 중간에 비어있으면 "결과 없음" 사용
let text =
decodedData.candidates.first?.content.parts.first?.text ?? "결과 없음"
// 마크다운 기호 제거해서 깔끔하게 만들기
let cleanText = text
.replacingOccurrences(of: "**", with: "") // 굵게 표시 ** 제거
.replacingOccurrences(of: "*", with: "") // 기울임 * 제거
.replacingOccurrences(of: "##", with: "") // 헤더 ## 제거
.replacingOccurrences(of: "#", with: "") // 헤더 # 제거
.replacingOccurrences(of: "`", with: "") // 코드 ` 제거
// UI 업데이트는 반드시 메인 스레드에서!
// (네트워크 응답은 백그라운드 스레드에서 도착하기 때문)
DispatchQueue.main.async {
// 정리된 텍스트를 결과창에 표시
self.textView.text = cleanText
}
} catch {
// 디코딩 실패하면 에러를 콘솔에 출력
print(error)
}
}
// resume()을 호출해야 실제로 통신이 시작됨
task.resume()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
}


'iOS' 카테고리의 다른 글
| iOS_3grade - 11주차 (0) | 2026.05.19 |
|---|---|
| iOS_3grade - 10주차 (1) | 2026.05.12 |
| iOS_grade3 - 9주차 (0) | 2026.04.28 |
| iOS_3grade - 7주차 (0) | 2026.04.14 |
| iOS_3grade - 6주차 (1) | 2026.04.07 |