Swift 枚举使用

枚举的遍历

合成枚举 allCases

 Swift4.2引入 protocol CaseIterable,它被用于合成简单枚举类型的allCases静态属性

 代码如下

enum LTSeasonFour: String, CaseIterable {

    case FIRST, SECOND, THIRD, FORTH

}

for c in LTSeasonFour.allCases{

    print("***c=\(c)***")

}

所谓“简单枚举类型”,指的是不带关联值的枚举类型。

 所以,上述LTSeasonFour枚举声明称enum LTSeasonFour: String, CaseIterable的话(指定rawType),编译器也是能够自动合成的


 自实现CaseIterable

 当无法自动合成的时候,可以自己实现CaseIterable。例如,带关联值的枚举:

enum LTMartialStatus: CaseIterable {

    case single

    case married(spouse: String)

    static var allCases: [LTMartialStatus]{

        return [.single, .married(spouse: "Totti")]

    }

}

 再比如:

某个case在某种情况下不可用,或者默认合成的实现不是你想要的时候,也可以自己实现

enum LTMartialStatus2: CaseIterable{

    @available(*, unavailable)

    case single

    case married

    static var allCases: [LTMartialStatus2]{

        return [.married]

    }

}

print(LTMartialStatus.allCases)

print(LTMartialStatus2.allCases)

总结:

CaseIterable 如同 Equatable 和 Hashable 一样,是泛型约束,而不是泛型类型

public protocol CaseIterable {

    /// A type that can represent a collection of all values of this type.

    associatedtype AllCases : Collection where Self.AllCases.Element == Self

    /// A collection of all values of this type.

    static var allCases: Self.AllCases { get }

}

它定义了一个关联类型AllCases作为allCases的返回值,然后约束这个类型是一个Collection,并且Element和这个枚举一致

关联值

如果希望用枚举表示复杂的含义,关联更多的信息,就需要使用关联值了(如上述:LTMartialStatus)

注意:具有关联值的枚举,就没有rawValue属性了,主要是因为一个case可以用一个或者多个值来表示,而rawValue只有单个的值 

如下是对应的SIL文件

enum LTMartialStatus : CaseIterable {

  case single

  case married(spouse: String)

  static var allCases: [LTMartialStatus] { get }

  typealias AllCases = [LTMartialStatus]

}

enum LTMartialStatus2 : CaseIterable {

  @available(*, unavailable)

  case single

  case married

  static var allCases: [LTMartialStatus2] { get }

  @_implements(Equatable, ==(_:_:)) static func __derived_enum_equals(_ a: LTMartialStatus2, _ b: LTMartialStatus2) -> Bool

  func hash(into hasher: inout Hasher)

  typealias AllCases = [LTMartialStatus2]

  var hashValue: Int { get }

}

通过查看SIL文件,发现enum中即没有别名,也没有init方法,计算属性rawValue也没有啦


枚举的其他用法

简单enum的模式匹配

枚举的匹配模式,其实就是匹配case的枚举值

具有关联值的enum模式匹配

关联值的模式匹配主要有两种

1、通过switch匹配所有case

let married = LTMartialStatus.married(spouse: "TOTTI")

switch married {

        case let .married(spouse)://相当于将TOTTI赋值给了声明的spouse常量

            print("***married***\(spouse)")

        case .single:

            print("***single***")

    }

将关联值的参数使用let、var修饰

也可以case let .married(spouse): 这么写,做了Value-Binding,相当于将TOTTI赋值给了的spouse常量

2、通过if case 匹配单个case,如下所示

if case let LTMartialStatus.married(spouse) = married{

        print("***married=\(spouse)**")

}


枚举嵌套枚举

enum LTDirect{

    //枚举中嵌套枚举

    enum LTBaseDirect{

        case up

        case down

        case left

        case right

    }

    //通过内部枚举组合的枚举值

    case leftUp(baseDirect1:LTBaseDirect, baseDirect2:LTBaseDirect)

    case leftDown(baseDirect1: LTBaseDirect, baseDirect2: LTBaseDirect)

    case rightUp(baseDirect1:LTBaseDirect, baseDirect2:LTBaseDirect)

    case rightDown(baseDirect1: LTBaseDirect, baseDirect2: LTBaseDirect)

}

//使用:

let leftUp = LTDirect.leftUp(baseDirect1: LTDirect.LTBaseDirect.left, baseDirect2: LTDirect.LTBaseDirect.up)


结构体嵌套枚举

struct LTSkill {

    enum LTKeyType {

        case up

        case down

        case left

        case right

    }

    let key: LTKeyType

    func launchSkill(){

        switch key{

        case .left, .right:

            print("left, right")

        case .up, .down:

            print("up, down")

        }

    }

}


枚举中包含属性

enum中只能包含计算属性,类型属性,不能包含存储属性

enum LTShape{

    case cricle(radius: Double)

    case rectangle(width: Double, height: Double)

    //var radius: Double

    //Eunms must not contain stored properties 定义属性radius会报错,因为enum本身是值类型

    //计算属性 - 本质是方法(get/set方法)

    var with: Double{

        get{

            return10.0

        }

    }

    //类型属性 - 是一个全局变量

    static let height = 20.0

}

疑问:

struct也是值类型,为什么struct可以放存储属性,而enum不可以?

struct中可以包含存储属性是因为其大小就是存储属性的大小。而对enum来说就不一样,enum枚举的大小是取决于case的个数的,如果没有超过255,enum的大小就是1字节(8位)


枚举中包含方法 

可以在enum中定义实例方法、static修饰的方法

enum LTSeasonFive: Int{

    case FIRST, SECOND, THIRD, FORTH

    mutating func nextSeason(){

        if self ==  .FORTH{

            self = LTSeasonFive(rawValue:0)!

        }else{

            self = LTSeasonFive(rawValue: self.rawValue+1)!

        }

    }

}

//调用

var s = LTSeasonFive.FIRST

s.nextSeason()


indirect关键字

如果要表达的enum是一个复杂的关键数据结构时,可以通过indirect关键字来让当前的enum更简洁

如下:用枚举表示链表结构

enum List{

    case end

    //表示case是引用来存储

    indirect case node(T, next: List)

    //也可以将indirect放在enum前: indirect enum List

}

enum是值类型,也就是在编译时期就确定了,但是使用indirect这么写的话对于当前的enum的大小是不能确定的,从系统的角度来说,不知道需要给enum分配多大的空间;

var node = List.node(10, next: List.end)

 使用indirect关键字就是通知编译器,当前的enum是递归的,大小是不确定的,需要分配一块堆的内存空间,用来存放enum

 如果是end,此时存储的是case值,而case为node时存储的是引用地址


枚举的大小

1、普通enum

2、关联值的enum

3、enum嵌套enum

4、struct嵌套enum

普通enum的大小

enum LTNormalEnum{

    case one

}

print(MemoryLayout.size)  //输出:0

print(MemoryLayout.stride)    //输出:1

enum LTNormalEnum1{

    case one

    case two

}

print(MemoryLayout.size) //输出:1

print(MemoryLayout.stride)  //输出:1

enum LTNormalEnum2{

    case one

    case two

    case three

    case four

    case five

}

print(MemoryLayout.size) //输出:1

print(MemoryLayout.stride)  //输出:1

从结果来看,enum就是以1字节存在在内存中的

case是UInt8,即1字节(8位),最大可存储255

如果超过了255,会自动从UInt8->UInt16->UInt32->UInt64...

总结:

如果enum中有原始值,其大小取决于case的多少,如果没有超过UInt8即255,就是1字节存储

Int标识的其实就是RawValue的值

只有一个case的情况下,size是0,说明这个enum没有意义

当有两个以上case时,enum才有意义,如果未超过255,则case的步长时1字节,如果超过,则会自动扩大UInt8->UInt16->UInt32->UInt64...

2、具有关联值enum的大小分析

分析上面定义的LTShape枚举

print(MemoryLayout.size)  //输出:17

print(MemoryLayout.stride) //输出:24

 从打印结果可知,enum中有关联值时,其内存大小取决于关联值的大小

 enum有关联值时,关联值的大小取对应枚举关联值最大的,例如LTShape中circle中关联值大小是8,

 而rectangle中关联大小是16,所以取16

 enum的size = 最大关联值大小+case大小 = 16+1=17

 而stride由于8字节对齐,所以自动补齐到24

总结:

具有关联值的enum大小,取决于最大case的内存大小

关联枚举的大小=最大case的内存大小+1 (case的大小)

size表示实际大小

stride表示对齐后的大小(内存空间中真实占用的大小)

3、enum嵌套enum的大小分析

分析上面定义的LTDirect枚举

print(MemoryLayout.size)  //输出:2

print(MemoryLayout.stride)    //输出:2

4、结构体嵌套enum的大小分析

struct LTSkill {

    enum LTKeyType {

        case up

        case down

        case left

        case right

    }

    let key: LTKeyType //1字节

    func launchSkill(){

        switch key{

        case .left, .right:

            print("left, right")

        case .up, .down:

            print("up, down")

        }

    }

}

//结构体中包含枚举变量

print(MemoryLayout.size)  //输出:1

print(MemoryLayout.stride) //输出:1


struct LTSkill1 {

    enum LTKeyType {

        case up

        case down

        case left

        case right

    }

}

//结构体中不包含任何变量

print(MemoryLayout.size)  //输出:0

print(MemoryLayout.stride) //输出:1

struct LTSkill2 {

    enum LTKeyType {

        case up

        case down

        case left

        case right

    }

    var num: UInt8    //1字节

    let key: LTKeyType    //1字节

    func launchSkill(){

        switch key{

        case .left, .right:

            print("left, right")

        case .up, .down:

            print("up, down")

        }

    }

}

//结构体中包含UInt8类型变量

print(MemoryLayout.size)  //输出:2

print(MemoryLayout.stride) //输出:2


struct LTSkill3 {

    enum LTKeyType {

        case up

        case down

        case left

        case right

    }

    var sum: Int    //8字节

    let key: LTKeyType    //1字节

    func launchSkill(){

        switch key{

        case .left, .right:

            print("left, right")

        case .up, .down:

            print("up, down")

        }

    }

}

//结构体中即包含枚举变量,也包含Int变量

print(MemoryLayout.size)  //输出:9

print(MemoryLayout.stride) //输出:16

通过上面的分析可知:

如果结构体中没有其他属性,只有枚举变量,那么结构体的大小就是枚举的大小,即size=1

如果结构体中嵌套了结构体,但没有声明变量,此时的size是0,stride是1

如果结构体中还有其他属性,按照OC的结构体对齐三原则进行分析

总之结构体的大小取决于包含的属性的大小之和


内存对齐&字节对齐 

1、内存对齐:iOS中是8字节对齐(在分配对象时apple采用的是16字节对齐)

2、字节对齐:存储属性的位置必须是偶地址,即OC内存对齐中的min(m,n),其中m表示存储的位置,n表示属性的大小,需要满足位置m整除n时,才能从该位置存放属性。

简单来说,就是必须在自身的倍数位置开始

3、外部调用对象时,对象时服从内存对齐

4、单纯从结构上说,结构内部服从最大字节对齐

你可能感兴趣的:(Swift 枚举使用)