iT邦幫忙

2025 iThome 鐵人賽

DAY 10
0
生成式 AI

iOS? AI-Yes!:用 Vide Coding 加速我的 Swift 學習曲線系列 第 10

Day 10 - 【鬧鐘實戰 I】打好地基:專案設定、Realm 模型與 App 框架

  • 分享至 

  • xImage
  •  

歡迎來到「鬧鐘實戰」的第一天!在過去九天,我們儲備了所有必要的知識。今天,我們要建立一個乾淨的專案,安裝好我們的資料庫,並設計出鬧鐘的核心資料模型

1-1:建立 Xcode 專案 (基於 XIB)

我的第一步,是在 Xcode 中建立一個全新的專案。我選擇了 App 模板,並將專案命名為 ClockClone

在專案選項中,雖然 Interface 我先選擇了預設的 Storyboard,但我計劃將它移除,改用我們在 Day 6 練習過的、更具彈性的 XIB 模式。

專案建立後,我便開始進行「無 Storyboard 化」的改造工程。我的操作步驟如下:

  1. 刪除 Storyboard 檔案: 在專案導覽器中,找到 Main.storyboard 檔案,直接將它刪除。

  2. 修改專案設定:

    • 點擊最頂層的專案檔案(藍色圖示)。

    • 選擇你的 TARGETS,切換到 Info 分頁。

    • 找到並刪除 Main storyboard file base name 這一行。

      image

    • 再選擇Build Settings 右上角搜尋 UIKit Main Storyboard File Base Name 把裡面的Main刪掉

      image

  3. 建立我們的第一個 ViewController (含 XIB)

    現在,我們來建立 App 的主畫面 MainViewController。這次,我們將體驗到建立 XIB 檔案的便捷之處。

    1. 在專案中建立一個 Controller 資料夾, 在 Controller 裡面再建一個 MainView 資料夾。
    2. 在 MainView 資料夾上右鍵,選擇 New File from Template...。
    3. 在跳出的模板視窗中,選擇 Cocoa Touch Class
    4. 在下一個畫面中,進行如下設定:
      • Class: 輸入 MainViewController。
      • Subclass of: 確保是 UIViewController。
      • Also create XIB file: 務必勾選這個選項!
    5. 點擊 Next 並儲存。
  4. 修改 SceneDelegate 我們需要用程式碼來告訴 App,它的第一個畫面是什麼。打開 SceneDelegate.swift,用以下程式碼替換掉 scene(_:willConnectTo:options:) 的全部內容。

    檔案:SceneDelegate.swift

    func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
        guard let windowScene = (scene as? UIWindowScene) else { return }
        let window = UIWindow(windowScene: windowScene)
    
        // 建立我們的根視圖控制器 (之後會換成 TabBarController)
        let mainVC = MainViewController(nibName: "MainViewController", bundle: nil)
        let navigationController = UINavigationController(rootViewController: mainVC)
    
        window.rootViewController = navigationController
        self.window = window
        window.makeKeyAndVisible()
    }
    

1-2:使用 Swift Package Manager 安裝 RealmSwift

接下來,我需要為專案安裝我們在 Day 9 選定的資料庫——Realm。我使用 Xcode 內建的 Swift Package Manager (SPM) 來完成這個任務。

我在頂部選單選擇 File -> Add Packages Dependencies...,選擇realm-swift,請確保 Realm 被勾選(不要勾RealmSwift),然後再次點擊 Add Package。安裝成功後,Package Dependencies 中出現了 Realm

image


1-3:設計我們的「心臟」—— AlarmData 資料模型

這是今天最需要動腦,也是最關鍵的一步:設計鬧鐘的資料模型

我知道我需要儲存哪些資訊(時間、標籤、重複日等),但我對 Realm 的具體語法還不熟悉,例如如何讓一個普通的 Swift Class 變成 Realm 可以儲存的格式。

  • AI 協作

    • 我的Prompt:我正在用 RealmSwift 設計一個鬧鐘的資料模型,class 名稱為 AlarmData。請問,我需要做哪些特別的宣告,才能讓 Realm 管理這個 class?例如,屬性前面那個 @Persisted是什麼作用?

    AI 的回答:

    好的,這是一個非常好的問題。在 RealmSwift 中,要讓一個普通的 Swift class 轉變為可以被 Realm 資料庫管理的資料模型,確實需要一些特定的宣告。這些宣告就像是給 Realm 的「指令」,告訴它如何儲存、管理和存取這個 class 的實例(instances)。

    針對您的 AlarmData class,以下是您需要了解的核心宣告概念,我將不提供完整的程式碼,而是專注於解釋這些宣告的作用。

    1. 繼承 Object

    這是最基本、也是最重要的一步。您的 AlarmData class 必須繼承自 RealmSwift 提供的 Object class。

    • 作用:一旦您的 class 繼承了 Object,Realm 就會辨識出它是一個資料模型。Realm 會在啟動時讀取所有 Object 的子類別,並根據它們的定義來建立或遷移(migrate)資料庫的結構(Schema)。可以說,繼承 Object 是將您的 class 納入 Realm 管理體系的「入場券」。

    2. 使用 @Persisted 屬性包裝器 (Property Wrapper)

    這是您問題的核心。您看到的 @Persisted 是 Swift 中的一個「屬性包裝器」,它是 RealmSwift 10.x 版本後推薦用來宣告模型屬性的現代化方式。

    • 作用@Persisted 的主要工作是將一個 Swift class 的屬性(property)與 Realm 資料庫中對應的欄位(column)連結起來。當您在屬性前加上 @Persisted,您等於是告訴 Realm:
      1. 請為我儲存這個值:這個屬性的值需要被持久化(persist)到資料庫檔案中。
      2. 請幫我管理存取:當您讀取(get)這個屬性的值時,@Persisted 會自動從 Realm 資料庫中取出最新的值。當您寫入(set)新值時,它會自動將新值存回資料庫。這一切都在背景無縫發生。
      3. 宣告資料庫欄位:Realm 會根據這個屬性的型別(如 String, Int, Bool, Date)在資料庫中建立一個對應型別的欄位。

    簡單來說,@Persisted 取代了舊版 RealmSwift 中需要使用 dynamic var 的寫法,讓程式碼更簡潔、更符合現代 Swift 的風格,並且提供了更強的型別安全。

    3. 定義主鍵 (Primary Key)

    對於鬧鐘這類資料,通常會有一個獨一無二的識別符,這在資料庫中稱為「主鍵」。

    • 作用:主鍵確保每一個鬧鐘物件都是獨一無二的,它有幾個關鍵好處:
      • 高效查詢:可以非常快速地透過主鍵找到特定的鬧鐘物件。
      • 防止重複:避免在資料庫中建立重複的鬧鐘。
      • 方便更新:當您從伺服器同步資料或需要更新現有鬧鐘時,可以使用「更新或新增(create or update)」的邏輯,Realm 會根據主鍵判斷是該更新現有物件還是新增一個物件。
    • 宣告方式:您需要在 AlarmData class 內部覆寫(override)一個名為 primaryKey() 的 class function,並回傳作為主鍵的屬性名稱(一個字串)。例如,您可以新增一個 id 屬性(可能是 UUIDInt 型別),然後在這個 function 中回傳 "id"

    4. 宣告索引屬性 (Indexed Properties)

    如果您的 App 會頻繁地根據某個欄位來搜尋或排序鬧鐘(例如,依「是否啟用」來篩選所有鬧鐘),您可以為該屬性建立索引。

    • 作用:索引就像是書本的目錄,它可以大幅提升資料庫的查詢效能。對某個屬性建立索引後,以該屬性為條件的查詢速度會快很多。但請注意,索引會佔用額外的儲存空間並稍微增加寫入資料的時間,所以只需為經常查詢的屬性建立索引即可。
    • 宣告方式:與主鍵類似,您需要覆寫一個名為 indexedProperties() 的 class function,並回傳一個包含所有需要建立索引的屬性名稱的陣列([String])。

    總結

    所以,要讓您的 AlarmData class 被 Realm 管理,您需要做以下幾件事的宣告:

    1. AlarmData 繼承 Object
    2. 使用 @Persisted 來宣告所有需要被儲存到資料庫的屬性(如:鬧鐘時間、標籤、鈴聲、是否啟用等)。
    3. (強烈建議) 選擇一個唯一識別屬性,並透過覆寫 primaryKey() 將其宣告為主鍵
    4. (可選,為效能考量) 對於經常需要查詢的屬性,透過覆寫 indexedProperties() 將其宣告為索引屬性

    透過這些宣告,RealmSwift 就能夠完全理解您的 AlarmData 模型結構,並為您處理所有底層的資料庫操作。

    • 根據 AI 給的建議,我把我的資料模型設定好了
      檔案:AlarmData.swift
    import Foundation
    import RealmSwift
    
    class AlarmData: Object {
        @Persisted(primaryKey: true) var id: String = UUID().uuidString
        @Persisted var alarmTime: String = ""
        @Persisted var creatTime: String = ""
        @Persisted var name: String = ""
        @Persisted var repeatDays: List<Bool> = List<Bool>()
        @Persisted var sound: String = ""
        @Persisted var isEnabled: Bool = true
        @Persisted var isSnoozeEnabled: Bool = true
    
        convenience init(alarmTime: String, creatTime: String, name: String, repeatDays: [Bool] = Array(repeating: false, count: 7), sound: String) {
            self.init()
            self.id = UUID().uuidString
            self.alarmTime = alarmTime
            self.creatTime = creatTime
            self.name = name
            self.sound = sound
            self.repeatDays.append(objectsIn: repeatDays)
        }
    }
    

今天我們成功地建立了一個乾淨的、基於 XIB 的純程式碼專案,安裝了強大的 Realm 資料庫,並為我們的鬧鐘設計好了AlarmData

明天,我們將專注於 UI 的打造,學習如何UITableView 來呈現一個靜態的列表畫面。


上一篇
Day 9 - 數據儲存:認識 RealmSwift,為之後的專案做準備
下一篇
Day 11 - 【鬧鐘實戰 II】App 框架搭建:用 TabBarController 打造我們的 App 骨架
系列文
iOS? AI-Yes!:用 Vide Coding 加速我的 Swift 學習曲線11
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
eating11111
iT邦新手 5 級 ‧ 2025-09-24 09:59:40

太全面了吧,受益匪淺/images/emoticon/emoticon12.gif

我要留言

立即登入留言