【じっくりSw1ftUI66】実践(DB)編36〜第48章 Core DataとSwiftUIの紹介
さてと、前回
で、
SwiftUI Document Groupの実践例
まではやったので〜〜〜今回からいよいよ、
SwiftUI上のデータベースを取り扱ってく〜〜〜
データベースとアプリを連携させないとアプリとは言えない
みたいなことを、ちょうど6年くらい前にAndroidの本で豪語し(=大嘘コイ)てる本もみたことがあるけど、
データベースとアプリを連携させるかどうかは
顧客の要求
自分が作りたいアプリのコンセプト
なんかに依るので、別に
アプリ自体に永続的なデータを管理したい機能がいらない
限りは、ここからしばらく学ぶ
データベース周りはさらっと、こんな機能もあるんだなあくらいでOK
別に、
ゲームでクリアの得点をクリア時点で出せればいい
クイズでその時だけ得点が分かればいい
まとめリンクみたいなアプリで、リンクに飛べればそれでいい
なんてアプリにデータベースなんて付けても仕方ないし、一時的なデータの集約であれば
UserDefault
の方がシンプルだし、向いていたりするからね👀💦
RPAでもGoogleAppsScriptでもそうだけど、
自分の技術力アピール
👉知ってる知識のひけらかし(:教科書事例)
で、
バカのひとつ覚え
みたいに、
ユーザーさんのメンタルモデル
顧客の業務
顧客の要求事項
アプリ全体のコンセプト
なんかも考慮せずに、
いずれ必要になるかもだから〜〜とか、入力漏れがないように〜〜〜とかで、
知ってるからってやみくもに何でもかんでもデータベース機能を盛り込めばいいってものではないからね
例えば、ごく最近の話だけど、RPAの案件で
システムエラーならロボットをコカした方がいいところに、無駄にTry-Catchなんかを入れて、システムエラーが起きてるのに、次の行を入力しに行ってるロボットを見た時には、
(コイツいつも自称プロで上級者ぶってんだけど、システムエラーって何か分かってないのでは?
しかも、ちゃんとしたテストデータでテストカバレッジも網羅できてないし、、、
顧客の要求にも、システムエラーの時にこんな要件載ってないぞ。
RPA7年やっていて、プログラミング未経験で3ヶ月しかやっていない子でもやらないくらいなミスを犯してる、、、👀マジか❓
👉素人じゃん。。。恥ずかし、、、💦)
って、笑ったけどね
7年やっていてこれはマジでセンスない。
てか、エラーハンドリングの基礎すら分かってない。。。
ま、アクション・リサーチが根付いていない自称、上級者とか大学の講義なんかでは、
データベースを連携させる前提とか必須とかで講義とか組み込みをしてるみたいだけど、
別にデータベース機能をつけていないとAppStoreからリジェクトを喰らう
なんてこともないので〜〜〜とにかく、
✨(そのアプリにおける)コンセプトの完全性✨
を意識してね
(*コンセプトの完全性については、下のリンク先の本なんかが世界的なバイブルだから、興味がある人はそっちも読んでみてね〜〜〜)
今回もオイラの学びなんて関係ないって人は、
に全部載ってるみたいだから、そっちで勉強したらいいんじゃね👀💦
んだば、今回も早速、
じっくり第48章を読んでく👓
いきなりだけど、ContentViewまわりを〜〜〜ってところの終盤まで以下で動かしてんだけど、どうやら
ここの章のサンプルは、端折り過ぎて、あくまでも概要説明でいろんな関連性の薄いサンプルコードとかスクショ例を繋ぎ合わせてるだけみたいなので、、、、💦
正直、これ以上この章の本のとおりにXcodeのサンプルを動かしても意味がない笑🤣
ま、だから紹介なのね👀💦💦💦
と、終盤まで動かしてみて、それがわかっただけでも
収穫あり🕺
以下は、ここがポイントでDB周りの考え方なんかに触れといてもらうだけでいいかなあ、本のサンプル自体がチグハグでどうせ動かないし〜〜〜〜
以下、いつもな感じで読み進めた記事
概要としては、
データを格納して管理する機能=データベース
について、
ま、SQLiteみたいなもんだよてな感じで
まずは書いてんね。
ここでポイント①:SwiftUIもフレームワークのひとつ
なんだけど、Swift周りで使えるデータベースって非常に多岐に渡っていて、
パッと思いつくだけでも、
◾️Apple純正(この本で取り扱ってるヤツ)
CoreData
CloudKit
SwiftData
など。
◾️Apple純正以外(この本で取り扱ってないヤツ)
Firebase
Realm
Laravel
などなど。
ま、要は、
自分が使いたいフレームワークをXcodeに組み込んで、取り込めばいいだけ
だから、今あげたヤツを全部覚えようとか使えないといけないなんて思い込まずに、
自分が使いたいデータベースフレームワークを取り込んで、
そのフレームワークを使えるようになればいい
くらいな気軽な気持ちでやってねえ〜〜〜
ここでポイント②:データベースって何?
例えば、
・テキストボックスなんかに何かの値を入力する=データ
↓
・入力したデータを他でも使いまわせるように何かの箱とか引き出しに格納しておく
=データベース:DB(実務ではデービーって読む)
↓
・使いたいデータがどこの箱や引き出しに入れてあり、すぐに取り出せるように管理までする
=データベース管理システム:DBMS。
↓
・各データベースの各データで一部が更新や削除されると、相関するデータも一緒に更新されたり、削除される(リレーション。相関関係)=リレーショナル・データベース管理システム:RDBMS
ってイメージでまずは、OK。
そんで、リレーショナル・データベース管理システムが備えるべき機能
CRUD:C(作成:Create)、R(読み込み:Read)、U(更新:Update)、D(Delete:削除)なんかもある。
ってだけ。
*ま、ここら辺はSwiftでは使わないけど、Microsoft社なんかが出してる
ACCESS
も立派なデータベース管理システムだから、データベースの基本を一番簡単に画面操作なんかでクエリなんか体感したい人は、触りながら学んでみるといい。
ここでポイント③:(当たり前すぎるからか🧐)RDBMSやCRUDは教えても、ACIDを教えない企業が多い。
さっき出てきたRDBMSって機構をよく会社の研修なんかではデータベースの概要として案内はするんだけど、トランザクションデータベースがほとんどで、そこに必要な
A:Atomicity(原子性)。更新内容の一部だけが実行されることはなく、すべて実行されるか、取り消される。
C:Consistency(一貫性)。データの整合性が保たれ、不可能な更新処理は実行されない。
I:Isolation(独立性)。更新中の処理が、他の更新中の処理の影響を受けることなく独立している。
D:Durability(永続性)。更新が完了したら結果は記録(永続)され、システムトラブルによって失われない。出典元:『Realm入門』
を最初に概念として、きちんと教えないから、組織的に、
ACIDじゃないデータベースは毒(=ACID)になってる
ことが多い。
*元々、SQL(シークェル。Structured Query Languageの略ではない方の、データベースの原始的な概念)を知らないばかりに、
主キーが必要か(いやいや、それはいる。てか基本でしょ。ないとリレーションできないでしょ。)
データテーブルごとにシーケンシャル番号は付けるべきか(付けずに管理しやすいか?)
処理列を増やすのは邪道(いやいや、中間ファイルや処理ファイルは基本でしょ)
みたいな不毛な論争(「きのこたけのこ戦争」にそっくり)をしてる動画とか記事、会社に遭遇するが、
何事も基本に忠実にしておくほうが、管理しやすいに決まってる
👉シンプルイズビューティフル
ま、データベース周りに関して関心があるなら、
過去に
でも記事にした本だけど、
が実務的に見ても一番、薄くてわかりやすいし、解決策が豊富なんでおすすめ。
ここでポイント④:データベースといえば、=SQLではないからね
日本の企業は古過ぎる+自己学習礼賛なせっかち文化なので〜〜〜
データベース=SQL
と即断即決で結び付けて考えてる人が多過ぎるんだけど、
リレーショナル・データベース管理システム(RDBMS):データを管理する環境、機構
SQL(Structured Query Language):データベースにやってほしいことを問い合わせる一手法
であって、モバイルデータベースとかでは
純粋なSQL自体を使わないデータベースも主流になってきてる
ま、自己学習礼賛+せっかちな日本の企業風土だと、
純粋なSQLを使わない=学習コストとマイグレーションコストが高い
って敬遠されがち。なんだけど、、、。
ここでポイント⑤:純粋なSQLを使わないRDBMSが生まれる背景
ま、端的にゆーてSQLは、
言語の書き方自体がモダンじゃない
効率が悪い
(処理速度など)テーブルの設計がアプリの設計思想に合わない
などなど、様々な問題があるから、
純粋なSQLを捨てるプラットフォーマーが増えた
ってのがあるんだよね〜〜〜。そんな時代に、
データベースといえば、SQLって即断即決で思い込み、
純粋なSQLじゃないデータベースは、学習コストやマイグレーションに時間がかかる
なんてだけで、旧来型のデータベースに固執しちゃってる企業が殆どな国は今、どーなってるかといえば、推してしるべしでしょ👀💦
要は、
その開発環境(プラットフォーム)に合わせて、やりたいデータの管理ができればそれでいいってだけで、SQLを採用してるかしてないかを基準に考えることがおかしいんだけどね。
👉ちょこっと勉強すれば別にデータベースなんて、CRUDとACIDだけでやってることに大差はないんだから。
=純正の環境に触れず、どこまでできるかも知らずに、開発環境自体を採用しないなんて勿体無いの極み
とまあ、データベース周りで書きたいことは大体書いたので、本編に進む〜〜〜
ま、まずは、
CRUDの最初として、データを放り込む、他のデータベースでゆーところのテーブルみたいな、
永続コンテナを作成
が出てきてるね。
その次に、
各オブジェクトを管理する
Manage Object
が出てきてる👀💦
それで、Manage Object Contextで、
CoreDataStack内で、リレーショナルデータベース的なデータの出し入れを各オブジェクトとやってる
みたいなことが書いてんね。
で、各オブジェクトの管理には、
Manage Object Model
で、MVVM(モデル-ビュー-ビューモデル)の設計思想に基づいた、
データモデルが必要
みたいなことが書いてんね。
ここでポイント⑥:ManageObjectModelに必要な要素
冒頭のリンクから抜粋するけど、
Relationships – Core Data のコンテキストでは、リレーションシップは、あるデータ オブジェクトが別のデータ オブジェクトとどのように関連しているかを示すという点で、他のリレーショナル データベースシステムと同じ。Core Data のリレーションシップは、1 対 1、1 対多、または多対多。
Fetched property –関係を定義する代わりに使用できる。フェッチされたプロパティを使用すると、エンティティ間に関係が定義されているかのように、あるデータ オブジェクトのプロパティに別のデータ オブジェクトからアクセスできる。フェッチされたプロパティには関係の柔軟性が欠けており、Apple の Core Data ドキュメントでは「弱い一方向の関係」と呼ばれ、「疎結合の関係」に最適。
Fetch request – 定義済みの述語に基づいてデータ オブジェクトを取得するために参照できる定義済みのクエリ。例えば、フェッチ要求をエンティティに構成して、名前フィールドが「John Smith」と一致するすべての連絡先オブジェクトを取得できる。
と、要は、今までで書いてきてることが色々とまとめて書いてあんね🧐
ここでポイント⑦:『Apple の Core Data ドキュメントでは「弱い一方向の関係」と呼ばれ、「疎結合の関係」に最適』てのが結構重要で、
アクション・リサーチが欠落してる学校や教科書なんかだけでお勉強してきただけの
自称、DB職人さんほど、CONSTRAINTをむやみに入れたがる
って傾向があるし、ACCESSなんかでもいいからデータベース管理をやったことがある人ならわかると思うんだけど、
CONSTRAINT:データテーブルにおける制約
を最初から入れちゃうと、リレーションの変更とか各レコードのデータ型の変更なんかでめっちゃ苦労するんだよね〜〜〜。
先に紹介した、『データベース・リファクタリング』って本なんかでも、冒頭でフロントエンドSEとデータベースSEの不毛な論争なんかが書かれてるんだけど、
今の時代に、
ウォーターフォール型の開発
👉ガッチガチに最初からデータテーブルの構成が未来永劫変わらない
なんてプロジェクトやアプリなんて存在しない。
にもかかわらず、自称、DB職人さんは、DBのことしか見てないからか、
未だに、ウォーターフォール型の開発みたいな言動で、DB管理の理想だけ押し付けてくるんだよね〜〜〜(てゆーても4年くらい前までの話なので、今はもう変わってることを願うが。。。💦)
例えば、顧客からデータ列をひとつ増やしたいって要望があっただけなのに、
下手にCONSTRAINTなんて入れちゃってると、下手したらデータベースそのものを最初から再度作り直さないといけないなんてことになりかねない。
その過程で、
再構築の間は、データの更新ができないから業務が止まる
既存のデータベース自体のデータ移行の過程で、データが飛ぶ
なんてことにでもなったら、
阿鼻叫喚の嵐😱
下手したら、顧客への損害賠償物😱
なんだよね。
データベースに不用意に制約を入れてガッチガチにしちゃう
👉いくら他を疎結合に作っていても、密結合にしてるのと変わらない。
ま、SQLの教科書とか資格の本なんかで最初に書いていて、むしろCONSTRAINTを推奨してるレベルのものも過去にお見受けしたことあるけど、それはあくまでも、
開発のエコサイクルを知らずに、データベースだけに焦点を絞った理想像
を書いてるだけだから鵜呑みにしないでね〜〜〜
過去に何でもかんでもデータテーブルを作成したら、すぐにCONSTRAINTを入れたがる駆け出しちゃんを何人も見たことあるし、そーゆー人ほど、教科書とか学校で習ったことを絶対視して、こちらを素人って見下してたからね🤣
オイラたちが、今後、テーブル構成が変わることを見越して、敢えて不要な制約を入れてないテーブル構成にしてるのを見て、「ここの組織の人はCONSTRAINTも知らないんですか?使えないんですか?」って(斜め)上から目線で言われてみんなで爆笑したことは今でも覚えてるわ。
ザ・教科書どおり
「すみません。もちろん、みんなそれくらいDBの基礎中の基礎なんで知ってますし、それをウチでは敢えて使ってません。DBがフロントエンドの改修の制約になったら意味ないんで!!!!」
って言ったなあ🧐
とまあ、よもやま話はその辺にして、とりあえず、
教科書に載ってるからって、CONSTRAINTは、(使えない、知らないじゃなく)極力使わない
何で使ってないか分からない時は素直に何で使ってないか職場の人に聞いてみて〜〜〜
それ確認せずに、教科書どおりにやっちゃうとおそらく周囲から顰蹙買うから
ってだけ覚えといてね〜〜〜〜🕺
で、本に戻るけど、
お次は、複数の格納オブジェクトを管理するのに必要な
永続ストアコーディネータ
について書いてんね。ま、要はリレーション管理機能みたいなものかなと、、、🧐
さらに、永続オブジェクトストアで
各レコードを管理する永続管理基盤の構築サポート
について書いてんね。
で、やっとこ操作が伴う説明で〜〜〜
Xcodeの環境にエンティティモデルを定義〜〜〜
なので、今回も、前々回同様に、新規のプロジェクトファイルを使って〜〜〜
次の画像は、あくまでも例え話みたいだから一旦無視🤣して〜〜〜
お次は、永続コンテナを初期化なんかをやっていこうとしたんだけど、、、
◾️E48FruitsCoreDataApp.swift
import SwiftUI
@main
struct E48FruitsCoreDataApp: App {
let persistenceController = PersistenceController.e48FruitsShared
var body: some Scene {
WindowGroup {
ContentView()
.environment(\.managedObjectContext, persistenceController.e48FruitsContainer.viewContext)
}
}
}
◾️ContentView.swift
import SwiftUI
import CoreData
struct PersistenceController {
static let e48FruitsShared = PersistenceController()
static var e48FruitsPreview: PersistenceController = {
let e48FruitsResult = PersistenceController(inMemory: true)
let e48FruitsViewContext = e48FruitsResult.e48FruitsContainer.viewContext
for _ in 0..<10 {
let newItem = Item(context: e48FruitsViewContext)
newItem.timestamp = Date()
}
do {
try e48FruitsViewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
return e48FruitsResult
}()
let e48FruitsContainer: NSPersistentContainer
init(inMemory: Bool = false) {
e48FruitsContainer = NSPersistentContainer(name: "E48FruitsCoreData")
if inMemory {
e48FruitsContainer.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null")
}
e48FruitsContainer.loadPersistentStores(completionHandler: { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Unresolved error \(error), \(error.userInfo)")
}
})
e48FruitsContainer.viewContext.automaticallyMergesChangesFromParent = true
}
}
struct ContentView: View {
@Environment(\.managedObjectContext) private var viewContext
@FetchRequest(
sortDescriptors: [NSSortDescriptor(keyPath: \Item.timestamp, ascending: true)],
animation: .default)
private var items: FetchedResults<Item>
var body: some View {
NavigationView {
List {
ForEach(items) { item in
NavigationLink {
Text("Item at \(item.timestamp!, formatter: itemFormatter)")
} label: {
Text(item.timestamp!, formatter: itemFormatter)
}
}
.onDelete(perform: deleteItems)
}
.toolbar {
ToolbarItem(placement: .navigationBarTrailing) {
EditButton()
}
ToolbarItem {
Button(action: addItem) {
Label("Add Item", systemImage: "plus")
}
}
}
Text("Select an item")
}
}
private func addItem() {
withAnimation {
let newItem = Item(context: viewContext)
newItem.timestamp = Date()
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
private func deleteItems(offsets: IndexSet) {
withAnimation {
offsets.map { items[$0] }.forEach(viewContext.delete)
do {
try viewContext.save()
} catch {
let nsError = error as NSError
fatalError("Unresolved error \(nsError), \(nsError.userInfo)")
}
}
}
}
private let itemFormatter: DateFormatter = {
let formatter = DateFormatter()
formatter.dateStyle = .short
formatter.timeStyle = .medium
return formatter
}()
#Preview {
ContentView().environment(\.managedObjectContext, PersistenceController.e48FruitsPreview.e48FruitsContainer.viewContext)
}
みたいな感じで、
本の流れにも沿っていないし、面白くもないので〜〜〜〜
新しいプロジェクトファイルをCoreDataなしで作り直して〜〜〜
作り直したプロジェクトファイルの初期値
◾️E48FruitsCoreData2App.swift
import SwiftUI
@main
struct E48FruitsCoreData2App: App {
var body: some Scene {
WindowGroup {
ContentView()
}
}
}
◾️ContentView.swift
import SwiftUI
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
から追加してく〜〜〜〜
まずは、CoreDataをインポート
import CoreData
で、永続コンテナの初期化
永続コンテナ用の構造体を作って〜〜〜
struct E48FruitsCoreData2PersistenceController {
}
struct E48FruitsCoreData2PersistenceController {
let e48Container: NSPersistentContainer
e48Container = NSPersistentContainer(name: "E48FruitsCoreData2")
e48Container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Container load failed: \(error)")
}
}
}
てな感じにすると、、、
最初のプロジェクトと見比べると、
イニシャライザの中にセットしないといけないみたいなので
(動くコードで書いとけよ笑🤣)
struct E48FruitsCoreData2PersistenceController {
let e48Container: NSPersistentContainer
init() {
self.e48Container = NSPersistentContainer(name: "E48FruitsCoreData2")
e48Container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Container load failed: \(error)")
}
}
}
}
で、
で、複数の管理対象オブジェクトを取得できるように
let e48ViewContext = e48Container.viewContext
でやるとできるみたいに書いてんだけど、、、
なので〜〜〜最初のコードを参考に永続コントローラーを適用したプレビュー用の変数を用意して、その中に宣言してあげると〜〜〜
struct E48FruitsCoreData2PersistenceController {
static var e48Preview:
E48FruitsCoreData2PersistenceController = {
let e48Result = E48FruitsCoreData2PersistenceController()
let e48ViewContext = e48Result.e48Container.viewContext
return e48Result
}()
let e48Container: NSPersistentContainer
init() {
self.e48Container = NSPersistentContainer(name: "E48FruitsCoreData2")
e48Container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Container load failed: \(error)")
}
}
}
}
次も特に例のなので、
contact.name = "John Smith"
contact.address = "1 Infinite Loop"
contact.phone = "555-564-0980"
コイツはすっ飛ばして〜〜〜
管理オブジェクトを保存するメソッドを追加〜〜〜
struct E48FruitsCoreData2PersistenceController {
static var e48Preview:
E48FruitsCoreData2PersistenceController = {
let e48Result = E48FruitsCoreData2PersistenceController()
let e48ViewContext = e48Result.e48Container.viewContext
do {
try e48ViewContext.save()
} catch {
let nsError = error as NSError
fatalError("未解決なエラー発生 \(nsError), \(nsError.userInfo)")
}
return e48Result
}()
let e48Container: NSPersistentContainer
init() {
self.e48Container = NSPersistentContainer(name: "E48FruitsCoreData2")
e48Container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Container load failed: \(error)")
}
}
}
}
あとは、
@FetchRequest(entity: Customer.entity(), sortDescriptors: [])
private var customers: FetchedResults<Customer>
てな感じのプロパティラッパーと、
@FetchRequest(entity: Customer.entity(),
sortDescriptors: [NSSortDescriptor(key: "name", ascending: true)])
private var customers: FetchedResults<Customer>
てな感じで、昇順や降順に並べ替えができる
@FetchRequest(
entity: Customer.entity(),
sortDescriptors: [],
predicate: NSPredicate(format: "name LIKE %@", "John Smith")
)
private var customers: FetchedResults<Customer>
でワイルドカードが使える。
くらいがわかっとけば充分らしい🤣なぜなら、次回
でしっかり操作しながらやるんだと笑😆
だったら最初からゆーとけよ😤って感じ
今回の感想:以前から感じていたけど、
読者が既にわかってる前提で書いてる本だから、
CoreDataの教え方までくるとマジで
めっちゃ教え方が下手な本だね藁😛
チュートリアルで実践例と紹介を切り分けたら、わかるものも分からないし、混乱するだけだってことが分かってない人が書いてんね。
(おそらく何人かグループに人がいて、めっちゃ文芸的な素養のない人が、、、💦)
ま、悪いサンプル=失敗例が今回はたくさん示せたから
こっちはそれだけで収穫だけど🕺
👉CoreData周りはEntityが肝になるし、
サンプルみたいに繋がりが悪いと
暗礁に乗り上げるから、注意して、
💃次回でしっかり最初っから操作しよ🕺
自分で完全オリジナルなテクニカルブログ書いてる時には、常に意識してることだけど、
雑な紹介とか書いた著者しか分からないようなサンプルコードや画像の本なんざ
百害あって一理なし
だねえ〜〜〜🧐
今回のコードまとめ
動かないからね〜〜〜あくまでも、この程度って感じで〜〜〜
import SwiftUI
import CoreData
struct E48FruitsCoreData2PersistenceController {
static var e48Preview:
E48FruitsCoreData2PersistenceController = {
let e48Result = E48FruitsCoreData2PersistenceController()
let e48ViewContext = e48Result.e48Container.viewContext
do {
try e48ViewContext.save()
} catch {
let nsError = error as NSError
fatalError("未解決なエラー発生 \(nsError), \(nsError.userInfo)")
}
return e48Result
}()
let e48Container: NSPersistentContainer
init() {
self.e48Container = NSPersistentContainer(name: "E48FruitsCoreData2")
e48Container.loadPersistentStores { (storeDescription, error) in
if let error = error as NSError? {
fatalError("Container load failed: \(error)")
}
}
}
}
struct ContentView: View {
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
}
}
#Preview {
ContentView()
}
Apple公式
さてと、次回は
本文で述べたとおり〜〜〜
第49章 iOS 17 SwiftUI コアデータチュートリアル
をやってく〜〜〜
記事公開後、
今回も記事公開後の作業は特になし🧐
さてと、後は
今回の芥川賞を読んだり、ジムのプールで歩いたり、来週から別の仕事なので〜〜〜〜
ゆっくりアナログ生活に戻って過ごそう🕺
じゃ、また来週
みなさんも良き週末を〜〜〜