Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

RealmファイルをwatchOSにfileTransferしたときの実機で生じるエラーと解決方法

More than 3 years have passed since last update.

はじめに

RealmファイルをiOSからwatchOSにfileTransferで送信しようとしたときに、実機ランでmake_dir() failed: Operation not permittedというエラーが生じました。この解決方法について備忘のためにメモを残します。

現象

実機ランすると、iOSがwatchOSにRealmファイルをfileTransferしたときに、default.realm.management': make_dir() failed: Operation not permittedというエラーが生じました。シミュレータでランしたときには、このエラーが発生しません。

Realm Swift 2.4.4、Xcode 8.2、iOS10.2.1、watchOS 3.1.1、シミュレータ(iOS/Watch)Version 10.0(SimulatorApp-236.7)という環境でした。

原因

watchOSがRealmファイルを受け取った後、そのファイルは読取り専用領域に保持されます。一方、Realmは管理のためにその領域に書き込みをしようとします。ここで権限がない操作が行われようとしたため、Operation not permittedというエラーが発生していました。

このエラーがAppleWatch実機のみで発生し、一方でシミュレータで発生しないのは、シミュレータが書込み権限の動作を完全にシミュレートできていないためだと考えられます。

プログラマが意図してwatchOS側のRealmファイルを変更しようとしなくても、Realm自体が書込みをしようとするので、この挙動は意識しておく必要があります。

解決

エラーが出てしまう従来のコードは、こちらです。読み取り専用領域にファイルが作られるので、Realmが管理用に編集しようとしたときにエラーが発生します。

before
// When the file was received
    func session(_ session: WCSession, didReceive file: WCSessionFile) {

        //set the recieved file to default Realm file
        var config = Realm.Configuration()
        config.fileURL = file.fileURL
        Realm.Configuration.defaultConfiguration = config

        // display the first of realm objects
        let realm = try! Realm()
        if let firstField = realm.objects(Field.self).first{
            realmLabel.setText(firstField.text)
        }
    }

解決のために、受信したファイルをそのまま利用するのではなく、そのコピーを書込み権限のあるdocumentDirectory配下に作り、そのパスを使うことにします。

加えて、同名のファイルがあるとFileManagerの重複ファイルエラーが発生してしまうので、その対応が必要になります。今回は、既存ファイルがあればそれを削除した後で、コピーするように実装します。

具体的には次のようなコードになります。

after
// When the file was received
    func session(_ session: WCSession, didReceive file: WCSessionFile) {

        //set the recieved file to default Realm file
        var config = Realm.Configuration()
        let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
        let documentsDirectory = paths[0]
        let realmURL = documentsDirectory.appendingPathComponent("data.realm")
        if FileManager.default.fileExists(atPath: realmURL.path){
            try! FileManager.default.removeItem(at: realmURL)
        }
        try! FileManager.default.copyItem(at: file.fileURL, to: realmURL)
        config.fileURL = realmURL
        Realm.Configuration.defaultConfiguration = config

        // display the first of realm objects
        let realm = try! Realm()
        if let firstField = realm.objects(Field.self).first{
            realmLabel.setText(firstField.text)
        }
    }

これで解決できました。

なお、FileManager.default.fileExists(atPath:)の引数には、realmURL.pathを代入します。引数がString型ですが、.absoluteStringや.relativeStringではありません。

参考リンク

今回のソースファイルは全部こちらのリポジトリで公開しています。
SampleRealmOnWatchOS3

基本的にこちらのコードの考え方を参考にしています。ただし、既存ファイルがあると重複エラーが発生するので、今回はその対応を加えて実装しました。
Apple WatchにRealmのデータを転送できない

hsylife
iOSエンジニア。フリーランス、株式会社ジーニー 代表取締役(2009年〜)自社/受託開発。「メモ電卓(ge-calc)」が88万ダウンロード。GitHubでSwiftyPickerPopover公開中。案件ご相談お気軽に連絡ください。
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away