歡迎來到「鬧鐘實戰」的第一天!在過去九天,我們儲備了所有必要的知識。今天,我們要建立一個乾淨的專案,安裝好我們的資料庫,並設計出鬧鐘的核心資料模型。
我的第一步,是在 Xcode 中建立一個全新的專案。我選擇了 App
模板,並將專案命名為 ClockClone
。
在專案選項中,雖然 Interface
我先選擇了預設的 Storyboard
,但我計劃將它移除,改用我們在 Day 6 練習過的、更具彈性的 XIB 模式。
專案建立後,我便開始進行「無 Storyboard 化」的改造工程。我的操作步驟如下:
刪除 Storyboard 檔案: 在專案導覽器中,找到 Main.storyboard
檔案,直接將它刪除。
修改專案設定:
點擊最頂層的專案檔案(藍色圖示)。
選擇你的 TARGETS
,切換到 Info
分頁。
找到並刪除 Main storyboard file base name
這一行。
再選擇Build Settings
右上角搜尋 UIKit Main Storyboard File Base Name
把裡面的Main刪掉
建立我們的第一個 ViewController (含 XIB)
現在,我們來建立 App 的主畫面 MainViewController
。這次,我們將體驗到建立 XIB 檔案的便捷之處。
Controller
資料夾, 在 Controller
裡面再建一個 MainView
資料夾。MainView
資料夾上右鍵,選擇 New File from Template...。修改 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()
}
接下來,我需要為專案安裝我們在 Day 9 選定的資料庫——Realm。我使用 Xcode 內建的 Swift Package Manager (SPM) 來完成這個任務。
我在頂部選單選擇 File
-> Add Packages Dependencies...
,選擇realm-swift
,請確保 Realm
被勾選(不要勾RealmSwift
),然後再次點擊 Add Package
。安裝成功後,Package Dependencies
中出現了 Realm
。
AlarmData
資料模型這是今天最需要動腦,也是最關鍵的一步:設計鬧鐘的資料模型。
我知道我需要儲存哪些資訊(時間、標籤、重複日等),但我對 Realm 的具體語法還不熟悉,例如如何讓一個普通的 Swift Class 變成 Realm 可以儲存的格式。
AI 協作
我正在用 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:
- 請為我儲存這個值:這個屬性的值需要被持久化(persist)到資料庫檔案中。
- 請幫我管理存取:當您讀取(get)這個屬性的值時,
@Persisted
會自動從 Realm 資料庫中取出最新的值。當您寫入(set)新值時,它會自動將新值存回資料庫。這一切都在背景無縫發生。- 宣告資料庫欄位:Realm 會根據這個屬性的型別(如
String
,Int
,Bool
,Date
)在資料庫中建立一個對應型別的欄位。簡單來說,
@Persisted
取代了舊版 RealmSwift 中需要使用dynamic var
的寫法,讓程式碼更簡潔、更符合現代 Swift 的風格,並且提供了更強的型別安全。3. 定義主鍵 (Primary Key)
對於鬧鐘這類資料,通常會有一個獨一無二的識別符,這在資料庫中稱為「主鍵」。
- 作用:主鍵確保每一個鬧鐘物件都是獨一無二的,它有幾個關鍵好處:
- 高效查詢:可以非常快速地透過主鍵找到特定的鬧鐘物件。
- 防止重複:避免在資料庫中建立重複的鬧鐘。
- 方便更新:當您從伺服器同步資料或需要更新現有鬧鐘時,可以使用「更新或新增(create or update)」的邏輯,Realm 會根據主鍵判斷是該更新現有物件還是新增一個物件。
- 宣告方式:您需要在
AlarmData
class 內部覆寫(override)一個名為primaryKey()
的 class function,並回傳作為主鍵的屬性名稱(一個字串)。例如,您可以新增一個id
屬性(可能是UUID
或Int
型別),然後在這個 function 中回傳"id"
。4. 宣告索引屬性 (Indexed Properties)
如果您的 App 會頻繁地根據某個欄位來搜尋或排序鬧鐘(例如,依「是否啟用」來篩選所有鬧鐘),您可以為該屬性建立索引。
- 作用:索引就像是書本的目錄,它可以大幅提升資料庫的查詢效能。對某個屬性建立索引後,以該屬性為條件的查詢速度會快很多。但請注意,索引會佔用額外的儲存空間並稍微增加寫入資料的時間,所以只需為經常查詢的屬性建立索引即可。
- 宣告方式:與主鍵類似,您需要覆寫一個名為
indexedProperties()
的 class function,並回傳一個包含所有需要建立索引的屬性名稱的陣列([String]
)。總結
所以,要讓您的
AlarmData
class 被 Realm 管理,您需要做以下幾件事的宣告:
- 讓
AlarmData
繼承Object
。- 使用
@Persisted
來宣告所有需要被儲存到資料庫的屬性(如:鬧鐘時間、標籤、鈴聲、是否啟用等)。- (強烈建議) 選擇一個唯一識別屬性,並透過覆寫
primaryKey()
將其宣告為主鍵。- (可選,為效能考量) 對於經常需要查詢的屬性,透過覆寫
indexedProperties()
將其宣告為索引屬性。透過這些宣告,RealmSwift 就能夠完全理解您的
AlarmData
模型結構,並為您處理所有底層的資料庫操作。
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
來呈現一個靜態的列表畫面。