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

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

新規登録して質問してみよう
ただいま回答率
85.50%
Realm

RealmとはSQLiteやCore Dataに代わるモバイルデータベースです。iOSとAndroidの両方でサポートされています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

Q&A

解決済

2回答

2046閲覧

Realmのスレッド間でのデータの受け渡しを可能にするためのスレッドセーフ参照が使用できない

swifty

総合スコア38

Realm

RealmとはSQLiteやCore Dataに代わるモバイルデータベースです。iOSとAndroidの両方でサポートされています。

Xcode

Xcodeはソフトウェア開発のための、Appleの統合開発環境です。Mac OSXに付随するかたちで配布されています。

Swift

Swiftは、アップルのiOSおよびOS Xのためのプログラミング言語で、Objective-CやObjective-C++と共存することが意図されています

0グッド

0クリップ

投稿2019/07/27 01:00

編集2019/07/27 01:20

実現したいこと

アプリ内課金で購入されたSubscription商品のレシート検証の際、realmを使おうとするとクラッシュしてしまうのでしないようにしたいです。

エラーメッセージ

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

該当のソースコード

swift

1func validateReceipt(completion: @escaping (Bool) -> Void) { 2 3 //(中略) 4 5 // 自前のサーバーを経由してAppleに問い合わせて取得したレシート情報が以下の receipt 6 verifier.verify { receipt in 7 guard let receipt = receipt else { 8 return 9 } 10 11 // レシート検証ロジック 12 //1. 正当なレシートか検証 13 if receipt["status"] as! Int == 0 { 14 15 //2. 自分のアプリのレシートか検証 16 let receiptDictionary = receipt["receipt"] as! NSDictionary 17 if receiptDictionary["bundle_id"] as! String == "com.〇〇.〇〇" { 18 19 //3. 未登録のレシートか最新のexpires_date_msがRealmにあるか否かで検証 20 21 let latestReceiptInfoArray = receipt["latest_receipt_info"] as! Array<Dictionary<String, AnyObject>> 22 23 var latestExpireDate:Int = 0 24 for latestReceiptInfo in latestReceiptInfoArray { 25 let receiptExpireDateMs = Int(latestReceiptInfo["expires_date_ms"] as? String ?? "") ?? 0 26 let receiptExpireDateS = receiptExpireDateMs / 1000 27 if receiptExpireDateS > latestExpireDate { 28 latestExpireDate = receiptExpireDateS 29 } 30 } 31 32 //RealmでlatestExpireDateで検索 33 let predicate = NSPredicate(format: "expireDate == %i",latestExpireDate) 34 let fetchedSubscriptionDataArray = self.realm.objects(Subscription.self).filter(predicate) 35 36 if fetchedSubscriptionDataArray .isEmpty { 37 38 //4. 有効期限内か確認 39 40 let now = Int(Date().timeIntervalSince1970) 41 42 //有効期限以内であればレシート検証結果OK 43 if latestExpireDate > now { 44 45 //Subscriptionを始めたことをRealmに保存 46 let subscription = Subscription() 47// subscription.setValue(true, forKey: "valid") 48// subscription.setValue(Date(), forKey: "startedTime") 49// subscription.setValue(Date(), forKey: "updatedTime") 50// subscription.setValue(latestExpireDate, forKey: "expireDate") 51 52 let subscriptionRef = ThreadSafeReference(to: subscription) 53 DispatchQueue(label: "background").async { 54 let realm = try! Realm() 55 guard let subscription = realm.resolve(subscriptionRef) else { 56 return 57 } 58 try! realm.write { 59 subscription.setValue(true, forKey: "valid") 60 subscription.setValue(Date(), forKey: "startedTime") 61 subscription.setValue(Date(), forKey: "updatedTime") 62 subscription.setValue(latestExpireDate, forKey: "expireDate") 63 } 64 } 65 66// do { 67// try self.realm.write { 68// self.realm.add(subscription) 69// } 70// } catch { 71// print("Error saving (error)") 72// } 73 74 completion(true) 75 76 } else { 77 print("Invalid Receipt (This receipt expired)") 78 completion(false) 79 } 80 } else { 81 print("Invalid Receipt (This receipt was already used)") 82 completion(false) 83 } 84 } else { 85 print("Invalid Receipt (Other product receipt)") 86 completion(false) 87 } 88 } else { 89 print("Invalid Receipt") 90 completion(false) 91 } 92 } 93}

以下トランザクション内の上記ファンクションが使われる該当箇所

Swift

1func paymentQueue(_ queue: SKPaymentQueue, updatedTransactions transactions: [SKPaymentTransaction]) { 2 3 for transaction in transactions { 4 5 if transaction.transactionState == .purchased { 6 7 print("Transaction successful!") 8 9 //レシートを検証 10 validateReceipt { (Bool) in 11 if Bool == true { 12 13 // MainPageに遷移 14 self.performSegue(withIdentifier: "welcomeToMain", sender: nil) 15 SKPaymentQueue.default().finishTransaction(transaction) 16 17 } else { 18 19 print("Receipt Validation Failed") 20 21 } 22 } 23 } 24 //(中略) 25 } 26}

試したこと

始めは上のソースコードでコメントアウトしているいつも自分が使っている方法でrealmにデータを保存しようとしていたのですが上記のエラーメッセージが出てしまったため、realmのこちらのドキュメントを参照して、スレッド間でのデータの受け渡しを可能にするスレッドセーフ参照を使えばいいのではないかと考え、上のソースコードのように書きかえました。

ですが同じエラーメッセージが出て来てしまい先に進めません。

皆様のお力添えいただけますと幸いです。
何卒よろしくお願いいたします。

補足情報

Swift 5.0.1
Xcode 10.3
Realm 3.17.3

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

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

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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答2

0

ベストアンサー

コメントアウトしている箇所のself.realmインスタンスを生成したスレッドと、verifyer.verifyのクロージャーが実行されるスレッドが異なるため、self.realmインスタンスを用いた時にエラーが出るのではないでしょうか?

self.realmではなく新しくrealmインスタンスを生成してやってみるとどうでしょうか?

swift

1verifier.verify { 2 ... 3 4 //Subscriptionを始めたことをRealmに保存 5 let subscription = Subscription() 6 subscription.setValue(true, forKey: "valid") 7 subscription.setValue(Date(), forKey: "startedTime") 8 subscription.setValue(Date(), forKey: "updatedTime") 9 subscription.setValue(latestExpireDate, forKey: "expireDate") 10 11 do { 12 try self.realm.write { // `self.realm`インスタンスを生成したスレッドと`verifier.verify`に渡すクロージャーが実行されるスレッドが異なるためエラーが出ている? 13 self.realm.add(subscription) 14 } 15 } catch { 16 print("Error saving (error)") 17 } 18 19 ... 20}

swift

1verifier.verify { 2 ... 3 4 //Subscriptionを始めたことをRealmに保存 5 let subscription = Subscription() 6 subscription.setValue(true, forKey: "valid") 7 subscription.setValue(Date(), forKey: "startedTime") 8 subscription.setValue(Date(), forKey: "updatedTime") 9 subscription.setValue(latestExpireDate, forKey: "expireDate") 10 11 do { 12 let realm = try Realm() 13 try realm.write { 14 realm.add(subscription) 15 } 16 } catch { 17 print("Error saving (error)") 18 } 19 20 ... 21}

ThreadSafeReferenceはRealmに管理されているオブジェクトを別のスレッドに引き渡す時に用いるものなので、Subscriptionインスタンスを生成してすぐはRealm管理下にはない為にエラーが起きるのではないでしょうか

投稿2019/07/29 07:43

simorgh3196

総合スコア157

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

swifty

2019/08/06 09:06

ご回答ありがとうございました! おっしゃる通りでした。新しくインスタンスを生成することで無事解決できました。 他の作業に追われていて検証に時間がかかり返事をするのが遅くなってしまいました。 申し訳ございません。 今後とも何卒よろしくお願いします。
guest

0

読み間違えていました
以下は間違いです。


ご提示のコードのみではデータの流れが全然見えないので憶測ですが ThreadSafeReference(_:) すべきなのはpaymentQueue(_:updatedTransactions:)の中の方ではないですか?

投稿2019/07/27 05:28

編集2019/07/27 06:31
MasakiHori

総合スコア3384

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

swifty

2019/07/27 06:15

お世話になっております。ご回答ありがとうございます。 もしそうだと仮定した場合ThreadSafeReference(_:)をどちらでどのように書きかえればよいのでしょうか? paymentQueue(_:updatedTransactions:)の中でrealmを使っているのがvalidateReceiptの中で ①expireDateでrealm内のデータを検索する部分 ②realmにデータを保存する部分 の2つだけだと自分では認識しているのでこちらで使っているSubscriptionオブジェクトをThreadSafeReference(_:)すべきなのではないだろうかと考えこのように実装しました。 もしもう少しこちらの詳細がわかれば回答できるなどありましたらお伝えいただければできるだけ追記などさせていただきます。 お忙しいところ大変恐縮ですが引き続きご教授いただけますと幸いです。 何卒よろしくお願いします。
MasakiHori

2019/07/27 06:31

すみません。 コードを読み間違えていました。
swifty

2019/07/27 06:55

いえいえ。 ご回答いただけただけで大変嬉しく思います。 もし何かお分かりになることございましたらよろしくお願いします。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.50%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問