重寫
override
-
重寫類型方法、下標(biāo)
- 被
class
修飾的類型方法堵腹、下標(biāo)炸站,允許被子類重寫 - 被
static
修飾的類型方法、下標(biāo)疚顷,不允許被子類重寫
注意:如果此時(shí)繼承父類重寫父類方法之后旱易,如果重寫的方法不想繼續(xù)被子類去重寫禁偎,那么可以此時(shí)在重寫的方法前面添加static關(guān)鍵字修飾來(lái)確保子類不能夠重寫當(dāng)前的方法
- 被
class AnimalSubscript {
func speak() {
print("animal speak")
}
subscript(index: Int) -> Int {index}
class func eat() {
print("animal eat")
}
static func drink() {
print("animal drink")
}
}
class Cat: AnimalSubscript {
override func speak() {
print("cat speak")
}
override subscript(index: Int) -> Int {index + 20}
override class func eat() {
print("cat eat")
}
//此時(shí)重寫的方法將不能被子類重寫修改
// override static func eat(){
// print("終結(jié)當(dāng)前的方法")
// }
}
- 重寫屬性
- 子類可以將父類的
計(jì)算屬性、存儲(chǔ)屬性
重寫為計(jì)算屬性
- 子類不可以將父類重寫為
存儲(chǔ)屬性
- 只能重寫
var
變量屬性阀坏。不能重寫let
屬性 - 重寫時(shí)如暖,屬性名、類型要一致
- 子類重寫后的
屬性權(quán)限
不能小于父類屬性的權(quán)限
- 如果父類屬性是只讀的忌堂,那么子類重寫后的屬性可以是
只讀
的盒至,也可以是可讀寫
的 - 如果父類屬性是
可讀可寫
的,那么子類重寫后的屬性也必須是可讀寫的
- 子類可以將父類的
class OverrideCircle {
//懶加載
lazy var lazyVar: Int = {
return 1
}()
private(set) var onlyRead: Int = 0
var radius: Int = 0
var diameter: Int{
get {
print("get diameter")
return (radius * 2)
}
set {
radius = newValue
print("Circle set diameter")
}
}
}
class OverrideCircleSon: OverrideCircle {
var newRadius = 0
override var diameter: Int {
get {
print("overrideCircleSon get value")
return radius * 2
}
set {
print("overrideCircleSon set value")
radius = newValue
}
}
override var radius: Int{
get {
print("reWrite stored perproty of radius")
return newRadius
}
set {
print("reGet sotre perproty of radius")
newRadius = newValue
}
}
}
- 屬性觀察器
- 可以在子類為父類屬性(除了只讀計(jì)算屬性士修、
let屬性
)增加屬性觀察器 -
set 與willset
枷遂、get 與 willget
均不能共存的 - 不管父類是實(shí)例屬性,類型屬性或者存儲(chǔ)屬性棋嘲,計(jì)算屬性酒唉,子類都可以為父類添加觀察屬性
- 可以在子類為父類屬性(除了只讀計(jì)算屬性士修、
class perprotyObserverClass: OverrideCircle{
override var radius: Int{
willSet {
print("will set value", newValue)
}
didSet {
//此時(shí)的radius不會(huì)引發(fā)死循環(huán),因?yàn)榇藭r(shí)并沒有重寫計(jì)算屬性沸移,此時(shí)訪問的仍舊是radius本身痪伦,
//屬性監(jiān)聽器的實(shí)現(xiàn)是通過(guò)中間臨時(shí)變量來(lái)實(shí)現(xiàn)的,所以不存在死循環(huán)
print("set value finish", oldValue, radius)
}
}
//不能夠正常添加觀察期監(jiān)聽
// override var onlyRead: Int{
// willSet {
//
// }
// }
override var lazyVar: Int{
willSet {
print("lazy load will set")
}
}
}
- final 使用
- 被
final
修飾的方法雹锣、下標(biāo)网沾、屬性
,禁止被重寫 - 被
final
修飾的類笆制,禁止被繼承
- 被
final class finalClass {
final var radius: Int {
set {
print("radius write")
}
get {
print("circle get radius")
return 20
}
}
}
多態(tài)
- 多態(tài)是指:父類指針指向子類對(duì)象的行為現(xiàn)象
- 在OC中多態(tài)是依靠runtime來(lái)實(shí)現(xiàn)的绅这,而Swift中的實(shí)現(xiàn)方式類似于C++中的虛函數(shù)表來(lái)實(shí)現(xiàn)的
幾個(gè)問題:
- 父類是如何判斷對(duì)應(yīng)的子類對(duì)象的類型的(多態(tài))
- 如果將
class
類型換成struct
類型,那么在內(nèi)存中調(diào)用的時(shí)候有什么區(qū)別在辆?
因?yàn)閷?duì)于結(jié)構(gòu)體來(lái)說(shuō),存儲(chǔ)在椂忍Γ空間
匆篓,所以存儲(chǔ)的地址在編譯期間
就已經(jīng)確定。而對(duì)象的實(shí)例寇窑,需要堆空間去動(dòng)態(tài)分配內(nèi)存
鸦概,此時(shí)的堆空間的地址是動(dòng)態(tài)分配
的,由此調(diào)用對(duì)應(yīng)的方法由于對(duì)象實(shí)例分配動(dòng)態(tài)的原因甩骏,并不能直接在編譯期間
直接找到其內(nèi)存地址窗市,所以需要在運(yùn)行的時(shí)候去找到動(dòng)態(tài)分配的地址空間
,進(jìn)而去通過(guò)該地址間接找到要調(diào)用的方法地址饮笛,因此對(duì)象的方法的調(diào)用地址
是變化的咨察。所以相比于類調(diào)用方法,結(jié)構(gòu)體調(diào)用的方法執(zhí)行效率要比放在類中執(zhí)行的效率高很多
初始化器
-
特點(diǎn)
-
類福青、結(jié)構(gòu)體摄狱、枚舉
都可以定義初始化器 - 類有兩種初始化器:指定初始化器(designed initializer)脓诡、便捷初始化器(convenience initializer)
- 每個(gè)類至少有一個(gè)
初始化器
,指定初始化器是類的主要初始化器 - 默認(rèn)初始化器總是類的指定初始化器
- 類偏向于少量的指定初始化器媒役,一個(gè)類通常只有一個(gè)指定初始化器
- 初始化過(guò)程(為了保證初始化安全祝谚,設(shè)定了
兩段式初始化
、安全檢查
)
-
-
初始化器的相互調(diào)用規(guī)則
- 指定初始化器必須從它的直系父類調(diào)用指定初始化器
- 便攜初始化器必須從相同的類里調(diào)用另一個(gè)初始化器
- 便攜初始化器最終必須調(diào)用一個(gè)指定初始化器
class Person {
var name: String
init(name: String) {
self.name = name
}
}
class Student: Person {
var score: Int
var height: Double
//指定初始化器
init(score: Int, height: Double) {
self.score = score
self.height = height
super.init(name: "student")
}
//便攜初始化器
convenience init(score: Int) {
self.init(score:99, height: 20.0)
self.score = score
}
//重寫父類的指定初始化器為便捷初始化器
convenience override init(name: String) {
self.init(score: 98, height: 177)
}
}
-
兩段式初始化過(guò)程
初始化所有的存儲(chǔ)屬性
1. 外層調(diào)用指定/便攜初始化器
2. 分配內(nèi)存給實(shí)例酣衷,但未初始化
3. 指定初始化器來(lái)確保當(dāng)前類定義的存儲(chǔ)屬性都初始化
4. 指定初始化器調(diào)用父類的初始化器交惯,不斷向上調(diào)用,形成初始化器鏈設(shè)置新的存儲(chǔ)屬性值
1. 從頂部初始化器往下穿仪,鏈中的每一個(gè)指定初始化器都有機(jī)會(huì)進(jìn)一步定制實(shí)例
2. 初始化器現(xiàn)在能夠使用self(訪問商玫、修改它的屬性,調(diào)用它的實(shí)例方法等等)
3. 最終牡借,鏈中的任何便捷初始化器都有機(jī)會(huì)定制實(shí)例以及使用self-
如果存在繼承關(guān)系拳昌,初始化順序如下:
- 先初始化指定初始化器完成子類的初始化
- 依次調(diào)用父類的指定初始化器完成初始化操作
安全檢查
1. 指定初始化器必須保證在調(diào)用父類初始化之前,其所在類定義的所有存儲(chǔ)屬性都要初始化完成
2. 指定初始化器必須先調(diào)用父類初始化器钠龙,然后才能為繼承的屬性設(shè)置新值
3. 便捷初始化器必須先調(diào)用同類中的其他初始化器炬藤,然后再為任意屬性設(shè)置新值
4. 初始化器在第一階段初始化完成之前,不能調(diào)用任何實(shí)例方法碴里,不能讀取任何實(shí)例屬性的值沈矿,也不能引用self
5. 直到第一階段完成,實(shí)例才能算完全合法重寫父類的指定初始化器
1. 必須加上override
(即使子類實(shí)現(xiàn)的是便捷初始化器)
2. 因?yàn)楦割惖谋憬莩跏蓟饔肋h(yuǎn)不會(huì)通過(guò)子類直接調(diào)用咬腋,所以羹膳,子類無(wú)法重寫父類的便捷初始化器
-
自動(dòng)繼承
- 特點(diǎn)
- 如果子類沒有定義任何
初始化器
,那么它將自動(dòng)繼承父類所有的指定初始化器
- 如果子類提供了父類所有指定初始化器的實(shí)現(xiàn)(一種是
全部重寫
根竿、另一種是通過(guò)步驟1
來(lái)實(shí)現(xiàn))陵像,此時(shí)子類將會(huì)自動(dòng)繼承
所有的父類的便捷初始化器
- 就算子類添加很多
便捷初始化器
,以上特點(diǎn)依舊適用 - 子類用
便捷初始化器
的形式重寫父類的指定初始化器
寇壳,此時(shí)步驟2
也適用于這種情況
-
required
使用- 特點(diǎn)
- 用
required
修飾指定初始化器醒颖,表明其所有子類都必須實(shí)現(xiàn)該初始化器(通過(guò)繼承或者重寫來(lái)實(shí)現(xiàn)) - 如果子類重寫了required初始化器,也必須加上required壳炎,不用加override
初始化引發(fā)的屬性觀察器的變化
特點(diǎn):
1. 父類的屬性在它自己的初始化器中賦值不會(huì)觸發(fā)屬性觀察器
2. 在子類的初始化器中賦值會(huì)觸發(fā)屬性觀察器
class Animal {
var type: String {
didSet {
print("oldValue:\(oldValue), new value:\(type)")
}
willSet {
print("newValue:\(newValue)")
}
}
required init(type: String) {
self.type = type
}
}
class Dog: Animal {
var age: Int
init(age: Int) {
self.age = age
super.init(type: "dog")
}
required init(type: String) {
self.age = 0
super.init(type: "dog")
self.type = "pig"
}
}
-
反初始化器
deinit
- 類似于
C++
的析構(gòu)函數(shù)泞歉、OC中的dealloc
方法 - 當(dāng)類的實(shí)例對(duì)象被釋放內(nèi)存的時(shí)候,會(huì)調(diào)用實(shí)例對(duì)象的
deinit
方法 -
deinit
不能接受任何參數(shù)匿辩、不能寫小括號(hào)腰耙、不能自行調(diào)用 - 父類的
deinit
能被子類繼承 - 子類的
deinit
實(shí)現(xiàn)執(zhí)行完畢后,會(huì)調(diào)用父類的deinit
- 類似于
-
可失敗初始化器
特點(diǎn):-
類铲球、結(jié)構(gòu)體挺庞、枚舉
都可以使用init?
定義可失敗初始化器
- 不允許同時(shí)定義參數(shù)標(biāo)簽、參數(shù)個(gè)數(shù)睬辐、參數(shù)類型相同的
可失敗初始化器
與非可失敗初始化器
-
可失敗初始化器
可以調(diào)用非可失敗初始化器
挠阁,非可失敗初始化器
調(diào)用可失敗初始化器
需要進(jìn)行解包操作
- 如果初始化器調(diào)用一個(gè)可失敗初始化器導(dǎo)致
初始化失敗
宾肺,那么整個(gè)初始化過(guò)程都會(huì)失敗
,并且之后的代碼都會(huì)停止
- 可以用一個(gè)
非可失敗初始化器
重寫一個(gè)可失敗初始化器
侵俗,但反過(guò)來(lái)是不行的 - 可以通過(guò)可選鏈來(lái)間接的進(jìn)行判空容錯(cuò)(如果為nil锨用,不執(zhí)行后序語(yǔ)句內(nèi)容)
-
class PersonCanFailed {
var name: String
var age: Int?
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
//定義一個(gè)便捷初始化器
convenience init?() {
self.init(name: "leo") //如果初始化失敗,后序代碼將不會(huì)再執(zhí)行
self.age = 10
}
//已經(jīng)接觸到的可失敗初始化器
func demo() {
//可能初始化失敗
let _ = Int("12345555")
enum Answer: Int{
case wrong, right
}
//可能初始化失敗
let _ = Answer(rawValue: 1)
}
}
func testOptinolChainUse() {
var scores = ["Jack": [89, 99, 67],
"Rose": [89, 99, 67]]
scores["Jack"]?[0] = 100
scores["Rose"]?[2] = 87
//判斷下面num1與num2 內(nèi)容是什么類型
var num1: Int? = 5
num1? = 10 //Optinoal(10)
//num2? 等同于判定num2是否為空隘谣,如果為空后序賦值操作將取消增拥,如果不為空則正常操作
var num2: Int? = nil
num2? = 10 //nil
num2 = 19 //19
}
func testOptionalChainDictUse() {
var dict:[String: (Int, Int) -> Int] = ["sum":(+), "difference":(-)]
var result = dict["sum"]?(10, 20) //Optional(30), Int?
}
可選鏈
?
-
可選鏈調(diào)用特點(diǎn)
-
?
的作用是判斷調(diào)用者是否有值,有的話會(huì)繼續(xù)調(diào)用后序方法寻歧,沒有的話掌栅,直接返回,返回的內(nèi)容是可選類型
(這個(gè)行為改變了原有的返回?cái)?shù)據(jù)類型
码泛,將之前返回的內(nèi)容包裝成可選類型
了) - 方法沒有返回值卻能夠使用
var
進(jìn)行接收猾封,原因是方法的調(diào)用默認(rèn)返回值是Void
,可以認(rèn)定是一個(gè)空元組類型
- 可以通過(guò)
可選鏈
接收返回值的方法來(lái)判定方法是否調(diào)用成功
-
-
其他需要注意的點(diǎn)
- 如果可選為
nil
噪珊,調(diào)用方法晌缘、下標(biāo)、屬性
失效痢站,結(jié)果為nil
- 如果可選項(xiàng)不為
nil
磷箕,調(diào)用方法、下標(biāo)阵难、屬性成功
岳枷,結(jié)果會(huì)被包裝成可選類型
- 如果結(jié)果本來(lái)就是
可選項(xiàng)
,不會(huì)進(jìn)行再包裝 - 多個(gè)
?
可選可以鏈接在一起 - 如果可選鏈中任何一個(gè)節(jié)點(diǎn)是
nil
呜叫,那么整個(gè)可選鏈
就會(huì)調(diào)用失敗
- 如果可選為
func testOptionlUse() {
var person: CarPerson? = CarPerson()
var age = person?.age() //可選鏈調(diào)用
var age1 = person!.age() //強(qiáng)制解包
var name = person?.name //可選鏈調(diào)用
var result = person?.eat() //沒有返回值仍能夠通過(guò)變量去接受返回值
if let _ = person?.eat() {
print("調(diào)用eat成功")
}else{
print("調(diào)用eat失敗")
}
}