Swift進(jìn)階-內(nèi)存管理

Swift進(jìn)階-類與結(jié)構(gòu)體
Swift-函數(shù)派發(fā)
Swift進(jìn)階-屬性
Swift進(jìn)階-指針
Swift進(jìn)階-內(nèi)存管理
Swift進(jìn)階-TargetClassMetadata和TargetStructMetadata數(shù)據(jù)結(jié)構(gòu)源碼分析
Swift進(jìn)階-Mirror解析
Swift進(jìn)階-閉包
Swift進(jìn)階-協(xié)議
Swift進(jìn)階-泛型
Swift進(jìn)階-String源碼解析
Swift進(jìn)階-Array源碼解析

類與結(jié)構(gòu)體章節(jié)講到:通過匯編調(diào)試和swift源碼知道我們的 純swift類的對象 Teacher() 的內(nèi)存分配: __allocating_init -> _swift_allocObject_ -> swift_slowAlloc -> malloc

_swift_allocObject_

其中_swift_allocObject_里面創(chuàng)建一個(gè) HeapObject對象并將其返回了,所以這個(gè)HeapObject就是我們實(shí)際創(chuàng)建的對象,來看看其初始化函數(shù):

  constexpr HeapObject(HeapMetadata const *newMetadata) 
    : metadata(newMetadata)
    , refCounts(InlineRefCounts::Initialized)
  { }

了解上面內(nèi)容后,來看看創(chuàng)建一個(gè)對象,輸出它的地址,格式化輸出它的內(nèi)容:

class Teacher {
    var name: String
    init(name: String) {
        self.name = name
    }
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        let teacher = Teacher(name: "陳老師")
        // 通過Unmanaged指定內(nèi)存管理,類似于OC與CF的交互方式(所有權(quán)的轉(zhuǎn)換 __bridge)
        // passUnretained 不增加引用計(jì)數(shù),即不需要獲取所有權(quán)
        // passRetained 增加引用技術(shù),即需要獲取所有權(quán)
        // toOpaque 不透明的指針
        let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
        print(ptr)
        print("end")  // 斷點(diǎn)在這里
    }
}
輸出

通過控制臺輸出后得到上圖,而x/8g輸出的前16個(gè)字節(jié)就是matadatarefcounts,這里的refcounts就是本章節(jié)研究的內(nèi)容。

ps: 注意此時(shí)要用Unmanaged.passUnretained去輸出teacher的指針,而不能在控制臺上 po teacher!因?yàn)?po teacher 會對其進(jìn)行引用,就不是上面這個(gè)結(jié)果了,來看看:

po對象造成引用

本章節(jié)圍繞一個(gè)問題探究引用計(jì)數(shù):那為什么一次引用后會變成這個(gè)數(shù)值0x0000000200000002?

引用后打印

1.引用計(jì)數(shù)的實(shí)質(zhì)

從源碼中HeapObject.h的找到refcounts的定義

refcounts 的定義
RefCounts

RefCounts其實(shí)就是對我們當(dāng)前引用計(jì)數(shù)的包裝類,而引用計(jì)數(shù)的具體類型取決于傳遞給RefCounts的參數(shù) -> InlineRefCountBits。
找到InlineRefCountBits的定義:

typedef RefCountBitsT<RefCountIsInline> InlineRefCountBits;

它同樣是一個(gè)模板類RefCountBitsT

RefCountIsInline

RefCountIsInline傳遞的是true/false,再去看看模板類RefCountBitsT

RefCountBitsT

RefCountBitsT就是我們真實(shí)操作引用計(jì)數(shù)的類,它只有一個(gè)屬性 bits,它的類型定義是 RefCountBitsInt

image.png

引用計(jì)數(shù)的實(shí)質(zhì): bits其實(shí)就是一個(gè)uint64位的位域信息,它存儲了就是運(yùn)行周期相關(guān)的引用計(jì)數(shù)(Swift和OC都是一樣的64位的位域信息)。

1.1 提問:當(dāng)創(chuàng)建一個(gè)引用對象的時(shí)候,它的引用計(jì)數(shù)是多少?

來看看源碼中的創(chuàng)建對象函數(shù)_swift_allocObject_

_swift_allocObject_
HeapObject定義

引用計(jì)數(shù)的初始化賦值,傳遞了一個(gè)Initialized,點(diǎn)進(jìn)去看看Initialized定義,在RefCount.h找到它是一個(gè)枚舉:

可以看到源碼的注釋:新對象的Refcount為1。
RefCountBits就是模板類RefCountBitsT(它有一個(gè)bits屬性)

找到RefCountBitsT初始化函數(shù),很快能定位到傳遞的參數(shù)strongExtraCount: 0unownedCount: 1是怎么操作bits的:

RefCountBitsT初始化函數(shù)

0左移33位還是0,1左移1位是2,所以refCounts是0x2。
當(dāng)聲明一個(gè)引用對象,該對象沒有被引用的時(shí)候,refCounts是0x0000000000000002:

let teacher = Teacher(name: "陳老師")
let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
print(ptr)
print("end")
此時(shí)格式化輸出ptr的地址

當(dāng)teacher被引用的時(shí)候,其refCounts是0x0000000200000002:

let teacher = Teacher(name: "陳老師")
let ptr = Unmanaged.passUnretained(teacher as AnyObject).toOpaque()
print(ptr)
print("end")
let t = teacher
print("end")

1左移33位,1左移1位是2,所以refCounts是0x0000000200000002。

0x0000000200000002

如果再被引用一次,其refCounts是0x0000000400000002
2左移33位,1左移1位是2,所以refCounts是0x0000000400000002。

0x0000000400000002

結(jié)論:強(qiáng)引用計(jì)數(shù)無主引用計(jì)數(shù)是通過位移的方式,存儲在這64位的信息當(dāng)中。

引用計(jì)數(shù)64位存儲
1.2 提問:強(qiáng)引用是如何添加的呢?

從swift源碼中找到HeapObject.cpp找到_swift_retain_函數(shù)實(shí)現(xiàn):

_swift_retain_
increment

引用計(jì)數(shù)+1:

incrementStrongExtraRefCount

引用計(jì)數(shù)-1:

decrementStrongExtraRefCount

2.循環(huán)引用問題

經(jīng)典案例:

class WJTeacher { 
    var age: Int = 18 
    var name: String = "林老師" 
    var subject: WJSubject?
}
class WJSubject { 
    var subjectName: String 
    var subjectTeacher: WJTeacher 
    init(_ subjectName: String, _ subjectTeacher: WJTeacher) {
        self.subjectName = subjectName 
        self.subjectTeacher = subjectTeacher 
    } 
}

class ViewController: UIViewController{
    override func viewDidLoad() {
        super.viewDidLoad()
        let teacher = WJTeacher()
        let subject = WJSubject.init("Swift進(jìn)階", teacher)
        teacher.subject = subject
    }
}

上面這段代碼兩個(gè)對象相互引用導(dǎo)致無法釋放。
Swift 提供了兩種辦法?來解決你在使 ?類的屬性時(shí)所遇到的循環(huán)強(qiáng)引?問題:弱引?( weak reference )?主引?( unowned reference )

weak弱引用解決上面的問題:

class WJTeacher {
    var age: Int = 18
    var name: String = "Steven"
    weak var subject: WJSubject?  // 弱引用subject
}
class WJSubject {
    var subjectName: String
    var subjectTeacher: WJTeacher
    init(_ subjectName: String, _ subjectTeacher: WJTeacher) {
        self.subjectName = subjectName
        self.subjectTeacher = subjectTeacher
    }
}

或者使用unowned無主引用解決上面的問題

class WJTeacher {
    var age: Int = 18
    var name: String = "Steven"
    var subject: WJSubject?
}
class WJSubject {
    var subjectName: String
    unowned var subjectTeacher: WJTeacher  // 無主引用subjectTeacher
    init(_ subjectName: String, _ subjectTeacher: WJTeacher) {
        self.subjectName = subjectName
        self.subjectTeacher = subjectTeacher
    }
}

弱引用和無主引用有什么區(qū)別呢?

2.1 weak弱引用

Alaways Show Disassembly打開編調(diào)試,看看weak修飾對象在創(chuàng)建時(shí)調(diào)用流程

weak var t = WJTeacher()
print(t)  // 斷點(diǎn)打在這里
匯編調(diào)試weak修飾的WJTeacher對象創(chuàng)建

類與結(jié)構(gòu)體章節(jié)看過__allocating_init()的調(diào)用過程,沒有看到相關(guān)弱引用的內(nèi)容;接下來又調(diào)用了swift_weakInit函數(shù);找到swift源碼:

swift_weakInit
nativeInit
formWeakReference

來看看allocateSideTable做了啥事兒

allocateSideTable

來找到side: HeapObjectSideTableEntry到底是啥玩意兒,全局搜索了一下找到了源碼中有這么一段注釋,直接給出了結(jié)論:

HeapObjectSideTableEntry說明

在swift里面存在著兩種引用計(jì)數(shù),一種是強(qiáng)引用包含的是strong RC + unowned RC + flags;一種是弱引用包含的是strong RC + unowned RC + weak RC + flags

來一下HeapObjectSideTableEntry的源碼:

HeapObjectSideTableEntry
SideTableRefCounts
image.png

可以發(fā)現(xiàn)弱引用與強(qiáng)引用共用一個(gè)模板類RefCounts 其實(shí)就是它:RefCountBitsT
來看看引用計(jì)數(shù)操作模板類RefCountBitsT通過弱引用的方式創(chuàng)建的源碼:

weak的RefCountBitsT初始化

weak方式創(chuàng)建RefCountBitsT,首先是將散列表右移3位,然后將62位和63位標(biāo)記為1。

通過逆反操作驗(yàn)證上面源碼分析的的準(zhǔn)確性:

weak引用后refCounts那8位返回的是一個(gè)內(nèi)存地址(而不是引用計(jì)數(shù)64位域)

image.png

1.我們要還原,先將62位63位的標(biāo)志位設(shè)置為0得到的結(jié)果:

還原62位63位

2.還要將這個(gè)結(jié)果向左移動3位:

左移動3位還原操作

將這個(gè)結(jié)果在控制臺上格式化輸出:

weak引用一個(gè)對象本質(zhì)上就是創(chuàng)建一個(gè)散列表

2.2 unownedweak

上面介紹了強(qiáng)引用計(jì)數(shù)和無主引用計(jì)數(shù)是通過位移的方式,存儲在這64位的信息當(dāng)中的。

  • unowned不允許被引用的對象有nil的可能,無需要新建/維護(hù)散列表,直接可以通過64位的位域操作即可進(jìn)行引用計(jì)數(shù)。
  • weak它所引用的對象允許為nil,它需要新建/維護(hù)散列表

共同點(diǎn):

  • 引用對象的自動引用計(jì)數(shù)都不會加1,不會造成對引用對象的強(qiáng)引用。

解決block引用計(jì)數(shù)問題:

class WJTeacher {
    var age: Int = 18
    var name: String = "Steven"
    
    var closure: (()->())?
    
    deinit {
        print("WJTeacher 消失")
    }
}

func test() {
    let t = WJTeacher()
    var closure = {
        t.age = 19
    }
    t.closure = closure
    closure()
}

此時(shí)循環(huán)引用:t->closure->t
修改后:

class WJTeacher {
    var age: Int = 18
    var name: String = "Steven"
    
    var closure: (()->())?
    
    deinit {
        print("WJTeacher 消失")
    }
}

func test() {
    let t = WJTeacher()
    var closure = { [weak t] in
        t?.age = 19
    }
    t.closure = closure
    closure()
}

// 或者
/**

func test() {
    let t = WJTeacher()
    var closure = { [unowned t] in
        t.age = 19
    }
    t.closure = closure
    closure()
}
*/
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容