Swift-Enum&optional

Swift-Enum&optional

一、Enum(枚举)

1.枚举的基本用法

在swift中通过enum来声明一个枚举类型。

enum LGEnum{
    case test_one
    case test_two
    case test_three
}

我们都知道在c/oc中枚举都是受整数支持的,下面的例子中ABC就默认代表了123

typedef NS_ENUM(NSInterger,LGEnum){
     A,
     B,
     C,
};

那么在swift中则更加灵活,我们不需要为枚举中的每个成员都提供值,他会按照默认方式推断,那么这个值可以是那么在swift中也是如此,但是这个值可以是字符串,字符,任意的整数值,或者是其他浮点类型。

2.RawValue的隐式分配

RawValue的隐式分配是建立咋swift的类型推断上的。我们以一个例子来说明

enum DayOfWeek:Int {
    case mon,tue,wed,thu,fri = 10,sat,sun
}
print(DayOfWeek.mon.rawValue)
print(DayOfWeek.fri.rawValue)
print(DayOfWeek.sat.rawValue)
//0 10 11

 上面是对于int类型,那么对于string类型呢?

enum DayOfWeek:String {
    case mon,tue,wed,thu,fri = "swift",sat,sun
}
print(DayOfWeek.mon.rawValue)
print(DayOfWeek.fri.rawValue)
print(DayOfWeek.sat.rawValue)
//mon swift sat

对于string类型来说,当没有指定枚举成员的原始值时,rawvalue的值和枚举成员的名字是一样的,是有编译器默认分配的。

3.关联值

关联值用于当枚举需要用来表示复杂的类型,比如枚举一个长方形时,需要定义他的长宽属性

enum Shap{
    case circle(radious: Double)
    case rectangle(width: Double, height: Double)
}

var circle = Shap.circle(radious: 10.0)
var rectangle = Shap.rectangle(width: 15, height: 7)

4.模式匹配

模式匹配类似于switch的实现,可以穷举或者使用default实现

enum Week:String{
    case MONDAY
    case TUEDAY
    case WEDDAY
    case THUDAY
    case FRIDAY
    case SATDAY
    case SUNDAY
}
let currentWeak = Week.MONDAY
switch currentWeak {
case .MONDAY:
    print(Week.MONDAY.rawValue)
case .TUEDAY:
    print(Week.TUEDAY.rawValue)
case .WEDDAY:
    print(Week.WEDDAY.rawValue)
case .THUDAY:
    print(Week.THUDAY.rawValue)
case .FRIDAY:
    print(Week.FRIDAY.rawValue)
case .SATDAY:
    print(Week.SATDAY.rawValue)
case .SUNDAY:
    print(Week.SUNDAY.rawValue)
}
//MONDAY

上面是穷举,下面是defalut关键字

enum Week:String{
    case MONDAY
    case TUEDAY
    case WEDDAY
    case THUDAY
    case FRIDAY
    case SATDAY
    case SUNDAY
}
let currentWeak = Week.FRIDAY
switch currentWeak {
case .MONDAY:
    print(Week.MONDAY.rawValue)
default:print("swift")

}

模式匹配关联值的写法(两种写法均可):

enum Shape{
    case circle(radious: Double)
    case rectangle(width: Int, height: Int)
}

let shape = Shape.circle(radious: 10.0)

switch shape{
     case let .circle(radious):
            print("Circle radious:\(radious)")
     case .rectangle(let width, var height):
        height += 10
        print("rectangle width:\(width),height\(height)")
}

枚举和类和结构体是一样的,枚举中可以添加mutaing、属性、extension、协议。另外枚举是值类型存储在栈区。

5.枚举的大小

5.1 No-payload enums

enum Week:String{
    case MONDAY
    case TUEDAY
    case WEDDAY
    case THUDAY
    case FRIDAY
    case SATDAY
    case SUNDAY
}
print(MemoryLayout<Week>.size)
print(MemoryLayout<Week>.stride)
//1 1

从这段内存测试中可以发现枚举不管是size还是stride都是1,这是为什么呢?其实在swift中枚举都一直尝试使用最少的空间来存储,对于case的数量来说,一个枚举(UInt8)可以表示256个,也就是说当一个默认枚举类型且没有关联值的case少于256时当前的枚举大小的类型都是1字节,当case大于256时,就会使用UInt16来存储。

var a = Week.MONDAY
var b = Week.TUEDAY
var c = Week.WEDDAY
print(MemoryLayout<Week>.size)
print(MemoryLayout<Week>.stride)

 //memory read 是读取内存地址的意思

通过上面的打印我们可以直观的看到,当前变量a,b,c这三个变量的存储内容是00,01,02 分别相差一个字节,这与我们上面所说的是一致的

5.2 Single-payload enums

Single-payload enums的意思是有一个成员负载,通过下面的这个例子来看:

enum LGEnum{
    case test_one(Bool)
    case test_two
    case test_three
}

print(MemoryLayout<LGEnum>.size)
print(MemoryLayout<LGEnum>.stride)
// 1 1
enum LGEnum{
    case test_one(Int)
    case test_two
    case test_three
}

print(MemoryLayout<LGEnum>.size)
print(MemoryLayout<LGEnum>.stride)
///9 16

我们先要知道,其实负载就是关联值的意思,当成员中的关联值不同时,就会造成内存大小的不同,这是因为在swift中的enum的Single-payload enums会使用负载类型中的额外的额空间来记录没有负载的case的值,我们以上面两个例子来解释说明:首先bool是1个字节,也就是UInt8,可以表示当前256个case的情况,对于布尔类型而言,只需要使用低位0,1这两个即可表示,那么剩下的7位仍然可以表示剩下的case,而Int就不一样了,Int本身就是8字节,这也就意味着Int没有额外的空间再来存储剩下的case,那么这个时候我们就需要开辟出额外的空间来存储剩下的case,所有就是8+1 = 9个字节

5.3 Mutil-payload enums

Mutil-payload enums 即是存在多个负载的情况。当存在多个负载时,当前枚举的大小取决于当前最大关联值的大小。

enum LGEnum{
    case test_one(Bool)
    case test_two(Int)
    case test_three
    case test_four
}

这里的枚举的大小就等于sizeof(Int) + sizeof(rawValue) = 9

enum LGEnum{
    case test_one(Bool)
    case test_two(Int,Int,Int)
    case test_three
    case test_four
}

这里的枚举的大小就等于3*sizeof(Int) + sizeof(rawValue) = 25

5.4 特殊情况

enum LGEnum{
    case test_one
}
//1

这种情况由于枚举中只有一个case,所有不需要区别不同的case,所以当我们打印这个枚举时是0,当然如果他带有关联值的话就不属于这种特殊情况了。

6 indirect 关键字

 indirect 关键字表示当前枚举是引用类型,分配在堆空间,比如当我们使用枚举来表示树中的一个节点时:

indirect enum BinaryTree<T>{
    case empty
    case node(left: BinaryTree, value: T, right: BinaryTree)
}

当我们使用indirect来修饰case时,则表示当前的case是引用类型,而empty依然是值类型:

enum BinaryTree<T>{
    case empty
    indirect case node(left: BinaryTree, value: T, right: BinaryTree)
}

二、optional

首先,我们先来认识一下optional

class Teacher{
    var age:Int?
}
var age:Int? = var age:Optional<Int>

这两种方式都是可选值的一种表达方式,上面的是我们比较长使用到的。

那么实际上,optional其实就是一个枚举类型,我们通过optional.swift中来看一下

@frozen
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
    case none
    case some(Wrapped)
}

接下来,我们自己实现一个optional类型。比如给定一个自然数,如果是偶数则返回,否则为nil,下面为我们实现的一个案例:

enum MyOptional<Value> {
    case some(Value)
    case none
}

func getOddValue(_ value: Int) -> MyOptional<Int> { 
    if value % 2 == 0 {
        return .some(value) }
    else{
        return .none
    } 
}

假如此时,我们要删除一个数组中的偶数:

var array = [1, 2, 3, 4, 5, 6]
for element in array {
    let value = getOddValue(element)
    array.remove(at: array.firstIndex(of: value)) 
}

我们发现报错了,原因是我们value与编译器所期望的Int不符,这样其实侧面说明了我们可以借助Myoptional来保证语法的安全性,那么我们可以通过模式匹配来实现或者将返回值改为Int,就会避免出现这种问题。

//  模式匹配
for element in array {
    let value = getOddValue(element) 
    switch value {
        case .some(let value):
            array.remove(at: array.firstIndex(of: value)!) 
        case .none:
            print("vlaue not exist") }
}
//返回值改为Int
func getOddValue(_ value: Int) -> Int? { 
    if value % 2 == 0 {
        return .some(value) 
    }else{
        return .none
    } 
}

如果每个可选值都使用模式匹配来实现就会导致代码很繁琐,因此,我们也可以通过if let来进行可选值的绑定

if let value = value{
    array.remove(at: array.firstIndex(of: value)!)
}

除了使用if let之外,我们还可以使用guard let来简化代码,guard let与if let相反,guard let守护就一定有值,如果没有就直接返回,借助guard let判断是否有值后就可以实现具体的逻辑。我们常用在输入框的使用中。

 

 2 可选链

在OC中,我们可以给一个nil对象来发送消息,但是在swift中是没有办法会给一个nil对象直接发送消息的,但我们可以借助可选链达到类似的效果。

let str: String? = "abc"
let upperStr = str?.uppercased() // Optional<"ABC">
var str: String?
let upperStr = str?.uppercased() // nil

同样的,可选链对下标和函数也是同样适用的。

var closure: ((Int) -> ())?
closure?(1) // closure 为 nil 不执行
let dict = ["one": 1, "two": 2]
dict?["one"]// Optional(1)
dict["three"] // nil

3 ??运算符

(a ?? b)将对可选类型a进行空判断,如果a包含一个值就进行解包,否则就返回一个默认值b

  • 表达式a必须是optional类型
  • 默认值b的类型必须要和a存储值的类型保持一致

4 运算符重载

struct Vector { 
    let x: Int 
    let y: Int
}

extension Vector {
    static func + (fistVector: Vector, secondVector: Vector) -> Vector {
        return Vector(x: fistVector.x + secondVector.x, y: fistVector.y + seco 
    }
    static prefix func - (vector: Vector) -> Vector { 
        return Vector(x: -vector.x, y: -vector.y)
    }
    static func - (fistVector: Vector, secondVector: Vector) -> Vector {
        return fistVector + -secondVector 
    }
}

5 隐式解析可选类型(X !)

隐式解析可选类型是可选类型的一种,使用的过程中和非可选类型无异。它们之间唯一 的区别是,隐式解析可选类型是你告诉对 Swift 编译器,我在运行时访问时,值不会为 nil

在日常的开发过程中,我们还是比较常见隐式解析可选类型的

 

IBOutlet 类型是 Xcode 强制为可选类型的,因为它不是在初始化时赋值的,而是在加载视图的时候。你可以把它设置为普通可选类型,但是如果这个视图加载正确,它是不会为空的。

6 与可选值相关的函数

  • map 这个方法接受一个闭包,如果可选值有内容则调用这个闭包来进行转换
var dict = ["one": "1", "two": "2"] 
let result = dict["one"].map{ Int($0) } // Optional(Optional(1))

上面的代码中我们从字典中取出字符串 "1",并将其转换为 Int 类型,但因为 String 转换成 Int 不一定能成功,所以返回的是 Int? 类型,而且字典通过键不一定能取得到值,所以 map 返回的也是一个 Optional,所以最后上述代码 result 的类型为 Int?? 类型。

  • flatmap 可以把结果展平成为单个可选值
ar dict = ["one": "1", "two": "2"]
let result = dict["one"].flatMap{ Int($0) } // Optional(1)
let array = ["1", "2", "3", nil]
let result = array.compactMap{ $0 } // ["1", "2", "3"]
let array = ["1", "2", "3", "four"]
let result = array.compactMap{ Int($0) } // [1, 2, 3]
  • 注意:这个方法是作用在 Optioanl 上的
  • 作用在 Sequence 上的 flatMap 方法在 Swift4.1 中被更名为 compactMap,该方法可以将序列中的 nil 过滤出去。
posted on 2022-01-24 17:04  suanningmeng98  阅读(98)  评论(0编辑  收藏  举报