一尘不染

通用函数在Swift中采用类型名称

swift

在C#中,可以通过指定类型来调用泛型方法:

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

我需要的是一种使用泛型将类型传递给函数并返回该类型对象的方法

这可行,但是不适合我想做的事:

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()

该语法很尴尬。在C#(与Swift一样强大的类型)中,您可以执行以下操作:

findFirst<MyProtocol>(array).SomeMethodInMyProtocol();

可悲的是,这在Swift中是不可能的。

因此,问题是: 有没有一种方法可以使用更简洁(不太尴尬)的语法来完成此任务。


阅读 248

收藏
2020-07-07

共1个答案

一尘不染

不幸的是,您不能显式定义泛型函数的类型(通过使用其<...>语法)。但是,您 可以 提供泛型元类型(T.Type)作为函数的 参数,以允许Swift推断函数的泛型,如Roman所说。

对于您的特定示例,您将希望函数看起来像这样:

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")
2020-07-07