《swift编程语言中文版》学习笔记
这篇文章是在阅读《swift编程语言中文版》一书时的部分学习笔记,这本书对Swift语言进行详解而全面的讲解,是Swift入门的不二之选。
2.9 类和结构体
swift中类和结构体的关系比其他语言更加密切,二者主要的区别在于:
- 类支持继承
- 类支持运行时检测类实例类型
- 类有deinit函数
- 类是引用类型而结构体是值类型
swift中类或结构体允许直接修改其属性(存在子属性的一般也就是类或结构体)的子属性,而在Objective-C不行
结构体有一个默认的成员逐一初始化器
在Swift中,整数、浮点数、布尔值、字符串、数组、字典都是值类型,并且是以结构体的形式存在的,结构体和枚举也都是值类型,意味着赋值或参数传递过程都是属于值拷贝,而类是引用类型
用于判定多个类引用是否指向同一个类实例的两个恒等运算符:等价于(===)和 不等价于(===)
swift中同样存在指针,比如引用类型,但是其声明方式同值类型一样
由于集合类型(数组和字典)都是以结构体实现的值类型,因此在赋值或传参过程中会发生拷贝,但是swift只会在有必要拷贝的情况下(比如拷贝后的数组长度发生变化等)才会执行拷贝,因此不必顾虑集合类型拷贝带来的性能问题。
字典类型的拷贝过程,如果键或值是值类型,则拷贝对应的值类型,如果是引用类型则拷贝的引用(其实就是指针)
数组类型只有在操作困难修改数组长度时才会发生,即使是修改数组内的元素值也不会发生拷贝,而是引用。
使用unshare函数可以确保数组的唯一性,但是并不意外这一定会发生拷贝,而是在有必要时才会拷贝。而copy函数属于强制拷贝数组。
可以使用恒等运算符来判定两个数组是否共用了相同的元素
数组只有在长度发生变化的操作中才会进行值拷贝,否则属于引用赋值
2.10 属性
属性分为存储属性和计算属性,前者用于存储常或变量作为实例的一部分,只能用于类或结构体中,而后者用于计算一个值,可以理解成一种运算,可用于类、结构体和枚举中。
存储属性
当值类型的实例赋值给一个常量时,值类型的所有属性都会变成常量,即使是变量属性也不能再修改,而当引用类型的实例赋值给一个常量时,仍可以修改实例的变量属性。
@lazy关键字用于声明延长存储属性:在第一次被调用时才会计算初始值的属性,类似懒加载。用前提包括必须是存储属性且是变量属性。因为常量属性在构造之前必须要有初始值,因此不能声明为延迟存储属性。
计算属性
计算属性必须声明为(var)变量属性,因为它的值是不固定的。计算属性不直接存储值,而是提供一对getter和setter用来获取和访问其他属性或者变量的值。
如果计算属性的setter没有定义表示新值的参数名,则可以使用默认名称newValue。
一个计算属性如果只有getter没有setter那就是只读类型,
属性监视器
作用类似于OC的KVO,可以为延迟存储属性之外的属性添加属性监视器,也可以通过重载的方式为父类的属性添加属性监视器。
两种监视器:willSet和didSet,前者的参数是新属性值,缺省参数名为newValue。后者的参数是旧属性值,缺省值参数名为oldValue。监视器在属性初始化时不会被触发。
全局变量和局部变量
计算属性和属性监视器同样适用于全局和局部变量。全局的常量或变量默认就是延迟计算的且不需要标记@lazy关键字。
类型属性
存储属性和计算属性通常用于特定类型的实例,但是属性也可以直接用于类型本身,称之为类型属性
类型属性类似于OC中声明在超类中的属性(swift没有超类的概念)。类型属性可以被所有实例访问。
用关键字static定义值类型的类型属性,用class定义类的类型属性。
值类型可以定义存储型和计算型的类型属性,而类只能定义计算型的类型属性。存储类型的类型属性必须在声明时指定默认值,因为类型本身没有构造器(只有为实例提供的初始化构造器)。
为什么类不能定义存储型的类型属性?很可能与类是引用类型有关系?
1 | //结构体类型 |
2.11 方法
方法是与某些特定类型相关联的函数,方法分:实例方法和类型方法
实例方法
方法和函数的局部名称和外部名称的默认行为的区别:
方法的第一个参数的外部参数可以省略,等价于在参数前加下划线(_),第二个及后面的参数名称可以同时作为局部和外部参数名称,等价于在参数前加#号。
self属性
self属性不需要显式调用,主要使用场景在于区分方法参数与实例属性,方法参数享有优先权,因此需要self指定实例属性消除歧义。
mutating关键字
类中的成员为引用类型,所以修改实例及其属性的值不会改变类型;而结构体和枚举中成员均为值类型,修改变量的值就是相当于修改变量的类型。Swift中默认不允许修改类型,因此需要前置mutaing关键字来表示该函数中能够修改类型。
值类型中的一般的实例方法不能修改其实例属性,而使用mutating关键字声明实例方法可以修改属性,并且修改的状态保留在原始结构中。在此方法中还可以给self重新赋值一个全新的实例,并替换原有实例。
枚举的mutating方法可以把self设置为枚举类型中不同的成员。
类型方法
类型方法的声明与类型属性声明类似,类的类型方法加关键字class,结构体和枚举的类型方法加static。
在本类或结构体的范围内,类型方法可以相互调用,也可以直接调用类型属性,不需要显式类型名称做为前缀。
2.12 附属脚本
附属脚本本质上提供了一种访问对象、集合或序列的快捷方式,可以定义在类、结构体和枚举中。使用时配合”[]”语法糖直接对多个属性同时访问或赋值
对于同一个目标可以定义多个附属脚本,通过索引值类型的不同进行重载,而且索引值得个数可以是多个。
定义附属脚本使用subscript关键字,没有函数名,或者说subcript就是默认函数名称。附属脚本对于索引入参的数量和类型没有限制,返回值可以是任何类型,但是不能使用in-out参数和对参数设置默认值。
附属脚本在定义上与实例方法类似,区别在于使用时附属脚本不需要指定函数名,而是根据参数自动匹配对应的脚本函数。
附属脚本与计算型属性类似,可以设定读写或者只读属性。区别在于附属脚本不是具体的属性,并且可以自定义参数和返回值类型和个数。而计算性属性的参数和返回值在定义时已经指定,可以理解为是对某一个具体属性的附属脚本,但是调用方式却没有附属脚本那么便捷,而是类似实例方法调用。
2.13 继承
在swift中,继承是区分“类”和其他类型的一个基本特征。子类可以调用和访问超类的方法、属性和附属脚本,并且可以重载这些方法。
不继承与其它类的类,称之为基类。不同于OC有一个共同的基类NSObject
在swift中,初始化器默认是不继承的。
重写
子类可以重写类方法、实例方法和附属脚本,使用关键字override标明
子类可以通过重写的方式将超类的存储型属性以计算型属性存在,但是该属性在超类还是存储型属性。
使用关键字final来防止重写
2.14 构造过程
与OC的构造器不同,Swift的构造器无返回值,主要任务是确保新实例在第一次使用之前完成正确的初始化。
在构造器中给存储型属性赋值时不会触发任何属性观测器
不同的构造器通过参数的不同进行区分,如果没有定义外部参数名,Swift会自动生成一个与内部参数名相同的外部名,等价于在内部名前添加#号。也可以使用下划线(_)来覆盖默认行为。
默认构造器
只有在所有成员变量都有默认值得时候,才能省略构造器,或者说Swift提供了一个默认的构造器。结构体的默认构造器是逐一成员构造器。
值类型的构造代理
构造器可以通过调用其他构造器完成部分构造过程,此过程称之为构造代理,且构造器代理的实现规则和形式在值类型和类类型有所不同。
值类型不支持继承,构造器代理任务只能给本身提供的其他构造器。而类类型可以调用超类的构造器完成构造代理。
值类型中一旦自定义了构造器后将无法访问到默认构造器,结构体则无法访问逐一成员构造器。如果定制的构造器是定义在扩张中,则不会覆盖默认构造器。
类的继承和构造过程
指定构造器是类中最主要的构造器:初始化类中所有的属性,并且根据父类链往上调用父类的构造器来实现父类初始化。每一个类至少拥有一个指定构造器,可继承自父类。
便利构造器则通过调用指定构造器实现,并给部分参数提供默认值。
构造器的继承与重载
与OC不同,一般情况下,Swift不会默认继承父类构造器,即此时创建子类时不能调用父类的构造器。可通过重载的方式继承与父类相同的构造器,且不需要使用override关键字。
两种特殊情况下,自动继承父类构造器:
- 子类没有定义任何指定构造器,它将自动继承所有父类的指定构造器和便利构造器。
- 子类提供了所有父类指定的构造器实现,将自动继承所有父类的便利构造器。
使用闭包或全局函数设置属性的默认值
如果某个存储型属性的默认值需要特别的定制,使用闭包或全局函数为其提供定制的默认值。赋值时执行闭包或调用函数,将返回值赋给该属性。
使用闭包初始化属性时,不能在闭包中访问其它的属性,即便属性有默认值也不允许。同时,不能使用隐式的self属性或调用其它的实例方法。
2.15 反初始化
反初始化函数使用关键字deint来标识,只适用于类类型,在实例释放前一步自动调用。
反初始化函数调用链与初始化函数调用链相反,先完成子类的资源释放再执行父类的反初始化函数。与OC类似。
2.16 自动引用计数(ARC)
引用计数只用于类类型的实例,值类型不是以引用的方式存储和传递的。
循环引用
使用weak引用或者无主引用来解除循环引用,弱引用可以为nil,所以必须是可选类型的,指向的实例释放后ARC自动将weak指针置空。无主(unowned)引用默认始终有值,因此必须是非可选型。如果无主引用指向的实例已释放,再次通过无主引用访问该实例即为野指针错误,需要手动置空。
weak指针适用情况:当两个对象AB之间均非一对一关系,即A对B或者B对A都是一个可选值,允许是nil
unowned指针适用情况:当对象A必须引用对象B,而A之于B是一个可选项,允许为空
unowned + 隐式展开可选项:对象AB属于一对一关系,且A对象是B的初始化参数之一,设置B为隐式展开的可选属性,此时B对象为赋值之前默认值为nil,不影响A的初始化操作,从而A初始化后可以作为参数完成B的初始化。
下面代码思路正确,但是代码执行出错,隐式展开可选项似乎没起作用,待进一步研究!
1 | //Country与City属于一对一关系,且City的初始化依赖于Country |
闭包产生的循环引用
类似于OC中Block产生的循环引用。
Swift中的约束:只要闭包内使用self的成员,就必须通过self指针调用,而不能直接调用,可提醒存在强引用的风险
通过定义占有列表解除强循环引用,占有列表有关键字weak或unowned和实例的引用组成,包含在一个中括号中,有逗号隔开。eg: [(weak|unowned), self]
占有列表放置在闭包参数和返回类型之前。如果没有指定参数或返回类型则放在关键字in前面。
当闭包和占有实例总是互相引用并总是同时销毁时,使用unowned定义占有列表。当占有引用有可能为nil时,使用weak。
2.17 自判断链接
Swift的自判断链和OC中的消息为空类似,但是Swift可用于任意类型(值类型和类类型),并且一定会有返回值,返回值一定是可选项类型。
可使用自判断链代替强制拆包
二者调用方式类似:自判断值后面使用?号,而强拆使用的是!号。当自判断值为空时,强拆会引发运行时错误,自判断链则会返回一个空值,表示自判断链调用失败。
自判断链适用于调用属性、实例方法和附属脚本(子脚本)中,三者的任意组合成自判断链。
2.18 类型转换
Swift中的is和as两个操作符分别用来做类型检测和类型转换。
假如向下转换可能出现失败的情况,使用as?返回一个可选值,转换失败返回nil。假如确定一定能转换成功则使用as,有点类似强制解包。
Swift为不确定类型提供了两种特殊类型别名:
- AnyObject可以代表任何class类型实例
- Any可以表示除了方法类型之外的任何类型
2.2 扩展
扩展是向一个已有的类、结构体或枚举类型添加新功能。与OC中的分类类似,但是Swift的扩展不需要知道名称,可以理解成OC中的匿名分类?。
Swift中的扩展可以:
- 添加计算型属性和计算静态属性
- 定义实例方法和类型方法
- 提供新的构造器
- 定义下标
- 定义和使用新的嵌套类型
- 使一个已有类似符合某个接口
Swift中扩展和OC中分类一样,扩展中的功能在该类型的所有实例都是可用的。
注意:扩展可以添加新的计算型属性,但是不能添加存储属性,也不可以向已有属性添加属性观测器
2.21 协议
类、结构体和枚举均可实现多个协议,中间用逗号分隔。
属性要求
协议能够要求遵循者包含一些特定名称和类型的实例属性或类属性,同时可以指定类型的读写权限,但是具体属性最终实现可以是存储型或者计算型属性。
方法要求
协议方法的声明和普通方法声明相似,但是不需要方法内容。
class中实现协议中的mutating方法时,不用写mutating关键字,因为类为引用类型,可直接修改类的属性;用结构体和枚举实现协议中的mutating方法时,则必须写mutating。
协议类型
协议可当做一种满足一定要求的类型对待,可用于声明满足协议的变量。比如委托模式,声明一个满足委托协议的代理变量,然后通过代理变量调用委托协议中的方法。
在扩展中添加协议成员
通过扩展为已存在的类型遵循协议是,该类型的所有实例也会随之添加协议中的方法。
当一个类型实现了某个协议中的所有要求却没有声明相应的协议时,可以通过扩展来补充协议声明。
集合中的协议类型
协议类型可以用来声明集合,表示集合中的元素均为协议类型。
协议的继承
协议能够继承一到多个其他协议。语法和类的继承相似。
协议合成
一个协议可又多个协议采用protocol<SomeProtocol, AnotherProtocol, ThirdProtocol>这样的格式进行组合。
协议合成不会生成一个新的协议类型,而是将多个协议合成为一个临时的协议,超出范围后立即失效。
检验协议的一致性
使用is检验协议的一致性,使用as将协议类型向下转换为其他的协议类型。
- is操作符用来检查实例是否遵循了某个协议
- as?返回一个可选值,当实例遵循协议时,返回该协议类型,否则返回为nil。
- as用以强制向下转换。
@objc
@objc表示协议的可选的,用可以用来表示暴露给Objective-C的代码。此外,@objc型协议只对类有效,因此只能在类中检查协议的一致性。
在@objc声明的协议中,可以使用@optional关键字作为前缀定义可选的属性或者方法。
调用可选方法是,在函数名和参数直接加上?来检查该方法是否被实现,当其不可访问时,?之后的语句不会执行,并且返回nil。
2.22 泛型
泛型是一种清晰和抽象的代码表达方式,可以根据自我需求定义、适用于任何类型的,灵活且可重用的函数和类型,从而避免了代码重复。
泛型是Swift强大特征之一,许多Swift标准库是通过泛型代码构建的。
关联类型
定义协议时,通过associatedtype声明一个关联类型,不用具体指定实际类型。直到实现协议时,才给出关联类型的实际类型。
通过extension为已知类型添加协议的兼容性
如果某个类型已经覆盖了某个协议所包含的要求,那么可以通过拓展将已知类型和协议建立关联。有点像JS中的鸭子类型。