一、枚举Enum
1.枚举的基本用法
swift 中通过 enum关键字来声明一个枚举
enum LGEnum{
case test_one
case test_two
case test_three
}
在 C 或者 OC 中默认受整数支持,也就意味着下面的例子中: A, B, C分别 默认代表 0, 1,2
typedef NS_ENUM(NSUInteger, LGEnum) {
A,
B,
C,
};
可以给指定的初始值,后面的会从新累加
Swift 中的枚举则更加灵活,并且不需给枚举中的每一个成员都提供值。如果一个值(所谓“原 始”值)要被提供给每一个枚举成员,那么这个值可以是字符串、字符、任意的整数值,或者是 浮点类型。但不能是引用类型的对象!
enum Color : String {
case red = "Red"
case amber = "Amber"
case green = "Green"
}
enum LGEnum: Double {
case a = 10.0
case b= 20.0
case c = 30.0
case d = 40.0
}
1.1系统默认会分配初始值的
1.2 不指定类型不能用初始化方法,也没有rawValue
指定了类型就可以使用了
1.3查看sil代码
enum WeekDaySwift : String {
case Mon
case Tue
case Wed
case Thu
case Fri
case Sat
case Sun
init?(rawValue: String)
typealias RawValue = String
var rawValue: String { get }
}
- 有一个可失败初始化器
- 有一个别别名
- 有一个计算属性
rawValue只有get方法
// WeekDaySwift.rawValue.getter
sil hidden @$s8NBCBBank12WeekDaySwiftO8rawValueSSvg : $@convention(method) (WeekDaySwift) -> @owned String {
// %0 "self" // users: %2, %1
bb0(%0 : $WeekDaySwift):
debug_value %0 : $WeekDaySwift, let, name "self", argno 1 // id: %1
switch_enum %0 : $WeekDaySwift,
case #WeekDaySwift.Mon!enumelt: bb1, case #WeekDaySwift.Tue!enumelt: bb2, case #WeekDaySwift.Wed!enumelt: bb3, case #WeekDaySwift.Thu!enumelt: bb4,
case #WeekDaySwift.Fri!enumelt: bb5, case #WeekDaySwift.Sat!enumelt: bb6, case #WeekDaySwift.Sun!enumelt: bb7 // id: %2
bb1: // Preds: bb0
%3 = string_literal utf8 "Mon" // user: %8
%4 = integer_literal $Builtin.Word, 3 // user: %8
%5 = integer_literal $Builtin.Int1, -1 // user: %8
%6 = metatype $@thin String.Type // user: %8
// function_ref String.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
%7 = function_ref @$sSS21_builtinStringLiteral17utf8CodeUnitCount7isASCIISSBp_BwBi1_tcfC : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %8
%8 = apply %7(%3, %4, %5, %6) : $@convention(method) (Builtin.RawPointer, Builtin.Word, Builtin.Int1, @thin String.Type) -> @owned String // user: %9
br bb8(%8 : $String)
通过模式匹配去找到对应的代码,比如和枚举.Mon对应,就返回"Mon",这个默认的字符串是一个常量,存储在TEXT_cString中的
因为有初始化器,但是如果rawvalue不在枚举的定义范围呢,就可能是nil,所以这个个可以失败的初始化器,不是nil的也是可选择
2.关联值
enum Shape{
case circle(Double)
case rectangle(Int, Int)
}
方便携带更多信息,适应更复杂的场景
3.模式匹配
3.1 swift的模式匹配必须要穷尽所有的case
3.1.1 判断所有的case
enum Week: String {
case MONDAY
case TUEDAY
case WEDDAY
case THUDAY
case FRIDAY
case SATDAY
case SUNDAY
}
let currentWeek: Week
switch currentWeek{
case .MONDAY: print(Weak.MONDAY.rawValue)
case .TUEDAY: print(Weak.TUEDAY.rawValue)
...
case .SUNDAY: print(Weak.TUEDAY.rawValue)
3.1.2其他的用default
switch currentWeak{
case .SATDAY, .SUNDAY: print("Happy Day")
default : print("SAD DAY")
}
3.2 匹配关联值
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 let .rectangle(width, height):
print("rectangle width:\(width),height\(height)")
}
或者
switch shape{
case .circle(let radious):
print("Circle radious:\(radious)")
case .rectangle(let width, var height):
//如果不修改height,用let或者var都一样
print("rectangle width:\(width),height\(height)")
}
- 枚举也可以定义方法,如果要修改自身,需要用muating 异变方法
- 枚举也可以定义熟悉
- 枚举也可以在extension中扩展
- 枚举也可以遵循协议
4.枚举的大小
枚举是值类型,数据存在栈上面
4.1没有负载的枚举
通过打印我们可以直观的看到,当前变量 x , y , z 这三个变量存储的内容分别 是 00, 01, 02
4.2有一个负载的枚举
bool 1字节
sil代码
enum WeekDaySwift {
case Mon
case Tue(Bool)
case Wed
case Thu
case Fri
case Sat
case Sun
}
Int 9字节
不是简单的相加
先计算负载的内存大小然后会使用负载类型中的额外空间来记录没有负载的case值,如果足够那就不用新开辟内存空间
首先 Bool 类型是 1字节,也就是 UInt8 ,所以当 前能表达 256 个 case的情况,对于 布尔类型来说,只需要使用低位的 0, 1 这两种情况,其 他剩余的空间就可以用来表示没有负载的 case 值。
对于 Int 类型的负载来说,其实系统是没有办法推算当前的负载所要使用的位数,也就意味着 当前 Int 类型的负载是没有额外的剩余空间的,这个时候我们就需要额外开辟内存空间来去存 储我们的 case 值,也就是 8 + 1 = 9 字节。
4.3有多个负载的枚举
按照最大的负载来计算,然后看是否有 common spare bits多余的存储空间来存储费负载的case值
enum LGEnum{
case test_one(Bool) //
case test_two(Bool)
case test_three
case test_four
}
4.4变化负载类型的顺序要考虑开辟连续内存的时候的对齐
enum WeekDaySwift{
case Mon(Int8)
case Tue(Int)
case Wed(Bool)
case Thu
case Fri
case Sat(Int,Int,Int,Bool)
case Sun
}
Bool在中间,系统为了开辟连续的内存,会给Bool开辟8字节内存;但是Bool在最后就只会分配1字节
二、可选项Optional
1.认识可选值
class LGTeacher{
var age: Int?
}
当前的 age 我们就称之为可选值,当然可选值的写法这两者是等同的
var age: Int? = var age: Optional<Int>
2.optional的本质
可选项的本质就是枚举,是利用当前编译器的类型检查来达到语法书写层面的安全性。
打开 Optional.swift 文件,源码如下
public enum Optional<Wrapped>: ExpressibleByNilLiteral {
case none
case some(Wrapped)
}
自己定义一个枚举代表可选
比如给定任意一个自然数,如果当前自然数是偶数返回,否则为 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
}
}
我们把上述的返回值更换一下,其实就和系统的 Optional 使用一样无疑
func getOddValue(_ value: Int) -> Int? {
if value % 2 == 0 {
return .some(value)
}
else {
return .none
}
}
2.可选链
2.1 给nil发消息
OC 中我们给一个 nil 对象发送消息什么也不会发生, Swift中我们是没有办法向一个 nil 对象直接发送消息,但是借助可选链可以达到类似的效果。我们看下面两段代码
let str: String? = "abc"
let upperStr = str?.uppercased() // Optional<"ABC">
var str: String?
let upperStr = str?.uppercased() // nil
2.2 可选链对于下标和函数调用
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 存储值的类型保持一致
查看sil代码发现?? 是一种被中间infix运算符
3.1运算符重载
在开发中我们定义了一个二维向量,这个时候我们想对两个向量进行基本的操作,那么我们就可以通过重载运算符来达到我们的目的.
- 运算符已经存在的
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 + secondVector.y)
}
}
直接在static方法里面修改运算逻辑即可
//1.先定义操作符
prefix operator 】前置操作符
postfix operator 【 后缀操作符
infix operator +++ 中间操作符
操作符不能定义系统中已经有的操作符,前置和后置的操作符只能是1位,中间的操作符可以是1-多位
//2.设置操作符的操作组,并设置优先级
//precedencegroup NBCBVectorPrecedence {
// higherThan: AdditionPrecedence//优先级在谁之上
// lowerThan: MultiplicationPrecedence//优先级在谁之下
// associativity: left//从左结合还是从右结合
// assignment: true//默认true
//}
//简单点可写成这样:
precedencegroup NBCBVectorPrecedence {
lowerThan: MultiplicationPrecedence//优先级在谁之下
associativity: left//从左结合还是从右结合
}
//3.在操作符的方法中实现操作逻辑
struct Vector {
let x: Int
let y: Int
}
extension Vector {
static func +++ (fistVector: Vector, secondVector: Vector) -> Vector {
return Vector(x: fistVector.x + 3*secondVector.x, y: fistVector.y + 3*secondVector.y)
}
}
//4.使用新的操作符
var v1 = Vector(x: 10, y: 20)
var v2 = Vector(x: 20, y: 50)
print(v1+++v2)//结果为Vector(x: 70, y: 170)
4.隐式解析可选类型
隐式解析可选类型是可选类型的一种,使用的过程中和非可选类型无异。它们之间唯一 的区别是,隐式解析可选类型是你告诉对 Swift 编译器,我在运行时访问时,值不会 为 nil。
- 类型还是可选类型
- a!隐式可选告诉编译器我不会为nil,后面不用在if let 或者 a?去解包了,写起来可读性高
- 如果调用之前没有赋值,nil就会崩溃
其实日常开发中我们比较常见这种隐式解析可选类型
IBOutlet类型是Xcode强制为可选类型的,因为它不是在初始化时赋值的,而是在加载视图的时候。你可以把它设置为普通可选类型,但是如果这个视图加载正确,它是不会为空的。
5.与可选值有关的高阶函数
- 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: 可以把双层可选展平成为单个可选值
var dict = ["one": "1", "two": "2", "three":""]
let result_one = dict["one"].flatMap{ Int($0) }
print(result_one) //可选1
let result_three = dict["three"].flatMap{ Int($0) }
print(result_three) //nil 不打印
注意,这个方法是作用在Optioanl的方法,而
不是作用在Sequence上的
- compactMap 该方法可以将序列中的nil过滤出去
作用在Sequence上的flatMap方法在Swift4.1中被更名为compactMap
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]