結(jié)構(gòu)體
- 在Swift標(biāo)準(zhǔn)庫中,絕大多數(shù)的公開類型都是結(jié)構(gòu)體,而枚舉和類只占很小一部分
- 比如
Bool、Int、Double、String、Array、Dictionary等常見類型都是結(jié)構(gòu)體
1、struct Data {
2、 var year:Int
3、 var month:Int
4、 var day:Int
5、}
6、var date = Data(year: 2019, month: 6, day: 23)
- 所有的結(jié)構(gòu)體都有一個編譯器自動生成的初始化器(initializer,初始化方法,構(gòu)造器,構(gòu)造方法)
- 在第6行調(diào)用的,可以傳入所有成員值,用以初始化所有成員(存儲屬性,Stored Property)
- 編譯器會根據(jù)情況,可能會為結(jié)構(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()
- 上述代碼可以編譯通過,因為可選項都有個默認(rèn)值nil
-
一旦在定義結(jié)構(gòu)體時自定義了初始化器,編譯器就不會再幫它自動生成其他初始化器
初始化器
- 所有的結(jié)構(gòu)體都有一個編譯器自動生成的初始化器(initializer,初始化方法)
- 類需要自己生成有成員值的初始化器,Swift中創(chuàng)建類和結(jié)構(gòu)體的實例時必須為所有的存儲屬性設(shè)置一個合適的初始值。所以類必須要提供對應(yīng)的指定初始化器,同時我們也可以為當(dāng)前的類提供便捷初始化器(注意:便捷初始化器必須從相同的類里調(diào)用另一個初始化器)
class LGPerson {
var age:Int
var name:String
init(_ age:Int,_ name:String) {
self.age = age
self.name = name
}
convenience init(_ age:Int){//便捷初始化器
self.init(18,"kody")
self.age = age
self.name = ""
}
}
注意點(diǎn)
1、指定初始化器必須保證在向上委托給父類初始化器之前,其所在類引入的所有屬性都要初始化完成
2、指定初始化器必須先向上委托父類初始化器,然后才能為繼承的屬性設(shè)置新值,如果不這樣做,指定初始化器賦予的新值將被父類中的初始化器所覆蓋
3、便捷初始化器必須先委托同類中的其他初始化器,然后再為任意屬性賦新值(包括同類里定義的屬性)。如果沒這么做,便捷初始化器賦予的新值將被自己類中其它指定初始化器所覆蓋
4、初始化器在第一階段初始化完成之前,不能調(diào)用任何實例方法、不能讀取任何實例屬性的值,也不能引用self作為值
class LGPerson {
var age:Int
var name:String
init(_ age:Int,_ name:String) {
self.age = age
self.name = name
}
convenience init(_ age:Int){//便捷初始化器
self.init(18,"kody")
self.age = age
self.name = ""
}
}
class LGTeacher:LGPerson{
var subjectName:String
init (_ subjectName: String){
self.subjectName = subjectName
super.init(18,"awe")
}
}
- 可失敗初始化器:當(dāng)前因為參數(shù)的不合法或者外部條件的不滿足,存在初始化失敗的情況。這種Swift中可失敗初始化器寫
return nil語句,來表明可失敗初始化器在何種情況下會觸發(fā)初始化失敗。寫法如下:
class LGPerson {
var age:Int
var name:String
init?(age:Int,name:String) {
if age<18 {return nil}
self.age = age
self.name = name
}
}
- 必要初始化器:在類的初始化器前添加required修飾符來表明所有該類的子類都必須實現(xiàn)該初始化器
初始化器的本質(zhì)
struct Point {
var x:Int = 0
var y:Int = 0
}
var p1 = Point()
struct Point {
var x:Int
var y:Int
init(){
self.x = 0
self.y = 0
}
}
var p1 = Point()
上面兩段代碼完全等效
結(jié)構(gòu)體內(nèi)存結(jié)構(gòu)
struct Point {
var x:Int = 0
var y:Int = 0
var origin:Bool = false
}
var p1 = Point()
print(MemoryLayout<Point>.size)//17
print(MemoryLayout<Point>.stride)//24
print(MemoryLayout<Point>.alignment)//8
結(jié)構(gòu)體和類的主要相同點(diǎn)
- 定義存儲值的屬性
- 定義方法
- 定義下標(biāo)以使用下標(biāo)語法提供對其值的訪問
- 定義初始化值
- 使用extension來拓展功能
- 遵循協(xié)議來提供某種功能
結(jié)構(gòu)體和類的主要不同點(diǎn)
- 類有繼承的特性,而結(jié)構(gòu)體沒有
- 類型轉(zhuǎn)換使您能夠在運(yùn)行時檢查和解釋類實例的類型
- 類有析構(gòu)函數(shù)用來釋放其分配的資源
- 引用計數(shù)允許對一個類實例有多個引用
結(jié)構(gòu)體與類的本質(zhì)區(qū)別
- 類是引用類型(指針類型),也就意味著一個類類型的變量并不直接存儲具體的實例對象,是對當(dāng)前存儲具體實例內(nèi)存地址的引用
- 結(jié)構(gòu)體是值類型(枚舉也是值類型),相比較類類型的變量中存儲的是地址,那么值類型存儲的就是具體的實例(或者說具體的值)
類舉例
-
類的定義和結(jié)構(gòu)體類似,但編譯器并沒有為類自動生成可以傳入成員值的初始化器
- 如果類的所有成員都在定義的時候指定了初始值,編譯器會為類生成無參的初始化器
- 成員的初始化是在這個初始化器中完成的
class Point {
var x:Int = 10
var y:Int = 20
}
let p1 = Point()
class Point {
var x:Int
var y:Int
init() {
x = 10
y = 20
}
}
let p1 = Point()
上面兩段代碼完全等效

- 上圖都是針對64bit環(huán)境
lldb指令:
po/p:po和p的區(qū)別在于使用po只會輸出對應(yīng)的值,而p則會返回值的類型以及命令結(jié)果的引用名
x/8g:讀取內(nèi)存中的值(8g:8字節(jié)格式輸出)
class LGTeacher {
var age:Int
var name:String
init(age:Int,name:String) {
self.age = age
self.name = name
}
}
var t = LGTeacher(age: 18, name: "Kody")
var t1 = t;
print("end")//打斷點(diǎn)
lldb
(lldb) po t
<LGTeacher: 0x10064e3d0>
(lldb) po t1
<LGTeacher: 0x10064e3d0>
(lldb) x/8g 0x10064e3d0
0x10064e3d0: 0x0000000100008180 0x0000000600000003
0x10064e3e0: 0x0000000000000012 0x0000000079646f4b
0x10064e3f0: 0xe400000000000000 0x0000000000000000
0x10064e400: 0x00000009a0080001 0x00007fff817025c8
(lldb) po withUnsafePointer(to: &t, {print($0)}) //獲取t的地址
0x0000000100008218
(lldb) po withUnsafePointer(to: &t1, {print($0)}) //獲取t1的地址
0x0000000100008220
- 靜態(tài)的存儲屬性(
static),也就是類型存儲屬性,他在程序運(yùn)行過程中,只初始化一次,因為本質(zhì)就是全局變量,全局變量在運(yùn)動過程中只初始化一次,而且static修飾的屬性默認(rèn)是lazy的
結(jié)構(gòu)體舉例
struct LGTeacher {
var age:Int
var name:String
init(age:Int,name:String) {
self.age = age
self.name = name
}
}
var t = LGTeacher(age: 18, name: "Kody")
var t1 = t;
print("end")
lldb
(lldb) po t
? LGTeacher
- age : 18
- name : "Kody"
(lldb) po t1
? LGTeacher
- age : 18
- name : "Kody"
值類型
- 值類型賦值給
var、let或者給函數(shù)傳參,是直接將所有內(nèi)容拷貝一份 - 類似于對文件進(jìn)行copy、paste操作,產(chǎn)生了全新的文件副本。屬于深拷貝(deep copy)
-
值類型存儲在棧上
struct Point {
var x:Int
var y:Int
}
func test() {
var p1 = Point(x: 10, y: 20)
var p2 = p1
p2.x = 11
p2.y = 22
//p1.x是10,p1.y是20
}
值類型的賦值操作
var s1 = "jack"
var s2 = s1
s2.append("_Rose")
print(s1)//jack
print(s2)//jack_Rose
var a1 = [1,2,3]
var a2 = a1
a2.append(4)
a1[0] = 2
print(a1)//[2,2,3]
print(a2)//[1,2,3,4]
var d1 = ["max":10,"min":2]
var d2 = d1
d1["other"] = 7
d2["max"] = 12
print(d1)//["other":7,"max":10,"min":2]
print(d2)//["max":12,"min":2]
- 在Swift標(biāo)準(zhǔn)庫中,為了提升性能,
String、Array、Dictiionary、Set采取了Copy On Write技術(shù),以String為例
var s1 = "Jack"
var s2 = s1
如果后續(xù)的代碼,沒有對s1和s2做修改,則s1和s2指向同一地址,即淺拷貝,如果后續(xù)有對s1或者s2做數(shù)據(jù)修改,則s1和s2會開出不同的地址做存儲,則深拷貝
- 比如僅當(dāng)有“寫”操作時,才會真正執(zhí)行拷貝操作
- 對于標(biāo)準(zhǔn)庫值類型的賦值操作,Swift能確保最佳性能,所以沒必要為了保證最佳性能來避免賦值
- 建議:不需要修改的,盡量定義成let
struct Point {
var x:Int
var y:Int
}
var p1 = Point(x: 10, y: 20)
p1 = Point(x: 11, y: 22)
存儲地址是一樣的,只是替換值
引用類型
- 引用賦值給
var、let或者給函數(shù)傳參,是將內(nèi)存地址拷貝一份 - 類似于制作一個文件的替身(快捷方式、鏈接),指向的是同一個文件,屬于淺拷貝
- 引用類型存儲在堆上
class Size {
var width:Int
var height:Int
init(width:Int,height:Int) {
self.width = width
self.height = height
}
}
func test() {
var s1 = Size(width: 10, height: 20)
var s2 = s1
}


內(nèi)存區(qū)域
棧區(qū)(Stack):局部變量和函數(shù)運(yùn)行過程中的上下文
堆區(qū)(Heap):存儲所有對象
全局區(qū)(Global))(全局區(qū)也可以細(xì)分為全局區(qū)、常量區(qū)、text指令區(qū)):存儲全局變量、常量、代碼區(qū)
Segment&Section:Mach-o文件有多個段(segment),每個段有不同的功能,然后每個段又分為很多小的section
TEXT.text :機(jī)器碼
TEXT.cstring:硬編碼的字符串
TEXT.const:初始化過的常量
DATA.data:初始化過的可變的(靜態(tài)/全局)數(shù)據(jù)
DATA.const:沒有初始化過的常量
DATA.bss:沒有初始化的(靜態(tài)/全局)變量
DATA.common:沒有初始化過的符號聲明
引用類型的賦值操作
class Size {
var width:Int
var height:Int
init(width:Int,height:Int) {
self.width = width
self.height = height
}
}
var s1 = Size(width: 10, height: 20)
s1 = Size(width: 11, height: 22)

值類型、引用類型的let
struct Point {
var x:Int
var y:Int
}
class Size {
var width:Int
var height:Int
init(width:Int,height:Int) {
self.width = width
self.height = height
}
}
let p = Point(x: 10, y: 20)
p = Point(x: 11, y: 22)//error:Cannot assign to value: 'p' is a 'let' constant
p.x = 33//error:Cannot assign to property: 'p' is a 'let' constant
p.y = 44//error:Cannot assign to property: 'p' is a 'let' constant
let s = Size(width: 10, height: 20)
s = Size(width: 11, height: 22)//error:Cannot assign to value: 's' is a 'let' constant
s.width = 33
s.height = 44
let str = "Jac"
str.append("ss")//error:Cannot use mutating member on immutable value: 'str' is a 'let' constant
let arr = [1,2,3]
arr[0] = 11//error:Cannot assign through subscript: 'arr' is a 'let' constant
arr.append(4)//error:Cannot use mutating member on immutable value: 'arr' is a 'let' constant
嵌套類型
struct Poker {
enum Suit:Character {
case spades = "??",hearts = "??",diamonds = "??",clubs = "??"
}
enum Rank:Int {
case two = 2,three,four,five,six,seven,eight,nine,ten
case jack,queen,king,ace
}
}
print(Poker.Suit.spades)//spades
var suit = Poker.Suit.spades
suit = .diamonds
var rank = Poker.Rank.five
rank = .king
枚舉、結(jié)構(gòu)體、類都可以定義方法
- 一般把定義在枚舉、結(jié)構(gòu)體、類內(nèi)部的函數(shù),叫做方法
class Size {
var width = 10
var height = 10
func show() {
print("width = \(width),height = \(height)")
}
}
let s = Size()
s.show()//width = 10,height = 10
struct Point {
var x = 10
var y = 10
func show() {
print("x = \(x),y = \(y)")
}
}
let p = Point(x: 10, y: 10)
p.show()//x = 10,y = 10
enum PokerFace:Character {
case spades = "??",hearts = "??",diamonds = "??",clubs = "??"
func show() {
print("face is \(rawValue)")
}
}
let pf = PokerFace.hearts
pf.show()//face is ??
- 方法不占用對象的內(nèi)存
- 方法的本質(zhì)就是函數(shù),方法、函數(shù)都存放在代碼段
異變方法
Swift中class和struct都能定義方法,但是有一點(diǎn)區(qū)別的是默認(rèn)情況下,值類型屬性不能被自身的實例方法修改
struct Point {
var x = 0.0,y = 0.0
func moveBy(x deltaX:Double,y deltaY:Double) {
x += deltaX//error:Left side of mutating operator isn't mutable: 'self' is immutable
y += deltaY//error:Left side of mutating operator isn't mutable: 'self' is immutable
}
}
var p = Point()
p.moveBy(x:10.0, y:20.0)
想要修改,需要在func前邊添加mutating,如下:
struct Point {
var x = 0.0,y = 0.0
mutating func moveBy(x deltaX:Double,y deltaY:Double) {
x += deltaX
y += deltaY
}
}
var p = Point()
p.moveBy(x:10.0, y:20.0)
異變方法的本質(zhì):對于異變方法,傳入的self被標(biāo)記為inout參數(shù)。無論在mutating方法內(nèi)部發(fā)生什么,都會影響外部依賴類型的一切。
輸入輸出參數(shù):如果我們想函數(shù)能夠修改一個形式參數(shù)的值,而且希望這些改變在函數(shù)結(jié)束之后依然生效,那么就需要將形式參數(shù)定義為輸入輸出形式參數(shù)。在形式參數(shù)定義開始的時候在前邊添加一個inout關(guān)鍵字可以定義一個輸入輸出形式參數(shù)。
var age = 10
func modifyage(_ age:Int){
age += 1//error:Left side of mutating operator isn't mutable: 'age' is a 'let' constant
報錯:因為函數(shù)的形式參數(shù)都是let類型的常量
}
如果想內(nèi)部修改age,并且影響外部age值,可以如下寫法
var age = 10
func modifyage(_ age:inout Int){
age += 1
}
modifyage(&age)
方法調(diào)度
方式總結(jié):

影響函數(shù)派發(fā)方式
-
final:添加了final關(guān)鍵字的函數(shù)無法被重寫,使用靜態(tài)派發(fā),不會在vtable中出現(xiàn),且對objc運(yùn)行時不可見 -
dynamic:函數(shù)均可添加dynamic關(guān)鍵字,為非objc類和值類型的函數(shù)賦予動態(tài)性,但派發(fā)方式還是函數(shù)表派發(fā) -
@objc:該關(guān)鍵字可以將Swift函數(shù)暴露給Objc運(yùn)行時,依舊是函數(shù)表派發(fā) -
@objc+dynamic:消息派發(fā)的方式
內(nèi)聯(lián)函數(shù)
函數(shù)內(nèi)聯(lián)是一種編譯器優(yōu)化技術(shù),它通過使用方法的內(nèi)容替換直接調(diào)用該方法,從而優(yōu)化性能
- 將確保有時內(nèi)聯(lián)函數(shù)。這是默認(rèn)行為,我們無需執(zhí)行任何操作,Swift編譯器可能會自動內(nèi)聯(lián)函數(shù)作為優(yōu)化
-
always- 將確保始終內(nèi)聯(lián)函數(shù),通過在函數(shù)前添加@inline(__always)來實現(xiàn)此行為 -
never- 將確保永遠(yuǎn)不會內(nèi)聯(lián)函數(shù)。這可以通過在函數(shù)前添加@inline(never)來實現(xiàn) - 如果函數(shù)很長并且想避免增加代碼段大小,請使用
@inline(never)
如果對象只在聲明的文件中可見,可以用private或fileprivate進(jìn)行修飾。編譯器會對private或fileprivate對象進(jìn)行檢查,確保沒有其他繼承關(guān)系的情形下,自動打上final標(biāo)記,進(jìn)而使得對象獲得靜態(tài)派發(fā)的特性(fileprivate:只允許在定義的源文件中訪問,private:定義的聲明中訪問)







