Understanding LinkingObjects in Realm Xcode 12, Also when to use it - swift

In Realm, I had problem understanding ( Im new in Realm T,T ) the implementations of LinkingObjects , let's say a Person could have more than one Dog ( List of Dog ) so I would write the code such as below:
Person : Object {
dynamic var name:String = ""
let dogs = List<Dog>()
}
Dog : Object {
dynamic var name: String = ""
let walkers = LinkingObjects<fromType: Person.self, property:"dogs">
}
lets say
Person A
dogs = [Alex,Cindy]
Person B
dogs = [Doggo,Kiba]
if I delete Alex using Realm.delete then it become Person A dogs = [Cindy]
this is quite straight forward.
but when I remove LinkingObjects from the class such as below:
Person : Object {
dynamic var name:String = ""
let dogs = List<Dog>()
}
Dog : Object {
dynamic var name: String = ""
}
the app still can run normally without any diff, I can still add Person 3 and append a list of Dog. Also when deleting ( Realm.delete ) there is no difference as when I'm adding LinkingObject to the Dog class. what's the difference here? When can I use LinkinObject? Can anyone please explain? or write some more understandable code?
Already read all the previous answer but still I need another explanation. Thank You So Much!!

You can think of LinkingObjects almost as a computed property - it automagically creates an inverse link to the parent object when the child object is added to the parent objects List.
So when a Dog is added to a person's dogs list, a person reference is added to the Dog's walkers list. Keeping in mind that it's a many to many relationship so technically if Person A adds Doggo, and Person B adds Doggo, the Doggo's inverse relationship 'walkers' will contain Person A and Person B
the app still can run normally without any diff
Which is true, it doesn't affect he operation of the app. HOWEVER the difference is that by removing the walkers LinkingObjects, there's no way to query Dogs for their Person and get Dog Results (i.e. you can't traverse the graph of the relationship back to the person)
In other words we can query Person for kinds of dog stuff
let results = realm.objects(Person.self).filter("ANY dogs.color == 'brown'")
which returns a results object contains Persons that have a brown dog. However, we don't know which dog they have is brown; could be one, could be three.
However, suppose you want to get a results object containing specific brown dogs and want the owners name for each - you can't really do that without an inverse relationship (LinkingObjects)
let dogResult = realm.object(Dog.self).filter("color == 'brown'")
for dog in dogResult {
let person = dog.walkers.first!.name //assume there is only one owner
print(person.name)
}
The dogResults will only contain brown dogs, and you know specifically which ones they are.
So that's a big difference; without LinkingObjects we rely on returning Person objects and then iterating or processing each to get to the data we want. With LinkingObjects we can specifically get the objects we want to work with.
It's super handy when you want to add an observer to results to watch for changes - say on Brown dogs - to be notified of changes.

Related

RealmSwift - how can I query a distinct array of objects

I have the following (simplified) model
class Taxonomy: Object {
var id: ObjectId // Primary Key
var parent: Taxonomy?
var name: String
var isSelected: Bool
var children = List<Taxonomy>()
var assets = List<Asset>()
}
class Asset: Object {
var id: String // Primary Key
var name: String
let taxonomies = LinkingObjects(fromType: Taxonomy.self, property: "assets")
}
The taxonomy can be an infinite hierarchy with each item containing zero or more children. A leaf would have no children. Assets can be classified as one or more 'leaf' taxonomies.
For example Animals->Birds->"White-faced Pigeon"
There could be multiple assets, let's say images ( but could be documents, recording, etc.) that have this classification with the references stored in Taxonomy.assets array.
If the user selected 'Birds' in the hierarchy then I want to retrieve a distinct list of all the assets from the SELECTED taxonomies and all their children's assets and the children's children etc.
In general only 'leaf' taxonomy items will contain assets - but not necessarily so since some assets may for example be as yet unclassified birds and simply associated with a higher level group until further refinement is done.
For example:
let selectedTaxonomies = realm.objects(Taxonomy.self).filter("isSelected = true")
let children = selectedTaxonomies.flatMap({$0.children})
let childrensChildren = children({$0.children})
etc... (how to traverse a hierarchy of arbitrary depth)
let assets = allSelectedTaxonomies.flatMap({$0.assets}).distinct(by: "id")
Is there any way to do this in some efficient way.
EDIT:
This seems to work but not sure it is very efficient
extension Taxonomy {
var allChildren: [Taxonomy] {
return Array(children)+Array(children.flatMap({$0.allChildren}))
}
}
and then
let selectedTaxonomies = realm.objects(Taxonomy.self).filter("isSelected == true")
let allChildren = selectedTaxonomies.flatMap({$0.allChildren})
let allTaxonomies = Array(selectedTaxonomies) + Array(allChildren)
assets = assets.filter("ANY taxonomies IN %#", allTaxonomies)
Example
Here the user could select images and drag and drop them onto one or more of the classifications on the left. So the same item could appear in more than one place. For example one of the eagle images might also appear in another hierarchy called Best Of -> Birds for example. Right now I am doing a super expensive fetch of the selected taxonomy hierarchy and am trying to establish if there is a way to build a Realm query maybe using Predicates that would be faster/more efficient.

Swift 4 multi dimensional array or struct?

I'm creating a table viewcell. this viewcell will show hotel and relative rooms .
to do that I created 2 classes to define hotel and rooms. each of 2 classes represent the relative object. example:
hotel: services, position, lat, lang , description
room: bed_type, people, Availability
I know need now to create a structure or array or whatever that act like a three so basically so basically , for each hotel I can have more than one available room.
I was looking using an 2d array of any object as first solution but I don't know If this is best way to solve the problem.
ideally best would be to later can access object in an easy way...
my first second idea is to use a struct like this
struct SearchResults{
var Hotel: HotelModel
var Rooms: [RoomModel]
}
any suggestion is welcome
Here is how you can model your data.
Just create a Hotel model that contains an array of Room as its property.
struct Hotel {
var rooms: [Room]
}
struct Room {
//your properties...
}
Now, instead of using an extra SearchResults model, you can simply use a array of Hotel, i.e.
var results: [Hotel]

Realm linking objects and deletions

In Realm, if I have a linkingOjbects relationship set-up, how should I handle deletions and not be left with orphans especially when it's a many-to-many inverse relationship?
Using the Realm Person and Dog examples, assuming that a Person in this instance is a Dog walker and that a Dog can be walked by a different Person on different days
So a Dog object is assigned to multiple Person objects. Each Person object can see the Dog. Assuming the following object structure and data
Person : Object {
dynamic var name:String = ""
let dogs = List<Dog>()
}
Dog : Object {
dynamic var name: String = ""
let walkers = LinkingObjects<fromType: Person.self, property:"dogs">
}
Person A
dogs = [Fido,Rover]
Person B
dogs = [Fido, Rover]
Person A no longer needs to walk Fido, so would the correct approach be
personA.dogs.remove(objectAtIndex:idxOfFido)
This would update the reference in personA but would it also update the reference in dog?
Secondly if personB also no longer needs to walk Fido I would do
personB.dogs.remove(objectAtIndex:idxOfFido)
but would this then leave an orphaned reference to Fido in my Dog realm as it is no longer walked by anyone? Would it then be up to me to do a check such as
if fido.walkers.count == 0 {
//remove Fido
}
1.) linking objects are the "other side of the relationship", so if you update it on one side, then the other side also updates. Removing fido from persons.dog will remove person from dog.walkers.
2.) just because a dog isn't walked by someone doesn't mean it's dead, so yes, you'd need to delete orphaned dog manually.

Querying in Firebase by child of child

I have a structure of objects in Firebase looking like this:
-KBP27k4iOTT2m873xSE
categories
Geography: true
Oceania: true
correctanswer: "Yaren (de facto)"
languages: "English"
question: "Nauru"
questiontype: "Text"
wronganswer1: "Majuro"
wronganswer2: "Mata-Utu"
wronganswer3: "Suva"
I'm trying to find objects by categories, so for instance I want all objects which has the category set to "Oceania".
I'm using Swift and I can't really seem to grasp the concept of how to query the data.
My query right now looks like this:
ref.queryEqualToValue("", childKey: "categories").queryOrderedByChild("Oceania")
Where ref is the reference to Firebase in that specific path.
However whatever I've tried I keep getting ALL data returned instead of the objects with category Oceania only.
My data is structured like this: baseurl/questions/
As you can see in the object example one question can have multiple categories added, so from what I've understood it's best to have a reference to the categories inside your objects.
I could change my structure to baseurl/questions/oceania/uniqueids/, but then I would get multiple entries covering the same data, but with different uniqueid, because the question would be present under both the categories oceania and geography.
By using the structure baseurl/questions/oceania/ and baseurl/questions/geography I could also just add unique ids under oceania and geography that points to a specific unique id inside baseurl/questions/uniqueids instead, but that would mean I'd have to keep track of a lot of references. Making a relations table so to speak.
I wonder if that's the way to go or? Should I restructure my data? The app isn't in production yet, so it's possible to restructure the data completely with no bigger consequences, other than I'd have to rewrite my code, that pushes data to Firebase.
Let me know, if all of this doesn't make sense and sorry for the wall of text :-)
Adding some additional code to Tim's answer for future reference.
Just use a deep query. The parent object key is not what is queried so it's 'ignored'. It doesn't matter whether it's a key generated by autoId or a dinosaur name - the query is on the child objects and the parent (key) is returned in snapshot.key.
Based on your Firebase structure, this will retrieve each child nodes where Oceania is true, one at a time:
let questionsRef = Firebase(url:"https://baseurl/questions")
questionsRef.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
.observeEventType(.ChildAdded, withBlock: { snapshot in
print(snapshot)
})
Edit: A question came up about loading all of the values at once (.value) instead of one at at time (.childAdded)
let questionsRef = Firebase(url:"https://baseurl/questions")
questionsRef.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)
.observeSingleEventOfType(.Value, withBlock: { snapshot in
print(snapshot)
})
Results in (my Firebase structure is a little different but you get the idea) uid_1 did not have Oceania = true so it was omitted from the query
results.
Snap (users) {
"uid_0" = {
categories = {
Oceania = 1;
};
email = "dude#thing.com";
"first_name" = Bill;
};
"uid_2" = {
categories = {
Oceania = 1;
};
"first_name" = Peter;
};
}
I think this should work:
ref.queryOrderedByChild("categories/Oceania").queryEqualToValue(true)

Fill array with objects

I have created a cities class, which consists of several City objects, each with specific data. Since the data is fixed, I want to assign it in the init() function of the Cities object.
// CASE 1 - ERROR
class Cities : NSObject {
var cityList:[City]
override init() {
cityList = []
let city = City()
city.fill("LA" , "USA")
self.cityList.append(city)
city.fill("Amsterdam" ,"Netherlands")
self.cityList.append(city)
city.fill("Beijing" , "China")
self.cityList.append(city)
Result: Beijing Beijing Beijing
// CASE 2 - CORRECT
class Cities : NSObject {
var cityList:[City]
override init() {
cityList = []
var city:City
city = City(name: "LA" ,country: "USA")
self.cityList.append(city)
city = City(name: "Amsterdam", country: "Netherlands")
self.cityList.append(city)
city = City(name: "Beijing" , country: "China")
self.cityList.append(city)
Result: LA, Amsterdam, Beijing
When I run this first script, I get a nice Cities object and the array has 3 cities in it, all Beijing.
In the Correct case, the data is assigned by the init function of object City. Now everything works as expected.
In both cases I have created only one City object: city. Why is this difference in Swift behaviour?
Using Apple Swift version 2.1
In Case 1 you have only created one City object, this is the line which create the object:
let city = City()
City is a class, which is a reference type. It means it points you to the same place in memory. Your first call to:
city.fill("LA" , "USA")
set the properties to 'LA' and 'USA', the second call
city.fill("Amsterdam" ,"Netherlands")
go to the same place in memory and override the properties with the new values and the same happens with the 3rd call which is the last call so you end up with the values.
Your array cityList has three items which points to the same place in memory this is why you see the same object:
cityList[0] === cityList[1] === cityList[2]
In Case 2 every time you create new object you call the constructor, for example:
City(name: "LA" ,country: "USA")
Constructor allocate new memory address which ends up with completely new object, so every time you call:
City(name:...)
your city object points to different place in memory.
In first case you create single City object and then fill it with different data, but it is one object that you created it once and simply placed the three pointer to it in the array, when you call fill you change the data inside this single object and in the end of your init you have array with 3 pointers to one object that contains last data
In second case you create 3 different objects and add it to array