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)用一個指定初始化器。


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)用失敗。