1. Swift继承的基本概况:
1) 只有类能继承其它类型都不支持继承,包括基本类型、集合、结构体、枚举;
2) 和Java一样只支持单继承不支持多继承,Swift的多继承是由协议实现的(和Java的接口很像,可以通过遵守多个协议的方式来间接达到多继承的目的);
3) Swift没有试下封装性,并不支持C++和Java那样的private、public、protected等访问限定符,因此类的所有成员都可以继承(属性、方法、下标等都可以),但是构造器不能继承,因为继承就是通过调用父类构造器在子类中构造出父类部分实现的,因此构造器不能算作类的属性或者方法等范畴,应该说构造器就是一段逻辑脚本,该脚本规定子类部分和父类部分在内存中加载的步骤和方法;
4) 继承的写法:和C++一样,直接在子类名后面加": 父类名"即可,而Java是使用extends关键字来声明子类继承父类关系的
class Father {
var a: Int = 5
init(a: Int) {
self.a = a
}
}
class Son: Father { // 注意继承的写法
var b: Int = 7
init(a: Int, b: Int) { // 和Java一样使用super代表父类,这里利用super调用父类的构造器
self.b = b
super.init(a: a) // 构造出父类部分的内存从而从父类那里继承得到父类的东西
}
}
1) 还是那个原则,就是先构造器后直接初始化;
2) 由于继承的关系会使各个类之间形成继承链条,比如C继承B,B又继承A从而形成一条继承链,而在继承机制中构造器和直接初始化之间的顺序非常微妙,这涉及到Swift构造过程的一个安全检查机制;
3) Swift的其中一大安全检查机制——保证类中所有的内容都被初始化:这个机制其实之前讲过的,而在继承机制中该安全机制是被这样执行的:
i) 大致顺序是:分两遍,第一遍是从子类到父类,第二遍再从父类到子类;
ii) 第一遍的从子类到父类是指先调用子类构造器,其中子类构造器中调用了父类的构造器,而父类的构造器又调用了爷类的构造器,从而一层一层往上知道最顶层的构造器调用完;
iii) 到达顶层后,再原路返回执行第二遍检查,而这个第二遍检查的内容就是直接初始化语句了,一直从顶层一层一层往下检查到底层的子类,方式和之前讲过的一样,就是忽略已经被构造器初始化过的属性只对那些没有被构造器初始化过属性执行直接初始化语句:
class Grandfather {
var a: Int = 1
init(a: Int) {
self.a = a
}
}
class Father: Grandfather {
var b: Int = 2
init(a: Int, b: Int) {
self.b = b
super.init(a: a)
}
}
class Son: Father {
var c: Int = 3
init(a: Int, b: Int, c: Int) {
self.c = c
super.init(a: a, b: b)
}
}
如果Son类这样写,则会由于构造器中未初始化c同时直接初始化语句中也找不到初始化c的语句而发生编译报错,要求对c进行初始化:
class Son: Father { // Bad!
var c: Int /* = 3 */
init(a: Int, b: Int, c: Int) {
// self.c = c
super.init(a: a, b: b)
}
}
3. Swift要求并建议的构造器代理规则:
1) 再次重申指定构造器和便利构造器的区别:这个区别只在类中有,对于结构体没有这个概念,便利构造器就是用convenience修饰的构造器,除此之外的构造器都是指定构造器;
2) Swift要求并建议的代理规则:前面的继承代码中已经出现了向上代理的概念了,即使用super.init的方式调用父类的构造器;
规则总共有5条,对于横向代理(便利构造器)有2条,向上代理有3条:
i) 横向代理规则一:便利构造器只能调用同一类中的其它构造器(指定构造器和便利构造器都行),但是不能调用父类构造器(否则就变成纵向代理而不是横向代理了),如果这样搞会直接报错的!
class A {
var a: Int = 1
var b: Int = 2
init(a: Int, b: Int) {
self.a = a
self.b = b
}
convenience init(a: Int) { // 不仅可以调用指定构造器
self.init(a: a, b: 7)
}
convenience init() { // 也可以调用其它便利构造器
self.init(a: 9)
}
}
ii) 横向代理规则二:代理的构造器调用必须出现在第一句,并且仅有一句(之前强调过了)
iii) 向上代理规则一:子类的每个指定构造器必须调用直接父类的指定构造器(必须是父类的指定构造器不能是便利构造器,可能会觉得记忆起来麻烦但其实很简单,因为便利构造器是横向的,纵向的不能和横向的交叉,因此纵向代理只能调用父类的指定构造器而不能调用父类的便利构造器),否则会报错!
class A {
var a: Int = 1
var b: Int = 2
init(a: Int, b: Int) {
self.a = a
self.b = b
}
convenience init(a: Int) { // 不仅可以调用指定构造器
self.init(a: a, b: 7)
}
convenience init() { // 也可以调用其它便利构造器
self.init(a: 9)
}
}
class B: A {
var c: Int = 10
init() {
// super.init(a: 7) // Bad! 只能调用父类的指定构造器,编译错误!
super.init(a: 3, b: 3)
c = 11
}
init(c: Int) {
self.c = c // 注意!调用父类构造器的语句不一定非得放在首位置
super.init(a: 7, b: 20)
}
convenience init(m_c c: Int) { // 遍历构造器只能调用本层的其它构造器!
self.init(c: 33)
}
}
其中你可以发现,向上代理的时候,代理的构造器不必放在第一句,这和横向代理的便利构造器的语法不太一样!
iv) 向上代理规则二:Swift建议在向上代理的时候先初始化子类的属性再调用直接父类的构造器,即子类的指定构造器要以向上代理作为结束
这个规则不是强制的,因为上例中也看到了,那个对c的赋值既可以写在上上代理前面也可以写在后面,两者都没有问题,但是还记不记得前面讲过的安全机制中的两遍检查?如果子类属性的初始化写在向上代理的后面,则构造的时候会先一直通过构造器构造到顶层,然后在从顶层开始执行那些写在向上代理后面的构造器内的初始化语句一直到底层,完了之后再从顶层到底层检查一遍直接初始化语句,这回非常麻烦影响效率,因此Swift建议将子类属性的初始化写在向上代理之前,也就是说将向上代理作为子类指定构造器的最后一句!
在之前的版本中违反这个规则会直接报错,但现在更先进了,并不会做那次重复的向下执行写在向上代理之后的语句了,而是在编译的时候Swift内部重新组织构造器的代码,隐式地将向上代理语句放在最后!
v) 向上代理规则三:在向上代理之前不得访问父类中的属性,因为在Swift的继承就是通过向上代理实现的,在调用父类构造器之前父类的内容在内存中还不存在因此是无法访问父类中的属性的,不管使用self还是用super访问父类中的属性都会报错!
class A {
var a: Int = 9
init(a: Int) {
self.a = a
}
}
class B: A {
var b: Int = 10
init(b: Int) {
self.b = b
// self.a = 9 // Bad!
// super.a = 9 // Bad! 此时父类的内存还不存在!
super.init(a: 7)
}
}
4. 子类从父类那里自动获取构造器:
1) 如果子类不定义任何构造器则会从父类那里获取构造器(即子类的默认构造器和父类的构造器一样),但是构造器不能继承,因此只能说是获取;
2) 获取的规则:
i) 如果子类不写任何构造器则将获取父类的所有指定构造器;
ii) 如果子类写了哪怕一个构造器则子类不会获得父类的任何一个构造器;
iii) 如果获得了一个父类的指定构造器,则父类中所有调用它的便利构造器以及间接调用它的便利构造器都将被子类获得
!!简单地说就是如果子类写过构造器则父类构造器一个都没,如果什么都没写,则父类所有的构造器都将获得,但是上述的过程也是Swift编译器的分析过程,因此在这里我觉得有必要声明一下!
class A {
var a: Int = 1
var b: Int = 2
init(a: Int, b: Int) {
self.a = 3
self.b = 3
}
convenience init() {
self.init(a: 10, b: 20)
}
init(a: Int, b: Int, c: Int) {
self.a = a
self.b = b
}
convenience init(s: String) {
self.init()
}
}
class B: A {
var c = 0
}
var b1 = B()
var b2 = B(s: "haha")
var b3 = B(a: 32, b: 77)
var b4 = B(a: 1, b: 2, c: 3)
3) 这种获取的关系可以一直继承下去(如果后面在定义一个class C继承B也是OK的):
class C: B {
var d = 0
}
var c1 = C()
var c2 = C(s: "haha")
var c3 = C(a: 32, b: 77)
var c4 = C(a: 1, b: 2, c: 3)
5. 重写属性:
1) Swift的继承可以在子类中覆盖父类的一些特性,和C++不同的是Swift不仅可以覆盖方法而且可以覆盖属性和下标,必须使用关键字override声明重写的内容,同时重写对象和被重写对象之间的名字、类型必须完全一致;
2) 属性重写的规则(一下的父类是指祖先,包括直接父类以及父类的父类等等):
* 重写存储属性:
i) 可以将父类的存储属性覆盖成计算属性或属性观察器;
ii) 只要是父类的存储属性被重写,则该存储属性会被继承下来,只不过外界无法访问该存储属性,只能在子类内部通过super关键字访问!
iii) 不能将父类的计算属性或属性观察器覆盖成存储属性;
iv) 不能将存储属性重写成存储属性;
** 重写计算属性:
i) 可以将可读可写计算属性重写为可读可写计算属性;
ii) 不能将可读可写计算属性重写为只读计算属性;
iii) 但只读计算属性可以覆盖为可读可写计算属性;
iv) 可以将可读可写计算属性重写成属性观察者;
v) 但是不能将只读计算属性覆盖成属性观察者;
!!如果覆盖成计算属性则必须同时覆盖getter和setter,不能只覆盖其中的一个,否则会报错!
!!如果覆盖成属性观察器,则willSet和didSet两个访问器可以只重写一个,但是不推荐这样做!
*** 重写属性观察器:
i) 可以将属性观察器重写为属性观察器;
ii) 可以将属性观察器重写为可读可写计算属性;
iii) 但是不可以将属性观察器重写为只读计算属性;
!!不能为常量存储属性覆盖计算属性和属性观察器,因为常量本身就不能定义成计算属性和属性观察器的!
class A {
var a = 2
var b: Int {
return 7
}
var t: Int = 30
var x: Int {
get {
return 76
}
set {
a = 770
}
}
var y: Int = 22 {
willSet {
println(newValue)
}
didSet {
println(oldValue)
}
}
var z: Int = 22 {
willSet {
println(newValue)
}
didSet {
println(oldValue)
}
}
}
// 只要是父类的存储属性被重写,则该存储属性会被继承下来
// 只不过外界无法访问该存储属性,只能在子类内部通过super关键字访问!
class B: A {
var c = 10
override var a: Int { // 如果覆盖成计算属性则必须同时覆盖getter和setter,不能只覆盖其中的一个,否则会报错!
get {
return 33
}
set {
c = 70
super.a = 33 // 此时父类被覆盖的存储属性会被保存下来,访问必须通过super关键字
}
}
override var b: Int { // 只读计算属性可以覆盖为可读可写
get {
return 99
}
set {
c = 23
}
}
override var t: Int { // 重写属性观察者
willSet { // 属性观察器可以只重写一个
println(newValue)
}
didSet {
println(oldValue)
}
}
override var x: Int { // 可以将父类的可读可写计算属性覆盖为属性观察器
// 但不能将只读计算属性重写成属性观察器,否则会报错!
willSet { // 同样属性观察器可以只覆盖一个
println(newValue)
}
didSet {
println(oldValue)
}
// return 99 // 不可以将可读可写计算属性重写为只读计算属性
}
override var y: Int { // 属性观察器可以重写成属性观察器
willSet {
println(10)
}
didSet {
println(20)
}
}
override var z: Int { // 属性观察器可以重写成可读可写计算属性
get {
return 77
}
set {
c = 44
}
// return 99 // 不能就爱你过属性观察器重写成只读属性!
}
}
var obj_b = B()
println(obj_b.a) // 33
// 虽然覆盖父类存储属性为计算属性后父类存储属性会被保留,但是无法访问!
// 只能访问子类覆盖过的计算属性
obj_b.b = 90
obj_b.t = 77 // 77 30,可见父类的t作为存储属性保存下来了
obj_b.x = 88 // 88 76,可见将父类计算属性的getter的返回值76作为原值来处理了
6. 重写方法:
Swift允许重写对象方法和静态方法,要求重写和被重写的方法的函数签名要完全一致(包括外部变量名),否则就是重载了:
class A {
func show(str s: String) {
println(s)
}
class func present(s: String) {
println(s)
}
}
class B: A {
override func show(str s: String) { // 函数签名必须完全一致,否则就会报错
super.show(str: "haha") // 有时需要利用父类的被重写的方法进行辅助
println("hihi")
}
override class func present(s: String) {
super.present("haha")
println("hihi")
}
func test() {
super.show(str: "x") // 同样可以在其它方法中调用父类的方法
}
}
var a = A()
var b = B()
a.show(str: "A") // A
b.show(str: "B") // haha\nhihi
A.present("A") // A
B.present("B") // haha\nhihi
!!注意!父类的方法并没有被删除,其实还是被继承下来了,只不过被覆盖了以后无法通过外界调用,而只能在子类的内部使用super关键字调用来完成一定的辅助功能;
7. 重写下标:
和重写方法的规则一模一样:
class MyArray {
let size: Int
var buf: [Int]
init(size: Int) {
self.size = size
buf = Array(count: size, repeatedValue: 0)
}
subscript(i: Int) -> Int {
get {
return buf[i]
}
set {
buf[i] = newValue
}
}
}
class ChildMyArrary: MyArray {
override subscript(i: Int) -> Int { // 同样函数签名必须相同,包括外部变量名
get { // 两个访问器必须都重写,不能只重写一个
return buf[i] // 因此即使其中一个代码和父类相同也必须重新写一遍
}
set {
buf[i] = newValue * newValue
}
}
}
var arr = ChildMyArrary(size: 15)
arr[10] = 9
println(arr[10]) // 81
8. 继承限制:
1) 和Java一样使用关键字final来限制继承;
2) 如果用final修饰类成员(静态/非静态)则该成员不可被重写;
3) 如果用final修饰整个类,则该类不可被继承;
4) 不能同时用final修饰类和成员,因为两者逻辑上是冲突的,因为不能继承的类何以重写它的成员!
5) 在开发商业软件的时候非常有必要使用final进行限制;