Generic Function Taking a Type Name in Swift

Generic function taking a type name in Swift

Unfortunately, you cannot explicitly define the type of a generic function (by using the <...> syntax on it). However, you can provide a generic metatype (T.Type) as an argument to the function in order to allow Swift to infer the generic type of the function, as Roman has said.

For your specific example, you'll want your function to look something like this:

func findFirst<T>(in array: [Any], ofType _: T.Type) -> T? {
return array.lazy.compactMap { $0 as? T }.first
}

Here we're using compactMap(_:) in order to get a sequence of elements that were successfully cast to T, and then first to get the first element of that sequence. We're also using lazy so that we can stop evaluating elements after finding the first.

Example usage:

protocol SomeProtocol {
func doSomething()
}

protocol AnotherProtocol {
func somethingElse()
}

extension String : SomeProtocol {
func doSomething() {
print("success:", self)
}
}

let a: [Any] = [5, "str", 6.7]

// Outputs "success: str", as the second element is castable to SomeProtocol.
findFirst(in: a, ofType: SomeProtocol.self)?.doSomething()

// Doesn't output anything, as none of the elements conform to AnotherProtocol.
findFirst(in: a, ofType: AnotherProtocol.self)?.somethingElse()

Note that you have to use .self in order to refer to the metatype of a specific type (in this case, SomeProtocol). Perhaps not a slick as the syntax you were aiming for, but I think it's about as good as you're going to get.

Although it's worth noting in this case that the function would be better placed in an extension of Sequence:

extension Sequence {
func first<T>(ofType _: T.Type) -> T? {
// Unfortunately we can't easily use lazy.compactMap { $0 as? T }.first
// here, as LazyMapSequence doesn't have a 'first' property (we'd have to
// get the iterator and call next(), but at that point we might as well
// do a for loop)
for element in self {
if let element = element as? T {
return element
}
}
return nil
}
}

let a: [Any] = [5, "str", 6.7]
print(a.first(ofType: String.self) as Any) // Optional("str")

Swift generics, same function name for array vs single object possible?

There isn't a way of saying that T will NOT be an array type?

Well, if you can a find a protocol P to which Array doesn't conform, but all the other Ts that you want to use the second overload with does conform to, then you can do:

func processResult<T: Decodable>(_ inputData: Data) -> [T] {
...
}
func processResult<T: Decodable & P>(_ inputData: Data) -> T {
...
}
struct SomeStruct : Decodable, P { ... }

let x: SomeStruct = processResult(someData) // second overload
let y: [SomeStruct] = processResult(someData) // first overload

Is it possible to write a generic function that returns type T and one with the same name that returns [T]?

Of course, you just need to pass in the type parameter explicitly to help the compiler infer what T is:

func processResult<T: Decodable>(_ inputData: Data, _ type: T.Type) -> [T] {
...
}
func processResult<T: Decodable>(_ inputData: Data, _ type: T.Type) -> T {
...
}
struct SomeStruct : Decodable { ... }

let x: SomeStruct = processResult(someData, SomeStruct.self) // second overload
let y: [SomeStruct] = processResult(someData, SomeStruct.self) // first overload

You might think that saying SomeStruct twice is long-winded, but it is very necessary here - without the first SomeStruct, it is ambiguous which overload you want to call, because the two overloads differ only in return type. Without the second SomeStruct, it is ambiguous what T is, because as you said, T can be an array too.

how to pass type to a function with a call with generic type in swift

Define a specific handler function:

func resultadosHandler(resultados: resultados<productResponse>) { // productResponse is the type you expect
switch resultados {
case .datos(let dato): print(dato)
case .error(let error): print(error)
}
}

Pass it to loader function:

dat.loadData(params: parameters.Endpoint(endpoint: end), completion: resultadosHandler)

Or if you prefer, you can use it inline mode like this:

loadData(params: parameters.Endpoint(endpoint: end)) { (resultados: resultados<productResponse>) in
switch resultados {
case .datos(let dato): print(dato)
case .error(let error): print(error)
}
}

Don't forget to replace prints with the correct implementation!

Get the name (string) of a generic type in Swift

A pure swift way to achieve that is not possible.

A possible workaround is:

class MyClass<T: AnyObject> {
func genericName() -> String {
let fullName: String = NSStringFromClass(T.self)
let range = fullName.rangeOfString(".", options: .BackwardsSearch)
if let range = range {
return fullName.substringFromIndex(range.endIndex)
} else {
return fullName
}
}
}

The limitations relies on the fact that it works with classes only.

If this is the generic type:

class TestClass {}

NSStringFromClass() returns the full name (including namespace):

// Prints something like "__lldb_expr_186.TestClass" in playground
NSStringFromClass(TestClass.self)

That's why the func searches for the last occurrence of the . character.

Tested as follows:

var x = MyClass<TestClass>()
x.genericName() // Prints "TestClass"

UPDATE Swift 3.0

func genericName() -> String {
let fullName: String = NSStringFromClass(T.self)
let range = fullName.range(of: ".")
if let range = range {
return fullName.substring(from: range.upperBound)
}
return fullName
}

Swift generic function call underlying method based on generic type

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.

Swift/iOS SDK: Generic Function with Class Type/Name Closure/Block Issue

The correct way to pass a Type is ClassName.self, e.g.

create(A.self)
create(B.self, addParameter: { (b: B) -> Void in
b.test3 = 1000
})

(and I cannot tell you why omitting .self works if the function
is called without additional arguments).

With this correction, your code compiles without errors, but does
not work. As noticed in Swift Generic Factory: Bug?,
you have to add an required init method to your base class,

class Base {
required init() { } // ***
var test1: Int = 0
}

otherwise

var object = aClass()

will always create an object of the base class and not of the actual
type what was passed as an argument (and therefore b.test3 = 1000
in the closure will abort with an exception).

How do I reassign a function which takes a generic type argument in Swift?

The error message is quite clear on what you need to do here - tell the compiler what type T should be. Unfortunately, you can't have a "generic variable" that has an unbound T.

To do this, you need to write out the full type name of the function XCTAssertEquals<T>, which, for the case of T == Double, is:

(@autoclosure () throws -> Double, @autoclosure () throws -> Double, Double, @autoclosure () -> String, StaticString, UInt) -> ()

So you need:

let assertEqual: (@autoclosure () throws -> Double, @autoclosure () throws -> Double, Double, @autoclosure () -> String, StaticString, UInt) -> () = XCTAssertEqual

I know, it's a mess. So if you are going to do this for various Ts, you can first do a type alias for the long function name:

typealias XCTAssertEqualsType<T> = (@autoclosure () throws -> T, @autoclosure () throws -> T, T, @autoclosure () -> String, StaticString, UInt) -> ()

And then you can use XCTAssertEqualsType<Double> and XCTAssertEqualsType<Float> etc.


But honestly though, I don't see why you want to alias this assert function. You lose a lot of its features as a function. You'd have to manually pass in the file name and line number "magic" parameters if you call it via your variable. You lose all the optional arguments, and as I said at the start, you lose the generics.

If all you want is a different name for the function, maybe just declare another function yourself:

func anotherName<T>(_ expression1: @autoclosure () throws -> T, _ expression2: @autoclosure () throws -> T, accuracy: T, _ message: @autoclosure () -> String = "", file: StaticString = #file, line: UInt = #line) where T : FloatingPoint {
XCTAssertEqual(try expression1(), try expression2(), accuracy: accuracy, message(), file: file, line: line)
}


Related Topics



Leave a reply



Submit