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

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.

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

Can't perform methods of objects stored in Array[Any]

I want to store objects of different types in an array.
The program below is only a minimum demo. In the anyArray:[Any] an instance of Object1 is stored. The print statement prints out the expected object type. In the following line the test of the stored object's type returns true. This means, during run time the correct object type is known and every thing seems to be fine.
class Object1 {
var name = "Object1"
}
var anyArray:[Any] = [Object1()]
print("\(type(of: anyArray[0]))")
let testResult = anyArray[0] is Object1
print("Test result:\(testResult)")
//print("Name:\((anyArray[0]).name)")
Console output:
Object1
Test result:true
However, if I try to print out the name property of the object, I get an error message from the editor:
Value of type 'Any' has no member 'name'
Well, at compile time the object's type is unknown. That's why the compiler complains. How can I tell the compiler that it is OK to access the properties of the stored object?
The difference comes from the difference from Type Checking in:
runtime, or
compile time
The is operator checks at runtime whether the expression can be cast to the specified type. type(of:) checks, at runtime, the exact type, without consideration for subclasses.
anyArray[0].name doesn't compile since the Type Any doesn't have a name property.
If you're sure anyArray[0] is an Object1, you could use the downcast operator as!:
print("\((anyArray[0] as! Object1).name)")
To check at runtime if an element from anyArray could be an Object1 use optional binding, using the conditional casting operator as?:
if let:
if let object = anyArray[0] as? Object1 {
print(object.name)
}
Or use the guard statement, if you want to use that object in the rest of the scope:
guard let object = anyArray[0] as? Object1 else {
fatalError("The first element is not an Object1")
}
print(object.name)
If all objects in your array have a name property, and you don't want to go through all the hoops of optional binding repeatedly, then use a protocol. Your code will look like this:
protocol Named {
var name: String {get set}
}
class Object1: Named {
var name = "Object1"
}
var anyArray:[Named] = [Object1()]
print("\(type(of: anyArray[0]))")
let testResult = anyArray[0] is Object1
print("Test result:\(testResult)")
print("Name:\(anyArray[0].name)")
Notice that anyArray is now an array of Named objects, and that Object1 conforms to the Named protocol.
To learn more about protocols, have a look here.
You object is still of type Any. You just checked if it can be of type Object1, but you did not cast it. If you want the object as Object1, you need to cast it.
Also if multiple classes can have name, you need to use Protocol like #vadian has mentioned in his comment and cast it to that protocol.
protocol NameProtocol {
var name: String {get set}
}
class Object1: NameProtocol {
var name = "Object1"
}
if let testResult = anyArray[0] as? NameProtocol {
print(testResult.name)
}
Edit: "I want to store objects of different types in an array". The solution that you have marked as correct will not work if all the objects that you have do not conform to the protocol.

How to pass realm.objects(SomeObject.self).filter() to a function that needs Results<Object>

This question is in conjunction with RealmSwift Cannot cast Results<SomeOjbect> to Results<Object>
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 framework: https://github.com/danielgindi/ChartsRealm, which pull data from Realm and draw charts. The framework is written in Swift, and can be used in Objc as well, so it combines RLMObject and Object types.
It has a function like:
public convenience init(results: Results<Object>?, xValueField: String?, yValueField: String, label: String?)
which takes Results<Object>?, however I cannot get my filter results as Results<Object> type. e.g.
realm.objects(SomeObject.self).filter("city=0")
it's Results<SomeObject>, and cannot be converted to Results<Object>, described in RealmSwift Cannot cast Results<SomeOjbect> to Results<Object>
How can I solve it?
Because the demo in ChartsRealm framework, it simply read all objects from Realm in + (RLMResults *)allObjectsInRealm:(RLMRealm *)realm;, but in real world, we usually need to filt the results first.
If there's really nothing I can do, I could accept modifying the framework function parameters, filing pull requests for the framework, just to make it work.
Results<Object> does not make any sense as a type, and the methods in ChartsRealm should be generic methods which take a Results<T>.
Because in this specific case all that ChartsRealm does with the Results<Object> is use ObjectiveCSupport.convert() to get a RLMResults, an unsafeBitCast() to Results<Object> should work fine. You could also just call ObjectiveCSupport.convert() yourself and pass ChartsRealm a RLMResults rather than a Swift Results object.

How to create optional List(array) of NSData objects in Realm?

I need to save pictures but Realm documentation says that it is not possible to have NSData List, let alone optioanal. How do you think, will it work if create entity what will be contain property of NDData, and make optional list of them? Somebody faced with same problem?
If you'd like to create optional type of object type (String, NSDate or NSData), you can just declare the properties as optional, like the following:
class Person: Object {
dynamic var name: String? = nil
}
If you want to declare a variable of optional type for Int, Float or Double, you should wrap the value with RealmOptional<T>, like the following:
class Person: Object {
let age = RealmOptional<Int>()
}
Please see also https://realm.io/docs/swift/latest/#optional-properties

Instatiate Realm Object from string in swift 3

I would like to know if it is possible to instantiate a realm object based on a string that is the class name of the realm object but without knowing what that string will be until it is provided.
For example:
for(_, object) in json["AllObjects"]{
let objectType = self.getRealmObjectBasedOnString(type: className, params: object.stringValue)
self.objectList.append(objectType)
}
Here I go through a json that I get and want to create a realm object from each json object in the array. The problem is that this method will be called several times and each time the only thing that will change is the className variable. So I would like to keep this logic in only one method instead of creating several methods with same logic or a huge and complicated if else that determines the realm object to be created.
Here is getRealmObjectBasedOnString
func getRealmObjectBasedOnString(type: String, params: String) -> Object{
switch type {
case "classA":
return ClassA(JSONString: params)!
case "classB":
return ClassB(JSONString: params)!
default:
return DefaultClass(JSONString: params)!
}
}
Can someone explain why this does not work and whether it is possible to accomplish what I want?
You can use NSClassFromString to get Realm object type from string, but keep in mind that Swift uses modules for nemespacing, so you'll need to add your app's module name before your class name.
guard let objectType = NSClassFromString("YourAppModuleName.\(json["className")") else {
// handle unexpected class here
}
let objectList = realm.objects(objectType)

Resources