SwiftDataの前にCore Dataを学ぶ
前書き
こんにちは、最近visionOSとWidgetの可能性を感じつつ、中々どういった遊び方があるか思い浮かばないNaoRandDです😇
WWDC23でSwiftDataが登場し、その実は "SwiftUI用に扱いやすくしたCore Data" だと聞いたとき、Core Dataがどんなものだったっけ?となってしまったので、復習もかねて記事を書いてみようかと思います
Core Dataとは?
簡単にいうとデータを管理するフレームワークです
データベースには SQLite が使われています
SQLiteとは?
SQLiteとは何ぞやということにも触れておきます
SQLiteはオープンソースで継承なRDBMS(データベース管理システム) です
SQLとついていますが、データベースを操作する言語ではなく、データベースシステム自体を指す言葉です
サーバーとしてでなく、アプリケーションに組み込むことで利用します
データベースの内容は全てローカルファイルに保存されるので、AndroidやiOSでのアプリケーションにも利用されているわけですね
登場人物
-
Container
- NSPersistentContainer
- アプリ内の Core Data スタックをカプセル化するコンテナ
- アプリとデータベース(SQLite)の橋渡しをしてくれる役割
-
Model
- NSManagedObjectModel
- .xcdatamodeldオブジェクト(実際に試すときのModelファイルに該当)を記述するファイルをプログラム的に表現したもの
-
Entity
- NSEntityDescription
- Model内にあるユーザーが定義するEntity
- Attribute(属性)と合わせて型情報など具体的にどういったデータを扱うかを含んだもの
-
Context
- NSManagedObjectContext
- データの検索や、生成、更新、削除などのCRUD処理やその追跡をするもの
-
Store
- NSPersistentStore
- SQLite(データベース)の情報を管理するクラス
- coordinator経由でModel内で定義されているEntityのインスタンス実体化などを行う
それぞれの役割は下記のような図で表せます
実際に試してみる
登場人物が多いので、やはり身体で覚えるしかありませんね!(脳筋💪)
実際に動かして確認してみることにします
流れ
- プロジェクトの作成
- Modelファイルの作成
- Containerの作成とStoreの読み込み
- ViewからEntityの取得と反映
- 最終的にできたアプリ
どんなアプリを作る
今回は筆者がひっそり好きな将棋を題材にサンプルを作成していきます
もうちょっとわかりやすい題材にしろよ,,,って?
Core Dataのサンプルなのに補足で将棋の解説を書くことになっている本人が一番わかっています😇
扱うデータ
-
Model
- Player:将棋棋士に該当します
- attribute(属性)
- name: 棋士の名前に該当します
- title: 対象の棋士が持つ将棋界8大タイトルに該当します
補足:
- 将棋界8大タイトルとは?
- ニュースでも話題となっていおりますが、2023年7月時点では藤井聡太先生が8つの内7つものタイトルを保持しています(残すは永瀬拓也先生の王座のみ、、、)
1. プロジェクトの作成
まずはXcodeから新規プロジェクトを作成するところから流れを掴んでいきましょう
新規プロジェクト作成時にCore Dataを追加することもできますが、どこが関わっているかを明確にするためにもサラの状態にCore Dataを導入してみます
これはお馴染みの流れですね
Xcodeの新規プロジェクト
Appを選択
Use Core Dataにはチェックを入れずに作成する
2. Modelファイルの作成
新規のプロジェクトが生成できたらModelファイルを作成していきます
Core Data >> Data Modelを選択して"Next"を選択します
SampleModelというModel名にします
Add Entityを選択してEntityをModel内に追加します
今回はPlayerという将棋棋士を表すEntityを用いるので、その名称への変更します
Entity(デフォルト)とある部分をダブルクリックでリネーム
次にEntityにAttributeを追加します。Player(将棋棋士)はname(名称)とtitle(保有するタイトル)を持たせたいのでそのように追加します
3. Containerの作成とStoreの読み込み
ここからはようやくコードに触れます🎉
まずは、アプリはContainerを経由してデータベースを扱うので、Containerを作成します
直接Containerを使用するViewなどに定義することもできますが、containerという情報をCore Dataのものとして明確にしたいため、PersistenceControllerを構造体として定義することにします
import CoreData
struct PersistenceController {
let container: NSPersistentContainer
init() {
// ModelファイルにつけたSampleModelを指定する
container = NSPersistentContainer(name: "SampleModel")
// ContainerからStoreを読み込む処理を記述する
container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error: \(error)")
}
}
}
}
4. ViewからEntityの取得と反映
Containerからデータを取得してViewに反映させていきます
Viewのどこからでもデータの取得ができるように、今回はEnvironmentオブジェクトとしてContainerを扱います
@main
struct TestCoreDataApp: App {
let persistenceController = PersistenceController()
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.container.viewContext)
}
}
}
データの取得は@FetchRequestでEntityを指定することで取得できます
struct ContentView: View {
@Environment(\.managedObjectContext) var viewContext
@FetchRequest(entity: Player.entity(), sortDescriptors: []) var fetchedPlayerList: FetchedResults<Player>
var body: some View {
List(fetchedPlayerList) { player in
if let name = player.name,
let data = player.title,
let title = try? JSONDecoder().decode(Title.self, from: data){
VStack {
Text("棋士名:\(name)")
Text("タイトル:\(title.rawValue)")
}
}
// ・・・
}
}
Titleのデコードは何?
Titleは将棋棋士の保有するタイトルを表します
タイトル数が決まっていることを加味してenum定義としています
enum Title: String, CaseIterable, Codable, Identifiable {
var id: String { rawValue }
case ryuo = "竜王"
case meijin = "名人"
case ouza = "王座"
case oui = "王位"
case ousyo = "王将"
case kisei = "棋聖"
case kio = "棋王"
case eio = "叡王"
case none = "なし"
}
また、データを追加するためにPickerを用いてタイトルを選択させるために、CaseIterableからタイトル名の一覧が取得できるようにしています
5. 最終的にできたアプリ
追加したい将棋棋士の名前をTextFieldに入力して、Pickerから選択したタイトルを保持させる動作が確認できました
Core Dataとして永続化できているので、アプリをキルしてもデータが残っていることが確認できます
1996年に果たされた羽生善治先生の7大タイトル制覇時を表せていますね
サンプルコードは下記にあります🙋♀️
Tips:あれ?叡王は?
当時は叡王がなかったため、7大タイトルで全タイトル制覇となります
まとめ
筆者のエゴ全開で作りたいサンプルに多少クセがあるため、サンプルとしてはあまり良くなかったかも知れませんね、、、(猛省)
ただ、実際に手を動かしてCore Dataがどのようなものかは掴めた気がしています
触った上で感想は以下のように思いました🤔
👍:メリット
- GUIからModelファイルを定義でき導入が容易になる
- UserDefaultsよりもデータを構造として扱いやすくなる
😿:デメリット(少し不便?)
- 登場人物の把握を含めると多少の学習コストがある
- Containerの定義部分などはボイラープレートとなる
- ModelファイルをGUIから行うために作成時の入力ミスが起きやすく、自動化などはしづらい
SwiftDataの内容にも触れて、どういった部分でメリット・デメリットがあるかをより掴んでいこうかと思います
あとがき
ここは間違っているよ、こういった解釈がいいんじゃないか?、など大歓迎です!
Discussion