美文网首页有些文章不一定是为了上首页投稿
100 Days of SwiftUI —— Day 12:可选

100 Days of SwiftUI —— Day 12:可选

作者: 韦弦Zhy | 来源:发表于2020-03-14 14:09 被阅读0次

    \color{red}{\Huge \mathtt{100 \ Days \ of \ SwiftUI \ — \ Day \ 12}}

    \color{orange}{\Large \mathbf{可选类型}}

    \underline{\color{orange}{\large \mathit{Optionals}}}

    空(Null)引用——从字面上讲是一个无值的变量——是托尼·霍尔(Tony Hoare)于1965年发明的。回想起来,他说:“我称之为十亿美元的错误(I call it my billion-dollar mistake)”,因为它们会导致很多问题。

    这是您学习Swift基础知识的最后一天,它专门用于Swift解决null引用(称为可选类型)的解决方案。这些是一项非常重要的语言功能,但会给您的大脑带来一点伤害——如果您需要重复播放一些视频,那也不要感到难过。

    本质上,可选值试图回答以下问题:“如果我们的变量没有值该怎么办?”Swift希望确保我们所有的程序都尽可能安全,因此它具有一些非常具体且非常重要的功能!——处理这种情况的技术。

    今天,您有11个一分钟的视频可供观看,并且您将看到解包可选链类型转换等更多内容。观看完每个视频后,我们会进行一次简短的测试,以帮助您了解所教的内容。

    1. 处理丢失的数据 Handling missing datatest

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

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

    Swift的解决方案称为“可选(optionals)”,您可以将任意类型设置为可选。一个可选的整数可能有一个像0或40这样的数字,但它可能根本没有值——它可能确实不存在,在Swift中为nil

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

    var age: Int? = nil
    

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

    age = 38
    

    2. 解包 Unwrapping optionalstest

    可选字符串可能包含诸如“ 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 guardtest

    使用if let解包的替代方案是guard let, guard let将为您解开一个可选的选项,但是如果在其中找到nil,则希望您退出使用它的函数,循环或条件。

    总的来说,if letguard 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 unwrappingtest

    可选参数代表可能存在或可能不存在的数据,但有时您可以肯定地知道该值不是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 optionalstest

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

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

    let age: Int! = nil
    

    由于它们的行为就好像它们已经被解包一样,因此不需对隐式解包的可选对象使用if letguard let。但是,如果您尝试使用它们而没有任何值——如果它们为nil——您的代码将崩溃。

    存在隐式解包的可选参数,因为有时变量将以nil开始,但在需要使用它之前始终具有值。因为您知道它们会在您需要它们的时候就具有价,所以不必一直if let,这很有用。

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

    6. 空合运算 Nil coalescingtest

    空合运算符展开一个可选的,如果有,则返回其中的值。如果没有值(如果可选值为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 chainingtest

    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 trytest

    回到在我们讨论抛出函数时,我们看到了以下代码:

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

    它运行一个抛出函数,使用dotrycatch来优雅地处理错误。

    有其他两种方法来写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 initializerstest

    在谈到强制解包时,我使用了以下代码:

    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. 类型转换 Typecastingtest

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

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

    我们可以创建两条鱼和两只狗,然后将它们放入一个数组中,如下所示:

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

    Swift可以看到FishDog都继承自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 summarytest

    您已经完成了本系列的第十部分,所以让我们总结一下:

    1、可选类型让我们以一种清晰明确的方式来表示一个值的缺失。
    2、Swift不会让我们在没有解包的情况下使用可选值,无论是使用if let还是使用guard let
    3、可以使用感叹号强制解包,但如果尝试强制展开nil,则代码将崩溃。
    4、隐式解包没有常规可选值的安全检查。
    5、您可以使用空合运算符(nil coalescing)??来展开一个可选值,如果里面没有任何内容,则提供一个默认值。
    6、可选链允许我们编写代码来操作可选值,但是如果可选值的结果是空,则忽略代码,并返回nil
    7、你可以使用try?要将抛出函数转换为可选返值,或者使用try!,但是在抛出错误时会崩溃。
    8、如果您需要初始化器在输入错误时失败,请使用init?设置可失败初始化器。
    9、可以使用类型转换将一种对象类型转换为另一种对象类型。

    ≧◔◡◔≦

    \color{orange}{\Huge \Bbb{Back \ \ to \ 100 \ \ Days \ \ of \ \ SwiftUI}}

    相关文章

      网友评论

        本文标题:100 Days of SwiftUI —— Day 12:可选

        本文链接:https://www.haomeiwen.com/subject/dyrkjhtx.html