Swift可选的inout参数和nil

22

在 Swift 中,是否可能将一个 Optionalinout 参数传递给函数?我正在尝试实现以下内容:

func testFunc( inout optionalParam: MyClass? ) {
    if optionalParam {
        ...
    }
}

...但是当我尝试调用它并传递nil时,却出现了一个奇怪的编译错误:

Type 'inout MyClass?' does not conform to protocol 'NilLiteralConvertible'
我不明白为什么我的课程已经被声明为可选,却还需要符合某些特殊的协议。
4个回答

31

由于函数期望接收引用类型的参数,但是你传递了 nil,所以它无法编译。问题与可选类型无关。

通过使用 inout 声明参数意味着你将在函数体内给它赋值。但是如何将值赋给 nil

你需要这样调用它:

var a : MyClass? = nil
testFunc(&a) // value of a can be changed inside the function
如果您了解C ++,那么这是没有可选项的代码的C ++版本。
struct MyClass {};    
void testFunc(MyClass &p) {}
int main () { testFunc(nullptr); }

而你遇到了这个错误信息

main.cpp:6:6: note: candidate function not viable: no known conversion from 'nullptr_t' to 'MyClass &' for 1st argument

这有点类似于你得到的那个(但更易于理解)


1
所以我猜答案是“不”。我相信在Objective-C中这是可能的。噢,算了。 - devios1
@chaiguy也许你可以尝试使用指针,像AutoreleasingPointer<MyClass>。但在我看来,这很丑陋。 - Bryan Chen
@devios 在 Objective-C 中肯定是可能的。 - user1951992
这就是为什么 nil 是有毒的。 - Pavel Zaitsev
Swift的示例很好,使其成为可能。 - Chewie The Chorkie

5
实际上,@devios1 需要的是“可选指针”。但是 inout MyClass? 表示“指向可选项的指针”。以下代码适用于 Swift 4。
class MyClass {
    // func foo() {}
}

func testFunc(_ optionalParam: UnsafeMutablePointer<MyClass>? ) {
    if let optionalParam = optionalParam {
        // optionalParam.pointee.foo()
        // optionalParam.pointee = MyClass()
    }
}

testFunc(nil)

var myClass = MyClass()
testFunc(&myClass)

3
这是可能的,也许与Closure有密切关联。()
可选项可以具有nil值,但它不期望是nil。因此,如果您只是不将可选项传递给函数或传递一个类型为MyClass?且值为nil的变量,则可选项将起作用。就像Bryan所说的那样。
当您编写函数时,必须有一个默认值才能成为可选参数,如下所示:
func testFunc(inout optionalParam:MyClass?={var nilRef:MyClass?;return &nilRef}()) {
    if optionalParam != nil {
        ...
    }
}

注意,如果将optionalParam {...}更改为if optionalParam != nil {...}

您不能在未检查它的情况下取消包装optionalParam,即if optionalParam? != nil,因为当optionalParam==nil时,取消包装optionalParam?会失败。请注意,var a :MyClass?的值为nil,直到被分配。

调用可以是testFunc(optionalParam:&a)testFunc(),但永远不要使用testFunc(nil)

您可能会混淆两个不同的概念,因为它们具有相似的名称,可选参数和可选项。可选项是具有扩展nil条件的变量。可选参数是可选的函数参数。

更多阅读此处。苹果正在试图将措辞从可选参数更改为“带有默认值的参数”。不幸的是,他们没有在这些新的可选项中合并可选参数行为。如果将nil可选项作为可选项传递,但在解包时会失败,那么这样做的意义何在?也许这个可选项的本质更深入人心,如果它可以在解包之前被传递... 薛定谔的猫


我忘了在try块中提到常见的catch语句,它应该是if a == nil {a = MyClass();} - izzy
1
不幸的是,这段代码在我使用Xcode上的Swift 3.0中似乎无法编译通过:我收到了错误信息“'&' can only appear immediately in a call argument list”。 - Todd

1

文档中的确切段落如下:

只能将变量作为输入输出参数的参数传递。 您不能将常量或文字值作为参数传递,因为 常量和文字值无法修改。当您将变量作为参数传递给 输入输出参数时,直接在变量名称前放置 ampersand (&) 表示该变量可以被函数修改。

然而需要注意的是,只要您打算或希望允许实例被/被替换为另一个实例,而不仅仅是原始实例被修改,就必须将类作为 inout (也称为引用)传递。覆盖此内容的文档段落如下:

输入输出参数

如上所述,变量参数只能在函数内部进行更改。如果您希望函数修改参数的值,并希望这些更改在函数调用结束后仍然存在,请将该参数定义为输入输出参数。

以下是三个测试,涵盖了var和inout的用法。

class Bar : Printable {
    var value = 1

    init(_ value:Int) { self.value = value }
    var description:String { return "Bar is: \(value)" }
}

let bar = Bar(1)
func changeBarWithoutInoutSinceBarIsAClassYaySwift(b:Bar) { b.value = 2 }
changeBarWithoutInoutSinceBarIsAClassYaySwift(bar)
println("after: \(bar)") // 2

var bar2 = Bar(0)
func tryToReplaceLocalBarWithoutInout(var b:Bar) { b = Bar(99) }
tryToReplaceLocalBarWithoutInout(bar2)
println("after: \(bar2)") // 0

var bar3 = Bar(0)
func replaceClassInstanceViaInout(inout b:Bar) { b = Bar(99) }
replaceClassInstanceViaInout(&bar3)
println("after: \(bar3)") // 99

实际上,对于引用(类),inout 的工作方式与其相同。除了结构体的值是结构体的所有元素之外,引用的值仅是引用本身。如果您传递NSMutableString thisString,则无法将其更改为NSMutableString thatString,除非使用var或inout。更改字符不会更改引用。 - gnasher729
@gnasher,感谢您的发帖,这更有意义了。我相应地更新了我的答案。 - Chris Conover

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