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 T
s 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 T
s, 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
Nsurlsession Concurrent Requests With Alamofire
Photopicker Discovery Error: Error Domain=Pluginkit Code=13
The "++" and "--" Operators Have Been Deprecated Xcode 7.3
Send Data from Tableview to Detailview Swift
Why Optional Constant Does Not Automatically Have a Default Value of Nil
What Is the 'Open' Keyword in Swift
Non-'@Objc' Method Does Not Satisfy Optional Requirement of '@Objc' Protocol
Initialize @Stateobject With a Parameter in Swiftui
Computed Read-Only Property VS Function in Swift
Load Local Web Files & Resources in Wkwebview
How to Get Mathemical Pi Constant in Swift
Deleting All Documents in Firestore Collection
Iterate Over Object Class Attributes in Swift
How Add Tabs Programmatically in Uitabbarcontroller With Swift