ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Core DataとCloudKitの動作を最適化する
Core DataとCloudKitの実装の最適化に有効な、 3 つの開発サイクルについてご紹介します。Appのアーキテクチャと機能セットを分析して仮定を検証し、大規模なデータセットを取り込んだ後の動作の変化を調べ、ワークフローを改善するための実用的なフィードバックを得る方法を紹介します。 このセッションを最大限に活用するには、ご利用のデータモデルとCloudKitとの同期に関する知識を習得しておくとよいでしょう。
リソース
関連ビデオ
WWDC22
WWDC21
WWDC20
WWDC19
-
ダウンロード
私はNick Gillett Core Dataチームのエンジニアです NSPersistentCloudKitContainer を使うAppについて 開発者ツールを使用して説明します 生産的かつ教育的な方法でAppを 探索する方法について詳しく見ていきます
次にいくつかのツールを使って Appの動作を分析します 最後はNSPersistentCloudKit Containerについて 詳細かつ実用的なフィード バックを提供する方法です
エンジニアリングを水の循環 のようなものと考えています 私は機能が存在する空間を探索することから 作業を始めます 学んだことをもとに ツールやテストを組み合わせ 再現性のある環境で自分の作品を解析します 最後に同僚と結果を確認し フィードバックを集めます このサイクルの目的は 仕事をしながら学んだことを 永続的に記録することです Appleのプラットフォームには Xcode Instruments XCTest といった 素晴らしいツールが揃っています これらのツールは 実用的な フィードバックを提供する 豊富な診断情報を収集することを可能にします
ここでは過去の多くの知識を参照します NSPersistentCloudKitContainerと紹介する Core Data CloudKit Sample Appに ついての詳細は 「CloudKit と Core Data で データを共有する App の構築」と 「CloudKit で Core Data を使用する」 で解説してます XcodeとInstrumentsを 使いテストを実行する方法や Device organizerの使い方も紹介します 「Instruments の概要」と 「Xcode Organizer でパフォーマンス問題を 診断する」を復習して ツールチェーンの2つの重要な部分について 学ぶことをお勧めします サイクルの最初の部分である 「探索」を始めましょう 探索の第一の目的は学ぶことです Appがどのように機能するか 私が仮定するすべてに挑戦し 検証したいと思います このボタンをタップするとどうなるか? 永続ストアにデータを保存すると NSPersistentCloudKitContainer は同期されるか? 大きなデータセット操作時に メモリが不足してないか? Core Dataから見れば これらの質問はすべて Appが処理するデータの影響を受けます サンプルの CoreDataCloudKit Appは このデータモデルを使います
タイトルとコンテンツのテキストフィールドを 持つ一連の投稿を管理します 投稿には添付ファイル 通常は画像が添付されることがあり これは非常に大きくなる可能性があるため
ImageDataはオンデマンドでロードできるように 1対1の関係で保存されます 今回はデータセットに焦点を 当てて調査していきます データの形状 構造 分散を変更すると Appに何が起こるのか
サンプルにはリリース以来 それを調査する ための方法が組み込まれています 「The Generate 1000 Posts」ボタンは ラベル記載通りに機能します タップすると短いタイトルの1000件の投稿の サンプルデータセットが生成されます 投稿テーブルビューは このレベルの データを簡単に処理します 次にAppで異なる形状 サイズのデータセットを どのように探索できるかという質問です 「The Generate 1000 Posts」ボタンは アルゴリズムデータジェネレータを 実行します 1000個のオブジェクトを挿入 すべてのフィールドに値があること または どのフィールドにも値がないこと 等のルールに従います 私たち自身がデータの ジェネレータでもあります コードやSQL Appと直接やりとりすることで 特定のデータセットを手作りすることができ これらの生成されたデータセットは 後で使用または分析するために保存できます 大きなデータセットを探索するために LargeDataGeneratorを定義し 単一のメソッドgenerateDataを指定して 新しいデータセットを構築できます 2つのforループで 11の画像添付ファイルが関連付けられた 60個の投稿セットを生成できます 全部で660枚の画像です 画像あたり平均10〜15メガバイトのサイズで データセットはほぼ10GBのデータを消費します シンプルなインターフェース を使ってこのようなテストで データジェネレータを簡単に呼び出せます この1行のコードで テストで使用する10GBを超える 典型的なデータを生成します
さらに データジェネレータが正しく 動作することを確認する 検証メソッドをテストに組み込むこともできます たとえば 各投稿に本当に 11 個の画像が 添付されていることを確認するメソッドです
これには NSPersistentCloudKitContainer を 同期させる必要があります それでは新しいテストを作成します
NSPersistentCloudKitContainer の インスタンスを使います 簡単にするために ヘルパーメソッドを作成しました 次に LargeDataGenerator を使って コンテナに目的のデータセットを入力します データのエクスポートを完了するのを待ちます このテストでは大きなデータセットを アップロードする時間として 最大で20分間待ちます
このテストでは さまざまな種類のイベントに対して 多くの待ち時間が発生しています ここではコンテナを作成した時に コンテナのセットアップが 終了するのを待っています ヘルパーメソッドを使って エクスポートイベント用に XCTestExpectationsを作成します 詳細を見ていきましょう
このメソッドは 希望するイベントタイプと NSPersistentCloudKitContainer の インスタンスを引数として受け取ります NSPersistentCloudKitContainer の eventChanged 通知を受け取りために XCTestCase の expectationForNotification メソッドを使用して コンテナ内の永続ストアごとに 期待値を作成します 通知ハンドラブロックでは 受信したイベントが 期待値が対象とするストアに対して 正しいタイプであることを確認し endDate が nil でないことを 確認して終了します このテクニックを使うことで NSPersistentCloudKitContainer の イベントとテストの制御点を 強く関連付けることができます テストに戻って データをインポートする コンテナを追加します これはある仕掛けを利用したテクニックです NSPersistentCloudKitContainer の 新しいインスタンスを 空のストアファイルで作成するのです これにより NSPersistentCloudKitContainer の初回インポート時に このデータがデバイスに ダウンロードされる際 何が起こるかを調査することができます Appの中でデータセットがどのように 動作するのか 見ていきましょう そのためにデータジェネレータを ユーザーインターフェースにバインドします 「Generate Large Data」ボタンをタップすると データジェネレータが データセットにデータを入力します 2番目のデバイスでは NSPersistentCloudKitContainer が データのダウンロード中に テーブルビューが表示されます 個々の投稿をタップすると 添付ファイルのダウンロードと入力が 段階的に行われていることを確認できます このユーザーインターフェイスは アラートコントローラが駆動します LargeDataGenerator の シンプルなインターフェイスにより 2行のコードで新しいアラート アクションを追加できます 明瞭かつ簡潔でわかりやすくなっています
データジェネレータの概念を使用して Appの動作を見てきました データジェネレータは テストやカスタムUI コマンドライン引数など 特定のユースケースに適した方法で Appの中で動かすことができます Appにデータを入力する方法がわかったので Appの動作の変化について 分析する準備ができました このセクションでは大規模なデータセットで Appがどのように動作するか分析するための ツールやテクニックについて学びます
具体的には Instrumentsを使用して LargeDataGeneratorで作成された データセットの時間と メモリの複雑さを分析します そしてシステムログから得られる 豊富な情報を見ていきます NSPersistentCloudKitContainer CloudKit システムスケジューラ プッシュ通知からの アクティビティの記録があります Instruments を見ていきましょう 私が気に入っているのは Xcode を使用すると テストの動作を簡単に分析できることです 実行ボタンのクロージャから 「Profile」を選択するだけです Xcodeはテスト用のビルドを行い Instrumentsを自動的に起動します Time Profiler Instruments を ダブルクリックして 処理に時間がかかっている部分を 調べることができます
記録ボタンをクリックすると Instruments は App を起動し 選択したテストを実行します このテストの実行にはかなり 時間がかかっているようです スキップして理由を見てみましょう Instruments は 既にメインスレッドを選択しており 右側にテスト実行の 最も重いスタックトレースが表示されます
もう少し読みやすくします
これでよしとしましょう LargeDataGenerator が サムネイルを生成するのに 多くの時間を費やしていることがわかります バグなのか機能なのか どうすれば判断できるでしょう?
LargeDataGenerator は添付ファイルごとに 新しいサムネイルを生成するために こんなコードを書いています App のデータモデルを見ると サムネイルは特殊で imageData から オンデマンドで計算されます つまり この行は不要です データジェネレーターは これに時間を浪費しています これを取り除くことができます テストのパフォーマンスは どのように変わるでしょうか 更新後のデータジェネレータで App をリビルドした後 Instruments でテストを再度実行します 正直なところあまり変化は見られませんが 数秒後にテストが完了します 前回のテスト実行よりもはるかに高速です それでは どこに一番時間が かかったのか見ていきましょう
右側のドロワーで 最も重いスタックトレースが 永続ストアに画像を保存する所だと わかります これだけのデータ量を管理するテストでは まさに想定内のことです
1つの変更により generateData テストの 実行時間を短縮しました 10分の1の時間で実行します テストを分析することで 常にバグを 発見できるわけではありませんが 特定のデータセットを扱う際に App がどこに時間を費やしているか 詳しく知ることができる場合もあります いずれにしても貴重な学習です
Time Profilerを使えば App がデータセットのどこに 時間を費やしてるか調べることができます データセットの大きさからテスト実行で どれくらいメモリを消費するかも 気になるところです Allocations instrument を 使って実行しましょう Xcode を使って Instruments を起動し 測定を行います Time Profiler Instrumentを選択する代わりに Allocationsをダブルクリックします
そして「Record」をクリックします
テストは高速に実行しているにもかかわらず 10GB以上のメモリを使っています テスト実行中はほぼ全てのデータセットが メモリに保持されていることが分かります その理由を探ってみましょう
確認したいアロケーションの範囲を選択します 下のペインに大きなアロケーションが 多数あります このディスクロージャをクリックして テスト用にアロケーションされた 大きなデータブロックの一つをクリックすれば 調べることができます 特定のブロックがアロケーションされて 約2秒間解放されませんでした テスト時間にしては長すぎです なぜそうなるのでしょうか
右のスタックトレースを展開して調べます
経験上 アロケーションとリリースの スタックトレースから Core Data によってフォルトが発生し 管理対象オブジェクトコンテキストが 作業を終了したときに解放されています これはオブジェクトがフェッチ オートリリースプール または テスト内のオブジェクトに よって保持されたことを示しています
コードの問題のある部分は 投稿のベリファイアにあります 添付ファイルから画像を 読み込んで検証しています でも添付ファイルと関連する画像データは マネージドオブジェクのトコンテキストに 登録されたままです 問題を解決するためには いくつかの方法があります 例えばテーブルビューでは バッチフェッチを使用して テーブルが投稿をスクロールする時に 画像を解放できます ただし テストの実行速度が 速すぎて効果がありません アプローチを変える必要があります 投稿をフェッチ 確認する代わりに 添付ファイルをフェッチできます objectIDのみをフェッチした場合 マネージドオブジェクトコンテキストは 要求されるまでロードされた オブジェクトをキャプチャしません
NSManagedObjectContext の objectWithID メソッドを使い 検証をしながら添付ファイルを フェッチすることができます 10個の添付ファイルを検証するたびに コンテキストをリセットし キャッシュされた状態と 関連するメモリを全て解放します
変更してテストを再実行すると 想定内の対応可能なレベルの メモリ消費になったことがわかります 実際 ベリファイアがオブジェクトを 挿入する際に使用するメモリは LargeDataGenerator よりも さらに少なくなっています
この修正がどのように機能するかを知るために 特定のアロケーションを掘り下げてみましょう
まず対象のアロケーションの範囲を選択します 次に検証するサイズを選択します
解放されたオブジェクトを見つけるために 破壊されたオブジェクトを有効にしておき 解析するアロケーションを選択します
右側にアロケーションスタックの トレースが表示されますが どこで解放されたか アロケーション リリースイベントを選択します このスタックトレースは NSManagedObjectContext が 非同期的にアロケーションをリリースし 消費されたメモリを 解放していることを意味しています この手法でテストのメモリ消費の 最大値を確認し メモリの少ないシステムで 実行できるようになります
テストを Instruments と 組み合わせることにより 望ましくない動作を発見しました その動作に直接対処するために 対象を絞った変更を加えて 結果を確認しました システムログには Appや CloudKit スケジューリング プッシュ通知など Appが依存するシステムサービスに関する 豊富な情報も含まれています MacBookPro と iPhone の間で 1つの投稿を同期してみます Macで短いタイトルを付けて新しい投稿を行い iCloudにアップロードすると システムログにさまざまな イベントが記録されます
iPhone と同期させると 中間状態となることもあり システムログは対応する一連の イベントをキャプチャします MacBook Proでは NSPersistentCloudKitContainer は Appのプロセス内で動作します (CoreDataCloudKitDemo) データが永続ストアに書き込まれると DASD というシステムサービスに 今そのデータを CloudKit に エクスポートするのに適した タイミングか尋ねます 適切であれば DASD は NSPersistentCloudKitContainer に アクティビティを実行するように指示します NSPersistentCloudKitContainer は cloudd というプロセスでの 作業をスケジュールし 変更したオブジェクトを CloudKit にエクスポートします コンソール App を使ってログを観察できます アプリケーションのログは CoreDataCloudKitDemo という プロセスを探します ここではエクスポートが 完了しているものを選びました スケジューリングは プロセス dasd と App のストアからの ログを見たいと思います ここでは Appのプライベートストアの エクスポートアクティビティ開始を 選択しました ログをもう少し詳しく見てみましょう NSPersistentCloudKitContainer が dasd で作成するアクティビティは 特定のフォーマットに従います アクティビティ識別子は NSPersistentCloudKitContainer が 使う特定のプレフィックスと アクティビティが属するストアの ストア識別子で構成されています dasd のログにはアクティビティを実行できるか 決定する方法に関する情報が含まれています Appの実行に影響を与えるポリシーは 最終的な判断とともにログに記載されます
最後に cloudd というプロセスが CloudKit からの情報をログに記録するのですが 私は作業中のコンテナ識別子で ログをフィルタリングするのが好きです ここでは先ほどのエクスポートに対応する レコードの修正操作を選択しました
受信側のデバイスで変更をインポートする場合 もう一つのプロセスを観察する必要があります apsd というプロセスが プッシュ通知を受信して App に転送します これにより NSPersistentCloudKitContainer は エクスポートプロセスと同様の 一連のアクティビティを開始します dasd にインポートを実行する時間を要求し cloudd と連携して 更新されたすべてのオブジェクトを CloudKit からフェッチし ローカルストアにインポートします
Apsd は App のプッシュ通知を 受信してログに記録します このログには いくつかの 重要な詳細が記録されます ログメッセージにはコンテナ識別子と プッシュ通知をトリガーした サブスクリプション名と ゾーン識別子が含まれます NSPersistentCloudKitContainer によって管理され com.apple.coredata.cloudkit の プレフィックスを持ちます
今のコンソールAppは素晴らしいです Mac で開発中はターミナルで log stream コマンドを使って App と一緒にこれらのログを 表示するのが好きです
次の条件ごとに1つのターミナル ウィンドウまたはタブを開きます 最初は App です ログを見て CloudKit サーバーで 起きていることを確認します 次にプッシュ通知ログの apsd です 最後に dasd を使用して NSPersistentCloudKitContainer の アクティビティで何が起きてるか確認します 検索条件はコンソール App で クエリをガイドする為にも使います
使用するデバイスについて 多くの情報を利用できます 課題は 分析するために どのツールを使うべきかです instrument だけでランタイムや メモリのパフォーマンスなど さまざまなトピックについて 学ぶことができます システムログは App の実行と バックグラウンドでの システムの実行を説明する イベントをキャプチャします 開発サイクルの最後のフェーズは 役に立つフィードバックを 収集して提供することです このセクションではデバイスから診断情報を 収集する方法を説明します この情報を使用して 問題に対処可能で特定の目標に沿った フィードバックを生成する目的です これらの手法はあらゆるデバイスから フィードバックを収集するのに役立ちます 診断情報を収集するには 3つのステップがあります CloudKitlogging プロファイルを インストールします 問題を特定して効果的にトリアージするための ロギングが有効になります 次にデバイスから sysdiagnose を収集します 最後はデバイスに物理的にアクセスできる場合 Xcode から永続ストアファイルを収集します ロギングプロファイルをインストールするには 開発者ポータルの「Profile and Logs」で CloudKit Profile をダウンロードします Profile をインストールする通知が 表示されるデバイスもあります ただし iOS では設定 App から手動で インストールする必要があります
設定 App の「Downloaded Profile」で ダウンロードしたプロファイルをタップします 手順に従ってインストールを完了します インストールした後デバイスを再起動すると 有効になります
デバイスが再起動したら キャプチャしたい動作を再現し sysdiagnose を実行します sysdiagnose の取得は キーコードを使用して行います 詳細はプロファイルの 説明ページをご覧ください たまたま気づいたのですが iPhone の場合 音量ボタンとサイドボタンを 数秒間押し続けてから放します すると「設定App」から 「sysdiagnose」を利用できます やり方については Profile の説明ファイルに 記載されています 「設定App」で「プライバシー」 「Settings」で「Privacy and Security」 「解析および改善」に移動し 「Analitics and Improvements」に移動し 「解析データ」を選択して 「Analitics Data」を選択して sysdiagnose が見つかるまで ログをスクロールします
「sysdiagnose」をタップしてから 共有ボタンをタップすると 共有する方法をいくつか選択できます 私は分析するために Mac に AirDrop します 最後にデバイスオーガナイザを使用して Xcode からストアファイルを収集できます iPhone からファイルを収集するには インストール済み App のリストで サンプル App をクリック Disclosure ボタンをクリック 「Download Conntainer」を選択し ダウンロードディレクトリに保存します
これでシステムログとストアファイルの両方を 分析できるようになりました log stream コマンドについて すでに説明しましたが sysdiagnose で logshow コマンドを使うと sysdiagnose からログを出力できます 前に説明した apsd ログの predicate をコピーしました
log show コマンドの 最後の引数は logarchive です 何も指定しない場合は 実行中のマシンのシステムログを表示します ここでは sysdiagnose で 取ったログを読み込むように system_logs.logarchive を指定しています 例えば、時間範囲を詳細に指定することで 気になるイベントが発生した時間帯に フォーカスできます
先ほど説明した 多くの条件を組み合わせて App に関連する すべてのアクティビティの 統一されたログを形成することもできます この強力なコマンドを使って フィードバックレポートに含め チームメイトと共有することで 誰もが特定のログの分析に 集中することができます
このセッションでは次のことをお話ししました データジェネレーターを 使用して App の動作を調査 Instrument とシステムログを使用して App を分析 NSPersistentCloudKitContainer を 使用する App から 実用的なフィードバックを 提供または収集する方法です
私はNick Gillett このプレゼンテーションを お届けできて光栄です ご視聴ありがとうございます WWDCをお楽しみください
-
-
4:35 - Define a large data generator
class LargeDataGenerator { func generateData(context: NSManagedObjectContext) throws { try context.performAndWait { for postCount in 1...60 { //add a post for attachmentCount in 1...11 { //add an attachment with an image let imageFileData = NSData(contentsOf: url!)! } } } } }
-
5:07 - Testing a large data generator
class TestLargeDataGenerator: CoreDataCloudKitDemoUnitTestCase { func testGenerateData() throws { let context = self.coreDataStack.persistentContainer.newBackgroundContext() try self.generator.generateData(context: context) try context.performAndWait { let posts = try context.fetch(Post.fetchRequest()) for post in posts { self.verify(post: post, has: 11, matching: imageDatas) } } } }
-
5:33 - Sync generated data in test
func testExportThenImport() throws { let exportContainer = newContainer(role: "export", postLoadEventType: .setup) try self.generator.generateData(context: exportContainer.newBackgroundContext()) self.expectation(for: .export, from: exportContainer) self.waitForExpectations(timeout: 1200) }
-
6:35 - Expectation helper method
func expectation(for eventType: NSPersistentCloudKitContainer.EventType, from container: NSPersistentCloudKitContainer) -> [XCTestExpectation] { var expectations = [XCTestExpectation]() for store in container.persistentStoreCoordinator.persistentStores { let expectation = self.expectation( forNotification: NSPersistentCloudKitContainer.eventChangedNotification, object: container ) { notification in let userInfoKey = NSPersistentCloudKitContainer.eventNotificationUserInfoKey let event = notification.userInfo![userInfoKey] return (event.type == eventType) && (event.storeIdentifier == store.identifier) && (event.endDate != nil) } expectations.append(expectation) } return expectations }
-
7:18 - Import data model in test
func testExportThenImport() throws { let exportContainer = newContainer(role: "export", postLoadEventType: .setup) try self.generator.generateData(context: exportContainer.newBackgroundContext()) self.expectation(for: .export, from: exportContainer) self.waitForExpectations(timeout: 1200) let importContainer = newContainer(role: "import", postLoadEventType: .import) self.waitForExpectations(timeout: 1200) }
-
8:23 - Data generator alert action
UIAlertAction(title: "Generator: Large Data", style: .default) {_ in let generator = LargeDataGenerator() try generator.generateData(context: context) self.dismiss(animated: true) }
-
10:50 - Eagerly generating thumbnail in data generator
func generateData(context: NSManagedObjectContext) throws { try context.performAndWait { for postCount in 1...60 { for attachmentCount in 1...11 { let attachment = Attachment(context: context) let imageData = ImageData(context: context) imageData.attachment = attachment imageData.data = autoreleasepool { let imageFileData = NSData(contentsOf: url!)! attachment.thumbnail = Attachment.thumbnail(from: imageFileData, thumbnailPixelSize: 80) return imageFileData } } } } }
-
11:13 - Lazily generating thumbnail in data generator
func generateData(context: NSManagedObjectContext) throws { try context.performAndWait { for postCount in 1...60 { for attachmentCount in 1...11 { let attachment = Attachment(context: context) let imageData = ImageData(context: context) imageData.attachment = attachment imageData.data = autoreleasepool { return NSData(contentsOf: url!)! } } } } }
-
14:14 - Problematic verifyPosts implementation
func verifyPosts(in context: NSManagedObjectContext) throws { try context.performAndWait { let fetchRequest = Post.fetchRequest() let posts = try context.fetch(fetchRequest) for post in posts { // verify post let attachments = post.attachments as! Set<Attachment> for attachment in attachments { XCTAssertNotNil(attachment.imageData) //verify image } } } }
-
14:49 - Efficient verifyPosts implementation
func verifyPosts(in context: NSManagedObjectContext) throws { try context.performAndWait { let fetchRequest = Attachment.fetchRequest() fetchRequest.resultType = .managedObjectIDResultType let attachments = try context.fetch(fetchRequest) as! [NSManagedObjectID] for index in 0...attachments.count - 1 { let attachment = context.object(with: attachments[index]) as! Attachment //verify attachment let post = attachment.post! //verify post if 0 == (index % 10) { context.reset() } } } }
-
20:41 - Display logs using `log stream`
# Application log stream --predicate 'process = "CoreDataCloudKitDemo" AND (sender = "CoreData" OR sender = "CloudKit")' # CloudKit log stream --predicate 'process = "cloudd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo"' # Push log stream --predicate 'process = "apsd" AND message contains[cd] "CoreDataCloudKitDemo"' # Scheduling log stream --predicate 'process = "dasd" AND message contains[cd] "com.apple.coredata.cloudkit.activity" AND message contains[cd] "CEF8F02F-81DC-48E6-B293-6FCD357EF80F"'
-
24:36 - Display logs with `log show`
log show --info --debug --predicate 'process = "apsd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo"' system_logs.logarchive log show --info --debug --start "2022-06-04 09:40:00" --end "2022-06-04 09:42:00" --predicate 'process = "apsd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo"' system_logs.logarchive
-
25:17 - Provide a predicate to `log show`
log show --info --debug --start "2022-06-04 09:40:00" --end "2022-06-04 09:42:00" --predicate '(process = "CoreDataCloudKitDemo" AND (sender = "CoreData" or sender = "CloudKit")) OR (process = "cloudd" AND message contains[cd] "iCloud.com.example.CloudKitCoreDataDemo") OR (process = "apsd" AND message contains[cd] "CoreDataCloudKitDemo") OR (process = "dasd" AND message contains[cd] "com.apple.coredata.cloudkit.activity" AND message contains[cd] "CEF8F02F-81DC-48E6-B293-6FCD357EF80F")' system_logs.logarchive
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。