一慨蓝、類與結(jié)構(gòu)體
1.1 結(jié)構(gòu)體
結(jié)構(gòu)體:結(jié)構(gòu)體和類十分相似感混,既可以定義屬性,又可以定義方法菌仁,但其不像類一樣具有繼承的特性浩习。
在swift中,使用struct
關(guān)鍵字來定義結(jié)構(gòu)體,結(jié)構(gòu)體中可以聲明變量或者常量作為結(jié)構(gòu)體的屬性济丘,也可以創(chuàng)建函數(shù)作為結(jié)構(gòu)體的方法谱秽,結(jié)構(gòu)體使用點語法
來調(diào)用其中的屬性和方法。
示例代碼如下:
struct ZZTeacher {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
1.2 類
類的定義:類是編程世界中萬物的抽象摹迷,使用類可以模擬萬物的對象疟赊。
類使用關(guān)鍵字class
來聲明
class ZZTeacher {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
deinit {
}
}
1.3 類與結(jié)構(gòu)體的異同
相同點:
1)定義存儲值的屬性
2)定義方法
3)定義下標以使用下標語法提供對其值的訪問
4)定義初始化器
5)使用 extension 來拓展功能
6)遵循協(xié)議來提供某種功能
不同點:
1)類有繼承的特性,而結(jié)構(gòu)體沒有
2)類型轉(zhuǎn)換使您能夠在運行時檢查和解釋類實例的類型
3)類有析構(gòu)函數(shù)用來釋放其分配的資源
4)引用計數(shù)允許對一個類實例有多個引用
1.4 引用類型和值類型
swift語言中的數(shù)據(jù)類型分為值類型
和引用類型
峡碉。結(jié)構(gòu)體近哟、枚舉以及除類以外所有數(shù)據(jù)類型都屬于值類型,只有類是引用類型的鲫寄。值類型數(shù)據(jù)和引用類型數(shù)據(jù)最大的區(qū)別在于當進行數(shù)據(jù)傳遞時吉执,值類型總是被復(fù)制,而引用類型不會被復(fù)制地来,引用類型是通過引用計數(shù)來管理其生命周期的戳玫。
類是引用類型,也就意味著一個類類型的變量并不直接存儲具體的實例對象未斑,而是對當前存儲具體實例內(nèi)存地址的引用
咕宿。示例代碼如下:
class ZGTeacher {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
deinit {
}
}
var t = ZGTeacher(age: 32, name: "Zhang")
var t1 = t
var t1 = t
這里我們借助兩個指令來查看當前變量的內(nèi)存結(jié)構(gòu)
po : p 和 po 的區(qū)別在于使用 po 只會輸出對應(yīng)的值,而 p 則會返回值的類型以及命令結(jié)果的引用名蜡秽。
x/8g: 讀取內(nèi)存中的值(8g: 8字節(jié)格式輸出)
根據(jù)上圖可以看到府阀,t和t1存儲的是同一實例對象的內(nèi)存地址,但是它們自己的存儲地址是不一樣的芽突。
Swift中有引用類型试浙,就有值類型,最典型的就是 Struct
,結(jié)構(gòu)體的定義也非常簡單寞蚌,相比較類類型的變量中存儲的是地址川队,那么值類型存儲的就是具體的實例(或者說具體的值)
。
struct ZGTeacher {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
var t = ZGTeacher(age: 32, name: "Zhang")
var t1 = t
t1.age = 20
讀者在這里需要注意睬澡,如果值類型有數(shù)據(jù)傳遞,原來的實例會被復(fù)制一份眠蚂,修改新的實例并不能修改原始的實例
煞聪。
其實引用類型就相當于 在線的Excel ,當我們把這個鏈接共享給別人的時候逝慧,別人的修改我們是能夠看到的昔脯;值類型就相當于本地的 Excel ,當我們把本地的 Excel 傳遞給別人的時候,就相當于重新復(fù)制了一份給別人啄糙,至于他們對于內(nèi)容的修改我們是無法感知的。
1.5 結(jié)構(gòu)體和類在內(nèi)存中的分布
引用類型和值類型還有一個最直觀的區(qū)別就是存儲的位置不同
:一般情況云稚,值類型存儲的在棧上隧饼,引用類型存儲在堆上
。
首先我們對內(nèi)存區(qū)域來進行一個基本概念的認知静陈,大家看下面這張圖
棧區(qū)(stack): 局部變量和函數(shù)運行過程中的上下文
func test () {
///我們在函數(shù)內(nèi)部聲明的age變量是不是就是一個局部變量
var age: Int = 32
print("end")
}
test()
Heap: 存儲所有對象
Global: 存儲全局變量燕雁;常量;代碼區(qū)
Segment & Section: Mach-O 文件有多個段( Segment )鲸拥,每個段有不同的功能拐格。然后每個段又分為很多小的 Section
TEXT.text : 機器碼
TEXT.cstring : 硬編碼的字符串
TEXT.const: 初始化過的常量
DATA.data: 初始化過的可變的(靜態(tài)/全局)數(shù)據(jù)
DATA.const: 沒有初始化過的常量
DATA.bss: 沒有初始化的(靜態(tài)/全局)變量
DATA.common: 沒有初始化過的符號聲明
///初始化過的可變的(靜態(tài)/全局)數(shù)據(jù)
int a = 20;
///沒有初始化過的符號聲明
int age;
int main(int argc, const char * argv[]) {
@autoreleasepool {
///硬編碼的字符串
char * p = "Zhang";
NSLog(@"Hello, World!");
}
return 0;
}
通過lldb打印地址,和libLGCatAddress.dylib
工具可以看到a
被放在DATA.data
(lldb) po &a 0x0000000100008020
(lldb) cat address 0x0000000100008020
address:0x0000000100008020, 8a <+0> , External: NO ZGOCTest.__DATA.__data +8
可以看到age
被放在DATA.__common
(lldb) po &age 0x0000000100008020
(lldb) cat address 0x0000000100008020
address:0x0000000100008020, 0age <+0> , External: NO ZGOCTest.__DATA.__common +0
可以看到p
被放在TEXT.__cstring
po &p 0x00007ff7bfeff2e8
(lldb) x/8g 0x00007ff7bfeff2e8
0x7ff7bfeff2e8: 0x0000000100003f9a 0x00007ff7bfeff440
0x7ff7bfeff2f8: 0x0000000000000001 0x00007ff7bfeff410
0x7ff7bfeff308: 0x00000001000194fe 0x0000000000000000
0x7ff7bfeff318: 0x0000000000000000 0x0000000000000000
(lldb) cat address 0x0000000100003f9a
address:0x0000000100003f9a, 0ZGOCTest.__TEXT.__cstring +0
下面我們看一下結(jié)構(gòu)體和類在內(nèi)存中的分布刑赶。示例代碼如下:
結(jié)構(gòu)體
struct ZGTeacher {
var age = 18
var name = "Zhang"
}
func test () {
var t = ZGTeacher()
print("end")
}
test()
接下來使用命令 frame varibale -L xxx
frame variable -L t
0x00007ff7bfeff2b0: (ZGSwiftTest.ZGTeacher) t = {
0x00007ff7bfeff2b0: age = 18
0x00007ff7bfeff2b8: name = "Zhang"
結(jié)構(gòu)體t在內(nèi)存中的地址為0x00007ff7bfeff2b0
,第一個成員變量age的地址正好和結(jié)構(gòu)體的內(nèi)存地址相同捏浊,接下來的8個字節(jié)存放的是另外一個成員變量name。根據(jù)這個打印不難看出結(jié)構(gòu)體在內(nèi)存中的分布情況撞叨,如下圖所示:
class ZGperson {
var age = 18
var name = "Zhang"
}
struct ZGTeacher {
var age = 18
var name = "Zhang"
var p = ZGperson()
}
func test () {
var t = ZGTeacher()
print("end")
}
test()
frame variable -L t
0x00007ff7bfeff2b0: (ZGSwiftTest.ZGTeacher) t = {
0x00007ff7bfeff2b0: age = 18
0x00007ff7bfeff2b8: name = "Zhang"
scalar: p = 0x00000001031074c0 {
0x00000001031074d0: age = 18
0x00000001031074d8: name = "Zhang"
}
}
為結(jié)構(gòu)體添加一個引用類型的變量金踪,結(jié)構(gòu)體分配的位置不會發(fā)生改變,還是被分配到棧上牵敷。
類
class ZGTeacher {
var age = 18
var name = "Zhang"
}
func test () {
///1胡岔、棧上分配8個字節(jié),分配實例對象的引用類型
///2劣领、堆空間上尋找合適的內(nèi)存區(qū)域
///3姐军、將value----拷貝到堆
///4、將棧上空間的地址指向堆區(qū)
var t = ZGTeacher()
print("end")
}
test()
(lldb) frame variable -L -t
scalar: (ZGSwiftTest.ZGTeacher) t = 0x0000000101009fd0 {
0x0000000101009fe0: age = 18
0x0000000101009fe8: name = "Zhang"
}
(lldb) cat address 0x0000000101009fd0
address:0x0000000101009fd0, (String) $R0 = "0x101009fd0 heap pointer, (0x30 bytes), zone: 0x7ff859c9e000"
可以看到類是被分配到堆(heap pointer)
上的
1.5 類與結(jié)構(gòu)體的應(yīng)用場景和時間分配
類和結(jié)構(gòu)體有著本質(zhì)的不同尖淘,它們在傳遞數(shù)據(jù)時的機制不同奕锌,分別適用于不同的應(yīng)用場景。蘋果官方推薦開發(fā)者在如下情況使用結(jié)構(gòu)體來描述數(shù)據(jù):
1)要描述的數(shù)據(jù)類型中只有少量的簡單數(shù)據(jù)類型的屬性村生。
2)要描述的數(shù)據(jù)類型在傳遞數(shù)據(jù)時需要以復(fù)制的方式進行惊暴。
3)要描述的數(shù)據(jù)類型中的所有屬性在進行傳遞時需要以復(fù)制的方式進行。
4)不需要繼承另一個數(shù)據(jù)模型
這里我們也可以通過github上StructVsClassPerformance這個案例來直觀的測試當前結(jié)構(gòu)體和類的時間分配趁桃。
我們來看兩個官方案例
案例一:
import UIKit
enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
var cache = [String : UIImage]()
func makeBallon(_ color : Color, _ orientation: Orientation, _ tail: Tail) -> UIImage {
let key = "\(color):\(orientation):\(tail)"
if let image = cache[key] {
return image
}
return UIImage.init()
}
通過上面的案例代碼可以看到調(diào)用key值會不停的訪問開辟和釋放空間辽话,效率不高,可以進行相關(guān)的優(yōu)化卫病。
enum Color { case blue, green, gray }
enum Orientation { case left, right }
enum Tail { case none, tail, bubble }
var cache = [String : UIImage]()
struct Ballon: Hashable {
var color: Color
var orientation: Orientation
var tail: Tail
}
func makeBallon(_ ballon: Ballon) -> UIImage {
if let image = cache[ballon] {
return image
}
return UIImage.init()
}
案例二:
struct Attachment {
let fileURL: URL
let uuid: String
let mineType: String
init?(fileURL: URL, uuid: String, mineType: String) {
guard mineType.isMineType else {
return nil
}
self.fileURL = fileURL
self.uuid = uuid
self.mineType = mimeType
}
}
可優(yōu)化為:
enum MineType: String {
case jpeg = "image/jpeg"
...
}
struct Attachment {
let fileURL: URL
let uuid: UUID
let mineType: MineType
init?(fileURL: URL, uuid: UUID, mineType: MineType) {
guard mineType.isMineType else {
return nil
}
self.fileURL = fileURL
self.uuid = uuid
self.mineType = mimeType
}
}
在實際開發(fā)應(yīng)用時油啤,應(yīng)該盡可能的用值類型替代引用類型,用結(jié)構(gòu)體替代類
蟀苛。
二益咬、類的初始化器
在結(jié)構(gòu)體中開發(fā)者并不需要提供構(gòu)造方法,結(jié)構(gòu)體會根據(jù)屬性自動生成一個構(gòu)造方法帜平,而類則要求開發(fā)者自己提供構(gòu)造方法幽告,在init()構(gòu)造方法
中梅鹦,需要完成對類中所有屬性的賦值操作。
struct ZGTeacher {
var age: Int
var name: String
// ///如果自己不創(chuàng)建的話冗锁,默認會自動生成的
// init() {
//
// }
}
Swift 中創(chuàng)建類和結(jié)構(gòu)體的實例時必須為所有的存儲屬性設(shè)置一個合適的初始值齐唆。所以類 ZGPerson 必須要提供對應(yīng)的指定初始化器,同時我們也可以為當前的類提供便捷初始化器(關(guān)鍵字convenience)
(注意:便捷初始化器必須從相同的類里調(diào)用另一個初始化器冻河。)
class ZGPerson {
var age: Int
var name: String
///_表示匿名函數(shù)
///_: 外部變量
/// age: 內(nèi)部變量
init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
///便捷初始化器箍邮,必須調(diào)用當前類的其他構(gòu)造方法
convenience init() {
self.init(32, "Zhang")
}
}
當我們派生出一個子類 ZGTeacher ,并指定一個指定初始化器之后會出現(xiàn)什么問題
class ZGTeacher: ZGPerson {
var subjectName: String
init(subjectName: String) {
self.subjectName = subjectName
}
}
Swift語言中有這樣的原則:
1)指定初始化器必須保證在向上委托給父類初始化器之前,其所在類引入的所有屬性都要初始化完成芋绸。
2)指定初始化器必須先向上委托父類初始化器媒殉,然后才能為繼承的屬性設(shè)置新值。如果不這樣做摔敛,指定初始化器賦予的新值將被父類中的初始化器所覆蓋廷蓉。
3)便捷初始化器必須先委托同類中的其它初始化器,然后再為任意屬性賦新值(包括同類里定義的屬性)马昙。如果沒這么做桃犬,便捷構(gòu)初始化器賦予的新值將被自己類中其它指定初始化器所覆蓋。
4)初始化器在第一階段初始化完成之前行楞,不能調(diào)用任何實例方法攒暇、不能讀取任何實例屬性的值,也不能引用 self 作為值子房。
可失敗初始化器
可失敗構(gòu)造方法的定義十分簡單形用,只需要使用init?()
即可
class ZGPerson {
var age: Int
var name: String
//_表示匿名函數(shù)
//_: 外部變量
// age: 內(nèi)部變量
init?(_ age: Int, _ name: String) {
///比如這里我們定義了如果小于18就不是一個合法的成年人
if age < 18 {
return nil
}
self.age = age
self.name = name
}
///便捷初始化器,必須調(diào)用當前類的其他構(gòu)造方法
convenience init?() {
self.init(32, "Zhang")
}
}
必要初始化器
另外证杭,開發(fā)者也可以設(shè)置某些構(gòu)造方法為必要構(gòu)造方法
田度,如果一個類中的某些構(gòu)造方法被指定為必要構(gòu)造方法,則其子類必須實現(xiàn)這個構(gòu)造方法(可以通過繼承或者覆寫的方式)解愤,必要構(gòu)造方法需要使用required
關(guān)鍵字進行修飾镇饺,示例代碼如下:
class ZGPerson {
var age: Int
var name: String
//_表示匿名函數(shù)
//_: 外部變量
// age: 內(nèi)部變量
required init(_ age: Int, _ name: String) {
self.age = age
self.name = name
}
///便捷初始化器,必須調(diào)用當前類的其他構(gòu)造方法
convenience init() {
self.init(32, "Zhang")
}
}
class ZGTeacher: ZGPerson {
var subjectName: String
init(subjectName: String) {
self.subjectName = subjectName
super.init(18, "Zhang")
}
///如果在子類沒有提供就會報錯
}
三送讲、類的生命周期
iOS開發(fā)的語言不管是OC還是Swift后端都是通過LLVM進行編譯的奸笤,如下圖所示:
OC 通過 clang 編譯器,編譯成 IR哼鬓,然后再生成可執(zhí)行文件 .o(這里也就是我們的機器碼)
Swift 則是通過 Swift 編譯器編譯成 IR监右,然后再生成可執(zhí)行文件。
// 分析輸出AST
swiftc main.swift -dump-parse
// 分析并且檢查類型輸出AST
swiftc main.swift -dump-ast
// 生成中間體語言(SIL)异希,未優(yōu)化
swiftc main.swift -emit-silgen
// 生成中間體語言(SIL)秸侣,優(yōu)化后的
swiftc main.swift -emit-sil
// 生成LLVM中間體語言 (.ll文件)
swiftc main.swift -emit-ir
// 生成LLVM中間體語言 (.bc文件)
swiftc main.swift -emit-bc
// 生成匯編
swiftc main.swift -emit-assembly
// 編譯生成可執(zhí)行.out文件
swiftc -o main.o main.swift
SIL文件讀取分析
@main
: 入口函數(shù), @為標識
%0
: 寄存器,虛擬的味榛,真的寄存器
xcrun swift-demangle
反編譯
Swift 對象內(nèi)存分配:
__allocating_init -----> swift_allocObject -----> swift_allocObject
-----> swift_slowAlloc -----> Malloc
Swift 對象的內(nèi)存結(jié)構(gòu) HeapObject (OC objc_object)
,有兩個屬性:
一個是Metadata
予跌,一個是 RefCount
搏色,默認占用 16 字節(jié)大小。
objc_object {
isa
}
源碼中 kind種類
struct HeapObject {
var metadata: UnsafeRawPointer
var refcounted1: UInt32
var refcounted2: UInt32
}
struct Metadata {
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}
class ZGTeacher {
var age: Int = 18
var name: String = "Zhang"
}
var t = ZGTeacher()
let objcRawPtr = Unmanaged.passUnretained(t as AnyObject).toOpaque()
let objcPtr = objcRawPtr.bindMemory(to: HeapObject.self, capacity: 1)
print(objcPtr.pointee)
let metadata = objcPtr.pointee.metadata.bindMemory(to: Metadata.self, capacity: MemoryLayout<Metadata>.stride).pointee
print(metadata)
HeapObject(metadata: 0x00000001000081a8, refcounted1: 3, refcounted2: 0)
Metadata(kind: 4295000432, superClass: _TtCs12_SwiftObject, cacheData: (140703534573568, 140943646785536), data: 4302469682, classFlags: 2, instanceAddressPoint: 0, instanceSize: 40, instanceAlignmentMask: 7, reserved: 0, classSize: 168, classAddressPoint: 16, typeDescriptor: 0x0000000100003c6c, iVarDestroyer: 0x0000000000000000)
經(jīng)過源碼分析和數(shù)據(jù)類型的重綁定,我們不難得出 swift 類的數(shù)據(jù)結(jié)構(gòu)
struct Metadata {
var kind: Int
var superClass: Any.Type
var cacheData: (Int, Int)
var data: Int
var classFlags: Int32
var instanceAddressPoint: UInt32
var instanceSize: UInt32
var instanceAlignmentMask: UInt16
var reserved: UInt16
var classSize: UInt32
var classAddressPoint: UInt32
var typeDescriptor: UnsafeMutableRawPointer
var iVarDestroyer: UnsafeRawPointer
}