Swift:对可空/可选类型感到困惑

时间:2015-07-28 20:03:43

标签: swift optional

我是Swift和iOS编码的新手,并且一直在编写我的第一个应用程序。虽然我的编程背景非常重要,但我来自Python和C#背景,其中几乎任何东西都可以Nonenull,并且由用户在运行时检查null 。我发现了这个 可空与非可空类型的整个概念"或"可选类型"在斯威夫特中令人困惑。

我理解核心概念是声明为类似myObject的类型的变量不能设置为nil。但是,如果我将其定义为myObject?类型,则可以将值设置为nil

问题在于,当我查看我的代码设计时,感觉一切必须是"可空的"在我的代码中。感觉就像这意味着我没有正确思考我的代码应该如何运行,或者我错过了一些重要的理解。

让我们把最困惑的事情放在最简单的例子上。假设我有两个类 - 一个用于存储和管理某种数据,另一个用于提供对该数据的访问。 (例如,这可能类似于数据库连接,文件句柄或类似的东西。)让我们调用包含数据myData的类和使用该数据的类{{1} }。

myObject需要对myObject的类级引用,因为它的许多方法都依赖于对该类的本地引用。因此,构造函数所做的第一件事就是生成数据连接,然后将其存储在局部变量myData中。该变量需要在类级别定义,以便其他方法可以访问它,但它将在构造函数中分配。无法获得连接将导致某种异常,从而干扰类的创建。

我知道Swift有两种定义变量的方法:dataConnectionvar,其中let类似于某些语言' let指令。由于数据连接将在整个课程期间持续存在,const似乎是一个明显的选择。但是,我不知道如何通过let定义一个类级变量,它将在运行时分配。因此,我使用像

这样的东西
let

在课外任何功能。

但是现在我必须处理可以为空的数据类型,并且每次在任何地方使用它时都要明确展开 。至少可以说令人困惑的是令人沮丧。

var dataConnection: myData?

这似乎是解决问题的极其冗长的方式。如果一个对象是func dealWithData() { self.dataConnection.someFunctionToGetData() <- results in an unwrapping error. self.dataConnection!.someFunctionToGetData() <- works. let someOtherObjectUsingData: otherObject = self.getOtherObject() <- may result in error unless type includes ? someOtherObjectUsingData.someMethod(self.dataConnection) <- unwrap error if type included ? var myData = self.dataConnection! someOtherObjectUsingData.someMethod(myData) <- works } func somethingNeedingDataObject(dataObject: myData?) { // now have to explicitly unwrap let myDataUnwrapped = myData! ... } ,那么显式解包并不会导致运行时错误(可能被捕获和处理)?当把事物串在一起时,这往往是一场噩梦。我必须做类似的事情:

nil

我以前这样做的方法是你只需要定义一个变量,如果它被设置为null并且你尝试用它做一些事情,就会抛出一个异常(你可以捕获和处理) 。这简直不是Swift中的工作方式吗?这让我很困惑,几乎每次我尝试编译应用程序时,都会遇到很多错误(我只是让Xcode修复它们)。但这不是处理它的最佳方式。

我是否必须始终如一地处理包装和解包变量 - 即使是那些预期永远不会为空但却无法在编译时分配的变量?

1 个答案:

答案 0 :(得分:1)

  

但是,我不知道如何通过let定义一个类级变量,它将在运行时分配。

这部分很容易。只需使用let代替var即可。使用Swift 1.2及更高版本,您可以延迟let的实际分配。编译器足够聪明,可以进行流分析,并确保在所有路径中分配一次,并且只分配一次。因此,对于类范围的let,赋值也可以在构造函数中发生。

  

但是现在我必须处理可以为空的数据类型,并且每次在任何地方使用它时都要进行显式解包。

但这是隐含的未包装的Optionals所针对的。例如,StoryBoard将所有@IBOutlets定义为隐式展开,因为语义非常明确:在进入viewDidLoad()之后和之后的任何地方,展开都是安全的。如果你能为自己证明清晰的语义,你也可以这样做。

所以你有大约4个选择:

A)在类级别声明为隐式展开:

let dataConnection: MyData!

并被迫在构造函数中初始化它:

init() {
    let whateverObj = someInitialCalculation()
    dataConnection = whateverObj.someWayOfGettingTheConnection()
}

从那以后,你不需要&#39;!&#39 ;;应该清楚的是隐式展开始终是安全的。

B)如果初始化是可靠且明智的,那么在其声明中对其进行初始化,允许您放弃Optionals的整个概念:

let dataConnection = SomeClass.someStaticMethod()

C)在类级别声明为var,作为隐式可选:

var dataConnection: MyData!

你不必在构造函数中初始化它;让它为nil,直到可以/应该计算它的值。您仍然需要一些流量分析来证明某一点之后,如@IBOutlets的情况,访问它将始终有效

D)最不可预测的&#39;案件。将其声明为显式可选项,因为在整个类的生命周期中,数据连接将会出现:

var dataConnection: MyData?

func someMethodThatHandlesData() {
  if let dC = dataConnection {
      dc.handleSomeData()
  }
  else {
      alert("Sorry, no data connection at the moment. Try again later.")
  }
}

我认为你想象Swift总是迫使你走下D)。

就你的spaghetti-string代码而言,你想要查看Optional Chaining,只需要检查nil的最终结果。