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

19

我正在尝试创建一个通用的函数,可以接受可选参数。

以下是我的代码:

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?)'

我做错了什么,应该如何正确声明/使用这个函数?另外,我希望保持函数的使用尽可能简单(我不想做像nil as String?这样的事情)。
7个回答

13
我猜编译器无法从 nil 中推断出 T 的类型。

但是下面的示例可以正常运行:

somethingGeneric(Optional<String>.None)


nil as String? 这个操作基本上也是这样实现的。 - Will M.
@WillM。我还提到了我不想要那种行为。 - Coder-256
我认为这是没有办法避免的。除非还有 func somethingGenericWithNil(),并调用它 :) - Mike Pollard

9
我认为您过于复杂化了问题,因为要求能够传递未定义类型的“nil”(实际上并不存在未定义类型的“nil”,即使“nil”也有一个类型)。虽然您答案中的方法似乎可行,但它允许创建“??”类型,因为Optional promotion。通常情况下这样做可以成功,但我曾经看到它以非常令人沮丧的方式失败,调用了错误的函数。问题在于,“String”可以隐式提升为“String?”,而“String?”可以隐式提升为“String??”。当“??”出现时,几乎总是会引起混淆。
正如MartinR指出的那样,您的方法不太直观,无法确定哪个版本会被调用。“UnsafePointer”也是“NilLiteralConvertible”。因此,很难推断将调用哪个函数。 “难以推断”使其成为令人困惑的错误来源。
只有在传递文字“nil”时才存在问题。如Valentin所指出的,如果传递的变量恰好是“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

Rob的回答非常出色,我留言是因为这绝对应该成为被采纳的答案。 - Ilias Karim
你说得很对,@RobNapier - 我想我在测试时复制和粘贴出了问题。我只是要删除原始评论。 - GSnyder

5

很好的问题和答案。我有一个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*

2
我明白了:
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*

2
你的 somethingGeneric<T>(input:T?) 函数仍然不接受 nil 作为值。它只是执行了空函数,因为该函数没有做任何事情。如果你使用 nil 调用该函数,它将通过带有 input: NilLiteralConvertible 参数的函数,但如果你使用 nil Optional 调用它,例如说 let x: String?,它将通过带有 input: T? 参数的函数,所以你需要复制你的 nil 处理逻辑。 - Will M.
2
请注意,例如 let ptr : UnsafePointer<Int>? = ... ; somethingGeneric(ptr) 会调用第二个函数,因为UnsafePointerNilLiteralConvertible,而第二个通用函数更具体。 - Martin R
1
@MartinR 所有的可选项都是 NilLiteralConvertible,但是一个可选字符串仍然会进入第一个函数,无论它是 Optional.Some 还是 Optional.None。 - Will M.
2
@WillM:第二个函数需要一个NilLiteralConvertible?,而String?不符合该类型(因为String不是NilLiteralConvertible)。 - Martin R
2
@WillM。这是可选的晋升。在您所描述的情况下,编译器正在晋升为“String??”,与“NilLiteralConvertible?”相匹配。我的经验是,这种使用泛型的方式非常脆弱,并且可选促进会以令人惊讶的方式损坏您的代码,创建“??”类型。我建议以某种方式约束“T”。 - Rob Napier
显示剩余5条评论

2

我认为你不会调用 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)

2

实际上有一种方法可以做到这一点,受到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)

解决方案定义(来源

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

1
这里是我想出的解决方案,它可以在Swift 5上编译,因为这里的许多解决方案对我来说都无法编译。它可能被认为是hacky,因为我使用了一个存储变量来帮助事情顺利进行。我无法想出一个Swift 5版本的nil参数,这些参数解析为类型T
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
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接