強(qiáng)引用
Swift
使用ARC管理內(nèi)存
OC
創(chuàng)建實(shí)例對(duì)象史翘,默認(rèn)引用計(jì)數(shù)為0
Swift
創(chuàng)建實(shí)例對(duì)象手趣,默認(rèn)引用計(jì)數(shù)為1
class LGTeacher{
var age: Int = 18
var name: String = "Zang"
}
var t=LGTeacher()
var t1=t
var t2=t
上述代碼,通過
LLDB
指令來查看t
的引?計(jì)數(shù):
輸出的refCounts
為什么是0x0000000600000002
拾给?
通過源碼進(jìn)行分析盒发,打開
HeapObhect.h
,看到一個(gè)宏
進(jìn)入
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS
宏定義玛迄,這里看到refCounts
類型是InlineRefCounts
進(jìn)入
InlineRefCounts
定義由境,它是RefCounts
類型的別名
進(jìn)入
RefCounts
定義,它是一個(gè)模板類蓖议。后續(xù)邏輯取決于模板參數(shù)RefCountBits
虏杰,也就是上圖中傳入的InlineRefCountBits
的類型
進(jìn)入
InlineRefCountBits
定義,它是RefCountBitsT
類型的別名
首先確認(rèn)
RefCountIsInline
是什么勒虾,進(jìn)入RefCountIsInline
定義纺阔,本質(zhì)上是enum
,只有true
和false
修然。這里傳入的RefCountIsInline
就是true
再進(jìn)入到
RefCountBitsT
的定義州弟,里面的成員變量bits
,類型為BitsType
bits
對(duì)RefCountBitsInt
的Type
屬性取別名低零,本質(zhì)上就是uint64_t
類型
明白了
bits
是什么,下面就來分析HeapObject
的初始化方法拯杠,重點(diǎn)看第二個(gè)參數(shù)refCounts
進(jìn)入
Initialized
定義掏婶,它的本質(zhì)是一個(gè)enum
,找到對(duì)應(yīng)的refCounts
方法潭陪,需要分析一下傳入的RefCountBits(0, 1)
到底在做什么
進(jìn)入
RefCountBits
雄妥,還是模板定義,把代碼繼續(xù)往下拉...
在下面找到真正的初始化方法
RefCountBitsT
依溯,傳入strongExtraCount
和unownerCount
兩個(gè)uint32_t
類型參數(shù)老厌,將這兩個(gè)參數(shù)根據(jù)Offsets
進(jìn)行位移操作
通過源碼分析,最終我們得出這樣?個(gè)
結(jié)論
:
isImmortal
(0)UnownedRefCount
(1-31):無主引用計(jì)數(shù)isDeinitingMask
(32):是否進(jìn)行釋放操作StrongExtraRefCount
(33-62):強(qiáng)引用計(jì)數(shù)UseSlowRC
(63)
對(duì)照上述結(jié)論黎炉,使用二進(jìn)制查看
refCounts
輸出的0x0000000600000002
1-31
位是UnownedRefCount
無主引用計(jì)數(shù)33-62
位是StrongExtraRefCount
強(qiáng)引用計(jì)數(shù)
通過SIL代碼枝秤,分析
t
的引用計(jì)數(shù),當(dāng)t
賦值給t1
慷嗜、t2
時(shí)淀弹,觸發(fā)了copy_addr
查看SIL文檔,
copy_addr
內(nèi)部又觸發(fā)了strong_retain
回到源碼庆械,來到
strong_retain
的定義薇溃,它其實(shí)就是swift_retain
,其內(nèi)部是一個(gè)宏定義CALL_IMPL
缭乘,調(diào)用的是_swift_retain_
沐序,然后在_swift_retain_
內(nèi)部又調(diào)用了object->refCounts.increment(1)
進(jìn)入
increment
方法,里面的newbits
是模板函數(shù),其實(shí)就是64位
整形策幼。這里我們發(fā)現(xiàn)incrementStrongExtraRefCount
方法點(diǎn)不進(jìn)去邑时,因?yàn)榫幾g器不知道RefCountBits
目前是什么類型
我們需要回到
HeapObject
,從InlineRefCounts
進(jìn)入垄惧,找到incrementStrongExtraRefCount
方法
通過BitsType
方法將inc
類型轉(zhuǎn)換為uint64_t
刁愿,通過Offsets
偏移StrongExtraRefCountShift
,等同于1<<33
到逊,十進(jìn)制的1
左移33
位铣口,再轉(zhuǎn)換為十六進(jìn)制,得到結(jié)果0x200000000
觉壶。故此上述代碼相當(dāng)于bits += 0x200000000
脑题,左移33
位后,在33-62
位上铜靶,強(qiáng)引用計(jì)數(shù)+1
上述源碼分析中叔遂,多次看到
C++
的模板定義,其目是為了更好的抽象争剿,實(shí)現(xiàn)代碼重用機(jī)制的一種工具已艰。它可以實(shí)現(xiàn)類型參數(shù)化,即把類型定義為參數(shù)蚕苇, 從而實(shí)現(xiàn)真正的代碼可重用性哩掺。模版可以分為兩類,一個(gè)是函數(shù)模版涩笤,另外一個(gè)是類模版嚼吞。
通過
CFGetRetainCount
查看引用計(jì)數(shù)
class LGTeacher{
var age: Int = 18
var name: String = "Zang"
}
var t=LGTeacher()
print(CFGetRetainCount(t))
var t1=t
print(CFGetRetainCount(t))
var t2=t
print(CFGetRetainCount(t))
//輸出以下內(nèi)容:
//2
//3
//4
上述代碼中,原本
t
的引用計(jì)數(shù)為3
蹬碧,使用CFGetRetainCount
方法會(huì)導(dǎo)致t
的引用計(jì)數(shù)+1
弱引用
class LGTeacher{
var age: Int = 18
var name: String = "Zang"
var stu: LGStudent?
}
class LGStudent {
var age = 20
var teacher: LGTeacher?
}
func test(){
var t=LGTeacher()
weak var t1=t
print(CFGetRetainCount(t))
}
test()
//輸出以下內(nèi)容:
//2
上述代碼舱禽,
t
創(chuàng)建實(shí)例對(duì)象引用計(jì)數(shù)默認(rèn)為1
,使用CFGetRetainCount
查看引用計(jì)數(shù)+1
恩沽,打印結(jié)果為2
誊稚。顯然將t
賦值給使用weak
修飾的t1
,并沒有增加t
的強(qiáng)引用計(jì)數(shù)
通過
LLDB
指令來查看t
的引?計(jì)數(shù):
將t
賦值給weak
修飾的t1
罗心,查看refCounts
打印出奇怪的地址
通過
LLDB
指令來查看t1
:
使用weak
修飾的t1
變成了Optional
可選類型片吊,因?yàn)楫?dāng)t
被銷毀時(shí),t1
會(huì)被置為nil
协屡,所以weak
修飾的變量必須為可選類型
通過斷點(diǎn)查看匯編代碼俏脊,發(fā)現(xiàn)定義
weak
變量,會(huì)調(diào)用swift_weakInit
函數(shù)
通過源碼進(jìn)行分析肤晓,找到
swift_weakInit
函數(shù)爷贫,這個(gè)函數(shù)由WeakReference
調(diào)用认然,相當(dāng)于weak
字段在編譯器聲明過程中自定義了一個(gè)WeakReference
對(duì)象,目的在于管理弱引用漫萄。在swift_weakInit
函數(shù)內(nèi)部調(diào)用了ref->nativeInit(value)
, 其中value
就是HeapObject
進(jìn)入
nativeInit
方法卷员,判斷object
不為空,調(diào)用formWeakReference
進(jìn)入
formWeakReference
方法腾务,首先通過allocateSideTable
方法創(chuàng)建SideTable
毕骡,如果創(chuàng)建成功,調(diào)用incrementWeak
進(jìn)入
allocateSideTable
方法岩瘦,先通過refCounts
拿到原有的引用計(jì)數(shù)未巫,再通過getHeapObject
創(chuàng)建SideTable
,將地址傳入InlineRefCountBits
方法
進(jìn)入
InlineRefCountBits
方法启昧,將參數(shù)SideTable
的地址叙凡,直接進(jìn)行偏移,然后存儲(chǔ)到內(nèi)存中密末,相當(dāng)于將SideTable
直接存儲(chǔ)到uint64_t的變量中
之前查看
t
的refCounts
握爷,打印出0xc0000000200d1d6e
這串奇怪的地址,去掉62位
和63位
保留字段严里,剩余的就是偏移后的HeapObjectSideTableEntry
實(shí)例對(duì)象的內(nèi)存地址新啼,即散列表的地址
回到源碼分析,進(jìn)入
HeapObjectSideTableEntry
定義刹碾,里面有object
對(duì)象和refCounts
燥撞,refCounts
是一個(gè)SideTableRefCounts
類型
進(jìn)入
SideTableRefCounts
定義,它是RefCounts
類型的別名教硫,和之前分析的InlineRefCountBits
類似,后續(xù)邏輯取決于模板參數(shù)的傳入辆布,這里傳入的是SideTableRefCountBits
類型
進(jìn)入
SideTableRefCountBits
定義瞬矩,它繼承于RefCountBitsT
RefCountBitsT
存儲(chǔ)的是uint64_t
類型的64位
的信息,用于記錄原有引用計(jì)數(shù)锋玲。除此之外SideTableRefCountBits
自身還有一個(gè)uint32_t
的weakBits
景用,用于記錄弱引用計(jì)數(shù)
還原散列表地址,查看弱引用
refCounts
- 將
0xc0000000200d1d6e
地址62位
和63位
的保留字段清零惭蹂,得到地址0x200D1D6E
- 將
0x200D1D6E
左移3位伞插,還原成HeapObjectSideTableEntry
對(duì)象地址0x10068EB70
,也就是散列表地址- 通過
x/8g
讀取地址0x10068EB70
循環(huán)引用
案例1:
閉包捕獲外部變量
var age = 10
let clourse = {
age += 1
}
clourse()
print(age)
//輸出以下內(nèi)容:
//11
從輸出結(jié)果來看盾碗, 閉包內(nèi)部對(duì)變量的修改將會(huì)改變外部原始變量的值媚污,因?yàn)殚]包會(huì)捕獲外部變量,這個(gè)與
OC
中的block
一致
案例2:
deinit
反初始化器
class LGTeacher{
var age = 18
deinit{
print("LGTeacher deinit")
}
}
func test(){
var t = LGTeacher()
}
test()
//輸出以下內(nèi)容:
//LGTeacher deinit
當(dāng)
test
函數(shù)里的局部變量t
被銷毀時(shí)廷雅,會(huì)執(zhí)行反初始化器deinit
方法耗美,這個(gè)與OC
中的dealloc
一致
案例3:
閉包修改實(shí)例變量的值京髓,閉包能否對(duì)
t
造成強(qiáng)引用?
class LGTeacher{
var age = 18
deinit{
print("LGTeacher deinit")
}
}
func test(){
var t = LGTeacher()
let closure = {
t.age += 1
}
closure()
print("age:\(t.age)")
}
test()
//輸出以下內(nèi)容:
//age:19
//LGTeacher deinit
從輸出結(jié)果來看商架, 閉包對(duì)
t
并沒有造成強(qiáng)引用
案例4
將
案例3
進(jìn)行修改堰怨,在LGTeacher
類里定義閉包類型屬性completionBlock
,在test
函數(shù)內(nèi)蛇摸,調(diào)用t.completionBlock
閉包备图,內(nèi)部修改t.age
屬性,這樣能否對(duì)t
造成強(qiáng)引用赶袄?
class LGTeacher{
var age = 18
var completionBlock: (() ->())?
deinit{
print("LGTeacher deinit")
}
}
func test(){
var t = LGTeacher()
t.completionBlock = {
t.age += 1
}
print("age:\(t.age)")
}
test()
//輸出以下內(nèi)容:
//age:18
從輸出結(jié)果來看揽涮,這里產(chǎn)生了循環(huán)引用,沒有執(zhí)行
deinit
方法弃鸦,也沒有打印LGTeacher deinit
绞吁。因?yàn)閷?shí)例變量t
的釋放,需要等待completionBlock
閉包的作用域釋放唬格,但閉包又被實(shí)例對(duì)象強(qiáng)引用家破,造成循環(huán)引用,t
對(duì)象無法被釋放
案例5
案例4
中循環(huán)引用的兩種解決方法
1购岗、使用
weak
修飾閉包傳入的參數(shù)汰聋,參數(shù)的類型是Optional
可選類型
func test(){
var t = LGTeacher()
t.completionBlock = { [weak t] in
t?.age += 1
}
print("age:\(t.age)")
}
//輸出以下內(nèi)容:
//age:18
//LGTeacher deinit
2、使用
unowned
修飾閉包參數(shù)喊积,與weak
的區(qū)別在于unowned
不允許被設(shè)置為nil烹困,在運(yùn)行期間假定它是有值的,所以使用unowned
修飾要注意野指針的情況
func test(){
var t = LGTeacher()
t.completionBlock = { [unowned t] in
t.age += 1
}
print("age:\(t.age)")
}
//輸出以下內(nèi)容:
//age:18
//LGTeacher deinit
捕獲列表
[unowned t]
乾吻、[weak t]
在Swift
中叫做捕獲列表
- 捕獲列表的定義在參數(shù)列表之前
- 書寫形式:??括號(hào)括起來的表達(dá)式列表
- 如果使?捕獲列表髓梅,即使省略參數(shù)名稱、參數(shù)類型和返回類型绎签,也必須使?
in
關(guān)鍵字[weak t]
就是獲取t
的弱引用對(duì)象枯饿,相當(dāng)于OC
中的weakself
var age = 0
var height = 0.0
let closure = { [age] in
print(age)
print(height)
}
age = 10
height = 1.85
closure()
//輸出以下內(nèi)容:
//0
//1.85
上述代碼中,捕獲列表的
age
是常量诡必,并且進(jìn)行了值拷貝奢方。對(duì)于捕獲列表中的每個(gè)常量,閉包會(huì)利?周圍范圍內(nèi)具有相同名稱的常量或變量爸舒,來初始化捕獲列表中定義的常量蟋字。