質問: [CLLocation]のデータをUserDefaultsを用いて保存しようとしたところ、UserDefaultsでは[CLLocation]をサポートしていないとのエラーが吐かれました。 [CLLocation]のデータを保存する方法はどのようなものがあるでしょうか。
回答: まず、第一に認識しておいて欲しいのは「`UserDefaults`はアプリの設定画面など、少量のユーザ別設定を保存しておくことを主眼として設計された枠組」みだということです。配列の要素数が数千を超えるなど巨大な場合は、`UserDefaults`の使用は避けるべきでしょう。 (残念ながら、そのお手軽さゆえに、初心者向けの入門記事等では`UserDefaults`が多用される傾向にあり、なんとある程度経験を経たプロでさえ「初心者向けの入門記事」まんまのやり方で作ったアプリをまんまApp Storeにあげたりしています...。アプリ全体のパフォーマンスに影響する場合もありますので、あなたが「アプリ開発初心者」を卒業している場合は、是非適切な方法を採用するようにしてください。) まずは`UserDefaults`に保存可能なデータ型のおさらいから。 UserDefaults > > A default object must be a property list—that is, an instance of (or for collections, a combination of instances of) `NSData`, > > `NSString`, `NSNumber`, `NSDate`, `NSArray`, or `NSDictionary`. If you > > want to store any other type of object, you should typically archive > > it to create an instance of `NSData`. > > > (節約) > > `UserDefaults`用のオブジェクトはプロパティリストでなければいけません。それはつまり`NSData`, `NSString`, `NSNumber`, `NSDate`, `NSArray`, または > > `NSDictionary`のインスタンス(または、コレクション型の場合、インスタンスの組み合わせ)のいずれかです。他の型のオブジェクトを保存したいなら、アーカイブして`NSData`のインスタンスを作成するのが一般的です。 > > > Swiftなら、`Data`, `String`, 組み込みの数値型及び`Bool`, `Date`, `Array`, `Dictionary`(`Array`と`Dictionary`なら要素、キー、値もこのいずれか)にあたります。(通称「plist互換」と言ったりします。)`CLLocation`は、このいずれでもない(plist互換ではない)ので、そのままでは`UserDefaults`には保存出来ません。 このようなデータを保存する場合、以下のような方法が考えられます。 1. (`UserDefaults`のドキュメントにあるように)アーカイブして`Data`に変換してから`UserDefaults`に登録する。 2. ファイルに直接保存する。(ファイルの中身は要はバイト列なので、上記の「アーカイブして`Data`に変換」はこの場合にも使われる。) 3. Core Dataを使う。 4. RealmなどのサードパーティライブラリやSQLiteなどを直接使う。 と言うわけで、3., 4.の方法は単純な配列を保存するには大げさすぎる割にメリットが少ないので、以下では(2.への応用も利く)1.のやり方を説明しておきます。 今、現在Swiftでの主流のアーカイブ(他言語ならシリアライズ、なんて言ったりもします)方法は、`Codable`プロトコルを使う(新しい、Swiftネイティブな型でないと使いにくい)ものと、Objective-C時代から続く`NSCoding`プロトコルを使う(古い、`NSObject`の子孫クラスでないと使えない)ものの大きく2つがあります。(探せば他にもありますが。) `CLLocation`は`NSCoding`を継承した`NSSecureCoding`プロトコルが実装されているので、後者の方が簡単です。 では、`[CLLocation]`から`Data`を作って`UserDefaults`に保存するコードから。 ``` var locations: [CLLocation] = [] //`locations`に値を入れる //... let locationData = NSKeyedArchiver.archivedData(withRootObject: locations) UserDefaults.standard.set(locationData, forKey: "locationData") ``` `Data`に変換するのが1行、`UserDefaults`に入れるのが1行とシンプルです。 (`UserDefaults`に入れる代わりにファイルに保存するのもそう難しくはありません。) `UserDefaults`から`Data`を取得してそれを元の`[CLLocation]`に復元するコード。 ``` if let locationData = UserDefaults.standard.data(forKey: "locationData"), let locations = NSKeyedUnarchiver.unarchiveObject(with: locationData) as? [CLLocation] { print(locations) //`locations`を使った処理 //... } else { print("NO locationData in UserDefaults") //UserDefaultsにlocationDataが無い時の処理 //... } ``` データが`UserDefaults`に存在するかどうか、それが`[CLLocation]`に変換できるかどうかをif-letで判定しているで少し長くなっていますが、実質はこちらもそれぞれ1行です。 ちょっと説明がしつこくて読みにくかったかもしれませんが、それぞれのコード、ご自身のプロジェクトに組み込むなどしてお試しください。