一、枚举解析
1.1 语法
enum Color {
case red
}
与 C 和 Objective-C 不同,Swift 的枚举成员在被创建时不会被赋予一个默认的整型值。把上面代码转为sil代码
enum Color {
case red
@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: Color, _ b: Color) -> Bool
var hashValue: Int { get }
func hash(into hasher: inout Hasher)
}
Swift的枚举默认实现Hashable协议。
1.2 成员遍历
enum Color: CaseIterable {
case red
}
//Color.allCases
转为sil
enum Color : CaseIterable {
case red
@_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: Color, _ b: Color) -> Bool
var hashValue: Int { get }
func hash(into hasher: inout Hasher)
typealias AllCases = [Color]
static var allCases: [Color] { get }
}
可以看到AllCases会被关联到Color数组,而allCases是只有get方法,所以只能被访问不能被修改。
调用allCases
就是把每一个case元素加入到数组中,然后返回该数组。
1.3 关联值
enum Color {
case red(String)
}
let red = Color.red("0xFF")
//模式匹配
switch red {
case .red("0xFF"):
print("红色")
//case .red(let hexStr): //值绑定
// print(hexStr)
default:
print("unknown")
}
//单匹配
if case let Color.red(hexStr) = red {
print("Color hexStr:\(hexStr)")
}
1.4 原始值
原始值
可以是字符串、字符
,或者任意整型值
或浮点型值
。每个原始值在枚举声明中必须是唯一
的。
enum Color: String {
case red = "0xFF" //显示赋值
case green //隐式赋值
}
转为sil
enum Color : String {
case red
case green
typealias RawValue = String
init?(rawValue: String)
var rawValue: String { get }
}
带原始值的枚举在原有基础上多实现了RawRepresentable
协议。
下面看一下sil下的init(rawValue:)
方法
先是创建了一个Array< StaticString >
来存放每一个原始值
通过把原始值变为StaticString存储到Array中,StaticString的初始化方法为StaticString.init(_builtinStringLiteral:utf8CodeUnitCount:isASCII:)
,可知各参数涵义。
通过_findStringSwitchCase(cases:string:)
查找,返回一个match值,在cases数组中找到了就返回对应的index,没找到返回-1。
@_semantics("findStringSwitchCase")
public // COMPILER_INTRINSIC
func _findStringSwitchCase(
cases: [StaticString],
string: String) -> Int {
for (idx, s) in cases.enumerated() {
if String(_builtinStringLiteral: s.utf8Start._rawValue,
utf8CodeUnitCount: s._utf8CodeUnitCount,
isASCII: s.isASCII._value) == string {
return idx
}
}
return -1
}
通过初始化方法可以看到enum的原始值是转为StaticString存放的,是字符串字面量,通过MachOView查看下
1.5 递归枚举
使用递归枚举时,编译器会插入一个间接层
。你可以在枚举成员前加上indirect
来表示该成员可递归。
enum Color {
case red(Int)
case green(Int)
case blue(Int)
indirect case white(Color, Color, Color) //可递归
func evaluate(_ expression: Color) -> Int {
switch expression {
case let .red(value), let .green(value), let .blue(value):
return value
case let .white(red, green, blue):
return evaluate(red) | evaluate(green) | evaluate(blue)
}
}
}
let red = Color.red(0xFF0000)
let green = Color.green(0x00FF00)
let blue = Color.blue(0x0000FF)
let white = Color.white(red, green, blue)
print(String(format: "0x%X", white.evaluate(white))) //0xFFFFFF
从sil中可看到加上indirect
关键字后会调用alloc_box在堆上
分配空间
在汇编中也可看到是调用的swift_allocObject
1.6 枚举的大小
- 无关联值
enum Color {
case red
}
print(MemoryLayout<Color>.size) //0 单case特殊处理了
print(MemoryLayout<Color>.stride) //1
enum Color1 {
case red
case green
}
var red = Color1.red
var green = Color1.green
var green1 = Color1.green
var red1 = Color1.red
var green2 = Color1.green
print(MemoryLayout<Color1>.size) //1
print(MemoryLayout<Color1>.stride) //1
对只有一个case的无关联enum做了特殊处理,MemoryLayout<Color>.size返回0;大于1个case刚开始会返回UInt8字节大小也就是1,当case数量超过UInt8.max时会变为Uint16,所以返回2,依次类推。
从上面内存中的数值来看,00和01对应的是第几个类型的case。
- 有关联值
enum Color {
case red(Int)
}
var red = Color.red(1)
var red1 = Color.red(2)
print(MemoryLayout<Color>.size) //8 + 0
print(MemoryLayout<Color>.stride) //8
带关联值的单case也有内存占用。
enum Color {
case red(Int)
case green(Int)
case blue(Int)
}
var red = Color.red(1)
var red1 = Color.red(2)
var green = Color.green(1)
var green1 = Color.green(2)
var blue = Color.blue(1)
var blue1 = Color.blue(2)
print(MemoryLayout<Color>.size) //8 + 1 = 9
print(MemoryLayout<Color>.stride) //16
print("end")
多个关联值case时,枚举大小受关联值数据类型影响。再来看下,多参数与无关联值混合情况
enum Color {
case red
case green(Int)
case blue(Int8, Int32, Int16)
case yellow
}
var red = Color.red
var red1 = Color.red
var green = Color.green(1)
var green1 = Color.green(2)
var blue = Color.blue(1, 1, 1)
var blue1 = Color.blue(2, 2, 2)
var yellow = Color.yellow
var yellow1 = Color.yellow
//注意这里Int8虽然占1字节,但是是按Int32的4字节对齐的,第一个Int8占用1字节后,第二个Int32就放不下4字节只好从第4位开始存放,所以1+(空3位)+ 4 + 2 + 1 = 11
print(MemoryLayout<Color>.size)
print(MemoryLayout<Color>.stride)
print("end")
下面是关联值包含类类型
class C { }
enum Color {
case red(C)
case green(Int8, C)
}
var red = Color.red(C())
var red1 = Color.red(C())
var green = Color.green(1, C())
var green1 = Color.green(1, C())
print(MemoryLayout<Color>.size)
print(MemoryLayout<Color>.stride)
print("end")
下面再看下协议类型
protocol ColorProtocol {}
extension Int: ColorProtocol {}
extension String: ColorProtocol {}
enum Color {
case red(ColorProtocol)
}
print(MemoryLayout<Color>.size) //40
print(MemoryLayout<Color>.stride) //40
enum Color1 {
case red
case green(ColorProtocol)
}
print(MemoryLayout<Color1>.size) //40 这里应该也是优化了
print(MemoryLayout<Color1>.stride) //40
enum Color2 {
case red
case green(ColorProtocol)
case blue(ColorProtocol)
}
var red = Color2.red
var red1 = Color2.red
var green = Color2.green(1)
var green1 = Color2.green(1)
var blue = Color2.blue("blue1")
var blue1 = Color2.blue("blue2")
print(MemoryLayout<Color2>.size) //41
print(MemoryLayout<Color2>.stride) //48
print("end")
这里最后存放的还是case类型,前面40字节存放的是协议,协议会涉及到existential container
- value buffer: 用来存储Inline的值,如果word数大于3,则采用指针的方式,在堆上分配对应需要大小的内存。
- vwt(Value Witness Table): 每个类型都对应这样一个表,用来存储值的创建,释放,拷贝等操作函数。(管理 Existential Container 生命周期)
- pwt(Protocol Witness Table): 用来存储协议的函数。
existential container
的大小不一定为5个word,会pwt的数量而改变。
有关联值
时枚举大小与关联值所占内存大小相关,此时MemoryLayout<Color>.size
为最大关联值所占内存大小 + enum case size
,但enum case size又会受到关联值中的类型影响
。
- 递归枚举
enum Color {
case red
case green
indirect case blue(Color)
}
print(MemoryLayout<Color>.size) //8
print(MemoryLayout<Color>.stride) //8
enum Color1 {
case red
case green(Int)
indirect case blue(Color1)
}
print(MemoryLayout<Color1>.size) //9
print(MemoryLayout<Color1>.stride) //16
indirect enum Color2 {
case red(Int)
case green(Int, Int, Int)
case blue(Color2)
}
print(MemoryLayout<Color2>.size) //8
print(MemoryLayout<Color2>.stride) //8
当部分case用indirect修饰
,如果没有关联值
时,枚举大小为max(enum case size, 指针大小)
,如果有关联值
,枚举大小为max(最大关联值所占内存大小, 指针大小) + enum case size
。当所有case都被indirect修饰
时,枚举大小为指针大小
。
- 小结
只是测试了一部分,也没总结出太好的结论,等了解再深入点后再补充。
1.7 与OC混编
如果想把Swift的枚举暴露给OC使用:
- 标记
@objc
关键字 - 枚举类型应该为
Int
类型
如果想让OC的枚举让Swift使用:
- 使用
NS_ENUM、NS_OPTION
定义。不要使用普通enum,会被转为struct。
二、错误处理
在 Swift 中,错误用遵循Error 协议
的类型的值来表示,这个空协议表明该类型可以用于错误处理。抛出错误使用throw
语句,用try
或其变体来标识出代码会抛出错误的地方。Swift 中的错误处理并不涉及解除调用栈,throw 语句的性能特性是可以和 return 语句相媲美的。
那么错误发生怎么处理呢?
- 把函数抛出的错误传递给调用此函数的代码
为了表示一个函数、方法或构造器可以抛出错误,在函数声明的参数之后加上throws
关键字。一个标有 throws 关键字的函数被称作 throwing 函数。只有 throwing 函数可以传递错误。任何在某个非 throwing 函数内部抛出的错误只能在函数内部处理。
- do-catch 语句处理错误
可以使用一个do-catch
语句运行一段闭包代码来处理错误。如果在do
子句中的代码抛出
了一个错误
,这个错误会与 catch 子句做匹配
,从而决定哪条子句能处理它。 - 将错误作为可选类型处理
可以使用try?
通过将错误转换成
一个可选值来处理错误
。如果是在计算 try? 表达式时抛出错误,该表达式的结果就为nil
。 - 断言此错误根本不会发生
使用try!
禁用错误传递,但一旦发生错误就是运行时错误。
2.1 实例
public enum CustomError: Error {
case notSuccessfulHTTP
case noData
case couldNotMakeObjectError
case bizError(resultCode: Int?, resultMsg: String?)
}
extension CustomError: LocalizedError {
public var errorDescription: String? {
switch self {
case .notSuccessfulHTTP:
return "网络连接失败"
case .noData:
return ""
case .couldNotMakeObjectError:
return "不正确的code"
case .bizError(_, let msg):
return msg
}
}
}
enum BizStatus: Int {
case bizSuccess = 200
case bizError
}
struct JSON {}
let statusCode = 200
let code = 380
func mapResponseToJSON() throws -> JSON {
guard ((200...209) ~= statusCode) else {
throw CustomError.notSuccessfulHTTP
}
if code == BizStatus.bizSuccess.rawValue {
return JSON()
} else {
throw CustomError.bizError(resultCode: code, resultMsg: "msg")
}
}
func test() {
do {
_ = try mapResponseToJSON()
} catch CustomError.notSuccessfulHTTP {
print(CustomError.notSuccessfulHTTP.localizedDescription)
} catch CustomError.bizError(_, let msg) {
print(msg ?? "unknow bizError")
} catch {
print(error.localizedDescription)
}
}
test()
总结
原始值
可以是字符串、字符
,或者任意整型值
或浮点型值
。每个原始值在枚举声明中必须是唯一
的。原始值是转为StaticString存放在一个数组中
。- 使用递归枚举时,
编译器会插入一个间接层
。可以在枚举成员前加上indirect
来表示该成员可递归。 - Swift调用OC枚举, OC枚举应使用
NS_ENUM或NS_OPTION
定义。 - 4种错误处理方式:
- 在throwing函数中throw错误
- do-catch,在do中抛出错误,在catch中捕捉并处理
- try?转为可选错误
- try!禁用错误传递
参考文章
Swift 性能相关