自动引用计数(ARC)
Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存。
通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存。
ARC 功能
- 当每次使用 init() 方法创建一个类的新的实例的时候,ARC 会分配一大块内存用来储存实例的信息。
- 内存中会包含实例的类型信息,以及这个实例所有相关属性的值。
- 当实例不再被使用时,ARC 释放实例所占用的内存,并让释放的内存能挪作他用。
- 为了确保使用中的实例不会被销毁,ARC 会跟踪和计算每一个实例正在被多少属性,常量和变量所引用。
- 实例赋值给属性、常量或变量,它们都会创建此实例的强引用,只要强引用还在,实例是不允许被销毁的。
类实例之间的循环强引用
两个类实例互相保持对方的强引用,并让对方不被销毁。这就是所谓的循环强引用。
解决实例之间的循环强引用:
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:
- 弱引用 weak:弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
- 无主引用 unowned:对于生命周期中会变为nil的实例使用弱引用。相反的,对于初始化赋值后再也不会被赋值为nil的实例,使用无主引用。
闭包引起的循环强引用
循环强引用还会发生在当你将一个闭包赋值给类实例的某个属性,并且这个闭包体中又使用了实例。这个闭包体中可能访问了实例的某个属性,例如self.someProperty,或者闭包中调用了实例的某个方法,例如self.someMethod。这两种情况都导致了闭包 “捕获” self,从而产生了循环强引用。
弱引用和无主引用
- 当闭包和捕获的实例总是互相引用时并且总是同时销毁时,将闭包内的捕获定义为无主引用。
- 相反的,当捕获引用有时可能会是nil时,将闭包内的捕获定义为弱引用。
- 如果捕获的引用绝对不会置为nil,应该用无主引用,而不是弱引用。
类型转换
- Swift 语言类型转换可以判断实例的类型。也可以用于检测实例类型是否属于其父类或者子类的实例。
- Swift 中类型转换使用 is 和 as 操作符实现,is 用于检测值的类型,as 用于转换类型。
- 类型转换也可以用来检查一个类是否实现了某个协议。
检查类型
- 类型转换用于检测实例类型是否属于特定的实例类型。
- 可以将它用在类和子类的层次结构上,检查特定类实例的类型并且转换这个类实例的类型成为这个层次结构中的其他类型。
- 类型检查使用 is 关键字。
- 操作符 is 来检查一个实例是否属于特定子类型。若实例属于那个子类型,类型检查操作符返回 true,否则返回 false。
向下转型
- 向下转型,用类型转换操作符(as? 或 as!)
- 当你不确定向下转型可以成功时,用类型转换的条件形式(as?)。条件形式的类型转换总是返回一个可选值(optional value),并且若下转是不可能的,可选值将是 nil。
- 只有你可以确定向下转型一定会成功时,才使用强制形式(as!)。当你试图向下转型为一个不正确的类型时,强制形式的类型转换会触发一个运行时错误。
Any和AnyObject的类型转换
Swift为不确定类型提供了两种特殊类型别名:
- AnyObject可以代表任何class类型的实例。
- Any可以表示任何类型,包括方法类型(function types)。
注意:只有当你明确的需要它的行为和功能时才使用Any和AnyObject。在你的代码里使用你期望的明确的类型总是更好的。
扩展
扩展就是向一个已有的类、结构体或枚举类型添加新功能。
扩展可以对一个类型添加新的功能,但是不能重写已有的功能。
Swift 中的扩展可以:
- 添加计算型属性和计算型静态属性
- 定义实例方法和类型方法
- 提供新的构造器
- 定义下标
- 定义和使用新的嵌套类型
- 使一个已有类型符合某个协议
语法
扩展声明使用关键字 extension:
extension SomeType {
// 加到SomeType的新功能写到这里
}
一个扩展可以扩展一个已有类型,使其能够适配一个或多个协议,语法格式如下:
extension SomeType: SomeProtocol, AnotherProctocol {
// 协议实现写到这里
}
计算型属性
扩展可以向已有类型添加计算型实例属性和计算型类型属性。
构造器
- 扩展可以向已有类型添加新的构造器。
- 这可以让你扩展其它类型,将你自己的定制类型作为构造器参数,或者提供该类型的原始实现中没有包含的额外初始化选项。
- 扩展可以向类中添加新的便利构造器 init(),但是它们不能向类中添加新的指定构造器或析构函数 deinit() 。
方法
扩展可以向已有类型添加新的实例方法和类型方法。
可变实例方法
- 通过扩展添加的实例方法也可以修改该实例本身。
- 结构体和枚举类型中修改self或其属性的方法必须将该实例方法标注为mutating,正如来自原始实现的修改方法一样。
下标
扩展可以向一个已有类型添加新下标。
嵌套类型
扩展可以向已有的类、结构体和枚举添加新的嵌套类型
协议
- 规定了用来实现某一特定功能所必需的方法和属性。
- 任意能够满足协议要求的类型被称为遵循(conform)这个协议。
- 类,结构体或枚举类型都可以遵循协议,并提供具体实现来完成协议定义的方法和功能。
语法
协议的语法格式如下:
protocol SomeProtocol {
// 协议内容
}
要使类遵循某个协议,需要在类型名称后加上协议名称,中间以冒号:分隔,作为类型定义的一部分。遵循多个协议时,各协议之间用逗号,分隔。
struct SomeStructure: FirstProtocol, AnotherProtocol {
// 结构体内容
}
如果类在遵循协议的同时拥有父类,应该将父类名放在协议名之前,以逗号分隔。
class SomeClass: SomeSuperClass, FirstProtocol, AnotherProtocol {
// 类的内容
}
对属性的规定
- 协议用于指定特定的实例属性或类属性,而不用指定是存储型属性或计算型属性。此外还必须指明是只读的还是可读可写的。
- 协议中的通常用var来声明变量属性,在类型声明后加上{ set get }来表示属性是可读可写的,只读属性则用{ get }来表示。
对 Mutating 方法的规定
- 有时需要在方法中改变它的实例。
- 例如,值类型(结构体,枚举)的实例方法中,将mutating关键字作为函数的前缀,写在func之前,表示可以在该方法中修改它所属的实例及其实例属性的值。
对构造器的规定
协议可以要求它的遵循者实现指定的构造器。
你可以像书写普通的构造器那样,在协议的定义里写下构造器的声明,但不需要写花括号和构造器的实体,语法如下:
protocol SomeProtocol {
init(someParameter: Int)
}
协议构造器规定在类中的实现
-
你可以在遵循该协议的类中实现构造器,并指定其为类的指定构造器或者便利构造器。在这两种情况下,都必须给构造器实现标上"required"修饰符。
-
使用required修饰符可以保证:所有的遵循该协议的子类,同样能为构造器规定提供一个显式的实现或继承实现。
协议类型
尽管协议本身并不实现任何功能,但是协议可以被当做类型来使用。
协议可以像其他普通类型一样使用,使用场景:
- 作为函数、方法或构造器中的参数类型或返回值类型
- 作为常量、变量或属性的类型
- 作为数组、字典或其他容器中的元素类型
在扩展中添加协议成员
- 我们可以可以通过扩展来扩充已存在类型( 类,结构体,枚举等)。
- 扩展可以为已存在的类型添加属性,方法,下标脚本,协议等成员。
协议的继承
协议能够继承一个或多个其他协议,可以在继承的协议基础上增加新的内容要求。
协议的继承语法与类的继承相似,多个被继承的协议间用逗号分隔:
protocol InheritingProtocol: SomeProtocol, AnotherProtocol {
// 协议定义
}
类专属协议
你可以在协议的继承列表中,通过添加class关键字,限制协议只能适配到类(class)类型。
该class关键字必须是第一个出现在协议的继承列表中,其后,才是其他继承协议。格式如下:
protocol SomeClassOnlyProtocol: class, SomeInheritedProtocol {
// 协议定义
}
协议合成
Swift 支持合成多个协议,这在我们需要同时遵循多个协议时非常有用。
检验协议的一致性
可以使用is和as操作符来检查是否遵循某一协议或强制转化为某一类型。
- is操作符用来检查实例是否遵循了某个协议。
- as?返回一个可选值,当实例遵循协议时,返回该协议类型;否则返回nil。
- as用以强制向下转型,如果强转失败,会引起运行时错误。
泛型
- Swift 提供了泛型让你写出灵活且可重用的函数和类型。
- Swift 标准库是通过泛型代码构建出来的。
- Swift 的数组和字典类型都是泛型集。
泛型类型
- Swift 允许你定义你自己的泛型类型。
- 自定义类、结构体和枚举作用于任何类型,如同 Array 和 Dictionary 的用法。
扩展泛型类型
当你扩展一个泛型类型的时候(使用 extension 关键字),你并不需要在扩展的定义中提供类型参数列表。更加方便的是,原始类型定义中声明的类型参数列表在扩展里是可以使用的,并且这些来自原始类型中的参数名称会被用作原始定义中类型参数的引用。
类型约束
类型约束指定了一个必须继承自指定类的类型参数,或者遵循一个特定的协议或协议构成。
类型约束语法
可以写一个在一个类型参数名后面的类型约束,通过冒号分割,来作为类型参数链的一部分。这种作用于泛型函数的类型约束的基础语法如下所示(和泛型类型的语法相同):
func someFunction<T: SomeClass, U: SomeProtocol>(someT: T, someU: U) {
// 这里是泛型函数的函数体部分
}
关联类
- Swift 中使用 associatedtype 关键字来设置关联类型实例。
Where 语句
- 类型约束能够确保类型符合泛型函数或类的定义约束。
- 可以在参数列表中通过where语句定义参数的约束。
- 可以写一个where语句,紧跟在在类型参数列表后面,where语句后跟一个或者多个针对关联类型的约束,以及(或)一个或多个类型和关联类型间的等价(equality)关系。
访问控制
- 访问控制可以限定其他源文件或模块中代码对你代码的访问级别。
- 可以明确地给单个类型(类、结构体、枚举)设置访问级别,也可以给这些类型的属性、函数、初始化方法、基本类型、下标索引等设置访问级别。
- 协议也可以被限定在一定的范围内使用,包括协议里的全局常量、变量和函数。
- 访问控制基于模块与源文件。
- 模块指的是以独立单元构建和发布的 Framework 或 Application。在 Swift 中的一个模块可以使用 import 关键字引入另外一个模块。
- 源文件是单个源码文件,它通常属于一个模块, 源文件可以包含多个类和函数 的定义。
- Swift 为代码中的实体提供了四种不同的访问级别:public、internal、fileprivate、private。
访问级别 | 定义 |
---|---|
public | 可以访问自己模块中源文件里的任何实体,别人也可以通过引入该模块来访问源文件里的所有实体。 |
internal | 可以访问自己模块中源文件里的任何实体,但是别人不能访问该模块中源文件里的实体。 |
fileprivate | 文件内私有,只能在当前源文件中使用。 |
private | 只能在类中访问,离开了这个类或者结构体的作用域外面就无法访问。 |
函数类型访问权限
函数的访问级别需要根据该函数的参数类型和返回类型的访问级别得出。
枚举类型访问权限
枚举中成员的访问级别继承自该枚举,你不能为枚举中的成员单独申明不同的访问级别。
子类访问权限
子类的访问级别不得高于父类的访问级别。比如说,父类的访问级别是internal,子类的访问级别就不能申明为public。
常量、变量、属性、下标访问权限
- 常量、变量、属性不能拥有比它们的类型更高的访问级别。
- 下标也不能拥有比索引类型或返回类型更高的访问级别。
Getter 和 Setter访问权限
- 常量、变量、属性、下标索引的Getters和Setters的访问级别继承自它们所属成员的访问级别。
- Setter的访问级别可以低于对应的Getter的访问级别,这样就可以控制变量、属性或下标索引的读写权限。
构造器和默认构造器访问权限
- 初始化
我们可以给自定义的初始化方法申明访问级别,但是要不高于它所属类的访问级别。但必要构造器例外,它的访问级别必须和所属类的访问级别相同。
如同函数或方法参数,初始化方法参数的访问级别也不能低于初始化方法的访问级别。 - 默认初始化方法
Swift为结构体、类都提供了一个默认的无参初始化方法,用于给它们的所有属性提供赋值操作,但不会给出具体值。
默认初始化方法的访问级别与所属类型的访问级别相同。
协议访问权限
- 如果想为一个协议明确的申明访问级别,那么需要注意一点,就是要确保该协议在你申明的访问级别作用域中使用。
- 如果定义了一个public访问级别的协议,那么实现该协议提供的必要函数也会是public的访问级别。这一点不同于其他类型,比如,public访问级别的其他类型,他们成员的访问级别为internal。
扩展访问权限
- 你可以在条件允许的情况下对类、结构体、枚举进行扩展。扩展成员应该具有和原始类成员一致的访问级别。比如扩展了一个公共类型,那么新加的成员应该具有和原始成员一样的默认的internal访问级别。
- 或者,可以明确申明扩展的访问级别(比如使用private extension)给该扩展内所有成员申明一个新的默认访问级别。这个新的默认访问级别仍然可以被单独成员所申明的访问级别所覆盖。
泛型访问权限
泛型类型或泛型函数的访问级别取泛型类型、函数本身、泛型类型参数三者中的最低访问级别。
类型别名
- 任何你定义的类型别名都会被当作不同的类型,以便于进行访问控制。一个类型别名的访问级别不可高于原类型的访问级别。
- 比如说,一个private级别的类型别名可以设定给一个public、internal、private的类型,但是一个public级别的类型别名只能设定给一个public级别的类型,不能设定给internal或private 级别的类型。
注意:这条规则也适用于为满足协议一致性而给相关类型命名别名的情况。