How to check that an object of type `Any` is an array of a particular class that implements some protocol

I ran into a problem. I have one protocol and two implementing it:

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

then at some point I create an array and pass it to the function:

 var array = [A]() func update(object: Any){ } update(object: array) 

from this update function I would like to pass object to another function if it sets other conditions for the function:

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

So, how can I verify that an object of type Any is an array of a specific class that implements the 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 does not work. Code:

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

Compiling is fine, but that’s not what I want - the process function should receive an array of a specific Initiatable implementation, so at some point it may use:

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

So is there a way to do this? Thank you so much in advance!

+5
source share
5 answers

I found a workaround to solve this problem:

 func classTypeFrom(_ className: String) -> AnyClass!{ if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String { let classStringName = "_TtC\(appName.characters.count)\(appName)\(className.characters.count)\(className)" return NSClassFromString(classStringName) } return nil; } func update(object: Any){ if let array = object as? [Initiatable]{ let arrayTypeName = "\(type(of: ar))" let objectTypeName = arrayTypeName.substringFrom(index: 6, length: arrayTypeName.characters.count - 7) if let arrayType = classTypeFrom(objectTypeName) as? Initiatable.Type{ process(array: array, type: arrayType) } } } func process(array: [Initiatable], type: Initiatable.Type){ var ar = array let newObj = type.init() ar.append(newObj) } 
0
source

There are several parts to this question:

Type array generation

An array declaration expects an array of objects A , not A To create an array with types A, you can pass the expression Postfix self : ( link )

 var array = [ A.self ] 

This would define an array as an A.Type array, called a metatype type ( same reference ).

You can also create an empty array with this type of metatype:

 var array:[A.Type] = [] 

If you need an array with A.self and B.self , you can specify it as [Any] ...

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

... or use the Initiatable protocol you created:

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

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

You are having trouble converting any object to an array of types. Here is my updated update method:

 func update(object: Any){ if let array = object as? [Initiatable.Type] { //1 process(array: array) } } 
  • Now you can optionally convert the array passed to your update method. Drag it into the array of Initiatable metadata Initiatable : (This is the only line I changed from your method)

Getting type as a parameter in a process method

I assume that you just want your process method to get an array of types and instantiate a variable based on one of these types. You did not specify which element in the array, so I just went with the first one.

 func process(array: [Initiatable.Type]){ //1 if let firstType = array.first { //2 let firstObject = firstType.init() //3 } } 
  • The process method can get an array of types that accept the Initatable protocol.
  • I use an optional binding to get the value of the first element in the array. It must be a type.
  • Create an object based on the first element of the array.
+3
source

If your array is not empty, then you can solve this by capturing the runtime type from 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 do not know how to do this in Swift (as is the case with Swift 3).

  • The static type of object is Any , so you need to use the runtime type of object to get anywhere.
  • However, Swift (and most languages ​​with generics) only allows parameters of type T based on the static types of the values ​​you pass. So your whole approach with process<T: Initiatable>(array: [T]) is a dead end: T can only accept the type that the compiler already knew when you called process() .
  • Swift gives 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. get A from Array<A> if the compiler no longer knows the type parameter statically.
    • You can get the string "Array<A>" and extract string A from it, but AFAIK there is no way to reassign strings for types in Swift unless the type is an Objective-C class.

In short, I think that you just click on the Swifts metatype / reflection constraint.

+1
source

Unfortunately, I cannot come up with a solution in the given constraints. It looks like you are trying to achieve polymorphism at compile time at run time.

Using generics implies that the compiler understands which class will be called. But using Any implies that at compile time it can be anything, and this will only be known at run time.

The closest solutions I can offer are as follows:

 // Either use generic `update` function func updateGeneric<T: Initiatable>(array: Array<T>){ process(array: array) } // or explicitly convert `Any` to the class, // so compiler understands what implementation of `process` to call func updateExplicit(object: Any) { if let array = object as? [A] { process(array: array) } if let array = object as? [B] { process(array: array) } } 
0
source

Does this help you?

 protocol Initiatable{ init() } class A: Initiatable{ required init() {} } class B: Initiatable{ required init() {} } class Test<T : Initiatable> { func update(object: Any){ if let array = object as? [T]{ process(array: array) } } func process(array: [T]){ } } 
0
source

Source: https://habr.com/ru/post/1266934/


All Articles