Runloop 本質(zhì)是什么?
本質(zhì)是一個(gè)OC對(duì)象,內(nèi)部也有isa指針。
Runloop 結(jié)構(gòu)分析
結(jié)構(gòu)內(nèi)部有2個(gè)重要的成員變量
Runloop 運(yùn)行模式
Runloop 事件處理流程
Runloop 牽涉概念
自動(dòng)釋放池
內(nèi)存管理
定時(shí)器
線程包活
卡頓檢測(cè)
OC對(duì)象本質(zhì)
KVC與kvo
KVC
什么是kvc,
KVC,俗稱“鍵值編碼”,全稱是“Key Value Coding”,它是一種可以直接通過字符串的名稱(Key)來訪問類屬性的機(jī)制,而不是通過調(diào)用Setter或者Getter方法來進(jìn)行訪問。
kvc 能干什么?
setValue:forkey 給對(duì)象的屬性賦值,但是層級(jí)只有一層。
setValue:forkeyPath,支持一級(jí)屬性賦值,也支持多級(jí)屬性賦值
利用kvc給對(duì)象的成員變量賦值。
kvc 的實(shí)現(xiàn)原理(kvc的賦值,取值流程)
賦值流程
1 依次查找方法看看有沒有實(shí)現(xiàn)。setKey:、_setKey,如果找到了方法,則傳參并且調(diào)用方法。
2,如果沒有找到方法,則通過accessInstanceVariablesDirectly一個(gè)方法,查看是否能直接放問成員變量。能則給成員變量賦值,如果不能則拋出異常。
取值流程
1,依次查找?guī)讉€(gè)方法是否實(shí)現(xiàn)。_key,_isKey,key
,isKey。如果實(shí)現(xiàn)就調(diào)用取值。
2,如果沒有實(shí)現(xiàn),則通過一個(gè)方法,查看是否能直接訪問成員變量。如果能則獲取成員變量的值,如果不能則報(bào)錯(cuò)。
KVC 相關(guān)問題
KVC 是否線程安全問題
可能會(huì)引出:kvc是否線程安全問題,以及自己設(shè)計(jì)kvo(主要是模型思路和上面幾個(gè)點(diǎn)的判斷和處理)
5.線程安全 (自旋鎖,遞歸鎖,互斥鎖,信號(hào)量,讀寫鎖,柵欄函數(shù)) 底層都是pthread_mutex的封裝
線程安全
內(nèi)存管理
內(nèi)存管理的三種方案,
iOS中主要通過引用計(jì)數(shù)來管理內(nèi)存,當(dāng)引用計(jì)數(shù)為0的時(shí)候銷毀內(nèi)存。
蘋果一共提供了3中方案,(TaggetPointer、NONPOINTER_ISA、散列表),
TaggetPointer
Tagged Pointer是專??來存儲(chǔ)?的對(duì)象,例如NSNumber,NSDat
Tagged Pointer指針的值不再是地址了,?是真正的值。所以,實(shí)際上它不再是?個(gè)對(duì)象了,它只是?個(gè)披著對(duì)象?的普通變量?已。所以,它的內(nèi)存并不存儲(chǔ) 在堆中,也不需要?jiǎng)?chuàng)建和釋放
NONPOINTER_ISA
一切對(duì)象均為objc_object對(duì)象,objc_object對(duì)象內(nèi)部有一個(gè)isa屬性。這個(gè)isa指針之前只是一個(gè)純指針。現(xiàn)在也報(bào)含指針外的其他信息,例如對(duì)象的引用計(jì)數(shù)、是否被弱引用...這時(shí)這個(gè)isa就是NONPOINTER_ISA。
isa是isa_t類型的聯(lián)合體,其內(nèi)部通過位域技術(shù)儲(chǔ)存很多了對(duì)象的信息。
NONPOINTER_ISA 中的引用計(jì)數(shù)存儲(chǔ)位置
/表示該對(duì)象的引用計(jì)數(shù)值,滿了就會(huì)存在sidetable 中/
uintptr_t extra_rc : 19;
復(fù)制代碼
源碼中的extra_rc就是用來存儲(chǔ)引用計(jì)數(shù)的,具體原理放到下面的引用計(jì)數(shù)部分說明。
散列表--引用計(jì)數(shù)&弱引用計(jì)數(shù)
散列表的結(jié)構(gòu)
系統(tǒng)維護(hù)了一張全局的Hash表,里面存了一張張SideTable散列表,而這個(gè)散列表中就儲(chǔ)存了對(duì)象的引用計(jì)數(shù)以及弱引用情況。
struct SideTable {
spinlock_t slock;//鎖,用于控制數(shù)據(jù)訪問安全
RefcountMap refcnts;//引用計(jì)數(shù)表s
weak_table_t weak_table;//弱引用計(jì)數(shù)表s
...
...
};
復(fù)制代碼
spinlock_t slock 鎖,用于控制這張散列表SideTble的數(shù)據(jù)訪問安全。
RefcountMap refcnts:引用計(jì)數(shù)表RefcountMap,用于儲(chǔ)存對(duì)象的引用計(jì)數(shù)情況,(在isa_t中extra_rc位引用計(jì)數(shù)滿了后會(huì)把一半的引用計(jì)數(shù)放到某個(gè)散列表SideTable中的引用計(jì)數(shù)表中)
weak_table_t weak_table:弱引用情況表,用于儲(chǔ)存對(duì)象的弱引用情況。
weak_table_t 弱引用計(jì)數(shù)表
1,weak_table_t 是個(gè)二維數(shù)組,里面包含了一個(gè)個(gè)weak_table,weak_table里面是一個(gè)個(gè)weak_entry數(shù)組
2, 當(dāng)一個(gè)對(duì)象的屬性被設(shè)置成weak時(shí),weak_table表中會(huì)查找當(dāng)內(nèi)部有沒有該對(duì)象的弱引用數(shù)組(weak_entry數(shù)組),如果有就直接插入這個(gè)屬性到這個(gè)weak_entry數(shù)組,沒有就先創(chuàng)建weak_entry數(shù)組再插入
3,、當(dāng)對(duì)象被釋放時(shí)delloc,會(huì)通過對(duì)象指針去查找weak_table沒有該對(duì)象的weak_entry數(shù)組,有的話遍歷weak_entry數(shù)組,將內(nèi)部的屬性置為nil;最后將這個(gè)weak_entry數(shù)組remov
為什么是 weak_entry數(shù)組 (因?yàn)橐粋€(gè)對(duì)象可能擁有多個(gè)弱應(yīng)用屬性)
散列表
檢測(cè)內(nèi)存管理的方式
1,xcode 自帶一個(gè)靜態(tài)內(nèi)存分析和Instrument
2,工具,MLeaksFinder:精準(zhǔn) iOS 內(nèi)存泄露檢測(cè)工具
內(nèi)存泄漏主要有兩種方式
Laek Memory 這種是忘記 Release 操作所泄露的內(nèi)存。
Abandon Memory 這種是循環(huán)引用,無法釋放掉的內(nèi)存。
MLeaksFinder 實(shí)現(xiàn)的原理
1,不入侵開發(fā)代碼
這里使用了 AOP 技術(shù),hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法,關(guān)于如何 hook,請(qǐng)參考 Method Swizzling。
2, 實(shí)現(xiàn)原理
為基類 NSObject 添加一個(gè)方法 -willDealloc 方法
該方法的作用是,先用一個(gè)弱指針指向 self,并在一小段時(shí)間(3秒)后,通過這個(gè)弱指針調(diào)用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中斷言.
如果已經(jīng)釋放那么空指針不會(huì)調(diào)用斷言函數(shù),如果沒有釋放舊會(huì)斷點(diǎn)。
空指針是指向nil(調(diào)用方法無反應(yīng)),野指針是指向垃圾內(nèi)存(危險(xiǎn))
內(nèi)存泄漏
內(nèi)存泄漏的解決方案
定時(shí)器,block 代理的循環(huán)引用。
1,用weak修飾,
2,nstimer 可以考慮添加中間層
ARC和MRC如何管理內(nèi)存
arc 依靠llvm編譯器和Runtime 實(shí)現(xiàn)
llvm 干了什么
加上,release,retain,autoRlease 操作
當(dāng)我們編譯源碼的時(shí)候,編譯器會(huì)分析源碼中每個(gè)對(duì)象的生命周期,然后基于這些對(duì)象的生命周期,來添加相應(yīng)的引用計(jì)數(shù)操作代碼。所以,ARC 是工作在編譯期的一種技術(shù)方案,這樣的好處是:
編譯之后,ARC 與非 ARC 代碼是沒有什么差別的,所以二者可以在源碼中共存。實(shí)際上,你可以通過編譯參數(shù) -fno-objc-arc 來關(guān)閉部分源代碼的 ARC 特性。
runtime 干了什么
運(yùn)行時(shí),清空弱引用的對(duì)象。
用一張哈希表,key 為弱引用的對(duì)象地址。values為數(shù)組,里面存放指針。
當(dāng)弱引用的對(duì)象被釋放時(shí),會(huì)將數(shù)組里面的指針全部置為nil。
自動(dòng)釋放池
@autoreleasepool{}關(guān)鍵字通過編譯器轉(zhuǎn)換成objc_autoreleasePoolPush和objc_autoreleasePoolPop這一對(duì)方法。 將自動(dòng)釋放池中的對(duì)象加到自動(dòng)釋放池中。
由objc_autoreleasePoolPush作為自動(dòng)釋放池作用域的第一個(gè)函數(shù)。
使用objc_autorelease將對(duì)象加入自動(dòng)釋放池。
由objc_autoreleasePoolPop作為自動(dòng)釋放池作用域的最后一個(gè)函數(shù)。
自動(dòng)釋放池的結(jié)構(gòu)
自動(dòng)釋放池都是由一個(gè)或者多個(gè)AutoreleasePoolPage組成,page的 SIZE 為 4096 bytes ,它們通過parent和child指針組成一個(gè)雙向鏈表。
hotPage:是當(dāng)前正在使用的page,操作都是在hotPage上完成,一般處于鏈表末端或者倒數(shù)第二個(gè)位置。存儲(chǔ)在 TLS 中,可以理解為一個(gè)每個(gè)線程共享一個(gè)自動(dòng)釋放池鏈表。
coldPage:位于鏈表頭部的page,可能同時(shí)為hotPage。
release 和autoRelease 方法
release 會(huì)立即釋放,見與set方法中先retain后release
autoRelease 則會(huì)等到自動(dòng)釋放池結(jié)束的時(shí)候釋放,見與類方法創(chuàng)建對(duì)象的時(shí)候。
[NSMutableArarry arry]
autorelease pool的釋放時(shí)機(jī)
MRC下調(diào)用自動(dòng)釋放池release方法后,會(huì)對(duì)在autorelease對(duì)象進(jìn)行釋放,因此,此后訪問的person變量為野指針,再去訪問自然會(huì)導(dǎo)致crash。
而ARC下,@autoreleasepool并不會(huì)立即在結(jié)束括號(hào)符后,立即釋放person變量,而是會(huì)在一個(gè)合適的時(shí)間點(diǎn)。
合適的時(shí)間點(diǎn)。因此,當(dāng)runloop進(jìn)入kCFRunLoopEntry時(shí),自動(dòng)釋放池會(huì)進(jìn)行push操作,當(dāng)runloop進(jìn)入kCFRunLoopBeforeWaiting | kCFRunLoopExit狀態(tài)時(shí),自動(dòng)釋放池會(huì)進(jìn)行pop操作。
自動(dòng)釋放池的應(yīng)用
autorelease pool和RunLoop(運(yùn)行循環(huán))
主線程中,系統(tǒng)已經(jīng)在main.m中通過@autoreleasepool創(chuàng)建了自動(dòng)釋放池,所以我們無需額外去創(chuàng)建和釋放了.
子線程中自動(dòng)釋放池的創(chuàng)建和釋放都無需我們進(jìn)行額外的操作。當(dāng)然,在某些場(chǎng)景下,也可以手動(dòng)通過@autoreleasepool進(jìn)行創(chuàng)建和釋放。
autorelease pool和降低內(nèi)存峰值
當(dāng)被加到自動(dòng)釋放池的對(duì)象越來越來多,卻沒有得到及時(shí)釋放,就會(huì)導(dǎo)致內(nèi)存溢出。這個(gè)時(shí)候,我們可以手動(dòng)添加自動(dòng)釋放池來解決這個(gè)問題。
block
block 是什么?
block 是帶有自動(dòng)變量的匿名函數(shù)。
block 的本質(zhì)
blcok的本質(zhì)是OC對(duì)象,其結(jié)構(gòu)體內(nèi)部也帶有isa指針。
block的變量捕獲機(jī)制
局部變量(捕獲值,)存放在block內(nèi)部一個(gè)同名的成員變量中.
靜態(tài)變量(靜態(tài)變量的地址捕獲到block中),存放在block內(nèi)部一個(gè)同名的成員變量中
當(dāng)訪問全局變量時(shí),因?yàn)槿肿兞渴且恢贝嬖?,不?huì)銷毀,所以在block中直接訪問全局變量,不需要進(jìn)行捕獲。
block 的類型 (全局,堆,棧)
2,block的類型,有沒訪問auto變量,區(qū)分是全局的還是??臻g的
3,??臻g類型的block 執(zhí)行copy操作,變成堆空間的block .
如何改變block內(nèi)捕獲的變量值(__block的用法)
__block 原理
在block內(nèi)部來修改外部變量的值,當(dāng)然,__block只能用來修飾auto變量,不能用來修飾全局變量和靜態(tài)變量。
__block修飾的auto變量,編譯器會(huì)將此變量封裝成一個(gè)結(jié)構(gòu)體(其實(shí)也是一個(gè)對(duì)象),結(jié)構(gòu)體內(nèi)部有以下幾個(gè)成員變量 isa,val(使用的外部變量,如果是基本數(shù)據(jù)類型,就是變量的值,如果是對(duì)象類型,就是指向?qū)ο蟮闹羔?
如果是修飾對(duì)象類型的auto變量,那么生成的結(jié)構(gòu)體中會(huì)多出copy和dispose兩個(gè)函數(shù),用來管理person對(duì)象的內(nèi)存。
block循環(huán)引用帶來的問題。如何解決
如果block作為一個(gè)對(duì)象的屬性,并且在block中也使用到了這個(gè)對(duì)象,則會(huì)產(chǎn)生循環(huán)引用,導(dǎo)致block和對(duì)象相互引用,無法釋放。
用weak 修飾block 內(nèi)用捕獲的對(duì)象。
性能優(yōu)化
事件響應(yīng)傳遞鏈
尋找 響應(yīng)者(iOS響應(yīng)者鏈)
觸碰屏幕時(shí),系統(tǒng)會(huì)把這一操作封裝成一個(gè)UIEvent放到事件隊(duì)列里。然后application從事件隊(duì)列中取出這個(gè)事件。接著尋找響應(yīng)這個(gè)事件的最佳視圖。此時(shí)用到2個(gè)重要的方法。
(void) hitTest (返回視圖層級(jí)中能響應(yīng)觸控點(diǎn)最深的視圖)
(Bool) pointInside (返回視圖中是否包含響應(yīng)的點(diǎn))
尋找響應(yīng)者結(jié)論
1,尋找事件的最佳響應(yīng)視圖是通過hittest和pointInside完成的。
2,hittest的調(diào)用順序是從UIWindow開始的,對(duì)每個(gè)視圖的子視圖一次調(diào)用。子視圖的調(diào)用順序是從后面往前,也可以說是顯示從上面到下面。
3,遍歷直到找到響應(yīng)視圖,然后逐級(jí)返回到UIWindow返回此視圖。
處理者
卡頓現(xiàn)象的解決和原理
組件化開發(fā)
mach-o
埋點(diǎn)
啟動(dòng)流程
app的啟動(dòng)流程分為2種(冷啟動(dòng)和熱啟動(dòng))
app 的啟動(dòng)階段
dyld
runtime
main