RLMArray object not stored with its object - ios

I've a very weird problem in RealmSwift.
I've the following property in a realm object class called Device.
class Device: Object {
....
dynamic var name: String = ""
var services: List<Service> = List<Service>()
}
The issue is that when trying to fill this list and save the Device object, the service list is not saved.
While debugging I used the following to test
print(device)
Which prints the objects without any service object.
and
print(device.services)
Which prints all services objects.
I know it's weird, but I cannot save the object with its list object, although I can save any normal property in the device object like name property.
Any idea what is happening here?

What you are describing could happen if you are directly assigning to the services property. This is not supported, and List properties should always be declared as let.

Related

Realm doesn't always return an object for its primary key from the cloud, although it does exist there

I am quite new to Realm and now I'm facing the issue, mentioned in the title. Basically, one of my Realms is a User class (this is different from Users that is created when new users log in) in Realm Cloud. The class has a userId property, which is set as its primary key. Now, I try to fetch users for a particular primary key with the following line:
let user = realm?.object(ofType: User.self, forPrimaryKey: "f677ca48b17bc7b90a886dd5d50148c1")
I do see that there is a user in the cloud with this primary key, but he is not always returned. I mean, say, if I run the same code 10 times, I will get the user only 4 times out of 10 (roughly). The above code is run from a method, which is always called when the controller is on screen. Also, there is no condition checking in the method for that part of code, so it is always called when the method is called.
I've also checked the realm property and it is not nil, so I don't quite understand why this happens.
The same happens when I try to get the current user by the line:
currentUser = realm?.object(ofType: User.self, forPrimaryKey: SyncUser.current?.identity)
The identity value is not nil (I print it before calling the code).
UPDATED
Here is how the User looks like:
class User: Object {
#objc dynamic var userId: String = ""
#objc dynamic var name: String = ""
override static func primaryKey() -> String? {
return "userId"
}
}
Maybe, I am missing something or that is a problem/bug on the cloud side, I don't know. If you've encountered such an issue and/or know what could cause it, I would greatly appreciate your help. Thanks in advance.
This is caused because of the race condition that could pops up when you try to access a reference that is not on the main thread & being worked on by other thread .
therefore it will return nil each most of the time when you try to call it from the main thread because Realm had no chance to write or read from yet as you are accessing it directly .
What to do:
Make sure that your properties in the User object are marked with dynamic .
properties of your Object subclasses need to be declared with the dynamic modifier to allow Realm to override the getter and setter of the property.
Without this the Swift compiler will access the object's instance
variable directly, which doesn't provide any opportunity for Realm to
read or write data from the Realm file.
Also please read this answer .

Accessing all class property values dynamically

So basically what I'm trying to do is list all properties of an object in a tableview in a key = value format.
But I want this to be independent of future changes to the object, so that if a new property is added later, no changes will be needed to my code.
So far I can access the properties via Mirror, but I run into problems when I'm trying to access the properties via value(forKey:), even though the class is inheriting NSObject it crashes with:
this class is not key value coding-compliant for the key
Some properties work while others don't, which I'm guessing is down to some of them being private and others #objc variables?
So is there any way to pre-validate that a key (property) can be accessed via value(forKey:) - so it doesn't end in a crash, so I if nothing else can show the values of the accessable properties?
Better yet, is there another way of accessing all properties and values of a given object in a dynamic way? (handling later additions of properties)
Code snippet:
let properties = Mirror(reflecting: currentUser).children.compactMap { $0.label }
if properties.count > 0 {
for property in properties {
if let test = currentUser[property] {
newData.append( (name: property, value: currentUser.value( forKey: property ).debugDescription) )
}
}
}
What you might be able to do is make your object conform to Encodable. That way, you could simply convert it into JSON (or some other format), then make your table view dynamically pull out all the keys and values from the JSON.
As long as any future changes to the object don’t break Encodable compliance, the table view will still be able to parse whatever JSON it receives.

Realm inverse relationship returns object with nil properties

I got a class named Team, it has a RLMArray property called players with Player class objects.
When i try to access the team by calling (according to documentation)
[self linkingObjectsOfClass:#"Team" forProperty:#"players"];
on a player object, i get a single team object ( how it's supposed to be ), but all the properties are nil, even primary key.
Has anyone faced the same issue?
It's expected that instance variables of persisted RLMObject instances will be nil as the property getters read values directly from the Realm file. The instance variables are only used for objects prior to being saved to the Realm, and remain nil after that point.
The Debugging section of the Realm documentation touches on this topic and mentions an LLDB script that can be used to show property values of persisted objects when debugging in Xcode. The -description method on the model classes, used by NSLog when formatting objects using the %# format specifier, will also show the property values as expected.

How to define a Fetched Property in an NSManagedObject Subclass

In a project I am working on, we have multiple persistent stores and fetched properties are defined on the Entities to provide access to the objects that live in the different stores.
When I run Editor -> Create NSManagedObject Subclass, the fetched properties do not get populated in the subclass, and therefore are not accessible in the different controllers that use this Entity.
My curiosity is how to define these objects in the subclass so that they can be used.
For example imagine I have some object below called "Some Object", and this object has a fetched property on it called "imageFile" (The File object lives in a different store so cannot be referenced directly)
class SomeObject: NSManagedObject {
#NSManaged var name: String
#NSManaged var id: String
#NSManaged var imageID: String
#NSManaged var imageFile: File //Not generated automatically like the rest
}
Unfortunately the above attempt fails with the following error:
unrecognized selector sent to instance 0x60800865de50
So my question is a nutshell, is how do you access Fetched Properties, or what is the syntax to reference them.
Please no answers saying "Don't use Fetched Properties" or "Use only one persistent store". I already know how to use normal relationships and want to know how to utilize this feature of Core Data. Thanks in advance!
UPDATE
Trying some of the solution's posted below I ran into some interesting information that may help. I printed out the object using "po someObject" and was suprised to see the following in the output under the data attribute:
imageFile = "<relationship fault: 0x618000043930 'imageFile'>";
imageID = "some Id"
However when trying to access imageFile using someObject.imageFile I am unable to access it. Using the valueForKey["imageID"] I am able to get a reference, but it fails on the cast to File every time. When printing the object out I get:
Optional(Relationship fault for (<NSFetchedPropertyDescription: 0x6180000e1780>), name imageFile, isOptional 1, isTransient 1, entity SomeObject...
Final Update
the valueForKey["imageID"] will trigger the fault and fetch the property, I had the attributes flipped in my xcdatamodelid file, and thats why it wasn't finding it at first.
In Objective-C you could define a #dynamic property file in a
category on SomeObject, but something similar does not exist in Swift
(as far as I know).
So the only possibility is to use Key-Value coding to retrieve the
fetched property (which is always represented as an array):
if let files = yourObject.valueForKey("imageFile") as? [File] {
// ...
}
Of course you can wrap this into a computed property as suggested
in #gutenmorgenuhu's answer.
If you want to add this to the same class as the NSManagedObject, you can use the extensions feature:
extension SomeObject{
var imageFile: String {
get {// Code to return your fetchedProperty
}
}
}

swift NSManagedObject subclass initialization issue

I'm making a new app and I deciced to try to develop it in swift.
I'm using CoreData and I've got a strange behavior I guess.
I'm creating a new Item (NSManagedObject subclass) like this :
let managedObjectContext: NSManagedObjectContext = (UIApplication.sharedApplication().delegate as AppDelegate).managedObjectContext!
self.newItem = NSEntityDescription.insertNewObjectForEntityForName("Item", inManagedObjectContext: managedObjectContext) as Item
self.newItem.setValue(NSDate(), forKey: "startDate")
when I print the startDate, it is always nil. It is the same with the item's properties (link to other objects).
In the debugger, it seems that the object (newItem) is well instanciated (I see a (myapp.Item!) as the object type), but it seems that the debugger is not really user friendly with CoreData. I found no way of inspecting or printing all the object's properties like it was the case in objectiveC.
Is there something I'm missing here? The instanciation seems quite correct to me no?
Thanks.
The fact that you see the object type in the debugger does not mean the object is correctly initialized. It most likely just shows you the type you declared in your class when defining self.newItem.
I agree, the Swift debugger has a lot to be desired. You cannot rely on it to debug this situation, unless you want to learn more advanced debugger commands, but the typing is just as much if you insert log statements into your code instead.
Check and confirm that
your managed object context is not nil
your insert method indeed creates a proper Item object which is not nil
your managed object subclass for the Item entity is properly configured
Maybe you want to try to first create a new local Item variable before assigning it to self.newItem to eliminate any ivar errors.
Try to see if anything changes if you save the context.
Try to get used to accessing the attribute with the subclass properties, such as newItem.startDate rather than relying on the error-prone valueForKey method.

Resources