Swift 之屬性、方法、下標(biāo)、繼承、初始化、可選鏈

1、屬性

Swift 中屬性可以分為以下幾種:
實例屬性(Instance Property):只能通過實例去訪問。
存儲實例屬性(Stored Instance Property):存儲在實例的內(nèi)存中,每個實例都有1份。
計算實例屬性(Computed Instance Property)

類型屬性(Type Property):只能通過類型去訪問。
存儲類型屬性(Stored Type Property):整個程序運行過程中,就只有1份內(nèi)存(類似于全局變量)。
計算類型屬性(Computed Type Property)

1、實例屬性

Swift 中跟實例相關(guān)的屬性可以分為2大類存儲屬性和計算屬性。

struct Circle {
    // 存儲屬性
    var radius: Double
    // 計算屬性
    var diameter: Double {
        set {
            radius = newValue / 2
        }
        get {
            radius * 2
        }
    }
}

var circle = Circle(radius: 5)
print(circle.radius) // 5.0
print(circle.diameter) // 10.0

circle.diameter = 12
print(circle.radius) // 6.0
print(circle.diameter) // 12.0

存儲屬性
1、類似于成員變量
2、存儲在實例的內(nèi)存中
3、結(jié)構(gòu)體、類可以定義存儲屬性
4、枚舉不可以定義存儲屬性
5、在創(chuàng)建類或結(jié)構(gòu)體的實例時,必須為所有的存儲屬性設(shè)置一個合適的初始值??梢栽诔跏蓟骼餅榇鎯傩栽O(shè)置一個初始值,也可以分配一個默認(rèn)的屬性值作為屬性定義的一部分。

計算屬性
1、本質(zhì)就是方法(函數(shù))
2、不占用實例的內(nèi)存
3、枚舉、結(jié)構(gòu)體、類都可以定義計算屬性
4、定義計算屬性只能用 var,不能用 let。let 代表常量,值是一成不變的。計算屬性的值是可能發(fā)生變化的,即使是只讀計算屬性(只有g(shù)et,沒有set)。
5、枚舉原始值 rawValue 的本質(zhì)是:只讀計算屬性。

enum TestEnum : Int {
    case test1 = 1, test2 = 2, test3 = 3
    var rawValue: Int {
        switch self {
        case .test1:
            return 10
        case .test2:
            return 11
        case .test3:
            return 12
        }
    }
}

print(TestEnum.test3.rawValue) // 12

延遲存儲屬性
1、使用 lazy 可以定義一個延遲存儲屬性,在第一次用到屬性的時候才會進(jìn)行初始化。
2、lazy 屬性必須是 var,不能是 let。let 必須在實例的初始化方法完成之前就擁有值。
3、如果多條線程同時第一次訪問 lazy 屬性,無法保證屬性只被初始化1次。
4、當(dāng)結(jié)構(gòu)體包含一個延遲存儲屬性時,只有 var 的結(jié)構(gòu)體實例才能訪問延遲存儲屬性,因為延遲屬性初始化時需要改變結(jié)構(gòu)體的內(nèi)存。

屬性觀察器(willSet 和 didSet)
1、可以給存儲屬性添加屬性觀察器。
2、willSet 會傳遞新值,默認(rèn)叫 newValue。didSet 會傳遞舊值,默認(rèn)叫 oldValue。
3、在初始化器中設(shè)置屬性值不會觸發(fā) willSet 和 didSet。
4、在屬性定義時設(shè)置初始值也不會觸發(fā) willSet 和 didSet。
5、屬性觀察器、計算屬性,可以應(yīng)用在全局變量、局部變量。

inout 的本質(zhì)
1、如果實參有物理內(nèi)存地址且沒有設(shè)置屬性觀察器,直接將實參的內(nèi)存地址傳入函數(shù)(實參進(jìn)行引用傳遞)。
2、如果實參是計算屬性或者設(shè)置了屬性觀察器,采取了 Copy In Copy Out 的做法。
調(diào)用該函數(shù)時,先復(fù)制實參的值,產(chǎn)生副本【get】。
將副本的內(nèi)存地址傳入函數(shù)(副本進(jìn)行引用傳遞),在函數(shù)內(nèi)部可以修改副本的值。
函數(shù)返回后,再將副本的值覆蓋實參的值【set】。
3、總結(jié):inout的本質(zhì)就是引用傳遞(地址傳遞)。

2、類型屬性

可以通過 static 定義類型屬性,如果是類,也可以用關(guān)鍵字 class。

struct Car {
    static var count: Int = 0
    init() {
        Car.count += 1
    }
}

let c1 = Car()
let c2 = Car()
let c3 = Car()
print(Car.count) // 3

類型屬性特點
1、不同于存儲實例屬性,必須給存儲類型屬性設(shè)定初始值。因為類型沒有像實例那樣的 init 初始化器來初始化存儲屬性。
2、存儲類型屬性默認(rèn)就是 lazy,會在第一次使用的時候才初始化。就算被多個線程同時訪問,保證只會初始化一次。
3、存儲類型屬性可以是 let。
4、枚舉類型也可以定義類型屬性(存儲類型屬性、計算類型屬性)。

單例模式

//示例一
public class FileManager {
    public static let shared = FileManager()
    private init() { }
}
//示例二
public class FileManager {
    public static let shared = {
        // ....
        // ....
        return FileManager()
    }()
    private init() { }
}

2、方法

枚舉、結(jié)構(gòu)體、類都可以定義實例方法、類型方法。
實例方法(Instance Method):通過實例對象調(diào)用。
類型方法(Type Method):通過類型調(diào)用,可以用 static 或者 class 關(guān)鍵字定義。

class Car {
    static var cout = 0
    init() {
        Car.cout += 1
    }
    static func getCount() -> Int { cout }
}
let c0 = Car()
let c1 = Car()
let c2 = Car()
print(Car.getCount()) // 3

self:在實例方法中代表實例對象,在類型方法中代表類型。

mutating
結(jié)構(gòu)體和枚舉是值類型,默認(rèn)情況下,值類型的屬性不能被自身的實例方法修改。在 func 關(guān)鍵字前加 mutating 可以允許這種修改行為。

struct Point {
    var x = 0.0, y = 0.0
    mutating func moveBy(deltaX: Double, deltaY: Double) {
        x += deltaX
        y += deltaY
        // self = Point(x: x + deltaX, y: y + deltaY)
    }
}
enum StateSwitch {
    case low, middle, high
    mutating func next() {
        switch self {
        case .low:
            self = .middle
        case .middle:
            self = .high
        case .high:
            self = .low
        }
    }
}

@discardableResult
在 func 前面加個 @discardableResult,可以消除函數(shù)調(diào)用后返回值未被使用的警告??。

3、下標(biāo)

1、使用 subscript 可以給任意類型(枚舉、結(jié)構(gòu)體、類)增加下標(biāo)功能,subscript 的語法類似于實例方法、計算屬性,其本質(zhì)就是方法(函數(shù))。
2、subscript 中定義的返回值類型決定了 get 方法的返回值類型、set 方法中 newValue 的類型。
3、subscript 可以接受多個參數(shù),并且類型任意。

class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        set {
            if index == 0 {
                x = newValue
            } else if index == 1 {
                y = newValue
            }
        }
        get {
            if index == 0 {
                return x
            } else if index == 1 {
                return y
            }
        return 0
        }
    }
}

var p = Point()
p[0] = 11.1
p[1] = 22.2
print(p.x) // 11.1
print(p.y) // 22.2
print(p[0]) // 11.1
print(p[1]) // 22.2
1、subscript 可以沒有 set 方法,但必須要有 get 方法。
class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        get {
            if index == 0 {
                return x
            } else if index == 1 {
                return y
            }
            return 0
        }
    }
}
2、如果只有 get 方法,可以省略 get。
class Point {
    var x = 0.0, y = 0.0
    subscript(index: Int) -> Double {
        if index == 0 {
            return x
        } else if index == 1 {
            return y
        }
        return 0
    }
}
3、可以設(shè)置參數(shù)標(biāo)簽
class Point {
    var x = 0.0, y = 0.0
    subscript(index i: Int) -> Double {
        if i == 0 {
            return x
        } else if i == 1 {
            return y
        }
        return 0
    }
}

var p = Point()
p.y = 22.2
print(p[index: 1]) // 22.2
4、下標(biāo)可以是類型方法
class Sum {
    static subscript(v1: Int, v2: Int) -> Int {
        return v1 + v2
    }
}
print(Sum[10, 20]) // 30
5、接收多個參數(shù)的下標(biāo)
class Grid {
    var data = [
        [0, 1, 2],
        [3, 4, 5],
        [6, 7, 8]
    ]
    subscript(row: Int, column: Int) -> Int {
        set {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {return}
            data[row][column] = newValue
        }
        get {
            guard row >= 0 && row < 3 && column >= 0 && column < 3 else {return 0}
        return data[row][column]
        }
    }
}

var grid = Grid()
grid[0, 1] = 77
grid[1, 2] = 88
grid[2, 0] = 99
print(grid.data)

4、繼承

1、值類型(枚舉、結(jié)構(gòu)體)不支持繼承,只有類支持繼承。
2、沒有父類的類,稱為基類。Swift 并沒有像 OC 那樣的規(guī)定:任何類最終都要繼承自某個基類。
3、子類可以重寫父類的下標(biāo)、方法、屬性,重寫必須加上 override 關(guān)鍵字。

1、重寫實例方法、下標(biāo)
class Animal {
    func speak() {
        print("Animal speak")
    }
    subscript(index: Int) -> Int {
        return index
    }
}

var anim: Animal
anim = Animal()
// Animal speak
anim.speak()
// 6
print(anim[6])
class Cat : Animal {
    override func speak() {
        super.speak()
        print("Cat speak")
    }
    override subscript(index: Int) -> Int {
        return super[index] + 1
    }
}

anim = Cat()
// Animal speak
// Cat speak
anim.speak()
// 7
print(anim[6])
2、重寫類型方法、下標(biāo)

被 class 修飾的類型方法、下標(biāo),允許被子類重寫;被 static 修飾的類型方法、下標(biāo),不允許被子類重寫。

class Animal {
    class func speak() {
        print("Animal speak")
    }
    class subscript(index: Int) -> Int {
        return index
    }
}

// Animal speak
Animal.speak()
// 6
print(Animal[6])
class Cat : Animal {
    override class func speak() {
        super.speak()
        print("Cat speak")
    }
    override class subscript(index: Int) -> Int {
        return super[index] + 1
    }
}

// Animal speak
// Cat speak
Cat.speak()
// 7
print(Cat[6])
3、重寫屬性

1、子類可以將父類的屬性(存儲、計算)重寫為計算屬性。
2、子類不可以將父類屬性重寫為存儲屬性。
3、只能重寫 var 屬性,不能重寫 let 屬性。
4、重寫時,屬性名、類型要一致。
5、子類重寫后的屬性權(quán)限不能小于父類屬性的權(quán)限。如果父類屬性是只讀的,那么子類重寫后的屬性可以是只讀的、也可以是可讀寫的;如果父類屬性是可讀寫的,那么子類重寫后的屬性也必須是可讀寫的。

4、重寫類型屬性

被 class 修飾的計算類型屬性,可以被子類重寫;被 static 修飾的類型屬性(存儲、計算),不可以被子類重寫。

5、屬性觀察器

可以在子類中為父類屬性(除了只讀計算屬性、let 屬性)增加屬性觀察器。

6、final

1、被 final 修飾的方法、下標(biāo)、屬性,禁止被重寫。
2、被 final 修飾的類,禁止被繼承。

5、初始化

類、結(jié)構(gòu)體、枚舉都可以定義初始化器。
類有 2 種初始化器:指定初始化器(designated initializer)、便捷初始化器(convenience initializer)。

// 指定初始化器
init(parameters) {
    //statements
}

// 便捷初始化器
convenience init(parameters) {
    //statements
}

指定初始化器
1、每個類至少有一個指定初始化器,指定初始化器是類的主要初始化器。
2、默認(rèn)初始化器總是類的指定初始化器。
3、類偏向于少量指定初始化器,一個類通常只有一個指定初始化器。

初始化器的相互調(diào)用規(guī)則:
1、指定初始化器必須從它的直系父類調(diào)用指定初始化器。
2、便捷初始化器必須從相同的類里調(diào)用另一個初始化器。
3、便捷初始化器最終必須調(diào)用一個指定初始化器。

初始化器的簡單調(diào)用

初始化器的復(fù)雜調(diào)用
1、兩段式初始化和安全檢查

Swift 為了保證初始化過程的安全,設(shè)定了兩段式初始化、 安全檢查。

兩段式初始化
第1階段:初始化所有存儲屬性
1、外層調(diào)用指定\便捷初始化器。
2、分配內(nèi)存給實例,但未初始化。
3、指定初始化器確保當(dāng)前類定義的存儲屬性都初始化。
4、指定初始化器調(diào)用父類的初始化器,不斷向上調(diào)用,形成初始化器鏈。
第2階段:設(shè)置新的存儲屬性值
1、從頂部初始化器往下,鏈中的每一個指定初始化器都有機會進(jìn)一步定制實例。
2、初始化器現(xiàn)在能夠使用 self(訪問、修改它的屬性,調(diào)用它的實例方法等等)。
3、最終,鏈中任何便捷初始化器都有機會定制實例以及使用 self。

安全檢查
1、指定初始化器必須保證在調(diào)用父類初始化器之前,其所在類定義的所有存儲屬性都要初始化完成。
2、指定初始化器必須先調(diào)用父類初始化器,然后才能為繼承的屬性設(shè)置新值。
3、便捷初始化器必須先調(diào)用同類中的其它初始化器,然后再為任意屬性設(shè)置新值。
4、初始化器在第1階段初始化完成之前,不能調(diào)用任何實例方法、不能讀取任何實例屬性的值,也不能引用 self。
5、直到第1階段結(jié)束,實例才算完全合法。

2、重寫

1、當(dāng)重寫父類的指定初始化器時,必須加上 override(即使子類的實現(xiàn)是便捷初始化器)。
2、如果子類寫了一個匹配父類便捷初始化器的初始化器,不用加上 override。因為父類的便捷初始化器永遠(yuǎn)不會通過子類直接調(diào)用,因此,嚴(yán)格來說,子類無法重寫父類的便捷初始化器。

3、自動繼承

1、如果子類沒有自定義任何指定初始化器,它會自動繼承父類所有的指定初始化器。
2、如果子類提供了父類所有指定初始化器的實現(xiàn)(要么通過方式 1 繼承,要么重寫)。子類自動繼承所有的父類便捷初始化器。
3、就算子類添加了更多的便捷初始化器,這些規(guī)則仍然適用。
4、子類以便捷初始化器的形式重寫父類的指定初始化器,也可以作為滿足規(guī)則 2 的一部分。

4、required

用 required 修飾指定初始化器,表明其所有子類都必須實現(xiàn)該初始化器(通過繼承或者重寫實現(xiàn))。如果子類重寫了 required 初始化器,也必須加上 required,不用加 override。

class Person {
    required init() { }
    init(age: Int) { }
}
class Student : Person {
    required init() {
        super.init()
    }
}
5、屬性觀察器

父類的屬性在它自己的初始化器中賦值不會觸發(fā)屬性觀察器,但在子類的初始化器中賦值會觸發(fā)屬性觀察器。

6、可失敗初始化器

類、結(jié)構(gòu)體、枚舉都可以使用 init? 定義可失敗初始化器。

class Person {
    var name: String 
    init?(name: String) {
        if name.isEmpty {
            return nil
        }
        self.name = name
    }
}

1、不允許同時定義參數(shù)標(biāo)簽、參數(shù)個數(shù)、參數(shù)類型相同的可失敗初始化器和非可失敗初始化器。
2、可以用 init! 定義隱式解包的可失敗初始化器。
3、可失敗初始化器可以調(diào)用非可失敗初始化器,非可失敗初始化器調(diào)用可失敗初始化器需要進(jìn)行解包。
4、如果初始化器調(diào)用一個可失敗初始化器導(dǎo)致初始化失敗,那么整個初始化過程都失敗,并且之后的代碼都停止執(zhí)行。
5、可以用一個非可失敗初始化器重寫一個可失敗初始化器,但反過來是不行的。

7、反初始化器(deinit)

1、deinit 叫做反初始化器,類似于 OC 中的 dealloc 方法。當(dāng)類的實例對象被釋放內(nèi)存時,就會調(diào)用實例對象的 deinit 方法。
2、deinit 不接受任何參數(shù),不能寫小括號,不能自行調(diào)用。父類的 deinit 能被子類繼承。子類的 deinit 實現(xiàn)執(zhí)行完畢后會調(diào)用父類的 deinit。

6、可選鏈

1、如果可選項為 nil,調(diào)用方法、下標(biāo)、屬性失敗,結(jié)果為 nil。
2、如果可選項不為 nil,調(diào)用方法、下標(biāo)、屬性成功,結(jié)果會被包裝成可選項。如果結(jié)果本來就是可選項,不會進(jìn)行再次包裝。
3、多個 ? 可以鏈接在一起,如果鏈中任何一個節(jié)點是 nil,那么整個鏈就會調(diào)用失敗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容