Swift-细说枚举(Enum)

一. 枚举的声明

枚举的定义
enum SomeEnumeration {
    // 用case修饰枚举项
    case item
}

声明一个方向的枚举,包含四个枚举值: 东/西/南/北.

enum Direction {
    case east
    case west
    case north
    case south
}

let east = Direction.east
var head: Direction = .west
head = .north

Swift 的枚举成员在被创建时本身就是完备的值,这些值的类型是已经明确定义好的 Direction 类型. 不会像 Objective-C 一样被赋予一个默认的整型值。在上面的 Direction 例子中,east、west 、north、south不会被隐式地赋值为 0,1,2 和 3。

枚举命名和使用注意点:
  • 枚举的第一个字母要大写,枚举值的第一个字母要小写;
  • 枚举类型起一个单数名字而不是复数名字,从而使得它能够顾名思义;
  • 一旦枚举类型确定,建议使用点语法设置值;

二. 枚举的特性

  • Swift 中的枚举成员类型: 字符串、字符、任意的整数值或浮点类型。
  • Swift 中的枚举成员可以指定任意类型的值来与不同的成员值关联储存(合集)。
  • Swift 中的枚举是具有自己权限的一类类型(计算属性、实例方法、扩展等)。

三. 枚举值相关

1. 枚举值的类型

Swift 中的枚举更加灵活,不必给每一个枚举成员提供一个值。如果给枚举成员提供一个值(称为原始值),则该值的类型可以是:字符串、字符、任意的整数值或浮点类型。

2. 原始值

Swift的枚举类型提供了一个叫原始值(RawValues)的实现,它为枚举项提供了一个默认值,这个默认值在编译期间就是确定的。

  • 获取原始值
let raw = Direction.west.rawValue
  • 通过原始值生成枚举项
let east1 = Direction.init(rawValue: "east")
print(type(of: east1))     
// Optional<Direction>

通过原值值获取的枚举项是可选类型的,所以需要用if let 做一下判断。

if let east = Direction.init(rawValue: "east") {     
    print(type(of: east))     
}
// Direction
3. 隐式原始值和显式原始值

未指定枚举值类型

enum Shape {
    case circle
    case square
    case oval
    case trilateral
}

// 不能给未指定类型的枚举成员提供值(case north = "north"❌)
error:  Enum case cannot have a raw value if the enum does not have a raw type
// 未指定类型的枚举成员无法使用rawValue属性(direction.rawValue❌)
error: Value of type 'Direction' has no member 'rawValue'

隐式原始值

enum Shape1: String {
    case circle  
    case square
    case oval
    case trilateral
}

隐式原始值下枚举项使用系统的分配值:

  • 如果是Sting类型,枚举值就是枚举项 case circle 等同于case circle = "circle"
  • 如果是Int类型,第一个枚举值默认是0,其他逐级+1
  • 如果是CGFloat等浮点类型,第一个枚举值默认是0.0,其他逐级+1

显式原始值

enum Shape2: String {
    case circle = "圆形"
    case square = "正方形"
    case oval = "椭圆"
    case trilateral = "三角形"
}

隐式 + 显式原始值

enum Shape4: Int {
    case circle = 1   
    case square = // 默认值是2
    case oval  // 默认值是3
    case trilateral  // 默认值是4
}

enum Shape4: Int {
    case circle   // 默认值是0
    case square = 2
    case oval  // 默认值是3
    case trilateral  // 默认值是4
}
2. 监听枚举值
var direction = Direction.east {
    willSet {
        if newValue != direction {
            print("方向即将发生变化")
        }
    }
    
    didSet {
        if oldValue != direction {
            print("方向已经发生变化")
        }
    }
}

这里使用了属性观察者,Swift里面的枚举更像一个对象,因此可以很方便的使用属性观察者对枚举值进行监听。

3. 关联值类型(Associated Values)

在 Swift 中,还可以定义这样的枚举类型,它的每一个枚举项都有一个附加信息,来扩充这个枚举项的信息表示,这又叫做关联值。

enum Trade {
    case buy(count: Int)
    case sell(count: Int)
}
let trade = Trade.buy(count: 10)

一定要学会使用关联值,这一点很重要。具体的使用后面的第九节会细说

4. 关联值相等判断

通常情况下枚举是很容易进行相等判断的。一旦为枚举增加了关联值,Swift就没法正确的比较了,需要自己对该枚举实现 ==运算符。

// 一个交易的枚举
enum Trade {
    case buy(count: Int)
    case sell(count: Int)
}

// 只有枚举值并且关联值都相等才算相等。
func == (before: Trade, after: Trade) -> Bool {
   switch (before, after) {
     case let (.buy(count1), .buy(count2)) where count1 == count2:
       return true
     case let (.sell(count1), .sell(count2)) where count1 == count2:
       return true
     default:
        return false
   }
}

let trade1 = Trade.buy(count: 10)
let trade2 = Trade.sell(count: 10)
        
if trade1 == trade2 {
      print("一样的")
} else {
     print("不一样")
}
5. 有关联值的枚举排序/比较大小
enum SortEnum: Comparable {
    
    case a(Int)
    case b(String)
    indirect case c(SortEnum)
    
    var ordering: Int {
        switch self {
        case .a(_):
            return 0
        case .b(_):
            return 1
        case .c(_):
            return 2
        }
    }
    
    static func < (lhs: SortEnum, rhs: SortEnum) -> Bool {
        lhs.ordering < rhs.ordering
    }
    static func > (lhs: SortEnum, rhs: SortEnum) -> Bool {
        lhs.ordering > rhs.ordering
    }
}

let a = SortEnum.a(0)
let b = SortEnum.b("b")
let c = SortEnum.c(a)
let sortArr: [SortEnum] = [b, c, a]

print(sortArr.sorted())

借助Comparable协议,来实现枚举的比较。

四. 枚举与 switch

switch head {
case .east:
      print("东方向")
case .north, .south, .west:
      print("非东方向")
}

五. 枚举的遍历

enum Shape5: CaseIterable {
    case circle
    case square
    case oval
    case trilateral
}

for item in Shape5.allCases {
    print(item)
}

let count = Shape5.allCases.count

遵守CaseIterable协议的swift枚举是可以遍历的,通过allCases获取所有的枚举成员.

五. 枚举的嵌套

app里面的接口地址,如果都放一起,命名或者寻找都不方便,可以用枚举的嵌套来设计。可以分散在多个文件中,方便维护和管理。

/// 总接口
enum Interface { }

extension Interface {
    // 系统相关接口
    enum System {
        case sendMessage
        case uploadImage
    }
}

extension Interface {
    // 用户相关接口
    enum User {
        case name
        case icon
        case phone
    }
}

Interface.User.login

用枚举来管理系统里面的常量是一个不错的选择。

六. 枚举的(计算)属性和方法

1. 计算属性
enum AppleDeivce {
    case iPad, iPhone
    
    // 不能使用存储属性
    // var deviceName: String? ❌
    // error: Enums must not contain stored properties

    var yeaer: Int {
        switch self {
        case .iPad:
            return 2010
        case .iPhone:
            return 2007
        }
    }
}
let _ = AppleDeivce.iPhone.yeaer

枚举中不能使用存储属性,但是可以使用计算属性,计算属性的内容是在枚举值或者枚举关联值中得到的。

2. 实例方法
enum AppleDeivce {
    case iPad, iPhone, iWatch, other
    
     func introduced() -> String {
        switch self {
        case .iPad:
            return "iPad"
        case .iPhone:
            return "iPhone"
        case .iWatch:
            return "iWatch"
        default:
            return "other"
        }
    }
}
let _ = AppleDeivce.iPhone.introduced()

在这里,可以认为枚举是一个类,introduced是一个成员方法,AppleDeivce.iPhone 就是一个AppleDeivce的实例,case们是它的属性。introduced里面的switch self,其实就是遍历这个匿名属性的所有场景,如iPad,iPhone等,然后根据不同的场景返回不同的值。

3. 枚举的静态方法
enum AppleDeivce {
    case iPad, iPhone
    
    static func fromSlang(slang: String) -> AppleDeivce? {
        if slang == "iPhone" {
            return .iPhone
        }
        return nil
    }
}
let _ = AppleDeivce.fromSlang(slang: "iPhone")

可以做枚举的自定义构造方法。

七. 枚举与协议

系统的打印协议

public protocol CustomStringConvertible {
    var description: String { get }
}

让枚举遵守这个协议

enum AppleDeivce: CustomStringConvertible {
    case iPad, iPhone
    
    // 实现协议属性 
    var description: String {
        switch self {
        case .iPhone:
            return "要出现款iPhone了"
        default:
            return "iPad"
        }
    }
}
let _ = AppleDeivce.iPhone.description

八. 枚举与扩展

枚举可以进行扩展。可以将枚举中的case与method/protocol分隔开,阅读者可以快速消化枚举的内容。

enum AppleDeivce {
    case iPad, iPhone
}

extension AppleDeivce: CustomStringConvertible {
    var description: String {
        switch self {
        case .iPhone:
            return "要出现款iPhone了"
        default:
            return "iPad"
        }
    }
}

extension AppleDeivce {
    static func fromSlang(slang: String) -> AppleDeivce? {
        if slang == "iPhone" {
            return .iPhone
        }
        return nil
    }
}

extension AppleDeivce {
    func introduced() -> String {
        switch self {
        case .iPad:
            return "iPad"
        case .iPhone:
            return "iPhone"
        }
    }
}

extension AppleDeivce {
    var yeaer: Int {
        switch self {
        case .iPad:
            return 2010
        case .iPhone:
            return 2007
        }
    }
}

九. 枚举与泛型

设计一个网络类下的错误信息的处理功能。

  • 先声明一个错误信息的结构体,包含两个属性code码和错误信息
struct ErrorMessage {
    var code : Int
    var message : String
}
  • 声明网络错误的枚举
    该枚举接受一个泛型,用来接受关联值。
enum NetworkError<T> {
    case codeError(T)
    case wrongReturn
    case networkNull
    case tokenOverdue
}
  • 假如在网络类有一个这样的枚举项通过闭包返回出去了
let errorMessage = ErrorMessage.init(code: 404, message: "not found")
let codeError = NetworkError.codeError(errorMessage)
  • 调用网络api处,对网络错误处理
switch codeError {
case .codeError(let message):
      print(message.code)
      print(message.message)
default:
       break
}

十. 枚举与结构体

在项目中经常使用UserDefaults来存储一下简单的用户信息。但是对Key的维护不会很方便。而且会想不起来。用枚举+结构体就能很好的解决这个问题。

  • 声明协议
public protocol UserDefaultsSettable {
    associatedtype defaultKeys: RawRepresentable
}

extension UserDefaultsSettable where defaultKeys.RawValue == String {
    static public func set(value: Any, forKey key: defaultKeys) {
        let aKey = key.rawValue
        UserDefaults.standard.set(value, forKey: aKey)
    }
    
    static public func getString(forKey key: defaultKeys) -> String? {
        let aKey = key.rawValue
        let value = UserDefaults.standard.string(forKey: aKey)
        return value
    }
}
  • 给UserDefaults添加扩展
public extension UserDefaults {

    struct Version : UserDefaultsSettable {
        public enum defaultKeys: String {
            case version
            case build
        }
    }

    struct LocationInfo: UserDefaultsSettable {
        public enum defaultKeys: String {
            case latitude
            case longitude
        }
    }
}
// 可以由大到小逐级查询,更符合认知习惯。
UserDefaults.Version.set(value: "version", forKey: .version)
let version = UserDefaults.Version.getString(forKey: .version)

这么设计APP的存储模块,是不是更有层级感,更加方便使用呢?

十一. 递归枚举

递归枚举是拥有另一个枚举作为枚举成员关联值的枚举。当编译器操作递归枚举时必须插入间接寻址层。可以在声明枚举成员之前使用 indirect关键字来明确它是递归的。 也可以声明在整个枚举前,让所有的枚举成员都是递归的。

enum Arithmetic {
    /// 单一数字表达式
    case number(Int)
    /// 两个表达式的加法
    indirect case add(Arithmetic, Arithmetic)
    /// 两个表达式的减法
    indirect case subtract(Arithmetic, Arithmetic)
    /// 两个表达式的乘法
    indirect case multiply(Arithmetic, Arithmetic)
    /// 两个表达式的除法
    indirect case divide(Arithmetic, Arithmetic)
    
    /// 表达式的类型转换的函数
    func evaluate() -> Double {
        
        switch self {
        case .number(let value):
            return Double(value)
            
        case .add(let item1,let item2):
            return item1.evaluate() + item2.evaluate()
            
        case .subtract(let item1,let item2):
            return item1.evaluate() - item2.evaluate()
            
        case .multiply(let item1,let item2):
            return item1.evaluate() * item2.evaluate()
            
        case .divide(let item1,let item2):
            
            // 除数为0的处理
            if item2.evaluate() == 0 { return Double(Int.min) }
            return item1.evaluate() / item2.evaluate()
        }
    }
}

let two = Arithmetic.number(2)
let four = Arithmetic.number(4)
let five = Arithmetic.number(5)
let sum = Arithmetic.add(five, four)
let product = Arithmetic.multiply(sum, two)

print(two.evaluate())
print(sum.evaluate())
print(product.evaluate())

// 打印值分别为:2 9 18
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,761评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,953评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,998评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,248评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,130评论 4 356
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,145评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,550评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,236评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,510评论 1 291
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,601评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,376评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,247评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,613评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,911评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,191评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,532评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,739评论 2 335

推荐阅读更多精彩内容