Swift 可选项(可选类型)

190 阅读6分钟

可选项(optionals)

用来处理值可能缺失的情况。它允许将值设置为nil。一般也叫可选类型。

表示两种可能:

  • 有值,你可以解析可选类型访问这个值
  • 根本没有值。

?

在类型名称后面加个问号? 来定义一个可选项,表示该变量可能为空(可能包含值,也可能不包含值)

var sex: String?       // sex 被自动设置为 nil
var name: String? = "Jack"

var age: Int? = 12
age = nil
   
var num: Int?  

print(name,age, num)
// 打印结果  Optional("Jack") nil nil

var num: Int = nil。这样是不可能赋值成功的。因为Int类型中没有nil这个概念!

可选项是对其他类型(如Int)的一层包装 。可以将它理解为一个盒子。 如果为nil,那么它是个空盒子; 如果不为nil,那么盒子里装的是:被包装类型的数据。

  • nil在Swift 和 Objective-C 中并不一样。 在 Objective-C 中,nil 是一个指向不存在对象的指针。 在 Swift 中,nil 不是指针。它是一个确定的值,用来表示值缺失。任何类型的可选状态都可以被设置为 nil,不只是对象类型。

解包

展开选项有两种方式

  • if let

if let marioOpposite = opposites["Mario"] {   
    print("Mario's opposite is \(marioOpposite)")
}  

这种if let语法在 Swift 中非常常见,它将创建条件 ( if) 与创建常量 ( let) 结合起来。它一起完成三件事:

  1. 它从字典中读取可选值。
  2. 如果可选项里面有一个字符串,它就会被解开——这意味着里面的字符串被放入marioOpposite常量中。
  3. 条件已成功 - 我们能够解开可选内容 - 因此条件的主体已运行。

仅当可选项内部有值时才会运行条件的主体。

因此,if let这是一种非常简洁的处理选项的方法,可以同时检查和提取值。

事实上,它非常简洁,甚至给我们提供了一些捷径。

if let number = number {  
    print(square(number: number))
}

我们实际上可以这样写:

if let number {  
    print(square(number: number))
}

它做了完全相同的事情——它创建了一个number仅在该条件体内展开的阴影副本,只是重复次数少了一点。

可选值最重要的一个特性是,Swift 不会让我们在不先解开它们的情况下使用它们

  • guard let
func printSquare(of number: Int?) {  

    guard let number = number else {     
        print("Missing input")       
        return    
    }   
    
    print("\(number) x \(number) is \(number * number)")
}  
  • ??
let new = captains["Serenity"] ?? "N/A"  

let savedData = first() ?? second() ?? ""  

可用于提供默认值

  • 可选链
let chosen = names.randomElement()?.uppercased() ?? "No one"  

可选链接的神奇之处在于,如果可选值为空,它会默默地不执行任何操作 - 它只会发回与之前相同的可选值,但仍然为空。这意味着可选链的返回值始终是可选的,这就是为什么我们仍然需要 nil 合并来提供默认值。

  • 强制解包

如果要取出被包装的数据,需使用感叹号! 进行强制解包。 强制解包前提是,确定可选项为非nil

print(name)
// 打印结果  Optional("Jack")
print(name!)
// 打印结果  Jack

注意:如果对值为nil的可选项强制解包,将会产生运行时错误。

  • 隐式解包

如果确定一个可选类型总会有值。在这种情况下,每如果次都要判断和解析可选值,是非常低效的。这时,可以定义隐式解析可选类型。

在类型后面加个!,来定义一个隐式解包的可选项。

let num1: Int! = 10  // 表示该变量现在or未来 都有值

可选项绑定

用来 判断可选项是否包含值。如果包含,就自动解包,把值赋给一个临时量(letvar),并返回true,否则返回false。

  • if语句
var age: Int? 
if let number = age { 
    // 有值
    // number是强制解包之后的 Int值
    // number作用域仅限于这个大括号
    
    print("字符串转换整数成功:\(number)") 
    
} else { 
    print("空")
}

if let firstNum  = Int("4"), let secondNum  = Int("42"), firstNum < secondNum && secondNum < 100 {
    print("\(firstNum ) < \(secondNum ) < 100")
}
  • guard语句
guard 条件 else {
    // do something....
} 

guard语句的条件为false时,就会执行{ }里面的代码; 为true时,就会跳过guard语句

注意:guard语句中绑定的常量let、变量var也能在else{ }外层作用域中使用

func login(_ info: [String : String]) {
    guard let username = info["username"] else {
        print("请输入用户名")
        return
    }
    guard let password = info["password"] else {
        print("请输入密码")
        return
    }
    // if username ....
    // if password ....
    print("用户名:\(username)", "密码:\(password)", "登陆ing")
}

可选绑定可以用在 if 和 while 语句中,这条语句不仅可以用来判断可选类型中是否有值,同时可以将可选类型中的值赋给一个常量或者变量。

空合并运算符 ??

a ?? b (类似于三目运算符 a?a:b

a 是可选项;b 可能是 可选项;b 跟 a 的类型必须相同

a ?? b表示:如果 a 不为nil,就返回 a;如果 a 为nil,就返回 b。如果 b 不是可选项,返回 a 时会自动解包。

与if 语句配合:

let a: Int? = nil    // a是可选项,并且设为nil
let b: Int? = 2
if let c = a ?? b {
    print(c)      // 打印结果    2
}

可选链

先定义几个类

class Person {
    var residence: Residence?  // 可选类型 初始化为nil
}
class Residence {
    var address: Address?
    var rooms = [Room]()  // 数组
    var numberOfRooms: Int {
        return rooms.count
    }
    // 通过下标,返回数组对应的元素
    subscript(i: Int) -> Room {
        get {
            return rooms[i]
        }
        set {
            rooms[i] = newValue
        }
    }
    func printNumberOfRooms() {
        print("The number of rooms is \(numberOfRooms)")
    }
    
}
class Room {
    let name: String
    init(name: String) { 
        self.name = name 
    }
}
class Address {
    var buildingName: String?
    var buildingNumber: String?
    var street: String?
    func buildingIdentifier() -> String? {
        if buildingName != nil {
            return buildingName
        } else if let buildingNumber = buildingNumber, let street = street {
            return "\(buildingNumber) \(street)"
        } else {
            return nil
        }
    }
}

通过可选 链式调用

  • 访问属性
let john = Person()
if let roomCount = john.residence?.numberOfRooms {
    print("有 roomCount 个房间")
} else {
    print("不能拿到rooms的个数")
}
// 因为 john.residence 为 nil,所以这个可选链式调用 失败
  • 设置属性
let someAddress = Address()
someAddress.buildingNumber = "29"
someAddress.street = "Acacia Road"

john.residence?.address = someAddress
// 通过 john.residence 来设定 address 属性也会失败
// 因为 john.residence 当前为 nil。

可选链式调用失败时,等号右侧的代码不会被执行。

  • 调用方法 通过可选链式调用得到的返回值都是可选的
if john.residence?.printNumberOfRooms() != nil {

} else {
    print("不可能打印the number of rooms.")
}
  • 访问下标 通过可选链式调用,我们可以在一个可选值上访问下标,并且判断下标调用是否成功。
if let firstRoomName = john.residence?[0].name {

} else {
    print("查不到值")
}

通过下标,用可选链式调用来赋值

john.residence?[0] = Room(name: "Bathroom")

连接多层可选链式调用

通过可选链式调用访问一个 Int 值,将会返回 Int?,无论使用了多少层可选链式调用。 类似的,通过可选链式调用访问 Int? 值,依旧会返回 Int? 值,并不会返回 Int??。

let johnsAddress = Address()
johnsAddress.buildingName = "The Larches"
johnsAddress.street = "Laurel Street"
john.residence?.address = johnsAddress

if let johnsStreet = john.residence?.address?.street {
    print("John's street name is \(johnsStreet).")
} else {

} 

在方法的可选返回值上进行可选链式调用

if let buildingIdentifier = john.residence?.address?.buildingIdentifier() {
    print("John's building identifier is \(buildingIdentifier).")
}

如果要在该方法的返回值上进行可选链式调用,在方法的圆括号后面加上问号即可:

if let beginsWithThe =
    john.residence?.address?.buildingIdentifier()?.hasPrefix("The") {
        if beginsWithThe {
            print("John's building identifier begins with \"The\".")
        } else { 
        
        }
} 

在上面的例子中,在方法的圆括号后面加上问号是因为你要在 buildingIdentifier() 方法的可选返回值上进行可选链式调用,而不是 buildingIdentifier() 方法本身。