重学Swift第六篇:枚举、错误处理

本文详细探讨了Swift中的枚举,包括语法、成员遍历、关联值、原始值、递归枚举、枚举大小以及与Objective-C的混编。同时,文章还介绍了Swift的错误处理机制,包括如何抛出和捕获错误,以及不同处理方式的实例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、枚举解析

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 性能相关

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值