【Swift】RealmファイルをiCloudにバックアップ・リストアする
RealmファイルをiCloudにバックアップ・リストアする方法を挙げます。この辺りの環境設定とコードの記載がある記事は、あまりないため、まとめました。
[実行環境]
Xcode13.1
Realm 10.19.0(SwiftPMから導入)
[手順]
手順は以下となります。
1). 事前準備
Realmを導入した後、何かしらデータを登録してある状態とします。
2). iCloudの環境設定
2-1). apple developer program側の設定
apple developer programで「Certificates, Identifiers & Profiles」のサイドメニューで、以下の画面で、画面右側の虫眼鏡マークの右横を「iCloud Containers」変更をして 「identifiers」の右のプラスマークからiCloud Containerの登録をします。(ご注意:追加したiCloud Containerは消せないので、登録はタイプミスがないようにご注意ください。削除ができないことは以下の記事にもあります。https://developer.apple.com/forums/thread/5547)
2-2-1). signinig&Capabilityの設定
Xcodeの設定で、「signinig&Capability」の+選択から「iCloud」を選択してください。以下の画面のようにiCloudの設定をできる画面が表示されるます。Xcode側でApple Idと紐付け設定済みの場合(※)は、以下の画面の白色で塗りつぶされている箇所には、2-1)で登録してiCloud Contaier名が表示されます。2-1)で、「iCloud.com.domain.appname」と登録してある場合は、同じ文字列が表示されます。この例の場合は、「iCloud.com.domain.appname」の欄にチェックを入れます。
2-2-2). infoの設定
Xcode13より、info.plistはなく、Targetsのinfoタブ欄に設定を追加します。
(参考になる他サイトの記事になります。:https://software.small-desk.com/development/2021/09/08/xcode-no-infoplist-xcode13/)
infoタブ欄に以下画像のようなkeyとvalueを追加します。赤枠部分には、2-1)で設定した例で挙げた文字列「iCloud.com.domain.appname」を入力します。緑の枠には、iCloudで表示するフォルダ名を指定します。
infoタブ欄に以下画像のようなkeyとvalueを追加します。赤枠部分には、2-1)で設定した例で挙げた文字列「iCloud.com.domain.appname」を入力します。緑の枠には、iCloudで表示するフォルダ名を指定します。
また、「NSUbiquitousContainerIsDocumentScopePublic」欄を1(true)に設定します。
iCloud Drive上にアプリから保存させたファイルを表示するためには、「NSUbiquitousContainerIsDocumentScopePublic」を1(true)の設定と2-2-1)からのここまでの設定にて、「iCloud.com.domain.appname」のような同じ文字列に設定をしないとiCloud Drive上に保存したファイルが表示されませんでした。
2-3). 端末の設定画面
端末の設定アプリを開き、iCloudの設定で、実行対象のアプリがiCloudの対象になっているか確認をします。
3).実装コード
iCloudにdefault.realmファイルのバックアップとiCloudからアプリのdefault.realをリストアするコードを記載します。
(※注意点があります。バックアップ処理がエラーになることがあります。それは、filesアプリからバックアップ先のディレクトリを手動で削除してしまった場合に、バックアップ処理で保存先が存在しないというエラーになります。その場合、端末の設定アプリにおけるICloud driveを手動でoffにして再度onにしないと書き込みできないようでした。)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// upload Realm file to icloud | |
func uploadRealmDbToiCloud() { | |
if !isCloudEnabled(){ | |
return | |
} | |
let fileManager = FileManager.default | |
let realmArchiveURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)! | |
.appendingPathComponent("Documents") | |
.appendingPathComponent("default.realm") | |
do { | |
if fileManager.fileExists(atPath: realmArchiveURL.path) { | |
try fileManager.removeItem(at: realmArchiveURL) | |
} | |
let realm = try! Realm() | |
realm.beginWrite() | |
try realm.writeCopy(toFile: realmArchiveURL) | |
realm.cancelWrite() | |
} catch { | |
print("ERR") | |
} | |
} | |
func isCloudEnabled() -> Bool { | |
if DocumentsDirectory.iCloudDocumentsURL != nil { return true } | |
else { return false } | |
} | |
// restore Realm file to icloud | |
func downloadRealmDbFromICloud(){ | |
if !isCloudEnabled() { | |
return | |
} | |
let fileManager = FileManager.default | |
if let icloudFolderURL = DocumentsDirectory.iCloudDocumentsURL, | |
let urls = try? fileManager.contentsOfDirectory(at: icloudFolderURL, includingPropertiesForKeys: nil, options: []) { | |
if let iClouPath = urls.first { | |
let lastPathComponent = iClouPath.lastPathComponent | |
print(lastPathComponent) | |
if lastPathComponent.contains(".realm") { | |
var isDownloaded = false | |
while !isDownloaded { | |
if fileManager.fileExists(atPath: iClouPath.path) { | |
isDownloaded = true | |
self.copyFileToLocal() | |
} | |
else { | |
do { | |
try fileManager.startDownloadingUbiquitousItem(at: iClouPath) | |
} catch { | |
print("Unexpected error: \(error).") | |
} | |
} | |
} | |
} | |
} | |
} | |
} | |
func copyFileToLocal() { | |
if isCloudEnabled() { | |
let realm = try! Realm() | |
try! realm.write { | |
realm.deleteAll() | |
} | |
remove(realmURL: DocumentsDirectory.localDocumentsURL) | |
let fileManager = FileManager.default | |
let enumerator = fileManager.enumerator(atPath: DocumentsDirectory.iCloudDocumentsURL!.path) | |
while let file = enumerator?.nextObject() as? String { | |
do { | |
try fileManager.copyItem(at: DocumentsDirectory.iCloudDocumentsURL!.appendingPathComponent(file), to: DocumentsDirectory.localDocumentsURL.appendingPathComponent(file)) | |
} catch let error as NSError { | |
print("Failed to move file to local dir : \(error)") | |
} | |
} | |
} | |
} | |
func remove(realmURL: URL) { | |
let realmURLs = [ | |
realmURL, | |
realmURL.appendingPathExtension("lock"), | |
realmURL.appendingPathExtension("note"), | |
realmURL.appendingPathExtension("management"), | |
] | |
for URL in realmURLs { | |
try? FileManager.default.removeItem(at: URL) | |
} | |
} | |
struct DocumentsDirectory { | |
static let localDocumentsURL = FileManager.default.urls(for: FileManager.SearchPathDirectory.documentDirectory, in: .userDomainMask).last! | |
static let iCloudDocumentsURL = FileManager.default.url(forUbiquityContainerIdentifier: nil)?.appendingPathComponent("Documents") | |
} |
4). 参考コード
本記事は、以下のstackoverflowのに掲載されたコードを流用させて頂きました。