ストリーミングはほとんどのブラウザと
Developerアプリで視聴できます。
-
Core Data: 雑件と格言
Core DataはあなたのAppからの情報を永続的に、常に保存する主要な方法です。データをより早く読み込み、取り込むためにより良く実装する方法をこれからお見せします。必要なデータ取得をバッチ挿入、必要なデータへのカスタムフェッチ要求を使ってどう改善し、持続ストアへの変更通知に対応するかを見ていきましょう。 このセッションを有効に活用いただくには、Core Dataの知識があり、過去に利用されたこともあることが望ましいです。フレームワークについてより詳細な情報が必要な場合には、“Making Apps with Core Data”をご参照ください。
リソース
関連ビデオ
WWDC19
-
ダウンロード
こんにちは WWDCへようこそ “Core Data: 雑件と格言” Core Dataチームのリシ・ヴァーマです Appのニーズに合った利用方法を紹介します まずはバッチ処理による― 効率的な永続化ストアの管理について お話しします 次はAppのニーズに合わせた フェッチ要求についてです 最後に永続化ストアの変更に 適応するコツをお伝えします これはサンプルの地震Appです Swiftを使っており ビュー・コンテキストと 地震データを取り込む バックグラウンド・コンテキストがあります ローカルコンテナもあります 地質調査所のJSONフィードを収集し AppのJSONパーサーへ バックグラウンド・コンテキストへ送り 管理オブジェクトとしてストアに保存 ビュー・コンテキストがUIに変更を反映させます
バックグラウンドで使用した 大量の管理オブジェクトは 保存すると しばらくして削除されます バッチ処理の出番です バッチ処理によって 簡単に挿入 更新 削除ができ 最小限の操作で済みます シンプルなため いくつか注意が必要です まず 保存はしないので― 保存した通知は出ません 管理オブジェクトを明示してないので コールバックやアクセサもありません でも履歴の永続化によって解決できます 履歴を有効化するとバッチ処理が記録され 通知が簡単に取得できます これで最初の問題が解決しました 次にコールバックやアクセサです ビューに関わる変更については 履歴をパースすることで解消できます バッチ処理を詳しく見てみます 一般的にAppはデータを― 永続化ストアにためてUIに変更を反映させます NSBatchInsertRequestは データ取得のバッチ処理を可能にしました 膨大なデータ取得を合理化したのです 私たちは さらに能力を強化しました 以前よりデベロッパは― ディクショナリ配列をバッチ挿入できました ディクショナリ配列を説明すると 名前であるキーと値を表すものです 新規機能のイニシャライザには ディクショナリなどを 初期化するブロックがあります 取得時の最大メモリを低減し オブジェクトの割り当て数も さらに減少させます 複数の方法があります
1つ目は地震の管理オブジェクトを生成し 地質調査所のデータの値と共に配列する方法です そして保存 次にNSBatchInsertRequestを 使います 全データのディクショナリを集め 配列に加え― BatchInsertRequestを実行します 最後にバッチ挿入の ブロック・バリアントです 値を設定するブロックの付いた BatchInsertRequesを生成し― 実行します ブロックが“true”と返すまでは 保存されません 3手法のパフォーマンスをAppで確認します 1つ目の大量データを 管理オブジェクトと保存する方法です 1分以上かかり 約30MBのメモリを使用しています 最初のピークはJSONデータです 最初の1分はデータ収集しておらず 変更の通知を準備しています トランザクションが多すぎたのです バッチ挿入を使えば大丈夫です 2つ目のNSBatchInsert方法では 使用するメモリは25MBで済みます たった13秒で 同数のオブジェクトを保存できます わずかな時間しか かかりません 3つ目の新しいブロック収集を使えば 11秒で取り込みが終わります データ取得を最適化したら バッチ挿入を学びましょう 地震Appの管理オブジェクトモデルです 地震エンティティを選択 右側はデータモデルの中身です 詳しく見ます 属性“コード”はユニーク制約です 永続化ストアにおいて 1つのオブジェクトのみが “コード”に対し唯一 値を持ちます 地震Appで確認しましょう JSONフィードが30日間の地震データをくれます ユーザーがリロードするたび― 私たちは全データを取り込みます データはほとんど変わりません 最近起きた地震のデータが加わるだけです コード42の地震情報がJSONフィードから パーサーに渡されます バックグラウンド・コンテキストを経て ストアに保存されます 新データとしてストアに加わります しかし同じ地震データを消して 再び挿入したくありません 変更した分だけを更新したいのです そこでUPSERTします UPSERTはSQL用語で見ると分かりやすいです 地震オブジェクトを挿入するとします その際 コードにCONFLICTがあると 挿入せずに更新を行います 分かりましたか? mergePolicyを設定し バッチ挿入リクエストを実行 NSMergeByPropertyObject- TrumpMergePolicyに対して しかし更新するなら― バッチ更新が一番シンプルです NSBatchUpdateRequestでは フェッチ要求は不要で 管理オブジェクトの更新・保存のみ 条件に合うオブジェクトのプロパティを 素早く効率的に更新してくれます フェッチ要求どおりに 例を見せましょう 地震Appを使います 異なるデータで全地震データを 検証できたらよいのですが… マグニチュード2.5以上の地震だけを 使用するとします
その際 バッチ更新を行います "Quake"に対しバッチ更新リクエストを作り propertiesToUpdateで "validated"をtrueに 条件はマグニチュード2.5以上です
そしてBatchUpdateを実行します 至ってシンプルです 挿入と更新の次は削除を紹介します バッチ削除は強力で― 大量のオブジェクトグラフを削除できます リレーションシップ・ルールは維持されるので 削除は伝搬し リレーションシップは無効化します よくあるのは期限付き削除です オブジェクトを残す期間を決め 期限を超えたものを削除します APIのおかげです シンプルに見えますが複雑な工程を含んでいます 実例を見ましょう 30日を過ぎたら期限を迎える 地震データを例とします これは消去作業なので 非同期の低優先度で処理されます 管理オブジェクト・コンテキストで ブロックが動き 削除日を決めます フェッチ要求を作り 消去される条件を設定します
新しいフェッチ要求で バッチ削除リクエストを生成し実行 しかし削除要件に合う オブジェクトの数が多い場合は? 困ったことにストアの書き込みがロックされ― かなりの時間がかかるかも 大丈夫 解決できます fetchLimitを設定すれば ムダな時間はかからず ユーザーをイライラさせません これで平気ですね 次はフェッチ要求についてです オブジェクトグラフが増えてきたので ストアを点検します 取得したデータは 大量のビューや演算を伴いますが そんなに必要でしょうか? ではオブジェクトグラフなしで 演算する方法は? managedObjectResultTypeにより 完全な形で走査が可能です fetchResultsControllerの バッチ処理に最適で― 管理オブジェクトが更新されると 反応して差分を適用します こちらをどうぞ 地震Appが初めてデータを取得します fetchResultsControllerが行を加えます オブジェクトを更新するとビューも更新されます しかしビューには15件ほどしかありません もっと読み込んだのに この点を改善します BatchSizeを決めて― フェッチ要求すれば 望んだ数のオブジェクトを取得できます スクロールすれば続きも見られます ユーザーのニーズによりますが バッチ処理した配列は通常とは異なります 見てください
これが通常の配列です 管理オブジェクトは反復した形で きれいに整列しています バッチ処理した配列には管理オブジェクトはなく 管理オブジェクトに変換する ObjectIDがあります 動くとバッチが解放されて 最後はObjectIDのみが残ります ご覧ください 地震Appがデータを読み込むと およそ17MBのメモリが使用されます バッチ・フェッチを試します
バッチサイズを決めると 使用するメモリは約12MBで済みます 使用メモリの約3分の1の 5MBも節約できました
他にも方法があります
読み出すデータを削減するのです 最終的に必要な属性や関係が分かれば フェッチ自体を調整できます propertiesToFetchを設定するのです 通常 リレーションシップ・セットは “false”なため 最初の走査で関連オブジェクトを取得します その回数が少なければ問題ありません しかし走査が大量に起きると 分かっている場合は プリフェッチのキー・パス設定を勧めます これでフェッチを後でしなくて済み 不必要な走査を防いでくれます 基本的なフェッチだと17.6MBを使用します propertiesToFetchtoを使うと 使用メモリは16.4MBに減ります 次はObjectIDです 管理オブジェクトはデータ量が多く 複数のスレッド使用に適しません ObjectIDResultTypeが便利です 条件に合ったオブジェクトを捜す際に これらの識別子は他のスレッドでも使用できます 検索コストを省くことが可能です でも管理オブジェクトとObjectIDの 中間のものは? ディクショナリ形式などです 読み出し専用の軽くて扱いやすいデータで 他のスレッドで使えます これらは複雑なデータにも対応でき オブジェクトグラフを必要とする演算を 大幅に減らせます
例えばgroupBy集計のような演算です 実際の例はこちら 場所ごとの平均震度です まず震度を表すキーパスの式に加えて 平均を求める機能の式を決めます これが平均震度を求める式の内容です
最後に場所ごとで集計するように プロパティを定めたうえでフェッチ要求します 結果はディクショナリ形式に 結果は こうなります 指定した地域の平均震度が表示されました 最後はcountResultTypeです シンプルで美しく 最適化されています もっと言わせます?
データ取得とフェッチ要求の次は 永続化ストアの変更に Appが適応できるようにします Core Dataはストアの状態を頻繁に通知します 追加 削除 保存 変更など 特に便利な2点に絞ってご紹介します 今年 ObjectIDの通知が 従来の“保存”の通知に加えて導入されました ObjectIDの通知は 管理オブジェクトの保存通知より軽いです 永続化履歴のトランザクションから生成されます Swiftで見てみます Swiftの通知が改良されました Swiftに合わせて古いものを刷新し 2つの通知を追加しました Appを管理オブジェクトではなく ObjectIDで動かせます それだけではありません 改良の一環としてNotificationKeyを加えました 通知の処理がずっと楽になります さらにObjectIDの通知に関する新キーも もう1つ紹介したいのが RemoteChangeNotificationです
RemoteChangeNotificationはとても有益で 全オペレーションに共通です 処理の内外やクライアントの違いを問いません これによってAppも通知も― ポーリングの必要がなくなりました 永続化履歴が有功になると… RemoteChangeNotificationのユーザー情報に 永続化履歴のトークンが含まれ ObjectID通知を得られます 確認しましょう
ローカルコンテナとAppです 表は今までに把握した永続化履歴です 送られてくるJSONフィードを バックグラウンド・コンテキストが取り込み 永続化ストアに
永続化履歴が詳細を認識します これは簡易版です 今までは変更を確認するため ポーリングが必要でした でも今はRemoteChangeNotificationが 変更があったことを教えてくれます またRemoteChangeNotificationのユーザー情報に 履歴トークンがあるので詳細の把握が可能です 実際にAppで確認します 新機能が追加されています 共有Extentionや第2Appや― 写真Extentionも これらの追加により永続化ストアを変更すると 永続化履歴に記録されます しかしAppが選択された時に 費用のかかるポーリングが必要です
RemoteChangeNotificationが有効なら Appが選択された時に通知されます
写真Extentionの変更だけでなく
第2Appや共有Extentionについても 通知されます
Appが起動すると 通知が来て どんな変更があったか分かります これら2つの機能により 誰が 何を いつ どこで どのように― 変更させたかが分かるのです 最後に永続化履歴のノウハウを
これはとても便利で フェッチ要求の裏技と似ています ニーズに応じて要求を調整するのです 変更通知要求のカスタマイズ方法です ある日付以降のObjectIDの変更を 検索するとします 永続化履歴オブジェクトから エンティティの説明を得ます
fetch request作成やentity設定のためです 特定のObjectIDの変更を捜す条件を決めて history requestを生成し fetchRequestを設定します さあ 実行です 指定した日付以降の ObjectIDの変更が出ました 今日の内容を振り返ります 目的に合わせて カスタマイズしたバッチを利用し 通知と永続化履歴の能力を強化します ご視聴ありがとうございました
-
-
1:48 - Batch Operations - Enable Persistent History
storeDesc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
-
2:32 - NSBatchInsertRequest.h
//NSBatchInsertRequest.h @available(iOS 13.0, *) open class NSBatchInsertRequest : NSPersistentStoreRequest { open var resultType: NSBatchInsertRequestResultType public convenience init(entityName: String, objects dictionaries: [[String : Any]]) public convenience init(entity: NSEntityDescription, objects dictionaries: [[String : Any]]) @available(iOS 14.0, *) open var dictionaryHandler: ((inout Dictionary<String, Any>) -> Void)? open var managedObjectHandler: ((inout NSManagedObject) -> Void)? public convenience init(entity: NSEntityDescription, dictionaryHandler handler: @escaping (inout Dictionary<String, Any>) -> Void) public convenience init(entity: NSEntityDescription, managedObjectHandler handler: @escaping (inout NSManagedObject) -> Void) }
-
3:01 - Earthquakes Sample - Regular Save
//Earthquakes Sample - Regular Save for quakeData in quakesBatch { guard let quake = NSEntityDescription.insertNewObject(forEntityName: "Quake", into: taskContext) as? Quake else { ... } do { try quake.update(with: quakeData) } catch QuakeError.missingData { ... taskContext.delete(quake) } ... } do { try taskContext.save() } catch { ... }
-
3:16 - Earthquakes Sample - Batch Insert with Array of Dictionaries
//Earthquakes Sample - Batch Insert var quakePropertiesArray = [[String:Any]]() for quake in quakesBatch { quakePropertiesArray.append(quake.dictionary) } let batchInsert = NSBatchInsertRequest(entityName: "Quake", objects: quakePropertiesArray) var insertResult : NSBatchInsertResult do { insertResult = try taskContext.execute(batchInsert) as! NSBatchInsertResult ... }
-
3:28 - Earthquakes Sample - Batch Insert with a block
//Earthquakes Sample - Batch Insert with a block var batchInsert = NSBatchInsertRequest(entityName: "Quake", dictionaryHandler: { (dictionary) in if (blockCount == batchSize) { return true } else { dictionary = quakesBatch[blockCount] blockCount += 1 } }) var insertResult : NSBatchInsertResult do { insertResult = try taskContext.execute(batchInsert) as! NSBatchInsertResult ... }
-
5:42 - NSBatchInsertRequest - UPSERT
let moc = NSManagedObjectContext(concurrencyType: NSManagedObjectContextConcurrencyType.privateQueueConcurrencyType) moc.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy insertResult = try moc.execute(insertRequest)
-
6:30 - Batch Update Example
//Earthquakes Sample - Batch Update let updateRequest = NSBatchUpdateRequest(entityName: "Quake") updateRequest.propertiesToUpdate = ["validated" : true] updateRequest.predicate = NSPredicate("%K > 2.5", "magnitude") var updateResult : NSBatchUpdateResult do { updateResult = try taskContext.execute(updateRequest) as! NSBatchUpdateResult ... }
-
7:33 - Batch Delete without and with a Fetch Limit
// Batch Delete without and with a Fetch Limit DispatchQueue.global(qos: .background).async { moc.performAndWait { () -> Void in do { let expirationDate = Date.init().addingTimeInterval(-30*24*3600) let request = NSFetchRequest<Quake>(entityName: "Quake") request.predicate = NSPredicate(format:"creationDate < %@", expirationDate) let batchDelete = NSBatchDeleteRequest(fetchRequest: request) batchDelete.fetchLimit = 1000 moc.execute(batchDelete) } } }
-
12:18 - Fetch average magnitude of each place
//Fetch average magnitude of each place let magnitudeExp = NSExpression(forKeyPath: "magnitude") let avgExp = NSExpression(forFunction: "avg:", arguments: [magnitudeExp]) let avgDesc = NSExpressionDescription() avgDesc.expression = avgExp avgDesc.name = "average magnitude" avgDesc.expressionResultType = .floatAttributeType let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "Quake") fetch.propertiesToFetch = [avgDesc, "place"] fetch.propertiesToGroupBy = ["place"] fetch.resultType = .dictionaryResultType
-
13:36 - NSManagedObjectContext.h - Modernized Notifications
//NSManagedObjectContext.h @available(iOS 14.0, *) extension NSManagedObjectContext { public static let willSaveObjectsNotification: Notification.Name public static let didSaveObjectsNotification: Notification.Name public static let didChangeObjectsNotification: Notification.Name public static let didSaveObjectIDsNotification: Notification.Name public static let didMergeChangesObjectIDsNotification: Notification.Name }
-
13:54 - NSManagedObjectContext.h - Modernized Keys
//NSManagedObjectContext.h @available(iOS 14.0, *) extension NSManagedObjectContext { public enum NotificationKey : String { case sourceContext case queryGeneration case invalidatedAllObjects case insertedObjects case updatedObjects case deletedObjects case refreshedObjects case invalidatedObjects case insertedObjectIDs case updatedObjectIDs case deletedObjectIDs case refreshedObjectIDs case invalidatedObjectIDs } }
-
14:08 - Enable Remote Change Notifications with Persistent History
storeDesc.setOption(true as NSNumber, forKey: NSPersistentStoreRemoteChangeNotificationPostOptionKey) storeDesc.setOption(true as NSNumber, forKey: NSPersistentHistoryTrackingKey)
-
16:19 - History Pointers
let changeDesc = NSPersistentHistoryChange.entityDescription(with: moc) let request = NSFetchRequest<NSFetchRequestResult>() //Set fetch request entity and predicate request.entity = changeDesc request.predicate = NSPredicate(format: "%K = %@",changeDesc?.attributesByName["changedObjectID"], objectID) //Set up history request with distantPast and set fetch request let historyReq = NSPersistentHistoryChangeRequest.fetchHistory(after: Date.distantPast) historyReq.fetchRequest = request let results = try moc.execute(historyReq)
-
-
特定のトピックをお探しの場合は、上にトピックを入力すると、関連するトピックにすばやく移動できます。
クエリの送信中にエラーが発生しました。インターネット接続を確認して、もう一度お試しください。