Cowboy Tech

Swift中的继承和多态

继承

继承的特点

  1. 单继承,每个子类只有一个直接父类,可以有多个间接父类. 与OC不同,他不继承NSObject。如果不显示父类,就说明没有父类。
  2. 子类继承父类,可以获得父类的属性,方法,下标,也可以进行重写
  3. swift的类并不是从一个通用的基类继承而来的
  4. override修饰被重写的部分
  5. final修饰的类不能被继承,属性,方法和下标不能被重写

父类的继承

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Fruit {  
var weight = 0.0
func info(){
print("我是一个水果,重\(weight)g!")
}
}
class Apple:Fruit {
var name:String!
func taste(){
print("\(name)吃起来很好吃")
}
}
var a = Apple()
//Apple本身没有weight属性
//因为Apple的父类有weight属性,也可以访问Apple实例的weight属性
a.weight = 56
a.name = "红富士"
//调用Apple实例的info方法
a.info()
a.taste()

重写父类的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Bird{
//Bird类的fly()方法
func fly(){
print("我在天空里自由自在的飞翔")
}
}
class Ostrich:Bird {
//重写Bird类的fly()方法
override func fly() {
print("我只能在地上奔跑...")
}
}
//创建Ostrich实例
var os = Ostrich()
//执行Ostrich实例的fly()方法,将输出....
os.fly()

重写父类的属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Bird {
var speed: Double = 0
}
class Ostrich: Bird {
//重写Bird类的speed属性
override var speed:Double{
get{
print("正在访问被重写的属性")
return super.speed
}
set{
super.speed = newValue * newValue
}
}
}
//创建Ostrich实例
var os = Ostrich()
//对重写后的属性赋值
os.speed = 20.0
// 访问被重写后的属性
print("os的速度为:\(os.speed)")

重写属性观察者

  1. 不要同时添加set和didSet,因为在didSet里就可以改变属性的值
  2. 不要给常量存储属性和只读计算属性添加属性观察者,因为值是不会变的,观察者又有什么用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Bird {
var speed: Double = 0
}
class Ostrich: Bird {
//重写Bird类的speed属性,添加属性观察者
override var speed:Double{
didSet{
//属性值被改变完成后,将会自动执行该方法
print("改变之前的speed为:\(oldValue)")
super.speed *= speed
}
}
}
//创建Ostrich实例
var os = Ostrich()
//对重写后的属性赋值
os.speed = 20.0
// 访问被重写后的属性
print("os的速度为:\(os.speed)")

重写父类的下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Base {
subscript(idx:Int) ->Int{
get{
print("父类的下标的get方法")
return idx + 10
}
}
}
class Sub:Base {
//重写Base类的下标
override subscript(idx:Int) ->Int{
get{
print("重写后的下标的get方法")
print("通过super访问被重写之前的下标:\(super[idx])")
return idx * idx
}
set{
print("重写后的下标的setter方法,传入的参数值为:\(newValue)")
}
}
}
var base = Base()
print(base[7]) //输出17
//创建Sub实例
var sub = Sub()
print(sub[7]) //输出49
sub[7] = 90

多态

  1. Swift引用变量有两个类型:编译时类型,运行时类型,编译器只认编译时类型
  2. 编译时类型由声明该变量时使用类型决定,编译器只认变量的编译类型
  3. 运行时类型由实际赋予该变量的实例决定
  4. 多态:相同类型的变量,调用同一个方法时呈现出多种不同的行为特征。也就是编译时类型和运行时类型不一样
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class BaseClass {
func base(){
print("父类的普通方法")
}
func test(){
print("父类的被覆盖的方法")
}
}
class SubClass:BaseClass {
override func test() {
print("子类的覆盖父类的方法")
}
func sub(){
print("子类的普通方法")
}
}
let bc:BaseClass = BaseClass()
//下面两次调用将执行BaseClass的方法
bc.base()
bc.test()
let sc:SubClass = SubClass()
//下面调用将执行从父类继承到的base()方法
sc.base()
//下面调用将执行从当前类的test()方法
sc.test()
sc.sub()
//下面编译时类型和运行时类型不一样,多态发生
//下面将子类实例赋值给父类变量,称为向上转型,由系统自动完成
//编译类型是BaseClass,运行时类型是SubClass
let ploymophicBc: BaseClass = SubClass()
//下面调用将执行父类继承到的base()方法
ploymophicBc.base()
//下面调用将执行从当前类的test()方法
ploymophicBc.test()
//因为ploymophicBc的编译类型是BaseClass
//BaseClass类没有提供sub方法,所以下面代码编译时会出现错误
//ploymophicBc.sub()

运算符检查类型 (变量 is 类)

  1. 使用is运算符检查类型,返回值为布尔类型,判断前面的引用变量是否引用后面的类或者子类的实例。是则true
  2. 前一个操作数是一个引用类型变量,后一个操作数是一个类
  3. 运算符前面操作数的编译类型要么与后面的类相同,要么具有继承关系,否则编译报错。
1
2
3
4
5
6
7
8
9
//定义一个可被objective-C类使用的协议
@objc protocol TestProtocol{}
let hello:NSObject = "Hello"
print("字符串是否是NSString类的实例:\(hello is NSString)")
print("字符串是否是NSDate类的实例:\(hello is NSDate)")
print("字符串是否是TestProtocol协议的实例:\(hello is TestProtocol)")
let a: NSString = "Hello"
//下面这行代码会报错
//print("字符串是否是Math类的实例:\(a is NSDate)")

转型

向上转型

将子类实例赋值给父类变量,称为向上转型,由系统自动完成

1
2
//编译类型是BaseClass,运行时类型是SubClass
let ploymophicBc: BaseClass = SubClass()

向下转型 (父类的对象 as! 子类)

  1. 使用as运算符向下转型.
  2. 向上转型:把一个子类实例直接赋给一个父类引用变量,不用任何类型转换
  3. 引用变量只能调用其编译时类型的方法,强制转换为其实际类型,可以调用运行时类型的方法,这种强制转换为向下转型
  4. 向下转型运算符: 1. as!强制将运算符前面的引用变量转换为后面的类型。 2.(a..as?…b)!可选形式的向下转换
  5. 向下转换只能在具有继承关系的两个类型之间进行

例子1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//编译类型为NSObject的obj向下转换,程序可以运行
let obj:NSObject = "Hello"
let objStr:NSString = obj as! NSString
print(objStr)
//
//编译类型为NSObject的obj向下转换为NSString,但实际上它运行时的类型为NSNumber。编译没问题,但运行相冲突
let objPri:NSObject = 5
let str:NSString = objPri as! NSString
//
//针对上述代码有以下两个解决方案
//1.------- 先进行判断,再转换
if objPri is NSString {
let str: NSString = objPri as! NSString
}
//
//2.------- 使用可选类型转换符as?
let str2:NSString = (objPri as? NSString)!

例子2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
class Fruit {
var name:String
var weight:Double
init(name:String, weight:Double){
self.name = name
self.weight = weight
}
}
//
class Apple:Fruit {
var color:String
init(name: String, weight: Double,color:String) {
self.color = color
super.init(name: name, weight: weight)
}
}
//
class Grape: Fruit {
var sugarRate:Double
init(name: String, weight: Double,sugarRate:Double) {
self.sugarRate = sugarRate
super.init(name: name, weight: weight)
}
}
//
//定义元素为any的数组,该数组的元素几乎可以放置任何数据
var anyArray:[Any] = [
"swift",
29,
["iOS":89,"android":92],
Fruit(name: "Cherry", weight: 2.3),
Apple(name: "Pink lady", weight: 2.4, color: "Pink")
]
//
//遍历元素为Any的数组
for element in anyArray{
//尝试将数组元素强转为Fruit,然后执行可选绑定
if let f = element as? Fruit {
print("\(f.name)水果重\(f.weight)")
}
}
//
//定义元素为AnyObject的数组,该数组的元素只能是对象
var anyObjectArray:[AnyObject] = [
Fruit(name: "Cherry", weight: 2.3),
Apple(name: "Pink lady", weight: 2.4, color: "Pink")
]

Any和AnyObject

  1. AnyObject: 可代表任何类的实例
  2. Any: 可代表任何类型,包括Int,Double等值类型,AnyObject修饰的实例

嵌套类型

  1. 在一个类型的内部定义另一个类型
  2. Swift的枚举、类,结构体都可以定义嵌套类型
  3. Swift的嵌套类型支持多级嵌套
  4. 嵌套类型不允许使用static或者class修饰
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class User {
var holidays:[Weekday]
var location:Point
init(holidays:[Weekday],location:Point){
self.holidays = holidays
self.location = location
}
//定义一个嵌套类型:结构体Point
struct Point {
var latitude:Double
var longitude:Double
var position:Orientation
//嵌套枚举
enum Orientation{
case Up,Left,Bottom,Right
}
}
//定义一个嵌套类型:枚举Weekday
enum Weekday{
case Monday, Tuesday,Wednesday,Thursday,Friday,Saturday,Sunday
struct Duration {
var num:Double
var unit:String
}
}cup
}
//使用嵌套类型时,需要以被嵌套类型的名字为前缀
var user = User(holidays:[],location:User.Point(
latitude:-23.33,
longitude:122.11,
position:User.Point.Orientation.Left))
//引用User.Weekday嵌套枚举
user.holidays.append(User.Weekday.Saturday)
user.holidays.append(User.Weekday.Sunday)

总结

  1. 继承的特点,一个直接,多个间接,不写则无,不继承NSObject
  2. 因为继承,所以可以重写属性,在重写属性中添加观察者,重写方法,下标,使用override,如果要不被重写,使用final
  3. 多态,就是编译类型和运行类型不同,例如左侧变量属性是父类,右侧赋值是子类对象。
  4. 向上转型:父类变量赋予子类的值,执行子类的方法,前提是该父类的方法被override
  5. 向下转型:(父类的对象 as! 子类),只能在具有继承关系的两个类型之间进行,转换前最好先进行is判断