面試題
- 使用CADisplayLink、NSTimer有什么注意點(diǎn)?
- 介紹下內(nèi)存的幾大區(qū)域
- 講一下你對(duì) iOS 內(nèi)存管理的理解
- ARC 都幫我們做了什么?
- LLVM + Runtime
- weak指針的實(shí)現(xiàn)原理
- autorelease對(duì)象在什么時(shí)機(jī)會(huì)被調(diào)用release
- 方法里有局部對(duì)象, 出了方法后會(huì)立即釋放嗎
定時(shí)器
CADisplayLink、NSTimer使用注意
CADisplayLink、NSTimer會(huì)對(duì)target產(chǎn)生強(qiáng)引用,如果target又對(duì)它們產(chǎn)生強(qiáng)引用,那么就會(huì)引發(fā)循環(huán)引用
解決方案:
-
使用block
-
使用代理對(duì)象(NSProxy)
GCD定時(shí)器
NSTimer依賴(lài)于RunLoop,如果RunLoop的任務(wù)過(guò)于繁重,可能會(huì)導(dǎo)致NSTimer不準(zhǔn)時(shí)
而GCD的定時(shí)器會(huì)更加準(zhǔn)時(shí)
內(nèi)存布局
iOS程序的內(nèi)存布局
- 代碼段:編譯之后的代碼
- 數(shù)據(jù)段
- 字符串常量:比如NSString *str = @"123"
- 已初始化數(shù)據(jù):已初始化的全局變量、靜態(tài)變量等
- 未初始化數(shù)據(jù):未初始化的全局變量、靜態(tài)變量等
- 棧:函數(shù)調(diào)用開(kāi)銷(xiāo),比如局部變量。分配的內(nèi)存空間地址越來(lái)越小
-
堆:通過(guò)alloc、malloc、calloc等動(dòng)態(tài)分配的空間,分配的內(nèi)存空間地址越來(lái)越大
Tagged Pointer
- 從64bit開(kāi)始,iOS引入了Tagged Pointer技術(shù),用于優(yōu)化NSNumber、NSDate、NSString等小對(duì)象的存儲(chǔ)
- 在沒(méi)有使用Tagged Pointer之前, NSNumber等對(duì)象需要?jiǎng)討B(tài)分配內(nèi)存、維護(hù)引用計(jì)數(shù)等,NSNumber指針存儲(chǔ)的是堆中NSNumber對(duì)象的地址值
- 使用Tagged Pointer之后,NSNumber指針里面存儲(chǔ)的數(shù)據(jù)變成了:Tag + Data,也就是將數(shù)據(jù)直接存儲(chǔ)在了指針中
- 當(dāng)指針不夠存儲(chǔ)數(shù)據(jù)時(shí),才會(huì)使用動(dòng)態(tài)分配內(nèi)存的方式來(lái)存儲(chǔ)數(shù)據(jù)
- objc_msgSend能識(shí)別Tagged Pointer,比如NSNumber的intValue方法,直接從指針提取數(shù)據(jù),節(jié)省了以前的調(diào)用開(kāi)銷(xiāo)
- 如何判斷一個(gè)指針是否為T(mén)agged Pointer?
- iOS平臺(tái),最高有效位是1(第64bit)
- Mac平臺(tái),最低有效位是1
判斷是否為T(mén)agged Pointer
面試題
思考以下2段代碼能發(fā)生什么事?有什么區(qū)別?
對(duì)象的內(nèi)存管理
OC對(duì)象的內(nèi)存管理
- 在iOS中,使用引用計(jì)數(shù)來(lái)管理OC對(duì)象的內(nèi)存
- 一個(gè)新創(chuàng)建的OC對(duì)象引用計(jì)數(shù)默認(rèn)是1,當(dāng)引用計(jì)數(shù)減為0,OC對(duì)象就會(huì)銷(xiāo)毀,釋放其占用的內(nèi)存空間
- 調(diào)用retain會(huì)讓OC對(duì)象的引用計(jì)數(shù)+1,調(diào)用release會(huì)讓OC對(duì)象的引用計(jì)數(shù)-1
- 內(nèi)存管理的經(jīng)驗(yàn)總結(jié)
- 當(dāng)調(diào)用alloc、new、copy、mutableCopy方法返回了一個(gè)對(duì)象,在不需要這個(gè)對(duì)象時(shí),要調(diào)用release或者autorelease來(lái)釋放它
- 想擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)+1;不想再擁有某個(gè)對(duì)象,就讓它的引用計(jì)數(shù)-1
- 可以通過(guò)以下私有函數(shù)來(lái)查看自動(dòng)釋放池的情況
extern void _objc_autoreleasePoolPrint(void);
copy和mutableCopy
引用計(jì)數(shù)的存儲(chǔ)
-
在64bit中,引用計(jì)數(shù)可以直接存儲(chǔ)在優(yōu)化過(guò)的isa指針中,也可能存儲(chǔ)在SideTable類(lèi)中
- refcnts是一個(gè)存放著對(duì)象引用計(jì)數(shù)的散列表
dealloc
當(dāng)一個(gè)對(duì)象要釋放時(shí),會(huì)自動(dòng)調(diào)用dealloc,接下的調(diào)用軌跡是
- dealloc
- _objc_rootDealloc
- rootDealloc
- object_dispose
- objc_destructInstance、free
自動(dòng)釋放池
自動(dòng)釋放池
- 自動(dòng)釋放池的主要底層數(shù)據(jù)結(jié)構(gòu)是:__AtAutoreleasePool、AutoreleasePoolPage
- 調(diào)用了autorelease的對(duì)象最終都是通過(guò)AutoreleasePoolPage對(duì)象來(lái)管理的
- 源碼分析
- clang重寫(xiě)@autoreleasepool
-
objc4源碼:NSObject.mm
AutoreleasePoolPage的結(jié)構(gòu)
- 每個(gè)AutoreleasePoolPage對(duì)象占用4096字節(jié)內(nèi)存,除了用來(lái)存放它內(nèi)部的成員變量,剩下的空間用來(lái)存放autorelease對(duì)象的地址
-
所有的AutoreleasePoolPage對(duì)象通過(guò)雙向鏈表的形式連接在一起
- 調(diào)用push方法會(huì)將一個(gè)POOL_BOUNDARY入棧,并且返回其存放的內(nèi)存地址
- 調(diào)用pop方法時(shí)傳入一個(gè)POOL_BOUNDARY的內(nèi)存地址,會(huì)從最后一個(gè)入棧的對(duì)象開(kāi)始發(fā)送release消息,直到遇到這個(gè)POOL_BOUNDARY
- id *next指向了下一個(gè)能存放autorelease對(duì)象地址的區(qū)域
Runloop和Autorelease
iOS在主線程的Runloop中注冊(cè)了2個(gè)Observer
- 第1個(gè)Observer監(jiān)聽(tīng)了kCFRunLoopEntry事件,會(huì)調(diào)用objc_autoreleasePoolPush()
- 第2個(gè)Observer
- 監(jiān)聽(tīng)了kCFRunLoopBeforeWaiting事件,會(huì)調(diào)用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
- 監(jiān)聽(tīng)了kCFRunLoopBeforeExit事件,會(huì)調(diào)用objc_autoreleasePoolPop()