質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

ただいまの
回答率

89.11%

Realmを使ったデータの送受信が出来ない

解決済

回答 2

投稿 編集

  • 評価
  • クリップ 0
  • VIEW 406

lahlah

score 16

前提・実現したいこと

iPhoneの Realmデータを Apple Watchに送るプログラムを考えています.
各デバイスにアプリをインストールすることは出来ましたが、データの送受信が上手く行きません.

何が不足しているのでしょうか?
ご回答よろしくお願いします.

発生している問題・エラーメッセージ

watchOSのプログラムを実行した時に以下のエラーメッセージが出ます.

Terminating app due to uncaught exception 'RLMException', reason: 'Realm accessed from incorrect thread.'

追記 11/26
watchOS プログラムの
let realm = try! Realm()
をrealmデータを取得する関数の中に持って行ったところ、上記のエラーは解消されましたが、代わりに以下のエラーが発生します.

Fatal error: 'try!' expression unexpectedly raised an error: Error Domain=io.realm Code=2 "Unable to open a realm at path '/var/mobile/Containers/Data/PluginKitPlugin/E7F03F8B-86D4-42AF-AC8A-A1F2B2E27780/Documents/Inbox/com.apple.watchconnectivity/AFBC4FDB-28C3-4D55-A833-1E5F59DA0C4B/Files/F39F9FCD-AA0E-48B8-897D-E67AF8D2D043/default.realm.management': make_dir() failed: Operation not permitted Path:Exception backtrace:

該当のソースコード

iOSのプログラム

import UIKit
import RealmSwift
import WatchConnectivity

class ViewController: UIViewController, WCSessionDelegate {
    let realm = try! Realm()
    let get = cData()

    override func viewDidLoad() {
        super.viewDidLoad()
        // Do any additional setup after loading the view.

        get.name = "abc"
        get.memo = "def"

        try! realm.write {
            realm.add(get)
        }

        print("for iPhone")
        print(get.name)
        print(get.memo)

        //データの追加
        try! realm.write {
            get.name = get.name + "ghi"
        }


        if WCSession.isSupported() {
            let session = WCSession.default
            session.delegate = self
            session.activate()
        }

    }

    //ボタン押したらデータ送信
    @IBAction func sending(_ sender: UIButton) {
        //realmデータを送る
        if let path = Realm.Configuration().fileURL {
            WCSession.default.transferFile(path, metadata: nil)
            print("send data to Apple Watch")
        }
    }


    @available(iOS 13.1, *)
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print("activation did complete!\n")
    }

    func sessionDidBecomeInactive(_ session: WCSession) {
        print("session did become inactive")
    }

    func sessionDidDeactivate(_ session: WCSession) {
        print("session did deactivate")
    }

}

watchOSのプログラム

import WatchKit
import Foundation
import RealmSwift
import WatchConnectivity

class InterfaceController: WKInterfaceController,  WCSessionDelegate {
    override func awake(withContext context: Any?) {
        super.awake(withContext: context)

        // Configure interface objects here.
        if WCSession.isSupported() {
            let session = WCSession.default
            session.delegate = self
            session.activate()
        }
    }


    @available(watchOS 6.0, *)
    func session(_ session: WCSession, activationDidCompleteWith activationState: WCSessionActivationState, error: Error?) {
        print("watch activation did completed")
    }

    //realmデータを取得する関数
    func session(_ session: WCSession, didReceive file: WCSessionFile) {
        var config = Realm.Configuration()
        config.fileURL = file.fileURL
        Realm.Configuration.defaultConfiguration = config

        let realm = try! Realm()
        let text = realm.objects(cData.self)
        print("memo: \(text[0].memo)")
        print("name: \(text[0].name)")
    }


    override func willActivate() {
        // This method is called when watch view controller is about to be visible to user
        super.willActivate()

    }

    override func didDeactivate() {
        // This method is called when watch view controller is no longer visible
        super.didDeactivate()
    }

}

試したこと

同様の事例がないか調べましたが、似た事例はあるものの今回のエラーの解決につながるようなものが未だ見つかりません.

補足情報(FW/ツールのバージョンなど)

バージョン
Xcode 11.1,  Swift5.0.1
iPhoneX,  Apple Watch Series4

・let realm = try! Realm()
をrealmデータを取得する関数内に書くようにしたところ、iOSのプログラムを実行すればボタンを押した時にコンソールに "send data to Apple Watch" は出力されるようになりました.

・watchOSのプログラムを実行した時は、"watch activation did completed" がコンソールに出力された後、エラーメッセージが表示されます.

・WatchConnectivityの使い方を知るために、Apple公式サイトを参考にしましたが、このサイトの Get Started の手順5.6がいまいちよく分からなかったため、正しい手順を踏めていないことが今回のエラーにつながっているのかもしれません.

手順5については、プロジェクトにSettings-Watch.bundleを追加し、Root.plistのApplicationGroupContainerIdentifierをApp groupsで作成したグループ名にしました.
手順6については、TestDataProvider classがどこにあるのか分からなかったため、何もしませんでした.

  • 気になる質問をクリップする

    クリップした質問は、後からいつでもマイページで確認できます。

    またクリップした質問に回答があった際、通知やメールを受け取ることができます。

    クリップを取り消します

  • 良い質問の評価を上げる

    以下のような質問は評価を上げましょう

    • 質問内容が明確
    • 自分も答えを知りたい
    • 質問者以外のユーザにも役立つ

    評価が高い質問は、TOPページの「注目」タブのフィードに表示されやすくなります。

    質問の評価を上げたことを取り消します

  • 評価を下げられる数の上限に達しました

    評価を下げることができません

    • 1日5回まで評価を下げられます
    • 1日に1ユーザに対して2回まで評価を下げられます

    質問の評価を下げる

    teratailでは下記のような質問を「具体的に困っていることがない質問」、「サイトポリシーに違反する質問」と定義し、推奨していません。

    • プログラミングに関係のない質問
    • やってほしいことだけを記載した丸投げの質問
    • 問題・課題が含まれていない質問
    • 意図的に内容が抹消された質問
    • 過去に投稿した質問と同じ内容の質問
    • 広告と受け取られるような投稿

    評価が下がると、TOPページの「アクティブ」「注目」タブのフィードに表示されにくくなります。

    質問の評価を下げたことを取り消します

    この機能は開放されていません

    評価を下げる条件を満たしてません

    評価を下げる理由を選択してください

    詳細な説明はこちら

    上記に当てはまらず、質問内容が明確になっていない質問には「情報の追加・修正依頼」機能からコメントをしてください。

    質問の評価を下げる機能の利用条件

    この機能を利用するためには、以下の事項を行う必要があります。

回答 2

+1

下記の実行スレッドが異なっていませんか。

let realm = try! Realm()
let text = realm.objects(cData.self)

Xcode - Documentationより
optional func session(_ session: WCSession, didReceive file: WCSessionFile)
Discussion
This method is called on a background thread of your app.
(Google翻訳)
このメソッドは、アプリのバックグラウンドスレッドで呼び出されます。

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

  • 2019/11/26 17:51

    Realmデータを送るより、SendMessageやtransferFileを使った方が良いということでしょうか?

    キャンセル

  • 2019/11/26 18:27

    こっちの問題かな。
    --------- Xcode - Documentationより
    Important
    Remember to move the file referenced by the file parameter if you intend to keep it. If you do not move the file synchronously during your implementation of this method, the system deletes the file when the method returns.
    (Google翻訳)
    重要
    保持する場合は、fileパラメーターによって参照されるファイルを移動することを忘れないでください。 このメソッドの実装中にファイルを同期的に移動しない場合、メソッドが戻るときにシステムはファイルを削除します。
    ---------
    現在のPathは操作が許可されていない(Operation not permitted Path)ので、許可されているフォルダに移動すればいいのではないでしょうか。

    キャンセル

  • 2019/11/26 18:43

    ありがとうございます.
    ちょうど参考になるサイトが見つかり、書き込みが許可されているディレクトリにコピーして開く必要があるということが分かり、コメントでご指摘いただいた通り、許可されているディレクトリに移動する必要がありました.
    初めのエラーを含め沢山アドバイスをいただき、とても参考になりました.
    ありがとうございました!

    キャンセル

check解決した方法

0

watchOSプログラムの realmデータを取得する関数を以下に修正したところ、上手くいきました.

//realmデータを取得する関数
func session(_ session: WCSession, didReceive file: WCSessionFile) 
{
    var config = Realm.Configuration()

    // --- 修正箇所 ↓ ---
    let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
    let documentsDirectory = paths[0]
    let realmURL = documentsDirectory.appendingPathComponent("data.realm")
    try! FileManager.default.copyItem(at: file.fileURL, to: realmURL)

    config.fileURL = realmURL
    // --- 修正箇所 ↑ ---

    Realm.Configuration.defaultConfiguration = config

    let realm = try! Realm()
    let text = realm.objects(cData.self)
    print("memo: \(text[0].memo)")
    print("name: \(text[0].name)")
    }

こちらのサイトを参考にさせていただきました.
Watch Connectivityで転送されたファイルは一旦システムが管理する特別なディレクトリに保存されます. そこでは、Realmが必要とする管理ファイルの作成が許可されていないため、読み取り専用でなければ、直接ファイルを開くことはできません. 転送されたファイルを、Documentディレクトリなど書き込み可能な場所にコピーしてから開く必要があります.

投稿

  • 回答の評価を上げる

    以下のような回答は評価を上げましょう

    • 正しい回答
    • わかりやすい回答
    • ためになる回答

    評価が高い回答ほどページの上位に表示されます。

  • 回答の評価を下げる

    下記のような回答は推奨されていません。

    • 間違っている回答
    • 質問の回答になっていない投稿
    • スパムや攻撃的な表現を用いた投稿

    評価を下げる際はその理由を明確に伝え、適切な回答に修正してもらいましょう。

15分調べてもわからないことは、teratailで質問しよう!

  • ただいまの回答率 89.11%
  • 質問をまとめることで、思考を整理して素早く解決
  • テンプレート機能で、簡単に質問をまとめられる