說(shuō)在前面:
許久沒(méi)更新,最近整理就文件的時(shí)候,突然翻到兩年前換工作時(shí)整理的思維導(dǎo)圖,包含了原理八股文,網(wǎng)絡(luò),算法,以及架構(gòu),重構(gòu)解決方案等,基本上面試必問(wèn)的一些項(xiàng)目以及原理都包含在內(nèi)了。當(dāng)然了這里整理的很多內(nèi)容都只是幫助當(dāng)時(shí)的我回憶知識(shí)脈絡(luò),并沒(méi)有深入說(shuō)明,還需要讀者自己去查閱資料深入理解。希望能也幫助到最近準(zhǔn)備換工作的小伙伴梳理匯總知識(shí)點(diǎn)。思維導(dǎo)圖高清原圖下載地址
歡迎關(guān)注一下我的 Github: https://github.com/CYXiang
后續(xù)會(huì)更新一些AI、Flutter等相關(guān)文章。

iOS原生相關(guān)
Objective-C 底層原理
-
OC對(duì)象
-
NSObject本質(zhì)
- NSObject底層是結(jié)構(gòu)體,有一個(gè) Class isa 指針
- 創(chuàng)建一個(gè)NSObject對(duì)象系統(tǒng)分配16字節(jié)(至少16),只使用了8字節(jié)(用于存放isa),objc源碼
-
復(fù)雜對(duì)象本質(zhì)
-
包含父類(lèi)成員結(jié)構(gòu)+子類(lèi)成員屬性
- 內(nèi)存對(duì)齊,結(jié)構(gòu)體的大小必須使最大成員變量的倍數(shù)
-
-
屬性和方法
- 實(shí)例對(duì)象存放成員變量,不存放方法(存一份在類(lèi)對(duì)象就夠了)
-
內(nèi)存分配注意點(diǎn)
- 內(nèi)存分配方式內(nèi)存對(duì)齊、桶、堆內(nèi)存、16的倍數(shù)。目的是內(nèi)存優(yōu)化(gnu 內(nèi)存對(duì)齊)<sizeof(a) 是個(gè)運(yùn)算符,編譯就確定了,不是函數(shù)>
-
對(duì)象分類(lèi)三類(lèi)
-
instance 實(shí)例對(duì)象
- isa指針、成員變量值
-
class 類(lèi)對(duì)象
- isa指針、superclass指針、類(lèi)的屬性信息(@property)、類(lèi)的對(duì)象方法信息(instance method)、類(lèi)的協(xié)議信息(protocol)、類(lèi)的成員變量信息(ivar)
-
meta-class 元類(lèi)對(duì)象
- object_getclass([NSObject class]);
- 與class類(lèi)對(duì)象結(jié)構(gòu)體一樣
- isa指針、superclass指針、類(lèi)的類(lèi)方法信息(class method)
-
-
isa指向哪里?
- instance的isa指向class、class的isa指向meta-class、meta-class的isa指向基類(lèi)的meta-class
- 調(diào)用對(duì)象方法軌跡:isa找到class,方法不存在,就通過(guò)superclass找父類(lèi)的方法,再不存在就找基類(lèi)..
-
superclass指向哪里?
- class的superclass指向父類(lèi)的class(如果沒(méi)有父類(lèi),superclass指針為null)、meta-class的superclass指向父類(lèi)的meta-class(基類(lèi)的meta-class的superclass指向基類(lèi)的class)
- 調(diào)用類(lèi)方法軌跡:isa找meta-class,方法不存在,就通過(guò)superclass找父類(lèi)
-
OC的類(lèi)信息存放在哪里?
- 對(duì)象方法、屬性、成員變量、協(xié)議信息,存放在class對(duì)象中
- 類(lèi)方法,存放在meta-class對(duì)象中
- 成員變量的具體值存放在instance對(duì)象中
-
-
KVO
-
本質(zhì)是什么?
- 利用Runtime動(dòng)態(tài)生成一個(gè)子類(lèi),讓instance對(duì)象的isa指向這個(gè)子類(lèi)NSKVONotifying_XXX
- 當(dāng)修改instance對(duì)象的屬性時(shí),會(huì)調(diào)用Foundation的_NSSetXXXValueAndNotify函數(shù)
- _NSSetXXXValueAndNotify函數(shù)內(nèi)部觸發(fā)監(jiān)聽(tīng)器的監(jiān)聽(tīng)方法
-
如何手動(dòng)觸發(fā)KVO?
- 手動(dòng)調(diào)用willChangeValueForKey: 和 didChangeValueForKey:
-
-
KVC
-
1、KVC賦值會(huì)觸發(fā)KVO嗎?
- 會(huì)!
-
賦值原理
- 賦值順序:①-(void)setKey; ②-(void)_setKey; 查看accessInstanceVariablesDirectly> ③_key; ④_isKey; ⑤key; ⑥isKey
-
取值原理
- 取值順序:① getKey、key、 isKey、_key方法 ②查看accessInstanceVariablesDirectly ③按照
_key、_isKey、key、isKey順序查找成員變量
- 取值順序:① getKey、key、 isKey、_key方法 ②查看accessInstanceVariablesDirectly ③按照
-
-
Cateogry
通過(guò)runtime動(dòng)態(tài)將分類(lèi)合并到類(lèi)/元類(lèi)對(duì)象中
-
實(shí)現(xiàn)原理
- 1、通過(guò)Runtime加載某個(gè)類(lèi)所有的Category數(shù)據(jù)
- 2、把所有Category的方法、屬性、協(xié)議數(shù)據(jù),合并到一個(gè)大數(shù)組中(后面參與編譯的Category數(shù)據(jù),會(huì)在數(shù)組的前面)
- 3、將合并后的分類(lèi)數(shù)據(jù)(方法、屬性、協(xié)議),插入到類(lèi)原來(lái)的數(shù)據(jù)前面
-
Category與Class Extension的區(qū)別?
- Class Extension在編譯時(shí)數(shù)據(jù)就包含在類(lèi)信息中
- Category是在運(yùn)行時(shí)才會(huì)將數(shù)據(jù)合并到類(lèi)信息中
-
+load方法
在runtime加載類(lèi)、分類(lèi)時(shí)調(diào)用,在程序運(yùn)行過(guò)程中只調(diào)用一次
-
調(diào)用順序
- 1、先調(diào)用類(lèi)的+load。(按照編譯先后順序調(diào)用,先編譯先調(diào)用。調(diào)用子類(lèi)的+load之前會(huì)先調(diào)用父類(lèi)的+load)
- 2、在調(diào)用分類(lèi)的+load。(按照編譯先后順序調(diào)用,先編譯先調(diào)用)
-
+initialize方法
+initialize方法會(huì)在類(lèi)第一次接收到消息時(shí)調(diào)用
-
調(diào)用順序
- 先調(diào)用父類(lèi)的+initialize,再調(diào)用子類(lèi)的+initialize(先初始化父類(lèi),再初始化子類(lèi),每個(gè)類(lèi)只會(huì)初始化一次)
-
關(guān)聯(lián)對(duì)象
-
原理
- 1、關(guān)聯(lián)對(duì)象并不是存儲(chǔ)在關(guān)聯(lián)對(duì)象本身內(nèi)存中
- 2、關(guān)聯(lián)對(duì)象存儲(chǔ)在全局統(tǒng)一的一個(gè)AssociationManage中
- 3、設(shè)置關(guān)聯(lián)對(duì)象為null,就是移除關(guān)聯(lián)對(duì)象
-
-
block
-
原理/本質(zhì)
- 本質(zhì)是OC對(duì)象,內(nèi)部也有個(gè)isa指針
- 封裝了函數(shù)調(diào)用以及函數(shù)調(diào)用環(huán)境的OC對(duì)象
-
block的類(lèi)型(三種,內(nèi)存存儲(chǔ)區(qū)域不同)
-
NSGlobalBlock
- 數(shù)據(jù)區(qū)域Data區(qū)(與全局變量一起)
-
NSStackBlock
- 棧(需要手動(dòng)銷(xiāo)毀)
-
NSMallocBlock
- 堆(自動(dòng)銷(xiāo)毀)
-
-
block的變量捕獲(capture)
-
為了保證block內(nèi)部能夠正常訪問(wèn)外部變量,有個(gè)變量捕獲機(jī)制
-
局部變量(捕獲?。?/p>
- auto類(lèi)型(自動(dòng)變量,離開(kāi)作用域就銷(xiāo)毀):值傳遞
- static類(lèi)型(還能訪問(wèn)內(nèi)存):指針傳遞
-
全局變量(不捕獲?。?/p>
- 直接訪問(wèn)
-
-
-
__block修飾符
-
用于解決修改block內(nèi)部無(wú)法修改auto變量值的問(wèn)題(不能修飾全局變量、靜態(tài)變量 static)
- 編譯器會(huì)把__block變量包裝成一個(gè)對(duì)象
-
-
__block內(nèi)存管理
1、當(dāng)block在棧上時(shí),并不會(huì)對(duì)__block變量產(chǎn)生強(qiáng)引用
-
2、當(dāng)block被copy到堆時(shí)
- 會(huì)調(diào)用block內(nèi)部的copy函數(shù)
- copy函數(shù)內(nèi)部會(huì)調(diào)用_Block_object_assign函數(shù)
- _Block_object_assign函數(shù)會(huì)對(duì)__block變量形成強(qiáng)引用(retain)
block循環(huán)引用問(wèn)題?
-
-
Runtime
-
objc_msgSend執(zhí)行流程三大階段
-
1、消息發(fā)送
- 先找自身緩存,自身緩存找不到找父類(lèi)方法緩存找,找不到在父類(lèi)方法列表查找,以此類(lèi)推,找到就緩存到自身緩存中。找不到進(jìn)入階段2 ↓
-
2、動(dòng)態(tài)方法解析
- ①調(diào)用+resolvenInstanceMethod: 或者+resolvenClassMethod: 方法來(lái)動(dòng)態(tài)解析 ,進(jìn)行動(dòng)態(tài)添加方法
- ②標(biāo)記為已經(jīng)動(dòng)態(tài)解析 YES
- ③回到階段1、消息發(fā)送,因?yàn)橐呀?jīng)動(dòng)態(tài)加了方法且已標(biāo)記為YES(如果沒(méi)有添加,進(jìn)入階段3)
-
3、消息轉(zhuǎn)發(fā)
①調(diào)用(id)forwordingTargetForSelector:(SEL)aSelector 返回轉(zhuǎn)發(fā)對(duì)象,(返回nil就走下一步)
-
②-返回方法簽名(返回nil就報(bào)錯(cuò),不為空就→)
- 不為空調(diào)用 -(void)forwardInvocation:(NSInvocation *)anInvocation
-
-
-
RunLoop
-
三種模式
NSDefalultRunLoopMode
UITrackingRunLoopMode
-
NSRunLoopCommonMode
- 并不是一個(gè)真的模式
-
運(yùn)行邏輯
- 1、通知Observers:進(jìn)入Loop;通知Observers:即將處理Timers;通知Observers: 即將處理Sources
- 2、處理Block;處理Source0(可能再次處理Block)
- 3、如果存在Source1,跳轉(zhuǎn)到5
- 4、通知Observers,開(kāi)始休眠(等待消息喚醒)
- 5、通知Observers,結(jié)束休眠(被某個(gè)消息喚醒)①處理Timer ②處理GCD ③處理Source1
- 6、根據(jù)前面的執(zhí)行結(jié)果決定如何操作
-
NSTimer失效問(wèn)題
- NSTimer在默認(rèn)模式下,切換到NSRunLoopCommonMode
-
線程?;?/p>
- 1、添加Source; addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode]
- 2、加while(weakSelf&&!weakSelf.isStopped) 循環(huán), 執(zhí)行currentRunLoop runMode:beforeDate:
-
利用RunLoop監(jiān)控卡頓
-
怎么才算卡頓?
- 1、進(jìn)入睡眠前方法執(zhí)行時(shí)間過(guò)長(zhǎng)而導(dǎo)致無(wú)法進(jìn)入睡眠
- 2、線程喚醒后接收消息時(shí)間過(guò)長(zhǎng)爾無(wú)法進(jìn)入下一步
-
如何監(jiān)控卡頓?
-
關(guān)注兩個(gè)階段(進(jìn)入睡眠之前和喚醒之后的兩個(gè)loop狀態(tài)定義的值)
-
1、kCFRunLoopBeforeSources
- 觸發(fā)Source0回調(diào)
-
2、kCFRunLoopAfterWaiting
- 接收mach_port消息
-
-
-
如何監(jiān)聽(tīng)
- 1、創(chuàng)建一個(gè)CFRunLoopObserverContext觀察者
- 2、將觀察者添加到主線程RunLoop的Common模式下觀察。
- 3、再創(chuàng)建一個(gè)持續(xù)的子線程專(zhuān)門(mén)用來(lái)監(jiān)控主線程的RunLoop狀態(tài)
- 4、一旦發(fā)現(xiàn)進(jìn)入睡眠前的kCFRunLoopBeforeSource狀態(tài)或喚醒后的kCFRunLoopAfterWaiting,在設(shè)置的時(shí)間閾值內(nèi)一直沒(méi)有變化,即可判斷為卡頓,再dump出堆棧信息
-
-
-
多線程
pthread
NSThread
-
GCD
-
同步 sync
- 沒(méi)有開(kāi)啟線程,串行執(zhí)行任務(wù)
-
異步 async
- 并且是并發(fā)隊(duì)列,才會(huì)開(kāi)啟新線程并發(fā)執(zhí)行任務(wù)
-
Semaphore信號(hào)量
- 控制最大并發(fā)數(shù)量,也可用來(lái)線程同步(把信號(hào)量設(shè) 1 )
-
-
死鎖
- ①使用sync函數(shù)
- ②往當(dāng)前串行隊(duì)列中添加任務(wù)
隊(duì)列組
-
NSOperation
- 封裝GCD
-
iOS線程同步方案(加鎖、GCD串行隊(duì)列)
-
OSSpinLock 自旋鎖
- (不安全了不建議使用,優(yōu)先反轉(zhuǎn)問(wèn)題)
-
os_unfair_lock
- OSSpinLock的替代方案
-
pthread_mutex
- pthread_mutex_signal 激活一個(gè)等待該條件的線程
-
dispatch_semphore
- 信號(hào)量
-
dispatch_queue(DISPATCH_QUEUE_SERIAL)
- GCD串行隊(duì)列
-
pthread_mutex 的OC封裝
NSLock
-
NSRecursiveLock
- 遞歸鎖,保證能夠遞歸調(diào)用
NSCondition
-
NSConditionLock
- 帶條件的lock(生產(chǎn)者消費(fèi)者模式)
-
@synchronized
- 性能最差
-
-
讀寫(xiě)安全方案
-
1、多讀單寫(xiě)(異步讀,同步寫(xiě)),用于文件數(shù)據(jù)讀寫(xiě)操作
-
pthread_rwlock 讀寫(xiě)鎖
- 互斥鎖,等待鎖的過(guò)程會(huì)進(jìn)入休眠
-
dispatch_barrier_async 異步柵欄調(diào)用
- 傳入的并發(fā)隊(duì)列必須是dispatch_queue_create創(chuàng)建的(不是就沒(méi)效果)
- 如果傳入的是一個(gè)串行或全局并發(fā)隊(duì)列,那這個(gè)函數(shù)等同于dispatch_async效果
-
-
-
內(nèi)存管理
-
定時(shí)器
- GCD是最準(zhǔn)確的,與RunLoop無(wú)關(guān)
-
內(nèi)存布局
保留內(nèi)存
-
代碼段(__TEXT)
- 編譯后的代碼
-
數(shù)據(jù)段(__DATA)
- 字符串常量
- 已初始化數(shù)據(jù)
- 未初始化數(shù)據(jù)
-
堆(heap)
- 通過(guò)alloc、malloc、calloc等動(dòng)態(tài)分配的空間
-
棧(stack)
- 函數(shù)調(diào)用開(kāi)銷(xiāo),局部變量的開(kāi)銷(xiāo)
內(nèi)核區(qū)
Tagged Pointer
MRC
copy
-
weak 原理
- 將弱引用存入到哈希表內(nèi),當(dāng)對(duì)象銷(xiāo)毀時(shí)就從表中取出弱引用并清除 (運(yùn)行時(shí)操作)
-
ARC 原理
- 利用LLVM+Runtime,LLVM自動(dòng)生成插入retain,release代碼
autoRelease
-
UI層基本原理
-
事件傳遞機(jī)制
- 當(dāng)一個(gè)事件發(fā)生后,事件會(huì)從父控件傳給子控件,也就是說(shuō)由UIApplication -> UIWindow -> UIView -> initial view,以上就是事件的傳遞,也就是尋找最合適的view的過(guò)程
- 事件的傳遞是從上到下(父控件到子控件),事件的響應(yīng)是從下到上(順著響應(yīng)者鏈條向上傳遞:子控件到父控件
- 攔截事件 hitTest:withEvent:
-
UI繪制原理
在layer內(nèi)部會(huì)創(chuàng)建一個(gè)backing store,我們可以理解為CGContextRef上下文。
-
判斷l(xiāng)ayer是否有delegate:
- 如果有delegate,則會(huì)執(zhí)行[layer.delegate drawLayer:inContext](這個(gè)方法的執(zhí)行是在系統(tǒng)內(nèi)部執(zhí)行的),然后在這個(gè)方法中會(huì)調(diào)用view的drawRect:方法,也就是我們重寫(xiě)view的drawRect:方法才會(huì)被調(diào)用到。
- 如果沒(méi)有delegate,會(huì)調(diào)用layer的drawInContext方法,也就是我們可以重寫(xiě)的layer的該方法,此刻會(huì)被調(diào)用到。
最后都由CALayer把繪制完的backing store(可以理解為位圖)提交給GPU。
-
異步繪制原理
-
[UIView setNeedsDisplay]方法的時(shí)候,不會(huì)立馬發(fā)送對(duì)應(yīng)視圖的繪制工作
- 調(diào)用[UIView setNeedsDisplay]后
- 然后會(huì)調(diào)用系統(tǒng)的同名方法[view.layer setNeedsDisplay]方法并在當(dāng)前view上面打上一個(gè)臟標(biāo)記
- 當(dāng)前Runloop將要結(jié)束的時(shí)候才會(huì)調(diào)用[CALyer display]方法,然后進(jìn)入到視圖真正的繪制工作當(dāng)中
-
是否知道異步繪制?如何進(jìn)行異步繪制?
- 基于系統(tǒng)開(kāi)的口子[layer.delegate dispayLayer:]方法
- 并且實(shí)現(xiàn)/遵從了dispayLayer這個(gè)方法,我們就可以進(jìn)行異步繪制
-
-
離屏渲染
-
觸發(fā)離屏渲染的場(chǎng)景
- 采用了光柵化的 layer (layer.shouldRasterize)
- 使用了 mask 的 layer (layer.mask)
- 需要進(jìn)行裁剪的 layer
- 設(shè)置了組透明度為 YES,并且透明度不為 1 的layer
- 高斯模糊
- 添加了投影的 layer (layer.shadow*)
- 繪制了文字的 layer (UILabel, CATextLayer, Core Text 等
使用Instruments的不同工具來(lái)測(cè)試性能
-
LLDB
-
動(dòng)態(tài)調(diào)試
- 子主題 1
Clang + LLVM
-
Clang編譯步驟
-
1、預(yù)處理
- 頭文件替換,宏替換,預(yù)編譯指令替換
-
2、詞法分析
- 輸出token流
-
3、語(yǔ)法分析
- 生成AST抽象語(yǔ)法樹(shù)
-
4、CodeGen
CodeGen 負(fù)責(zé)將語(yǔ)法樹(shù)叢頂至下遍歷,翻譯成LLVM IR
-
生成中間代碼IR,與RunTime橋接
- ARC:分析對(duì)象引用關(guān)系,將objc_storeStrong/objc_storeWeak等ARC代碼插入
- 根據(jù)修飾符strong/weak/copy/atomic合成@property 自動(dòng)實(shí)現(xiàn)的 setter/getter
-
-
LLVM后端
- 優(yōu)化 IR
- LLVM BitCode字節(jié)碼
-
如何使用Clang做靜態(tài)分析
-
OCLint
- 基本覆蓋了具有通用性的規(guī)則,主要包括語(yǔ)法上的基礎(chǔ)規(guī)則、Cocoa 庫(kù)相關(guān)規(guī)則、一些約定俗成的規(guī)則、各種空語(yǔ)句檢查、是否按新語(yǔ)法改寫(xiě)的檢查、命名上長(zhǎng)變量名短變量名檢查、無(wú)用的語(yǔ)句變量和參數(shù)的檢查
-
Clang 靜態(tài)分析器
- scan-build 是用來(lái)運(yùn)行分析器的命令行工具
-
Infer(Facebook 開(kāi)源的、使用 OCaml 語(yǔ)言編寫(xiě))
- 空指針訪問(wèn)
- 資源泄露
- 內(nèi)存泄露
-
iOS簽名機(jī)制
- 保證安裝到用戶(hù)手機(jī)上的APP都是經(jīng)過(guò)Apple官方允許的
- 生成CertificateSigningRequest.certSigningRequest文件
iOS安全
-
代碼混淆
-
源碼的混淆
- 類(lèi)名
- 方法名
- 協(xié)議名
-
字符串加密
越獄相關(guān)
- 越獄檢測(cè)
Swift
性能優(yōu)化
CPU與GPU
-
CPU
- 對(duì)象的創(chuàng)建銷(xiāo)毀、對(duì)象屬性的調(diào)整、布局計(jì)算、文本計(jì)算和排版、圖片的格式轉(zhuǎn)換和解碼、圖像的繪制(Core Graphics)
-
GPU
- 紋理的渲染、
啟動(dòng)優(yōu)化
-
啟動(dòng)速度監(jiān)控
1、定時(shí)抓取主線程上方法調(diào)用堆棧,計(jì)算一段時(shí)間里各個(gè)方法的耗時(shí)(Xcode 工具套件里自帶的 Time Profiler ,采用的就是這種方式)
-
2、對(duì)objc_msgSend方法進(jìn)行hook
- 原理解釋
- 如何使用
-
方案與實(shí)踐
-
iOS冷啟動(dòng)階段思路
-
System Interface
加載主二進(jìn)制、啟動(dòng)dyld、加載動(dòng)態(tài)庫(kù)以及l(fā)ibSystem初始化
- 避免鏈接不使用的框架
- 減少動(dòng)態(tài)庫(kù)的加載
- 減少OC類(lèi),分類(lèi)等
-
runtime Init
執(zhí)行 +load和staic initializer初始化函數(shù)等
- 避免在+load里操作
- 延遲加載+load
- 減少staic initializer(C++ 靜態(tài)全局變量)
UIKit init
-
Application init
實(shí)例化UI Application 和 UI Application Delegate、處理生命周期回調(diào)、首幀渲染直到首頁(yè)渲染完成
- 減少或延遲各種SDK的初始化
-
initial Frame Render
- 減少視圖層級(jí)和視圖數(shù)量
- 懶加載View
- 變AutoLayout為手動(dòng)frame布局等
-
Extended
- 去掉viewDidLoad和viewWillAppear中不必要的邏輯,少做事不做事
-
-
方案
-
低成本高收益方案
- 生命周期延遲
- +load治理
- 動(dòng)態(tài)庫(kù)下線
- 二進(jìn)制重排
- 首頁(yè)預(yù)加載
-
深入優(yōu)化方案
- 動(dòng)態(tài)庫(kù)懶加載
- staic initlializer治理
- 編譯期寫(xiě)入I/O
- 任務(wù)編排
-
-
-
流程規(guī)范與監(jiān)控
-
規(guī)范
- 1、新增或修改任務(wù)要有足夠的理由,必須經(jīng)過(guò)嚴(yán)格的code review
- 2、首頁(yè)渲染完成前不允許監(jiān)聽(tīng)生命周期
- 3、不允許新增+load耗時(shí)方法
- 4、不允許新增C++ initialize
- 5、新增動(dòng)態(tài)庫(kù)必須經(jīng)過(guò)評(píng)估
- 6、任務(wù)項(xiàng)相對(duì)上個(gè)版本有5ms以上的增長(zhǎng)時(shí),必須進(jìn)行修改
監(jiān)控
-
卡頓排查與解決
-
UI界面卡頓優(yōu)化(滑動(dòng)掉幀等)
- 盡量減少CPU GPU資源消耗
崩潰類(lèi)型的卡頓排查(線程卡頓)
耗電排查與解決
-
1、如何獲取電量
- (1)引入 IOPowerSources.h、IOPSKeys.h 和 IOKit
- (2)把 batteryMonitoringEnabled 置為 true
-
2、如何診斷電量問(wèn)題
- (1)通過(guò) task_threads 函數(shù),獲取所有的線程信息數(shù)組 threads以及線程總數(shù) threadCount
- (2)thread_basic_info 里有一個(gè)記錄 CPU 使用百分比的字段 cpu_usage
- (3)遍歷所有線程,去查看是哪個(gè)線程的 CPU 使用百分比過(guò)高。(某個(gè)線程的 CPU 使用率長(zhǎng)時(shí)間都比較高,可能有問(wèn)題)
-
3、如何優(yōu)化電量
-
(1)避免讓 CPU 做多余的事情。對(duì)于大量數(shù)據(jù)的復(fù)雜計(jì)算,應(yīng)該把數(shù)據(jù)傳到服務(wù)器去處理
必須要在 App 內(nèi)處理復(fù)雜數(shù)據(jù)計(jì)算,可以通過(guò) GCD 的 dispatch_block_create_with_qos_class 方法指定隊(duì)列的 Qos 為 QOS_CLASS_UTILITY,將計(jì)算工作放到這個(gè)隊(duì)列的 block 里。在 QOS_CLASS_UTILITY 這種 Qos 模式下,系統(tǒng)針對(duì)大量數(shù)據(jù)的計(jì)算,以及復(fù)雜數(shù)據(jù)處理專(zhuān)門(mén)做了電量?jī)?yōu)化。
-
(2)I/O 操作也是耗電大戶(hù),優(yōu)化I/O操作
業(yè)內(nèi)的普遍做法是,將碎片化的數(shù)據(jù)磁盤(pán)存儲(chǔ)操作延后,先在內(nèi)存中聚合,然后再進(jìn)行磁盤(pán)存儲(chǔ)。碎片化的數(shù)據(jù)進(jìn)行聚合,在內(nèi)存中進(jìn)行存儲(chǔ)的機(jī)制,可以使用系統(tǒng)自帶的 NSCache 來(lái)完成。
NSCache 是線程安全的,NSCache 會(huì)在到達(dá)預(yù)設(shè)緩存空間值時(shí)清理緩存,這時(shí)會(huì)觸發(fā) cache:willEvictObject: 方法的回調(diào),在這個(gè)回調(diào)里就可以對(duì)數(shù)據(jù)進(jìn)行 I/O 操作,達(dá)到將聚合的數(shù)據(jù) I/O 延后的目的。I/O 操作的次數(shù)減少了,對(duì)電量的消耗也就減少了
(3)蘋(píng)果維護(hù)了一個(gè)電量?jī)?yōu)化指南“Energy Efficiency Guide for iOS Apps”
-
包瘦身
官方 App Thinning
-
圖片資源優(yōu)化
-
無(wú)用圖片移除,圖片壓縮
- LSUnusedResources
TinyPng或者ImageOptim、轉(zhuǎn)webp
-
-
代碼瘦身
刪除無(wú)用功能代碼(A/B測(cè)試結(jié)果刪除)
-
源代碼瘦身
-
LinkMap 結(jié)合 Mach-O 找無(wú)用代碼
- AppCode(人工二次確認(rèn))
-
運(yùn)行時(shí)檢查類(lèi)是否真正被使用過(guò)
- 通過(guò)isInitialized,判斷一個(gè)類(lèi)是否初始化過(guò)
-
重復(fù)代碼刪除(Clang插件)
-
可執(zhí)行文件瘦身
- 編譯器優(yōu)化
架構(gòu)設(shè)計(jì)
組件化
-
協(xié)議式
-
協(xié)議式架構(gòu)設(shè)計(jì)主要采用的是協(xié)議式編程的思路
- 在編譯層面使用協(xié)議定義規(guī)范,實(shí)現(xiàn)可在不同地方,從而達(dá)到分布管理和維護(hù)組件的目的
-
缺陷
- 協(xié)議式編程缺少統(tǒng)一調(diào)度層,導(dǎo)致難于集中管理
- 協(xié)議式編程接口定義模式過(guò)于規(guī)范,從而使得架構(gòu)的靈活性不夠高。當(dāng)需要引入一個(gè)新的設(shè)計(jì)模式來(lái)開(kāi)發(fā)時(shí),我們就會(huì)發(fā)現(xiàn)很難融入到當(dāng)前架構(gòu)中,缺乏架構(gòu)的統(tǒng)一性。
-
-
中間者
-
優(yōu)勢(shì)
- 拆分的組件都會(huì)依賴(lài)于中間者,但是組間之間就不存在相互依賴(lài)的關(guān)系
- 其他組件都會(huì)依賴(lài)于這個(gè)中間者,相互間的通信都會(huì)通過(guò)中間者統(tǒng)一調(diào)度
- 在中間者上也能夠輕松添加新的設(shè)計(jì)模式,從而使得架構(gòu)更容易擴(kuò)展
- 中間者架構(gòu)的易管控帶來(lái)的架構(gòu)更穩(wěn)固,易擴(kuò)展帶來(lái)的靈活性
-
實(shí)現(xiàn)方案
-
CTMediator
-
CTMediator 本質(zhì)就是一個(gè)方法,用來(lái)接收 target、action、params,對(duì)于調(diào)用者來(lái)說(shuō)十分不友好
- 通過(guò)響應(yīng)者給 CTMediator 做的 category 或者 extension 發(fā)起調(diào)用
- category 或 extension 以函數(shù)聲明的方式,解決了參數(shù)的問(wèn)題
- 不會(huì)直接依賴(lài) CTMediator 去發(fā)起調(diào)用,而是直接依賴(lài) category Pod 去發(fā)起調(diào)用
解耦的精髓在于業(yè)務(wù)邏輯能夠獨(dú)立出來(lái),并不是形式上的解除編譯上的耦合(編譯上解除耦合只能算是解耦的一種手段而已)。更多的還是需要在功能邏輯和組件劃分上做到同層級(jí)解耦,上下層依賴(lài)清晰
-
-
URLRoutor
-
缺陷
- 本地間調(diào)用無(wú)法傳遞非常規(guī)參數(shù),復(fù)雜參數(shù)的傳遞方式非常丑陋
- 必須要在app啟動(dòng)時(shí)注冊(cè)URL響應(yīng)者
- 新增組件化的調(diào)用路徑時(shí),蘑菇街的操作相對(duì)復(fù)雜
-
-
-
MVC
MVVM
- 雙向綁定(RAC,RSSwift)
設(shè)計(jì)模式
系統(tǒng)化思維
五大設(shè)計(jì)原則
- 單一功能原則:對(duì)象功能要單一,不要在一個(gè)對(duì)象里添加很多功能
- 開(kāi)閉原則:擴(kuò)展是開(kāi)放的,修改是封閉的
- 里氏替換原則:子類(lèi)對(duì)象是可以替代基類(lèi)對(duì)象的
- 接口隔離原則:接口的用途要單一,不要在一個(gè)接口上根據(jù)不同入?yún)?shí)現(xiàn)多個(gè)功能
- 依賴(lài)反轉(zhuǎn)原則:方法應(yīng)該依賴(lài)抽象,不要依賴(lài)實(shí)例。iOS 開(kāi)發(fā)就是高層業(yè)務(wù)方法依賴(lài)于協(xié)議
23種設(shè)計(jì)模式實(shí)現(xiàn)原理
- 子主題 1
網(wǎng)絡(luò)協(xié)議相關(guān)
IP層
TCP/UDP
-
TCP
- TCP是一個(gè)傳輸層協(xié)議,提供端到端(Host-To-Host) 數(shù)據(jù)的可靠傳輸
- 支持全雙工,是一個(gè)連接導(dǎo)向的協(xié)議(面向連接的)
-
UDP
- 目標(biāo)是在傳輸層提供直接發(fā)送報(bào)文(Datagram)的能力
HTTP/HTTPS
-
為什么可以相信一個(gè) HTTPS 網(wǎng)站?
- 當(dāng)用戶(hù)用瀏覽器打開(kāi)一個(gè) HTTPS 網(wǎng)站時(shí),會(huì)到目標(biāo)網(wǎng)站下載目標(biāo)網(wǎng)站的證書(shū)
- 瀏覽器會(huì)去驗(yàn)證證書(shū)上的簽名,一直驗(yàn)證到根證書(shū),如果根證書(shū)被預(yù)裝,那么就會(huì)信任這個(gè)網(wǎng)站
DNS
Socket
-
Socket 是一種編程的模型
- 客戶(hù)端將數(shù)據(jù)發(fā)送給在客戶(hù)端側(cè)的Socket 對(duì)象,然后客戶(hù)端側(cè)的 Socket 對(duì)象將數(shù)據(jù)發(fā)送給服務(wù)端側(cè)的 Socket 對(duì)象
- Socket 對(duì)象負(fù)責(zé)提供通信能力,并處理底層的 TCP 連接/UDP 連接
- 對(duì)服務(wù)端而言,每一個(gè)客戶(hù)端接入,就會(huì)形成一個(gè)和客戶(hù)端對(duì)應(yīng)的 Socket 對(duì)象,如果服務(wù)器要讀取客戶(hù)端發(fā)送的信息,或者向客戶(hù)端發(fā)送信息,就需要通過(guò)這個(gè)客戶(hù)端 Socket 對(duì)象
-
Socket 還是一種雙向管道文件
- 操作系統(tǒng)將客戶(hù)端傳來(lái)的數(shù)據(jù)寫(xiě)入這個(gè)管道,也將線程寫(xiě)入管道的數(shù)據(jù)發(fā)送到客戶(hù)端
算法
數(shù)組&鏈表
堆棧&隊(duì)列
面試題:【判斷字符串括號(hào)是否合法】
-
單調(diào)棧
-
遞增棧
- 小數(shù)消除大數(shù)
-
遞減棧
- 大數(shù)消除小數(shù)
-
-
優(yōu)先隊(duì)列
- 正常進(jìn),安裝優(yōu)先級(jí)出
- 實(shí)現(xiàn)機(jī)制:1、Heap(堆)(Binary、Binomial、Fiboncci)
哈希表
-
Map/Set
- 【有效的字母異位詞】
- 【兩數(shù)之和】
- 【三數(shù)之和】
樹(shù)
-
二叉樹(shù)
-
反轉(zhuǎn)二叉樹(shù)
- 遍歷二叉樹(shù)
-
二叉搜索樹(shù)
字典樹(shù)
遞歸&分治
動(dòng)態(tài)規(guī)劃
- 貪心算法
- 買(mǎi)賣(mài)股票
- 背包問(wèn)題
LRU Cache
Bloom Filter(布隆過(guò)濾器)
斐波那契數(shù)列
Flutter
底層基本實(shí)現(xiàn)
Bloc與響應(yīng)式
容器化&配置化
組件化
性能優(yōu)化與實(shí)踐
安全與密碼學(xué)
單向散列函數(shù)(哈希函數(shù))
- SHA-1、MD5(已不安全)
- SHA-256、SHA-384、SHA-512(目前流行)
加密算法
-
對(duì)稱(chēng)加密
-
序列算法(優(yōu)先使用)
- ChaCha20、AES-256、AES-128
分組算法
-
非對(duì)稱(chēng)加密
亮點(diǎn)與疑難解決
動(dòng)態(tài)化
容器化
配置化
持續(xù)集成
fastlane
RunTime無(wú)埋點(diǎn)方案
產(chǎn)品主要想知道:頁(yè)面進(jìn)入次數(shù)、頁(yè)面停留時(shí)間、點(diǎn)擊事件的埋點(diǎn)(用來(lái)計(jì)算曝光率、轉(zhuǎn)化率)
運(yùn)行時(shí)方法替換方式進(jìn)行埋點(diǎn)(AOP)
- 寫(xiě)一個(gè)運(yùn)行時(shí)方法替換的類(lèi) SMHook
- 利用運(yùn)行時(shí)接口將方法的實(shí)現(xiàn)進(jìn)行了交換,原方法調(diào)用時(shí)就會(huì)被 hook 住,從而去執(zhí)行指定的方法
- 每個(gè) UIViewController 生命周期到了 ViewWillAppear 時(shí)都會(huì)去執(zhí)行 insertToViewWillAppear 方法
事件唯一標(biāo)識(shí)區(qū)分不同埋點(diǎn)
- NSStringFromClass([self class]) 方法來(lái)取類(lèi)名,區(qū)別不同的 UIViewController
- action 選擇器名 NSStringFromSelector(action)” +“視圖類(lèi)名 NSStringFromClass([target class])”組合成一個(gè)唯一的標(biāo)識(shí)
- 通過(guò)視圖的 superview 和 subviews 的屬性,我們就能夠還原出每個(gè)頁(yè)面的視圖樹(shù)
- 復(fù)用機(jī)制 UITableViewCell 用 indexPath
RunLoop運(yùn)行步驟
1、通知 observers:RunLoop 要開(kāi)始進(jìn)入 loop 了。緊接著就進(jìn)入 loop
2、開(kāi)啟一個(gè) do while 來(lái)保活線程。通知 Observers:RunLoop 會(huì)觸發(fā) Timer 回調(diào)、Source0 回調(diào),接著執(zhí)行加入的 block
通知 Observers:RunLoop 的線程將進(jìn)入休眠(sleep)狀態(tài)
4、進(jìn)入休眠后,會(huì)等待 mach_port 的消息,以再次喚醒
5、喚醒時(shí)通知 Observer:RunLoop 的線程剛剛被喚醒了
6、RunLoop 被喚醒后就要開(kāi)始處理消息了
啟動(dòng)優(yōu)化方案
main() 函數(shù)執(zhí)行前
- 加載可執(zhí)行文件(App 的.o 文件的集合)
- 加載動(dòng)態(tài)鏈接庫(kù),進(jìn)行 rebase 指針調(diào)整和 bind 符號(hào)綁定
- Objc 運(yùn)行時(shí)的初始處理,包括 Objc 相關(guān)類(lèi)的注冊(cè)、category 注冊(cè)、selector 唯一性檢查等
- 初始化,包括了執(zhí)行 +load() 方法、attribute((constructor)) 修飾的函數(shù)的調(diào)用、創(chuàng)建 C++ 靜態(tài)全局變量
main() 函數(shù)執(zhí)行后(appDelegate 的 didFinishLaunchingWithOptions 方法里首屏渲染相關(guān)方法執(zhí)行完成)
- 首屏初始化所需配置文件的讀寫(xiě)操作
- 首屏列表大數(shù)據(jù)的讀取
- 首屏渲染的大量計(jì)算等
首屏渲染完成后(加載時(shí)長(zhǎng))
- 減少視圖層級(jí)和視圖數(shù)量
- 懶加載View
- 變AutoLayout為手動(dòng)frame布局等
- 預(yù)加載(緩存首頁(yè),骨架屏等)
- 去掉viewDidLoad和viewWillAppear中不必要的邏輯,少做事不做事
APM系統(tǒng)
啟動(dòng)優(yōu)化、卡頓監(jiān)聽(tīng)、崩潰監(jiān)聽(tīng)、性能監(jiān)控
GNUStep 源碼
LLD鏈接器
鏈接器最主要的作用,就是將符號(hào)綁定到地址上
使用 dyld 加載動(dòng)態(tài)庫(kù)
調(diào)用 +load 方法是通過(guò) runtime 庫(kù)處理的
Injection for Xcode 動(dòng)態(tài)調(diào)試
- 1、Injection 會(huì)監(jiān)聽(tīng)源代碼文件的變化
- 2、如果文件被改動(dòng)了,Injection Server 就會(huì)執(zhí)行 rebuildClass 重新進(jìn)行編譯、打包成動(dòng)態(tài)庫(kù),也就是 .dylib 文件
- 3、編譯、打包成動(dòng)態(tài)庫(kù)后使用 writeSting 方法通過(guò) Socket 通知運(yùn)行的 App
更安全的方法交換庫(kù) Aspects
通過(guò) Runtime 消息轉(zhuǎn)發(fā)機(jī)制來(lái)實(shí)現(xiàn)方法交換的庫(kù)
它將所有的方法調(diào)用都指到 _objc_msgForward 函數(shù)調(diào)用上
按照自己的方式實(shí)現(xiàn)了消息轉(zhuǎn)發(fā),自己處理參數(shù)列表,處理返回值
最后通過(guò) NSInvocation 調(diào)用來(lái)實(shí)現(xiàn)方法交換
事件總線技術(shù) Promise
PromiseKit
通過(guò)簡(jiǎn)單、清晰、規(guī)范的 Promise 接口將異步的數(shù)據(jù)獲取、業(yè)務(wù)邏輯、界面串起來(lái),對(duì)于日后的維護(hù)或重構(gòu)都會(huì)容易很多
Source0與Source1
Source0
- 不能主動(dòng)觸發(fā)事件
- 使用時(shí),你需要先調(diào)用CFRunLoopSourceSignal,將這個(gè)Source標(biāo)記為待處理,然后手動(dòng)調(diào)用CFRunLoopWakeUp來(lái)喚醒RunLoop,讓其處理這個(gè)事件
Source1
- 主動(dòng)觸發(fā)事件。其中它有一個(gè)mach_port_t
TCP 最核心的價(jià)值是提供了可靠性,而 UDP 最核心的價(jià)值是靈活
HTTP 協(xié)議 1.1 和 2.0 都基于 TCP,而到了 HTTP 3.0 就開(kāi)始用 UDP
TCP與UDP區(qū)別
目的差異
- TCP 協(xié)議的核心目標(biāo)是提供可靠的網(wǎng)絡(luò)傳輸
- UDP 的目標(biāo)是在提供報(bào)文交換能力基礎(chǔ)上盡可能地簡(jiǎn)化協(xié)議輕裝上陣
可靠性差異
- TCP 核心是要在保證可靠性提供更好的服務(wù)。TCP 會(huì)有握手的過(guò)程,需要建立連接
- UDP 并不具備以上這些特性,它只管發(fā)送數(shù)據(jù)封包,而且 UDP 不需要 ACK
連接 vs 無(wú)連接
- TCP 是一個(gè)面向連接的協(xié)議,傳輸數(shù)據(jù)必須先建立連接
- UDP 是一個(gè)無(wú)連接協(xié)議,數(shù)據(jù)隨時(shí)都可以發(fā)送,只提供發(fā)送封包(Datagram)的能力
傳輸速度
- UDP 協(xié)議簡(jiǎn)化,封包小,沒(méi)有連接、可靠性檢查等,因此單純從傳輸速度上講,UDP 更快
場(chǎng)景差異
-
TCP 場(chǎng)景
- 遠(yuǎn)程控制(SSH)
File Transfer Protocol(FTP)
郵件(SMTP、IMAP)等
點(diǎn)對(duì)點(diǎn)文件傳出(微信等)
-
UDP 場(chǎng)景
- 網(wǎng)絡(luò)游戲
音視頻傳輸
DNS
Ping
直播
APP如何加載?
iOS系統(tǒng)架構(gòu)
- 用戶(hù)體驗(yàn)層,主要是提供用戶(hù)界面。這一層包含了 SpringBoard、Spotlight、Accessibility
- 第二層是應(yīng)用框架層,是開(kāi)發(fā)者會(huì)用到的。這一層包含了開(kāi)發(fā)框架 Cocoa Touch
- 第三層是核心框架層,是系統(tǒng)核心功能的框架層。這一層包含了各種圖形和媒體核心框架、Metal 等
- 第四層是 Darwin 層,是操作系統(tǒng)的核心,屬于操作系統(tǒng)的內(nèi)核態(tài)。這一層包含了系統(tǒng)內(nèi)核 XNU、驅(qū)動(dòng)等
XNU 怎么加載 App?
- iOS 的可執(zhí)行文件和動(dòng)態(tài)庫(kù)都是 Mach-O 格式,所以加載 APP 實(shí)際上就是加載 Mach-O 文件
- 加載 Mach-O 文件,內(nèi)核會(huì) fork 進(jìn)程,并對(duì)進(jìn)程進(jìn)行一些基本設(shè)置,比如為進(jìn)程分配虛擬內(nèi)存、為進(jìn)程創(chuàng)建主線程、代碼簽名等。用戶(hù)態(tài) dyld 會(huì)對(duì) Mach-O 文件做庫(kù)加載和符號(hào)解析
好架構(gòu)定義
高可用
高性能
易擴(kuò)展
在功能邏輯和組件劃分上做到同層級(jí)解耦,上下層依賴(lài)清晰,這樣的結(jié)構(gòu)才能夠使得上層組件易插拔,下層組件更穩(wěn)固
組件化架構(gòu)
1、業(yè)務(wù)完全解耦,通用功能下沉
組件
2、每個(gè)業(yè)務(wù)都是一個(gè)獨(dú)立的 Git 倉(cāng)庫(kù),每個(gè)業(yè)務(wù)都能夠生成一個(gè) Pod 庫(kù),最后再集成到一起
組件分層
- 底層可以是與業(yè)務(wù)無(wú)關(guān)的基礎(chǔ)組件,比如網(wǎng)絡(luò)和存儲(chǔ)等
- 中間層一般是通用的業(yè)務(wù)組件,比如賬號(hào)、埋點(diǎn)、支付、購(gòu)物車(chē)等
- 最上層是迭代業(yè)務(wù)組件,更新頻率最高
多團(tuán)隊(duì)之間如何分工?
- 基建團(tuán)隊(duì),負(fù)責(zé)業(yè)務(wù)無(wú)關(guān)的基礎(chǔ)功能組件和業(yè)務(wù)相關(guān)通用業(yè)務(wù)組件的開(kāi)發(fā)
- 每個(gè)業(yè)務(wù)都由一個(gè)專(zhuān)門(mén)的團(tuán)隊(duì)來(lái)負(fù)責(zé)開(kāi)發(fā)
- 基建團(tuán)隊(duì)人員應(yīng)該是流動(dòng)的,從業(yè)務(wù)團(tuán)隊(duì)里來(lái),再回到業(yè)務(wù)團(tuán)隊(duì)中去
監(jiān)控崩潰與采集
崩潰類(lèi)型
-
信號(hào)可捕獲到
KVO、數(shù)組越界、返回類(lèi)型不匹配N(xiāo)ULL
-
多線程問(wèn)題
- 在子線程中進(jìn)行 UI 更新可能會(huì)發(fā)生崩潰。多個(gè)線程進(jìn)行數(shù)據(jù)的讀取操作,因?yàn)樘幚頃r(shí)機(jī)不一致,比如有一個(gè)線程在置空數(shù)據(jù)的同時(shí)另一個(gè)線程在讀取這個(gè)數(shù)據(jù),可能會(huì)出現(xiàn)崩潰情況
-
野指針
- 指針指向一個(gè)已刪除的對(duì)象訪問(wèn)內(nèi)存區(qū)域時(shí),會(huì)出現(xiàn)野指針崩潰
-
信號(hào)不可捕獲
-
后臺(tái)任務(wù)超時(shí)
-
iOS 后臺(tái)保活
-
Background Task (3 分鐘)
- 系統(tǒng)提供了 beginBackgroundTaskWithExpirationHandler 方法來(lái)延長(zhǎng)后臺(tái)執(zhí)行時(shí)間,可以解決你退后臺(tái)后還需要一些時(shí)間去處理一些任務(wù)的訴求
-
-
-
主線程卡頓超閾值
-
主線程無(wú)響應(yīng)
- 如果主線程超過(guò)系統(tǒng)規(guī)定的時(shí)間無(wú)響應(yīng),就會(huì)被 Watchdog 殺掉
-
-
內(nèi)存打爆
-
JetSam 機(jī)制
- 操作系統(tǒng)為了控制內(nèi)存資源過(guò)度使用而采用的一種資源管控機(jī)制
-
通過(guò)內(nèi)存警告獲取內(nèi)存限制值
-
didReceiveMemoryWarning
- 強(qiáng)殺掉 App 之前還有 6 秒鐘的時(shí)間
-
-
定位內(nèi)存問(wèn)題信息收集
-
誰(shuí)分配的內(nèi)存?定位到函數(shù)
- 用 fishhook 去 Hook “malloc_logger”函數(shù),分析統(tǒng)計(jì)
-
-
-
監(jiān)控方案
-
第三方的
- Fabric或Bugly
-
第三方開(kāi)源自建服務(wù)器
-
PLCrashReporter
- 第三方開(kāi)源庫(kù)捕獲崩潰日志,然后上傳到自己服務(wù)器上進(jìn)行整體監(jiān)控的
-
A/B測(cè)試方案(SkyLab)
三個(gè)部分
-
策略服務(wù),為策略制定者提供策略
- 決策流程、策略維度
-
A/B 測(cè)試 SDK,集成在客戶(hù)端內(nèi)
-
客戶(hù)端SDK:SkyLab
使用的是 MMKV 保存策略
SkyLab 對(duì)外的調(diào)用接口使用的是 Block ,來(lái)接收版本 A 和 B 的區(qū)別處理。
-
如何做人群測(cè)試桶劃分
- 隨機(jī)分配方式,將分配結(jié)果通過(guò) MMKV 進(jìn)行持續(xù)化存儲(chǔ),確保測(cè)試桶的一致性
-
日志系統(tǒng),負(fù)責(zé)反饋策略結(jié)果供分析人員分析不同策略執(zhí)行的結(jié)果
服務(wù)端返回A/B實(shí)驗(yàn)
性能監(jiān)控
線下性能
- Energy Log 就是用來(lái)監(jiān)控耗電量的
- Leaks 就是專(zhuān)門(mén)用來(lái)監(jiān)控內(nèi)存泄露問(wèn)題的
- Network 就是用來(lái)專(zhuān)門(mén)檢查網(wǎng)絡(luò)情況
- Time Profiler 就是通過(guò)時(shí)間采樣來(lái)分析頁(yè)面卡頓問(wèn)題
線上監(jiān)控(不要侵入到業(yè)務(wù)代碼、采用性能消耗最小的監(jiān)控方案)
-
CPU 使用率的線上監(jiān)控(App 作為進(jìn)程運(yùn)行起來(lái)后會(huì)有多個(gè)線程,每個(gè)線程對(duì) CPU 的使用率不同。各個(gè)線程對(duì) CPU 使用率的總和,就是當(dāng)前 App 對(duì) CPU 的使用率)
- thread_info.h 根據(jù)當(dāng)前 task 獲取所有線程
- 遍歷所有線程來(lái)獲取單個(gè)線程的基本信息
- thread_basic_info 結(jié)構(gòu)體獲取CPU 使用率的字段:cpu_usage
- 累加這個(gè)字段就能夠獲取到當(dāng)前的整體 CPU 使用率
-
FPS 線上監(jiān)控
- 通過(guò)注冊(cè) CADisplayLink 得到屏幕的同步刷新率
- 記錄每次刷新時(shí)間,然后就可以得到 FPS
-
內(nèi)存使用量的線上監(jiān)控
- 內(nèi)存信息存在 task_vm_info
- 類(lèi)似于對(duì) CPU 使用率的監(jiān)控,我們只要從這個(gè)結(jié)構(gòu)體里取出 phys_footprint 字段
啟動(dòng)優(yōu)化監(jiān)控方案
定時(shí)抓取主線程上的方法調(diào)用堆棧,計(jì)算一段時(shí)間里各個(gè)方法的耗時(shí)(Xcode 工具套件里自帶的 Time Profiler ,采用的就是這種方式)
對(duì) objc_msgSend 方法進(jìn)行 hook 來(lái)掌握所有方法的執(zhí)行耗時(shí)
使用RunLoop監(jiān)控卡頓
原因
- 復(fù)雜 UI 、圖文混排的繪制量過(guò)大
- 在主線程上做網(wǎng)絡(luò)同步請(qǐng)求
- 在主線程做大量的 IO 操作
- 運(yùn)算量過(guò)大,CPU 持續(xù)高占用
- 死鎖和主子線程搶鎖
RunLoop基本原理
- 用來(lái)監(jiān)聽(tīng)輸入源,進(jìn)行調(diào)度處理的。這里的輸入源可以是輸入設(shè)備、網(wǎng)絡(luò)、周期性或者延遲時(shí)間、異步回調(diào)
- RunLoop 會(huì)接收兩種類(lèi)型的輸入源:一種是來(lái)自另一個(gè)線程或者來(lái)自不同應(yīng)用的異步消息;另一種是來(lái)自預(yù)訂時(shí)間或者重復(fù)間隔的同步事件
- 當(dāng)有事件要去處理時(shí)保持線程忙,當(dāng)沒(méi)有事件要處理時(shí)讓線程進(jìn)入休眠