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
过滤出去。