空(Null)引用——从字面上讲是一个无值的变量——是托尼·霍尔(Tony Hoare)于1965年发明的。回想起来,他说:“我称之为十亿美元的错误(I call it my billion-dollar mistake)”,因为它们会导致很多问题。
这是您学习Swift基础知识的最后一天,它专门用于Swift解决null引用(称为可选类型)的解决方案。这些是一项非常重要的语言功能,但会给您的大脑带来一点伤害——如果您需要重复播放一些视频,那也不要感到难过。
本质上,可选值试图回答以下问题:“如果我们的变量没有值该怎么办?”Swift希望确保我们所有的程序都尽可能安全,因此它具有一些非常具体且非常重要的功能!——处理这种情况的技术。
今天,您有11个一分钟的视频可供观看,并且您将看到解包,可选链,类型转换等更多内容。观看完每个视频后,我们会进行一次简短的测试,以帮助您了解所教的内容。
1. 处理丢失的数据 Handling missing data – test
我们使用了Int
之类的类型来保存5之类的值。但是,如果您想为用户存储年龄属性,如果您不知道某人的年龄,该怎么办?
您可能会说“好吧,我会存储0”,但这样会使新生儿和您不知道年龄的人混淆。您可以使用一个特殊的数字(例如1000或-1)来表示“未知”,这两个年龄都是不可能的年龄,但是您真的会在所有使用过的地方记住该数字吗?
Swift的解决方案称为“可选(optionals)”,您可以将任意类型设置为可选。一个可选的整数可能有一个像0或40这样的数字,但它可能根本没有值——它可能确实不存在,在Swift中为nil
。
要使类型为可选,请在其后添加问号。例如,我们可以这样创建一个可选的整数:
var age: Int? = nil
那没有任何数字——它没有存储任何东西。但是,如果我们以后知道那个年龄,我们可以使用它:
age = 38
2. 解包 Unwrapping optionals – test
可选字符串可能包含诸如“ Hello”之类的字符串,也可能为nil
——没有值。
思考以下可选字符串:
var name: String? = nil
如果我们使用name.count
会怎样?真实的字符串具有一个count
属性,该属性存储它拥有多少个字母,但这是nil
——它是空的内存,不是字符串,因此没有count
。
因此,尝试读取name.count
是不安全的,Swift不允许这样做。相应的,我们必须查看可选值的内容——这个过程称为解包。
解包可选对象的一种常见方法是使用if let
语法,它会与条件一起解包。如果可选内容中包含一个值,则可以使用它,但如果没有,则条件失败。
例如:
if let unwrapped = name {
print("\(unwrapped.count) letters")
} else {
print("Missing name.")
}
如果name
包含一个字符串,它将作为常规String放入unwrapped
,我们可以在条件内读取其count
属性。或者,如果name
为空,则将运行else
代码。
3. 使用guard解包 Unwrapping with guard – test
使用if let
解包的替代方案是guard let
, guard let
将为您解开一个可选的选项,但是如果在其中找到nil
,则希望您退出使用它的函数,循环或条件。
总的来说,if let
和guard let
之间的主要区别在于,在guard
之后,解包后的内容在guard
代码仍然可用。(通俗的说:if let
的解包值在大括号内使用,guard let
的解包值,在大括号外的下文中使用)
让我们尝试使用greet()
函数。这将接受一个可选字符串作为唯一参数,并尝试对其进行解包,但是如果其中没有任何内容,则会显示一条消息并退出。由于使用guard let
解包的可选选项在guard
代码完成后仍会保留,因此我们可以在函数末尾打印解包的字符串:
func greet(_ name: String?) {
guard let unwrapped = name else {
print("You didn't provide a name!")
return
}
print("Hello, \(unwrapped)!")
}
使用guard let
可让您在功能开始时处理问题,然后立即退出。这意味着函数的其余部分是幸福的路径——如果一切正确,代码将采用的路径。
4. 强制解包 Force unwrapping – test
可选参数代表可能存在或可能不存在的数据,但有时您可以肯定地知道该值不是nil
。在这些情况下,Swift可让您强制解包:将其从可选类型转换为非可选类型。
例如,如果您有一个包含数字的字符串,则可以将其转换为如下所示的Int
:
let str = "5"
let num = Int(str)
这使num
成为可选的Int
,因为您可能已尝试转换“Fish”而不是“5”之类的字符串。即使Swift不确定转换是否能正常进行,您也可以看到代码是安全的,因此可以通过在Int(str)
之后编写!
来强制打开结果,像这样:
let num = Int(str)!
Swift将立即打开可选包装,并将num
设置为常规Int
而不是Int?
。但是如果您错了——如果`str·是无法转换为整数的内容——您的代码将崩溃。
5. 隐式解包 Implicitly unwrapped optionals – test
像常规的可选内容一样,隐式解包的可选内容可能包含一个值,或者它们可能为nil
。但是,与常规的可选选项不同,您无需解开它们即可使用它们:您可以像完全不是可选的那样使用它们。
通过在您的类型名称后添加感叹号来创建隐式解包的可选内容,如下所示:
let age: Int! = nil
由于它们的行为就好像它们已经被解包一样,因此不需对隐式解包的可选对象使用if let
或guard let
。但是,如果您尝试使用它们而没有任何值——如果它们为nil
——您的代码将崩溃。
存在隐式解包的可选参数,因为有时变量将以nil
开始,但在需要使用它之前始终具有值。因为您知道它们会在您需要它们的时候就具有价,所以不必一直if let
,这很有用。
话虽这么说,但是如果您能够使用常规的可选值,通常是个好主意。
6. 空合运算 Nil coalescing – test
空合运算符展开一个可选的,如果有,则返回其中的值。如果没有值(如果可选值为nil
),则使用默认值。不管是哪种方式,结果都不是可选的:它要么是由可选的内部值决定的,要么是用备份的默认值。
下面是一个函数,它接受一个整数作为它的唯一参数并返回一个可选字符串:
func username(for id: Int) -> String? {
if id == 1 {
return "Taylor Swift"
} else {
return nil
}
}
如果我们使用ID 15调用它,我们将返回nil
,因为无法识别用户,但是使用空合运算,我们可以提供一个默认值“Anonymous”,如下所示:
let user = username(for: 15) ?? "Anonymous"
这将检查从username()
函数返回的结果:如果它是一个字符串,那么它将被展开并放入用户中,但是如果它的内部有nil
,那么将使用“Anonymous”。
7. 可选链 Optional chaining – test
Swift在使用optionals时为我们提供了一个快捷方式:如果您想访问a.b.c
但是b
是可选的,您可以在它后面写一个问号以启用可选链:a.b?.c
。
当代码运行时,Swift将检查b
是否有一个值,如果是nil
,则该行的其余部分将被忽略——Swift将立即返回nil
。但是,如果它有一个值,它将被展开并继续执行。
要尝试此操作,请使用以下名称数组:
let names = ["John", "Paul", "George", "Ringo"]
我们将使用该数组的第一个属性,如果有一个,则返回第一个名称;如果数组为空,则返回nil
。然后,我们可以对结果调用uppercased()
使其成为大写字符串:
let beatle = names.first?.uppercased()
这个问号是可选链,如果首先返回nil
,那么Swift不会尝试大写,并会立即将beatle
设置为nil
。
8. 可选try Optional try – test
回到在我们讨论抛出函数时,我们看到了以下代码:
enum PasswordError: Error {
case obvious
}
func checkPassword(_ password: String) throws -> Bool {
if password == "password" {
throw PasswordError.obvious
}
return true
}
do {
try checkPassword("password")
print("That password is good!")
} catch {
print("You can't use that password.")
}
它运行一个抛出函数,使用do
、try
和catch
来优雅地处理错误。
有其他两种方法来写try
,这两种方法都会更有意义,因为你已经了解了可选值和强制解包。
首先是try?
,并将抛出函数更改为返回可选函数的函数。如果函数抛出一个错误,则结果为nil
,否则返回值将被包装为可选值。
使用try?
我们可以这样运行checkPassword()
:
if let result = try? checkPassword("password") {
print("Result was \(result)")
} else {
print("D'oh.")
}
另一个选择是try!
,在确定函数不会失败时可以使用。如果函数确实抛出错误,则代码将崩溃。
使用try!
我们可以将代码重写为:
try! checkPassword("sekrit")
print("OK!")
9. 可失败初始化器 Failable initializers – test
在谈到强制解包时,我使用了以下代码:
let str = "5"
let num = Int(str)
它将字符串转换为整数,但因为您可能会尝试在那里传递任何字符串,所以实际上得到的是一个可选整数。
这是一个可失败的初始值设定项:一个可能工作也可能不工作的初始值设定项。您可以在自己的结构和类使用中init?
编写他们,而不是init()
,如果出现问题则返回nil
。然后,返回值将是您的类型中的一个可选值,供您根据需要展开。
例如,我们可以编写一个Person
结构,该结构必须使用九个字母的ID字符串创建。如果使用的不是9个字母的字符串,我们将返回nil
,否则我们将继续正常工作。
struct Person {
var id: String
init?(id: String) {
if id.count == 9 {
self.id = id
} else {
return nil
}
}
}
10. 类型转换 Typecasting – test
Swift必须始终知道每个变量的类型,但有时你知道的信息比Swift多。例如,这里有三个类:
class Animal { }
class Fish: Animal { }
class Dog: Animal {
func makeNoise() {
print("Woof!")
}
}
我们可以创建两条鱼和两只狗,然后将它们放入一个数组中,如下所示:
let pets = [Fish(), Dog(), Fish(), Dog()]
Swift可以看到Fish
和Dog
都继承自Animal
,因此它使用类型推断使pets
成为一个Animal
数组。
如果我们想在pets
数组上循环并要求所有的狗吠叫,我们需要执行一个类型转换:Swift将检查每个宠物是否是一个Dog
对象,如果是,我们可以调用makeNoise()
。
这使用了一个新的关键字as?
,它返回一个可选值:如果类型转换失败,则返回nil
,否则返回转换后的类型。
下面是我们如何用Swift编写循环:
for pet in pets {
if let dog = pet as? Dog {
dog.makeNoise()
}
}
关于类型转换的其他更好的写法可参考:Swift编程小技巧中的相关内容。
11. 可选值:总结 Optionals summary – test
您已经完成了本系列的第十部分,所以让我们总结一下:
1、可选类型让我们以一种清晰明确的方式来表示一个值的缺失。
2、Swift不会让我们在没有解包的情况下使用可选值,无论是使用if let
还是使用guard let
。
3、可以使用感叹号强制解包,但如果尝试强制展开nil
,则代码将崩溃。
4、隐式解包没有常规可选值的安全检查。
5、您可以使用空合运算符(nil coalescing)??
来展开一个可选值,如果里面没有任何内容,则提供一个默认值。
6、可选链允许我们编写代码来操作可选值,但是如果可选值的结果是空,则忽略代码,并返回nil
。
7、你可以使用try?
要将抛出函数转换为可选返值,或者使用try!
,但是在抛出错误时会崩溃。
8、如果您需要初始化器在输入错误时失败,请使用init?
设置可失败初始化器。
9、可以使用类型转换将一种对象类型转换为另一种对象类型。
网友评论