ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Core Spotlightによるセマンティック検索のサポート
Core Spotlightを使用して、アプリにセマンティック検索の結果を表示する方法をご紹介します。ユーザーのデバイス上のプライベートインデックス内でアプリのコンテンツを参照可能にして、ユーザーが自然言語で項目を検索できるようにする方法を学べます。また、インデックス作成アクティビティのスケジュールを設定して、アプリのパフォーマンスを最適化する方法も説明します。 このセッションの内容を最大限に活用するには、まずApple Developer WebサイトにあるCore Spotlightのドキュメントを確認されることをお勧めします。
関連する章
- 0:00 - Introduction
- 1:37 - Searchable content
- 5:05 - Demo: Creating an index delegate extension
- 6:56 - Results and suggestions
- 9:18 - Ranking
- 10:17 - Wrap-up
リソース
関連ビデオ
WWDC24
WWDC21
-
ダウンロード
こんにちは Spotlightチームの エンジニアのJenniferです Appleは検索を重視しています 本日は Spotlightが提供する 新しいAPIをすべて紹介します これにより アプリ内に強力な検索機能を 組み込むことができます CoreSpotlightは アプリが 検索可能なコンテンツを Spotlightに提供し クエリを使って コンテンツを取得できるようにする フレームワークです アプリの永続ストレージソリューションを 強化する優れた方法です アプリが提供する検索可能なコンテンツは 非公開の完全にローカルな インデックスに保存され デバイス外に出ることはありません ユーザーは コンテンツを Spotlightで検索できますが 他のアプリではデータを 見ることができません CoreSpotlightでは 検索バーに 直接入力された検索語句から 検索結果や候補をアプリに 簡単に提供できます 今年 CoreSpotlightでは セマンティック 検索によるクエリの理解をサポートします これまで Spotlightではアプリ内の コンテンツを検索できましたが 検索語句が完全に一致する必要がありました セマンティック検索を使うと ユーザーは似た意味の検索語句で アプリ内のコンテンツを検索できます Spotlightのクエリ理解モデルでは どのような方法で検索しても 適切な結果が得られます 本日は アプリに優れた検索機能を 構築する方法を説明します まず 検索インデックスへの コンテンツの追加 次に データの移行と復元に関する ベストプラクティスを説明します さらに 結果や候補を取得する方法 ユーザーに最も関連性の高い 検索結果の順位を 上げる方法を説明します ここでは ユーザーが書いた 日記のエントリを検索するアプリを 作成します このサンプルアプリの完全なコードは 下のリンクから確認できます
優れた検索機能を構築する第一歩は ユーザーがアプリで検索する内容となる 検索可能コンテンツを Spotlightに提供することです この日記アプリでは ユーザーがすべての 入力内容を検索できるようにするため 各日記エントリが検索可能なアイテムに なります 以上を念頭に置いて アイテムを適切にインデックス化し クエリを使用して ユーザーインターフェイスのビューに 直接反映できるようにする必要があります 現在 セマンティック検索は テキストや 画像/ビデオなどのメディアアセットで 最も効果的に機能します 最良の結果を得るには 検索可能アイテムに 適切なコンテンツタイプが 含まれている必要があります また 可能な限りシステム定義の属性を 使ってください CSSearchableItemを作成するために 一意の識別子 オプションのドメイン識別子 属性セットを指定します 一意の識別子はアプリの 永続ストレージソリューションに保存します それにより 完全なアイテムデータを 復元できます 次にCSSearchableAttributeSetを 作成します 必ず有効なコンテンツタイプを 設定してください サポートするUTタイプのリストや 独自のコンテンツタイプを 作成する方法については ドキュメントをご覧ください テキストで検索可能なアイテムを提供する場合 titleとtextContentを必ず設定します これらの属性はセマンティック インデックスで処理されます アイテムが画像またはビデオアセットの場合 contentURLにアセットへのパスを 必ず設定します これにより アプリの Sandbox化されたコンテナから アセットをセマンティックインデックスで 処理できます
また 添付ファイルや Webコンテンツを参照する場合 独自のコンテンツタイプと属性を使って これらを個別アイテムとして インデックスに提供することを 検討してください ソースアイテムとの関係を維持するには relatedUniqueIdentifierを使います
検索可能なアイテムを設計したら 検索可能インデックスを作成して Spotlightに提供します アプリの設計に応じて提供を より効率的に行うための 新しいAPIがあります クライアントステートによる バッチインデックス化 アイテムの更新などが含まれます まず 名前付きのCSSearchableIndexを 作成します Spotlightに送信した最後の クライアントステートを取得して検証し 検索可能アイテムをインデックス化します インデックス化の呼び出しがアイテムの バッチの開始と終了の呼び出しで囲まれ 次のクライアントステートが送信される点に 注意してください
クライアントステートは アイテムの大きなカタログの管理や アプリとSpotlight間の データ整合性の維持に役立ちます また アプリのパフォーマンスに 影響を与える 過度のアイテム提供の防止にも役立ちます さらに 新しいisUpdateフラグと 組み合わせると アプリは必要なものだけを 確実に提供できます 移行と復元は 一貫性のある検索機能を 構築するための重要な部分です Spotlightインデックスは 完全にローカルかつ非公開です そのため 検索可能コンテンツが Spotlight内で最新であることを 確認する必要があります 何らかの問題のため Spotlightが インデックスの移行または データの復元を必要とする場合 Spotlightは すべてまたは一部の アイテムの再インデックス化を アプリに要求します アプリでこれらのリクエストに 対応するには アプリ実行時に デリゲートプロトコルを使用し さらにデリゲートExtensionを実装します SpotlightはこのExtensionを アプリとは別に呼び出すことができます インデックスデリゲートExtensionを使うと Spotlightはデバイスがスリープ状態や アイドル状態の場合などに リクエストするよう スケジュールを設定できます アイテムは時間が経過すると移行され アプリの既存の検索機能は ほぼ変更不要です 新しいXcodeファイルターゲットを使うと インデックスデリゲートExtensionは とても簡単に設定できます その方法を見てみましょう 日記アプリから始めます バンドルIDを含む 検索可能コンテンツを 提供するように設定済みです 最初に新しいターゲットを作成します 「File」メニューに移動し 「New」「Target」を選択します
Extensionのプラットフォームを選択します ここではmacOSから始めます 新しいCoreSpotlight Delegate Extensionテンプレートを探します
「Next」をクリックして 新しいターゲットを設定します 「Finish」をクリックして 新しい ターゲットをプロジェクトに追加します
追加したら 新しいExtensionを有効にします
スタブ実装を見てみましょう
Spotlight自体がこのプロセスを 開始するので デバッグに役立つ 便利なコマンドラインユーティリティを 使います 実際に試してみましょう
まず このメソッドに ブレークポイントを設定し コード内でこのブレークポイントを キャッチします 呼び出し元をブロックしないよう acknowledgmentHandlerを 呼び出します
次に この新しいApp Extensionを 含めるためにアプリを再ビルドします アプリのターゲットを選択し「Product」で 「Bユーザーインターフェイスld」を選択します
再びExtensionターゲットに切り替え 「Debug」メニューから 「Attach to Process」を選択します
これでデバッグできます ここでターミナルアプリを起動して ユーティリティコマンドを実行します
mdutilツールで デバッグ用のバンドルIDに対する リクエストをシミュレートします 実行します
Xcodeのプロジェクトに戻り ブレークポイントへの到達を確認します これで すべてまたは一部の アイテムの再インデックス化 ドラッグ&ドロップのサポートの追加 重要な提供パスを制限するシナリオへの 対応の実装を完了できます 検索可能アイテムを インデックス化したので 次にユーザーインターフェイスで検索機能をサポートします ユーザーインターフェイスのニーズに合わせて クエリを設定できます セマンティック検索は デフォルトで有効ですが Spotlightと同じ 最新の機械学習モデルを使って 順位付けされた結果を 返すようにクエリを 設定することもできます また クエリを設定してアプリで 候補メニューをサポートできます CSUserQueryContextを使って ユーザーインターフェイスに適した クエリを設定します クエリから返される各アイテムに対して 取得される属性のリストを 設定してください これらの取得された属性には通常 ユーザーインターフェイスへの表示に 必要なものだけが含まれます 順位付けした結果はフラグで有効化でき 表示数も制限できます これらの結果は通常 別の 「Top Hits」セクションに表示されます すべての結果が返されたら アプリで結果を並べ替えます これは新しいcompareByRank コンパレータで実行できます
候補は数字で件数を設定したり 無効にしたりできます 候補は順位の順に返されますが 並べ替える時に 順序を元に戻すこともできます メタデータ構文を使う構造化クエリは 検索結果をアプリのユーザーインターフェイスに 合わせる最適な方法です 例えば 画像のみを表示するタブを 選択する場合 このようなフィルタクエリを使って 結果セットに 画像のみを含むように指定できます フィルタクエリは アプリの特定の部分に移動する ためのブレッドクラムなど Spotlightにのみ表示する コンテンツにも便利です メタデータ構文を使ってカスタム フィルタクエリを作成する方法については ドキュメントをご覧ください
最後に 検索バーのクエリ文字列と クエリコンテキストを使って CSUserQueryを作成します 結果と候補は 非同期レスポンスで返されます アイテムの結果は非同期バッチで返されます そのため順位が有効な場合は 表示前にアイテムを並べ替えてください 通常 候補は入力された文字列の 補完として返されます CSSuggestionは 候補メニューに表示できる 属性付き文字列を提供します ユーザーが候補を選択したら この文字列で検索バーのテキストを置き換え 新しい検索をトリガします
セマンティック検索では アプリのプロセスで実行される 機械学習モデルをデバイスに ダウンロードする必要があります これらのモデルは いつでも ロードまたはアンロードして 実行中にアプリのメモリ容量を 節約できます そのため 検索インターフェイスが 毎回表示される直前に このクラスメソッドを 呼び出すことをおすすめします それにより 検索開始後すぐに すべてのリソースを利用できます 検索が機能するようになったので 時間の経過とともに 検索機能を改善するために ユーザーが最も関心を持っている コンテンツの順位を高めるシグナルを 提供することをおすすめします エンゲージメントと新鮮さは 適応型の 順位付け体験を提供する 重要なシグナルです ユーザーは 検索可能アイテムの 関連コンテンツを閲覧する場合があります または検索を開始して 結果セットの項目をスクロールして確認し 最後に結果を選択して 詳細を確認する場合があります それぞれの場合に アプリから エンゲージメントシグナルを Spotlightに送信して 今後の検索での順位を改善できます
CSSearchableItemの関連コンテンツを ユーザーが閲覧している場合は アイテム属性セットに lastUsedDateプロパティを設定し 最新情報として インデックスに反映します ユーザーがクエリの結果や クエリの候補を選択した場合 その操作をそのクエリに関連する操作 としてマークできます 以上です これで 様々なプラットフォームや地域の 多様な検索コンテンツを シームレスに処理する 本格的な検索機能を利用できます デバイス上の完全に非公開の 検索インデックスにコンテンツを 提供する方法を説明しました 結果や候補を取得する方法 時間の経過とともに最適な検索結果を 増やす方法についても説明しました CoreSpotlightにより ユーザーがアプリで コンテンツを見つけやすくなります また セマンティック検索が これまで以上に強力になりました
Spotlightとのその他の統合については App Intentの活用と CoreDataを使って CoreSpotlightへの提供を 無料で実現する方法に関する セッションをご覧ください ご視聴ありがとうございました Spotlightでお会いしましょう
-
-
2:14 - Creating CSSearchableItem
// Creating searchable items for donation let item = CSSearchableItem(uniqueIdentifier: uniqueIdentifier, domainIdentifier: domainIdentifier, attributeSet: attributeSet)
-
2:28 - Creating CSSearchableAttributeSet
// Creating searchable content for donation let attributeSet = CSSearchableItemAttributeSet(contentType: UTType.text) attributeSet.contentType = UTType.text.identifier
-
2:40 - Searchable items with type
// Searchable items with text attributeSet.title attributeSet.textContent // Searchable items with media attributeSet.contentType attributeSet.contentURL // Searchable items with links attributeSet.contentURL attributeSet.relatedUniqueIdentifier
-
3:31 - Batch indexing with client state
// Batch indexing with client state let index = CSSearchableIndex(name: "SpotlightSearchSample") index.fetchLastClientState { state, error in if state == nil { index.beginBatch() index.indexSearchableItems(items) index.endIndexBatch(expectedClientState: state, newClientState: newState) { error in } } }
-
3:56 - Avoid overwriting existing attributes
// Make it an update to avoid overwriting existing attributes item.isUpdate = true
-
7:19 - Configure a query
// Configure a query let queryContext = CSUserQueryContext() queryContext.fetchAttributes = ["title", "contentDescription"]
-
7:33 - Ranked results
// Ranked results queryContext.enableRankedResults = true queryContext.maxRankedResultCount = 2
-
7:47 - Suggestions
// Suggestions queryContext.maxSuggestionCount = 4
-
7:55 - Filter queries
// Filter queries queryContext.filterQueries = ["contentTypeTree=\"public.image\""]
-
8:23 - Query for searchable items and suggestions
// Query for searchable items and suggestions let query = CSUserQuery(userQueryString: "windsurfing carmel", userQueryContext: queryContext) for try await element in query.responses { switch(element) { case .item(let item): self.items.append(item) break case .suggestion(let suggestion): self.suggestions.append(suggestion) break } }
-
8:40 - Suggestions
// Suggestions suggestion.localizedAttributedSuggestion
-
8:56 - Preparing for queries
// Preparing for queries CSUserQuery.prepare CSUserQuery.prepareWithProtectionClasses
-
9:50 - Set the lastUsedDate
// Set the lastUsedDate when the user interacts with the item item.attributeSet.lastUsedDate = Date.now item.isUpdate = true
-
10:00 - Interactions with items and suggestions from a query
// Interactions with items and suggestions from a query query.userEngaged(item, visibleItems: visibleItems, interaction: CSUserQuery.UserInteractionKind.select) query.userEngaged(suggestion, visibleSuggestions: visibleSuggestions, interaction: CSUserQuery.UserInteractionKind.select)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。