2024.12.19
システムの穴を運用でカバーしようとしてミス多発… バグが大量発生、決算が合わない状態から業務効率化を実現するまで
提供:LINE株式会社
リンクをコピー
記事をブックマーク
Bruce Evans氏:では、ここからはiOSの話になります。LINEでは昔からCoreDataを利用して、大量のコードがCoreDataに依存しています。CoreDataは、AndroidのRoomのように、ハイレベルなパーシステンスフレームワークですがApple専用のものです。
これから、このCoreDataのセットアップ、データベース作成、ID変更、検索、保存、そしてデータの読み込みを紹介します。
まずは、CoreDataは暗号化のサポートがないため、最初から暗号化の仕組みを加える必要があります。今表示している画像は、CoreDataの基本的なかたちです。NSManagedObjectContextは、プログラマーがふだん使っている部分です。オブジェクトやエンティティがこのレイヤーなので、CoreDataのメイン機能と考えてもいいと思います。
その下はNSPersistentStoreCoordinatorです。NSPersistentStoreCoordinatorは、Storeを複数管理するレイヤーだけです。そしてNSPersistentStoreは、ファイルストアのレベルで、最もraw SQLiteと近いです。
では、NSPersistentStoreをサブクラスしましょう。でもその前に、サブクラスしたあとにどうやって使うかをまず見てみましょう。
このコードは、カスタムのMySecureStoreをCoreDataに接続します。はじめに、MySecureStoreを登録する必要があります。これはUICollectionViewと似ていて、MySecureStoreのクラスをStringIDに紐づけます。
このコードはどこでも置けますが、絶対にCoreDataが立ち上がる前に呼ばないといけません。
それでは、CoreDataを立ち上げましょう。これについて2つのポイントがあります。まずはNSPersistentStoreのdescriptionのタイプに、先ほど登録したStringIDを使います。ここはすごく大事です。これを忘れてしまうと、作ったサブクラスは一切使われません。
次にSQLCipherを使っているので、パスワードのオプションを追加します。ここからは、いつもどおりのCoreDataです。呼ぶ場所やオブジェクトの使い方は、気にせずに使いましょう。
ではCoreDataの準備が終わったので、次にSQLCipherを準備しましょう。SQLCipherへのリンクは、SQLCipherのサイトに詳しく書かれているので、ここはあまり細かく話しません。ただ、次の2点はかなり大事なので、気をつけてください。
まず1つは、SQLCipherはSQLiteと一緒に動かないので、SQLiteの代わりにリンクしましょう。もう1つはlinker flagsです。もしSwiftで動かなければ、おそらくlinker flagの問題です。
準備が終わったので、やっとサブクラスを作れます。このスライドが表示しているように実装します。
基本の動きは、この5つのファンクション。loadMetadataで準備して、excute(_:with:)で検索と保存します。2つのnewValueは、データベースから読み込んだデータをNSManagedObjectに変更するためで、obtainPermanentIDs(for:)は、データベースのプライマリーキーを使ってNSManagedObjectIDに作成します。
とりあえず、loadMetadataを見てみましょう。loadMetadataは、CoreDataが起動する時に呼ばれます。やっとStoreをセットアップするタイミングです。
主に3つのタスクがあります。まず1つ目は、データベースを開くことです。
続いて2つ目は、データベースのパスワードを使って、復号化の作業です。先ほどoptionsに追加をしたパスワードを呼んでデータに変更し、UnsafePointerとしてSQLCipherに渡します。
最後に、loadMetadataの名前どおり、metadataをセットしないといけません。基本のvalueはこの2つで、ユニークキーはNSPersistentStoreからそのまま使えます。そしてtypeは先ほどのStringIDになります。
次は、データベース作成が必要です。基本的にmanagedObjectModelのエンティティを全部テーブル化します。なのでエンティティ、リレーションシップ、そしてインデックスにテーブルを分けます。
まずは、エンティティのテーブルを作りましょう。このテーブルは普通のSQLiteテーブルで、エンティティのプロパティをカラムにマッピングします。
はじめにテーブルのプライマリーキーを指定します。これはNamespace collisionの可能性があるので、名前に気をつけましょう。
次に、エンティティのアトリビュートをカラムに変更します。ここで、attributesByNameを使います。attributesByNameは、NSAttributeDescriptionのディクショナリです。
キーをコードネームとして使い、attributeTypeでSQLiteのタイプを決められます。
そしてエンティティのリレーションシップです。CoreDataでリレーションシップは、2種類あります。toOneとtoManyです。toManyはちょっと特別なので、別のテーブルを作ります。この方法はのちほどお話しします。toOneは、ここで追加できます。ただ、オブジェクトごとに保存はできません。その代わりに、先ほどのプライマリーキーを使います。
ここで止めることはできますが、CoreDataはだいたいsubentityを使いますので、subentityを追加しましょう。CoreDataの裏のsubentityはsuperentityのテーブルに入っているので、同じにしました。今のファンクションがrecursiveになれば、普通に呼んで、コードを書かなくていいので、楽にできます。
そして、カラムの情報が全部集まったので、CREATEを実行して、エンティティテーブルは完成です。
続いて、toManyをどこかでストアしないといけません。なので別のテーブルを作ります。このテーブルのローは、1つのエンティティと1つのリレーションシップです。そこで複数のローを合わせることによって、toManyのリストを作れます。
今一番大事なのは、ここです。toOneはもう使ったので、まずはフィルタリングして、toManyのみにしましょう。そしてテーブルが終わったら、またsubentityのケースを考えないといけません。ただし今度は、同じテーブルではなく、新しいテーブルを作ります。
最後にインデックスを追加しましょう。エンティティにインデックスのリストがあるので、かなり助かります。NSFetchIndexDescriptionを使って、どのカラムをインデックスするか確かめます。また同じテーブルを使っているので、subentityも同じ扱いです。これで、データベース作成が終わりました。
データベースを作成したら、プライマリーキーのマッピングが必要です。これは簡単に実装できますので、説明は省略します。
一番大きなプライマリーキーを見つけて、1を足してベースIDとして使います。そのベースIDをnewObjectID(for: referenceObject:)に渡して使います。これは、スーパークラスに実装しているので、呼ぶだけで大丈夫です。
検索と保存は、すべて同じファンクションを通します。少し不思議ですが、AppleのAPIなので仕方ありません。NSPersistentStoreRequestはサブクラスなので、サブクラスのタイプによって検索か保存か分けることは可能です。
検索の場合は、NSFetchRequestですが、保存の場合は、NSSaveChangesRequestです。まずは検索のほうを見てみましょう。
Fetch Requestは、さらに4種類あります。基本はそんなに変わりませんが、全部Arrayをreturnします。ということで、.countResultTypeの場合は、1つのエレメントしか入っていないArrayをreturnします。ここのは、1つのエレメントに1つのオブジェクトのかたちです。
全部の検索をお見せするのは時間的に難しいので、managedObjectIDResultTypeだけを見てみましょう。
まず、さっき作ったプライマリーキーをセレクトします。ここから結果がある間にループして、データベース用のIDを読みます。これはこのまま使いませんので、newObjectID(for: referenceObject:)を通して、NSManagedObjectIDに変更します。あとはreturnするだけです。
ということで検索が終わって、残されているのは保存です。今回は1つのタイプしかないんですが、タイプの中に、3つのタスクが含んでいます。幸いすべて似ているので今日はInsertに集中します。
ちなみに1ヶ所変な行があります。この最後のreturnは、空のArrayです。これはさっきの検索と同じファンクションですから、returnタイプはOptionalじゃない上に、Appleのドキュメンテーションによると、Arrayを期待しています。
とりあえず、Insertを見てみましょう。これはデータベース作成の時とよく似ています。ただし今回の目的はアトリビュートではなく、アトリビュートバリューのことです。今回のほうが、少し難しいです。
まずは、データベースのプライマリーキーを必ず入れないといけないので、先に用意しましょう。続いて、プロパティごとに名前を使って、カラムの名前をマッピングします。バリューのほうはもうちょっとやっかいです。アトリビュートの場合は、そのまま使いますが、リレーションシップの場合はNSManagedObjectなので、直接データベースで使えません。
nilじゃない時に、データベース用のキーに変更する必要があります。ちなみにここでは、toManyを無視していますが、実際にそれもフィルタリングしないといけないんです。ただ、スライドに入れるのはちょっと難しかったです。
では、検索も保存もできたから試してみると、動きません。今のはまだ、Faultしか返せないので、Faultをきちんと読み込まないといけません。
これについては、2つのファンクションがあります。newValuesForObject(with:with:)と、newValue(forRelationship:forObjectWith:with:)です。簡単に言うと、newValuesForObject(with:with:)は、アトリビュートを取り出し、newValue(forRelationship:forObjectWith:with:)は、リレーションシップを取り出します。
はじめに、アトリビュートを見てみます。主に2つのステップがあります。ここで、カラムの名前とタイプを取ります。リレーションシップは、完全に無視しています。先ほどの、newValue(forRelationship:forObjectWith:with:)でできますので、ここはだいぶ楽になります。
これを実装したら、次にvalueをマッピングしないといけません。特別なことはやっていませんが、SQLiteのAPIにより、タイプごとの取り扱いになります。なので、かなり大きい数値になります。
ただし、これが終われば、ストアノードを作って、returnするだけです。
リレーションシップのほうは楽です。1つ引っかかるポイントとしては、このAnyのreturnタイプです。実は3つのタイプをreturnできます。toOneのリレーションシップなら、NSIIncrementalStoreNodeかNSManagedObjectIDにreturnします。そして、toManyの場合は、NSManagedObjectIDのArrayをreturnします。
returnだけではなく、実装も別々なので、toOneとtoManyを分けてみましょう。
toOneの場合は、さっきのアトリビュートより簡単です。リレーションシップをメインエンティティテーブルから取って、NSManagedObjectIDに変更するだけです。毎回同じタイプなので、さっきの大きい数値も必要ありません。
注意点はここです。SQLiteのAPIは、nil Intの場合、0をreturnします。なのでこの実装だと、0をvalid IDとして認めてはいけません。
toManyはtoOneと似ていますが、データベースが別なので、スレッドは少し違います。複数なのでループも入っていますが、それ以外はtoOneとまったく一緒です。
これで、簡単なStoreを作成できました。いろいろ(説明が)足りない点はありますが、もし興味があればぜひSNSで連絡してください。喜んでお話しします。
今日はiOSと暗号化したCoreDataの作り方という内容をお話ししました。ありがとうございました。
LINE株式会社
2024.12.20
日本の約10倍がん患者が殺到し、病院はキャパオーバー ジャパンハートが描く医療の未来と、カンボジアに新病院を作る理由
2024.12.19
12万通りの「資格の組み合わせ」の中で厳選された60の項目 532の資格を持つ林雄次氏の新刊『資格のかけ算』の見所
2024.12.16
32歳で成績最下位から1年でトップ営業になれた理由 売るテクニックよりも大事な「あり方」
2023.03.21
民間宇宙開発で高まる「飛行機とロケットの衝突」の危機...どうやって回避する?
PR | 2024.12.20
モンスター化したExcelが、ある日突然崩壊 昭和のガス工事会社を生まれ変わらせた、起死回生のノーコード活用術
2024.12.12
会議で発言しやすくなる「心理的安全性」を高めるには ファシリテーションがうまい人の3つの条件
2024.12.18
「社長以外みんな儲かる給与設計」にした理由 経営者たちが語る、優秀な人材集め・会社を発展させるためのヒント
2024.12.17
面接で「後輩を指導できなさそう」と思われる人の伝え方 歳を重ねるほど重視される経験の「ノウハウ化」
2024.12.13
ファシリテーターは「しゃべらないほうがいい」理由 入山章栄氏が語る、心理的安全性の高い場を作るポイント
2024.12.10
メールのラリー回数でわかる「評価されない人」の特徴 職場での評価を下げる行動5選
Climbers Startup JAPAN EXPO 2024 - 秋 -
2024.11.20 - 2024.11.21
『主体的なキャリア形成』を考える~資格のかけ算について〜
2024.12.07 - 2024.12.07
Startup CTO of the year 2024
2024.11.19 - 2024.11.19
社員の力を引き出す経営戦略〜ひとり一人が自ら成長する組織づくり〜
2024.11.20 - 2024.11.20
「確率思考」で未来を見通す 事業を成功に導く意思決定 ~エビデンス・ベースド・マーケティング思考の調査分析で事業に有効な予測手法とは~
2024.11.05 - 2024.11.05