Swift Realm - get types of all database objects - ios

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")
}

Related

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.

RealmSwift Cannot cast Results<SomeOjbect> to Results<Object>

RealmSwift version: latest master branch
So I have a Realm Object like:
import RealmSwift
class SomeObject: Object
{
#objc dynamic var datetime = ""
#objc dynamic var city = 0
convenience init(city: Int, datetime: String)
{
self.init()
self.city = city
self.datetime = datetime
}
}
There is a func call like
static func createlineData(from results: Results<Object>?) -> LineChartData
Now I fetch some results and pass to createLineData:
let realm = try! Realm()
let results = realm.objects(SomeObject.self).filter("city = \(city.rawValue)")
let lineData = createlineData(from: results as? Results<Object>)
compiler warns me that the type cast will always fail:
Cast from Results<"SomeObject"> to unrelated type Results<"Object"> always fails
I am confused since SomeObject is just a subclass. How can I fix it? Thanks in advance.
UPDATE:
What I want to do is that, the param of
static func createlineData(from results: Results<Object>?) -> LineChartData
can never be changed, so I need to make a query to filt based on city which is enum, pass them into createlineData(from results: Results<Object>?), and access other properties like datetime later in createlineData, from Results<Object>
In Swift, each generic class represents its own type and even if you have a generic class where the generic type parameter is a subclass of your other generic class having the superclass as its generic parameter, the two generic classes won't be related through inheritance.
This is why you cannot cast Results<SomeObject> to Results<Object> even though SomeObject is a subclass of Object.
Here's a simple example representing the same issue with a generic class:
class A{}
class B:A{}
class GenericClass<T> {
let val:T
init(val:T) {
self.val = val
}
}
let genericA = GenericClass<A>(val: A())
let genericB = GenericClass<B>(val: B())
let upcasted = genericB as? GenericClass<A> //warning: Cast from 'GenericClass<B>' to unrelated type 'GenericClass<A>' always fails
Moreover, the Results type in Realm is a homogenous collection type, so you cannot store different subclasses of Object in the same Results object, so casting to Results<Object> wouldn't make sense anyways. If you need to store objects from different Realm model classes in the same collection, you will need to sacrifice the self-updating nature of Results and stick with storing your objects in an Array for instance.

Swift: Store multiple Types in a single array

How do I store multiple Types in a single array?
I'm doing the following:
var vehicles = [string]()
let mustang = Car() // car is a class name
mustang.brandName = "Ford"
mustang.modelName = "Mustang" // string type
mustang.modelYear = 1968
mustang.isConvertibible = true
mustang.isHatchback = false
mustang.hasSunroof = false // bool type
mustang.numberOfDoors = 2 // int type
mustang.powerSource = "gas engine"
// Add it to array
vehicles.append(mustang.brandName)
vehicles.append(mustang.modelName)
vehicles.append(mustang.modelYear) // Error: int type not supported
vehicles.append(mustang.isConvertibible) // Error: bool type not supported
vehicles.append(mustang.brandName)
vehicles.append(mustang.brandName)
How should I achieve this? I'm new to Swift / iOS.
Instead of creating string array you can create the array of car like this and store directly car object
var cars = [Car]()
let mustang = Car() // car is a class name
mustang.brandName = "Ford"
mustang.modelName = "Mustang" // string type
mustang.modelYear = 1968
mustang.isConvertibible = true
mustang.isHatchback = false
mustang.hasSunroof = false // bool type
mustang.numberOfDoors = 2 // int type
mustang.powerSource = "gas engine"
cars.append(mustang)
Or if you want to store different types of object then you cant create array of AnyObject, So that it will store Any type of instance or object inside that array.
var arr = [AnyObject]()
let car = Car()
let bike = Bike()
arr.append(car)
arr.append(bike)
Swift 5
Using protocol oriented programming
A little more swifty solution could be to use protocols. This is type safe, so you won't have to deal with unwrapping and casting optionals.
1. Define a protocol
protocol ModeOfTransportation {
// Define anything in common between objects
// Example:
var color: UIColor { get set }
}
2. Make your models conform to the protocol
(The compiler will ensure you give it the correct types: ex. color)
struct Car: ModeOfTransportation {
var color = UIColor.red
}
struct Bicycle: ModeOfTransportation {
var color = UIColor.blue
}
3. Give your array the protocol as a type
class Example {
var myTransportList: [ModeOfTransportation] = []
func addModesOfTransport() {
let car = Car()
let bike = Bicycle()
myTransportList.append(car)
myTransportList.append(bike)
}
}
I think you're doing something really wrong.
Arrays are designed to hold only one type of stuff. If you really want to hold different types of stuff. Here's some possible methods:
you can try creating an array of AnyObjects:
-
var vehicles = [AnyObject]()
This won't work if the type you want to store does not conform to AnyObject. If you have such a type, you will have to use Any.
you can create an array of strings and convert all the values you want to store to a string
You can just create an array of Cars to store all the properties of a car. Then you can add more cars to it later on. I think this is what you intended.
Per the Swift documentation:
AnyObject can represent an instance of any class type.
Any can represent an instance of any type at all, including function types.
So you could make an array of
var vehicles = [AnyObject]()
and this would take objects (class instances) of any type.
However, the documentation goes on to say:
Use Any and AnyObject only when you explicitly need the behavior and capabilities they provide. It is always better to be specific about the types you expect to work with in your code.
So, ideally you are specific about the type your array can hold.

Do I need to write all the child objects in a class in realm as well?

Following the example code as shown:
// Define your models like regular Swift classes
class Dog: Object {
dynamic var name = ""
dynamic var age = 0
}
class Person: Object {
dynamic var name = ""
dynamic var picture: NSData? = nil // optionals supported
let dogs = List<Dog>()
}
// Use them like regular Swift objects
let myperson = Person()
let mydog = Dog()
mydog.name = "Rex"
myperson.dogs.append(mydog)
// Persist your data easily
let realm = try! Realm()
try! realm.write {
// do I need to add this statement??
realm.add(mydog)
realm.add(myperson)
}
Do I need to persist the mydog object as well, or that Realm is smart enough to know that it is a new child object of the myperson and it will persist it for me?
No you do not need to persist the actual dog object, if you already persist an object containing it.
For the users who are wondering about cascading delete, the answer is NO.
Realm supports cascading write but NOT delete. For delete you may have to query all the relations and delete one by one.

Looping through NSMutableArray in Swift

How can Ioop through an NSMutableArray in Swift? What I have tried:
var vehicles = NSMutableArray()
The array contains objects from class: Vehicle
for vehicle in vehicles {
println(vehicle.registration)
}
I cannot run the above code without the compiler telling me registration doesn't belong to AnyObject. At this point I assumed that was because I hadn't told the for loop what type of class item belongs to. So I modified by code:
for vehicle: Vehicle in vehicles {
println(vehicle.registration)
}
Now the compiler complains about downcasting... how can I simply gain access to the custom registration property whilst looping through the array of Vehicles?
This should work:
for vehicle in (vehicles as NSArray as! [Vehicle]) {
println(vehicle.registration)
}
As Romain suggested, you can use Swift array. If you continue to use NSMutableArray, you could do either:
for object in vehicles {
if let vehicle = object as? Vehicle {
print(vehicle.registration)
}
}
or, you can force unwrap it, using a where qualifier to protect yourself against cast failures:
for vehicle in vehicles where vehicle is Vehicle {
print((vehicle as! Vehicle).registration)
}
or, you can use functional patterns:
vehicles.compactMap { $0 as? Vehicle }
.forEach { vehicle in
print(vehicle.registration)
}
Obviously, if possible, the question is whether you can retire NSMutableArray and use Array<Vehicle> (aka [Vehicle]) instead. So, instead of:
let vehicles = NSMutableArray()
You can do:
var vehicles: [Vehicle] = []
Then, you can do things like:
for vehicle in vehicles {
print(vehicle.registration)
}
Sometimes we're stuck with Objective-C code that's returning NSMutableArray objects, but if this NSMutableArray was created in Swift, it's probably preferable to use Array<Vehicle> instead.
NSMutableArray comes from the Objective-C world. Now you can use generics and strongly-typed arrays, like this:
var vehicles = [Vehicle]()
...
for vehicle in vehicles {
println(vehicle.registration)
}

Resources