swift引用計(jì)數(shù):
-
swift對(duì)象
都是以HeapObject
為模板創(chuàng)建,其中HeapObject的模板中第二個(gè)元素檐盟,是refCount
引用計(jì)數(shù)屬性传睹,該屬性記錄了strong
(強(qiáng)引用計(jì)數(shù))和unowned
(弱引用計(jì)數(shù))等信息叫潦。 -
weak修飾的對(duì)象
,會(huì)另外生成WeakReference對(duì)象
,內(nèi)部HeapObjectSideTableEntry
散列表類- - 在原heapObject類
的基礎(chǔ)上水援,重新記錄了refCount
(管理strong和unowned引用計(jì)數(shù))并新增了weakBits弱引用計(jì)數(shù)
。
swift與OC強(qiáng)引用計(jì)數(shù)對(duì)比
OC中創(chuàng)建實(shí)例對(duì)象時(shí)為0
swift中創(chuàng)建實(shí)例對(duì)象時(shí)默認(rèn)為1
1. Swift三大引用計(jì)數(shù)(strong茅郎、unowned蜗元、weak)
- 不管是哪種引用,持有的都是原對(duì)象(從p到p5內(nèi)存地址可以看出)
- 在每一行執(zhí)行完后系冗,x/4g打印p對(duì)象內(nèi)存信息奕扣,在第二個(gè)地址上,可以清晰感受到掌敬,
強(qiáng)引用
和無(wú)主引用
的引用計(jì)數(shù)在有規(guī)律的增加惯豆,而弱引用
卻沒有變化。
refCount內(nèi)存布局
isImmortal(0)
UnownedRefCount(1-31): unowned的引用計(jì)數(shù)
isDeinitingMask(32):是否進(jìn)行釋放操作
StrongExtraRefCount(33-62): 強(qiáng)引用計(jì)數(shù)
UseSlowRC(63)
重點(diǎn)關(guān)注UnownedRefCount
和StrongExtraRefCount
總結(jié)
對(duì)于HeapObject
來(lái)說(shuō)奔害,其refCounts
有兩種:
- 無(wú)弱引用:
strongCount
+unownedCount
- 有弱引用:
object
+xxx
+(strongCount + unownedCount)
+weakCount
HeapObject {
InlineRefCountBit {strong count + unowned count }
HeapObjectSideTableEntry{
HeapObject *object
xxx
strong Count + unowned Count(uint64_t)//64位
weak count(uint32_t)//32位
}
}
弱引用
- 我們知道
swift
是使用ARC
(自動(dòng)引用計(jì)數(shù)管理)的楷兽。如果產(chǎn)生循環(huán)引用
,我們必須有弱引用
機(jī)制去打破循環(huán)
华临。
swift中的
弱引用
芯杀,使用weak修飾
。與OC不同的是:
OC
:
弱引用計(jì)數(shù)
是存放在全局維護(hù)
的散列表
中雅潭,isa
中會(huì)記錄
是否使用了散列表
揭厚。
在引用計(jì)數(shù)
為0
時(shí),自動(dòng)觸發(fā)dealloc
,會(huì)檢查
并清空
當(dāng)前對(duì)象
的散列表計(jì)數(shù)
寻馏。
swift
:
弱引用計(jì)數(shù)
也是存放在散列表
中棋弥,但這個(gè)散列表
不是全局的。* 如果對(duì)象`沒有`使用`weak`弱引用诚欠,就是單純的`HeapObject`對(duì)象顽染,`沒有散列表`漾岳。 * 如果使用`weak`弱引用,會(huì)變?yōu)閌WeakReference`對(duì)象粉寞。這是一個(gè)`Optionl(可空對(duì)象)`尼荆。其結(jié)構(gòu)中自帶`散列表計(jì)數(shù)`區(qū)域。 但`swift`的`散列表`與`refCount`無(wú)關(guān)聯(lián)唧垦。當(dāng)`強(qiáng)引用計(jì)數(shù)`為`0`時(shí)捅儒,不會(huì)觸發(fā)`散列表`的清空。而是在`下次訪問(wèn)`發(fā)現(xiàn)`當(dāng)前對(duì)象不存在(為nil)`時(shí)振亮,會(huì)清空`散列表計(jì)數(shù)`巧还。
下面,我們通過(guò)案例
和源碼
來(lái)分析swift
的弱引用
: WeakReference對(duì)象
和內(nèi)存結(jié)構(gòu)
案例:
- 可以發(fā)現(xiàn):
weak修飾前
坊秸,p對(duì)象是HeapObject類型
麸祷,可從refCount
中看出強(qiáng)引用計(jì)數(shù)
和無(wú)主引用計(jì)數(shù)
。
weak修飾后
褒搔,p對(duì)象的類型變了
image
- 可以看到
weak修飾
的p1對(duì)象
阶牍,變成了optinal可選值
。
(不難理解星瘾,weak修飾
的對(duì)象
走孽,不
會(huì)改變
原對(duì)象的引用計(jì)數(shù)
,只是多
一層可空
的狀態(tài)
)image
斷點(diǎn)
琳状,匯編
可以看到swift_weakInit
初始化磕瓷,swift_weakDestroy
釋放。image
常規(guī)對(duì)象
與弱引用對(duì)象
區(qū)別:image
2. 內(nèi)存管理 - 循環(huán)引用
主要是研究閉包捕獲外部變量
念逞,以下面代碼為例
var age = 10
let clourse = {
age += 1
}
clourse()
print(age)
<!--打印結(jié)果-->
11
從輸出結(jié)果中可以看出:閉包內(nèi)部對(duì)變量的修改將會(huì)改變外部原始變量的值
,主要原因是閉包會(huì)捕獲外部變量生宛,這個(gè)與OC中的block是一致的
- 定義一個(gè)類,在test函數(shù)作用域消失后肮柜,會(huì)執(zhí)行init
class CJLTeacher {
var age = 18
//反初始化器(當(dāng)前實(shí)例對(duì)象即將被回收)
deinit {
print("CJLTeacher deinit")
}
}
func test(){
var t = CJLTeacher()
}
test()
<!--打印結(jié)果-->
CJLTeacher deinit
- 修改例子陷舅,通過(guò)閉包修改其屬性值
class CJLTeacher {
var age = 18
//反初始化器(當(dāng)前實(shí)例對(duì)象即將被回收)
deinit {
print("CJLTeacher deinit")
}
}
var t = CJLTeacher()
let clourse = {
t.age += 1
}
clourse()
<!--打印結(jié)果-->
11
- 【修改1】將上面例子修改為如下,其中閉包是否對(duì)t有強(qiáng)引用审洞?
class CJLTeacher {
var age = 18
deinit {
print("CJLTeacher deinit")
}
}
func test(){
var t = CJLTeacher()
let clourse = {
t.age += 1
}
clourse()
}
test()
<!--運(yùn)行結(jié)果-->
CJLTeacher deinit
運(yùn)行結(jié)果發(fā)現(xiàn)莱睁,閉包對(duì) t 并沒有強(qiáng)引用
- 【修改2】繼續(xù)修改例子為如下,是否有強(qiáng)引用芒澜?
class CJLTeacher {
var age = 18
var completionBlock: (() ->())?
deinit {
print("CJLTeacher deinit")
}
}
func test(){
var t = CJLTeacher()
t.completionBlock = {
t.age += 1
}
}
test()
從運(yùn)行結(jié)果發(fā)現(xiàn)仰剿,沒有執(zhí)行deinit方法,即沒有打印CJLTeacher deinit
痴晦,所以這里有循環(huán)引用
循環(huán)引用解決方法
有兩種方式可以解決swift中的循環(huán)引用
- 【方式一】使用
weak
修飾閉包傳入的參數(shù)南吮,其中參數(shù)的類型是optional
func test(){
var t = CJLTeacher()
t.completionBlock = { [weak t] in
t?.age += 1
}
}
- 【方式二】使用
unowned
修飾閉包參數(shù),與weak
的區(qū)別在于unowned
不允許被設(shè)置為nil誊酌,即總是假定有值
的
func test(){
var t = CJLTeacher()
t.completionBlock = { [unowned t] in
t.age += 1
}
}
捕獲列表
[weak t] / [unowned t]
在swift中被稱為捕獲列表
定義在參數(shù)列表之前
【書寫方式】捕獲列表被寫成用逗號(hào)括起來(lái)的表達(dá)式列表部凑,并用方括號(hào)括起來(lái)
如果使用捕獲列表露乏,則即使省略參數(shù)名稱、參數(shù)類型和返回類型涂邀,也必須使用
in
關(guān)鍵字[weak t]
就是取t的弱引用對(duì)象 類似weakself
請(qǐng)問(wèn)下面代碼的clourse()
調(diào)用后瘟仿,輸出的結(jié)果是什么?
func test(){
var age = 0
var height = 0.0
//將變量age用來(lái)初始化捕獲列表中的常量age比勉,即將0給了閉包中的age(值拷貝)
let clourse = {[age] in
print(age)
print(height)
}
age = 10
height = 1.85
clourse()
}
<!--打印結(jié)果-->
0
1.85
所以從結(jié)果中可以得出:對(duì)于捕獲列表
中的每個(gè)常量
劳较,閉包會(huì)利用周圍范圍內(nèi)具有相同名稱的常量/變量,來(lái)初始化捕獲列表中定義的常量浩聋。有以下幾點(diǎn)說(shuō)明:
捕獲列表中的常量是
值拷貝
观蜗,而不是引用捕獲列表中的常量的相當(dāng)于復(fù)制了變量age的值
捕獲列表中的常量是只讀的,即不可修改
3衣洁、 swift中Runtime探索
請(qǐng)問(wèn)下面代碼嫂便,會(huì)打印方法和屬性嗎?
class CJLTeacher {
var age: Int = 18
func teach(){
print("teach")
}
}
let t = CJLTeacher()
func test(){
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(CJLTeacher.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodList?[I]{
let methodName = method_getName(method)
print("方法列表:\(methodName)")
}else{
print("not found method")
}
}
var count: UInt32 = 0
let proList = class_copyPropertyList(CJLTeacher.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[I]{
let propertyName = property_getName(property)
print("屬性成員屬性:\(property)")
}else{
print("沒有找到你要的屬性")
}
}
print("test run")
}
test()
運(yùn)行結(jié)果如下闸与,發(fā)現(xiàn)并沒有打印方法和屬性
-
【嘗試1】如果給屬性 和 方法 都加上 @objc,可以打印嗎岸售?
image從運(yùn)行結(jié)果看践樱,是可以打印,但是由于類并沒有暴露給OC凸丸,所以O(shè)C是無(wú)法使用的拷邢,這樣做是沒有意義的
-
【嘗試2】如果swift的類
繼承NSObject
,沒有@objc修飾屬性和方法屎慢,是否可以打印全部屬性+方法瞭稼?image從結(jié)果發(fā)現(xiàn)獲取的只有
init
方法,主要是因?yàn)樵?swift.h
文件中暴露出來(lái)的只有init
方法 -
如果想讓OC能使用腻惠,必須類
繼承NSObject
+@objc修飾
屬性环肘、方法image -
如果
去掉@objc
修飾屬性,將方法改成dynamic
修飾集灌,是否可以打印方法悔雹?image從結(jié)果可以看出,依舊不能被OC獲取到欣喧,需要修改為
@objc dynamic
修飾image
結(jié)論
對(duì)于純swift類來(lái)說(shuō)腌零,沒有
動(dòng)態(tài)特性dynamic
(因?yàn)?code>swift是靜態(tài)語(yǔ)言
),方法和屬性不加任何修飾符的情況下唆阿,已經(jīng)不具備runtime
特性益涧,此時(shí)的方法調(diào)度,依舊是函數(shù)表調(diào)度即V_Table調(diào)度
對(duì)于純swift類驯鳖,方法和屬性添加
@objc
標(biāo)識(shí)的情況下闲询,可以通過(guò)runtime API獲取到久免,但是在OC中是無(wú)法進(jìn)行調(diào)度的,原因是因?yàn)?code>swift.h文件中沒有swift類的聲明對(duì)于
繼承自NSObject
類來(lái)說(shuō)嘹裂,如果想要?jiǎng)討B(tài)的獲取當(dāng)前屬性+方法妄壶,必須在其聲明前
添加@objc
關(guān)鍵字,如果想要使用方法交換
寄狼,還必須在屬性+方法前
添加dynamic
關(guān)鍵字丁寄,否則當(dāng)前屬性+方法只是暴露給OC使用,而不具備任何動(dòng)態(tài)特性
objc源碼驗(yàn)證
(由于xcode12.2暫時(shí)無(wú)法運(yùn)行objc源碼泊愧,下列驗(yàn)證圖片僅供參考)
-
進(jìn)入
class_copyMethodList
源碼,斷住伊磺,查看此時(shí)的cls
,其中data()
存儲(chǔ)類的信息image -
進(jìn)入
data
删咱,打印bits屑埋、superclassimage從這里可以得出
swift
中有默認(rèn)基類,即_SwiftObject
-
打印methods
image swift源碼中搜索
_SwiftObject
痰滋,繼承自NSObject
摘能,在內(nèi)存結(jié)構(gòu)上與OC基本類似的
#if __has_attribute(objc_root_class)
__attribute__((__objc_root_class__))
#endif
SWIFT_RUNTIME_EXPORT @interface SwiftObject<NSObject> {
@private
Class isa;
//refCounts
SWIFT_HEAPOBJECT_NON_OBJC_MEMBERS;
}
-
在swift類的結(jié)構(gòu)中,其中
TargetAnyClassMetadata
繼承自TargetHeapMetaData
敲街,其中只有一個(gè)屬性kind团搞,TargetAnyClassMetadata有四個(gè)屬性:isa、superclass多艇、cacheData逻恐、data
即bitsimage所以swift為了保留和OC交互,其在底層存儲(chǔ)的數(shù)據(jù)結(jié)構(gòu)上和OC是一致的
objc源碼中搜索
swift_class_t
峻黍,繼承自objc_class
复隆,保留了OC模板類的4個(gè)屬性,其次才是自己的屬性
struct swift_class_t : objc_class {
uint32_t flags;
uint32_t instanceAddressOffset;
uint32_t instanceSize;
uint16_t instanceAlignMask;
uint16_t reserved;
uint32_t classSize;
uint32_t classAddressOffset;
void *description;
// ...
void *baseAddress() {
return (void *)((uint8_t *)this - classAddressOffset);
}
};
問(wèn)題:為什么繼承NSObject姆涩?:必須通過(guò)NSObject聲明挽拂,來(lái)幫助編譯器判斷,當(dāng)前類是一個(gè)和OC交互的類
4骨饿、元類型轻局、AnyClass、Self
AnyObject
-
AnyObject
:代表任意類的instance样刷、類的類型仑扑、僅類遵守的協(xié)議
class CJLTeacher: NSObject {
var age: Int = 18
}
var t = CJLTeacher()
//此時(shí)代表的就是當(dāng)前CJLTeacher的實(shí)例對(duì)象
var t1: AnyObject = t
//此時(shí)代表的是CJLTeacher這個(gè)類的類型
var t2: AnyObject = CJLTeacher.self
//繼承自AnyObject,表示JSONMap協(xié)議只有類才可以遵守
protocol JSONMap: AnyObject { }
例如如果是結(jié)構(gòu)體遵守協(xié)議置鼻,會(huì)報(bào)錯(cuò)
需要將struct修改成class
//繼承自AnyObject镇饮,表示JSONMap協(xié)議只有類才可以遵守
protocol JSONMap: AnyObject {
}
class CJLJSONMap: JSONMap {
}
Any
-
Any
:代表任意類型
,包括 function類型 或者Optional類型,可以理解為AnyObject
是Any
的子集
//如果使用AnyObject會(huì)報(bào)錯(cuò)箕母,而Any不會(huì)
var array: [Any] = [1, "cjl", "", true]
AnyClass
-
AnyClass
:代表任意實(shí)例的類型
储藐,類型是AnyObject.Type
- 查看定義,是
public typealias AnyClass = AnyObject.Type
- 查看定義,是
T.self & T.Type
-
T.self
:如果T是
實(shí)例
對(duì)象俱济,返回的就是它本身
如果
T
是類,那么返回的是MetaData
T.Type
:一種類型
钙勃,T.self
是T.Type
類型
//此時(shí)的self類型是 CJLTeacher.Type
var t = CJLTeacher.self
打印結(jié)果如下
- 查看t1蛛碌、t2存儲(chǔ)的是什么?
var t = CJLTeacher()
//實(shí)例對(duì)象地址:實(shí)例對(duì)象.self 返回實(shí)例對(duì)象本身
var t1 = t.self
//存儲(chǔ)metadata元類型
var t2 = CJLTeacher.self
type(of:)
-
type(of:)
:用來(lái)獲取一個(gè)值的動(dòng)態(tài)類型
<!--demo1-->
var age = 10 as NSNumber
print(type(of: age))
<!--打印結(jié)果-->
__NSCFNumber
<!--demo2-->
//value - static type 靜態(tài)類型:編譯時(shí)期確定好的
//type(of:) - dynamic type:Int
var age = 10
//value的靜態(tài)類型就是Any
func test(_ value: Any){
print(type(of: value))
}
test(age)
<!--打印結(jié)果-->
Int
實(shí)踐
demo1
請(qǐng)問(wèn)下面這段代碼的打印結(jié)果是什么辖源?
class CJLTeacher{
var age = 18
var double = 1.85
func teach(){
print("LGTeacher teach")
}
}
class CJLPartTimeTeacher: CJLTeacher {
override func teach() {
print("CJLPartTimeTeacher teach")
}
}
func test(_ value: CJLTeacher){
let valueType = type(of: value)
value.teach()
print(value)
}
var t = CJLPartTimeTeacher()
test(t)
<!--打印結(jié)果-->
CJLPartTimeTeacher teach
CJLTest.CJLPartTimeTeacher
demo2
請(qǐng)問(wèn)下面代碼的打印結(jié)果是什么蔚携?
protocol TestProtocol {
}
class CJLTeacher: TestProtocol{
var age = 18
var double = 1.85
func teach(){
print("LGTeacher teach")
}
}
func test(_ value: TestProtocol){
let valueType = type(of: value)
print(valueType)
}
var t = CJLTeacher()
let t1: TestProtocol = CJLTeacher()
test(t)
test(t1)
<!--打印結(jié)果-->
CJLTeacher
CJLTeacher
- 如果將test中參數(shù)的類型修改為泛型,此時(shí)的打印是什么克饶?
func test<T>(_ value: T){
let valueType = type(of: value)
print(valueType)
}
<!--打印結(jié)果-->
CJLTeacher
TestProtocol
從結(jié)果中發(fā)現(xiàn)酝蜒,打印并不一致,原因是因?yàn)楫?dāng)有協(xié)議矾湃、泛型
時(shí)亡脑,當(dāng)前的編譯器并不能推斷出準(zhǔn)確的類型,需要將value轉(zhuǎn)換為Any
邀跃,修改后的代碼如下:
func test<T>(_ value: T){
let valueType = type(of: value as Any)
print(valueType)
}
<!--打印結(jié)果-->
CJLTeacher
CJLTeacher
demo3
在上面的案例中霉咨,如果class_getClassMethod
中傳t.self
,可以獲取方法列表嗎拍屑?
func test(){
var methodCount: UInt32 = 0
let methodList = class_copyMethodList(t.self, &methodCount)
for i in 0..<numericCast(methodCount) {
if let method = methodList?[I]{
let methodName = method_getName(method)
print("方法列表:\(methodName)")
}else{
print("not found method")
}
}
var count: UInt32 = 0
let proList = class_copyPropertyList(CJLTeacher.self, &count)
for i in 0..<numericCast(count) {
if let property = proList?[I]{
let propertyName = property_getName(property)
print("屬性成員屬性:\(property)")
}else{
print("沒有找到你要的屬性")
}
}
print("test run")
}
test()
從結(jié)果運(yùn)行看途戒,并不能,因?yàn)?code>t.self是實(shí)例對(duì)象本身
丽涩,即CJLTeacher
,并不是CJLTeacher.Type
類型
總結(jié)
當(dāng)無(wú)弱引用時(shí)裁蚁,
HeapObject
中的refCounts
等于strongCount + unownedCount
當(dāng)有弱引用時(shí)矢渊,
HeapObject
中的refCounts
等于object + xxx + (strongCount + unownedCount) + weakCount
循環(huán)應(yīng)用可以通過(guò)
weak / unowned
修飾參數(shù)來(lái)解決swift中閉包的
捕獲列表
是值拷貝
,即深拷貝枉证,是一個(gè)只讀的常量swift由于是
靜態(tài)語(yǔ)言
矮男,所以屬性、方法在不加任何修飾符的情況下時(shí)是不具備動(dòng)態(tài)性即Runtime特性
的室谚,此時(shí)的方法調(diào)度是V-Table函數(shù)表
調(diào)度如果想要
OC使用swift
類中的方法毡鉴、屬性,需要class繼承NSObject
秒赤,并使用@objc修飾
如果想要使用方法交換猪瞬,除了
繼承NSObject+@objc修飾
,還必須使用dynamic
修飾Any
:任意類型入篮,包括function類型陈瘦、optional類型AnyObject
:任意類的instance、類的類型潮售、僅類遵守的協(xié)議痊项,可以看作是Any的子類AnyClass
:任意實(shí)例類型锅风,類型是AnyObject.Type
T.self
:如果T是實(shí)例對(duì)象,則表示它本身鞍泉,如果是類皱埠,則表示metadata
.T.self
的類型是T.Type