Swift enumeration

Time:2022-4-14

AndCObjective-CCompared with the enumeration in,SwiftinenumerationMore functionpowerful。 It supports many onlyclassUnique features, such as:PropertiesMethodsInitializationExtensionsProtocols

  • Writing method of enumeration in C language

Only supportIntOne type:

//Week: enumeration name, which can be omitted
//Mon: enumeration members
//Week: define enumeration variables
enum WEEK {
    MON, TUE, WED, THU, FRI, STA, SUN
} week;
  • Swift enumeration

supportIntegerFloat pointString (string)Boolean typeFour basic types, if you want to support otherscustomThe type of needs to be implementedStringLiteralConvertibleagreement:

enum WEEK {
   case MON, TUE, WED, THU, FRI, STA, SUN
}
//Or
enum WEEK {
   case MON
   case TUE
   case WED
   case THU
   case FRI
   case STA 
   case SUN
}

You can see from the memory of the variable:SwiftofEnumerating membersCase is ainteger That is, where it isindex, and only1Bytes(UInt8)。

Swift enumeration

If type is indicatedString, meansrawValueyesStringType, andnoyesCase memberType of.

enum WEEK : String {
   case MON = "MON"
   case TUE = "TUE"
   case WED = "WED"
   case THU = "THU"
   case FRI = "FRI"
   case STA = "STA"
   case SUN = "SUN"
}

casehinder"MON"namelyenum "="hinder"MON"yesrawValue(original value)
SwiftinImplicit rawvalue assignment: do not write “=” and the following string, that is:

enum WEEK : String {
   case MON 
   case TUE 
   case WED 
   case THU 
   case FRI
   case STA 
   case SUN 
}
  • Define enumeration variables

var w = WEEK.MON

oncewThe type of is declared asWEEK, you can use aAbbreviation syntax (.)Set it toWEEKValue of, i.e.:

var w : WEEK = .TUE

Indicate typeAfter, you canw.MON.rawValueGet native valuerawValue, essentially callingrawValueofget(); If the type is not indicated, rawvalue cannot be obtained.

//Rawvalue can be obtained only if the type of rawvalue is indicated
var w = WEEK.MON.rawValue

Look at the difference through SIL: hereIndicateThe type of isInt

Swift enumeration

Not indicatedTypeSIL

Swift enumeration

It is known that the marked type can be calledrawValueThe essence of is:rawValueIt’s aCalculation properties, by calling itsget()Gets the of the propertyvalue; And inrawValueofget()In,rawValueyesIntDefault from type0Start, yesStringThe default value isEnumeration member itselfString for:

Swift enumeration

Swift enumeration

Implementation of get() method with rawvalue type int png
Swift enumeration

Swift enumeration

Implementation of get () method with rawvalue type of string png

Then when typeStringWhen,get()String built indepositWhere is it?

Swift enumeration

The storage location of the string built by implicit rawvalue in machoview

It can be seen that:Implicit rawvalue stringstayCompilation processAlready stored inMachOViewIn the file__Text,__cstringAnd it can be known from the address of the string that it occupies a segmentContiguous memory space

  • enumerationvariableandrawValueDifferences between

print(WEEK.THU) //THU
print(WEEK.THU.rawValue) //THU

Here, although all printed out areTHU, but what needs to be distinguished isWEEK.THUyesWEEKType, andWEEK.THU.rawValueyesStringType.

Swift enumeration

Swift enumeration

  • Enumeratinginit?( )

  1. Let’s see what happens firstcallEnumeratedinitializationmethod?
Swift enumeration

We know that only throughWEEK(rawValue:)Only in this way will it be calledinit()method.

  1. Initialization method of analysis enumeration:
Swift enumeration

_allocateUninitializedArray: create atuple
The first element: the same size as the enumeratorarray, used to store the data in the enumerationrawValue, in this caseStaticString—>Array<StaticString>
The second element: the of the arrayFirst address—>BuildIn.RawPointer

Swift enumeration

Swift enumeration

_findStringSwitchCase: find specifiedenum In the arrayposition, returnindex

from0reachcount-1Sequential andindexFor comparison:

  • IfequalbestructureCorrespondingenumeration
  • IfUnequalThen build aOptional.none!enumeltEnumeration of
Swift enumeration

Swift enumeration

Swift enumeration

  • Association value

enum Shape {
    case circle(radius:Double)
    case rectangle(length:Int, width:Double, height:Int)
}

//Create an associated value for the enumeration
var shape = Shape.circle(radius: 3.00)
//Reassign associated values
shape = Shape.circle(radius: 2.0)

At this timeenum No morerawValue, it is not necessary (rawvalue is a single value), andAssociation valueCan beA set of values
adoptSILIt can also be seen that:enuminnoAgaininitializationMethods and calculation propertiesrawValue

Swift enumeration

Enum of associated value in SIL

The label of the associated value can beellipsis, write only type(Not recommended, poor readability), such as:

enum Shape {
    case circle(Double)
    case rectangle(Int, Double, Int)
}
  • Enumeration usage: pattern matching

  1. Simple usage
enum WEEK : String {
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case STA
    case SUN
}

var currentDay : WEEK = WEEK.SUN
var str : String
switch currentDay {
    case .STA, .SUN:
        str = "Happy Day"
    default:
        str = "Sad Day"
}

SILImplementation of: throughMatch caseTo do the corresponding code jump.

Swift enumeration

Swift enumeration

2. Complex usage: match associated values

Swift enumeration

You can know through SIL file:

Swift enumeration

  • case let .circle(radius):MediumletexpressAssociation valueIt’s a常量,不可被修改;也可换一种写法case .circle(let radius):
  • case .rectangle(let length, var width) :var width表示 width 是一个变量,可以被修改;如果关联值width用不到则可以用_代替,即case .rectangle(let length, _) :
  • 若只想匹配单个case可以:
if case let Shape.circle(radius) = shape {
    tempValue = radius
}
  • 如果只关心不同的case相同关联值,可以这样写:
enum Shape {
    case circle(radius:Double)
    case rectangle(length:Double, width:Double)
    case square(length:Double, width:Double)
}

var rectangle = Shape.rectangle(length:5, width: 3)
var square = Shape.square(length: 9, width: 5)
var tempValue : Double

switch square {
case let .rectangle (5, x), let .square(x, 5):
    tempValue = x
default :
    tempValue = 0.0
} 
// 若switch rectangle {..}则tempValue=3
// 若switch square {..}则tempValue=9

还可以使用通配符的方式:

case let .rectangle (_, x), let .square(x, _):
print(x)
// 若switch rectangle {..}则 3
// 若switch square {..}则 9
//或者
case let .rectangle (x, y), let .square(y, x):
print(x,y)
// 若switch rectangle {..}则 (5,3)
// 若switch square {..}则 (5,9)

底层实现还是通过匹配case,匹配成功后取出元组中x所在的元素,将该元素赋值给x,再将元素值赋值给tempValue

Swift enumeration

Swift enumeration

  • 枚举的嵌套

  1. 枚举中嵌套枚举
enum CombineDirect {
    enum BaseDirect {
        case up
        case down
        case left
        case right
    }
    case leftUp(direct1:BaseDirect, direct2:BaseDirect)
    case rightUp(direct1:BaseDirect, direct2:BaseDirect)
    case leftDown(direct1:BaseDirect, direct2:BaseDirect)
    case rightDown(direct1:BaseDirect, direct2:BaseDirect)
}

var direct = CombineDirect.leftDown(direct1: .left, direct2: .right)
  1. 结构体中嵌套枚举
struct Skill {
    enum Direct {
        case up
        case down
        case left
        case right
    }
    
    var direct : Direct
    func launchSkill() {
        switch direct {
            case .left, .right:
                print("控制方向")
            case .up, .down:
                print("移动距离")
        }
    }
}
  • 枚举中包含属性

枚举本身是类型,只能包含计算属性(只有方法,不存储在enum中)和类型属性(也不存储在enum中),能包含存储属性。
注意:结构体可以有存储属性,结构体大小就是存储属性的大小。

  • 枚举中包含方法

enum WEEK : Int {
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case STA
    case SUN
    
    func nextDay()-> WEEK{
        switch self {
        case .SUN:
            return .MON
        default:
            return WEEK(rawValue: self.rawValue + 1) ?? self
        }
    }
}
  • 迭代枚举

如果需要迭代枚举中的所有值时,需要自定义的枚举遵守CaseIterable协议,通过获取enum的allCases属性获取所有值:

enum FlowerType: CaseIterable {
    case Rose
    case Orchid
    case Peony
}

let numOfFlowerType = FlowerType.allCases.count
for flower in FlowerType.allCases {
    print(flower)
}
//Rose
//Orchid
//Peony
  • 枚举中可使用协议

protocol CustomDesc {
    var description : String { get }
}

enum FlowerType: CustomDesc {
    case Rose
    case Orchid
    case Peony
    
    var description: String {
        switch self {
        case .Rose:
            return "Rose"
        case .Orchid:
            return "Orchid"
        case .Peony:
            return "Peony"
        }
    }
}
  • 枚举可扩展

枚举通过扩展case(放在枚举中)和方法(放在枚举的扩展中)分离

enum FlowerType {
    case Rose
    case Orchid
    case Peony
}

extension FlowerType {
    func introduced() -> String {
        switch self {
        case .Rose:
            return "Rose"
        case .Orchid:
            return "Orchid"
        case .Peony:
            return "Peony"
        }
    }
}
  • 枚举可使用泛型

枚举通过泛型参数定义以适应枚举中的关联值(可有多个泛型参数),拿Swift标准库中的Optional类型为例:

enum Optional<Wrapped> {
    case none
    case some(Wrapped)
}

let aValue = Optional<Int>.some(5)
let noValue = Optional<Int>.none
if noValue == Optional.none { print("No value")
  • 枚举的大小

1.只有一个case的简单枚举(其实无意义):

enum test {
  case a
}
print(MemoryLayout<test>.size) //0
print(MemoryLayout<test>.stride) // 1
  1. 有多个case的
enum test {
  case a
  case b
  case c
  case d
}
print(MemoryLayout<test>.size) // 1
print(MemoryLayout<test>.stride) // 1

enum其实就是以1字节的长度存储在内存中即UInt8,当case个数超过UInt8的最大值255个时,编译器自动将enum的内存大小UInt16。也可在汇编模式下看出:每次移动1个字节。

Swift enumeration

var tmp = test.a时
Swift enumeration

var tmp = test.b时

总结:原始值的enum大小取决于case的数量,与rawValue类型无关,case超不过255个就是 UInt8—>1字节,如果超过则自动升级成UInt16

  1. 关联值的枚举:大小取决于case中最大内存的大小以及字节对齐

PS:是否+1还没弄透彻,下次弄明白了再补上...,这里先记录一下自测总结出来的结果,可能有误…(可略过)

猜想1: enum去占最大内存的case外:
有占大于等于最大关联值类型a(>8视为8,<8就是a本身)的case的话,size就需要在对齐基础上+1(case的大小);
小于最大关联值类型a字节则需要+1

 enum Shape {
     case circle(radius:UInt8) //1
     case rectangle(length:UInt8, width:Int, height:UInt16) // 1   8   2
     case square(width:Int32, height:Int32) // 4  4
 }  // size : 19

 enum Shape {
     case circle(radius:UInt8)
     case rectangle(length:UInt8, width:Int, height:UInt16)
     case square(width:UInt8, height:UInt16)
 }  // size : 18

size:除去最大的rectangle(字节对齐的原则上其大小为8 + 8 + 2 = 18),circlesquare中只要任一个所占内存大于等于enum中的最大类型(这里所有的case中最大的是Int为8字节),该enum的size大小都需要在对齐基础+ 1(case的大小);
这里circle只占了1个字节,而在上面的enum中square占4 + 4 等于8字节,所以需要+1 = 19,下面的enum中square占1 + 2 = 3小于8字节,不需要+1。

猜想2: case数量>1的前提下, 最大case只有一个关联值时始终+1。

Swift enumeration

stride:为了字节对齐(空间换取时间,提高效率),需要将实际大小补齐到最大所占内存大小(这里是Int–>8)的倍数,所以19补齐成8的倍数即3 * 8 = 24

  1. 枚举嵌套枚举的大小
    比较特殊:取决于内层枚举的大小字节对齐
Swift enumeration

5.嵌套枚举的结构体的大小 :取决于存储属性的内存大小(有没有方法都一样,因为方法存在结构体中)
有存储属性时size为1、stride为1
无存储属性时size为0、stride为1 (空结构体也是size为0、stride为1 )

  • 区分内存对齐字节对齐
    内存对齐:64位下8字节对齐,分配对象时用到内存对齐。
    字节对齐:对齐的目的就是地址要从偶地址开始,成员变量的起始位要从该变量内存大小的倍数开始。

下面两个结构体,不同类型的成员变量交换位置都会导致结构体的内存大小不一致

struct A {
    var height : Double  //8
    var count : UInt16   //2
    var age : UInt8        //1
}

print(MemoryLayout<A>.size) //11
print(MemoryLayout<A>.stride)  //16

struct B {
    var height : Double  //8
    var age : UInt8        //1
    var count : UInt16   //2
}

print(MemoryLayout<B>.size) //12
print(MemoryLayout<B>.stride)  //16
Swift enumeration

结构体A的内存存放
Swift enumeration

结构体B的内存存放

OC的结构体内存对齐规则如下:

  • struct的第一个数据成员要从偏移量offset为0的位置开始,后面的其他成员的起始的位置要从该成员类型的字节大小(Swift中>8的视为8)的整数倍开始

  • 步长是最大成员类型(Swift中>8的视为8)的整数倍

  • 若一个结构体包含另一个结构体的成员,结构体成员要从自身结构体中最大类型(Swift中>8的视为8)的整数倍开始

  • indirect关键字

  1. 使用场景:用递归的方式表达想要的数据结构
Swift enumeration

使用`嵌套枚举`表达`链表`的结构

上面图片中是一个递归枚举,若不用indirect关键字声明编译器会报错,所以下面case需要用indirect关键字声明:

Swift enumeration

递归枚举

原因:普通枚举大小在编译时期就确定好的,而这里的递归枚举大小在编译时是未知的,所以需要用indirect关键字来说明需要在堆上分配一块内存空间来存放,当前case会使用引用类型存储。

  1. 本质:在上分配一块内存,存储一个指向case的值地址
Swift enumeration

递归枚举的内存结构

定义一个简单的递归枚举变量,分析SILindirect关键字到底做了些什么事情?

var list = List<Int>.node(4, List<Int>.end)
// main
sil @main : [email protected](c) (Int32, UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>) -> Int32 {
bb0(%0 : $Int32, %1 : $UnsafeMutablePointer<Optional<UnsafeMutablePointer<Int8>>>):
  alloc_global @main.list : main.List<Swift.Int>        // id: %2
  %3 = global_addr @main.list : main.List<Swift.Int> : $*List<Int> // user: %16
  %4 = metatype [email protected] List<Int>.Type
  // 构建Int类型的值4
  %5 = integer_literal $Builtin.Int64, 4          // user: %6
  %6 = struct $Int (%5 : $Builtin.Int64)          // user: %13
  %7 = metatype [email protected] List<Int>.Type
  %8 = enum $List<Int>, #List.end!enumelt         // user: %14
  // 分配堆空间存储metadata、refCount、value(List<Int>.node(T, List<Int>)这个case的值(是一个元组))
  %9 = alloc_box $<τ_0_0> { var (τ_0_0, List<τ_0_0>) } <Int> // users: %15, %10
  // 取出%9(类似对象)里面value的地址 *(Int, List<Int>)
  %10 = project_box %9 : $<τ_0_0> { var (τ_0_0, List<τ_0_0>) } <Int>, 0 // users: %12, %11
  // 第一个元素的地址 *Int
  %11 = tuple_element_addr %10 : $*(Int, List<Int>), 0 // user: %13
  // 第二个元素的地址 *List<Int>
  %12 = tuple_element_addr %10 : $*(Int, List<Int>), 1 // user: %14
  // 将4存入元组中第一个元素的地址里
  store %6 to %11 : $*Int                         // id: %13
  // 将List<Int>.end存入元组中第二个元素的地址里
  store %8 to %12 : $*List<Int>                   // id: %14
  // 构建一个枚举  List<Int>.node %9--->List<Int>.node(4, List<Int>.end)
  %15 = enum $List<Int>, #List.node!enumelt, %9 : $<τ_0_0> { var (τ_0_0, List<τ_0_0>) } <Int> // user: %16
  store %15 to %3 : $*List<Int>                   // id: %16
  %17 = integer_literal $Builtin.Int32, 0         // user: %18
  %18 = struct $Int32 (%17 : $Builtin.Int32)      // user: %19
  return %18 : $Int32                             // id: %19
} // end sil function 'main'

alloc_box:在空间上分配内存区域存放T类型的value,box相当于在value外面包裹了一层;project_box:取出来的是value的地址;可通过断点看到调用了swift_allocObject

Swift enumeration

Swift enumeration

通过上面SIL分析可知:indirect关键字的本质就是在上分配一块内存来存储一个引用地址,该地址中存放的是被indirect修饰的case

  1. indirect关键字修饰enum:表明整个enum类型都是以引用类型来存储。

先看看indirect修饰enum时,其case的内存结构:case里面直接存放的是关联值9;

Swift enumeration

下面是加indirect修饰enum时,其case的内存结构:case里面存放的是一个引用地址

Swift enumeration

  • Swift和OC枚举混编

  • OC访问Swift中的枚举
  1. enum要用@objc修饰
  2. 必须将rawValue的类型声明成Int类型(因为OC中的枚举就是的整型值)
@objc enum Week : Int {
    case MON
    case TUE
    case WED
    case THU
    case FRI
    case STA
    case SUN
}

然后在OC-Swift.h桥接文件中,该enum就已存在:

typedef SWIFT_ENUM(NSInteger, Week, closed) {
  WeekMON = 0,
  WeekTUE = 1,
  WeekWED = 2,
  WeekTHU = 3,
  WeekFRI = 4,
  WeekSTA = 5,
  WeekSUN = 6,
};

OC文件中就可以直接使用

Swift enumeration

OC中使用Swift的enum
  1. OC访问String类型的enum
// .swift文件中封装String类型的enum
@objc class Week : NSObject {
    @objc enum WeekInt : Int {
        case MON, TUE, WED, THU, FRI, STA ,SUN
        
        var string : String {
            return Week.getName(weekValue: self)
        }
    }

   @objc class func getName(weekValue:WeekInt)->String {
        switch weekValue {
            case .MON: return "MON"
            case .TUE: return "TUE"
            case .WED: return "WED"
            case .THU: return "THU"
            case .FRI: return "FRI"
            case .STA: return "STA"
            case .SUN: return "SUN"
        }
    }
}
// OC-Swift.h桥接文件
enum WeekInt : NSInteger;
@class NSString;

SWIFT_CLASS("_TtC8YYOCTest4Week")
@interface Week : NSObject
+ (NSString * _Nonnull)getNameWithWeekValue:(enum WeekInt)weekValue SWIFT_WARN_UNUSED_RESULT;
- (nonnull instancetype)init OBJC_DESIGNATED_INITIALIZER;
@end

typedef SWIFT_ENUM(NSInteger, WeekInt, closed) {
  WeekIntMON = 0,
  WeekIntTUE = 1,
  WeekIntWED = 2,
  WeekIntTHU = 3,
  WeekIntFRI = 4,
  WeekIntSTA = 5,
  WeekIntSUN = 6,
};
// OC中调用
NSString *weekStr = [Week getNameWithWeekValue:WeekIntFRI];
        NSLog(@"%@", weekStr); // FRI
// Swift中调用
var weekStr = Week.WeekInt.STA.string
print(weekStr) //STA
  • Swift访问OC中的枚举
    OC中的枚举会被自动转换成Swift的enum。

1.typedef NS_ENUM方式声明的枚举

//OC .h文件中定义的枚举
typedef NS_ENUM(NSInteger, YYSTATE){
    Invalid = -1,
    Failed,
    Success
};

// 自动转换成Swift的枚举:(在系统自动生成的Swift文件中)
public enum YYSTATE : Int {

    
    case Invalid = -1

    case Failed = 0

    case Success = 1
}

// .swift文件中使用enum:
var state = YYSTATE.Success
print(state.rawValue) // 1

//转换后的enum的大小与步长:
print(MemoryLayout<YYSTATE>.size) //8
print(MemoryLayout<YYSTATE>.stride)  //8
Swift enumeration

查看系统生成的Swfit文件
  1. NS_ENUM方式声明的枚举
// OC .h文件中定义枚举
NS_ENUM(NSInteger, YYSTATE){
    Invalid = -1,
    Failed,
    Success
};

//自动转换后的枚举:
public var YYSTATE: YYSTATE

public enum YYSTATE : Int {

    
    case Invalid = -1

    case Failed = 0

    case Success = 1
}

// .swift中使用枚举:
var state = YYSTATE.Success
print(state.rawValue) // 1

//转换后的enum的大小与步长:
print(MemoryLayout<YYSTATE>.size) //8
print(MemoryLayout<YYSTATE>.stride)  //8
  1. typedef enum方式声明的枚举
// .h中定义枚举
typedef enum {
    YYSTATEInvalid = -1,
    YYSTATEFailed,
    YYSTATESuccess
}YYSTATE;

//自动转换后的枚举:
public struct YYSTATE : Equatable, RawRepresentable {

    public init(_ rawValue: Int32)

    public init(rawValue: Int32)

    public var rawValue: Int32
}

//.swift中使用枚举:
var state = YYSTATESuccess
print(state.rawValue) //1

//转换后的enum的大小与步长:
print(MemoryLayout<YYSTATE>.size) //4
print(MemoryLayout<YYSTATE>.stride)  //4

Recommended Today

Modul of fastems

Each module of fastems is implemented from the abstract class Fastems.Mms.Client.Infrastructure.UiModuleBase; public class DataManagerModule : UiModuleBase { public override void Initialize() { AddResourceDictionary(“/Resources/DataManagerResources.xaml”, typeof(DataManagerModule)); RegisterViewWithRegion(“DialogRegion”, typeof(DialogView)); RegisterViewWithRegion(“BusyIndicatorRegion”, typeof(BusyIndicatorView)); } } And Fastems.Mms.Client.Infrastructure.UiModuleBase inherits from Fastems.Mms.Client.Infrastructure.ModuleBase public abstract class UiModuleBase : ModuleBase { [Import] public IRegionManager RegionManager { get; set; } [Import] public IMergedDictionaryRegistry MergedDictionaryRegistry { […]