iT邦幫忙

2025 iThome 鐵人賽

DAY 29
0
Mobile Development

Swift iOS 開發新手村:從入門到 AI 聊天室系列 第 29

Day 29|Xcode 專案實戰:打造 Gemini AI 聊天室!(三)

  • 分享至 

  • xImage
  •  

昨天,我們成功打造了聊天室的核心介面與互動邏輯,但 ChatMessageNetworkManager 這兩個幕後功臣還只是「概念上」的存在。今天,我們就要來補齊這些基礎建設,讓 App 真正地與世界頂尖的 AI 模型進行對話!

我們將會定義對話所需的資料模型、實作串接 Google Gemini API 的網路層,並探討如何安全地管理我們的 API Key。完成今天的工作後,我們的 App 就完整了!

今日學習重點

  • 定義資料模型:建立 ChatMessageGeminiResponse 兩種 struct 來管理對話與 API 回應。
  • 實作 NetworkManager:撰寫 getGeminiResponse 方法,完成發送請求、接收與解析 JSON 的完整流程。
  • 解析複雜 JSON:學習如何用多個 Codable 結構體來解析巢狀的 JSON 資料。
  • API Key 的管理:了解為什麼不該將 API Key 直接寫在程式碼中,並學習基礎的處理方式。

一、定義資料模型:讓程式看懂對話

首先,我們需要定義兩種資料結構:一種用來在 App 內部表示每一則對話訊息,另一種則是用來解析從 Gemini API 回傳的複雜 JSON 資料。

1. 建立 ChatMessage.swift

這是在 TableView 中顯示資料的基礎模型。

  1. 在專案中新增一個 Swift 檔案,命名為 ChatMessage.swift
  2. 貼上以下程式碼:
// ChatMessage.swift
import Foundation

struct ChatMessage {
    let text: String    // 訊息內容
    let isUser: Bool    // true = 使用者, false = Gemini
}

2. 建立 GeminiResponse.swift

這個檔案專門用來解析 Gemini API 回傳的 JSON 格式。你會發現 JSON 是一層一層包起來的,所以我們也需要用多個 struct 來對應它的巢狀結構。

在專案中新增一個 Swift 檔案,命名為 GeminiResponse.swift

貼上以下程式碼:

// GeminiResponse.swift
import Foundation

// MARK: - GeminiResponse
// 代表整個 Gemini API 的回應物件
struct GeminiResponse: Decodable {
    let candidates: [Candidate]?
}

// MARK: - Candidate
// 代表一個單獨的生成結果
struct Candidate: Decodable {
    // 包含由模型生成的實際內容
    let content: Content
    
    // 說明生成過程為何結束的理由(例如 "STOP" 或 "SAFETY")
    let finishReason: String?
}

// MARK: - Content
// 包含由模型生成的內容細節
struct Content: Decodable {
    // 內容的角色,通常為 "model"
    let role: String?
    
    // 內容的部分,通常包含文字
    let parts: [Part]
}

// MARK: - Part
// 代表內容的單個部分
struct Part: Decodable {
    // 生成的純文字內容。如果內容是文字,此欄位會存在
    let text: String?
}

程式碼說明

我們使用了 Decodable 協定,讓 Swift 的 JSONDecoder 能夠自動將網路回傳的 JSON 資料,轉換成我們定義好的這些 struct 物件。

二、取得你的 Gemini API Key:開啟 AI 對話的鑰匙

在我們能呼叫 Gemini API 之前,需要先向 Google 取得一把專屬的「鑰匙」,也就是 API Key。這就像是你的 App 要和 Google AI 服務對話時,需要出示的通行證。取得過程完全免費且非常快速,我們開始吧!

步驟 1:前往 Google AI Studio

首先,請使用你的 Google 帳號登入 Google AI Studio 官方網站。

步驟 2:點擊「取得 API 金鑰」

在 Google AI Studio 的介面中,找到並點擊「Get API key」(取得 API 金鑰)的按鈕。這個按鈕通常位於頁面的左側選單下方:
https://ithelp.ithome.com.tw/upload/images/20251012/20177542r3XcjybIt7.png]

步驟 3:建立新的 API 金鑰

點擊後,會彈出一個視窗。請點擊「Create API key」按鈕。接著,你可以為你的鑰匙命名:
https://ithelp.ithome.com.tw/upload/images/20251012/20177542LTREqTGcrb.png

步驟 4:複製並妥善保管你的金鑰

接著,你會看到畫面新增一串資訊,其中由英數字元組成的 API Key,待會在撰寫 NetworkManager 程式碼時會用到它。

三、實作 NetworkManager:與 Gemini API 對話的核心

接下來是重頭戲!我們將建立一個 NetworkManager.swift 檔案,並在裡面撰寫一個專門用來呼叫 Gemini API 的方法。

在專案中新增一個 Swift 檔案,命名為 NetworkManager.swift

貼上以下程式碼:

// NetworkManager.swift
import Foundation

class NetworkManager {

    static let apiKey = "請在此處貼上你剛才申請好的 Gemini API Key"
    
    @discardableResult
    static func getGeminiResponse(for userInput: String, completion: @escaping (String) -> Void) -> URLSessionDataTask {
        
        let modelName = "gemini-1.5-flash" // 建議使用較新的模型
        let urlString = "https://generativelanguage.googleapis.com/v1beta/models/\(modelName):generateContent?key=\(apiKey)"
        
        guard let url = URL(string: urlString) else {
            completion("URL 格式錯誤")
            fatalError("Invalid URL")
        }
        
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.setValue("application/json", forHTTPHeaderField: "Content-Type")
        
        let body: [String: Any] = [
            "contents": [
                [
                    "parts": [
                        [
                            "text": userInput
                        ]
                    ]
                ]
            ]
        ]
        
        do {
            request.httpBody = try JSONSerialization.data(withJSONObject: body)
        } catch {
            completion("JSON 轉換失敗")
            fatalError("Invalid JSON")
        }
        
        let task = URLSession.shared.dataTask(with: request) { data, response, error in
            
            if let error = error as NSError?, error.code == NSURLErrorCancelled {
                print("使用者中斷 Gemini 回覆")
                completion("回覆已取消")
                return
            }
            
            if let error = error {
                print("請求錯誤: \(error)")
                completion("網路錯誤,請稍後再試。")
                return
            }

            guard let data = data else {
                completion("沒有收到回傳資料")
                return
            }
            
            // 用來除錯,看看 API 回傳了什麼
            if let jsonString = String(data: data, encoding: .utf8) {
                print("🧾 Gemini 回傳內容:\(jsonString)")
            }

            do {
                let geminiResponse = try JSONDecoder().decode(GeminiResponse.self, from: data)
                // 從巢狀結構中,安全地取出文字
                let text = geminiResponse.candidates?.first?.content.parts.first?.text ?? "抱歉,我無法理解。"
                completion(text)
            } catch {
                print("JSON 解析錯誤: \(error)")
                completion("解析 API 回應時發生錯誤。")
            }
        }
        
        task.resume()
        return task
    }
}

程式碼說明

  • API Key:這是你與 Google Gemini API 溝通的「鑰匙」,請務必替換成自己的。
  • Request Body:我們按照 Gemini API 的文件要求,將使用者的輸入文字包裝成一個特定的 JSON 格式。
  • 解析回應:收到 data 後,我們使用 JSONDecoder 和剛剛建立的 GeminiResponse.self 來解析它,並從層層結構中取出最終的文字 text

四、安全地管理你的 API Key

直接將 API Key 寫在程式碼中是一個非常不安全的做法!如果直接將這樣的程式碼上傳到 GitHub 等公開平台,你的 Key 就會被洩漏,也很容易被他人濫用。

在大型專案中,我們會使用更複雜的方法(例如 xcconfig 檔案或伺服器端管理)來保護 Key。但對於初學者來說,這裡提供一個簡單且有效的起步方法:使用 Info.plist

步驟 1:將 Key 加入 Info.plist

  1. 打開專案中的 Info.plist 檔案。
  2. 在空白處按右鍵 → Add Row
  3. 將新的 Key 命名為 GeminiAPIKey
  4. Value 設為自己的 Gemini API Key 字串。

步驟 2:修改 NetworkManager 來讀取 Key

修改 NetworkManager.swift,讓它從 Info.plist 中讀取 Key,而不是直接寫死。

class NetworkManager {

    // 從 Info.plist 讀取 API Key
    static var apiKey: String {
        guard let filePath = Bundle.main.path(forResource: "Info", ofType: "plist") else {
            fatalError("找不到 Info.plist 檔案。")
        }
        let plist = NSDictionary(contentsOfFile: filePath)
        guard let value = plist?.object(forKey: "GeminiAPIKey") as? String else {
            fatalError("在 Info.plist 中找不到 GeminiAPIKey。")
        }
        if value.starts(with: "【") {
             fatalError("請在 Info.plist 中設定你的 GeminiAPIKey。")
        }
        return value
    }
    
    // ... (getGeminiResponse 方法保持不變) ...
}

重要!!!

別忘了將你的 Info.plist 檔案加入到 .gitignore 中,這樣在使用 Git 時,這個包含 Key 的檔案就不會被上傳到遠端倉庫!

這邊就不教學 Github 倉庫的使用方法,有興趣的朋友們,可以自行上網搜尋相關資料哦!

小結一下

今天我們完成了 Gemini 聊天室所有「看不見」的幕後英雄!我們定義了清晰的資料模型來處理對話與 API 回應,並打造了一個功能完整的 NetworkManager 來與 Google Gemini API 進行溝通。

最重要的是,我們學習了如何安全地管理 API Key,這是從練習專案邁向真實產品開發的重要一步。現在,我們的 App 已經萬事俱備,準備好真正「開口說話」了!

🌟 明天預告

明天就是我們鐵人賽的最後一天了!

我們將進行最後的總結與測試,確保 App 的所有功能都能正常運作。同時,我們也會回顧這 30 天的學習旅程,從一個 print("Hello, World!") 開始,到今天能串接 AI 模型。

這將是一次充滿成就感的收尾,也是你作為一名 iOS 開發者旅程的全新起點!

敬請期待《Day 30|Xcode 專案實戰:Gemini AI 聊天室總結與鐵人賽回顧!》


上一篇
Day 28|Xcode 專案實戰:打造 Gemini AI 聊天室!(二)
系列文
Swift iOS 開發新手村:從入門到 AI 聊天室29
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言