延遲存儲(chǔ)屬性
class HotpotCat {
lazy var age: Int = 20
}
var hotpot = HotpotCat()
hotpot.age = 30
延遲屬性賦值&大小
在hotpot.age賦值前和賦值后各打個(gè)斷點(diǎn),x/8g看下內(nèi)存地址闰非,可以看到賦值前是0x0膘格,沒有值峭范。
我們知道不加lazy的情況下內(nèi)存大小為24财松,那么加了lazy后呢?
print(class_getInstanceSize(HotpotCat.self))
32
(lldb)
發(fā)現(xiàn)變?yōu)?2了,多了8字節(jié)辆毡。那么分析下SIL:
這里有個(gè)小細(xì)節(jié)是加xcrun swift-demangle菜秦,會(huì)還原sil中swift的符號(hào),方便閱讀
swiftc -emit-sil main.swift | xcrun swift-demangle >> ./main.sil && open main.sil
我們可以看到在main函數(shù)中舶掖,會(huì)給age賦值球昨,調(diào)用了age
的setter
方法
%14 = class_method %9 : $HotpotCat, #HotpotCat.age!setter : (HotpotCat) -> (Int) -> (), $@convention(method) (Int, @guaranteed HotpotCat) -> () // user: %15
在age
的setter
方法中,可以看到是個(gè)可選值眨攘。
getter同理缝驳,有值走bb1欣除,沒有值(
Optional.none
)走bb2賦默認(rèn)值。那么延遲加載屬性本質(zhì)上是可選類型,在沒有被訪問前咏连,默認(rèn)值是nil(0x0) 。在getter方法中枚舉值分支進(jìn)行賦值操作堪藐。
那這里就解釋了為什么這里存儲(chǔ)屬性加上lazy
就變成32
字節(jié)了滋恬。因?yàn)橐鎯?chǔ)枚舉信息,實(shí)際大小為9
字節(jié)秦效,字節(jié)對(duì)齊16
字節(jié)雏蛮。
print(MemoryLayout<Optional<Int>>.size)
print(MemoryLayout<Optional<Int>>.stride)
9
16
swift enum
默認(rèn)大小為Int8
類型,編譯器會(huì)根據(jù)枚舉數(shù)量調(diào)整(Int8
阱州、Int16
挑秉、Int32
、Int64
)苔货。如果只有一個(gè)枚舉值系統(tǒng)也會(huì)優(yōu)化size
為0
衷模,stride
為1
。
獲取實(shí)例的大小可以通過MemoryLayout.size(ofValue:)
獲取蒲赂。
延遲存儲(chǔ)屬性不能保證線程安全
上面我們分析了在getter方法中阱冶,會(huì)有bb1
和bb2
兩個(gè)分支,假設(shè)此時(shí)線程1在訪問bb2
并且沒有結(jié)束滥嘴,線程2也調(diào)用getter那么也可能會(huì)走bb2
分支木蹬。這里就不能保證線程安全。
- 用關(guān)鍵字
lazy
來標(biāo)識(shí)一個(gè)延遲存儲(chǔ)屬性若皱。 - 延遲存儲(chǔ)屬性必須有一個(gè)默認(rèn)的初始值镊叁。
- 延遲存儲(chǔ)屬性在第一次訪問的時(shí)候才被賦值。
- 延遲存儲(chǔ)屬性并不能保證線程安全走触。
- 延遲存儲(chǔ)屬性對(duì)實(shí)例對(duì)象大小有影響晦譬。
類型屬性
class HotpotCat {
static var age: Int = 20
}
var age = HotpotCat.age
// globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0
sil_global private @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0 : $Builtin.Word
// static HotpotCat.age
sil_global hidden @static main.HotpotCat.age : Swift.Int : $Int
生成的age
變成了全局的變量sil_global
age
是HotpotCat.age.unsafeMutableAddressor
生成的。
// HotpotCat.age.unsafeMutableAddressor
sil hidden [global_init] @main.HotpotCat.age.unsafeMutableAddressor : Swift.Int : $@convention(thin) () -> Builtin.RawPointer {
bb0:
%0 = global_addr @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_token0 : $*Builtin.Word // user: %1
%1 = address_to_pointer %0 : $*Builtin.Word to $Builtin.RawPointer // user: %3
// function_ref globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
%2 = function_ref @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0 : $@convention(c) () -> () // user: %3
%3 = builtin "once"(%1 : $Builtin.RawPointer, %2 : $@convention(c) () -> ()) : $()
%4 = global_addr @static main.HotpotCat.age : Swift.Int : $*Int // user: %5
%5 = address_to_pointer %4 : $*Int to $Builtin.RawPointer // user: %6
return %5 : $Builtin.RawPointer // id: %6
} // end sil function 'main.HotpotCat.age.unsafeMutableAddressor : Swift.Int'
這個(gè)方法執(zhí)行了全局函數(shù)globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC
,并且這里有個(gè)builtin "once"
互广。
// globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0
sil private @globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0 : $@convention(c) () -> () {
bb0:
alloc_global @static main.HotpotCat.age : Swift.Int // id: %0
%1 = global_addr @static main.HotpotCat.age : Swift.Int : $*Int // user: %4
%2 = integer_literal $Builtin.Int64, 20 // user: %3
%3 = struct $Int (%2 : $Builtin.Int64) // user: %4
store %3 to %1 : $*Int // id: %4
%5 = tuple () // user: %6
return %5 : $() // id: %6
} // end sil function 'globalinit_029_12232F587A4C5CD8B1EEDF696793B2FC_func0'
這個(gè)函數(shù)將初始值賦值給了全局變量age
敛腌。
前面說到的once
就是swift_once
卧土,我們?cè)创a里面搜索一下swift_once
可以看到
dispatch_once_f
,這也就是我們的GCD
像樊,所以static類型屬性只會(huì)被初始化一次尤莺。
- 用static聲明類型屬性
- 類型屬性屬于類本身,不論有多少個(gè)實(shí)例生棍,類型屬性只有一份
- 全局的
- 線程安全的
單例
首先回顧下OC
單類的寫法颤霎,需要dispatch_once
確保線程安全并初始化一次,并且為了保證安全還要重寫allocWithZone
+ (instancetype)sharedInstance {
static HotpotCat *instance = nil;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
instance = [[HotpotCat alloc] init];
});
return instance;
}
swift 2.0
的時(shí)候swift
的單類也是翻譯的oc
的寫法,現(xiàn)在已經(jīng)不用了涂滴。
swift
單類只需要兩點(diǎn):
-
static
修飾一個(gè)常量友酱。 - 給
init
添加一個(gè)訪問控制權(quán)限。
class HotpotCat {
static let sharedInstance = HotpotCat()
private init(){
}
}
var hotpot = HotpotCat.sharedInstance
值類型
struct
struct HotpotCatStruct {
var age: Int
var name: String
}
class HotpotCatClass {
var age: Int
var name: String
}
對(duì)于
HotpotCatStruct
而言柔纵,已經(jīng)有自動(dòng)合成的初始化方法粹污,對(duì)于HotpotCatClass
而言"Class 'HotpotCatClass' has no initializers"。通過
SIL
查看下
struct HotpotCatStruct {
@_hasStorage var age: Int { get set }
@_hasStorage var name: String { get set }
init(age: Int, name: String)
}
已經(jīng)有了init(age: Int, name: String)
方法首量。
那么我們給age
一個(gè)默認(rèn)值呢?
struct HotpotCatStruct {
var age: Int = 1
}
//sil
struct HotpotCatStruct {
@_hasStorage @_hasInitialValue var age: Int { get set }
init(age: Int = 1)
init()
}
可以看到既有默認(rèn)的初始化方法壮吩,也有age初始化方法。
那么我們自己實(shí)現(xiàn)了init
方法呢?
struct HotpotCatStruct {
var age: Int = 1
init(age: Int) {
self.age = age
}
}
//sil
struct HotpotCatStruct {
@_hasStorage @_hasInitialValue var age: Int { get set }
init(age: Int)
}
可以看到編譯器不會(huì)幫我們生成了加缘。
- 結(jié)構(gòu)體不需要自定義初始化方法鸭叙,系統(tǒng)自動(dòng)合成。
- 如果屬性有默認(rèn)值拣宏,系統(tǒng)會(huì)提供不同的默認(rèn)初始化方法沈贝。
- 如果自定義初始化方法,系統(tǒng)不會(huì)幫我們生成勋乾。
struct值類型
首先明白下基本概念
- 地址存儲(chǔ)的就是值
- 傳遞過程中傳遞副本
struct HotpotCat {
var age: Int = 1
var age2: Int = 18
}
var hotpot = HotpotCat()
//lldb
(lldb) po hotpot
? HotpotCat
- age : 1
- age2 : 18
po
直接打印出來是值宋下,不是地址。說明是值類型辑莫,我們?cè)俅蛴∫幌聝?nèi)存地址看下存儲(chǔ)的內(nèi)容
(lldb) po withUnsafeMutablePointer(to: &hotpot){print($0)}
0x0000000100008028
0 elements
可以看到地址存儲(chǔ)的確實(shí)是
age
和age2
学歧。再將
hotpot
賦值給hotpot2
再看下SIL,發(fā)現(xiàn)直接調(diào)用了
init
各吨,沒有alloc
我們?cè)倏聪?code>init
可以看到默認(rèn)
alloc_stack
自身枝笨,在age
和age2
賦值的時(shí)候是從self
往下找的。init
返回分配在satck
的自身揭蜒。所以他是一個(gè)值類型横浑。
引用類型
那么如果是class
呢?
class HotpotClass {
var age: Int = 1
var age2: Int = 18
}
var hotpotClass = HotpotClass()
(lldb) po hotpotClass
<HotpotClass: 0x100634610>
po
直接顯示地址屉更。
對(duì)于引用類型存儲(chǔ)的是地址徙融。
如果值類型中有引用類型呢?
struct HotpotStruct {
var age: Int = 1
var age2: Int = 18
var hotpotClass: HotpotClass = HotpotClass()
}
class HotpotClass {
var age: Int = 1
var age2: Int = 18
}
var hotpotStruct = HotpotStruct()
var hotpotStruct2 = hotpotStruct
可以看出依然傳遞的是地址瑰谜,所以應(yīng)當(dāng)避免值類型中包含引用類型欺冀。
我們?cè)偻ㄟ^SIL看一下
struct HotpotStruct {
var age: Int = 1
var age2: Int = 18
var hotpotClass: HotpotClass
}
class HotpotClass {
var name: String = "HotpotCat"
}
var hotpotClass = HotpotClass()
var hotpotStruct = HotpotStruct(hotpotClass: hotpotClass)
var hotpotStruct2 = hotpotStruct
看到這里有引用計(jì)數(shù)了,strong_retain
搜索查看下發(fā)現(xiàn)一共有3
處树绩。
我們打印一下看看
(lldb) po CFGetRetainCount(hotpotStruct.hotpotClass)
3
mutating
struct HotpotStruct {
var count: Int = 0
init() {
self.count = 10
}
mutating func clear() {
self.count = 0
}
}
值類型本身創(chuàng)建后不允許修改,要修改需要加上mutating
關(guān)鍵字
那么mutating
到底干了什么呢脚猾?
先看個(gè)正常訪問變量的例子
struct HotpotStruct {
var count: Int = 0
func clear() {
print(count)
}
}
對(duì)應(yīng)的SIL
中clear
實(shí)現(xiàn)如下,可以看到隱藏參數(shù)self
為let
修飾砚哗,修改count
就為修改地址也就是修改值類型self
本身龙助,所以方法中不能修改count
。
init
中為什么能修改呢蛛芥?init
中alloc_stack
的self
為var
提鸟。那如果self
我們用var
接收再修改呢?修改是可以修改仅淑,只不過修改的是self
的另外一份拷貝(值傳遞)称勋。
func clear() {
var s = self
s.count = 10
}
加了mutaing
進(jìn)行了什么操作呢?可以看到self
是用var
修飾的添加了inout
關(guān)鍵字,debug_value
也變成了debug_value_addr
。
mutaing本質(zhì)就是給參數(shù)添加了
inout
關(guān)鍵字傳遞地址涯竟,只用于值類型赡鲜,因?yàn)橐妙愋捅旧砭褪菍?duì)地址的操作再用
&
操作驗(yàn)證下
func swap(_ a:inout Int, _ b:inout Int) {
a = a ^ b
b = a ^ b
a = a ^ b
}
var valueA = 1
var valueB = 2
swap(&valueA, &valueB)
struct函數(shù)調(diào)用方式
struct HotpotStruct {
func clear() {
print("clear")
}
}
var hotpotStruct = HotpotStruct()
hotpotStruct.clear()
在函數(shù)調(diào)用處打個(gè)斷點(diǎn),直接看下匯編代碼看到是直接調(diào)用的函數(shù)地址(0x100003da0
)
control +step into
跟進(jìn)去可以看到執(zhí)行的就是HotpotStruct.clear()
庐船。接著我們直接分析下可執(zhí)行文件
可以確定編譯鏈接完成后银酬,可執(zhí)行文件調(diào)用的函數(shù)地址就已經(jīng)確定了。
結(jié)構(gòu)體中的方法是靜態(tài)調(diào)度(編譯筐钟、鏈接完成后函數(shù)地址就已經(jīng)確定存放在了代碼段)
剛才可執(zhí)行程序分析的時(shí)候并沒有對(duì)應(yīng)的符號(hào)
SwiftStructMutating.HotpotStruct.clear()
揩瞪,符號(hào)從哪里來的呢?符號(hào)表(Symbol Table
存儲(chǔ)符號(hào)位于字符串表的偏移)篓冲。clear
在字符串表偏移0x2
李破。靜態(tài)函數(shù)只在debug下有符號(hào),release下會(huì)生成dsym文件壹将。不能確定地址的當(dāng)然還會(huì)有嗤攻。Debug模式下只是方便我們調(diào)試符號(hào)表不直接存放字符串,字符串存放在字符串表(
String Table
,存放了所有的變量名诽俯、函數(shù)名)屯曹。偏移兩個(gè)字節(jié),從
005F開始~005F結(jié)束
惊畏,都是clear
的符號(hào)恶耽。這里的字符串經(jīng)過了命名重整。我們直接在終端
nm 可執(zhí)行文件路徑
dump下符號(hào)
0000000100003da0 T _$s19SwiftStructMutating06HotpotB0V5clearyyF
0000000100003e90 T _$s19SwiftStructMutating06HotpotB0VACycfC
0000000100003f64 s _$s19SwiftStructMutating06HotpotB0VMF
0000000100003ea0 T _$s19SwiftStructMutating06HotpotB0VMa
0000000100004018 s _$s19SwiftStructMutating06HotpotB0VMf
0000000100003f40 S _$s19SwiftStructMutating06HotpotB0VMn
0000000100004020 S _$s19SwiftStructMutating06HotpotB0VN
0000000100003f24 s _$s19SwiftStructMutatingMXM
還原一下符號(hào)名稱
xcrun swift-demangle s19SwiftStructMutating06HotpotB0V5clearyyF
$s19SwiftStructMutating06HotpotB0V5clearyyF ---> SwiftStructMutating.HotpotStruct.clear() -> ()
搜索符號(hào)nm 可執(zhí)行文件路徑 | grep 地址
這里地址不帶0x
nm /Users/***/Library/Developer/Xcode/DerivedData/SwiftStructMutating-bdtkkopulnojomaeuxaauwsnscxz/Build/Products/Debug/SwiftStructMutating | grep 0100003da0
0000000100003da0 T _$s19SwiftStructMutating06HotpotB0V5clearyyF
與之對(duì)應(yīng)的我們看一下print
的調(diào)度方式
c/oc中方法的調(diào)度
void test(){
}
int main(int argc, const char * argv[]) {
test();
return 0;
}
可以看到c語言中符號(hào)直接就是_test
,所以這也是c語言不允許函數(shù)重載的原因颜启。OC
同理-[Class selector]
偷俭。
這也就解釋了
Swift
命名重整規(guī)則復(fù)雜的原因(確保符號(hào)的唯一)。
struct HotpotCat {
func pot123() {
}
}
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
var hotpot = HotpotCat()
hotpot.pot123()
}
}
打下斷點(diǎn)看下函數(shù)地址0x10ff42bb0
可以看到兩個(gè)地址有偏差缰盏,這里偏差的就是
ALSR
(隨機(jī)地址偏移)可以通過
image list
查看,這里首地址0x0ff3f000
就是ALSR
涌萤。
[ 0] A3D54669-3D78-3CA1-A48F-958F7718E05B 0x000000010ff3f000
VM Address
靜態(tài)基地址淹遵,首地址就是根據(jù)靜態(tài)及地址+偏移量確定的。
pot123
的地址0000000100003BB0
+隨機(jī)偏移地址(0x0ff3f000
)=運(yùn)行時(shí)地址(0x10ff42bb0
)