Swift 枚举,结构体,类

枚举(enum)

枚举的成员类型

相对于OC的枚举来说.Swift中的枚举功能要更加强大

OC中枚举值只能是int型.而Swift中的枚举值可以是int,char,String.同一个枚举中的值也可以是不同类型.

枚举的原始值(rawValue)

当我们给枚举设置了类型后,枚举的成员都必须是相同类型.我们也可以用一个默认值将每个枚举值关联起来.

当设置为String和Int类型时,swift会自动为枚举成员设置原始值

enum Test : String{
    case test1 = "test1"
    case test2 = "test2"
}
enum Test1 : String{
    case test1
    case test2
}
print(Test.test1.rawValue)      //test1
print(Test1.test1.rawValue)     //test1

enum TestNumber:Int {
    case first
    case seconde
}

enum TestNumber1:Int {
    case first = 0
    case seconde = 1
}
print(TestNumber.first.rawValue)        //0
print(TestNumber1.first.rawValue)       //0

枚举的关联值

有时候会将枚举的成员值跟其他类型的值关联存储在一起.

enum Time {
    case intValue(hour:Int,min:Int,sec:Int)
    case stringValue(String)
}
var time = Time.intValue(hour: 10, min: 10, sec: 10)
time = .stringValue("10:20:21")

枚举的内存分配

没有关联值的枚举.有多个case的情况下占用的内存为1个字节.只用来存放枚举的成员值.而枚举的原始值,通过其他方法获取.如果只有一个case.不占用内存

enum Test {
    case number
    case other
}

MemoryLayout<Test>.size //1
MemoryLayout<Test>.stride //1
MemoryLayout<Test>.alignment //1

var t = Test.number
//内存分布
//00
t = Test.other
//01

enum Test1 {
    case number
}

MemoryLayout<Test1>.size //0
MemoryLayout<Test1>.stride //1
MemoryLayout<Test1>.alignment //1

存在关联值的枚举.在内存中占用的字节为占用字节最大关联值的字节大小+1.

最后的1个字节存放枚举的成员值.也可以理解为序号.表明当前是哪个case.而前面的字节存放关联值

enum Test {
    case number(Int, Int, Int, Int)
    case other
}

MemoryLayout<Test>.size //33
MemoryLayout<Test>.stride //40
MemoryLayout<Test>.alignment //8

var t = Test.number(1, 2, 3, 4)
//内存分布
//01 00 00 00 00 00 00 00
//02 00 00 00 00 00 00 00
//03 00 00 00 00 00 00 00
//04 00 00 00 00 00 00 00
//00
//00 00 00 00 00 00 00
t = Test.other
//00 00 00 00 00 00 00 00
//00 00 00 00 00 00 00 00
//00 00 00 00 00 00 00 00
//00 00 00 00 00 00 00 00
//01
//00 00 00 00 00 00 00

结构体(struct)

swift中 绝大多数公开类型都是结构体. 枚举和类只占小部分

Bool Int Double String Array Dictionary等常见类型都是结构体

结构体的初始化器

结构体会自动生成初始化器

struct A{
    var a1: Int
    var a2: Int
}
var a1 = A(a1: 10, a2: 10)
var a2 = A(a2: 10)              //Missing argument for parameter 'a1' in call
var a3 = A(a1: 10)              //Missing argument for parameter 'a2' in call
var a4 = A()                    //Missing arguments for parameters 'a1', 'a2' in call

struct C {
    var a:Int = 0
    var b:Int = 0
    var c:Int = 0
    
    func printValue() {
        print(a,b,c)
    }
}
var c1 = C(a: 10, b: 10, c: 10)
var c2 = C(a: 10)
var c3 = C(b: 10)
var c4 = C(c: 10)
var c5 = C()
c1.printValue()     //10 10 10
c2.printValue()     //10 0 0
c3.printValue()     //0 10 0
c4.printValue()     //0 0 10
c5.printValue()     //0 0 0

struct C1 {
    var a:Int
    var b:Int
    var c:Int
    init() {
        self.a = 0;
        self.b = 0;
        self.c = 0;
    }
    func printValue() {
        print(a,b,c)
    }
}
var c1 = C1(a: 10, b: 10, c: 10)    //Argument passed to call that takes no arguments
var c2 = C1(a: 10)                  //Argument passed to call that takes no arguments
var c3 = C1(b: 10)                  //Argument passed to call that takes no arguments
var c4 = C1(c: 10)                  //Argument passed to call that takes no arguments
var c5 = C1()
c1.printValue()     //10 10 10
c2.printValue()     //10 0 0
c3.printValue()     //0 10 0
c4.printValue()     //0 0 10
c5.printValue()     //0 0 0

对比A跟C可以看出.当当我们没有声明初始化器的时候.会根据情况自动生成初始化器.

  • 如果结构体声明成员变量时给了初始值,则会生成多个初始化器.
  • 如果成员变量没有初始值,则只会自动生成一个需要给所有成员赋值的初始化器

结论:结构体初始化完成时,必须保证所有的成员变量有值

对比C跟C1两个基本相同的结构体.可以看到.如果定义了初始化器,则其他的初始化器都不会自动生成.

结构体的内存布局

struct C {
    var a:Int = 0
    var b:Int = 0
    var c:Int = 0
    func printValue() {
        print(a,b,c)
    }
}
var c1 = C(a: 10, b: 10, c: 10)
print(MemoryLayout.size(ofValue: c1))//24

查看汇编代码发现,结构体实例化时调用了struct.C.init方法来进行结构体的实例化.

->  0x100003848 <+8>:   movl   $0xa, %eax
    0x10000384d <+13>:  movl   %edi, -0x1c(%rbp)
    0x100003850 <+16>:  movq   %rax, %rdi
    0x100003853 <+19>:  movq   %rsi, -0x28(%rbp)
    0x100003857 <+23>:  movq   %rax, %rsi
    0x10000385a <+26>:  movq   %rax, %rdx
    0x10000385d <+29>:  callq  0x100003b90               ; struct.C.init(a: Swift.Int, b: Swift.Int, c: Swift.Int) -> struct.C at main.swift:10

结构体C中.定义了3个Int成员变量.各占8个字节.通过MemoryLayout.size(ofValue: c1)得到结果24.所以可以得出结论.结构体的内存大小为所有成员变量的总和.那在内存上它是怎么分布的呢.

这里我们通过断点,窥探内存分布

image-20210519162316476.png
0x100008038 | 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00

发现0x100008038后的连续24个字节,每八个字节都是十六进制值0000000A,也就是十进制10.说明结构体的内存是在栈上连续分布的.每n位存放对应的成员变量值.n=成员变量类型占用字节大小.

结构体赋值

当我们把一个现有的结构体变量赋值给另一个结构体变量,并且修改其中一个结构体的某个成员值,会发生什么呢

struct C {
    var a:Int = 0
    var b:Int = 0
    var c:Int = 0
    func printValue() {
        print(a,b,c)
    }
}
var c1 = C(a: 10, b: 10, c: 10)
var c2 = c1
c1.a = 20
c1.printValue() //20 10 10
c2.printValue() //10 10 10

结论说明结构体之间的赋值.是深拷贝.

当一个结构体被赋值给另一个结构体时,会重新开辟一段连续的内存,再将对应的值放入对应的位置.当完成这一操作后,两个结构体完全独立.

我们可以按照上面的方式看看他们的内存分布

c1--0x100008038 | 14 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00
c2--0x100008050 | 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 0A 00 00 00 00 00 00 00 

可以发现两者的内存分布是正好相差了24个字节.也印证了上面说的,结构体在栈上连续分布.

结构体的写时复制(copy on write)

在swift中,String, Array, Dictionary等结构体类型,为了不浪费内存,采取了copy on write操作.

即:当上述类型赋值时,如果没有进行写的操作.两个变量都会指向同一块地址.当进行了写操作时,才会执行拷贝操作.

类(class)

类的初始化器

类与结构体长得很相似.但是类生成初始化器的规则并不同.

class A {
    var a = 0
    var b = 0
}
let a = A()

class B {                   //Class 'B' has no initializers
    var a : Int
    var b : Int
}
let b = B()             //'B' cannot be constructed because it has no accessible initializers

class C {
    var a : Int
    var b : Int
    init() {}           //Return from initializer without initializing all stored properties
}
let c = C()

class D {
    var a : Int
    var b : Int
    init() {
        self.a = 0
        self.b = 0
    }
}
let d = D()

class E {
    var a : Int
    var b : Int
    init(a:Int, b:Int) {
        self.a = a
        self.b = b
    }
}
let e = E(a: 10, b: 10)

通过上面的例子可以发现.如果在声明类的时候,有以下几种可能

  • 类的成员变量添加了初始值.则会自动生成无参的初始化器.
  • 类成员变量没有初始值,声明无参初始化器,需要在初始化器中为成员变量赋值
  • 手动声明带参数的初始化器

类的内存分布

下面通过汇编,可以看到在初始化类时,调用了__allocating_init.

class A {
    var a = 10
    var b = 10
}
let a = A()

->  0x100003251 <+17>: movq   %rcx, %rdi
    0x100003254 <+20>: movq   %rsi, -0x18(%rbp)
    0x100003258 <+24>: callq  0x1000032a0               ; type metadata accessor for struct.A at <compiler-generated>
    0x10000325d <+29>: movq   %rax, %r13
    0x100003260 <+32>: movq   %rdx, -0x20(%rbp)
    0x100003264 <+36>: callq  0x1000036e0               ; struct.A.__allocating_init() -> struct.A at main.swift:11

继续往下跟踪,_allocating_init->_swift_allocObject -> swift_slow_alloc -> malloc

会发现最终调用了malloc方法.说明在初始化类实例对象的过程中.会在堆空间开辟一段新的内存,用来存放实例对象的内容.我们来看看堆空间存放了什么

a | 0x10057fc20 28 82 00 00 01 00 00 00
                03 00 00 00 02 00 00 00 
                0A 00 00 00 00 00 00 00 
                0A 00 00 00 00 00 00 00

通过查看内存分布.发现有两个0A的值.正好就是a和b的值.而前面的16位.分别存放了对象的引用计数以及类型信息

类的赋值

class A {
    var a = 10
    var b = 10
    func printValue() {
        print(a,b)
    }
}
let a = A()
let b = a
b.a = 20
a.printValue()  //20 10
b.printValue()  //20 10

与结构体不同,类赋值时,是浅拷贝.只是将堆地址复制了一份.修改其中一个实例对象变量的值,都会引起另一个值的变化

类和结构体的不同

类型上:

  • 结构体:值类型 赋值给另一个参数.等于值传递.会复制多一份内存.且两部分内存完全独立.深拷贝
  • 类:引用类型 赋值时时引用传递,只传递了指针 浅拷贝

在内存中:

  • 结构体:栈上连续的N个字节.N = 结构体的size,结构体可能存在在栈或全局区(数据段)
  • 类:栈上存放指针变量.指针指向堆中实际类对象的地址,类一直存在堆区.但是指向它的指针有可能存在栈或全局区(数据段)

初始化:

  • 结构体:只调用init方法

  • 类: _allocating_init -> _swift_allocObject -> swift_slow_alloc -> malloc

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

推荐阅读更多精彩内容