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

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.

Related

Can #dynamicMemberLookup be used to call methods?

In the documentation for #dynamicMemberLookup it says,
Apply this attribute to a class, structure, enumeration, or protocol to enable members to be looked up by name at runtime.
If I'm not mistaken, instance methods are considered members of a struct / class. However, when I try to call a function dynamically I get an error saying:
Dynamic key path member lookup cannot refer to instance method foo()
To reproduce the problem:
struct Person {
var name: String
var age: Int
func greet() {
print("hello, my name is \(name)")
}
}
#dynamicMemberLookup
struct Wrapper {
var value: Person
subscript<T>(dynamicMember keypath: KeyPath<Person, T>) -> T {
value[keyPath: keypath]
}
}
let person = Person(name: "John Doe", age: 21)
let wrapper = Wrapper(value: person)
wrapper.greet() // << Error: Dynamic key path member lookup cannot refer to instance method `greet()`
// Or
let function = wrapper.greet // << Error: Dynamic key path member lookup cannot refer to instance method `greet()`
function()
How can I dynamically call greet() using #dynamicMemberLookup? Is there any way to achieve what I'm trying to do?
Thanks in advance!
No, dynamicMemberLookup does not work for methods. As the signature of the subscript suggests, it only works for things that can be represented as a KeyPath. Method calls cannot be part of a key path. :(
Key-Path Expression
A key-path expression refers to a property or subscript of a type.
The path consists of property names, subscripts, optional-chaining
expressions, and forced unwrapping expressions. Each of these key-path
components can be repeated as many times as needed, in any order.
At compile time, a key-path expression is replaced by an instance of
the KeyPath class.
I suspect the reason why it is called "dynamic member lookup" is because it also works with subscripts. The alternative of dynamicPropertyOrSubscriptLookup is rather a mouthful isn't it?
One rather hacky fix would be to change greet into a computed property:
var greet: () -> Void { {
print("hello, my name is \(name)")
} }
If greet has had parameters, you could also change it into a subscript, but I think that is an even uglier solution.

What's the difference between : and = in swift

Sorry if the title is rather confusing, but I'm curious to know the difference between these two lines:
var title = String()
var title: String
Is one being initialized and one only be declared? Which is more correct?
For example, if I have a struct should I use one of the other?
So the reason I ask this is because I'm learning about how to grab some JSON from a url and then display it in my app. One of the new ways of doing so is using Decodable. So, I have a struct in a model class like so:
struct Videos: Decodable {
var title = String()
var number_of_views : Int
var thumbnail_image_name: String
var channel: Channel
var duration: Int
}
In another class I have this:
URLSession.shared.dataTask(with: url){(data,response,error) in
if(error != nil){
print(error!)
return
}
guard let data = data else { return }
do{
self.Videos2 = try JSONDecoder().decode([Videos].self, from: data)
//self.collectionView?.reloadData()
}catch let jsonErr{
print(jsonErr)
}
}.resume()
So, should I declare or initialize the variables in my struct? I'm assuming I should just declare them like so:
var title: String?
Would that be the correct syntax in my struct?
UPDATE:
I understand this question was more broad then I originally proposed it to be. I'm sorry about that, but thank you so much for all your great answers that clarified a lot up for me.
The difference is that : defines the type of your variable, whereas = assigns an actual value to the variable.
So:
var title = String()
This calls the initializer of the String type, creating a new String instance. It then assigns this value to title. The type of title is inferred to be String because you're assigning an object of type String to it; however, you could also write this line explicitly as:
var title: String = String()
This would mean you are declaring a title variable of type String, and assigning a new String to it.
var title: String
This simply says you're defining a variable of type String. However, you are not assigning a value to it. You will need to assign something to this variable before you use it, or you will get a compile error (and if this is a property rather than just a variable, you'll need to assign it before you get to the end of your type's init() method, unless it's optional with ? after it, in which case it gets implicitly initialized to nil).
EDIT: For your example, I'd probably declare all the variables using let and :, assuming that your JSON provides values for all of those properties. The initializer generated by Decodable should then set all the properties when you create the object. So, something like:
struct Videos: Decodable {
let title: String
let number_of_views : Int
let thumbnail_image_name: String
let channel: Int
let duration: Int
}
This initializes a value
var title = String()
This declares a value but does not initialize it
var title: String
If you attempt to use the latter, such as print(title), you will get a compiler error stating Variable 'title' used before being initialized
It does not matter whether the value is a class or a struct.
The = operator is the assignment operator, it assigns a value to the object on the left of the =
Typically, class or struct properties are declared but not initialized until the init() is called. A simple class might be
class MyClass {
let myProperty: String
init(aString: String) {
self.myProperty = aString
}
}
Whereas inside the scope of a function you may declare a local variable that only lives inside the scope of the function.
func doSomethingToAString(aString: String) -> String {
let extraString = "Something"
let amendedString = aString + extraString
return amendedString
}
In your specific example, the struct synthesizes an initializer that will allow you to initialize the struct with all the values needed to fill your properties. The initializer generated by Decodable should then set all the properties when you create a Videos struct, you will do it something like:
let aVideos = Videos(title: "My Title", number_of_views: 0, thumbnail_image_name: "ImageName", channel: Channel(), duration: 10)
Is one being initialized and one only be declared?
Yes, meaning that the declared cannot be used. If you tried to set a value for it, you would get a compile-time error:
variable 'title' passed by reference before being initialized
Which is more correct?
There is no rule of thumb to determine which is more correct, that would be depends on is there a need to initialize title directly.
On another hand, when it comes to declare properties for a class, saying var title = String() means that you are give title an initial value ("") which means that you are able to create an instance of this class directly, example:
class Foo {
var title = String()
}
let myFoo = Foo()
However, if title declared as var title: String, you will have to implement the init for Foo:
class Foo {
var title: String
init(title: String) {
self.title = title
}
}
let myFoo = Foo(title: "")
Also, you have an option to declare it as lazy:
lazy var title = String()
which means:
A lazy stored property is a property whose initial value is not
calculated until the first time it is used. You indicate a lazy stored
property by writing the lazy modifier before its declaration.
Properties - Lazy Stored Properties

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.

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

Cannot assign a value of type '[Thing]' to a value of type '[Any]'

I'm struggling with what seems like a simple Swift problem.
I've declared a struct with a static function that returns some instances:
struct Thing {
static func allTheThings() -> [Thing] {
...
}
}
I've got a CustomViewController with an property declared:
var objects = [Any]()
In the subclass of that controller in viewDidLoad(), I'm trying to assign the objects property.
objects = Thing.allTheThings()
But I'm getting a compiler error
Cannot assign a value of type '[Thing]' to a value of type '[Any]'
Isn't that the whole point of Any?
This works:
objects = Thing.allTheThings().map { $0 }
But this doesn't
let things = Thing.allTheThings().map { $0 }
objects = things
Any ideas what's going on here?
It seems Swift can convert Thing to Any but not [Thing] to [Any].
The reason this works
objects = Thing.allTheThings().map { $0 }
is that the compiler can infer that the type of $0 is Any, but in the second example
let things = Thing.allTheThings().map { $0 }
it infers $0 to be of type Thing. You end up with the variable things being of type [Thing], which means that the assignment
objects = things
will mean a conversion from [Thing] to [Any] which does not work.

Resources