Can #dynamicMemberLookup be used to call methods? - ios

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.

Related

Missing argument for parameter 'from' in call. Insert 'from: <#Decoder#>'

I am trying to create a new instance of a codable struct
#State private var parcel = Parcel()
but I'm getting this error:
Missing argument for parameter 'from' in call
Insert 'from: <#Decoder#>'
struct Parcel: Codable {
var created_at: String
var height: Double
var id: String
var length: Double
var mode: String?
var object: String
var predefined_package: String?
var updated_at: String?
var weight: Double
var width: Double
}
Every object in Swift needs an initializer: some code to set up the object when it is first created. If your object is an instance of a class, the initializer needs to be explicitly defined by you. However if the object is an instance of a struct, Swift implicitly defines an initializer. For example, this struct
struct Foo {
let bar: Int
}
implicitly gets an initializer that looks like this
init(bar: Int) {
self.bar = bar
}
Initializers can also be implicitly created through protocol extensions. That means if your struct inherits a protocol (such as Codable), the protocol can define additional initializers for you. For this simple example, Codable would add something like this
init(from decoder: Decoder) throws {
// decode a key value pair with name "bar" and an Int value using decoder
let decodedBar = try ...
self.init(bar: decodedBar)
}
In your case, when you write parcel = Parcel() you are calling this kind of initializer
init() {
// initialize somehow with no input!
}
But you never defined anything like that! The compiler is suggesting that you call the initalizer you got from Codable since it's a close match, but that's probably not what you want either.
You can either define that missing initializer, or define default values for all of your struct's members. If you do that, the implicit initializer defined by Swift will have no arguments, making your code valid. For example
struct Foo {
let bar: Int = 3
}
let f = Foo() // now this is valid, implicit init has no arguments
By default, structs create an initialiser behind the scenes for every non-optional property that you declare. So by simply creating an instance of the struct you need to include all non-optional values it requires. If you don’t want to add the values when you initialise it, change them to vars and make them optional (add ? to the end).

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.

.self after struct type in Swift

I’m confused by a line of code found in the Metal example where the memory pointer is bound to a type.
uniforms = UnsafeMutableRawPointer(uniformBuffer.contents()).bindMemory(to: Uniforms.self, capacity: 1)
My confusion is the .self after the Uniforms type. Uniforms is a struct defined in an Objective-C file and the code wont run without .self being in the call. Why is that necessary?
The .self returns the metatype instance for the corresponding type. Think of it as a typesafe type identifier (e.g., way safer than using a string for that). You can then safely call the available initializers, static methods, static properties on such metatype instance.
For instance, you could store it in a variable as well:
let metatype: Uniforms.Type = Uniforms.self
and Uniforms.Type is the actual metatype (i.e., the type's type).
Metatype crash course. A very quick example to get a feel of how this meta stuff might be actually useful:
class Super {
let id: Int
required init(id: Int) { self.id = id }
}
class SubA: Super { ... }
class SubB: Super { ... }
let subclass: Super.Type = SubA.self
and then, later on, use subclass to create an instance without hardcoding the actual subclass type name:
let obj = subclass.init(id: 123) // new SubA instance.
In Swift, .self could be used on a type to extract its meta type or on an instance of a type. Example, use .self to get the meta type and pass it to the API:
self.tableView.registerClass(
UITableViewCell.self, forCellReuseIdentifier: "myUIViewCell")

Swift compile time errors when using generics and accessing dictionary's values

Below I have a class, B, with a generic type and the generic type has a subclass type constraint. In a separate class, A, I create a dictionary property, values, with the key as String and value as B. Class A then has methods to return or set values in the dictionary such that the values are not constrained to a single type (they maintain their generic SomeType type which is a subclass of NSObject). However, this produces the following two errors noted inline below:
class A: NSObject {
var values = [String : B]()
func get<SomeType: NSObject>(key: String) -> B<SomeType>? {
// #1 Error on line below: cannot convert return expression of type 'B<NSObject>?' to return type 'B<SomeType>?'
return values[key]
}
func set<SomeType: NSObject>(key: String, value: B<SomeType>) {
// #2 Error on line below: cannot assign value of type 'B<SomeType>' to type 'B<NSObject>?'
values[key] = value
}
}
class B<SomeType: NSObject>: NSObject {
}
I've attempted various forms of declaring the values dictionary to tell the compiler that SomeType is a subclass of NSObject and everything is going to be ok, but have been unsuccessful. Similarly to this question, I'm a bit stumped because the methods define SomeType as a subclass of NSObject and therefore things appear to be type safe when setting and getting from values.
I could remove the generic types from the methods and instead force the type to be <NSObject>, but then I'd run into the same problem as noted here.
This may not be doing what you think it's doing:
var values = [String : B]()
This isn't really [String : B], it's [String : B<NSObject>]. (I'm actually kind of surprised that this is legal syntax; I'd be tempted to open a bugreport about that; B isn't a proper type in Swift.) You may realize this already, but it's the first important note.
The second important note is that generic types are not covariant in Swift. A Thing<Cat> is not a subtype of Thing<Animal>. There are some type-theory reasons for this, and there are some technical implementation reasons for this, but the important fact is that it's not possible in Swift.
If you need to hold a variety of B types, then you'll need to build a type eraser. In your case, the type eraser could possibly be B<NSObject> with something like this:
class B<SomeType: NSObject>: NSObject {
let value: SomeType
init(value: SomeType) {
self.value = value
}
func liftGeneric() -> B<NSObject> {
return B<NSObject>(value: value)
}
}
If you need to hold just one kind of B type, then make A generic as well.

How to create an enumeration that accepts a generic type as associated type

I'm trying to create an enumeration that accepts a generic type as associated value.
The compiler complains:
Reference to generic type 'GenericItem' requires arguments in <...>
The scheme is pretty simple:
struct GenericItem <Item:FormattableAsStringWithPrecision> {
let value: Item
}
enum Enumeration {
case Generic(values: [GenericItem])
}
I can't understand how to make this possible.
You need to add the Generic type to the enum too, the types can be inferred from the initialiser so you do not need to pass it as a generic type argument.
Below is an example of how you might do it.
struct GenericItem<T: CustomDebugStringConvertible> {
let value: T
}
enum Enumeration<T: CustomDebugStringConvertible> {
case Generic(value: [GenericItem<T>])
}
let someValue = Enumeration.Generic(value: [ GenericItem(value: "") ])
edit: I changed the FormattableAsStringWithPrecision to CustomDebugStringConvertible as I assumed it was one of your own custom protocols which can be easily swapped out, but the same logic would still apply for any protocol.

Resources