2 / 17
Mar 2023

Hello guys, given the following implementation of a RealmDatamanager and some basic View, I end up with this code; similarly, after asking chatgpt, I ended up with the exact same example code.
It keeps crashing when deleting an element from the database.

User.swift

import Foundation import RealmSwift class User: Object, Identifiable { @objc dynamic var id: String = UUID().uuidString @objc dynamic var name: String = "" @objc dynamic var age: Int = 0 override static func primaryKey() -> String? { return "id" } }

RealmDatabaseManager.swift

import Foundation import RealmSwift import Combine class RealmDatabaseManager: ObservableObject { private var realm: Realm private var cancellables: Set<AnyCancellable> = [] @Published var users: [User] = [] init() { realm = try! Realm() fetchUsers() observeUsers() } private func fetchUsers() { users = Array(realm.objects(User.self)) } private func observeUsers() { realm.objects(User.self) .observe { [weak self] (changes: RealmCollectionChange) in DispatchQueue.main.async { switch changes { case .initial, .update: self?.fetchUsers() default: break } } } } func addUser(_ user: User) { do { try realm.write { realm.add(user) } } catch { print("Error adding user: \(error)") } } func updateUser(_ user: User, withName name: String, age: Int) { do { try realm.write { user.name = name user.age = age } } catch { print("Error updating user: \(error)") } } func deleteUser(_ user: User) { do { try realm.write { realm.delete(user) } } catch { print("Error deleting user: \(error)") } } }

UserRowView.swift

import SwiftUI struct UserRowView: View { let user: User var body: some View { HStack { if !user.isInvalidated { // Add this check VStack(alignment: .leading) { Text(user.name) .font(.headline) Text("Age: \(user.age)") .font(.subheadline) } } } } }

UserListView.swift

import SwiftUI struct UserListView: View { @ObservedObject private var userManager = RealmDatabaseManager() var body: some View { NavigationView { List { ForEach(userManager.users) { user in if !user.isInvalidated { // Add this check NavigationLink(destination: UserDetailView(user: user, userManager: userManager)) { UserRowView(user: user) } } } } .navigationTitle("Users") .toolbar { ToolbarItem(placement: .navigationBarTrailing) { Button(action: addUser) { Image(systemName: "plus") } } } } } private func addUser() { let newUser = User() newUser.name = "New User" newUser.age = 0 userManager.addUser(newUser) } }

UserDetailView.swift

import SwiftUI struct UserDetailView: View { let user: User let userManager: RealmDatabaseManager @State private var name: String = "" @State private var age: String = "" @Environment(\.presentationMode) var presentationMode var body: some View { Form { Section { TextField("Name", text: $name) TextField("Age", text: $age) .keyboardType(.numberPad) } Section { Button("Save") { if let ageInt = Int(age), !user.isInvalidated { userManager.updateUser(user, withName: name, age: ageInt) } presentationMode.wrappedValue.dismiss() } Button("Delete") { if !user.isInvalidated { userManager.deleteUser(user) presentationMode.wrappedValue.dismiss() } } .foregroundColor(.red) } } .onAppear { if !user.isInvalidated { name = user.name age = String(user.age) } else { presentationMode.wrappedValue.dismiss() } } .navigationBarTitle(user.isInvalidated ? "" : user.name, displayMode: .inline) } }

App.swift

import SwiftUI @main struct SomeTestApp: App { var body: some Scene { WindowGroup { UserListView() } } }

I am starting to give up on Realm. Working with it with no backtrace on the error is becoming very frustrating.
This code above, by simply adding it in a new swift/swiftui project, will compile and replicate the issue. (do not forget to add the realmswift repo in the package depedency)

Do you have any recommendations to improve that code not to make it crash?
I checked the other existing topics, with for example using thaw. should I add the thaw to the delete function in the realm manager? it seems weird, why wouldn’t it be by default instead of crashing?

Edit 1: I tried by changing my deleteUser function to the following

func deleteUser(_ user: User) { guard let thawedItem = user.thaw() else { return } if thawedItem.isInvalidated == false { //ensure it's a valid item let thawedRealm = thawedItem.realm! //get the realm it belongs to do { try thawedRealm.write { thawedRealm.delete(thawedItem) } } catch { print("Error deleting user: \(error)") } } }

still crashing

What does TestFlight say?

@Oleg_Gorbatchev Apple TestFlight will literally tell you at what point it’s crashing, connect it to your app and run your app on an iPhone or iPad, or in the emulator and post the TestFlight logs.

It would then make it a lot faster to look through the code and see what we can change up.

I am not sure what you are asking me here. Are you saying I have to deploy a test flight to be able to access a decent crash log?
This is happening in a demo project with no other code than the one above.
I already shared the entire code that makes it crash above. if you copy and paste all of it into a project, it compiles.

To be honest? Yes. Or Crashalytics etc but TestFlight is ridiculously incredible at literally saying the exact point your app crashes, and even the exact process that crashed it. As well as it will tell you other environment factors that are causing it to crash.

Take out the USER and just do system, and see if something changes. If the same error comes out with system.

So what’s happening, is Realm is not letting go of this User object, because it’s still being referenced somewhere even after being deleted via a Var, and for the life of me I’m not catching it.

Where are you deleting the user object?

In UserDetailView.swift

Button("Delete") { if !user.isInvalidated { userManager.deleteUser(user) presentationMode.wrappedValue.dismiss() } } .foregroundColor(.red)

In RealmDatabaseManager.swift

func deleteUser(_ user: User) { do { try realm.write { realm.delete(user) } } catch { print("Error deleting user: \(error)") } }

After this deletion where then is it being referenced? Because that’s what causing your error.

it is referenced in UserDetailView.swift and in the associated row UserRowView.swift

I’m getting a bit confused, I apologize, just one of these are causing your error. We figure out which it is, which I’d recommend changing out one, see if the error persists, then do the same for the other. If it still does it for both, remove both and see if the error is still there.

But Realm isn’t liking it being referenced after it’s being deleted.

Well, this is kinda sad for a big framework like this not to have an easy way to delete an object without crashing half the app.
The solution I ended up using now is to:

  • add a @objc dynamic var isDeleted = false to User
  • change my deleteUser function to do user.isDeleted = true
  • change the results with an added filter : realm.objects(User.self).filter("isDeleted == false")

then at each app launch, I will clear the database from all objects that have isDeleted set to true

that’s it
I shouldn’t have to do something like that, but I guess that is what it comes down to

Honestly I do have to agree with you, but I do apologize that’s what it ends up having to be.

I’m going to look at reproducing this in the future and see if there’s possibly maybe simpler ways to do this, as this can be replicated in Flutter and React.Native SDKs as well. I’ll post some findings when I get around to them.

I took a quick look a the question and here’s a possibility:

@Brock

When you’re getting the users from Realm, instead of returning a Realm Collection (List or Results), they are instead being returned as a Swift Array

There are two issues with that, the first is Results are lazily loaded so casting them to an Array could have an impact on memory with large datasets.

The more important thing is that deleting a user from Realm does not also remove that index from the Array. Looking at the deleteUser function in the RealmDatabaseManager, I am not seeing anything being done with the array when a user is removed.

In other words if Realm has three users

user_0
user_1
user_2

which is a .count of 3, and user_1 is deleted, the collection would have a count of 2 - so all is good.

However, the Array that contained those elements would still have a count of 3 - causing a crash as the iterator is attempting to access an object that no longer exists.

Two possible fixes:

1 - When deleting the object from Realm, also delete it from the Array

or preferably

2 - return Realm Results object instead of the array - the Results object always reflects the status of the underlying data.

I could be incorrect here but in our experience, casting Realm collections to Arrays is always a red flag for us.

@Jay THANK YOU!!!

THAT is what I was missing! I completely spaced on the array, I kept trying to figure out why it wasn’t zeroing out the object, which I know causes that error. I didn’t think about the array referencing it!

Now that makes sense! Thank you Jay! I didn’t even think about the array.

Closed on Apr 6, 2023

This topic was automatically closed 5 days after the last reply. New replies are no longer allowed.