结构与枚举
结构
struct属于值类型,具有值拷贝语义(赋值与传值)
struct不支持面向对象,主要用于定义轻量级数值类型;class支持面向对象,主要用于设计有丰富关系的组件系统。
struct有传参拷贝成本,不要定义尺寸过大的结构;class有ARC内存管理成本。
不要在struct内定义引用类型属性(会改变struct的值拷贝语义)。
struct VS. class
相同点:
都可以定义以下成员:属性、方法、下标、初始化器
都支持类型扩展、协议
不同点:
类支持继承和多态,结构不支持;
类必须自己定义初始化器,结构会有默认的按成员初始化器;
类支持析构器,结构不支持;
类的实例在堆上,由ARC负责释放;结构的实例在栈上,栈结束自动释放,不参与ARC管理
类支持引用相等比较(===与!==),结构不支持
枚举
enum用于定义一组相关的值成员,enum同属于值类型,具有值拷贝语义(赋值与传参)。
可以使用switch-case语句处理enum,但case必须包含所有的枚举值,否则需要用default。
可以为enum指定原始值,即raw value,类型可以是字符、字符串、整数、浮点数。数值默认从0开始,字符串与枚举值名称相同。
enum支持关联值,可以设置不同类型的值成员,类似联合数据结构
enum还可以定义以下成员:计算属性、方法、初始化器。
继承与多态
继承:子类自动继承基类的属性、方法、下标。
只有类支持继承,结构和枚举不支持继承。
继承同时支持实例成员和类成员。
继承的两层含义:成员复用:子类复用基类成员;类型抽象:将子类当作父类来使用(IS-A关系准则)
继承的内存模型:
多态:子类在同一行为接口下,表现不同的实现方式。
子类使用override关键字来表达多态定义。
可以重写的成员有:方法、属性、下标;包括实例成员和类型成员。
在子类的代码中,可以使用super来调用基类的实现。
在成员上使用final阻止子类override该成员;在类上使用final阻止该类被继承。
继承中的init和deinit
初始化器init:
如果子类没有定义初始化器,则将自动继承基类的初始化器;
如果子类定义了初始化器,则不再继承,此时子类初始化器必须调用基类的一个初始化器。如果手工不调用,编译器将自动生成调用;
如果子类初始化器与基类初始化器原型一致,必须使用override;
在子类中使用基类属性,必须确保首先调用基类初始化器
析构器deinit
如果子类没有定义析构器,会自动继承基类析构器;
子类析构器执行完毕后,会自动调用基类析构器(无法手工调用);
子类析构器自动具有多态性
协议
协议:类型的合同约定,只描述外部接口,不提供具体实现。
协议可以包含以下成员:属性、方法、初始化器、下标、操作符。
一个类型可以实现多个协议,协议可以应用在如下类型上。但可以添加class关键字标明协议只能应用在类上:类、结构、枚举。
使用协议:
协议本质上是一种类型,可以作为声明类型,但不能创建实例。
协议变量的内存模型遵从实际类型的内存模型:引用类型传参、拷贝采用传引用方式;值类型传参、拷贝采用传值方式。
检查协议类型:使用is检查类型是否实现了协议;使用as?和as!将类型下溯转型为协议。
协议中的属性:
协议中可以定义只读属性,也可以定义读写属性;
协议中可以定义实例属性,也可以定义类型属性;
协议中只能定义变量属性,不能定义常量属性;
实现协议时,并不要求实现为存储属性、还是计算属性。
协议中的方法:
协议可以定义实例方法,也可以定义类型方法;
协议中的方法不能定义参数的默认值;
针对值类型的mutating协议方法:
值类型(struct,enum)实现的实例方法如果要更改实例本身,需要在协议方法的定义中标明mutating关键字,同时在方法实现时也添加mutating关键字。
添加了mutating的协议方法,对类的实现方法无影响。在类内实现mutating协议方法时,无需再添加mutating关键字。
协议中的初始化器:
协议中可以定义初始化器,但不可以定义析构器。
当class中实现协议定义的初始化器时,需要添加required关键字:
标明子类也需要提供该初始化器;如果定义为final类,则不需要required关键字。
协议可以定义可失败的初始化器init?,具体实现时可以失败、也可以非失败。
更多协议形式:
协议继承:一个协议可以继承一个或多个协议;实现子协议的类型,也必须实现父协议中约定的成员。
协议组合:
可以使用protocol<A,B,...>来组合多个协议。
实现组合协议的类型,必须实现组合协议中的每一个协议。
组合协议是一个临时类型。
可选协议:
协议的某些成员可以定义为optional,不必实现;
可选协议只能应用于class,不能应用于struct和enum;
可选协议必须标明@objc特性(即使不需要和Objective-C互操作)。
字符串
String是一个Unicode编码、16位字符的字符序列。
String与NSString支持无缝互操作。
String被定义为struct,值类型,拷贝时具有值语义。
String是一个struct,但内部包含一个指向堆上的“内容指针”,其指向的对象存放真正的字符串内容。
内存模型
使用字符串:
使用var和let来控制字符串的可变性;
获取字符串中的字符;
使用append和+连接字符串;
使用字符转义;
字符串插值(String Interpolation)。
copy-on-write共享技术
同一个字符串内容拷贝到不同的变量中时,“内容指针”不变,即不同变量“共享”同一份堆内存。从而实现“节省内存”。
如果某一个变量的字符串内容改变时,先将原来堆内存拷贝一份,“内容指针”指向新的拷贝,然后再更改新的拷贝。从而确保“正确性”。
copy-on-write的目的是实现“内容相同的字符串共享内存,同时又支持字符串随时改变”。
最佳实践:尽量不改变字符串,尽量使用常量字符串let。
copy-on-write--更改前
copy-on-write--更改后
缓存容量与增长:
字符串初始化后,会分配一个缓存容量capacity,其长度一般大于实际的字符数量。
当字符串增长时,如果实际需求大于capacity,其capacity会以二倍的方式指数增长。代价:分配新的堆内存 2*capacity;将原来堆内存上的内容拷贝到新内存;释放原来堆内存。
最佳实践:估计好capacity,预先分配好一定容量,避免以后capacity的增长。
集合类型
数组:
Array是一个有序的元素序列,支持随机存取,支持动态更新长度。
索引从0开始,索引访问越界会抛出运行时异常。
Array被定义为struct,值类型,拷贝时具有值语义。
Array是一个struct,但内部包含一个指向堆上的“元素指针”,其指向的对象存放真正的数组元素。
1.如果数组元素为值类型,拷贝数组时,元素包含值本身。
2.如果数组元素为引用类型,拷贝数组时,元素引用指向相同的对象。
内存模型:
使用数组:
使用var和let来控制数组的常量性:数组长度和元素内容都不能更改。
数组遍历:使用for循环访问array[index]需要检查索引越界,具有性能代价;尽可能使用for-in遍历数组元素;或使用Array.enumerate()遍历索引;二者编译器会优化掉索引检查。
尽量避免使用insert和remove操作,因为会改变数组元素序列,涉及到大量的内存拷贝操作,代价较高。
缓存容量与增长
数组初始化后,会分配一个缓存容量capacity,其长度一般大于实际的元素数量
当数组长度增长时,如果实际需求大于capacity,其capacity会以近似二倍的方式指数增长。代价:分配新的堆内存 2*capacity;将原来堆内存上的内容拷贝到新内存;释放原来堆内存。
最佳实践:估计好capacity,预先分配好一定容量,避免以后capacity的增长。
copy-on-write共享技术
同一个数组拷贝到不同的变量中时,“元素指针”不变,即不同变量“共享”同一份堆内存。从而实现“节省内存”。
如果某一个变量的元素内容改变时,先将原来堆内存拷贝一份,“元素指针”指向新的拷贝,然后再更改新的拷贝。从而确保“正确性”。
copy-on-write的目的是实现“元素内容相同的数组共享内存,同时又支持元素随时改变”。
最佳实践:尽量不改变数组元素和长度,尽量使用常量数组。
copy-on-write--更改前
copy-on-write--更改后
集合Set
Set是一个无序的集合,其存储的值不能重复。
Set中的元素必须有哈希值,即支持Hashable协议。
Set被定义为struct,值类型,拷贝时具有值语义。
Set也是一个struct,但内部包含一个指向堆上的“元素指针”,其指向的对象存放真正的元素。
字典Dictionary
Dictionary是一个存储key-value的无序的集合,key唯一,value可重复。
Dictionary中的key必须有哈希值,即支持Hashable协议。
Dictionary被定义为struct,值类型,拷贝时具有值语义。
Dictionary也是一个struct,但内部包含一个指向堆上的“元素指针”,其指向的对象存放真正的元素。