Swift基础-可选链,解包和类型转换

2019-12-18  本文已影响0人  Augs

处理丢失的数据

我们使用了诸如Int这样的类型来保存诸如5之类的值。但是,如果您想为用户存储属性age,但是您不知道某人的年龄,该怎么办?

你可能会说“好吧,我会存储0”,但这样你就会混淆新生婴儿和年龄你不知道的人。你可以用一个特殊的数字,比如1000或-1来表示“未知”,这两个数字都是不可能存在的年龄,但是你真的会记得在所有使用过的地方都有这个数字吗?

Swift的解决方案称为optionals,您可以使任意类型的options。一个可选的整数可能有一个像0或40这样的数字,但它可能根本没有值nil-它可能确实不存在,在Swift中是这样。

要使类型为可选,请在其后添加问号。例如,我们可以这样创建一个可选的整数:

var age: Int? = nil

那没有任何数字-它没有任何东西。但是,如果我们以后知道那个年龄,我们可以使用它:

age = 38

解开可选项

可选字符串可能包含“ Hello”之类的字符串,也可能为nil-完全没有。

考虑以下可选字符串:

var name: String? = nil

如果使用name.count怎么办?真正的字符串具有一个count属性,该属性存储它具有多少个字母,但这是nil–它是空的内存,不是字符串,因此没有count

因此,尝试读取name.count是不安全的,Swift不允许这样做。取而代之的是,我们必须查看可选的内容并查看其中的内容-这个过程称为unwrapping

展开可选内容的一种常见方法是使用if let语法,该语法使用条件进行展开。如果可选变量中有一个值,则可以使用它,但是如果没有,则条件失败。

例如:

if let unwrapped = name {
    print("\(unwrapped.count) letters")
} else {
    print("Missing name.")
}

如果包含一个字符串name,它将作为常规字符串String放在unwrapped中,我们可以在条件中读取它的count属性。或者,如果name为空,则运行else代码。

if let的替代方法是guard let,它也取消了可选的包装。guard let将为您打开一个可选的选项,但是如果在nil内部找到它,则希望您退出使用它的函数,循环或条件。

但是,if letguard let之间的主要区别在于,在guard代码之后,未包装的可选内容仍然可用。

让我们尝试一个greet()函数。这将接受可选字符串作为其唯一参数,并尝试对其进行拆包,但是如果其中没有任何内容,则将打印一条消息并退出。因为可选的选项guard letguard完成后会留在原处,所以我们可以在函数末尾打印未封装的字符串:

func greet(_ name: String?) {
    guard let unwrapped = name else {
        print("You didn't provide a name!")
        return
    }

    print("Hello, \(unwrapped)!")
}

使用guard let,您可以在功能开始时处理问题,然后立即退出。这意味着函数的其余部分是幸福的路径-如果一切正确,代码将采用的路径。

强制展开

可选参数表示可能存在或可能不存在的数据,但是有时您可以确定一个值不是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某些东西无法转换为整数-您的代码将崩溃。

结果,只有在确定它是安全的时才应强制打开包装–这是通常将其称为崩溃操作符的原因。

隐式展开的可选内容

与常规的可选内容一样,隐式解包的可选内容可能包含一个值,也可能是nil。但是,与常规的可选选项不同,您不需要为使用它们而将它们拆开:您可以像完全不是可选的那样使用它们。

通过在类型名称后添加感叹号来创建隐式解包的可选内容,如下所示:

let age: Int! = nil

因为它们的行为就好像它们已经被解开,所以您不需要if letguard let使用隐式解开的可选对象。但是,如果您尝试使用它们,而它们没有任何价值nil(如果确实如此),则您的代码将崩溃。

隐式展开的选项存在,因为有时变量将以nil开始,但在需要使用它之前将始终有一个值。因为你知道它们在你需要的时候会有价值,所以不必一直写if let是有帮助的。

话虽这么说,但是如果您能够使用常规的可选参数,那通常是个好主意。

Nil合并

nil合并运算符解开一个可选值,如果有则返回内部的值。如果没有值(如果可选为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()函数返回的结果:如果是字符串,则将其解包并放入user,但是如果它内部为nil,则将改用“Anonymous”。

可选链接

Swift在使用可选选项时为我们提供了一条捷径:如果您想访问诸如a.b.c其中b是可选项,则可以在其后写一个问号以启用可选链接:a.b?.c

运行该代码时,Swift将检查b是否具有值,如果该值是nil,则该行的其余部分将被忽略-Swift将立即返回nil。但是,如果它具有值,它将被解包并继续执行。

要尝试此操作,请使用以下名称数组:

let names = ["John", "Paul", "George", "Ringo"]

我们将使用该数组的first属性,如果数组为空,它将返回第一个名字。然后,我们可以调用uppercased()使其结果成为大写字符串:

let beatle = names.first?.uppercased()

这个问号是可选的链接-如果first返回nil则Swift不会尝试将其大写,而是立即设置beatlenil

可选try

回到我们谈论抛出函数的时候,我们看了下面的代码:

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.")
}

运行投掷功能,使用dotry以及catch优雅地处理错误。

try有两种替代方法,既然您了解可选方法并强制展开,则这两种方法都会更有意义。

第一个是try?,并将throwing函数更改为返回可选函数的函数。如果函数抛出错误nil,则将结果发送给您,否则,您将获得返回值,并将其包装为可选值。

使用try?我们可以像这样运行checkPassword()

if let result = try? checkPassword("password") {
    print("Result was \(result)")
} else {
    print("D'oh.")
}

另一种选择是try!,您可以在确定函数不会失败时使用它。如果该函数确实抛出错误,则您的代码将崩溃。

使用try!我们可以将代码重写为:

try! checkPassword("sekrit")
print("OK!")

初始化失败

在谈论强制展开时,我使用了以下代码:

let str = "5"
let num = Int(str)

它将字符串转换为整数,但是由于您可能尝试传递任何字符串,因此您实际上返回的是一个可选整数。

这是一个失败的初始化程序:可能有效或无效的初始化程序。您可以使用init?()而不是init(),在自己的结构和类中编写这些代码,并在出现问题时返回nil。然后,返回值将是您的类型的可选值,以便您随意展开。

例如,我们可以编写一个Person必须使用9个字母的ID字符串创建的结构。如果使用了除9个字母的字符串以外的其他任何字符,我们将返回nil,否则我们将照常继续。

这是Swift中的内容:

struct Person {
    var id: String

    init?(id: String) {
        if id.count == 9 {
            self.id = id
        } else {
            return nil
        }
    }
}

类型转换

Swift必须始终知道每个变量的类型,但是有时您比Swift知道更多的信息。例如,这是三个类:

class Animal { }
class Fish: Animal { }

class Dog: Animal {
    func makeNoise() {
        print("Woof!")
    }
}

我们可以创建几条鱼和几条狗,并将它们放入数组中,如下所示:

let pets = [Fish(), Dog(), Fish(), Dog()]

Swift可以看到FishDog两者从Animal类继承,因此它使用类型推断Animal来构成一个pets数组。

如果我们要遍历pets数组并要求所有的狗吠叫,则需要执行类型转换:Swift将检查每个宠物是否都是Dog对象,如果是,则可以调用makeNoise()

这使用了一个名为的新关键字as?,该关键字返回一个可选参数:如果类型转换失败nil,则为可选关键字,否则为转换后的类型。

这是我们在Swift中编写循环的方式:

for pet in pets {
    if let dog = pet as? Dog {
        dog.makeNoise()
    }
}

总结

上一篇下一篇

猜你喜欢

热点阅读