Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to check that object of type `Any` is an array of concrete class implementing some protocol

I have faced an issue. Consider I have one protocol and two classes implementing it:

protocol Initiatable{
    init()
}

class A: Initiatable{
    required init() {}
}

class B: Initiatable{
    required init() {}
}

then at some point I'm creating an array and passing it to function:

var array = [A]()

func update(object: Any){

}

update(object: array)

from that function update I would like to pass object to another function if it mets another function's conditions:

func process<T: Initiatable>(array: T){
    /* ... */
}

So how can I check that object of type Any is an array of concrete class implementing Initiatable protocol? I would like to write something like

func update(object: Any){
    if let array = object as Array<T: Initiatable>{
        process(array: array)
    }
}

But that's doesn't work. Code like:

func update(object: Any){
    if let array = object as [Initiatable]{
        process(array: array)
    }
}

func process(array: [Initiatable]){ }

Compiles fine, but that's not what I want – process function should receive an array of concrete implementation of Initiatable so at some point it could use:

func process<T: Initiatable>(array: [T]){
    /* other code */
    T.init()
}

So is there any way to do this? Thank you very much in advance!

like image 840
Nikita Arkhipov Avatar asked Apr 19 '17 16:04

Nikita Arkhipov


People also ask

How to define an array of objects in typescript?

One of which is Array of Objects, in TypeScript, the user can define an array of objects by placing brackets after the interface. It can be named interface or an inline interface.

How to create an interface for an array of objects?

We defined an interface for an array of objects; we explained the interface for the type of each object. Put the array to be Type []. All the objects added to the array must obey the type, or the type checker gives an error. In the above example, the type of an array of objects is used as an interface.

How to check the type of an array in JavaScript?

In javascript, there is no type checking, declared variable holds any type of data includes array or any type. We need to know the object type based on the data stored.

Is object constructor name is array or not?

Object constructor name return type of the object like an object,Number,String,Array. the following checks constructor name is array or not Array inbuilt-in method isArray checks for objects, This is part of the native method in javascript language, installation of libraries is not required.


2 Answers

There's a few parts to this question:

Generating the array of types

Your array declaration is expecting an array of A objects rather than A types. To generate an array with A types, you could pass in a Postfix self expression: (link)

var array = [ A.self ]

This would define array as an array of A.Type, called the Metatype Type (same link).

You could also generate an empty array with this metatype type:

var array:[A.Type] = []

If you wanted an array with both A.self and B.self you could either specify it as [Any]...

var array:[Any] = [A.self,B.self]

...or, make use of the Initiatable protocol you created:

var array:[Initiatable.Type] = [A.self,B.self]

Downcasting an array to an array of types in your update method

You were having trouble downcasting an Any object to an array of types. Here's my updated update method:

func update(object: Any){
  if let array = object as? [Initiatable.Type] {    //1
    process(array: array)
  }
}
  1. You can now perform an optional downcast on the array that is being past into your update method. Downcast it to an array of metadata types of Initiatable: (This is the only line I modified from your method)

Receiving a type as a parameter in the process method

I'm assuming that you just want your process method to receive an array of types, and instantiate a variable based on one of those types. You didn't mention which element in the array, so I've just gone with the first.

func process(array: [Initiatable.Type]){    //1
  if let firstType = array.first {          //2
    let firstObject = firstType.init()      //3
  }
}
  1. The process method can receive an array of types that adopt the Initiatable protocol.
  2. I'm using optional binding to get at the value of the first element in the array. This should be a type.
  3. Instantiate an object based on the first element in the array.
like image 182
Craig Grummitt Avatar answered Nov 15 '22 03:11

Craig Grummitt


If your array is nonempty, then you can solve this by grabbing the runtime type of one element from the array:

func update(object: Any){
    if let array = object as? [Initiatable]{
        process(array: array)
    } else {
        // Not initiatable
    }
}

func process(array: [Initiatable]) {  // no need for generics here
    guard let first = array.first else {
        // Unable to determine array type
        // throw or return or whatever
    }

    // type(of: first) gives A or B
    type(of: first).init()
}

If the array is empty, then I don’t know of a way to do this in Swift (as of Swift 3, anyway).

  • The static type of object is Any, so you need to use the runtime type of object in order to get anywhere.
  • However, Swift (and most languages with generics) only resolve type parameters like T based on the static types of the values you pass. Therefore your whole approach with process<T: Initiatable>(array: [T]) is a dead end: T can only take on a type the compiler already knew when you called process().
  • Swift does give you access to runtime types: type(of: object) will return (for example) Array<A>. So close! But:
    • AFAIK, there is currently no way to extract type parameters from a metatype, i.e. to get A from Array<A>, unless the compiler already knows the type parameter statically.
    • You can get the string "Array<A>" and extract the string A from it, but AFAIK there is no way to map strings back to types in Swift unless the type is an Objective-C class.

In short, I think you’ve just hit a limit of Swift’s metatype / reflection capabilities.

like image 24
Paul Cantrell Avatar answered Nov 15 '22 04:11

Paul Cantrell