1.下标
下标 (subscripts)可以定义在类(class)、结构体(structure)和枚举(enumeration)中,是访问集合(collection),列表(list)或序列(sequence)中元素的快捷方式。
可以使用下标的索引,设置和获取值,而不需要再调用对应的存取方法。
举例来说,用下标访问一个Array实例中的元素可以写作someArray[index],访问Dictionary实例中的元素可以写作someDictionary[key]。
一个类型可以定义多个下标,通过不同索引类型进行重载。下标不限于一维,你可以定义具有多个入参的下标满足自定义类型的需求。
下标语法
struct TimesTable {
let multiplier: Int
subscript(index: Int) -> Int {
return multiplier * index
}
}
let threeTimesTable = TimesTable(multiplier: 3)
print("six times three is \(threeTimesTable[6])")
// 输出 "six times three is 18"
TimesTable
例子基于一个固定的数学公式,对threeTimesTable[someIndex]
进行赋值操作并不合适,因此下标定义为只读的。
2.继承
一个类可以继承(inherit)另一个类的方法(methods),属性(properties)和其它特性。
在Swift
中,类可以调用和访问超类的方法,属性和下标(subscripts),并且可以重写(override)这些方法,属性和下标来优化或修改它们的行为。Swift
会检查你的重写定义在超类中是否有匹配的定义,以此确保你的重写行为是正确的。
子类生成(Subclassing)
class SomeClass: SomeSuperclass {
// 这里是子类的定义
}
重写(Overriding)
子类可以为继承来的实例方法(instance method),类方法(class method),实例属性(instance property),或下标(subscript)提供自己定制的实现(implementation)。我们把这种行为叫重写(overriding)。
如果要重写某个特性,你需要在重写定义的前面加上override关键字。这么做,你就表明了你是想提供一个重写版本,而非错误地提供了一个相同的定义。意外的重写行为可能会导致不可预知的错误,任何缺少override关键字的重写都会在编译时被诊断为错误。
override关键字会提醒 Swift 编译器去检查该类的超类(或其中一个父类)是否有匹配重写版本的声明。这个检查可以确保你的重写定义是正确的。
访问超类的方法,属性及下标
重写属性
你可以重写继承来的实例属性或类型属性,提供自己定制的 getter
和 setter
,或添加属性观察器使重写的属性可以观察属性值什么时候发生改变。
重写属性的 Getters 和 Setters
可以提供定制的 getter
(或 setter
)来重写任意继承来的属性,无论继承来的属性是存储型的还是计算型的属性。
重写属性观察器(Property Observer)
你可以通过重写属性为一个继承来的属性添加属性观察器
。这样一来,当继承来的属性值发生改变时,你就会被通知到,无论那个属性原本是如何实现的。
防止重写
可以通过把方法,属性或下标标记为final
来防止它们被重写,只需要在声明关键字前加上final
修饰符即可(例如:final var
,final func
,final class func
,以及final subscript
)。
3.构造过程(Initialization)
构造过程是使用类、结构体或枚举类型的实例之前的准备过程。在新实例可用前必须执行这个过程,具体操作包括设置实例中每个存储型属性的初始值和执行其他必须的设置或初始化工作。
通过定义构造器(Initializers
)来实现构造过程,这些构造器可以看做是用来创建特定类型新实例的特殊方法。与 Objective-C
中的构造器不同,Swift
的构造器无需返回值,它们的主要任务是保证新实例在第一次使用前完成正确的初始化。
类的实例也可以通过定义析构器(deinitializer
)在实例释放之前执行特定的清除工作。想了解更多关于析构器的内容,请参考析构过程。
存储属性的初始赋值
类和结构体在创建实例时,必须为所有存储型属性设置合适的初始值。存储型属性的值不能处于一个未知的状态。
你可以在构造器中为存储型属性赋初值,也可以在定义属性时为其设置默认值。
当你为存储型属性设置默认值或者在构造器中为其赋值时,它们的值是被直接设置的,不会触发任何属性观察者(property observers
)。
构造器
构造器在创建某个特定类型的新实例时被调用。它的最简形式类似于一个不带任何参数的实例方法,以关键字init
命名:
struct Fahrenheit {
var temperature: Double
init() {
temperature = 32.0
}
}
var f = Fahrenheit()
print("The default temperature is \(f.temperature)° Fahrenheit")
// 输出 "The default temperature is 32.0° Fahrenheit”
默认属性值
你可以在构造器中为存储型属性设置初始值。同样,你也可以在属性声明时为其设置默认值。
struct Fahrenheit {
var temperature = 32.0
}
参数的内部名称和外部名称
跟函数和方法参数相同,构造参数也拥有一个在构造器内部使用的参数名字和一个在调用构造器时使用的外部参数名字。
默认构造器
var item = ShoppingListItem()
结构体的逐一成员构造器
struct Size {
var width = 0.0, height = 0.0
}
let twoByTwo = Size(width: 2.0, height: 2.0)
值类型的构造器代理
类的继承和构造过程
Swift
为类类型提供了两种构造器来确保实例中所有存储型属性都能获得初始值,它们分别是指定构造器
和便利构造器
。
指定构造器和便利构造器
指定构造器(designated initializers)是类中最主要的构造器。一个指定构造器将初始化类中提供的所有属性,并根据父类链往上调用父类的构造器来实现父类的初始化。
便利构造器(convenience initializers)是类中比较次要的、辅助型的构造器。你可以定义便利构造器来调用同一个类中的指定构造器,并为其参数提供默认值。你也可以定义便利构造器来创建一个特殊用途或特定输入值的实例。
析构过程
析构器只适用于类类型,当一个类的实例被释放之前,析构器会被立即调用。析构器用关键字deinit
来标示,类似于构造器要用init来标示。
析构过程原理
Swift
会自动释放不再需要的实例以释放资源。如自动引用计数章节中所讲述,Swift
通过自动引用计数(ARC
)处理实例的内存管理。
deinit {
// 执行析构过程
}
自动引用计数(Automatic Reference Counting)
Swift
使用自动引用计数(ARC
)机制来跟踪和管理你的应用程序的内存。通常情况下,Swift
内存管理机制会一直起作用,你无须自己来考虑内存的管理。ARC
会在类的实例不再被使用时,自动释放其占用的内存。
引用计数仅仅应用于类的实例。结构体和枚举类型是值类型,不是引用类型,也不是通过引用的方式存储和传递。
当你每次创建一个类的新的实例的时候,ARC
会分配一块内存来储存该实例信息。内存中会包含实例的类型信息,以及这个实例所有相关的存储型属性的值。
自动引用计数实践
var reference1: Person? = Person(name: "SUPER")
var reference2: Person?
var reference3: Person?
reference2 = reference1
reference3 = reference1
// 现在这一个Person实例已经有三个强引用了。
reference1 = nil
reference2 = nil
// 如果你通过给其中两个变量赋值nil的方式断开两个强引用(包括最先的那个强引用),只留下一个强引用,Person实例不会被销毁
// 在你清楚地表明不再使用这个Person实例时,即第三个也就是最后一个强引用被断开时,ARC 会销毁它:
reference3 = nil
// 打印 “John Appleseed is being deinitialized”
类实例之间的循环强引用
我们可能会写出一个类实例的强引用数永远不能变成0的代码。如果两个类实例互相持有对方的强引用,因而每个实例都让对方一直存在,就是这种情况。这就是所谓的循环强引用。
解决实例之间的循环强引用
Swift 提供了两种办法用来解决你在使用类的属性时所遇到的循环强引用问题:弱引用(weak reference)
和无主引用(unowned reference)
。
弱引用和无主引用允许循环引用中的一个实例引用另外一个实例而不保持强引用。这样实例能够互相引用而不产生循环强引用。
对于生命周期中会变为nil
的实例使用弱引用。相反地,对于初始化赋值后再也不会被赋值为nil
的实例,使用无主引用。
解决闭包引起的循环强引用
在定义闭包时同时定义捕获列表作为闭包的一部分,通过这种方式可以解决闭包和类实例之间的循环强引用。
捕获列表定义了闭包体内捕获一个或者多个引用类型的规则。跟解决两个类实例间的循环强引用一样,声明每个捕获的引用为弱引用或无主引用,而不是强引用。应当根据代码关系来决定使用弱引用还是无主引用。
定义捕获列表
捕获列表中的每一项都由一对元素组成,一个元素是weak
或unowned
关键字,另一个元素是类实例的引用(例如self
)或初始化过的变量(如delegate = self.delegate!
)。这些项在方括号中用逗号分开。
lazy var someClosure: (Int, String) -> String = {
[unowned self, weak delegate = self.delegate!] (index: Int, stringToProcess: String) -> String in
// 这里是闭包的函数体
}
如果闭包没有指明参数列表或者返回类型,即它们会通过上下文推断,那么可以把捕获列表和关键字in放在闭包最开始的地方:
lazy var someClosure: Void -> String = {
[unowned self, weak delegate = self.delegate!] in
// 这里是闭包的函数体
}
弱引用和无主引用
在闭包和捕获的实例总是互相引用并且总是同时销毁时,将闭包内的捕获定义为无主引用。
相反的,在被捕获的引用可能会变为nil
时,将闭包内的捕获定义为弱引用。弱引用总是可选类型,并且当引用的实例被销毁后,弱引用的值会自动置为nil
。这使我们可以在闭包体内检查它们是否存在。