結(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)體都有一個(gè)編譯器自動(dòng)生成的初始化器(initializer玻佩,初始化方法出嘹,構(gòu)造器,構(gòu)造方法)
- 在第6行調(diào)用的咬崔,可以傳入所有成員值税稼,用以初始化所有成員(存儲(chǔ)屬性烦秩,Stored Property)
- 編譯器會(huì)根據(jù)情況,可能會(huì)為結(jié)構(gòu)體生成多個(gè)初始化器郎仆,宗旨是:保證所有成員都有初始值
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()
- 上述代碼可以編譯通過只祠,因?yàn)榭蛇x項(xiàng)都有個(gè)默認(rèn)值nil
-
一旦在定義結(jié)構(gòu)體時(shí)自定義了初始化器,編譯器就不會(huì)再幫它自動(dòng)生成其他初始化器
初始化器
- 所有的結(jié)構(gòu)體都有一個(gè)編譯器自動(dòng)生成的初始化器(initializer扰肌,初始化方法)
- 類需要自己生成有成員值的初始化器抛寝,Swift中創(chuàng)建類和結(jié)構(gòu)體的實(shí)例時(shí)必須為所有的存儲(chǔ)屬性設(shè)置一個(gè)合適的初始值。所以類必須要提供對(duì)應(yīng)的指定初始化器曙旭,同時(shí)我們也可以為當(dāng)前的類提供便捷初始化器(注意:便捷初始化器必須從相同的類里調(diào)用另一個(gè)初始化器)
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)用任何實(shí)例方法、不能讀取任何實(shí)例屬性的值骡苞,也不能引用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)前因?yàn)閰?shù)的不合法或者外部條件的不滿足垂蜗,存在初始化失敗的情況。這種Swift中可失敗初始化器寫
return nil
語句解幽,來表明可失敗初始化器在何種情況下會(huì)觸發(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修飾符來表明所有該類的子類都必須實(shí)現(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)
- 定義存儲(chǔ)值的屬性
- 定義方法
- 定義下標(biāo)以使用下標(biāo)語法提供對(duì)其值的訪問
- 定義初始化值
- 使用extension來拓展功能
- 遵循協(xié)議來提供某種功能
結(jié)構(gòu)體和類的主要不同點(diǎn)
- 類有繼承的特性,而結(jié)構(gòu)體沒有
- 類型轉(zhuǎn)換使您能夠在運(yùn)行時(shí)檢查和解釋類實(shí)例的類型
- 類有析構(gòu)函數(shù)用來釋放其分配的資源
- 引用計(jì)數(shù)允許對(duì)一個(gè)類實(shí)例有多個(gè)引用
結(jié)構(gòu)體與類的本質(zhì)區(qū)別
- 類是引用類型(指針類型)躲株,也就意味著一個(gè)類類型的變量并不直接存儲(chǔ)具體的實(shí)例對(duì)象片部,是對(duì)當(dāng)前存儲(chǔ)具體實(shí)例內(nèi)存地址的引用
- 結(jié)構(gòu)體是值類型(枚舉也是值類型),相比較類類型的變量中存儲(chǔ)的是地址霜定,那么值類型存儲(chǔ)的就是具體的實(shí)例(或者說具體的值)
類舉例
-
類的定義和結(jié)構(gòu)體類似档悠,但編譯器并沒有為類自動(dòng)生成可以傳入成員值的初始化器
- 如果類的所有成員都在定義的時(shí)候指定了初始值,編譯器會(huì)為類生成無參的初始化器
- 成員的初始化是在這個(gè)初始化器中完成的
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()
上面兩段代碼完全等效
- 上圖都是針對(duì)64bit環(huán)境
lldb指令:
po/p:po和p的區(qū)別在于使用po只會(huì)輸出對(duì)應(yīng)的值望浩,而p則會(huì)返回值的類型以及命令結(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)的存儲(chǔ)屬性(
static
)辖所,也就是類型存儲(chǔ)屬性,他在程序運(yùn)行過程中曾雕,只初始化一次奴烙,因?yàn)楸举|(zhì)就是全局變量,全局變量在運(yùn)動(dòng)過程中只初始化一次,而且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)容拷貝一份 - 類似于對(duì)文件進(jìn)行copy、paste操作幅虑,產(chǎn)生了全新的文件副本丰滑。屬于深拷貝(deep copy)
-
值類型存儲(chǔ)在棧上
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ù)的代碼绍申,沒有對(duì)s1和s2做修改噩咪,則s1和s2指向同一地址,即淺拷貝极阅,如果后續(xù)有對(duì)s1或者s2做數(shù)據(jù)修改胃碾,則s1和s2會(huì)開出不同的地址做存儲(chǔ),則深拷貝
- 比如僅當(dāng)有“寫”操作時(shí)筋搏,才會(huì)真正執(zhí)行拷貝操作
- 對(duì)于標(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)
存儲(chǔ)地址是一樣的俄周,只是替換值
引用類型
- 引用賦值給
var
、let
或者給函數(shù)傳參髓迎,是將內(nèi)存地址拷貝一份 - 類似于制作一個(gè)文件的替身(快捷方式栈源、鏈接),指向的是同一個(gè)文件竖般,屬于淺拷貝
- 引用類型存儲(chǔ)在堆上
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):存儲(chǔ)所有對(duì)象
全局區(qū)(Global))(全局區(qū)也可以細(xì)分為全局區(qū)甚垦、常量區(qū)、text指令區(qū)):存儲(chǔ)全局變量涣雕、常量艰亮、代碼區(qū)
Segment&Section:Mach-o文件有多個(gè)段(segment),每個(gè)段有不同的功能挣郭,然后每個(gè)段又分為很多小的section
TEXT.text :機(jī)器碼
TEXT.cstring:硬編碼的字符串
TEXT.const:初始化過的常量
DATA.data:初始化過的可變的(靜態(tài)/全局)數(shù)據(jù)
DATA.const:沒有初始化過的常量
DATA.bss:沒有初始化的(靜態(tài)/全局)變量
DATA.common:沒有初始化過的符號(hào)聲明
引用類型的賦值操作
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 ??
- 方法不占用對(duì)象的內(nèi)存
- 方法的本質(zhì)就是函數(shù)逞怨,方法者疤、函數(shù)都存放在代碼段
異變方法
Swift中class
和struct
都能定義方法,但是有一點(diǎn)區(qū)別的是默認(rèn)情況下叠赦,值類型屬性不能被自身的實(shí)例方法修改
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ì):對(duì)于異變方法除秀,傳入的self
被標(biāo)記為inout
參數(shù)糯累。無論在mutating
方法內(nèi)部發(fā)生什么,都會(huì)影響外部依賴類型的一切册踩。
輸入輸出參數(shù):如果我們想函數(shù)能夠修改一個(gè)形式參數(shù)的值泳姐,而且希望這些改變?cè)诤瘮?shù)結(jié)束之后依然生效,那么就需要將形式參數(shù)定義為輸入輸出形式參數(shù)
暂吉。在形式參數(shù)定義開始的時(shí)候在前邊添加一個(gè)inout
關(guān)鍵字可以定義一個(gè)輸入輸出形式參數(shù)仗岸。
var age = 10
func modifyage(_ age:Int){
age += 1//error:Left side of mutating operator isn't mutable: 'age' is a 'let' constant
報(bào)錯(cuò):因?yàn)楹瘮?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ā)较锡,不會(huì)在vtable
中出現(xiàn)业稼,且對(duì)objc
運(yùn)行時(shí)不可見 -
dynamic
:函數(shù)均可添加dynamic
關(guān)鍵字,為非objc
類和值類型的函數(shù)賦予動(dòng)態(tài)性蚂蕴,但派發(fā)方式還是函數(shù)表派發(fā) -
@objc
:該關(guān)鍵字可以將Swift
函數(shù)暴露給Objc
運(yùn)行時(shí)低散,依舊是函數(shù)表派發(fā) -
@objc+dynamic
:消息派發(fā)的方式
內(nèi)聯(lián)函數(shù)
函數(shù)內(nèi)聯(lián)是一種編譯器優(yōu)化技術(shù),它通過使用方法的內(nèi)容替換直接調(diào)用該方法骡楼,從而優(yōu)化性能
- 將確保有時(shí)內(nèi)聯(lián)函數(shù)熔号。這是默認(rèn)行為,我們無需執(zhí)行任何操作鸟整,Swift編譯器可能會(huì)自動(dòng)內(nèi)聯(lián)函數(shù)作為優(yōu)化
-
always
- 將確保始終內(nèi)聯(lián)函數(shù)引镊,通過在函數(shù)前添加@inline(__always)
來實(shí)現(xiàn)此行為 -
never
- 將確保永遠(yuǎn)不會(huì)內(nèi)聯(lián)函數(shù)。這可以通過在函數(shù)前添加@inline(never)
來實(shí)現(xiàn) - 如果函數(shù)很長并且想避免增加代碼段大小篮条,請(qǐng)使用
@inline(never)
如果對(duì)象只在聲明的文件中可見弟头,可以用private或fileprivate進(jìn)行修飾。編譯器會(huì)對(duì)private或fileprivate對(duì)象進(jìn)行檢查涉茧,確保沒有其他繼承關(guān)系的情形下赴恨,自動(dòng)打上final標(biāo)記,進(jìn)而使得對(duì)象獲得靜態(tài)派發(fā)的特性(fileprivate:只允許在定義的源文件中訪問伴栓,private:定義的聲明中訪問)