重寫
override
-
重寫類型方法、下標
- 被
class
修飾的類型方法、下標,允許被子類重寫 - 被
static
修飾的類型方法、下標,不允許被子類重寫
注意:如果此時繼承父類重寫父類方法之後,如果重寫的方法不想繼續被子類去重寫,那麼可以此時在重寫的方法前面添加static關鍵字修飾來確保子類不能夠重寫當前的方法
- 被
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")
}
//此時重寫的方法將不能被子類重寫修改
// override static func eat(){
// print("終結當前的方法")
// }
}
- 重寫屬性
- 子類可以將父類的
計算屬性、存儲屬性
重寫爲計算屬性
- 子類不可以將父類重寫爲
存儲屬性
- 只能重寫
var
變量屬性。不能重寫let
屬性 - 重寫時,屬性名、類型要一致
- 子類重寫後的
屬性權限
不能小於父類屬性的權限
- 如果父類屬性是隻讀的,那麼子類重寫後的屬性可以是
只讀
的,也可以是可讀寫
的 - 如果父類屬性是
可讀可寫
的,那麼子類重寫後的屬性也必須是可讀寫的
- 子類可以將父類的
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
}
}
}
- 屬性觀察器
- 可以在子類爲父類屬性(除了只讀計算屬性、
let屬性
)增加屬性觀察器 -
set 與willset
、get 與 willget
均不能共存的 - 不管父類是實例屬性,類型屬性或者存儲屬性,計算屬性,子類都可以爲父類添加觀察屬性
- 可以在子類爲父類屬性(除了只讀計算屬性、
class perprotyObserverClass: OverrideCircle{
override var radius: Int{
willSet {
print("will set value", newValue)
}
didSet {
//此時的radius不會引發死循環,因爲此時並沒有重寫計算屬性,此時訪問的仍舊是radius本身,
//屬性監聽器的實現是通過中間臨時變量來實現的,所以不存在死循環
print("set value finish", oldValue, radius)
}
}
//不能夠正常添加觀察期監聽
// override var onlyRead: Int{
// willSet {
//
// }
// }
override var lazyVar: Int{
willSet {
print("lazy load will set")
}
}
}
- final 使用
- 被
final
修飾的方法、下標、屬性
,禁止被重寫 - 被
final
修飾的類,禁止被繼承
- 被
final class finalClass {
final var radius: Int {
set {
print("radius write")
}
get {
print("circle get radius")
return 20
}
}
}
多態
- 多態是指:父類指針指向子類對象的行爲現象
- 在OC中多態是依靠runtime來實現的,而Swift中的實現方式類似於C++中的虛函數表來實現的
幾個問題:
- 父類是如何判斷對應的子類對象的類型的(多態)
- 如果將
class
類型換成struct
類型,那麼在內存中調用的時候有什麼區別?
因爲對於結構體來說,存儲在棧空間
,所以存儲的地址在編譯期間
就已經確定。而對象的實例,需要堆空間去動態分配內存
,此時的堆空間的地址是動態分配
的,由此調用對應的方法由於對象實例分配動態的原因,並不能直接在編譯期間
直接找到其內存地址,所以需要在運行的時候去找到動態分配的地址空間
,進而去通過該地址間接找到要調用的方法地址,因此對象的方法的調用地址
是變化的。所以相比於類調用方法,結構體調用的方法執行效率要比放在類中執行的效率高很多
初始化器
-
特點
-
類、結構體、枚舉
都可以定義初始化器 - 類有兩種初始化器:指定初始化器(designed initializer)、便捷初始化器(convenience initializer)
- 每個類至少有一個
初始化器
,指定初始化器是類的主要初始化器 - 默認初始化器總是類的指定初始化器
- 類偏向於少量的指定初始化器,一個類通常只有一個指定初始化器
- 初始化過程(爲了保證初始化安全,設定了
兩段式初始化
、安全檢查
)
-
-
初始化器的相互調用規則
- 指定初始化器必須從它的直系父類調用指定初始化器
- 便攜初始化器必須從相同的類裏調用另一個初始化器
- 便攜初始化器最終必須調用一個指定初始化器
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)
}
}
-
兩段式初始化過程
初始化所有的存儲屬性
1. 外層調用指定/便攜初始化器
2. 分配內存給實例,但未初始化
3. 指定初始化器來確保當前類定義的存儲屬性都初始化
4. 指定初始化器調用父類的初始化器,不斷向上調用,形成初始化器鏈設置新的存儲屬性值
1. 從頂部初始化器往下,鏈中的每一個指定初始化器都有機會進一步定製實例
2. 初始化器現在能夠使用self(訪問、修改它的屬性,調用它的實例方法等等)
3. 最終,鏈中的任何便捷初始化器都有機會定製實例以及使用self-
如果存在繼承關係,初始化順序如下:
- 先初始化指定初始化器完成子類的初始化
- 依次調用父類的指定初始化器完成初始化操作
安全檢查
1. 指定初始化器必須保證在調用父類初始化之前,其所在類定義的所有存儲屬性都要初始化完成
2. 指定初始化器必須先調用父類初始化器,然後才能爲繼承的屬性設置新值
3. 便捷初始化器必須先調用同類中的其他初始化器,然後再爲任意屬性設置新值
4. 初始化器在第一階段初始化完成之前,不能調用任何實例方法,不能讀取任何實例屬性的值,也不能引用self
5. 直到第一階段完成,實例才能算完全合法重寫父類的指定初始化器
1. 必須加上override
(即使子類實現的是便捷初始化器)
2. 因爲父類的便捷初始化器永遠不會通過子類直接調用,所以,子類無法重寫父類的便捷初始化器
-
自動繼承
- 特點
- 如果子類沒有定義任何
初始化器
,那麼它將自動繼承父類所有的指定初始化器
- 如果子類提供了父類所有指定初始化器的實現(一種是
全部重寫
、另一種是通過步驟1
來實現),此時子類將會自動繼承
所有的父類的便捷初始化器
- 就算子類添加很多
便捷初始化器
,以上特點依舊適用 - 子類用
便捷初始化器
的形式重寫父類的指定初始化器
,此時步驟2
也適用於這種情況
-
required
使用- 特點
- 用
required
修飾指定初始化器,表明其所有子類都必須實現該初始化器(通過繼承或者重寫來實現) - 如果子類重寫了required初始化器,也必須加上required,不用加override
初始化引發的屬性觀察器的變化
特點:
1. 父類的屬性在它自己的初始化器中賦值不會觸發屬性觀察器
2. 在子類的初始化器中賦值會觸發屬性觀察器
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++
的析構函數、OC中的dealloc
方法 - 當類的實例對象被釋放內存的時候,會調用實例對象的
deinit
方法 -
deinit
不能接受任何參數、不能寫小括號、不能自行調用 - 父類的
deinit
能被子類繼承 - 子類的
deinit
實現執行完畢後,會調用父類的deinit
- 類似於
-
可失敗初始化器
特點:-
類、結構體、枚舉
都可以使用init?
定義可失敗初始化器
- 不允許同時定義參數標籤、參數個數、參數類型相同的
可失敗初始化器
與非可失敗初始化器
-
可失敗初始化器
可以調用非可失敗初始化器
,非可失敗初始化器
調用可失敗初始化器
需要進行解包操作
- 如果初始化器調用一個可失敗初始化器導致
初始化失敗
,那麼整個初始化過程都會失敗
,並且之後的代碼都會停止
- 可以用一個
非可失敗初始化器
重寫一個可失敗初始化器
,但反過來是不行的 - 可以通過可選鏈來間接的進行判空容錯(如果爲nil,不執行後序語句內容)
-
class PersonCanFailed {
var name: String
var age: Int?
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
//定義一個便捷初始化器
convenience init?() {
self.init(name: "leo") //如果初始化失敗,後序代碼將不會再執行
self.age = 10
}
//已經接觸到的可失敗初始化器
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 內容是什麼類型
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?
}
可選鏈
?
-
可選鏈調用特點
-
?
的作用是判斷調用者是否有值,有的話會繼續調用後序方法,沒有的話,直接返回,返回的內容是可選類型
(這個行爲改變了原有的返回數據類型
,將之前返回的內容包裝成可選類型
了) - 方法沒有返回值卻能夠使用
var
進行接收,原因是方法的調用默認返回值是Void
,可以認定是一個空元組類型
- 可以通過
可選鏈
接收返回值的方法來判定方法是否調用成功
-
-
其他需要注意的點
- 如果可選爲
nil
,調用方法、下標、屬性
失效,結果爲nil
- 如果可選項不爲
nil
,調用方法、下標、屬性成功
,結果會被包裝成可選類型
- 如果結果本來就是
可選項
,不會進行再包裝 - 多個
?
可選可以鏈接在一起 - 如果可選鏈中任何一個節點是
nil
,那麼整個可選鏈
就會調用失敗
- 如果可選爲
func testOptionlUse() {
var person: CarPerson? = CarPerson()
var age = person?.age() //可選鏈調用
var age1 = person!.age() //強制解包
var name = person?.name //可選鏈調用
var result = person?.eat() //沒有返回值仍能夠通過變量去接受返回值
if let _ = person?.eat() {
print("調用eat成功")
}else{
print("調用eat失敗")
}
}