iOSアプリで読み取り専用データをbundleするための方法
データ作成
実際に使うものと同様のデータモデルで作成する。
https://qiita.com/kenmaz/items/818d61cd0ece8664c017
の設定をしておくと、SQLiteのファイルパスがログに出力されるので便利。
サイズを小さくするためにVACUUMする。
sqlite> VACUUM;
Bundleのやり方
普通にSQLiteのファイルをドラッグアンドドロップしてbundleする。
書き込みたい場合は Application Support とかにコピーしてから使う必要があるが、読み取り専用なのでその必要はない。
ただし、sqliteの一時ファイルである Hogehoge.sqlite-wal と Hogehoge.sqlite-shm も一緒にbundleしてあげる必要がある。
これが存在しないと、Unable to open って怒られる。
両方とも空ファイルでOK。
↑のはiOS11で開けないですよってクラッシュすることが判明。
iOS13でもなんかwarnちょいちょい出てたし、やはりきちんとApplication Supportにコピーすべきっぽい。
-walと-shmは空ファイル作成すれば良いのでbundleする必要はない。
読み込み
private(set) lazy var container: NSPersistentContainer? = {
let container = NSPersistentContainer(name: "Bundle")
let dbBaseUrl = try! FileManager.default
.url(for: .applicationSupportDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
.appendingPathComponent("Bundle")
let dbUrl = dbBaseUrl.appendingPathExtension("sqlite")
if !FileManager.default.fileExists(atPath: dbUrl.path) {
do {
for fileExtension in ["sqlite-wal", "sqlite-shm"] {
try "".write(to: dbBaseUrl.appendingPathExtension(fileExtension), atomically: false, encoding: .utf8)
}
let source = Bundle.main.url(forResource: "Bundle", withExtension: "sqlite")!
try FileManager.default.copyItem(at: source, to: dbUrl)
} catch {
for fileExtension in ["sqlite", "sqlite-wal", "sqlite-shm"] {
try? FileManager.default.removeItem(at: dbBaseUrl.appendingPathExtension(fileExtension))
}
なんやかんやしてユーザーに失敗したことを伝える
return nil
}
}
let description = NSPersistentStoreDescription()
description.url = dbUrl
description.isReadOnly = true
container.persistentStoreDescriptions = [description]
container.loadPersistentStores(completionHandler: { (_, error) in
if let error = error { fatalError("Unresolved error \(error)") }
})
return container
}()