【SwiftData】 データストアファイルを SQLite コマンドで見てみる

そもそものきっかけは、

「Preview と シュミレーター の保存データは違うのか」

という素朴な疑問から。

 

📂 データファイルはどこにあるのか

xcrun で調べたみたが、パスが深くて長いし、

デバイスやアプリのIDがあれこれ違うので面倒すぎる。

コード内から吐かすのが楽ちん。


FileManager
  .default
  .urls(for: .applicationSupportDirectory, in: .userDomainMask)
  .last!
  .path(percentEncoded: false)

👉 Where does SwiftData store the data? | Apple Developer Forums hatena-bookmark

とか、いや、


URL.applicationSupportDirectory
  .path(percentEncoded: false)

で取得できるので print() などで出力。


// Playground
/Users/{USER_NAME}/Library/
  Application Support/

// Preview
/Users/{USER_NAME}/Library/Developer/
  Xcode/UserData/Previews/
  Simulator Devices/{DEVICE_ID}/
  data/Containers/Data/Application/{APP_ID}/Library/
  Application Support/

// Simulator
/Users/{USER_NAME}/Library/Developer/
  CoreSimulator/
  Devices/{DEVICE_ID}/
  data/Containers/Data/Application/{APP_ID}/Library/
  Application Support/

見づらいので改行しています。

実行環境で違うのか。

それぞれの「Application Support」 ディレクトリ以下に該当ファイルがある。


default.store
default.store-shm
default.store-wal

 

📂 SQLite コマンドで見てみる

👉 sqlite — Homebrew Formulae hatena-bookmark

せっかくなので覗く。


$ sqlite default.store

SQLite version 3.43.2 2023-10-10 13:08:14
Enter ".help" for usage hints.

sqlite> .tables
ACHANGE             ATRANSACTIONSTRING  ZTODO               Z_MODELCACHE
ATRANSACTION        Z_METADATA          Z_PRIMARYKEY

sqlite> .schema ZTODO
CREATE TABLE ZTODO ( Z_PK INTEGER PRIMARY KEY, Z_ENT INTEGER, Z_OPT INTEGER, ZTIME TIMESTAMP, ZTEXT VARCHAR );

sqlite> .headers on
sqlite> .mode column

sqlite> select * from ZTODO;
Z_PK  Z_ENT  Z_OPT  ZTIME             ZTEXT
----  -----  -----  ----------------  -----------------------------------------------------------
11    2      1      727759392.308872  2. Do, or do not. There is no try.
12    2      1      727759392.914907  3. Most things look better when you put them in a circle.
13    2      1      727759393.191901  4. Could not get advice.
14    2      1      727760485.069984  0. You're not that important; it's what you do that counts.
15    2      1      727760485.781123  1. Most things done in secrecy are better left undone.
16    2      1      727760486.503812  2. When in doubt, just take the next small step.
17    2      1      727760487.112024  3. Have a firm handshake.

よくできてるなあ、SwiftData って。




【 #SwiftData 】ModelContainer の View へのセット

ModelContainer セット時には便利な、

view extension と modifier のセットですが、

複数に重なってくると、

なんだか頭が痛くなる。

👉 sample-backyard-birds/BackyardBirdsData/General/BackyardBirdsDataContainer.swift at 1843d5655bf884b501e2889ad9862ec58978fdbe · apple/sample-backyard-birds hatena-bookmark

再度、ModelContainer の基本的な利用の流れを整理しておきます。

 

👨‍💻 ModelContainer から ViewModifier を作成


struct DataContainerViewModifier: ViewModifier {
  private let container: ModelContainer
    
  init() {
    container = try! ModelContainer( 
      for: Schema([Plant.self, Bird.self]),
      configurations: [ModelConfiguration()]
    )
  }
    
  func body(content: Content) -> some View {
    content
      .modelContainer(container)
  }
}

 

👨‍💻 ViewModifier から extension View を作成


extension View {
  func dataContainer() -> some View {
    modifier(DataContainerViewModifier())
  }
}

これ、#Preview など利用したい View で簡単に利用できて便利です。

 

👨‍💻 View にセットする


@main
struct SampleApp: App {
  var body: some Scene {
    WindowGroup {
      ContentView()
        .dataContainer()
    }
  }
}

これで この View 以下の View では、

@Query, @Environment を利用して、

データ取得、監視、操作が行えるようになりました。

 

👨‍💻 元コードを編集してみる

元のサンプルコードは、

以上の流れが、2層になっているので、

1層にして、少し見通し良くしてみました。

作成後の ModelContainer の ModelContext は、


container.mainContext

で、直接取得することができます。

 

👨‍💻 まとめ

SwiftData の基本的な構成を図でまとめておきます。

👉 Preserving your app’s model data across launches | Apple Developer Documentation hatena-bookmark

手順的には、


1. Scheme (@Model) と ModelConfiguration を作成する。

2. それらを使って ModelContainer を作成する。

3. それを使って extension View を作成する。

4. それを View にセットする。

というかんじで定型的にサンプルコードでは書いてます。

以下、参考記事。

👉 Dive deeper into SwiftData - WWDC23 - Videos - Apple Developer hatena-bookmark
👉 SwiftData Stack: Understanding Schema, Container & Context - swiftyplace hatena-bookmark
👉 View を拡張したい場合は原則として extension を使用し、状態保持が必要な場合のみ `ViewModifier` を実装する。 · YusukeHosonuma/Effective-SwiftUI · Discussion #31 hatena-bookmark

👉 【SwiftUI + SwiftData】List のアイテムの Preview hatena-bookmark


【Swift】「UnicodeScalar」とは、いわゆる「コードポイント !」だったのか ☀️

高校生のときに聞いたことある「スカラー」。

〘名〙 (scalar) 長さ、面積、重さなど、大きさだけで定まる量。常識上の数。ベクトルに対していう。スケーラー。

👉 スカラーとは? 意味や使い方 - コトバンク hatena-bookmark

「方向がない」という雰囲気だけ覚えていたけども。

 

☀️ UnicodeScalar

以下、サンプルコード。


let data = [
  ["61"],
  ["3042"],
  ["1F635", "200D", "1F4AB"],
  ["1F468", "200D", "2764", "FE0F", "200D", "1F468"],
  ["1F1EF", "1F1F5"]
]

for codepoints in data {
  let s = String(
    codepoints
      .map { Int($0, radix: 16)! }
      .map { UnicodeScalar($0)! }
      .map { Character($0) }
  )
  print(s)
  print(codepoints)
  print(
    s.unicodeScalars
      .map { String($0.value, radix: 16, uppercase: true) }
  )
  print()
}

変換の流れ的には以下の順序で変換。


[Int]

  ↕

[UnicodeScalar]

  ↕

[Character]

  ↕

String([Character])

 

☀️ まとめ

「UnicodeScalar」とは「コードポイント !」のことですね。

nil は許しません。

String.unicodeScalars() は、文字のコードポイントパーサーとしても使えます。

👉 Strings and Characters | Documentation hatena-bookmark