Swift - 在带有可选参数的泛型函数中将 Nil 作为参数

Swift - Take Nil as Argument in Generic Function with Optional Argument

我正在尝试创建一个可以接受可选参数的通用函数。 这是我目前所拥有的:

func somethingGeneric<T>(input: T?) {
    if (input != nil) {
        print(input!);
    }
}

somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(nil) // Errors!

如图所示,它适用于 String,但不适用于 nil。 将它与 nil 一起使用会出现以下两个错误:

error: cannot invoke 'somethingGeneric' with an argument list of type '(_?)'
note: expected an argument list of type '(T?)'

我做错了什么,我应该如何正确declare/use这个功能?另外,我想尽可能简单地使用函数(我不想做类似 nil as String?)的事情。

我猜编译器无法从 nil.

中找出 T 是什么

以下工作正常,例如:

somethingGeneric(Optional<String>.None)

我想通了:

func somethingGeneric<T>(input: T?) {
    if (input != nil) {
        print(input!);
    }
}

func somethingGeneric(input: NilLiteralConvertible?) {}


somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(nil) // *nothing printed*
somethingGeneric(nil as String?) // *nothing printed*

我认为你永远不会调用 somethingGeneric(nil) 但大多数情况下 somethingGeneric(value)somethingGeneric(function()) 编译器有足够的信息不会被卡住试图猜测类型:

func somethingGeneric<T>(input: T?) {
    if let input = input {
        print(input);
    }
}

func neverString() -> String? {
    return nil
}

let a: String? = nil

somethingGeneric("Hello, World!") // Hello, World!
somethingGeneric(a) // Nothing and no error
somethingGeneric(neverString()) // Nothing and no error

此外,我会使用 if let 语法而不是 if(value != nil)

我认为您要求能够传递未类型化的 nil(实际上并不存在;甚至 nil 也有类型),从而使问题过于复杂。虽然您的答案中的方法似乎有效,但由于 Optional 提升,它允许创建 ?? 类型。你经常会很幸运并且成功了,但我已经看到它以非常令人沮丧的方式爆炸并且调用了错误的函数。问题是 String 可以隐式提升为 String?String? 可以隐式提升为 String??。当 ?? 隐含地出现时,混乱几乎总是随之而来。

正如 MartinR 指出的那样,您的方法对于调用哪个版本不是很直观。 UnsafePointer 也是 NilLiteralConvertible。因此很难推断将调用哪个函数。 “难以推理”使其成为令人困惑的错误的可能来源。

唯一一次出现问题是在传递文字时 nil。正如@Valentin 指出的那样,如果您传递的变量恰好 be nil,则没有问题;你不需要特殊情况。为什么要强制调用者传递一个未类型化的 nil?让来电者什么也不传递。

我假设 somethingGeneric 在通过 nil 的情况下做了一些真正有趣的事情。如果不是这样;如果您显示的代码表示真正的功能(即所有内容都包含在 if (input != nil) 检查中),那么这不是问题。只是不要打电话 somethingGeneric(nil);这是一个可证明的空操作。只需删除代码行。但我假设还有一些“其他工作”。

func somethingGeneric<T>(input: T?) {
    somethingGeneric() // Call the base form
    if (input != nil) {
        print(input!);
    }
}

func somethingGeneric() {
   // Things you do either way
}

somethingGeneric(input: "Hello, World!") // Hello, World!
somethingGeneric() // Nothing

很好的问答。我有一个 Swift 4 更新要贡献:

var str: String? = "Hello, playground"
var list: Array<String>? = ["Hello", "Coder256"]

func somethingGeneric<T>(_ input: T?) {
  if (input != nil) {
    print(input!);
  }
}

func somethingGeneric(_ input: ExpressibleByNilLiteral?) {}


somethingGeneric("Hello, World!")    // Hello, World!
somethingGeneric(nil)                // *nothing printed*
somethingGeneric(nil as String?)     // *nothing printed*
somethingGeneric(str)                // Hello, playground
str = nil
somethingGeneric(str)                // *nothing printed*
somethingGeneric(list)               // ["Hello", "Coder256"]
list = nil
somethingGeneric(list)               // *nothing printed*

这是我想出的在 Swift 5 上编译的解决方案,因为这里的许多解决方案都没有为我编译。它可能被认为是 hacky,因为我使用存储变量来帮助事情进展。我无法想出解析为类型 T.

的 nil 参数的 Swift 5 版本
class MyClass {
    func somethingGeneric<T>(input: T?) {
        if let input = input {
            print(input)
        }
    }

    func somethingGeneric() {
        somethingGeneric(Object.Nil)
    }

}

final class Object {
    static var Nil: Object? //this should never be set
}

实际上有一种方法可以做到这一点,灵感来自 Alamofire 的内部代码。

您无需安装 Alamofire 即可使用此解决方案。

用法

你有问题的方法定义

func someMethod<SomeGenericOptionalCodableType: Codable>(with someParam: SomeGenericOptionalCodableType? = nil) {
        // your awesome code goes here
}

有效的方法 ✅

// invoke `someMethod` correctly 
let someGoodParam1 = Alamofire.Empty.value
someMethod(with: someGoodParam1)

我认为可以在 someMethod 定义中使用 Alamofire.Empty.value 作为默认值作为参数。

什么不起作用 ❌

// invoke `someMethod` incorrectly
let someBadParam1: Codable? = nil
let someBadParam2 = nil
someMethod(with: someBadParam1)
someMethod(with: someBadParam2)

解决方案定义(source

/// Type representing an empty value. Use `Empty.value` to get the static instance.
public struct Empty: Codable {
    /// Static `Empty` instance used for all `Empty` responses.
    public static let value = Empty()
}