Swift reflection: casting Any to a Function with unknown type

Consider we have a generic struct like so:

struct SomeStruct<T> {
    var value: T
    var closure: (T) -> Any
}

I'm using reflection and want to call closure with parameter value in runtime:

let str = SomeStruct<String>(value: "abc", closure: { $0 })
let mirror = Mirror(reflecting: str)
callClosure(mirror: mirror)

func callClosure(mirror: Mirror) {
    guard let value = mirror.descendant("value"),
        let closure = mirror.descendant("closure")
        else { return }
    if let castedClosure = closure as? (Any) -> Any {
        print("How to cast to an unknown inner type to get here?")
        castedClosure(value)
    }
    if let castedClosure = closure as? (String) -> Any,
        let castedValue = value as? String {
        print("Succeeds for SomeStruct<String>")
        castedClosure(castedValue) // works
    }
}

Since the type of value and parameter of the closure is always the same (although unknown), it should always be legit to call closure with value as a parameter.

How can we either cast the closure properly to make the compiler happy, or call the closure directly silencing the type-check system?

I've tried so far casting with unsafeBitcast(_:to:) and unsafeDowncast(_:to:), with no luck

That's not true. Consider this example:

struct DifferentStruct {
    var value: Int
    var closure: (String) -> Any
}
let str2 = DifferentStruct(value: 1, closure: { $0 })
let mirror2 = Mirror(reflecting: str2)
callClosure(mirror: mirror2)

Please see how SomeStruct is defined. value and closure are using the same type. This is guaranteed in the scenario I'm using it:

struct SomeStruct<T> {
    var value: T
    var closure: (T) -> Any
}

That's very scary that you're using unsafe functions just because you trust that everybody will read and understand your function before using it.

Swifty way to do that is just to not erase the type information with mirror. That way the type information is passed along automatically

func callClosure<T>(value: SomeStruct<T>) {
    value.closure(value.value)
}

When you converted your value to a Mirror you deleted the type information. You cannot get it back from the thin air. At this point nothing guarantees that the types match

Look, I'm writing a unit testing library for SwiftUI, and I have a good reason for asking how to do exactly what I've asked. I know this is unsafe and bad design and so on, but I don't have other choice!

SomeStruct is a very simplified example of the problem, and I don't have access to it

You could do this:

protocol Invokable {
  func invoke() -> Any
}

extension SomeStruct: Invokable {
  func invoke() -> Any { return closure(value) }
}

func tryToCallClosure(x: Any) -> Any {
  if let invokable = x as? Invokable {
    return invokable.invoke()
  }
  // fallback logic...
}
2 Likes

Unfortunately, this doesn't work in my case. SomeStruct is defined in external framework (SwiftUI) and its instance variables are all private and visible in the reflection only

You can still use the same technique, extending the struct with reflection-based logic to find and invoke the closure. Within the struct's own context, you'll have access to its generic parameters so you can cast to the function type you expect.

1 Like

At first, I thought this could work but then realized - the reference to the SomeStruct is of type Any as well. So I'd still need to cast struct of type Any to specific SomeStruct<TYPE> before invoking the method from the extension. SomeStruct<type(of: value)> would do the trick, but it's not a valid code

The way I would factor it is to make the type conform to a protocol with a type-erased interface, and put the generic type specific logic in the conformance.

1 Like

Great, thank you! The trick worked!

Now, there is a similar problem but with a private structure, which I cannot extend... All I have is its name as a string.

So here is where I stuck:
The runtile type of the Closure is (EnvironmentValues) -> ModifiedContent
The trick suggested by @Joe_Groff would look like

protocol SomeModifiedContent { }
extension ModifiedContent: SomeModifiedContent { } // erasing the generic parameter

let closure: Any = ... // in fact a (EnvironmentValues) -> ModifiedContent
let casted = closure as? (EnvironmentValues) -> SomeModifiedContent // fails
Terms of Service

Privacy Policy

Cookie Policy