SwiftUI 学习笔记 12:处理空变量
空引用(字面意义上是变量没有值)是1965年托尼·霍尔(Tony Hoare)发明的。回想起有关空引用的问题,他说:“我称之为十亿美元的错误”,因为它们导致了很多问题。
它专门用于Swift的null引用解决方案(称为optionals)的解决方案。
本质上,一个可选的方法试图回答“如果我们的变量没有值该怎么办?”这个问题,Swift希望确保我们所有的程序都尽可能安全,因此它有一些非常具体且非常重要的功能!–处理这种可能性的技术。
创建一个为空的变量
我们可以让变量为可选项,通过类型后添加?
。如果想要创建一个空的变量,我们还需要让变量等于nil
,例如:
1 | var build: Int? = nil |
分别处理空变量和有值变量(解包可选项)
我们可以使用if let
来处理空变量和有值变量。例如:
1 | var name: String? = nil |
如果使用该name.count
怎么办?真正的字符串具有一个count
属性,该属性存储它具有多少个字母,但这是nil
–它是空,不是字符串,因此没有count
。
因此,尝试读取name.count
是不安全的,Swift不允许这样做。取而代之的是,我们必须查看可选的内容并查看其中的内容-这个过程称为unwrapping
。
展开可选内容的一种常见方法是使用if let
语法,该语法使用条件进行展开。如果可选变量中有一个值,则可以使用它,但是如果没有,则条件失败。
例如:
1 | if let unwrapped = name { |
如果name持有一个字符串,它将被正常放入,我们可以count在条件中读取它的属性。或者,如果name为空,则else代码将运行。
注意:
不要对非可选项的变量使用if let
语法
可选项的变量要使用if let
语法if let
需要将可选内容绑定到一个新的名称,所以需要if let a=b{}
这个格式
其他例子:
1 | let album = "Red" |
其他例子:
1 | var weatherForecast: String? = "sunny" |
可选值不能直接比较,下面的代码是错误的:
1 | var bestScore: Int? = nil |
**下面的代码是正确的:
1 | var bestScore: Int? = nil |
在构建时考虑解包
if let
有一种替代方法是guard let
。guard
展开后跟随的else
表示为空时所经历的事件。
if let
和guard let
之间的区别就是在if let
中创建的解包后的变量只能在if let
中使用,而guard let
创建的解包后的变量在guard let
外面也依然可用。因为可选值在guard let
完成后会留在原处,所以我们可以在函数末尾打印未封装的字符串。例如:
1 | func greet(_ name: String?) { |
使用guard let
,你可以在功能开始时处理问题,然后立即退出。这意味着函数的其余部分是完美的路径-如果一切正确,代码将采用的路径。
而使用if let
就需要将print
放入到if let
之中:
1 | func greet(_ name: String?) { |
注意:
if let
和guard let
还有一点不同,guard let
必须直接跟随else
guard let
必须在最后有一个return
,即使没有返回值。有返回值可以直接return
需要的返回值,没有返回值直接在guard let
…else{}
中的最后部分写return
其他例子:
1 | func double(number: Int?) -> Int? { |
其他例子:
1 | func uppercase(string: String?) -> String? { |
其他例子:
1 | func describe(occupation: String?) { |
强制解包可选值
可选参数表示可能存在或可能不存在的数据,但是有时你可以确定一个值不是nil。在这些情况下,Swift可让你强制打开可选的包装:将其从可选类型转换为非可选类型。
例如,如果你的字符串包含数字,则可以将其转换为Int如下形式:
1 | let str = "5" |
这num
是可选的, Int
因为你可能已尝试转换“ Fish”而不是“ 5”之类的字符串。
即使Swift不确定转换是否会起作用,你也可以看到代码是安全的,因此可以通过编写!
来强制展开结果Int(str)
,如下所示:
1 | let num = Int(str)! |
Swift会立即解开可选项,并使其num
成为常规Int
而不是Int?
。但是,如果你错了 -如果str
某些东西无法转换为整数-你的代码将崩溃。
结果,只有在确定它是安全的时才应强制打开包装。
例如:
1 | let str = "fish" |
num
得到的结果为nil
。
其他例子:
1 | func title(for page: Int) -> String? { |
隐式解包
与常规的可选内容一样,隐式解包的可选内容可能包含一个值,也可能是nil。但是,与常规的可选选项不同,你不需要为使用它们而将它们拆开:你可以像完全不是可选的那样使用它们。
通过在类型名称后添加感叹号来创建隐式解包的可选内容,如下所示:
1 | let age: Int![]= nil |
因为它们的行为就好像它们已经被解开,所以你不需要if let
或guard let
使用隐式解开的可选对象。但是,如果你尝试使用它们,而它们没有任何价值(如果确实如此),则nil你的代码将崩溃。
隐式地存在未包装的可选内容,因为有时变量会以nil
开头,但在使用前始终具有值。因为你知道它们会在你需要它们的时候就具有价值,所以不必一直写会很有帮助的if let
。
话虽这么说,但是如果你能够使用常规的可选参数,那通常是个好主意。
合并(对于没有值的判断并给予默认值)
我们有的时候会让一个函数返回空值,但这对创建变量是不利的。但是我们可以对空值进行判断:
1 | func username(for id: Int) -> String? { |
这是一个正常的函数,它在id = 15
时会返回空值,我们可以用??
为这种情况提供一个默认值(使用??
的过程称之为合并:
1 | let user = username(for: 15) ?? "Anonymous" |
这将检查从username()
函数返回的结果:如果是字符串,则将其解包并放入user
,但是如果是nil
,则将改用“Anonymous”。
注意:
不能合并非可选值
不能合并不同类型的值
其他例子:
1 | var userOptedIn: Bool? = nil |
可选链接
如果格式为a.b.c
,并且b
为可选项,那么我们可以在b
后面添加?
。如果b
返回值为nil
,那么将会忽略其他所有值,直接设置为nil
;如果不是nil
则解包并正常运行。例如:
1 | let names = ["John", "Paul", "George", "Ringo"] |
我们将使用数组的属性first
,如果不为nil
,它将返回名字。然后,我们可以调用uppercased()
结果使其成为大写字符串:
1 | let beatle = names.first?.uppercased() |
这个问号是可选的链接-如果first
返回nil
则Swift不会尝试将其大写,而是立即设置beatle
为nil
。
注意:
因为数组是可选的,所以在使用first
、last
等调用是必须使用?
来展开
其他例子:
1 | let songs: [String]? = [String]() |
其他例子:
1 | let shoppingList = ["eggs", "tomatoes", "grapes"] |
其他例子:
1 | let capitals = ["Scotland": "Edinburgh", "Wales": "Cardiff"] |
其他例子:
1 | let opposites = ["hot": "cold", "near": "far"] |
相关阅读:数组的属性和方法
可选尝试
相关阅读:编写投掷函数
回到我们谈论抛出函数的时候,我们看了下面的代码:
1 | enum PasswordError: Error { |
运行投掷功能,使用do
,try
以及catch
优雅地处理错误。
有两种替代方法try
,既然你了解可选方法并强制展开,则这两种方法都会更有意义。
第一个是try?
,并将throwing
函数更改为返回可选函数的函数。如果函数抛出错误,则将nil
结果发送给你,否则,你将获得返回值,并将其包装为可选值。
使用try?
我们可以checkPassword()
像这样运行:
1 | if let result = try? checkPassword("password") { |
另一种选择是try!,你可以在确定函数不会失败时使用它。如果该函数确实抛出错误,则你的代码将崩溃。
使用try!我们可以将代码重写为:
1 | try![]checkPassword("sekrit") |
可选的初始化容器
在谈论强制展开时,我使用了以下代码:
1 | let str = "5" |
它将字符串转换为整数,但是由于你可能尝试传递任何字符串,因此你实际上返回的是一个可选整数。
这是一个失败的初始化程序:可能有效或无效的初始化程序。你可以使用init?()
而不是init()
,在自己的结构和类中编写这些代码,并nil在出现问题时返回。然后,返回值将是你的类型的可选值,以便你随意展开。
例如,我们可以编写一个Person
必须使用9个字母的ID字符串创建的结构。如果使用了除9个字母的字符串以外的其他任何字符,我们将返回nil
,否则我们将照常继续。
这是Swift中的内容:
1 | struct Person { |
类型转换
Swift必须始终知道每个变量的类型,但是有时你比Swift知道更多的信息。例如,这是三个类:
1 | class Animal { } |
我们可以创建几条鱼和几条狗,并将它们放入数组中,如下所示:
1 | let pets = [Fish(), Dog(), Fish(), Dog()] |
Swift可以看到两者Fish
并Dog
从Animal
类继承,因此它使用类型推断来构成pets
一个数组Animal
。
如果我们要遍历pets
数组并要求所有的狗吠叫,则需要执行类型转换:Swift将检查每个宠物是否都是Dog
对象,如果是,则可以调用makeNoise()
。
这使用了一个名为的新关键字as?
,该关键字返回一个可选参数:nil
如果类型转换失败,则为可选关键字,否则为转换后的类型。
这是我们在Swift中编写循环的方式:
1 | for pet in pets { |
其他例子:
1 | class Person { |
其他例子:
1 | let flavor = "apple and mango" |
其他例子:
1 | class Transport { } |
其他例子:
1 | class Bird { |
总结
- 可选选项使我们以清晰明确的方式表示值的缺失。
- Swift不会允许我们使用if let或使用而不拆开可选的选项guard let。
- 你可以使用感叹号强制打开可选选项,但是如果尝试强制打开nil代码,则代码将崩溃。
- 隐式展开的选件没有常规选件的安全检查。
- 你可以使用nil合并来解开可选值,如果其中没有任何内容,则提供默认值。
- 可选链接使我们可以编写代码来操作可选对象,但是如果可选对象结果为空,则代码将被忽略。
- 你可以try?用来将throwing函数转换为可选的返回值,或者try!在抛出错误时崩溃。
- 如果你在初始化输入不正确时需要初始化失败,请使用init?()创建失败的初始化。
- 你可以使用类型转换将一种类型的对象转换为另一种类型。
一些例子:
1 | struct Dog { |
参考资料
- 感谢你赐予我前进的力量