Dulgan
Dulgan

Reputation: 6694

How to perform "optional unwrapping" to a function using generic parameters

I'll begin with small code examples, made as simple as possible to sum up the problem.

Let's say I've got a function defined as follow :

func doSomething<T>(using f: (String, (T) -> Void) -> Void, key: String) {
   f("test", {
      object in
      // do something with object using key
      //...
   })

}

I've got a bunch of auto generated classes with class methods, for exemple :

class MyClass {
   var prop1: String = ""
}

class MyClassAPI {

   class func f1(param: String, completion: (MyClass) -> Void) {
      let mc = MyClass()
      mc.prop1 = "hello"
      completion(mc)
   }
}

Then, I got a whole set of key/functions couples in a dictionary :

// Any because of cast problem !
let keyFunctions: [String: Any] = [ 
    "first" : MyClassAPI.f1,
    "second" : MySecondClassAPI.f2,
    "third" : MyThirdClassAPI.f3
    //...
]

Finally, i'll iterate through all the keys/function to call doSomething:

for (k, f) in keyFunctions {
   // This line doesn't work as the compiler complain about typing errors
   doSomething(using: f, key: k)
}

The problem I'm facing is that I can't cast my functions to the right type to pass them to doSomething :

Xcode suppose to force cast using f as! (String, (_) -> ()) -> Void then gives me an error, _ is not a type.

I tried to be more permissive using if let uf = f as? (String, (Any) -> Void) -> Voidwith no chance.

I read the whole generics pages of Swift manual without any hint on how to achieve this.

Please let me know of any existing way to perform such things using genericity.

Upvotes: 1

Views: 336

Answers (2)

Dulgan
Dulgan

Reputation: 6694

I finally made it this way :

func doSomething<T>(using f: (String, (Any) -> Void, key: String, type: T.Type) -> Void, key: String) {
   f("test", {
      object in
      guard let o as? type else { return }
      // do something with o using key
      //...
   })
}

let keyFunctions: [String: (String, (Any) -> Void)] = [ 
   "first" : MyClassAPI.f1,
   "second" : MySecondClassAPI.f2,
   "third" : MyThirdClassAPI.f3
   //...

]

Upvotes: 0

Dmitry
Dmitry

Reputation: 2887

Instead of Any make a more specific type for a block

let keyFunctions: [String: (String) -> Any]

Then, at least, your will compile and doSomething will be called. However, there is no point in making it generic, as T will always be Any . If doSomething relies on common behaviour between your classes, then it would make sense to define a protocol for all of them.

If you actually want to have information about your classes, then you can introduce a non generic class that would keep this info:

class Wrap {
    var resultType: Any.Type
    let f: (String) -> Any

    init<T>(_ f: @escaping (String) -> T) {
        self.f = f
        resultType = T.self
    }
}

let keyFunctions: [String: Wrap] = [
    "first" : Wrap(MyClassAPI.f1),
    "second" : Wrap(MySecondClassAPI.f2)
]

But resultType can not be used in casting later though.

Upvotes: 1

Related Questions