Общая функция, использующая имя типа в Swift
В С# можно вызвать общий метод, указав тип:
public T f<T>()
{
return something as T
}
var x = f<string>()
Swift не позволяет вам специализировать общий метод при его вызове. Компилятор хочет полагаться на вывод типа, поэтому это невозможно:
func f<T>() -> T? {
return something as T?
}
let x = f<String>() // not allowed in Swift
Мне нужен способ передать тип функции, и эта функция возвращает объект этого типа, используя generics
Это работает, но это не очень подходит для того, что я хочу сделать:
let x = f() as String?
РЕДАКТИРОВАТЬ (ПОДТВЕРЖДЕНИЕ)
Я, вероятно, не совсем понял, что вопрос на самом деле, все о более простом синтаксисе для вызова функции, которая возвращает данный тип (любой тип).
В качестве простого примера предположим, что у вас есть массив Any, и вы создаете функцию, которая возвращает первый элемент данного типа:
// returns the first element in the array of that type
func findFirst<T>(array: [Any]) -> T? {
return array.filter() { $0 is T }.first as? T
}
Вы можете вызвать эту функцию следующим образом:
let array = [something,something,something,...]
let x = findFirst(array) as String?
Это довольно просто, но что, если возвращаемый тип - это какой-то протокол с методом и вы хотите вызвать метод на возвращаемом объекте:
(findFirst(array) as MyProtocol?)?.SomeMethodInMyProtocol()
(findFirst(array) as OtherProtocol?)?.SomeMethodInOtherProtocol()
Этот синтаксис просто неудобен. В С# (который так же сильно напечатан как Swift) вы можете сделать это:
findFirst<MyProtocol>(array).SomeMethodInMyProtocol();
К сожалению, это невозможно в Swift.
Итак, вопрос: есть способ сделать это с помощью более чистого (менее неудобного) синтаксиса.
Ответы
Ответ 1
К сожалению, вы не можете явно определить тип универсальной функции (используя синтаксис <...>
для нее). Тем не менее, вы можете предоставить универсальный T.Type
(T.Type
) в качестве аргумента функции, чтобы позволить Swift выводить универсальный тип функции, как сказал Роман.
Для вашего конкретного примера вы хотите, чтобы ваша функция выглядела примерно так:
func findFirst<T>(in array: [Any], ofType _: T.Type) -> T? {
return array.lazy.compactMap { $0 as? T }.first
}
Здесь мы используем compactMap(_:)
, чтобы получить последовательность элементов, которые были успешно преобразованы в T
, а затем first
получить первый элемент этой последовательности. Мы также используем lazy
чтобы мы могли перестать оценивать элементы после нахождения первого.
Пример использования:
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()
Обратите внимание, что вы должны использовать .self
для ссылки на метатип определенного типа (в данном случае SomeProtocol
). Возможно, не такой уж интересный синтаксис, на который вы нацеливались, но я думаю, что он настолько хорош, насколько это возможно.
Хотя в этом случае стоит отметить, что эту функцию лучше разместить в расширении 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")
Ответ 2
Что вам, вероятно, нужно сделать, так это создать протокол, который выглядит примерно так:
protocol SomeProtocol {
init()
func someProtocolMethod()
}
Затем добавьте T.Type
в качестве параметра в свой метод:
func f<T: SomeProtocol>(t: T.Type) -> T {
return T()
}
Тогда предположим, что у вас есть тип, соответствующий SomeProtocol
следующим образом:
struct MyType: SomeProtocol {
init() { }
func someProtocolMethod() { }
}
Затем вы можете вызвать свою функцию следующим образом:
f(MyType.self).someProtocolMethod()
Как отмечали другие, это кажется запутанным способом делать то, что вы хотите. Если вы знаете тип, например, вы можете просто написать:
MyType().someProtocolMethod()
Нет необходимости в f
.