
Swift 結(jié)構(gòu)體和類
結(jié)構(gòu)體
在 Swift 標(biāo)準(zhǔn)庫(kù)中,絕大多數(shù)的公開類型都是結(jié)構(gòu)體,而枚舉和類只占很小一部分
比如Bool、Int、Double、 String、Array、Dictionary等常見類型都是結(jié)構(gòu)體
1. 初始化器
所有的結(jié)構(gòu)體都有一個(gè)編譯器自動(dòng)生成的初始化器(initializer,初始化方法、構(gòu)造器、構(gòu)造方法)
struct Date {
var year: Int
var month: Int
var day: Int
}
// 可以傳入所有成員值,用以初始化所有成員(存儲(chǔ)屬性,Stored Property)
var date = Date(year: 2019, month: 06, day: 29)
如果給屬性設(shè)置可選變量,則會(huì)生成多個(gè)構(gòu)造器(初始化器)
struct Point {
var x: Int?
var y: Int?
}
var p1 = Point(x: 10, y: 10)
var p2 = Point(y: 10)
var p3 = Point(x: 10)
var p4 = Point()
類
1. 類的初始化器
- 類的定義和結(jié)構(gòu)體類似,但編譯器并沒有為類自動(dòng)生成可以傳入成員值的初始化器
- 如果類的所有成員都在定義的時(shí)候指定了初始值,編譯器會(huì)為類生成無(wú)參的初始化器
class Point {
var x: Int = 0
var y: Int = 0
}
var point = Point()
2. 結(jié)構(gòu)體與類的本質(zhì)區(qū)別
結(jié)構(gòu)體是值類型(枚舉也是值類型),類是引用類型(指針類型)

值類型
- 值類型賦值給var、let或者給函數(shù)傳參,是直接將所有內(nèi)容拷貝一份
- 類似于對(duì)文件進(jìn)行copy、paste操作,產(chǎn)生了全新的文件副本。屬于深拷貝(deep copy)
- 在Swift標(biāo)準(zhǔn)庫(kù)中,為了提升性能,String、Array、Dictionary、Set采取了Copy On Write的技術(shù)
- 比如僅當(dāng)有“寫”操作時(shí),才會(huì)真正執(zhí)行拷貝操作
- 對(duì)于標(biāo)準(zhǔn)庫(kù)值類型的賦值操作,Swift 能確保最佳性能,所有沒必要為了保證最佳性能來(lái)避免賦值
- 建議:不需要修改的,盡量定義成let
引用類型
- 引用賦值給var、let或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份
- 類似于制作一個(gè)文件的替身(快捷方式、鏈接),指向的是同一個(gè)文件。屬于淺拷貝(shallow copy)
Swift 屬性
1. 存儲(chǔ)屬性&計(jì)算屬性
Swift中跟實(shí)例對(duì)象相關(guān)的屬性可以分為2大類
存儲(chǔ)屬性(Stored Property)
- 類似于成員變量這個(gè)概念
- 存儲(chǔ)在實(shí)例對(duì)象的內(nèi)存中
- 結(jié)構(gòu)體、類可以定義存儲(chǔ)屬性
- 枚舉不可以定義存儲(chǔ)屬性
計(jì)算屬性(Computed Property)
- 本質(zhì)就是方法(函數(shù))
- 不占用實(shí)例對(duì)象的內(nèi)存
- 枚舉、結(jié)構(gòu)體、類都可以定義計(jì)算屬性
struct Circle {
// 存儲(chǔ)屬性
var radius: Double
// 計(jì)算屬性
var diameter: Double {
set {
radius = newValue / 2
}
get {
return radius * 2
}
}
}
2. 計(jì)算屬性
set傳入的新值默認(rèn)叫做newValue,也可以自定義
struct Circle {
var radius: Double
var diameter: Double {
set(newDiameter) {
radius = newDiameter / 2
}
get {
radius * 2 }
}
}
只讀計(jì)算屬性:只有g(shù)et,沒有set
struct Circle {
var radius: Double
var diameter: Double {
get {
radius * 2
}
}
}
- 定義計(jì)算屬性只能用var,不能用let
- let代表常量:值是一成不變的
- 計(jì)算屬性的值是可能發(fā)生變化的(即使是只讀計(jì)算屬性)
3. 枚舉rawValue原理
枚舉原始值rawValue的本質(zhì)是:只讀計(jì)算屬性
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 }
}
}
4. 延遲存儲(chǔ)屬性(Lazy Stored Property)
使用lazy可以定義一個(gè)延遲存儲(chǔ)屬性,在第一次用到屬性的時(shí)候才會(huì)進(jìn)行初始化
- lazy屬性必須是var,不能是let
- let必須在實(shí)例對(duì)象的初始化方法完成之前就擁有值
- 如果多條線程同時(shí)第一次訪問(wèn)lazy屬性
- 無(wú)法保證屬性只被初始化1次
class PhotoView {
lazy var image: Image = {
let url = "https://www.../xx.png"
let data = Data(url: url)
return Image(data: data)
}()
}
5. 屬性觀察器(Property Observer)
可以為非lazy的var存儲(chǔ)屬性設(shè)置屬性觀察器
struct Circle {
var radius: Double {
willSet {
print("willSet", newValue)
}
didSet {
print("didSet", oldValue, radius)
}
}
init() {
self.radius = 1.0
print("Circle init!")
}
}
- willSet會(huì)傳遞新值,默認(rèn)叫newValue
- didSet會(huì)傳遞舊值,默認(rèn)叫oldValue
- 在初始化器中設(shè)置屬性值不會(huì)觸發(fā)willSet和didSet
6.類型屬性(Type Property)
嚴(yán)格來(lái)說(shuō),屬性可以分為
實(shí)例屬性(Instance Property): 只能通過(guò)實(shí)例對(duì)象去訪問(wèn)
- 存儲(chǔ)實(shí)例屬性(Stored Instance Property):存儲(chǔ)在實(shí)例對(duì)象的內(nèi)存中,每個(gè)實(shí)例對(duì)象都有1份
- 計(jì)算實(shí)例屬性(Computed Instance Property)
類型屬性(Type Property):只能通過(guò)類型去訪問(wèn)
- 存儲(chǔ)類型屬性(Stored Type Property):整個(gè)程序運(yùn)行過(guò)程中,就只有1份內(nèi)存(類似于全局變量)
- 計(jì)算類型屬性(Computed Type Property)
可以通過(guò)static定義類型屬性 p如果是類,也可以用關(guān)鍵字class
struct Car {
static var count: Int = 0
init() {
Car.count += 1
}
}
不同于存儲(chǔ)實(shí)例屬性,你必須給存儲(chǔ)類型屬性設(shè)定初始值
- 因?yàn)轭愋蜎]有像實(shí)例對(duì)象那樣的init初始化器來(lái)初始化存儲(chǔ)屬性
存儲(chǔ)類型屬性默認(rèn)就是lazy,會(huì)在第一次使用的時(shí)候才初始化
- 就算被多個(gè)線程同時(shí)訪問(wèn),保證只會(huì)初始化一次
- 存儲(chǔ)類型屬性可以是let
枚舉類型也可以定義類型屬性(存儲(chǔ)類型屬性、計(jì)算類型屬性)
7. 單例模式
通過(guò) 類型屬性+let+private 來(lái)寫單例;
public class FileManager {
public static let shared = {
// ....
// ....
return FileManager()
}()
private init() { }
}
Swift 方法
枚舉、結(jié)構(gòu)體、類都可以定義實(shí)例方法、類型方法
- 實(shí)例方法(Instance Method):通過(guò)實(shí)例對(duì)象調(diào)用
- 類型方法(Type Method):通過(guò)類型調(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
1.在實(shí)例方法中代表實(shí)例對(duì)象
2.在類型方法中代表類型
在類型方法static func getCount中
cout等價(jià)于self.cout、Car.self.cout、Car.cout
1.mutating
結(jié)構(gòu)體和枚舉是值類型,默認(rèn)情況下,值類型的屬性不能被自身的實(shí)例方法修改
- 在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)
} }
2. @discardableResult
在func前面加個(gè)@discardableResult,可以消除:函數(shù)調(diào)用后返回值未被使用的警告
struct Point {
var x = 0.0, y = 0.0
@discardableResult mutating
func moveX(deltaX: Double) -> Double {
x += deltaX
return x }
}
var p = Point()
p.moveX(deltaX: 10)
@discardableResult
func get() -> Int {
return 10 }
get()
Swift 下標(biāo)
使用subscript可以給任意類型(枚舉、結(jié)構(gòu)體、類)增加下標(biāo)功能,有些地方也翻譯為:下標(biāo)腳本
- subscript的語(yǔ)法類似于實(shí)例方法、計(jì)算屬性,本質(zhì)就是方法(函數(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
- subscript中定義的返回值類型決定了
- get方法的返回值類型
- set方法中newValue的類型
- subscript可以接受多個(gè)參數(shù),并且類型任意
1. 下標(biāo)的細(xì)節(jié)
- subscript可以沒有set方法,但必須要有g(shù)et方法
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 }
} }
- 如果只有g(shù)et方法,可以省略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
}
}
- 可以設(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 }
}
- 下標(biāo)可以是類型方法
class Sum {
static subscript(v1: Int, v2: Int) -> Int {
return v1 + v2
}
}
print(Sum[10, 20]) // 30
2. 接收多個(gè)參數(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)
Swift 繼承
- 值類型(枚舉、結(jié)構(gòu)體)不支持繼承,只有類支持繼承
- 沒有父類的類,稱為:基類 ,Swift并沒有像OC、Java那樣的規(guī)定:任何類最終都要繼承自某個(gè)基類
- 子類可以重寫父類的下標(biāo)、方法、屬性,重寫必須加上override關(guān)鍵字
class Animal {
var age = 0
}
class Dog : Animal {
var weight = 0
}
class ErHa : Dog {
var iq = 0
}
1. 重寫實(shí)例方法、下標(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])
- 被class修飾的類型方法、下標(biāo),允許被子類重寫
- 被static修飾的類型方法、下標(biāo),不允許被子類重寫
2. 重寫屬性
- 子類可以將父類的屬性(存儲(chǔ)、計(jì)算)重寫為計(jì)算屬性
- 子類不可以將父類屬性重寫為存儲(chǔ)屬性
- 只能重寫var屬性,不能重寫let屬性
- 重寫時(shí),屬性名、類型要一致
- 子類重寫后的屬性權(quán)限 不能小于 父類屬性的權(quán)限
- 如果父類屬性是只讀的,那么子類重寫后的屬性可以是只讀的、也可以是可讀寫的
- 如果父類屬性是可讀寫的,那么子類重寫后的屬性也必須是可讀寫的
3. 屬性觀察器
可以在子類中為父類屬性(除了只讀計(jì)算屬性、let屬性)增加屬性觀察器
class Circle {
var radius: Int = 1
}
class SubCircle : Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue) }
didSet {
print("SubCircle didSetRadius", oldValue, radius)
} }
}
var circle = SubCircle()
// SubCircle willSetRadius 10 // SubCircle didSetRadius 1 10 circle.radius = 10
class Circle {
var radius: Int = 1 {
willSet {
print("Circle willSetRadius", newValue)
} didSet {
print("Circle didSetRadius", oldValue, radius) }
}
}
class SubCircle : Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
} didSet {
print("SubCircle didSetRadius", oldValue, radius) }
}
}
class Circle {
var radius: Int {
set {
print("Circle setRadius", newValue)
} get {
print("Circle getRadius")
return 20 }
}
}
class SubCircle : Circle {
override var radius: Int {
willSet {
print("SubCircle willSetRadius", newValue)
} didSet {
print("SubCircle didSetRadius", oldValue, radius) }
}
}
4. final
- 被final修飾的方法、下標(biāo)、屬性,禁止被重寫
- 被final修飾的類,禁止被繼承
Swift 初始化
1. 初始化器
類、結(jié)構(gòu)體、枚舉都可以定義初始化器
類有2種初始化器: 指定初始化器(designated initializer)、便捷初始化器(convenience initializer)
// 指定初始化器
init(parameters) {
statements
}
// 便捷初始化器
convenience init(parameters) {
statements
}
規(guī)則:
- 每個(gè)類至少有一個(gè)指定初始化器,指定初始化器是類的主要初始化器
- 默認(rèn)初始化器總是類的指定初始化器
- 類偏向于少量指定初始化器,一個(gè)類通常只有一個(gè)指定初始化器
初始化器的相互調(diào)用規(guī)則
- 指定初始化器必須從它的直系父類調(diào)用指定初始化器
- 便捷初始化器必須從相同的類里調(diào)用另一個(gè)初始化器
- 便捷初始化器最終必須調(diào)用一個(gè)指定初始化器
// 便捷初始化器最終必須調(diào)用一個(gè) 指定初始化器,保證安全,保證所有屬性都被初始化
class Person {
var name: String
var age: Int
var height: Int {
get {
return age*10;
}
}
// 指定初始化器
init(name: String, age: Int) {
self.name = name;
self.age = age;
}
// 便捷初始化器
convenience init(name: String){
self.init(name: name, age: 0);
}
// 便捷初始化器
convenience init (age: Int) {
self.init(name: "", age: age);
}
// 便捷初始化器
convenience init(){
self.init(name: "", age: 0);
}
}
var ppp = Person(age: 10);
var ppp1 = Person(name: "alex");
var PPP2 = Person(name: "alex", age: 10);
初始化器的相互調(diào)用

2. 兩段式初始化
Swift在編碼安全方面是煞費(fèi)苦心,為了保證初始化過(guò)程的安全,設(shè)定了兩段式初始化、 安全檢查
兩段式初始化
-
第1階段:初始化所有存儲(chǔ)屬性
- 外層調(diào)用指定\便捷初始化器
- 分配內(nèi)存給實(shí)例,但未初始化
- 指定初始化器確保當(dāng)前類定義的存儲(chǔ)屬性都初始化
- 指定初始化器調(diào)用父類的初始化器,不斷向上調(diào)用,形成初始化器鏈
-
第2階段:設(shè)置新的存儲(chǔ)屬性值
- 從頂部初始化器往下,鏈中的每一個(gè)指定初始化器都有機(jī)會(huì)進(jìn)一步定制實(shí)例
- 初始化器現(xiàn)在能夠使用self(訪問(wèn)、修改它的屬性,調(diào)用它的實(shí)例方法等等)
- 最終,鏈中任何便捷初始化器都有機(jī)會(huì)定制實(shí)例以及使用self
3. 安全檢查
- 指定初始化器必須保證在調(diào)用父類初始化器之前,其所在類定義的所有存儲(chǔ)屬性都要初始化完成
- 指定初始化器必須先調(diào)用父類初始化器,然后才能為繼承的屬性設(shè)置新值
- 便捷初始化器必須先調(diào)用同類中的其它初始化器,然后再為任意屬性設(shè)置新值
- 初始化器在第1階段初始化完成之前,不能調(diào)用任何實(shí)例方法、不能讀取任何實(shí)例屬性的值,也不能引用self n 直到第1階段結(jié)束,實(shí)例才算完全合法
4. 重寫
- 當(dāng)重寫父類的指定初始化器時(shí),必須加上override(即使子類的實(shí)現(xiàn)是便捷初始化器)
- 如果子類寫了一個(gè)匹配父類便捷初始化器的初始化器,不用加上override
- 因?yàn)楦割惖谋憬莩跏蓟饔肋h(yuǎn)不會(huì)通過(guò)子類直接調(diào)用,因此,嚴(yán)格來(lái)說(shuō),子類無(wú)法重寫父類的便捷初始化器
5.自動(dòng)繼承
- 如果子類沒有自定義任何指定初始化器,它會(huì)自動(dòng)繼承父類所有的指定初始化器
- 如果子類提供了父類所有指定初始化器的實(shí)現(xiàn)(要么通過(guò)方式1繼承,要么重寫)
- 子類自動(dòng)繼承所有的父類便捷初始化器
- 就算子類添加了更多的便捷初始化器,這些規(guī)則仍然適用
- 子類以便捷初始化器的形式重寫父類的指定初始化器,也可以作為滿足規(guī)則2的一部分
6.required
- 用required修飾指定初始化器,表明其所有子類都必須實(shí)現(xiàn)該初始化器(通過(guò)繼承或者重寫實(shí)現(xiàn))
- 如果子類重寫了required初始化器,時(shí)也必須加上required,不用加override
class Person {
required init() { }
init(age: Int) { }
}
class Student: Person {
required init() {
super.init()
}
}
7.屬性觀察器
父類的屬性在它自己的初始化器中賦值不會(huì)觸發(fā)屬性觀察器,但在子類的初始化器中賦值會(huì)觸發(fā)屬性觀察器
class Person {
var age: Int {
willSet {
print("willSet", newValue)
} didSet {
print("didSet", oldValue, age)
}
} init() {
self.age = 0 }
}
class Student: Person {
override init() {
super.init()
self.age = 1 }
}
// willSet 1
// didSet 0 1
var stu = Student()
8.可失敗初始化器
類、結(jié)構(gòu)體、枚舉都可以使用init?定義可失敗初始化器
class Person {
var name: String
init?(name: String) {
if name.isEmpty {
return nil
}
self.name = name
}
}
- 不允許同時(shí)定義參數(shù)標(biāo)簽、參數(shù)個(gè)數(shù)、參數(shù)類型相同的可失敗初始化器和非可失敗初始化器
- 可以用init!定義隱式解包的可失敗初始化器
- 可失敗初始化器可以調(diào)用非可失敗初始化器,非可失敗初始化器調(diào)用可失敗初始化器需要進(jìn)行解包
- 如果初始化器調(diào)用一個(gè)可失敗初始化器導(dǎo)致初始化失敗,那么整個(gè)初始化過(guò)程都失敗,并且之后的代碼都停止執(zhí)行
- 可以用一個(gè)非可失敗初始化器重寫一個(gè)可失敗初始化器,但反過(guò)來(lái)是不行的
9.反初始化器(deinit)
deinit叫做反初始化器,類似于C++的析構(gòu)函數(shù)、OC中的dealloc方法
- 當(dāng)類的實(shí)例對(duì)象被釋放內(nèi)存時(shí),就會(huì)調(diào)用實(shí)例對(duì)象的deinit方法
class Person {
deinit {
print("Person對(duì)象銷毀了")
}
}
- deinit不接受任何參數(shù),不能寫小括號(hào),不能自行調(diào)用
- 父類的deinit能被子類繼承
- 子類的deinit實(shí)現(xiàn)執(zhí)行完畢后會(huì)調(diào)用父類的deinit
Swift 可選鏈
可選鏈?zhǔn)且粋€(gè)調(diào)用和查詢可選屬性、方法和下標(biāo)的過(guò)程,它可能為 nil 。如果可選項(xiàng)包含值,屬性、方法或者下標(biāo)的調(diào)用成功;如果可選項(xiàng)是 nil ,屬性、方法或者下標(biāo)的調(diào)用會(huì)返回 nil 。多個(gè)查詢可以鏈接在一起,如果鏈中任何一個(gè)節(jié)點(diǎn)是 nil ,那么整個(gè)鏈就會(huì)得體地失敗。
class Car { var price = 0 }
class Dog { var weight = 0 }
class Person {
var name: String = ""
var dog: Dog = Dog()
var car: Car? = Car()
func age() -> Int {
return 18
}
func eat() {
print("Person eat")
}
subscript(index: Int) -> Int {
return index
}
}
var person: Person? = Person()
var age1 = person!.age() // Int
var age2 = person?.age() // Int?
var name = person?.name // String?
var index = person?[6] // Int?
func getName() -> String { return "jack" }
// 如果person是nil,不會(huì)調(diào)用getName()
person?.name = getName()
- 如果可選項(xiàng)為nil,調(diào)用方法、下標(biāo)、屬性失敗,結(jié)果為nil
- 如果可選項(xiàng)不為nil,調(diào)用方法、下標(biāo)、屬性成功,結(jié)果會(huì)被包裝成可選項(xiàng)
- 如果結(jié)果本來(lái)就是可選項(xiàng),不會(huì)進(jìn)行再次包裝
var dog = person?.dog // Dog?
var weight = person?.dog.weight // Int?
var price = person?.car?.price // Int?
- 多個(gè)?可以鏈接在一起
- 如果鏈中任何一個(gè)節(jié)點(diǎn)是nil,那么整個(gè)鏈就會(huì)調(diào)用失敗