Swift第二章 枚举、可选项

枚举

基本使用

  1. 示例

     enum Direction {
         case north
         case south
         case east
         case west
     }
        
     enum Direction {
         case north, south, east, west
     }
        
     var dir = Direction.west
     dir = Direction.east
     dir = .north
     print(dir) // north
        
     switch dir {
         case .north:
             print("north")
         case .south:
             print("south")
         case .east:
             print("east")
         case .west:
             print("west")
     }
    

关联值(Associated Values)

  1. 有时将枚举的成员值跟其他类型的值关联存储在一起,会非常有用

     enum Score {
         case points(Int)
         case grade(Character)
     }
        
     var score = Score.points(96)
     score = .grade("A")
        
     switch score {
         case let .points(i):
             print(i, "points")
         case let .grade(i):
             print("grade", i)
     } // grade A
        
        
     enum Date {
         case digit(year: Int, month: Int, day: Int)
         case string(String)
     }
     var date = Date.digit(year: 2011, month: 9, day: 10)
     date = .string("2011-09-10")
     switch date {
     case .digit(let year, let month, let day):
         print(year, month, day)
     case let .string(value):
         print(value)
     }
     //必要时let也可以改为var
    
  2. 举例

     //手机密码分为手势密码、数字密码
     enum Password {
         case number(Int, Int, Int, Int)
         case gesture(String)
     }
     //设置数字密码
     var pwd = Password.number(3, 5, 7, 8)
     //设置的手势密码
     pwd = .gesture("12369")
     switch pwd {
     case let .number(n1, n2, n3, n4):
         print("number is ", n1, n2, n3, n4)
     case let .gesture(str):
         print("gesture is", str)
     }
    

原始值(Raw Values)

  1. 枚举成员可以使用相同类型的默认值预先对应,这个默认值叫做:原始值

     //冒号: 代表的就是原始值
     enum PokerSuit : Character {
         case spade = "♠"
         case heart = "♥"
         case diamond = "♦"
         case club = "♣"
     }
     var suit = PokerSuit.spade
     print(suit) // spade
     //通过rawValue访问原始值
     print(suit.rawValue) // ♠
     print(PokerSuit.club.rawValue) // ♣
        
     enum Grade : String {
         case perfect = "A"
         case great = "B"
         case good = "C"
         case bad = "D"
     }
     print(Grade.perfect.rawValue) // A
     print(Grade.great.rawValue) // B
     print(Grade.good.rawValue) // C
     print(Grade.bad.rawValue) // D
    
  2. 注意:原始值不占用枚举变量的内存

隐式原始值(Implicitly Assigned Raw Values)

  1. 如果枚举的原始值类型是Int、String,Swift会自动分配原始值

     enum Direction : String {
     case north = "north"
     case south = "south"
     case east = "east"
     case west = "west"
     }
        
     //等价于上面
     enum Direction : String {
         case north, south, east, west
     }
     print(Direction.north) // north
     print(Direction.north.rawValue) // north
        
        
     enum Season : Int {
         case spring, summer, autumn, winter
     }
     print(Season.spring.rawValue) // 0
     print(Season.summer.rawValue) // 1
     print(Season.autumn.rawValue) // 2
     print(Season.winter.rawValue) // 3
    
     enum Season : Int {
         case spring = 1, summer, autumn = 4, winter
     }
     print(Season.spring.rawValue) // 1
     print(Season.summer.rawValue) // 2
     print(Season.autumn.rawValue) // 4
     print(Season.winter.rawValue) // 5
    

递归枚举(Recursive Enumeration)

  1. 使用indirect修饰枚举或者枚举的成员

     //方式1:枚举前面加上indirect
     indirect enum ArithExpr {
         case number(Int)
         //成员类型是枚举类型
         case sum(ArithExpr, ArithExpr)
         case difference(ArithExpr, ArithExpr)
     }
        
     //方式2:
     enum ArithExpr {
         case number(Int)
         //成员前面加上indirect
         indirect case sum(ArithExpr, ArithExpr)
         indirect case difference(ArithExpr, ArithExpr)
     }
        
     let five = ArithExpr.number(5)
     let four = ArithExpr.number(4)
     let two = ArithExpr.number(2)
     let sum = ArithExpr.sum(five, four)
     let difference = ArithExpr.difference(sum, two)
        
        
     func calculate(_ expr: ArithExpr) -> Int {
         switch expr {
             case let .number(value):
             return value
             case let .sum(left, right):
             return calculate(left) + calculate(right)
             case let .difference(left, right):
             return calculate(left) - calculate(right)
         }
     }
     calculate(difference)
    

MemoryLayout

  1. 可以使用MemoryLayout获取数据类型占用的内存大小

     enum Password {
         case number(Int, Int, Int, Int)
         case other
     }
        
     MemoryLayout<Password>.stride // 40, 分配占用的空间大小
     MemoryLayout<Password>.size // 33, 实际用到的空间大小
     MemoryLayout<Password>.alignment // 8, 对齐参数
        
     var pwd = Password.number(9, 8, 6, 4)
     pwd = .other
     MemoryLayout.stride(ofValue: pwd) // 40
     MemoryLayout.size(ofValue: pwd) // 33
     MemoryLayout.alignment(ofValue: pwd) // 8
    

可选项

  1. 可选项,一般也叫可选类型,它允许将值设置为nil,即如果不是可选类型,是不允许设置为nil
  2. 在类型名称后面加个问号 ? 来定义一个可选项

     var name: String? = "Jack"
     name = nil
        
     var age: Int? // 默认就是nil
     age = 10
     age = nil
        
     var array = [1, 15, 40, 29]
     func get(_ index: Int) -> Int? {
         if index < 0 || index >= array.count {
             return nil
         }
         return array[index]
     }
        
     //注意打印的是Optional(15)
     print(get(1)) // Optional(15)
     print(get(-1)) // nil
     print(get(4)) // nil
    

强制解包(Forced Unwrapping)

  1. 可选项是对其他类型的一层包装,可以将它理解为一个盒子,如果为nil,那么它是个空盒子, 如果不为nil,那么盒子里装的是:被包装类型的数据
  2. 如果要从可选项中取出被包装的数据(将盒子里装的东西取出来),需要使用感叹号 ! 进行强制解包

     var age: Int? = 10
     var ageInt: Int = age!
     ageInt += 10
    
  3. 如果对值为nil的可选项(空盒子)进行强制解包,将会产生运行时错误

     //Fatal error: Unexpectedly found nil while unwrapping an Optional value
     var age: Int?
     age!
    
  4. 判断可选项是否包含值

     //将字符串转换为整数,此时返回的是可选类型Int?
     let number = Int("123")
     if number != nil {
         print("字符串转换整数成功:\(number!)")
     } else {
         print("字符串转换整数失败")
     }
     // 字符串转换整数成功:123
    

可选项绑定(Optional Binding)

  1. 可以使用可选项绑定来判断可选项是否包含值:如果包含就自动解包,把值赋给一个临时的常量(let)或者变量(var),并返回true,否则返回false
  2. 使用方法:就是在if条件句中定义一个let或者var来接收可选项作为判断条件。

     if let number = Int("123") {
         print("字符串转换整数成功:\(number)")
         // number是强制解包之后的Int值
         // number作用域仅限于这个大括号
     } else {
         print("字符串转换整数失败")
     }
     // 字符串转换整数成功:123
        
        
     enum Season : Int {
         case spring = 1, summer, autumn, winter
     }
     if let season = Season(rawValue: 6) {
         switch season {
         case .spring:
             print("the season is spring")
         default:
             print("the season is other")
         }
     } else {
         print("no such season")
     }
     // no such season
    
  3. 多个“且”可选绑定可以用逗号“,”隔开

     if let first = Int("4") {
         if let second = Int("42") {
             if first < second && second < 100 {
                 print("\(first) < \(second) < 100")
             }
         }
     }
     // 4 < 42 < 100
        
     //等价于上面
     //可选项绑定作为条件,“且”用逗号“,”隔开,不能使用&&
     if let first = Int("4"),
     let second = Int("42"),
     first < second && second < 100 {
         print("\(second) < \(second) < 100")
     }
     // 4 < 42 < 100
    

while循环中使用可选项绑定

// 遍历数组,将遇到的正数都加起来,如果遇到负数或者非数字,停止遍历
var strs = ["10", "20", "abc", "-20", "30"]
var index = 0
var sum = 0
while let num = Int(strs[index]), num > 0 {
    sum += num
    index += 1
}
print(sum) //30

空合并运算符 ??(Nil-Coalescing Operator)

  1. a ?? b
    1. a 是可选项
    2. b 是可选项 或者 不是可选项
    3. b 跟 a 的存储类型必须相同
    4. 如果 a 不为nil,就返回 a
    5. 如果 a 为nil,就返回 b
    6. 如果 b 不是可选项,返回 a 时会自动解包
    7. 从上面可以看出,返回结果的类型取决于b的类型
  2. 源码定义

     public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T?) rethrows -> T?
     public func ?? <T>(optional: T?, defaultValue: @autoclosure () throws -> T) rethrows -> T
    
  3. 举例使用

     let a: Int? = 1
     let b: Int? = 2
     let c = a ?? b // c是Int? , Optional(1)
        
     let a: Int? = nil
     let b: Int? = 2
     let c = a ?? b // c是Int? , Optional(2)
        
     let a: Int? = nil
     let b: Int? = nil
     let c = a ?? b // c是Int? , nil
        
     let a: Int? = 1
     let b: Int = 2
     let c = a ?? b // c是Int , 1
        
     let a: Int? = nil
     let b: Int = 2
     let c = a ?? b // c是Int , 2
        
     let a: Int? = nil
     let b: Int = 2
     // 如果不使用??运算符
     let c: Int
     if let tmp = a {
         c = tmp
     } else {
         c = b
     }
    
  4. 多个 ?? 一起使用

     let a: Int? = 1
     let b: Int? = 2
     let c = a ?? b ?? 3 // c是Int , 1
        
     let a: Int? = nil
     let b: Int? = 2
     let c = a ?? b ?? 3 // c是Int , 2
        
     let a: Int? = nil
     let b: Int? = nil
     let c = a ?? b ?? 3 // c是Int , 3
    
  5. ??跟if let配合使用

     let a: Int? = nil
     let b: Int? = 2
     if let c = a ?? b {
         print(c)
     }
     // 类似于if a != nil || b != nil
        
     if let c = a, let d = b {
         print(c)
         print(d)
     }
     // 类似于if a != nil && b != nil
    

guard语句

  1. if语句实现登陆

     func login(_ info: [String : String]) {
         let username: String
         if let tmp = info["username"] {
             username = tmp
         } else {
             print("请输入用户名")
             return
         }
         let password: String
         if let tmp = info["password"] {
             password = tmp
         } else {
             print("请输入密码")
             return
         }
         // if username ....
         // if password ....
         print("用户名:\(username)", "密码:\(password)", "登陆ing")
     }
     login(["username" : "jack", "password" : "123456"]) // 用户名:jack 密码:123456 登陆ing
     login(["password" : "123456"]) // 请输入密码
     login(["username" : "jack"]) // 请输入用户名
    
  2. 书写方式
    1. 当guard语句的条件为false时,就会执行大括号里面的代码
    2. 当guard语句的条件为true时,就会跳过guard语句
    3. guard语句特别适合用来“提前退出”
     guard 条件 else {
         // do something....
         退出当前作用域
         // return、break、continue、throw error
     }
    
  3. 当使用guard语句进行可选项绑定时,绑定的常量(let)、变量(var)也能在外层作用域中使用

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

隐式解包(Implicitly Unwrapped Optional)

  1. 在某些情况下,可选项一旦被设定值之后,就会一直拥有值
  2. 在这种情况下,可以去掉检查,也不必每次访问的时候都进行解包,因为它能确定每次访问的时候都有值
  3. 可以在类型后面加个感叹号 ! 定义一个隐式解包的可选项
let num1: Int! = 10
//隐式对num1进行解包
let num2: Int = num1
if num1 != nil {
    //隐式解包
    print(num1 + 6) // 16
}
if let num3 = num1 {
    print(num3) //10
}

let num1: Int! = nil
// Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
let num2: Int = num1

字符串插值

  1. 可选项在字符串插值或者直接打印时,编译器会发出警告

     var age: Int? = 10
     print("My age is \(age)")
    
  2. 至少有3种方法消除警告

     print("My age is \(age!)")
     // My age is 10
        
     print("My age is \(String(describing: age))")
     // My age is Optional(10)
        
     print("My age is \(age ?? 0)")
     // My age is 10
    

多重可选项

  1. 示例

     //包装一个Int类型为可选类型
     var num1: Int? = 10
     //包装一个可选类型为可选类型
     var num2: Int?? = num1
     //等价num2
     var num3: Int?? = 10
        
     print(num2 == num3) // true
    
  2. 案例2

     var num1: Int? = nil
     var num2: Int?? = num1
     var num3: Int?? = nil
     print(num2 == num3) // false
     (num2 ?? 1) ?? 2 // 2
     (num3 ?? 1) ?? 2 // 1
    
Table of Contents