从零学习Swift 08: 继承体系

2020-05-03  本文已影响0人  小心韩国人
总结
一: 方法

方法的定义:


func 方法名(参数列表) -> 返回值类型{
    方法体
}

同 OC 一样, Swift 也有实例方法和对象方法:


class Person{
    //实例方法
    func run(){
        
    }
    
    //类型方法
    static func eat(){
        
    }
}

var person = Person()
//通过实例调用
person.run()

//通过类调用
Person.eat()

在 Swift 中,类,结构体,枚举都可以定义方法.需要注意的是:swift 语法规定:结构体,枚举的实例方法默认不能修改自身的属性,如果想要修改自身属性,需要在方法前加上mutating`关键字:

值类型实例方法内部默认不能修改属性

如果要在值类型的实例方法内部修改值类型的属性,需要在实例方法前加上mutating关键字:

mutating

如果调用一个方法,它的返回值没有被使用,编译器会报出警告:


返回值未被使用警告

可以使用@discardableResult关键字消除警告:

discardableResult 关键字

另外Swift 语法规定:类型方法中不能访问实例属性:

类型方法中不能访问实例属性
二: 下标

如果我们想像访问数组一样array[0],通过下标访问自己写的类的属性.可以使用subscript关键字给任意类型增加下标功能.


//下标

class Person{
    var age = 18
    var height = 175
    subscript(index: Int) -> Int{
        get{
            if index == 0{
                return age
            }else if index == 1{
                return height
            }
            return 0
        }
        
        set{
            if index == 0{
                age = newValue
            }else if index == 1{
                height = newValue
            }
        }
        
    }
}

var p1 = Person()
print(p1.age, p1.height)

p1[0] = 22
p1[1] = 180
print(p1.age, p1.height)

//打印结果
18 175
22 180

可以看到下标和计算属性很像,都有get , set方法.其实下标的本质就是方法,只不过下标不是用func定义,而是使用subscript定义.

同计算属性一样,下标可以没有set方法,只有get方法.如果只有get方法,get可以省略.

省略 get

使用下标时需要注意一个细节,只有当下标的返回值是class类型时,外部才可以修改,如图:

如果下标的返回值是struct类型,默认外部是不可以修改此返回值的,如图:

如果要想修改,必须加上set方法,如图:

set方法中的newValuePoint类型,为什么我们我们赋值的实收11 , 22可以直接赋值成功呢?其实p[0].x = 11 , p[0].y = 22的本质是这样的:


// p[0].x = 11
Point(x: 11, y: p[0].y)

// p[0].y = 22
Point(x: p[0].x, y: 22)

总结:
下标返回的对象外面是不是可以直接修改?

  1. class类型可以直接修改
  2. struct类型必须加上set方法才可以修改

其实通过下标访问属性的本质就是调用get,set方法,可以通过汇编证实这一点:

get set
三: 继承

继承是面向对象语言的三大特性之一.在 Swift 语言中,只有类支持继承,结构体和枚举不支持继承.

  1. 继承的内存结构

//继承的内存结构
class Person{
    var age = 10
}

class Father: Person {
    var height = 180
}

class Son: Father{
    var firstName = "王"
}

var person = Person()

var laowang = Father()

var xiaoWang = Son()

如上所示的代码,person , laowang , xiaoWang堆空间的内存布局如下:

内存布局图

从上图可以看出来,子类会用继承父类的属性,并且会有额外的空间存储从父类继承过来的属性.

  1. 重写 override

子类可以重写父类的下标 , 方法 , 属性,必须加上override关键字.

2.1 重新写实例方法 , 下标


//重写实例方法下标

class Father {
    var height = 180
    
    func eat(){
        print("Father eat")
    }
    
    subscript(index: Int) -> Int{
        print("Father index")
        return height
    }
}

class Son: Father{
    var firstName = "王"
    
    //重写父类实例方法
    override func eat() {
        print("Son eat")
    }
    //重写父类下标
    override subscript(index: Int) -> Int {
        print("Son index")
        return super.height
    }
}

2.2 重写类型方法 , 下标


//重写类型方法下标

class Father {
    var height = 180
    
    class func eat(){
        print("Father eat")
    }
    
    class subscript(index: Int) -> Int{
        print("Father index")
        return index
    }
}

class Son: Father{
    var firstName = "王"
    
    //重写父类类型方法

    override class func eat() {
        print("Son eat")
    }
    //重写父类类型下标
    override class subscript(index: Int) -> Int {
        print("Son index")
        return super[index]
    }
}

之前我们讲过,类型方法可以通过class,static定义.需要注意的是,通过class定义的方法,下标允许被子类重写;通过static定义的方法 , 下标不允许被子类重写.

如图:

static 定义的方法下标不允许被重写

2.3 重写实例属性
子类可以将父类的存储属性,计算属性重写为计算属性.注意:只能重写为计算属性.


//重写实例属性
class Father{
    var age = 30
    var money: Int{
        get{
            print("Father getMoney")
            return age * 10
        }
        set{
            print("Father setMoney")
            age = newValue / 10
        }
    }
}

var person : Father


person = Father()


class Son: Father {
    override var age: Int{
        set{
            print("Son setAge")
            super.age = newValue > 0 ? newValue : 0
        }

        get{
            print("Son getAge")
            return super.age
        }
    }

    override var money: Int{
        set{
            print("Son setMoney")
            super.money = newValue > 0 ? newValue : 0
        }

        get{
            print("Son getMone")
            return super.money
        }
    }
}


print("---------------------------------------------------------")



person = Son()

//Son setAge
person.age = 10
print("---------------------")


//Son getMone
//Father getMone
//Son getAge
//100
print(person.money)
print("---------------------")


//Son setMoney
//Father setMoney
//Son setAge
person.money = 20
print("---------------------")


//Son getAge
//2
print(person.age)


以上代码就是子类把父类的存储属性和计算属性都重写成了计算属性.并且把每条打印语句的执行步骤都注释出来.

需要单独解释一下print(person.money)这条语句的执行步骤:

首先person.money此时personSon对象的实例,执行步骤如下:
1: 所以先执行Son.moneyget方法,打印Son getMone;
2: 然后调用super.money执行父类的getMoney方法打印Father getMoney;
3: 在Father getMoney中又访问了age (return age * 10),而子类有把age属性重写成了计算属性(set , get),所以会执行子类的Son getAge
4: 最后输出 100

2.4 重写类型属性

通过static修饰的属性不可以被子类重写,通过class修饰的属性才可以被子类重写.

static 修饰的属性不能被子类重写

子类重写父类类型属性,及调用步骤:


//重写类型属性
class Father{
    static var age = 30
    class var money: Int{
        get{
            print("Father getMoney")
            return age * 10
        }
        set{
            print("Father setMoney")
            age = newValue / 10
        }
    }
}
var person : Father

person = Father()

class Son: Father {

    override static var money: Int{
        set{
            print("Son setMoney")
            super.money = newValue > 0 ? newValue : 0
        }

        get{
            print("Son getMone")
            return super.money
        }
    }
}

Father.age = 10

//Father getMoney
//100
print(Father.money)
print("---------------------")


//Father setMoney
Father.money = 20
print("---------------------")

//2
print(Father.age)
print("---------------------")

Son.age = 10

//Son getMone
//Father getMoney
//100
print(Son.money)
print("---------------------")


//Son setMoney
//Father setMoney
Son.money = 20
print("---------------------")


2.5 通过重写为父类属性添加属性观察器

我们还可以通过override为父类属性添加属性观察器.但是不能为let属性 , 或者只读的计算属性添加.因为let属性和只读计算属性都不允许更改值,所以根本没必要添加属性观察器.

下面代码就实现了为父类的存储属性 , 计算属性设置属性观察器:


//子类给父类添加属性观察器
class Father{
    //存储属性
    var age: Int = 20
    //计算属性
    var money: Int{
        get{
            print("Father getMoney")
            return age * 10
        }
        set{
            print("Father setMoney")
            age = newValue / 10
        }
    }
}

class Son: Father {

    //重写父类的实例存储属性,并设置属性观察器
    override var age: Int{
        willSet{
            print("Son willSetAge :",newValue)
        }

        didSet{
            print("Son didSetAge : ",age)
        }
    }
    //重写父类的计算属性,并设置属性观察器
    override var money: Int{
        willSet{
            print("Son willSetMoney : ",newValue)
        }
        didSet{
            print("Son didSetMoney : ",money)
        }
    }
}

var son = Son()
son.money = 16


以上代码的打印顺序是这样的:

打印顺序

我们对上面的打印顺序做一下解释:

  1. 首先第一行打印的Father getMoney,我们在为有属性观察器的的属性赋值的时候,会首先调用他的get方法,拿到原来的值.

  2. 第二行Son willSetMoney.这个不用解释,都明白.

  3. 第三行Father setMoney.在给子类Son.mone赋值的时候会先给它的父类Father.money赋值,所以打印这一句.

  4. 第四行Son willSetAge.在执行第三行的Father setMoney中有一句age = newValue / 10.给age赋值,而子类为父类添加了属性观察器,所以会走子类的Son willSetAge

  5. 第五行Son didSetAge.第四行执行完,不解释

  6. 第六行Father getMoney.这一行是因为在didSetMoney中访问了money,如图:

money
  1. 第七行Son didSetMoney.设置完毕,不解释.
final

如果我们不想让我们的类被外界继承;或者不想让我们写的方法,属性,下标被重写,可以加上final关键字.


结尾在补充一下static , class两个关键字的区别,虽然这两个关键字都是用来修饰类型级别的方法或者属性的,但是还是些区别:

static , class 区别
上一篇下一篇

猜你喜欢

热点阅读