Runloop 本質是什麼?

Runloop 本質是什麼?
本質是一個OC對象,內部也有isa指針。
Runloop 結構分析
結構內部有2個重要的成員變量

Runloop 運行模式
Runloop 事件處理流程
Runloop 牽涉概念
自動釋放池
內存管理
定時器
線程包活
卡頓檢測
OC對象本質

KVC與kvo
KVC
什麼是kvc,
KVC,俗稱“鍵值編碼”,全稱是“Key Value Coding”,它是一種可以直接通過字符串的名稱(Key)來訪問類屬性的機制,而不是通過調用Setter或者Getter方法來進行訪問。
kvc 能幹什麼?

setValue:forkey 給對象的屬性賦值,但是層級只有一層。
setValue:forkeyPath,支持一級屬性賦值,也支持多級屬性賦值
利用kvc給對象的成員變量賦值。

kvc 的實現原理(kvc的賦值,取值流程)
賦值流程
1 依次查找方法看看有沒有實現。setKey:、_setKey,如果找到了方法,則傳參並且調用方法。
2,如果沒有找到方法,則通過accessInstanceVariablesDirectly一個方法,查看是否能直接放問成員變量。能則給成員變量賦值,如果不能則拋出異常。
取值流程
1,依次查找幾個方法是否實現。_key,_isKey,key
,isKey。如果實現就調用取值。
2,如果沒有實現,則通過一個方法,查看是否能直接訪問成員變量。如果能則獲取成員變量的值,如果不能則報錯。
KVC 相關問題
KVC 是否線程安全問題
可能會引出:kvc是否線程安全問題,以及自己設計kvo(主要是模型思路和上面幾個點的判斷和處理)
5.線程安全 (自旋鎖,遞歸鎖,互斥鎖,信號量,讀寫鎖,柵欄函數) 底層都是pthread_mutex的封裝
線程安全
內存管理
內存管理的三種方案,
iOS中主要通過引用計數來管理內存,當引用計數爲0的時候銷燬內存。
蘋果一共提供了3中方案,(TaggetPointer、NONPOINTER_ISA、散列表),
TaggetPointer

Tagged Pointer是專⻔⽤來存儲⼩的對象,例如NSNumber,NSDat
Tagged Pointer指針的值不再是地址了,⽽是真正的值。所以,實際上它不再是⼀個對象了,它只是⼀個披着對象⽪的普通變量⽽已。所以,它的內存並不存儲 在堆中,也不需要創建和釋放

NONPOINTER_ISA
一切對象均爲objc_object對象,objc_object對象內部有一個isa屬性。這個isa指針之前只是一個純指針。現在也報含指針外的其他信息,例如對象的引用計數、是否被弱引用...這時這個isa就是NONPOINTER_ISA。
isa是isa_t類型的聯合體,其內部通過位域技術儲存很多了對象的信息。
NONPOINTER_ISA 中的引用計數存儲位置
/表示該對象的引用計數值,滿了就會存在sidetable 中/
uintptr_t extra_rc : 19;
複製代碼
源碼中的extra_rc就是用來存儲引用計數的,具體原理放到下面的引用計數部分說明。
散列表--引用計數&弱引用計數
散列表的結構
系統維護了一張全局的Hash表,裏面存了一張張SideTable散列表,而這個散列表中就儲存了對象的引用計數以及弱引用情況。
struct SideTable {
spinlock_t slock;//鎖,用於控制數據訪問安全
RefcountMap refcnts;//引用計數表s
weak_table_t weak_table;//弱引用計數表s
...
...
};

複製代碼

spinlock_t slock 鎖,用於控制這張散列表SideTble的數據訪問安全。

RefcountMap refcnts:引用計數表RefcountMap,用於儲存對象的引用計數情況,(在isa_t中extra_rc位引用計數滿了後會把一半的引用計數放到某個散列表SideTable中的引用計數表中)

weak_table_t weak_table:弱引用情況表,用於儲存對象的弱引用情況。

weak_table_t 弱引用計數表
1,weak_table_t 是個二維數組,裏面包含了一個個weak_table,weak_table裏面是一個個weak_entry數組
2, 當一個對象的屬性被設置成weak時,weak_table表中會查找當內部有沒有該對象的弱引用數組(weak_entry數組),如果有就直接插入這個屬性到這個weak_entry數組,沒有就先創建weak_entry數組再插入
3,、當對象被釋放時delloc,會通過對象指針去查找weak_table沒有該對象的weak_entry數組,有的話遍歷weak_entry數組,將內部的屬性置爲nil;最後將這個weak_entry數組remov

爲什麼是 weak_entry數組 (因爲一個對象可能擁有多個弱應用屬性)

散列表
檢測內存管理的方式
1,xcode 自帶一個靜態內存分析和Instrument
2,工具,MLeaksFinder:精準 iOS 內存泄露檢測工具
內存泄漏主要有兩種方式

Laek Memory 這種是忘記 Release 操作所泄露的內存。
Abandon Memory 這種是循環引用,無法釋放掉的內存。

MLeaksFinder 實現的原理
1,不入侵開發代碼
這裏使用了 AOP 技術,hook 掉 UIViewController 和 UINavigationController 的 pop 跟 dismiss 方法,關於如何 hook,請參考 Method Swizzling。
2, 實現原理

爲基類 NSObject 添加一個方法 -willDealloc 方法
該方法的作用是,先用一個弱指針指向 self,並在一小段時間(3秒)後,通過這個弱指針調用 -assertNotDealloc,而 -assertNotDealloc 主要作用是直接中斷言.

如果已經釋放那麼空指針不會調用斷言函數,如果沒有釋放舊會斷點。
空指針是指向nil(調用方法無反應),野指針是指向垃圾內存(危險)
內存泄漏
內存泄漏的解決方案
定時器,block 代理的循環引用。
1,用weak修飾,
2,nstimer 可以考慮添加中間層
ARC和MRC如何管理內存
arc 依靠llvm編譯器和Runtime 實現
llvm 幹了什麼

加上,release,retain,autoRlease 操作

當我們編譯源碼的時候,編譯器會分析源碼中每個對象的生命週期,然後基於這些對象的生命週期,來添加相應的引用計數操作代碼。所以,ARC 是工作在編譯期的一種技術方案,這樣的好處是:
編譯之後,ARC 與非 ARC 代碼是沒有什麼差別的,所以二者可以在源碼中共存。實際上,你可以通過編譯參數 -fno-objc-arc 來關閉部分源代碼的 ARC 特性。
runtime 幹了什麼
運行時,清空弱引用的對象。
用一張哈希表,key 爲弱引用的對象地址。values爲數組,裏面存放指針。
當弱引用的對象被釋放時,會將數組裏面的指針全部置爲nil。
自動釋放池
@autoreleasepool{}關鍵字通過編譯器轉換成objc_autoreleasePoolPush和objc_autoreleasePoolPop這一對方法。 將自動釋放池中的對象加到自動釋放池中。

由objc_autoreleasePoolPush作爲自動釋放池作用域的第一個函數。
使用objc_autorelease將對象加入自動釋放池。
由objc_autoreleasePoolPop作爲自動釋放池作用域的最後一個函數。

自動釋放池的結構
自動釋放池都是由一個或者多個AutoreleasePoolPage組成,page的 SIZE 爲 4096 bytes ,它們通過parent和child指針組成一個雙向鏈表。

hotPage:是當前正在使用的page,操作都是在hotPage上完成,一般處於鏈表末端或者倒數第二個位置。存儲在 TLS 中,可以理解爲一個每個線程共享一個自動釋放池鏈表。
coldPage:位於鏈表頭部的page,可能同時爲hotPage。

release 和autoRelease 方法
release 會立即釋放,見與set方法中先retain後release
autoRelease 則會等到自動釋放池結束的時候釋放,見與類方法創建對象的時候。
[NSMutableArarry arry]
autorelease pool的釋放時機

MRC下調用自動釋放池release方法後,會對在autorelease對象進行釋放,因此,此後訪問的person變量爲野指針,再去訪問自然會導致crash。

而ARC下,@autoreleasepool並不會立即在結束括號符後,立即釋放person變量,而是會在一個合適的時間點。

合適的時間點。因此,當runloop進入kCFRunLoopEntry時,自動釋放池會進行push操作,當runloop進入kCFRunLoopBeforeWaiting | kCFRunLoopExit狀態時,自動釋放池會進行pop操作。

自動釋放池的應用
autorelease pool和RunLoop(運行循環)

主線程中,系統已經在main.m中通過@autoreleasepool創建了自動釋放池,所以我們無需額外去創建和釋放了.

子線程中自動釋放池的創建和釋放都無需我們進行額外的操作。當然,在某些場景下,也可以手動通過@autoreleasepool進行創建和釋放。

autorelease pool和降低內存峯值
當被加到自動釋放池的對象越來越來多,卻沒有得到及時釋放,就會導致內存溢出。這個時候,我們可以手動添加自動釋放池來解決這個問題。
block
block 是什麼?
block 是帶有自動變量的匿名函數。
block 的本質
blcok的本質是OC對象,其結構體內部也帶有isa指針。
block的變量捕獲機制
局部變量(捕獲值,)存放在block內部一個同名的成員變量中.
靜態變量(靜態變量的地址捕獲到block中),存放在block內部一個同名的成員變量中
當訪問全局變量時,因爲全局變量是一直存在,不會銷燬,所以在block中直接訪問全局變量,不需要進行捕獲。
block 的類型 (全局,堆,棧)
2,block的類型,有沒訪問auto變量,區分是全局的還是棧空間的
3,棧空間類型的block 執行copy操作,變成堆空間的block .
如何改變block內捕獲的變量值(__block的用法)
__block 原理
在block內部來修改外部變量的值,當然,__block只能用來修飾auto變量,不能用來修飾全局變量和靜態變量。
__block修飾的auto變量,編譯器會將此變量封裝成一個結構體(其實也是一個對象),結構體內部有以下幾個成員變量 isa,val(使用的外部變量,如果是基本數據類型,就是變量的值,如果是對象類型,就是指向對象的指針)
如果是修飾對象類型的auto變量,那麼生成的結構體中會多出copy和dispose兩個函數,用來管理person對象的內存。
block循環引用帶來的問題。如何解決
如果block作爲一個對象的屬性,並且在block中也使用到了這個對象,則會產生循環引用,導致block和對象相互引用,無法釋放。
用weak 修飾block 內用捕獲的對象。
性能優化
事件響應傳遞鏈
尋找 響應者(iOS響應者鏈)
觸碰屏幕時,系統會把這一操作封裝成一個UIEvent放到事件隊列裏。然後application從事件隊列中取出這個事件。接着尋找響應這個事件的最佳視圖。此時用到2個重要的方法。

(void) hitTest (返回視圖層級中能響應觸控點最深的視圖)
(Bool) pointInside (返回視圖中是否包含響應的點)

尋找響應者結論
1,尋找事件的最佳響應視圖是通過hittest和pointInside完成的。
2,hittest的調用順序是從UIWindow開始的,對每個視圖的子視圖一次調用。子視圖的調用順序是從後面往前,也可以說是顯示從上面到下面。
3,遍歷直到找到響應視圖,然後逐級返回到UIWindow返回此視圖。
處理者
卡頓現象的解決和原理
組件化開發
mach-o
埋點
啓動流程
app的啓動流程分爲2種(冷啓動和熱啓動)
app 的啓動階段

dyld
runtime
main

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章