View in English

  • メニューを開く メニューを閉じる
  • Apple Developer
検索
検索を終了
  • Apple Developer
  • ニュース
  • 見つける
  • デザイン
  • 開発
  • 配信
  • サポート
  • アカウント
次の内容に検索結果を絞り込む

クイックリンク

5 クイックリンク

ビデオ

メニューを開く メニューを閉じる
  • コレクション
  • トピック
  • すべてのビデオ
  • 利用方法

その他のビデオ

ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。

  • 概要
  • トランスクリプト
  • コード
  • SwiftDataの新機能

    SwiftDataを利用すると、表現力に優れた宣言型のAPIによって、アプリのデータを簡単に永続化できます。このセッションでは、複合的な一意性制約、#indexによるクエリの高速化、Xcodeプレビューでのクエリ、豊富な述語表現など、SwiftDataの改善点について解説します。これらのさまざまな機能を使用してより充実したモデルを表現し、アプリのパフォーマンスを向上させたいデベロッパの方は、ぜひご参加ください。SwiftDataでカスタムデータストアを構築する方法や履歴に関するAPIを使用する方法については、「Create a custom data store with SwiftData(SwiftDataを使用したカスタムデータストアの作成)」と「Track model changes with SwiftData history(SwiftDataの履歴機能によるモデル変更のトラッキング)」をご視聴ください。

    関連する章

    • 0:00 - Introduction
    • 0:57 - Adopt SwiftData
    • 2:11 - Customize the schema
    • 2:43 - #Unique macro
    • 3:37 - History API
    • 4:29 - Tailor a model container
    • 5:39 - Custom data stores
    • 6:41 - Xcode previews
    • 9:20 - Customize queries
    • 10:18 - #Expression macro
    • 11:56 - #Index macro

    リソース

    • Adopting SwiftData for a Core Data app
    • Forum: Programming Languages
    • SwiftData
      • HDビデオ
      • SDビデオ

    関連ビデオ

    WWDC24

    • SwiftDataの履歴機能によるモデル変更のトラッキング
    • SwiftDataを使用したカスタムデータストアの作成

    WWDC23

    • SwiftDataについて
  • このビデオを検索

    こんにちは Rishi Vermaです SwiftDataチームで エンジニアをしています 本日はSwiftDataの新機能 について説明します

    SwiftDataは iOS 17で導入されました このフレームワークにより Appleのすべてのプラットフォームで Swiftにおけるアプリデータのモデル化と 永続化が可能になります マクロなど Swift言語の 最新機能を活用して 高速で効率的かつ安全な コードを記述できます このビデオでは SwiftDataフレームワークの 概要を簡単に復習してから 新機能を掘り下げていきます まず 新しいスキーママクロを使って モデルの重複を避ける方法を紹介し 次に モデルコンテナを設定して 構成するための新しい方法を説明します

    最後に 複雑なフィルタを使って クエリを最適化する方法と 新しいマクロでパフォーマンスを 向上させる方法を説明します

    まず SwiftDataについて 簡単に説明します SwiftDataは アプリのモデルレイヤを簡単に構築し アプリの起動間でそれを 永続化できるフレームワークです

    このフレームワークは永続化だけでなく スキーマのモデル化と移行 グラフ管理 CloudKitとの同期など 多数の機能を提供します SwiftDataをアプリに導入するのが いかに簡単かを説明するために 私とチームが開発しているアプリ Tripsを紹介します TripsはSwiftUIで書かれたアプリで 旅行について検討したことなど 様々な記録をすべて残せます

    このアプリのモデルで SwiftDataを使用するには フレームワークをインポートし 各モデルに@Modelマクロを 追加するだけです これがSwiftDataの強力な機能です

    アプリの定義では WindowGroupの modelContainerモディファイアが Tripモデルのビュー階層全体に 通知します

    これが設定されると ビューは静的データを削除し 代わりに@Queryを使って ビューを構成します これにより モデルコンテナから Tripモデルを取得しTripsの配列を返します これで完了です! アプリは私が作成した 旅行をすべて永続化し SwiftUIビューに完璧にフィットします 最初のステップは@Modelマクロを追加 することでしたが これはスキーマのカスタマイズ方法の 始まりに過ぎません

    @Modelマクロは強力で 永続化の経験に役立ちます 永続化可能なすべてのクラスに このマクロを付けるだけで Tripクラスと関連モデルの 保存プロパティが永続化されます

    さらに @Attributesや @Relationshipsのマクロを使って スキーマをカスタマイズしたり 保存プロパティを @Transientと指定して データの永続化を回避することもできます

    今年は新しいスキーママクロが導入され 永続モデルに対する 複合制約を構築できるようになりました

    新しい#Uniqueマクロを使用して モデルのプロパティの どの組み合わせがモデルデータ内で 一意でなければならないのかを SwiftDataに指示できます 2つのモデルインスタンスが 同じ一意の値を共有する場合 SwiftDataは既存モデルと競合した際に アップサートを実行します

    例えば Tripsアプリでは #Uniqueマクロを使って 旅行の名前 開始日 終了日が 一意であることを保証しています これでこのアプリは 同じ名前の 複数の旅行を持つことができますが 開始日と終了日が異なる場合に限ります これにより データの重複を避けることが とても簡単になります なぜなら SwiftDataは より多くの情報に基づいて 実際にどのモデルが重複しているかを判断し 代わりにデータの更新を実行するからです

    #Uniqueプロパティで@Modelが 重複していないことを確認できるため これらのプロパティは このモデルの識別子にもなります @Attributeマクロを使って preserveValueOnDeletionを これらのプロパティに 指定することもできます これにより SwiftDataで 履歴APIを使う際に これらの識別値を利用できます

    SwiftDataの履歴機能を使うと 挿入 更新 削除されたモデルを 時間の経過とともに アプリで把握できます モデルが削除されると 保存するように指定された値は トゥームストーン値として 履歴情報に保持され これらの変更を処理するために必要な情報が アプリに提供されます また これをサポートするよう構築された カスタムデータストアとシームレスに連携します 詳細はこちらのビデオ 「Track model changes with SwiftData history」 をご覧ください モデルコンテナをカスタマイズすると データの場所とアプリ全体での データの使い方をアプリで微調整できます

    modelContainerモディファイアは SwiftDataを使う最も簡単な方法です 永続化するモデルの型を指定するだけで SwiftDataがコンテナを用意します modelContainerモディファイアで コンテナの一部のプロパティを カスタマイズすることもできます

    例えば ディスクではなく メモリにデータを保存したり 自動保存を有効または無効にしたり 元に戻す/やり直しの オンとオフを切り替えたりできます

    modelContainerを さらにカスタマイズして ディスク上の保存先の変更などを行うには 独自のmodelContainer インスタンスを別に構築します Tripsアプリでこれを試してみましょう modelContainerモディファイアで コンテナを構築する代わりに containerプロパティを使って 独自のコンテナを作成します

    このプロパティのクロージャで モデルの構成を作成してスキーマを渡します ここでディスク上のデータの URLもカスタマイズします 次に ModelContainerイニシャライザに この構成を渡して 結果を返します

    iOS 18のSwiftDataでは 完全カスタムのデータストアを使って modelContainerを さらにカスタマイズできます デフォルトのデータストアは SwiftDataの全機能をサポートする 堅牢な永続性バックエンドを提供しますが ここでは 独自のデータストアを作成します これにより 独自の実装で コンテナ全体のデータを永続化します

    例えば Tripsアプリでは JSONファイルで構成された 独自の カスタムドキュメント形式を実装しました これをアプリで使用するためには モデルの構成を カスタムデータストアで指定した 構成に入れ替えるだけです この例では JSONStoreConfigurationです

    カスタムデータストアでは データの永続化形式を問わず @Modelや@Queryマクロなどの 使い慣れたSwiftData APIを 使うことができます データストアに機能を段階的に 取り入れることもできるため すぐに使い始めることができます 詳細についてはこちらのビデオ 「Create a custom data store with SwiftData」をご覧ください

    Xcodeのプレビュー用に カスタムコンテナを作成することもできます プレビューは SwiftUIでのアプリ開発において 最適なツールであり SwiftDataと非常に相性がいいです

    Tripsアプリのすべてのビューで 優れたプレビューを作成したいと思います まず プレビュー特性を使います

    そのために SampleDataという 新しい構造体を作成し PreviewModifierに準拠させます この構造体には 実装する必要のある 2つの関数があります 1つはプレビュー用の 共有コンテキストを設定するためのもので もう1つは共有コンテキストを ビューに適用するためのものです Tripsのプレビューでは ModelContainerをsampleDataの 共有コンテキストとして用意します プレビューではディスクに 何も保存する必要がないので データをメモリにのみ保存する ModelConfigurationを作成し ModelContainerを設定します

    次に 先ほど作成したメソッドを呼び出します これは様々なサンプル旅行を作成し モデルコンテナに保存します 旅行は名前と日付が一意であるため このコードではデータの重複排除は不要です SwiftDataが行ってくれます

    最後にコンテナを返します 次に sampleDataを使用するビューに このmodelContainerを 追加するメソッドを 実装する必要があります そのためには modelContainerモディファイアを使用して このコンテナを適用するだけです

    最後に PreviewTraitに 拡張機能を追加します これで このsampleDataに 簡単にアクセスできます これにより 新しい静的プロパティ sampleData()が作成され SampleData()構造体が モディファイアとして適用されます

    これで 任意のSwiftUIビューの プレビュー宣言時に traitsパラメータで .sampleDataを 使用できます これにより メモリ内のモデルコンテナが作成され sampleDataが読み込まれ SwiftUIビューで 使えるようにプレビューが変更されます

    優れたサンプルデータを用意すれば SwiftDataクエリを使って アプリの どのビューでも簡単に作業できます ただし 一部のアプリのビューは モデルが渡されることに依存しているため クエリが含まれていない可能性があります この場合も @Previewableマクロで 素晴らしいプレビューを作成できます

    例えばTripsでは BucketListItemViewは1つの旅行を パラメータとして受け取ります sampleDataを使用すると bucketListItemViewは sampleDataを含む モデルコンテナを持ちますが まだそのデータのクエリを実行していません

    今は @Previewableマクロを使用し プレビュー宣言内で 直接クエリを作成できます これにより 提供される旅行の配列を BucketListItemViewに渡すことができ sampleDataを使ったプレビューを 作成できます

    最後に SwiftData向けに 最適化された 強力なクエリの作成方法を紹介します クエリは ソートやフィルタリングを 簡単に行えるモデルの配列で SwiftUIビューを駆動し ModelContainerに加えられた 変更に自動的に反応します #Predicateは フィルタリングを容易にし データクエリの実行時に評価できます 大規模なメモリ内データセットを 使う場合とは対照的です Tripsでフィルタを使う方法を いくつか説明します Tripsアプリに検索バーを追加する場合 searchTextでクエリの フィルタリングや取得用の 述語を構築できます

    述語の構築は簡単です 指定されたsearchTextを取得し 旅行名にそのテキストが 含まれているか確認します ただし テキストは旅行名以外に 適用される場合があります

    そこで複合述語を構築して 旅行のdestinationプロパティも確認します これで複合述語は完成しましたが 述語では他にも様々なことを行えます

    iOS 18で導入された Foundationの新しい #Expressionマクロを使うと 複雑な述語を簡単に構築できます

    Expressionは trueやfalseを生成しない参照値を許可し 代わりに任意の型を許可します

    Expressionでは モデルのプロパティを使って 複雑な評価を表すことができます 述語内に組み込むと クエリの結果をさらにカスタマイズできます

    Tripsアプリでクエリを作成して まだ訪れていない場所がある 進行中の旅行を取得しましょう これらは isInPlanプロパティが まだfalseである旅行の BucketListItemで モデル化されます まず 述語を構築します

    この述語で 旅行が 継続中であることを指定します つまり 現在の日付は 開始日と終了日の間です

    ただし 旅行のBucketListItemの 少なくとも1つでisInPlanプロパティが falseであることを指定する 必要があります 述語だけではこれを表現できません なぜなら 計画外のBucketListItemの数を カウントするプロパティがないからです そこで このロジックを述語に 組み込む式を作ります

    この式は まだ計画していない BucketListItemの数をカウントします これはBucketListItemの配列を受け取り フィルタ条件を満たす項目の数を返します

    これで 指定された旅行の BucketListItemを使って 述語の一部としてこの式を評価できます さらに この述語では 式の結果が 0より大きいかどうかを チェックできます Expressionを使うと述語マクロが クエリを記述するための より強力で優れたツールとなり アプリに必要なデータを 効果的に取得できます 実は クエリのパフォーマンスを 向上させるもう1つの方法があります 新しいスキーママクロ #Indexを使う方法です #Indexは モデルに1つのインデックス または複合インデックスを 作成する機能を追加します インデックスは 本の目次のような 追加のメタデータを表します このメタデータはSwiftDataが 生成してコンテナに保存します このメタデータを使うと 指定されたキーパスに対するクエリが 高速で効率的になります

    このメリットを得るには どのプロパティにSwiftDataが インデックスを作成するかを宣言します クエリの並べ替えやフィルタリングで 最も頻繁に使用する プロパティについて考えます

    Tripsアプリの場合 旅行のクエリで名前 開始日 終了日の プロパティを使ったフィルタリングや 並べ替えが頻繁に行われます このクエリを高速にするには #Indexマクロを追加し 名前 開始日 終了日の キーパスと これら3つの 複合インデックスを指定します 旅行の詳細な検討内容のような 大規模データセットの場合 この方法でフィルタリングと 並べ替えが大幅に高速になります 述語マクロを使うことで SwiftUIでのクエリの使い勝手が向上し Expressionを使うことで より強力になります #Indexマクロを使うと アプリ内でクエリのパフォーマンスを 一層向上させることができます

    SwiftDataのパワーを使って アプリのモデルレイヤを構築しましょう #Unique制約をスキーマに追加して モデルの重複回避を容易にしましょう 新しい#Indexマクロを追加して クエリを高速化し

    新しい履歴APIで アプリのモデルに対する変更を追跡できます カスタムデータストアを使うと 独自のドキュメント形式または 永続性バックエンドにより SwiftDataのパワーを活用できます

    ご視聴ありがとうございました 皆さんが作り上げる 素晴らしいアプリを楽しみにしています

    • 1:32 - SampleTrips models decorated with @Model

      // Trip Models decorated with @Model
      import Foundation
      import SwiftData
      
      @Model
      class Trip {
          var name: String
          var destination: String
          var startDate: Date
          var endDate: Date
          
          var bucketList: [BucketListItem] = [BucketListItem]()
          var livingAccommodation: LivingAccommodation?
      }
      
      @Model
      class BucketListItem {...}
      
      @Model
      class LivingAccommodation {...}
    • 1:43 - SampleTrips using modelContainer scene modifier

      // Trip App using modelContainer Scene modifier
      import SwiftUI
      import SwiftData
      
      @main
      struct TripsApp: App {
          var body: some Scene {
              WindowGroup {
                  ContentView
              }
              .modelContainer(for: Trip.self)
          }
      }
    • 1:53 - SampleTrips using @Query

      // Trip App using @Query
      import SwiftUI
      import SwiftData
      
      struct ContentView: View {
          @Query
          var trips: [Trip]
          var body: some View {
              NavigationSplitView {
                  List(selection: $selection) {
                      ForEach(trips) { trip in
                          TripListItem(trip: trip)
                      }
                  }
              }
          }
      }
    • 2:16 - SampleTrips models decorated with @Model

      // Trip Models decorated with @Model
      import Foundation
      import SwiftData
      
      @Model
      class Trip {
          var name: String
          var destination: String
          var startDate: Date
          var endDate: Date
      
          var bucketList: [BucketListItem] = [BucketListItem]()
          var livingAccommodation: LivingAccommodation?
      }
      
      @Model
      class BucketListItem {...}
      
      @Model
      class LivingAccommodation {...}
    • 3:08 - Add unique constraints to avoid duplication

      // Add unique constraints to avoid duplication
      import SwiftData
      
      @Model 
      class Trip {
          #Unique<Trip>([\.name, \.startDate, \.endDate])
          
          var name: String
          var destination: String
          var startDate: Date
          var endDate: Date
          
          var bucketList: [BucketListItem] = [BucketListItem]()
          var livingAccommodation: LivingAccommodation?
      }
    • 3:36 - Add .preserveValueOnDeletion to capture unique columns

      // Add .preserveValueOnDeletion to capture unique columns
      import SwiftData
      
      @Model 
      class Trip {
          #Unique<Trip>([\.name, \.startDate, \.endDate])
          
          @Attribute(.preserveValueOnDeletion)
          var name: String
          var destination: String
      
          @Attribute(.preserveValueOnDeletion)
          var startDate: Date
      
          @Attribute(.preserveValueOnDeletion)
          var endDate: Date
          
          var bucketList: [BucketListItem] = [BucketListItem]()
          var livingAccommodation: LivingAccommodation?
      }
    • 4:35 - SampleTrips using modelContainer scene modifier

      // Trip App using modelContainer Scene modifier
      import SwiftUI
      import SwiftData
      
      @main
      struct TripsApp: App {   
          var body: some Scene {
              WindowGroup {
                  ContentView()
              }
              .modelContainer(for: Trip.self)
         }
      }
    • 4:52 - Customize a model container in the app

      // Customize a model container in the app
      import SwiftUI
      import SwiftData
      
      @main
      struct TripsApp: App {   
          var body: some Scene {
              WindowGroup {
                  ContentView()
              }
              .modelContainer(for: Trip.self,
                              inMemory: true,
                              isAutosaveEnabled: true,
                              isUndoEnabled: true)
         }
      }
    • 5:13 - Add a model container to the app

      // Add a model container to the app
      import SwiftUI
      import SwiftData
      
      @main
      struct TripsApp: App {
          var container: ModelContainer = {
              do {
                  let configuration = ModelConfiguration(schema: Schema([Trip.self]), url: fileURL)
                  return try ModelContainer(for: Trip.self, configurations: configuration)
              }
              catch { ... }
          }()
          
         var body: some Scene {
              WindowGroup {
                  ContentView()
              }
              .modelContainer(container)
         }
      }
    • 5:59 - Use your own custom data store

      // Use your own custom data store
      import SwiftUI
      import SwiftData
      
      @main
      struct TripsApp: App {
          var container: ModelContainer = {
              do {
                  let configuration = JSONStoreConfiguration(schema: Schema([Trip.self]), url: jsonFileURL)
                  return try ModelContainer(for: Trip.self, configurations: configuration)
              }
              catch { ... }
          }()
          
         var body: some Scene {
              WindowGroup {
                  ContentView()
              }
              .modelContainer(container)
         }
      }
    • 6:58 - Make preview data using traits

      // Make preview data using traits
      
      struct SampleData: PreviewModifier {
          static func makeSharedContext() throws -> ModelContainer {
              let config = ModelConfiguration(isStoredInMemoryOnly: true)
              let container = try ModelContainer(for: Trip.self, configurations: config)
              Trip.makeSampleTrips(in: container)
              return container
          }
          
          func body(content: Content, context: ModelContainer) -> some View {
              content.modelContainer(context)
          }
      }
      
      extension PreviewTrait where T == Preview.ViewTraits {
          @MainActor static var sampleData: Self = .modifier(SampleData())
      }
    • 8:15 - Use sample data in a preview

      // Use sample data in a preview
      
      import SwiftUI
      import SwiftData
      
      struct ContentView: View {
          @Query
          var trips: [Trip]
      
          var body: some View {
              ...
          }
      }
      
      #Preview(traits: .sampleData) {
          ContentView()
      }
    • 8:50 - Create a preview query using @Previewable

      // Create a preview query using @Previewable
      
      import SwiftUI
      import SwiftData
      
      #Preview(traits: .sampleData) {
          @Previewable @Query var trips: [Trip]
          BucketListItemView(trip: trips.first)
      }
    • 9:55 - Create a predicate to find a Trip based on search text

      // Create a Predicate to find a Trip based on Search Text
      
      let predicate = #Predicate<Trip> {
          searchText.isEmpty ? true : $0.name.localizedStandardContains(searchText)
      }
    • 10:06 - Create a Compound Predicate to find a Trip based on Search Text

      // Create a Compound Predicate to find a Trip based on Search Text
      
      let predicate = #Predicate<Trip> {
          searchText.isEmpty ? true :
          $0.name.localizedStandardContains(searchText) ||
          $0.destination.localizedStandardContains(searchText)
      }
    • 10:46 - Build a predicate to find Trips with BucketListItems that are not in the plan

      // Build a predicate to find Trips with BucketListItems that are not in the plan
      
      let unplannedItemsExpression = #Expression<[BucketListItem], Int> { items in
          items.filter {
              !$0.isInPlan
          }.count
      }
      
      let today = Date.now
      let tripsWithUnplannedItems = #Predicate<Trip>{ trip
          // The current date falls within the trip
          (trip.startDate ..< trip.endDate).contains(today) &&
      
          // The trip has at least one BucketListItem
          // where 'isInPlan' is false
          unplannedItemsExpression.evaluate(trip.bucketList) > 0
      }
    • 12:41 - Add Index for commonly used KeyPaths or combination of KeyPaths

      // Add Index for commonly used KeyPaths or combination of KeyPaths
      import SwiftData
      
      @Model 
      class Trip {
          #Unique<Trip>([\.name, \.startDate, \.endDate
          #Index<Trip>([\.name], [\.startDate], [\.endDate], [\.name, \.startDate, \.endDate])
      
          var name: String
          var destination: String
          var startDate: Date
          var endDate: Date
          
          var bucketList: [BucketListItem] = [BucketListItem
          var livingAccommodation: LivingAccommodation
      }

Developer Footer

  • ビデオ
  • WWDC24
  • SwiftDataの新機能
  • メニューを開く メニューを閉じる
    • iOS
    • iPadOS
    • macOS
    • tvOS
    • visionOS
    • watchOS
    Open Menu Close Menu
    • Swift
    • SwiftUI
    • Swift Playground
    • TestFlight
    • Xcode
    • Xcode Cloud
    • SF Symbols
    メニューを開く メニューを閉じる
    • アクセシビリティ
    • アクセサリ
    • App Extension
    • App Store
    • オーディオとビデオ(英語)
    • 拡張現実
    • デザイン
    • 配信
    • 教育
    • フォント(英語)
    • ゲーム
    • ヘルスケアとフィットネス
    • アプリ内課金
    • ローカリゼーション
    • マップと位置情報
    • 機械学習
    • オープンソース(英語)
    • セキュリティ
    • SafariとWeb(英語)
    メニューを開く メニューを閉じる
    • 英語ドキュメント(完全版)
    • 日本語ドキュメント(一部トピック)
    • チュートリアル
    • ダウンロード(英語)
    • フォーラム(英語)
    • ビデオ
    Open Menu Close Menu
    • サポートドキュメント
    • お問い合わせ
    • バグ報告
    • システム状況(英語)
    メニューを開く メニューを閉じる
    • Apple Developer
    • App Store Connect
    • Certificates, IDs, & Profiles(英語)
    • フィードバックアシスタント
    メニューを開く メニューを閉じる
    • Apple Developer Program
    • Apple Developer Enterprise Program
    • App Store Small Business Program
    • MFi Program(英語)
    • News Partner Program(英語)
    • Video Partner Program(英語)
    • セキュリティ報奨金プログラム(英語)
    • Security Research Device Program(英語)
    Open Menu Close Menu
    • Appleに相談
    • Apple Developer Center
    • App Store Awards(英語)
    • Apple Design Awards
    • Apple Developer Academy(英語)
    • WWDC
    Apple Developerアプリを入手する
    Copyright © 2025 Apple Inc. All rights reserved.
    利用規約 プライバシーポリシー 契約とガイドライン