Realm query Object property field by its property - ios

I'm developing an application for iOS using Swift and chose Realm as a database solution for it. I asked one question about Realm and now I have another.
Suppose we have a schema like this:
class Person: Object {
dynamic var id: String = NSUUID().UUIDString
dynamic var name: String?
dynamic var cars: Car?
class Car: Object {
dynamic var name: String?
I have one class (Person) that contains any number of objects of another class (Car). Car that are "linked" with the Person has some properties in context of that Person (and they can be different for same Car for different Persons or for two similar Cars for one Person). Using List<...>() we can not store such properties for each Item, am I right?
If we use Car only for one Person and only once we can create another class that includes only additional properties for Cars and links them with ID of Person plus ID of Car. But it does't work if we have two similar Cars with different additional properties.
So, how I see the solution. Have one table (class) stores ID of Person, ID of one Car and additional properties for this Car. For another Car for the same Person it has the same Person ID, Car ID (same or not) and another additional properties for this instance of a Car.
There is a problem (and a question that I mean). Having that structure I want to query all Cars from that table with their additional properties that have Person ID equals to some_id. How should I do this? Or maybe what another structure (maybe using List<...>) I should have to achieve such kind of behavior?

What is FastList exactly ?
If you want Items to have a property of Lists collection.
You have to redefine your Realm model. something like this.
class Car:Object{
dynamic var createDate: NSDate = NSDate()
}
class Person:Object{
let cars = List<Car>()
}
and query by predicate like this
let realm = Realm()
var ownedCarsFilterByDate = realm.objects(Person).filter("ANY cars.createDate = '\(date)'")
Edited to updated question
Your solution is to create table class, which has 'Person' , 'Car' and 'Context Attribute'.
Your model would be like this
class PersonAndCarRelation:Object{
dynamic var person: Person?
dynamic var car: Car?
dynamic var contextAttribute = ""
}
and you can query all cars associated with person
let personID = "123456789"
let personAndCarArray = realm.objects(PersonAndCarRelation).filter("person.id == \(personID)")
for personAndCar in personAndCarArray{
let personName = personAndCar.person.name
let carName = personAndCar.car.name
let context = personAndCar.contextAttribute
println("I am \(personName). I have a \(carName) with \(context)")
}

Related

Swift Realm - get types of all database objects

I have a list of various objects in my Realm Database all of which are created as default ClassName: Object classes. Is there any way to get types (classes names) of those objects, that can be saved as variables, or create an enum of these types?
The issue is that Realm Results objects are homogenous - they only contain one object type. That translates to meaning you will never have a situation where Results would contain Person and Dog classes at the same time.
That being said we have the option of using the Realm AnyRealmValue
Let me set this up: Suppose you have a PersonClass and a DogClass
let dog = DogClass()
let person = PersonClass()
and a class with a List that can contain AnyRealmValue
class MyClass: Object {
#Persisted var myList = List<AnyRealmValue>()
}
when then need to cast those objects to AnyRealmValue to make this work
let obj0: AnyRealmValue = .object(dog)
let obj1: AnyRealmValue = .object(person)
and we add those to the List
let m = MyClass()
m.myList.append(obj0)
m.myList.append(obj1)
You mentioned switch but here's a simple if...then clause to handle them differently - depending on the class
if let person = item.object(PersonClass.self) {
print("is a person")
} else if let dog = item.object(DogClass.self) {
print("is a dog")
}

How Query and Count entries in a Realm Database

I'd like to build a search on which the user can filter down the results step-by-step. So with no choice set, there is a button which says e.g. "1,234,567 Results" and if you choose a color for example the results set shrinks... we all know this kind of search. I did build it many times, but this is the first time in Realm (and swift).
Lets Say I have 5 Persons in my Person Table, then there are about 145,224 Dog entries per Person and about 2,507,327 Cat entries per Dog. How do I query and Count nested Objects in Realm?
class Person: Object {
#objc dynamic var name = ""
let dogs = List<Dog>()
// ...other Properties
}
extension Person {
static func all(in realm: Realm = try! Realm()) -> Results<Person> {
return realm.objects(Person.self)
}
}
// counts -> 145,224 db entries per person
class Dog: Object {
#objc dynamic var name = ""
dynamic var Person: Person?
let cats = List<Cats>()
// ...other Properties as well
}
extension Dog {
static func all(in realm: Realm = try! Realm()) -> Results<Dog> {
return realm.objects(Dog.self)
}
}
// counts -> 2,507,327 db entries per dogs
class Cat: Object {
#objc dynamic var name = ""
dynamic var Cat: Cat?
}
extension Cat {
static func all(in realm: Realm = try! Realm()) -> Results<Cat> {
return realm.objects(Cat.self)
}
}
// Get the default Realm
let realm = try! Realm()
// Query Realm for all dogs
let dogs = Person.all(in: realm).flatMap { $0.dogs }
dogs.count // => takes ~20 seconds to count
In other words, what is the fastest way to get (count) all Dog entries of all Persons (let the cats by side for now).
I tried to workaround the problem by limit the results to 1000. If the results are >1000, then label the button like so "> 1000 Results". But even than it takes very long (I guess the get all count anyway).
So what did I do wrong?
They way you were computing the count required all Dog objects to be loaded into memory which is really inefficient. This is why you were seeing such poor performance. You want to take advantage of Realm's lazy loading features. You may want to read up on that.
I would update your Dog object by getting rid of your managed Person property and replace it with LinkingObjects. If you store a Dog in a Person.dogs List, then realm will create a back link to the Person for you. You will likely want to do the same thing with Cat. That way you can set up some really powerful nested queries.
For convenience you can add a computed Person property to index into the LinkingObjects. Just know that you won't be able to use that property in any of your queries.
class Dog: Object {
#objc dynamic var name = ""
let persons = LinkingObjects(fromType: Person.self, property: "dogs")
var person: Person? { persons.first }
}
One way to compute the count is to query of all Person objects and sum the count of dogs for each Person.
let count = realm.objects(Person.self).reduce(0) { result, person in
return result + person.dogs.count
}
Another options is to query for Dog objects that have a corresponding Person. If a Dog is not in a Person.dogs List, then it won't show up the query.
let realm = try! Realm()
let count = realm.objects(Dog.self).filter("persons.#count > 0").count
Either option is going to be much, much more efficient than what you had.
Hope this helps.

iOS (Swift): Core data fetching with relationships

If I have a Person entity and a Book entity where a Person can have many Books.
final class Person: NSManagedObject {
#NSManaged public fileprivate(set) var name: String
#NSManaged public fileprivate(set) var books: Set<Book>
}
final class Book: NSManagedObject {
#NSManaged public fileprivate(set) var name: String
#NSManaged public fileprivate(set) var person: Person
static func add(bookNamed name: String, to person: Person) {
guard let context = person.managedObjectContext else { fatalError("Can not obtain managed object Context") }
let book = NSEntityDescription.insertNewObject(forEntityName: "Book", into: context) as Book
book.name = name
book.person = person
}
}
In some UIViewController, I then add a few Books to a Person:
let alex = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: context) as Person
Alex.name = "Alex"
Book.add("first book", to: alex)
Book.add("second book", to: alex)
Book.add("third book", to: alex)
and then a couple of Books to another Person
let john = NSEntityDescription.insertNewObjectForEntityForName("Person", inManagedObjectContext: context) as Person
john.name = "John"
Book.add("another first book", to: john)
Book.add("another second book", to: john)
I then reload my app and use a fetch request to obtain all the people (i.e. alex and john) in a table view. I then click on alex, which takes me to another view controller, which has an instance of Person that I assign as alex so that I can view the books associated to this instance.
Question Are the Books all loaded into memory even if I have 1000s? Or do I need to perform another fetch to get the Books belonging to alex if I want to display them all? I just want a bit of clarity on what is happening with relationships between two entities as it's slightly confused me recently.
Thanks for any help.
CoreData uses a proxy object model. When you execute a fetch request it transparently creates all directly accessible objects, as proxies. Since they're proxies and not the full object, they are not actually loaded from the data base at creation time, rather they will be loaded from the database when needed (when properties are actually referenced) Likewise, they can be unloaded at any time if they're unmodified and unused.
In this case, that means that your fetch request will create unloaded proxies for each Person. When you display the persons name in the first table view, the data for that Person will be loaded (and cached) in the proxy object. At that time, proxies for each Book referenced by the person will be created as an empty proxy object. When you subsequently select the book and display the details of the book, the actual Book data will be fetched (cached) from the database.
Note: all this is very dependent on the actual datastore in use and is only true of stores, such as sqlite, that allow partial loading. If using an XML or plist store the entire object graph is instantiated and loaded with no empty proxies.

Will an array of a superclass contain subclasses as well?

I created a class Person. Person contain properties like name and email. Both are from type String.
Beside the Person class, I have a subclass Student that inherited from superclass Person. Subclass Student contain properties like student number (String) and isGraduated (Boolean).
I have an empty array of persons from the class Person, like:
var persons: [Person]()
After I created both Person and Student objects inside the array persons, I read them out using a UITableView. Both models will be print in the cells. But when I want to check the value of isGraduated from the selected row, auto-completion doesn't give me the value of the property: persons.isGraduated.
The first thought of this problem is, will my persons array contain also the subclass Student? My second thought would be, that I think I should not check the value isGraduated inside the TableView. My wish of this function is that it will do something, like call the native camera if the person is graduated.
Looking forward to the solution.
Tree options here
A: Change the array type to Student
var students: [Student]()
B: Cast each element from the persons array to a Student. Ideally inside a for each person.
guard let student = person as? Student else {
return // or continue if inside a for loop
}
Then u will have access to that student variable.
C: Or if they might be students and persons at the same time in the array then
for person in persons {
switch person{
case let student as Student: //student case
//do something
default: //person case
//do something
}
}
If you want to check what is the type of your person : Person or Student you can use is keyword like this :
if persons[0] is Person {
//if a person
} else if persons[0] is Student {
//if a student
}
If you want to use a function member for your specific type, you need to downcast the type by using the keyword as like this :
if let person = persons[0] as? Person {
//person is now a Person type
} else if let student = persons[0] as? Student {
//student is now a Student type
}

Adding An Item To An NSSet For A Core Data One To Many Relationship

I have a core data relationship where one entity holds many of another entity. As far as I am aware each instance of the many class is held inside an NSSet? inside the one class. (?)
My question is - what is the best way to add items to this set? I figure this must be a very common problem - but I cannot seem to find an easy method.
This is my attempt: (This is all taken from the one class)
static var timeSlotItems: NSSet? //The Set that holds the many?
...
static func saveTimeSlot(timeSlot: TimeSlot) { //TimeSlot is the many object
retrieveValues()
var timeSlotArray = Array(self.timeSlotItems!)
timeSlotArray.append(timeSlot)
var setTimeSlotItems = Set(timeSlotArray)
self.timeSlotItems = setTimeSlotItems // This is the error line
}
Where retrieveValues() just updates all the coreData values in the class.
TimeSlot is the many object which I want to add.
I get an error on the last line, the error is: "cannot invoke initializer for type Set<_> with an argument of list of type Array"
Am I conceptually wrong at all? Thanks!
Nowadays it is this easy...
For a to-many item named say "Reply", CoreData knows to add a call "addToReplys".
Hence...
p = one Post. your core data Post items have many core data Reply items.
for jr in yourJson {
r = convert jr to a core data Reply
p.addToReplys( r )
so it's just
p.addToReplys( r )
Full example
For one-to-many this is easy. Just use the reverse to-one relationship.
timeSlot.item = self
For many-to-many I use this convenience method:
// Support adding to many-to-many relationships
extension NSManagedObject {
func addObject(value: NSManagedObject, forKey key: String) {
let items = self.mutableSetValueForKey(key)
items.addObject(value)
}
func removeObject(value: NSManagedObject, forKey key: String) {
let items = self.mutableSetValueForKey(key)
items.removeObject(value)
}
}
which is used like this:
self.addObject(slot, forKey:"timeSlotItems")
You've declared both timeSlotItems and saveTimeSlot: as static, so I'm not sure what your intention is there. I suspect it's not what you need.
In the same way that Core Data automatically runtime-generates optimized accessors for attributes, it also generates accessors for relations.
You don't say what the name of the "one" side of the to-many relation is, but if I assume that it's something like Schedule, where Schedule has a to-many relation to TimeSlot called timeSlotItems, then Core Data will runtime-generate the following accessors for you:
class Schedule: NSManagedObject {
#NSManaged public var timeSlotItems: Set<TimeSlot>
#NSManaged public func addTimeSlotItemsObject(value: TimeSlot)
#NSManaged public func removeTimeSlotItemsObject(value: TimeSlot)
#NSManaged public func addTimeSlotItems(values: Set<TimeSlot>)
#NSManaged public func removeTimeSlotItems(values: Set<TimeSlot>)
}

Resources