2021年5月8日星期六

Swift系列四

适度给类型起别名能够让代码更加易懂,开发效率更高,可维护性更好。

一、typealias(别名)

typealias用来给类型起别名。

typealias Byte = Int8typealias Short = Int16typealias Long = Int64typealias Date = (year: Int, month: Int, day: Int)func test(_ date: Date) { print(date.year)}test((2019, 6, 25))// 输出:2019typealias IntFn = (Int, Int) -> Intfunc diff(v1: Int, v2: Int) -> Int { v1 - v2}let fn: IntFn = difffn(10, 5)// 输出:5

Void的本质就是空元祖的别名:public typealias Void = ()

二、枚举

Swift枚举和C/OC语言不一样,以前写OC的时候枚举本质是int类型,但Swift中枚举可以是多类型的。

官方建议:枚举名称使用大写,成员使用小写。

2.1. 基本用法

// 定义枚举enum YBColor { case white case black case gray}// 等价于上面的代码//enum YBColor {// case white, black, gray//}var color = YBColor.whitecolor = YBColor.blackcolor = .gray // 简写(因为此时已经确定变量color是YBColor类型)print(color) // 输出:gray// 循环控制switch color {case .white: print("white")case .black: print("black")case .gray: print("gray")}

2.2. 关联值

有时将枚举的成员值其他类型的值关联存储在一起,会非常有用.

案例:

enum Score { case points(Int) case grade(Character)}// 数值表达var score = Score.points(96)// 等级/字符表达score = .grade("A")enum Date { case digit(year: Int, month: Int, day: Int) case string(String)}var date = Date.digit(year: 2019, month: 06, day: 25)date = .string("2019-06-25")switch date {case .digit(let year, let month, let day): print(year, month, day, separator:"/")case let .string(value): print(value)}/* 输出: 2019-06-25*/

let写在枚举成员前面意味着枚举成员形参只能是常量,放在形参里面可以自定义选择是var还是let

2.2. 原始值

枚举成员可以使用相同类型的默认值预先关联,这个默认值叫做:原始值。

enum Direction : Character { case up = "w" case down = "s" case left = "a" case right = "d"}var direction = Direction.upprint(direction) // 输出:upprint(direction.rawValue) // 输出:wprint(Direction.down.rawValue) // 输出:s

如果枚举的原始值类型是IntString,Swift会自动分配原始值:

enum Direction : String { case up = "up" case down = "down" case left = "left" case right = "right"}var direction = Direction.upprint(direction) // 输出:upprint(direction.rawValue) // 输出:upprint(Direction.down.rawValue) // 输出:down// 等价enum Direction : String { case up, down, left, right}var direction = Direction.upprint(direction) // 输出:upprint(direction.rawValue) // 输出:upprint(Direction.down.rawValue) // 输出:down

Int类型,成员值自增(类似C/OC枚举):

enum Season : Int { case spring, summer, autumn, winter}print(Season.spring.rawValue) // 输出:0print(Season.summer.rawValue) // 输出:1print(Season.autumn.rawValue) // 输出:2print(Season.winter.rawValue) // 输出:3enum Season : Int { case spring = 1, summer, autumn = 4, winter}print(Season.spring.rawValue)print(Season.summer.rawValue)print(Season.autumn.rawValue)print(Season.winter.rawValue)

2.3. 递归枚举

  • 关键字:indirect
  • 可以把需要递归枚举的成员前面加indirect,也可以为了方便直接加到枚举定义前面。
indirect enum ArithExpr { case number(Int) case sum(ArithExpr, ArithExpr) case diff(ArithExpr, ArithExpr)}//enum ArithExpr {// case number(Int)// indirect case sum(ArithExpr, ArithExpr)// indirect case diff(ArithExpr, ArithExpr)//}let five = ArithExpr.number(5)let four = ArithExpr.number(4)let two = ArithExpr.number(2)let sum = ArithExpr.sum(five, four)let diff = ArithExpr.diff(sum, two)func cal(_ expr: ArithExpr) -> Int { switch expr { case let .number(value):  return value case let .sum(left, right):  return cal(left) + cal(right) case let .diff(left, right):  return cal(left) - cal(right) }}cal(diff) // 输出:7

三、枚举的内存布局

在Swift中查看内存占用大小及对齐方式使用枚举:MemoryLayout

  • size:实际用到的空间大小
  • stride:分配占用的空间大小
  • alignment:内存对齐方式

下面的意思是,Int在内存中占用8个字节,内存对齐数是8:

MemoryLayout<int>.size // 输出:8MemoryLayout<int>.stride // 输出:8MemoryLayout<int>.alignment // 输出:8

查看枚举占用内存:

enum Password { case number(Int, Int, Int, Int) case other}var pwd = Password.number(1, 2, 2, 3)MemoryLayout.size(ofValue: pwd) // 输出:33MemoryLayout.stride(ofValue: pwd) // 输出:40MemoryLayout.alignment(ofValue: pwd) // 输出:8

为什么是占用内存大小是33,而分配了40?

  • number(Int, Int, Int, Int)占用32个字节,other占用1个字节,所以一共只需要占用33个字节就够用了
  • 因为内存对齐数是8,所以分配内存的时候只能是8的倍数,而33个字节不够8的倍数,所以往高位补齐后就是40了

为什么other占用1个字节呢?

enum Season { case spring, summer, autumn, winter}MemoryLayout<season>.size // 输出:1MemoryLayout<season>.stride // 输出:1MemoryLayout<season>.alignment // 输出:1// 限定类型enum Season: String { case spring, summer, autumn, winter}MemoryLayout<season>.size // 输出:1MemoryLayout<season>.stride // 输出:1MemoryLayout<season>.alignment // 输出:1
  • 上面代码可以看出不管类型是什么占用的内存大小都是1个字节;
  • 其实本质上是关联值和原始值的区别。

结论一: 把传进去的关联值直接存储到枚举变量内存里面的,所以枚举变量是关联值的话,内存是一定和将要存储的关联值大小有关。

为了证实结论一,比较下面的两个不同类型的关联值:

enum Password { case number(Int, Int, Int, Int) case other}MemoryLayout<password>.size // 输出:33MemoryLayout<password>.stride // 输出:40MemoryLayout<password>.alignment // 输出:8enum Password { case number(String, String, String, String) case other}MemoryLayout<password>.size // 输出:65MemoryLayout<password>.stride // 输出:72MemoryLayout<password>.alignment // 输出:8

结论二: 原始值固定后是不能修改的,内存中只会把对应的成员值(序号)存下来,这时候1个字节足够用了,和枚举类型无关(不管是Int还是String枚举都是占用一个字节)。

分析下面代码:

enum Season: Int { // 序号0   序号1  序号2  序号3 case spring = 1, summer = 2, autumn = 3, winter = 4}var season1 = Season.springvar season2 = Season.springvar season3 = Season.springMemoryLayout<season>.size // 输出:1MemoryLayout<season>.stride // 输出:1MemoryLayout<season>.alignment // 输出:1

疑问:成员值在内存中只占用1个字节,Int或String的原始值是怎么存下的?rawValue其实是另外一块地址。

  • 关联值才会存储到枚举变量中,原始值不会占用枚举变量内存
  • 我们可以通过内存地址看到前面的字节被关联值占用,关联值后面有一个字节是保存成员值
    • 1个字节存储成员值(如果只有一个枚举成员则不占用内存)
    • N个字节存储关联值(N取占用内存最大的关联值),任何一个case的关联值都共用这N个字节(共用体)
    • 剩余字节按照对齐数补齐

Switchcase其实是比较枚举的成员值的









原文转载:http://www.shaoqun.com/a/729775.html

跨境电商:https://www.ikjzd.com/

心怡科技:https://www.ikjzd.com/w/1327

联动优势:https://www.ikjzd.com/w/1921


适度给类型起别名能够让代码更加易懂,开发效率更高,可维护性更好。一、typealias(别名)typealias用来给类型起别名。typealiasByte=Int8typealiasShort=Int16typealiasLong=Int64typealiasDate=(year:Int,month:Int,day:Int)functest(_date:Date){print(date.year
beien:https://www.ikjzd.com/w/1336
三维度:https://www.ikjzd.com/w/1312
FEN:https://www.ikjzd.com/w/2668
2019年跨境电商大盘点:列王的纷争:https://www.ikjzd.com/home/113907
谷歌上线"热门产品"功能 跨境卖家引流再添新渠道:https://www.ikjzd.com/home/114948
理工生硬核分析:制造强国能生产多少口罩?:https://www.ikjzd.com/home/115674

没有评论:

发表评论