Can't save or load objects - swift

I am new to Realm and its the first time I am using it. I followed every step from the guide and its inserted in my project just fine. I created a model and a function to insert the object into the realm database.
Somehow I keep getting errors. Here is what I do.
my function
do {
let realm = try Realm()
let proposition = Proposition()
proposition.name = (currentProposition.name)
proposition.energyType = (currentProposition.energyType)
proposition.lifetime = (currentProposition.lifetime)
proposition.saving = (currentProposition.saving)
proposition.investing = (currentProposition.investing)
if let _ = propositionsArray.indexOf(proposition) {
try! realm.write {
realm.delete(proposition)
loadPropositions()
}
} else {
try! realm.write {
realm.add(proposition)
loadPropositions()
}
}
} catch let error as NSError {
print("Add proposition error \(error)")
}
Here is my model
import RealmSwift
import Foundation
class Proposition : Object {
dynamic var name: String = ""
dynamic var energyType: String = ""
dynamic var lifetime = 0
dynamic var saving = 0
dynamic var investing = 0
}
Somehow I keep getting the following error
Can someone tell me what I am doing wrong?

The errors you're seeing indicate that the data model defined by your application does not match the data model of the Realm you're opening. This is usually due to changing your data model. In this case, the errors mention that you've added the lifetime, saving, and investing properties, and changed name and energyType to be non-nullable.
There are two ways to accommodate changes to your data model:
If you're in early development and don't need to support your old data model, you can simply remove the Realm files and start over with empty data.
You can perform a migration to have Realm update the data model of the Realm file. See the Migrations section of the Realm documentation for information about how to perform a migration.

Related

Swift & Core Data unwrapping fetchedresults

I'm pretty new to iOS and trying to understand how to work with Core Data. The basic challenge I struggle with is accessing the data from a fetchResult outside of a view, as if it were in an array or a database query result (which I thought this was)
Example:
Core Data Entity Foo has a 1:1 relationship with Core Data Entity Bar. I create a new Foo that needs to attach to an existing Bar.
I do a fetch<Bar> with a predicate that returns a fetchedResult object. I need to get access to the Bar object inside the fetchedResult object so that I can assign it:
newFoo.bar = fetched<Bar> doesn't work, and I can't figure out how to get to the Bar - I've spent probably 10 hours going through tutorials and Apple docs and can't find any way to unwrap it or access it - convert it or access it as if it were just a simple array of objects.
At this point I'm about to give up - I'm creating duplicate classes for the entities and when the app inits I load ALL of the Core Data from every entity and map it to Arrays that can be easily accessed, then just update Core Data with changes. That can't be how this is supposed to work.
What am I missing? This should be easy.
EDIT===========================
Added a simplified code section:
import Foundation
import CoreData
struct Create: View {
#Environment(\.managedObjectContext) var viewContext
#EnvironmentObject var show: ViewLogic
func newOnDemand() {
let newT = T(context: viewContext)
let newS = S(context: viewContext)
let existingPlace = Place.idRequest(id: placeId) <-- returns fetchedResults<Place>, I need Place or [Place]
newT.place = existingPlace <--- Place object required here. HOW?
newT.S = newS <--- New Objects. Simple.
do {
// print("Saving session...")
try viewContext.save()
} catch let error as NSError {
print("Failed to save session data! \(error): \(error.userInfo)")
}
=========================== fetch
class Place:
static func idRequest(id: UUID) -> NSFetchRequest<Place> {
let request: NSFetchRequest<Place> = Place.fetchRequest()
request.predicate = NSPredicate(format: "id == %#", id)
request.sortDescriptors = [NSSortDescriptor(key: "sortOrder", ascending: true)]
request.fetchLimit = 1 // use to specify specific queries. Position.
return request
=========================== entity class
import Foundation
import CoreData
extension T {
#nonobjc public class func fetchRequest() -> NSFetchRequest<T> {
return NSFetchRequest<T>(entityName: "T")
}
#NSManaged public var id: UUID
#NSManaged public var place: Place?
}
According to the code the method idRequest returns a fetch request, not any fetchresults. You have to call fetch on the managed object context passing the request to fetch the data
let request = Place.idRequest(id: placeId)
do {
if let existingPlace = try viewContext.fetch(request).first {
newT.place = existingPlace
// do other things
}
} catch {
print(error)
}

Firebase's ref().child(stringPath: String) returning the entire top level collection

I'm trying to retrieve a specific child of my Firebase database using swiftUI. To do that I use the simple expression
func addListeners() {
let database = Database.database(url: "https://someUrl")
let ref = database.reference(withPath: "users")
let currentUserId = "u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2"
let drivingTowardsRef = database.reference(withPath: "users/\(currentUserId)/drivingTowardsUsers")
print("Loading data from \(drivingTowardsRef)")
//THIS RIGHT HERE IS CAUSING THE PROBLEM
ref.observe(.childAdded) { snapshot in
print("Got TOP LEVEL data for user \(snapshot.key): \(String(describing: snapshot.value))")
}
//---------------------------------------
drivingTowardsRef.observe(.childAdded) { snapshot in
ref.child(snapshot.key).getData { (error, userSnapshot) in
if let error = error {
print(error)
} else {
print("Got arriving user data \(snapshot.key): \(String(describing: userSnapshot.value))")
}
}
}
}
The function will just return the entire database data
EDIT: The function returns the data from the first observer ref top level in this case users/ which in my case has two elements: niixi6iORjNn8gWq6tKvSi3Bxfc2, u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2
Got arriving user data niixi6iORjNn8gWq6tKvSi3Bxfc2: Optional({
niixi6iORjNn8gWq6tKvSi3Bxfc2 = {
aproxTime = 0;
distance = 0;
latitude = "37.33070704";
longitude = "-122.03039943";
parkingMode = searching;
userId = niixi6iORjNn8gWq6tKvSi3Bxfc2;
username = testeroNumero;
};
u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2 = {
aproxTime = 0;
distance = 0;
drivingTowardsUsers = {
niixi6iORjNn8gWq6tKvSi3Bxfc2 = {
approxTime = 0;
distance = "560.1447571016249";
};
};
latitude = "37.32984184";
longitude = "-122.02018095";
parkingMode = offering;
userId = u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2;
username = cleoBadu;
};
The key for the child path I pass him seems to be correct but it's still returning the entire top level collection instead of the single item...
EDIT: The problem seems to be on the first observer which messes up the .getData() of the ref.child(snapshot.key). Is that even possible?
Just commenting out that ref.observe(.childAdded) will automatically make the second ref.child(snapshot.key) behave totally normally
What am I missing?
I could get the entire database as a single mega dictionary and then get the child I want from there but it doesn't seem really conventional, especially when google's library offers the possibility to not do that.
EDIT: I added a printing statement that prints the url of the database ref. If I then type in the url on my browser, it redirects me on the FRT database and landing me on the correct object. So the url it's generating is correct and works perfectly fine.
Still the object returned by the getData() is the entire db
SN: I removed all codable structs as that is not the problem, so the question is more focused on the actual problem
EDIT: Created a simple view as that. On a clean project it works on my project it doesn't. I guess it's some sort of configuration but's it's hard to look into it.
PROBLEM: Whatever child(string) I pass him it returns the entire top level data either way (replacing so snapshot.key). For example: I pass the key "something" -> all users are returned, I pass the key "" all users are returned
I just tried to reproduce the problem with (mostly) your code and data, but am not getting the same behavior.
I put the equivalent data into a database of mine at: https://stackoverflow.firebaseio.com/68956236.json?print=pretty
And used this code in Xcode 1.2 with Firebase SDK version 8.6.1:
let ref: DatabaseReference = Database.database().reference().child("68956236")
let currentUserId: String = "u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2"
let drivingTowardsRef: DatabaseReference! = ref.child("\(currentUserId)/drivingTowardsUsers");
print("Loading data from \(drivingTowardsRef)")
drivingTowardsRef.observe(.childAdded) { snapshot in
ref.child(snapshot.key).getData { (error, userSnapshot) in
if let error = error {
print(error)
} else {
do {
//let parkingUser = try userSnapshot.data(as: ParkingUser.self)
print("Got data for user \(snapshot.key): \(String(describing: userSnapshot.value))")
} catch {
print("There has been an error while decoding the user location data with uid \(snapshot.key), the object to be decoded was \(userSnapshot). The decode failed with error: \(error)")
}
}
}
}
The output I get is:
Loading data from Optional(https://stackoverflow.firebaseio.com/68956236/u3Ebr6M3BAbP7PBSYYJ7q9kEe1l2/drivingTowardsUsers)
2021-08-27 10:39:09.578043-0700 Firebase10[36407:3458780] [] nw_protocol_get_quic_image_block_invoke dlopen libquic failed
Got data for user niixi6iORjNn8gWq6tKvSi3Bxfc2: Optional({
aproxTime = 0;
distance = 0;
latitude = "37.32798355";
longitude = "-122.01982712";
parkingMode = searching;
userId = niixi6iORjNn8gWq6tKvSi3Bxfc2;
username = testeroNumero;
})
As far as I can see this behavior is correct, but different from what you get. I hope knowing that I don't see the same behavior, and what versions I use, may be helpful to you.
This is not an issue with Firebase but rather client-side handling of the data returned, You’re expecting a Double within your Codable struct but supplying a String in the other end— Can you try:
public struct ParkingUser: Codable {
var latitude: String
var longitude: String
}

Core Data Object was written to, but never read

As I try to update an existing entry in my Core Data DB, I fetch the desired item by id, change it to a new item and save in context.
However, when I fetch the object and replace it, I get the warning "Core Data Object was written to, but never read." It does make sense since I'm not really using that object, but as I understand it, just giving it a value saves it in Core Data.
static var current: User? {
didSet {
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
CoreDataManager.saveInContext()
}
}
static func get(with id: String?) -> User? {
guard let id = id else { return nil }
let request: NSFetchRequest = User.fetchRequest()
let predicate = NSPredicate(format: "id = %#", id)
request.predicate = predicate
do {
let users = try CoreDataManager.managedContext.fetch(request)
return users.first
} catch let error {
print(error.localizedDescription)
return nil
}
}
I want to make sure, is this the recommended process to overwrite a value in Core Data, or am I doing something wrong?
This section
if var userInCoreData = User.get(with: current?.id), let current = current { //userInCoreData is the value with the warning
userInCoreData = current
}
seems just updating local variable userInCoreData, not User object in Core Data.
So the warning says "you fetched data from core data and set to a variable, but you set another value to the variable soon, never use the first value from core data. Is it OK?"
What you really want to do is something like this?
if var userInCoreData = User.get(with: current?.id), let current = current {
userInCoreData.someValue = current.someValue
userInCoreData.anotherValue = current.anotherValue
}

Object has been deleted or invalidated realm

I have this class inherit from Object:
class Location: Object {
dynamic var id: String = ""
dynamic var name: String = ""
override class func primaryKey() -> String {
return "id"
}
}
This class is used as an instance inside my manager like this:
class LocationServiceAPI {
fileprivate var _location: Location?
var location: Location? {
get {
if _location == nil {
let realm = try! Realm()
_location = realm.objects(Location.self).first
}
return _location
}
set {
let realm = try! Realm()
if let newValue = newValue {
// delete previous locations
let locations = realm.objects(Location.self)
try! realm.write {
realm.delete(locations)
}
// store new location
try! realm.write {
realm.add(newValue, update: true)
_location = newValue
}
} else {
let locations = realm.objects(Location.self)
try! realm.write {
realm.delete(locations)
}
}
}
}
}
So whenever I get a location I delete the old one (new and old locations could be identical) and replace it with the new one, then I used the newValue as new value for the property _location but whenever I try to access the location it gives me 'Object has been deleted or invalidated'.
I am really confused since location will hold the value passed from the setter but not the realm!!
Note: If I stop the deleting then It will work fine.
The Object has been deleted or invalidated error will occur if an object has been deleted from a Realm, but you subsequently try and access a stored property of an instance of that object that your code was hanging onto since before the deletion.
You'll need to examine your logic paths and make sure there's no way you're deleting the location object, and not subsequently updating the _location property. There's no mention of deleting the object in the sample code you've provided, but your if let newValue = newValue line of code would mean that _location wouldn't actually get cleared if you passed in nil.
Finally, it's possible to manually check if an object has been deleted from a Realm by calling _location.invalidated, so if this happens a lot, it might be a good idea to include some extra checks in your code as well.
Without knowing really anything about your app and your design choices, it looks like you're trying to avoid reading/writing to the DB too often by caching the location property. Unless you're working with tons of LocationServiceAPI objects it shouldn't be a real performance penalty to actually read/write directly in the DB, like this :
class LocationServiceAPI {
var location: Location? {
get {
let realm = try! Realm()
return realm.objects(Location.self).first
}
set {
let realm = try! Realm()
if let newValue = newValue {
// store new location
try! realm.write {
realm.add(newValue, update: true)
}
} else {
// delete the record from Realm
...
}
}
}
}
Also, I would in general avoid keeping Realm objects along for longer periods, I don't say it's not possible but in general it leads to issues like you've experienced (especially if do multi-threading). In most cases I'd rather fetch the object from DB, use it, change it and save it back in the DB asap. If keeping references to specific records in the DB is necessary I'd rather keep the id and re-fetch it when I need it.

How to improve performance for large datasets with Realm?

My database has 500,000 records. The tables don't have a primary key because Realm doesn't support compound primary keys. I fetch data in background thread, then I want to display it in the UI on the main thread. But since Realm objects cannot be shared across threads I cannot use the record I fetched in the background. Instead I need to refetch the record on main thread? If I fetch a record out of the 500,000 records it will block the main thread. I don't know how to deal with it. I use Realm because it said it's enough quick. If I need refetch the record many times, is it really faster than SQLite? I don't want to create another property that combine other columns as primary key because the Realm database is already bigger than a SQLite file.
#objc class CKPhraseModel: CKBaseHMMModel{
dynamic var pinyin :String!
dynamic var phrase :String = ""
class func fetchObjects(apinyin :String) -> Results<CKPhraseModel> {
let realm = Realm.createDefaultRealm()
let fetchString = generateQueryString(apinyin)
let phrases = realm.objects(self).filter(fetchString).sorted("frequency", ascending: false)
return phrases
}
func save(needTransition :Bool = true) {
if let realm = realm {
try! realm.write(needTransition) {[unowned self] in
self.frequency += 1
}
}
else {
let realm = Realm.createDefaultRealm()
if let model = self.dynamicType.fetchObjects(pinyin).filter("phrase == %#", phrase).first {
try! realm.write(needTransition) {[unowned self] in
model.frequency += self.frequency
}
}
else {
try! realm.write(needTransition) {[unowned self] in
realm.add(self)
}
}
}
}
}
then I store fetched records in Array
let userInput = "input somthing"
let phraseList = CKPhraseModel().fetchObjects(userInput)
for (_,phraseModel) in phraseList.enumerate() {
candidates.append(phraseModel)
}
Then I want to display candidates information in UI when the user clicks one of these. I will call CKPhraseModel's save function to save changes. This step is on main thread.
Realm is fast if you use its lazy loading capability, which means that you create a filter that would return your candidates directly from the Realm, because then you'd need to only retrieve only the elements you index in the results.
In your case, you copy ALL elements out. That's kinda slow, which is why you end up freezing.