Search code examples
swiftgenericsmetatype

Swift generic function call underlying method based on generic type


I am writing an interface to Someone Else's Code (SEC), and I have a long list of functions more or less like this:

public func readString(_ row: Row, columnName: String) -> String? {
    return SEC.getString(row, columnName)
}

public func readInt(_ row: Row, columnName: String) -> Int? {
    return SEC.getInt(row, columnName)
}

And so on.

What I would like to do is have a single function:

public func read<T>(_ row: Row, columnName: String) -> T? {
    // call correct SEC.get* method
}

I tried a simple switch statement on T and on T.Type, but no dice. I also tried a modified version of the above:

public func read<T: ReadableType>(_ row: Row, columnName: String) -> T? {
    let getter = T.getterMethod
    return getter(row, columnName)
}

Where I could create an enum of the metatypes that had SEC.get* functions, and extend it to return the proper getter method. This to me seemed ideal. Alas:

public enum ReadableTypes {
    case String.Type       // nope
    case Int.self          // also nope
}

I'm not sure what else to try; it's not the end of the world if I simply have a dozen read* methods, but the code that calls this method could be so dang tight if I could make it generic.


Solution

  • You can individually test to see what type the generic placeholder represents like this:

    if T.self is Int.Type //...
    

    The same sort of test can be done in a switch..case statement.

    Assuming SEC is of type SECType what I'd do is extend SECType to have a generic get method that keys on the return type:

    extension SECType {
      public func get<T>(_ row: Row, _ columnName: String) -> T? {
        // switch on the generic type
        switch T.self {
        // As much as I dislike force-unwrapping an Optional
        // this is about as safe as it gets.
        case is Int.Type   : return getInt   (row, columnName) as! T?
        case is String.Type: return getString(row, columnName) as! T?
        //...
        default: return nil
        }
      }
    }
    

    Now you can write your own read function like:

    public func read<T>(_ row: Row, columnName: String) -> T? {
      return SEC.get(row, columnName)
    }
    

    Of course you can skip doing the extension and just do a generic function with a switch. However, it's harmless to add the method to the type and it makes sense for the type to have a generic method like this.