30天学会Swift-06:可选类型 (Optionals)

学习目标

  • 理解可选类型(Optional)在Swift中的作用和重要性。
  • 掌握如何声明和使用可选类型。
  • 学习多种安全解包可选类型的方法:强制解包、可选绑定(if letguard let)。
  • 理解隐式解包可选类型(Implicitly Unwrapped Optionals)及其使用场景。
  • 掌握可选链(Optional Chaining)的概念和用法。

学习内容

1. 可选类型的作用与声明

在Swift中,可选类型用于处理值可能缺失的情况。它表示两种可能性:

  1. 有值,并且你可以解包它来访问这个值。
  2. 没有值。
  • 为什么需要可选类型?

    • Swift是类型安全的语言,不允许变量在没有值的情况下使用。
    • 许多情况下,变量可能没有值(例如,网络请求失败、字典中不存在的键、字符串转数字失败等)。
    • 可选类型强制你在使用值之前检查它是否存在,从而避免了运行时错误(如空指针异常)。
  • 声明可选类型:在类型后面加上问号?

    var surveyAnswer: String? // 可以是 String 类型的值,也可以是 nil
    // surveyAnswer 自动设置为 nil
    
    var optionalInt: Int? = 42 // 包含一个 Int 值 42
    var optionalString: String? = "Hello"
    
    // 将可选类型设置为 nil
    optionalInt = nil
    optionalString = nil
    

2. 强制解包 (Forced Unwrapping)

当你确定可选类型一定包含值时,可以使用感叹号!进行强制解包。如果可选类型为nil,强制解包会导致运行时错误(fatal error)。

let myString: String? = "Hello Swift"
print(myString!) // 输出: Hello Swift

let anotherString: String? = nil
// print(anotherString!) // 运行时错误:Fatal error: Unexpectedly found nil while unwrapping an Optional value

警告:强制解包非常危险,应尽量避免,除非你百分之百确定可选类型有值。

3. 可选绑定 (Optional Binding)

可选绑定是安全解包可选类型的首选方式。它允许你检查可选类型是否包含值,如果包含,则将该值赋给一个临时常量或变量。

3.1 if let 语句
var optionalName: String? = "John Appleseed"
var greeting = ""

if let name = optionalName { // 如果 optionalName 包含值,则将其赋给 name 常量
    greeting = "Hello, \(name)"
    print(greeting) // 输出: Hello, John Appleseed
} else {
    greeting = "Hello, stranger"
    print(greeting)
}

// 多个可选绑定和布尔条件
if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber < secondNumber {
    print("\(firstNumber) < \(secondNumber)")
}
// 输出: 4 < 42

if let firstNumber = Int("4"), let secondNumber = Int("42"), firstNumber > secondNumber {
    print("\(firstNumber) > \(secondNumber)")
} else {
    print("Conditions not met")
}
// 输出: Conditions not met
3.2 guard let 语句

guard let语句用于提前退出(early exit)。它要求条件必须为true才能继续执行guard语句之后的代码。如果条件为false,则else分支的代码会被执行,并且必须退出当前作用域(例如,使用return, break, continuethrow)。

guard let通常用于函数或方法的开头,用于验证输入参数或状态。

func greet(person: [String: String]) {
    guard let name = person["name"] else { // 如果 person["name"] 为 nil,则执行 else 块并返回
        print("I don't know your name.")
        return
    }
    
    print("Hello \(name)!")
    
    guard let location = person["location"] else {
        print("I hope the weather is good where you are.")
        return
    }
    
    print("I hope the weather is good in \(location).")
}

greet(person: ["name": "John"])
// 输出:
// Hello John!
// I hope the weather is good where you are.

greet(person: ["name": "Jane", "location": "Cupertino"])
// 输出:
// Hello Jane!
// I hope the weather is good in Cupertino.

4. 隐式解包可选类型 (Implicitly Unwrapped Optionals)

隐式解包可选类型在声明时使用感叹号!而不是问号?。它在第一次被赋值之后,可以像非可选类型一样直接使用,而无需每次都解包。Swift会在运行时自动解包它。

  • 使用场景:当一个可选类型在第一次被赋值之后,就确定它总会有值,并且后续访问时不需要每次都检查时,可以使用隐式解包可选类型。
    • 例如,在类的生命周期中,某个属性在初始化阶段可能暂时为nil,但在使用之前一定会赋值。
let myLabel: UILabel! // 隐式解包可选类型
// myLabel 此时为 nil

// 假设在某个初始化方法中赋值
// myLabel = UILabel()

// 之后可以直接使用,无需解包
// myLabel.text = "Hello"

var assumedString: String! = "An implicitly unwrapped optional string."
let implicitString: String = assumedString // 无需解包,直接赋值给非可选类型
print(implicitString)

assumedString = nil
// let anotherImplicitString: String = assumedString // 运行时错误:Unexpectedly found nil while unwrapping an Optional value

警告:如果隐式解包可选类型在被使用时为nil,同样会导致运行时错误。因此,使用时仍需谨慎。

5. 可选链 (Optional Chaining)

可选链是一种查询和调用可选类型属性、方法和下标的过程,如果可选类型当前为nil,则可选链会优雅地失败,而不是导致运行时错误。

  • 语法:在可选类型后面加上问号?
  • 返回值:可选链的调用总是返回一个可选类型的值,即使被调用的属性、方法或下标本身返回的是非可选类型。这允许你继续链式调用。
class Person {
    var residence: Residence?
}

class Residence {
    var numberOfRooms = 1
    func printNumberOfRooms() {
        print("The number of rooms is \(numberOfRooms)")
    }
    var address: Address?
}

class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if let buildingNumber = buildingNumber, let street = street {
            return "\(buildingNumber) \(street)"
        } else if buildingName != nil {
            return buildingName
        } else {
            return nil
        }
    }
}

let john = Person()

// 尝试访问 residence 的 numberOfRooms
// 如果 john.residence 为 nil,则整个表达式返回 nil
if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
// 输出: Unable to retrieve the number of rooms.

john.residence = Residence()

if let roomCount = john.residence?.numberOfRooms {
    print("John's residence has \(roomCount) room(s).")
} else {
    print("Unable to retrieve the number of rooms.")
}
// 输出: John's residence has 1 room(s).

// 调用可选方法
if john.residence?.printNumberOfRooms() != nil {
    print("It was possible to print the number of rooms.")
} else {
    print("It was not possible to print the number of rooms.")
}
// 输出: It was possible to print the number of rooms.

// 访问可选下标
let someArray: [Int]? = [1, 2, 3]
if let value = someArray?[0] {
    print("Value at index 0 is \(value)")
} else {
    print("Could not get value at index 0.")
}
// 输出: Value at index 0 is 1

// 链式调用多个可选类型
let someAddress = Address()
someAddress.buildingName = "The Larches"
someAddress.street = "Some Street"
john.residence?.address = someAddress

if let johnsStreet = john.residence?.address?.street {
    print("John's street is \(johnsStreet).")
} else {
    print("Unable to retrieve John's street.")
}
// 输出: John's street is Some Street.

// 调用返回可选类型的方法
if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
    print("John's building identifier is \(buildingIdentifier).")
} else {
    print("Unable to retrieve John's building identifier.")
}
// 输出: John's building identifier is The Larches.

实践练习

  1. 声明一个可选的Int变量age,并将其初始化为nil。然后尝试给它赋一个整数值,并打印。
  2. 声明一个可选的String变量userName,并赋一个非nil的值。使用if let安全解包并打印“Hello, [userName]!”。如果userNamenil,则打印“Hello, Guest!”。
  3. 编写一个函数findUser(id: Int) -> String?,模拟根据ID查找用户。如果ID是100,返回"Alice",否则返回nil。在函数中使用guard let来检查返回值,如果找到用户则打印其姓名,否则打印“User not found.”。
  4. 创建一个Book类,包含一个可选的author属性(String?)。创建一个Library类,包含一个可选的Book数组([Book]?)。使用可选链来尝试访问图书馆中第一本书的作者姓名,并打印结果。

思考题

  • 为什么Swift不推荐大量使用强制解包!
  • if letguard let在解包可选类型时有什么异同?在实际开发中,你会在什么场景下优先选择哪一个?
  • 可选链的返回值为什么总是一个可选类型?

学习资源

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

明似水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值