gpt4 book ai didi

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

转载 作者:可可西里 更新时间:2023-11-01 02:19:38 25 4
gpt4 key购买 nike

我是 Swift 和 iOS 编码的新手,一直在努力编写我的第一个应用程序。虽然我的编程背景非常重要,但我来自 Python 和 C# 背景,其中几乎所有内容都可以是 Nonenull 并且由用户在运行时检查一个空。我发现 Swift 中“可空类型与不可空类型”或“可选类型”的整个概念令人困惑。

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

问题是,当我查看我的代码设计时,感觉代码中的一切 都必须是“可空的”。感觉这要么意味着我没有正确思考我的代码应该如何运行,要么我缺少一些关键的理解。

让我们举一个我感到困惑的最简单的例子。假设我有两个类——一个存储和管理某种数据,另一个提供对该数据的访问。 (这方面的一个例子可能是数据库连接、文件句柄或类似的东西。)让我们调用包含数据的类 myData 和使用该数据的类 myObject.

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

我知道 Swift 有两种定义变量的方法:varlet,其中 let 类似于某些语言的 const 指令。由于数据连接将贯穿类的整个生命周期,let 似乎是一个显而易见的选择。但是,我不知道如何通过 let 定义将在运行时分配的类级变量。因此,我使用类似

var dataConnection: myData?

在任何函数之外的类中。

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

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,显式解包本身不会导致运行时错误(可以被捕获和处理)吗?当把事情串在一起时,这往往是一场噩梦。我不得不做类似的事情:

self.dataConnection!.somethingReturningAnObject!.thatObjectsVariable!.someMethod()

var myData? = self.dataConnection
var anotherObject? = myData!.somethingReturningAnObject
...

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

我是否必须始终如一地处理包装和展开变量 - 即使是那些一开始就应该永远不会为 null 但根本无法在编译时分配的变量?

最佳答案

However, I do not know how to define a class-level variable via let which will be assigned at runtime.

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

But now I have to deal with the nullable data type, and do explicit unwrapping every time I use it anywhere.

但这就是隐式解包 Optionals 的用途。例如,StoryBoard 将所有 @IBOutlets 定义为隐式解包,因为语义非常清楚:在进入 viewDidLoad() 时以及之后的任何地方,解包都是安全的。如果您可以向自己证明清晰的语义,您也可以这样做。

所以你有大约 4 个选择:

A) 在类级别声明为隐式解包:

let dataConnection: MyData!

并强制在构造函数中初始化它:

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

从那时起你就不需要'!';应该清楚,隐式解包总是安全的。

B) 如果此时它的初始化是可靠和合理的,就在它的声明中初始化它,让你放弃整个 Optionals 的概念:

let dataConnection = SomeClass.someStaticMethod()

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

var dataConnection: MyData!

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

D) 最“不可预测”的情况。将其声明为显式可选,因为在类的整个生命周期中,数据连接会来来去去:

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 路)。

至于你的意大利面条代码,你想研究可选链,只需要检查最终结果是否为 nil。

关于Swift:对可空/可选类型感到困惑,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31686040/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com