Swift中的类与结构体
初识类与结构体
先看代码十分相似,在使用中也基本一致,他们之间存在相同点和不同点

相同点
定义存储值的属性
定义方法
定义下标以使用下标语法提供对其值的访问
定义初始化器
使用 extension 来拓展功能
遵循协议来提供某种功能
不同点
类有继承的特性,而结构体没有
类型转换使您能够在运行时检查和解释类实例的类型
类有析构函数用来释放其分配的资源
引用计数允许对一个类实例有多个引用
引用类型和值类型
类是引用类型。也就意味着一个类类型的变量并不直接存储具体的实例对象,是对当前存储具体
实例内存地址的引用。
结构体是值类型。相比较类类型的变量中存储的是地址,值类型存储的就是具体的实例(或者说具体的值)
我们利用简单赋值具体看一下
我们在控制台输出可以看出person和p的内存地址是相同的,结构体person1和p1的值是相同的
另外引用类型和值类型还有一个最直观的区别就是存储的位置不同:一般情况,值类型存储的在 栈上,引用类型存储在堆上。
内存区域的一些基本认知
1、内存的排列是从高到低
2、一般只有从0x00000001到0x000007F之间的内存才是我们可以操作的区域
3、一般我们把操作区域分为Stack(栈 - 存放的是局部变量和函数运行过程中的上下文),Heap(堆 - 存储所有对象),Global(全局区 + 常量区 + 指令区 - 存储全局变量 + 常量 + 代码区)
4、当栈和堆拉伸后导致内存地址重合,即溢出
5、内存分配栈空间比堆空间快,因为堆空间需要查找合适的区域再拷贝值,完成实例对象分配,离开作用域后销毁查找并把内存块插入,而栈空间每次会直接分配新的空间,离开作用域直接回滚销毁。简单来说就是堆始终需要寻找,而栈不用
综合效率因素,在应用中把类替换成结构体能够有效提升项目效率。
类的初始化器
在类编译的时候默认不会自动提供成员初始化器,但是对于结构体来说编译 器会提供默认的初始化方法(前提是我们自己没有指定初始化器)!
Swift 中创建类和结构体的实例时必须为所有的存储属性设置一个合适的初始值。所以
类 Person 必须要提供对应的指定初始化器,同时我们也可以为当前的类提供便捷初
始化器(注意:便捷初始化器必须从相同的类里调用另一个初始化器。)
1、指定初始化器必须保证在向上委托给父类初始化器之前,其所在类引入的所有属性都要初始化完成。
2、指定初始化器必须先向上委托父类初始化器,然后才能为继承的属性设置新值。如果不这样做,指定初始化器赋予的新值将被父类中的初始化器所覆盖
3、便捷初始化器必须先委托同类中的其它初始化器,然后再为任意属性赋新值(包括同类里定义的属性)。如果没这么做,便捷构初始化器赋予的新值将被自己类中其它指定初始化器所覆盖。
4、初始化器在第一阶段初始化完成之前,不能调用任何实例方法、不能读取任何实例属性的值,也不能引用 self 作为值。
可失败初始化器: 这个也非常好理解,也就意味着当前因为参数的不合法或者外部条件 的不满足,存在初始化失败的情况。这种 Swift 中可失败初始化器写 return nil 语句,
来表明可失败初始化器在何种情况下会触发初始化失败。
必要初始化器:在类的初始化器前添加 required 修饰符来表明所有该类的子类都必须
实现该初始化器。如果在子类中没有实现会报错。
类的生命周期
OC和Swift均是通过LLVM进行编译的
OC 通过 clang 编译器,编译成 IR,然后再生成可执行文件 .o(这里也就是我们的机器码)
Swift 则是通过 Swift 编译器编译成 IR,然后在生成可执行文件。
Swift 对象内存分配:
__allocating_init -> swift_allocObject -> swift_allocObject -> swift_slowAlloc -> Malloc
Swift 对象的内存结构 HeapObject (OC objc_object) ,有两个属性: 一个是Metadata ,一个是 RefCount ,默认占用 16 字节大小。
Swift类的数据结构如下