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

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

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

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

Swift

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

Q&A

解決済

1回答

3466閲覧

swift realm 「Object has been deleted or invalidated.」エラー

asedsa

総合スコア3

Realm

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

Swift

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

0グッド

0クリップ

投稿2022/06/05 13:04

編集2022/06/05 13:08

swift初心者です。
swiftuiを使用してます。

データの削除をしようとしているのですが、
「Object has been deleted or invalidated.」が出てしまいます。
原因がわかりません。
教えていただきたいです。

また、
realmを見てもデータが登録されないのですが、
なぜでしょうか?

他に追記があった方がいい場合は追記いたします。

・View

import SwiftUI // MARK: UserListView struct UserView: View { @ObservedObject var viewModel = UserViewModel() @State private var isUserTextFieldPresented = false @State private var isDeleteAlertPresented = false @State private var isDeleteAllAlertPresented = false @State private var userTextField = "" var body: some View { NavigationView { VStack { if (isUserTextFieldPresented) { TextField("メモを入力してください", text: $userTextField) .textFieldStyle(DefaultTextFieldStyle()) .keyboardType(.asciiCapable) } List { ForEach(viewModel.users.sorted { $0.postedDate > $1.postedDate }, id: \.self) { user in HStack { UserRowView(user: user) Spacer() // Buttonにすると行全体にタップ判定がついてしまったので、Text.onTapGestureを代わりに使っている Text("削除").onTapGesture { isDeleteAlertPresented.toggle() } .padding() .foregroundColor(.white) .background(Color.red) } .alert(isPresented: $isDeleteAlertPresented) { Alert(title: Text("警告"), message: Text("メモを削除します。\nよろしいですか?"), primaryButton: .cancel(Text("いいえ")), secondaryButton: .destructive(Text("はい")) { viewModel.deleteUser = user } ) } } } } .navigationTitle("メモの一覧") .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button("追加") { if (isUserTextFieldPresented) { viewModel.userTextField = userTextField userTextField = "" } isUserTextFieldPresented.toggle() }.disabled(isUserTextFieldPresented && userTextField.isEmpty) } } } } } // MARK: UserRowView struct UserRowView: View { var user: User var body: some View { VStack(alignment: .leading) { Text(formatDate(user.postedDate)) .font(.caption) .fontWeight(.bold) Text(user.userName) .font(.body) } } func formatDate(_ date : Date) -> String { let formatter = DateFormatter() formatter.dateStyle = .long formatter.timeStyle = .medium formatter.locale = Locale(identifier: "ja_JP") return formatter.string(from: date) } }

・ViewModel

import Foundation import Combine import RealmSwift class UserViewModel: ObservableObject { @Published private(set) var users: [User] = Array(User.findAll()) @Published var userTextField = "" @Published var deleteUser: User? @Published var isDeleteAllTapped = false private var addUserTask: AnyCancellable? private var deleteUserTask: AnyCancellable? init() { addUserTask = self.$userTextField .sink() { username in guard !username.isEmpty else { return } let user = User() user.userName = username self.users.append(user) User.add(user) } deleteUserTask = self.$deleteUser .sink() { user in guard let user = user else { return } if let index = self.users.firstIndex(of: user) { do{ self.users.remove(at: index) User.delete(user) }catch { print("Error \(error)") } } } } }

・User

import Foundation import RealmSwift class User: Object { //ユーザID @objc dynamic var id: Int = 0 //ユーザ名 @objc dynamic var userName: String = "" @objc dynamic var postedDate = Date() //Primary Keyの設定 override static func primaryKey() -> String? { return "id" } } extension User { private static var config = Realm.Configuration(schemaVersion: 1) private static var realm = try! Realm(configuration: config) static func findAll() -> Results<User> { realm.objects(self) } static func add(_ user: User) { try! realm.write { realm.add(user, update: .all) } } static func delete(_ user: User) { try! realm.write { realm.delete(user) } } }

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

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

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

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

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

退会済みユーザー

退会済みユーザー

2022/06/06 11:16

すみません、再現確認してなくて、コードを眺めただけなのですが、 Userのidは初期値の0以外に設定しているところはありますでしょうか?
asedsa

2022/06/06 11:30

質問ありがとうございます。 Userのidは載せているコードの部分でしか設定していません。
退会済みユーザー

退会済みユーザー

2022/06/06 11:35

コメントありがとうございます。 > 「Object has been deleted or invalidated.」 > realmを見てもデータが登録されないのですが、 Realmについてあまり詳しくなくて、見当違いなことを書いてしまうかもしれませんが、 idが主キーになっているものの、 全てのデータが0なので、 正しく登録できなそうな気がしました。 登録されていないのであれば、削除することもできないのかな、と思いました。 *一般的に主キーは一意という印象から書いています・・
asedsa

2022/06/07 03:25

なるほどですね! ありがとうございます! 確認してます!
guest

回答1

0

ベストアンサー

まず、Realmの使用以前に削除ボタンをタップした時の処理がおかしいので、その点を指摘しておきます。

swift

1.alert(isPresented: $isDeleteAlertPresented) { 2 Alert(title: Text("警告"), 3 message: Text("メモを削除します。\nよろしいですか?"), 4 primaryButton: .cancel(Text("いいえ")), 5 secondaryButton: .destructive(Text("はい")) { 6 viewModel.deleteUser = user 7 } 8 ) 9}

この中で viewModel.deleteUser = useruser を参照していますが、削除ボタンをタップした時のユーザは、この user には入っていません。

削除ボタンをタップした時のユーザは、onTapGestureの中でないと取得できませんので、削除ボタンをタップした時のユーザを覚えておくプロパティを次のように作成しておき、

swift

1@State private var deleteUser: User?

onTapGestureの中で次のように deleteUser に保管する処理を追加しておき、

swift

1self.deleteUser = user

Alertの中で、次のようにして削除ボタンをタップした時のユーザを渡す必要があります。

swift

1 secondaryButton: .destructive(Text("はい")) { 2 viewModel.deleteUser = self.deleteUser 3 }

次に、追記・修正依頼欄でxg63ex2bさんがコメントされている通り、UserをRealmに保存する時にidを正しく設定していないので、新しいユーザーを何個追加しても同じidのユーザーを上書きする処理になってしまい、1個しか保存されていません。
(画面上に表示されているのは、Realmに保存したデータそのものではなく、自分で追加・削除しているUserの配列を表示していますから、画面上には追加されたように見えていますが、Realmのデータベースの中には1個しか保存されていません。)

UserViewModel.swiftのaddUserTask処理の中で、次のようにして user.id を設定すると良いと思います。

swift

1let user = User() 2user.id = (try! Realm().objects(User.self).max(ofProperty: "id") ?? 0) + 1 3user.userName = username 4self.users.append(user) 5User.add(user)

これでRealmへの保存処理は正しくなりますが、これだけでは質問のエラーは解消されません。
質問のエラーが発生する原因は、Realmのデータ管理処理とSwiftUIの処理が競合してしまうためです。
そこで、今の実装をなるべく尊重するなら、RealmのモデルクラスであるUserをfreezeして処理すると
うまく行きます。
まず、先ほどのaddUserTaskの処理は、次のように先にRealmへの保存を実施し、その後freezeしたuserをusers配列に追加します。

swift

1let user = User() 2user.id = (try! Realm().objects(User.self).max(ofProperty: "id") ?? 0) + 1 3user.userName = username 4User.add(user) 5self.users.append(user.freeze())

次に、User.swiftのfindAllは、次のようにfreezeした一覧を返します。

swift

1static func findAll() -> Results<User> { 2 realm.objects(self).freeze() 3}

そして、User.swiftのdelete処理は、freezeされたuserのidを元にRealmに管理されているuserを取得し、それを使って削除します。

swift

1static func delete(_ user: User) { 2 let actualUser = realm.object(ofType: User.self, forPrimaryKey: user.id)! 3 try! realm.write { 4 realm.delete(actualUser) 5 6 } 7}

これで正しく処理できるようになると思います。
(実際にやってみて正しく動作することを確認しました。)

なお、今の方法は、Realmデータベースに保存されているデータを一旦配列に全て読み込んで処理しており、データベースのメリットをあまり活かせていません。SwiftUIでRealmを使う時は、バージョン10.10以降からSwiftUIに対応した処理方法が用意されていますので、

https://tkgstrator.work/2021/09/27/observedresults/

で解説されているように、 @Persisted@ObservedResults を使い、 append メソッド、 remove メソッドで追加/削除する方法で実装することをお勧めします。

投稿2022/06/07 02:53

TakeOne

総合スコア6299

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

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

asedsa

2022/06/07 13:57

とても丁寧な解説ありがとうございます ベストアンサーとさせていただきます
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.44%

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

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

質問する

関連した質問