[toc]
主要是一些視頻筆記和面試時候常問到的問題記錄。(持續(xù)更新)
Runtime
什么是 Runtime?它的作用是什么?
Runtime 是 Objective-C 的運行時系統(tǒng),它包含一系列的 API,允許在運行時創(chuàng)建類、調(diào)用方法、訪問屬性等。其作用是實現(xiàn)動態(tài)消息傳遞和運行時類型識別。
消息傳遞、消息轉(zhuǎn)發(fā)
消息傳遞
objc_msgSend(obj, SEL/@selector(aMethod)/);
1、從消息緩存列表里面通過哈希表查找對應(yīng)的方法(哈希沖突怎么辦?應(yīng)該是通過再哈希的方式解決的)
2、在當(dāng)前類方法列表中查找
對于已排序好的列表,采用二分查找算法查找方法對應(yīng)執(zhí)行函數(shù)
對于沒有排序的列表,采用一般遍歷查找方法對應(yīng)執(zhí)行函數(shù)
類方法是沒有排序的,所以是使用遍歷查找方法
3、從父類逐級查找
判斷父類是否是nil
有父類
在緩存中查找
緩存中無,則從父類方法列表中查找,直至父類為nil,進入消息轉(zhuǎn)發(fā)流程
消息轉(zhuǎn)發(fā)
當(dāng)一個對象接收到無法解讀的消息時,Runtime 會調(diào)用消息轉(zhuǎn)發(fā)機制。這包括三個步驟:動態(tài)方法解析、備用接收者和完整轉(zhuǎn)發(fā)。開發(fā)者可以通過重載 resolveInstanceMethod和 forwardInvocation 方法來自定義消息的處理過程。
resolveInstanceMethod:
(resolveClassMethod:)
為類添加一個方法,返回YES
forwardingTargetForSelector:
返回一個其他對象去處理這個消息(備用receiver)
forwardInvocation:
如果上面兩種情況沒有執(zhí)行,就會執(zhí)行通過forwardInvocation進行消息轉(zhuǎn)發(fā)
方法替換(Method-Swizzling)
Method Swizzing是發(fā)生在運行時的,在運行時將一個方法的實現(xiàn)替換成另一個方法的實現(xiàn);
每個類都維護著一個方法列表,即methodList,methodList中有不同的方法,每個方法中包含了方法的SEL和IMP,方法交換就是將原本的SEL和IMP對應(yīng)斷開,并將SEL和新的IMP生成對應(yīng)關(guān)系;
RunLoop
什么是RunLoop?
RunLoop是通過內(nèi)部維護的事件循環(huán)對消息/事件進行管理的對象
**事件循環(huán)(Event Loop)
沒有消息需要處理的時候,休眠以避免資源占用;【用戶態(tài)】->【內(nèi)核態(tài)】
有消息需要處理的時候,立刻被喚醒【內(nèi)核態(tài)】->【用戶態(tài)】
RunLoop的數(shù)據(jù)結(jié)構(gòu)
Runloop和線程是一一對應(yīng)的

主線程的runloop自動啟動,而子線程的runloop需要手動啟動
Timer與RunLoop的面試題
問題:定時器有個RunLoop mode,默認(rèn)是在defaultMode,scrollView滾動的時候,主線程的RunLoop會轉(zhuǎn)到UITrackingRunLoopMode,這時候定時器就會失效
解決:將定時器添加到CommonMode上
思考:為什么?
NSRunloopCommonModes
- CommonMode不是實際存在的一種Mode
- 是同步source/Timer/Observer到多個mode的一種技術(shù)解決方案
Block
這篇文章講的挺透徹
iOS-Block本質(zhì)
什么是Block?
- Block是將
函數(shù)及執(zhí)行上下文封裝起來的對象
block的幾種形式?
堆block(__NSMallocBlock__),
棧block(__NSStackBlock__),使用外部變量并且未進行copy操作的block是棧block
全局block(__NSGlobalBlock__)不使用外部變量的block是全局block
block變量截獲?
- 局部變量(截獲其值)
基本數(shù)據(jù)類型
對象類型(連同所有權(quán)修飾符一同截獲) - 靜態(tài)局部變量(指針形式截獲)
- 全局變量(不截獲)
- 靜態(tài)全局變量(不截獲)
一般block會在棧區(qū),經(jīng)過copy之后,會拷貝到堆區(qū),棧區(qū)的block的__forwarding指針指向拷貝后的堆區(qū)的block,而堆區(qū)的__forwarding指針會指向自己
為什么要用__block修飾局部變量?
__block修飾之后的局部變量實際變成了一個結(jié)構(gòu)體,它內(nèi)部有一個isa指針,這個結(jié)構(gòu)體會被block捕獲,成為其成員變量;block內(nèi)部修改的時候,實際是通過這個結(jié)構(gòu)體的isa指針去修改所修飾的局部變量的值的
弱引用管理
如何添加一個weak變量到弱引用表
一個被聲明為__weak的對象指針,經(jīng)過編譯器編譯之后,調(diào)用objc_initweak(),經(jīng)過一些列的函數(shù)調(diào)用(storeWeak()),最終在weak_register_no_lock()函數(shù)中進行弱引用變量的添加;具體添加的位置是通過哈希算法進行位置查找,如果說查找對應(yīng)位置當(dāng)中已經(jīng)有當(dāng)前對象對應(yīng)的弱引用數(shù)組,那么就把新的弱引用變量添加到這個數(shù)組當(dāng)中,如果沒有,重新創(chuàng)建一個弱引用數(shù)組,然后第0個位置添加上最新的weak指針,后面的都初始化為0或者nil。
weak如何置nil
當(dāng)一個對象被dealloc之后,在dealloc的內(nèi)部實現(xiàn)當(dāng)中,會調(diào)用弱引用清除的相關(guān)函數(shù)weak_clear_no_lock(),在這個函數(shù)內(nèi)部實現(xiàn)當(dāng)中會根據(jù) 當(dāng)前對象指針 查找弱引用表,把當(dāng)前對象相對應(yīng)的弱引用(數(shù)組)都拿出來,遍歷數(shù)組當(dāng)中所有的弱引用指針,置為nil。
weak自動置nil的原理(簡書1,做參考)
runtime維護著一個weak表即hash表,用于存儲指向?qū)ο蟮膚eak指針
Weak表是Hash表,Key是所指對象的地址,Value是Weak指針地址的數(shù)組
以對象的地址作為key,去找weak指針
觸發(fā)調(diào)用arr_clear_deallocating 函數(shù) ,根據(jù)對象的地址將所有weak指針地址的數(shù)組,遍歷數(shù)組把其中的數(shù)據(jù)置為nil。
weak自動置nil的原理(簡書2,做參考)
一 、實現(xiàn)
runtime在注冊類時,會布局一個weak表(hash表),key是所指對象的地址,value是weak指針的地址的數(shù)組;當(dāng)對象釋放時,層層調(diào)用后,通過arr_clear_deallocating釋放;
二、weak實現(xiàn)原理步驟:通過clang可以分析源碼;
objc_initWeak//初始化weak;
objc_storeWeak()//修更新指針指向,創(chuàng)建對應(yīng)的弱引用表;
clearDeallocating//通過key找到weak數(shù)組,然后對數(shù)組里的weak指針置nil,把這個entry(入口,記錄)從weak表刪除;
自動釋放池問題
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *array = [NSMutableArray array];
NSLog(@"%@",array);
}
Q: array在什么時候釋放?
A: 在當(dāng)前RunLoop將要結(jié)束的時候調(diào)用AutoreleasePoolPage::pop()來對其進行釋放。
(實際上在每一次的RunLoop循環(huán)當(dāng)中都會在將要結(jié)束的時候?qū)η耙淮蝿?chuàng)建的AutoreleasePool進行pop操作,同時會push進來一個新的AutoreleasePool)
問題拓展:
要回答這個問題需要知道RunLoop和AutoReleasePool的關(guān)系。
Runloop每次循環(huán)都是被一個AutoReleasePool包圍著的,具體說每次Runloop循環(huán)將要結(jié)束的時候會釋放當(dāng)前runloop的內(nèi)存占用。再創(chuàng)建好一個AutoReleasePool給下一次Runloop循環(huán)使用。(慕課網(wǎng)6-7)
ViewDidLoad是在主線程執(zhí)行,在該方法中創(chuàng)建的array會加入到當(dāng)次RunLoop的AutoReleasePool中,array會在當(dāng)前RunLoop將要結(jié)束的時候得到內(nèi)存釋放。
一般錯誤的回答都是viewDidLoad方法結(jié)束就釋放了。
AutoreleasePool原理?
數(shù)據(jù)結(jié)構(gòu):是以棧為節(jié)點,通過雙向鏈表的形式組合而成。和線程是一一對應(yīng)的。
objc_autoreleasePoolPush()
objc_autoreleasePoolPop()
objc_autorelease()
AutoreleasePool為什么可以嵌套調(diào)用?
A:多層嵌套就是多次插入哨兵對象
AutoreleasePool使用場景?
在for循環(huán)中alloc圖片數(shù)據(jù)等內(nèi)存消耗較大的場景手動插入autoreleasePool
組件化
組件化的好處?
- 業(yè)務(wù)分層、解耦,使代碼變得可維護;
- 有效的拆分、組織日益龐大的工程代碼,使工程目錄變得可維護;
- 便于各業(yè)務(wù)功能拆分、抽離,實現(xiàn)真正的功能復(fù)用;
- 業(yè)務(wù)隔離,跨團隊開發(fā)代碼控制和版本風(fēng)險控制的實現(xiàn);
- 模塊化對代碼的封裝性、合理性都有一定的要求,提升開發(fā)同學(xué)的設(shè)計能力;
- 在維護好各級組件的情況下,隨意組合滿足不同客戶需求;(只需要將之前的多個業(yè)務(wù)組件模塊在新的主App中進行組裝即可快速迭代出下一個全新App)
如何實現(xiàn)解耦?
-
分層
基礎(chǔ)功能組件:按功能分庫,不涉及產(chǎn)品業(yè)務(wù)需求,跟庫Library類似,通過良好的接口供上層業(yè)務(wù)組件調(diào)用;不寫入產(chǎn)品定制邏輯,通過擴展接口完成定制;
(網(wǎng)絡(luò)組件、彈框組件、工具組件、)
基礎(chǔ)UI組件:各個業(yè)務(wù)模塊依賴使用,但需要保持好定制擴展的設(shè)計業(yè)務(wù)組件:業(yè)務(wù)功能間相對獨立,相互間沒有Model共享的依賴;業(yè)務(wù)之間的頁面調(diào)用只能通過UIBus進行跳轉(zhuǎn);業(yè)務(wù)之間的邏輯Action調(diào)用只能通過服務(wù)提供;
中間件:target-action,url-block,protocol-class
http://www.itdecent.cn/p/464a8f1ab949
CTMeditor
- 通過反射機制利用字符串找到相對應(yīng)的target然后向它發(fā)送消息
AvoidCrash
Foundation框架潛在的崩潰的危險比如:
- 將 nil 插入可變數(shù)組中會導(dǎo)致崩潰。
- 數(shù)組越界會導(dǎo)致崩潰。
- 根據(jù)key給字典某個元素重新賦值時,若key為 nil 會導(dǎo)致崩潰。
- ......
利用runtime的特性,使用方法替換,在即將發(fā)生崩潰的位置給它替換成默認(rèn)實現(xiàn),防止崩潰,同時上報這個錯誤到bugly
攔截所有檢測崩潰類型
捕獲到異常之后的處理(其實就是獲取出現(xiàn)異常的堆棧,最后以通知的形式發(fā)送出去)
/**
* 提示崩潰的信息(控制臺輸出、通知)
*
* @param exception 捕獲到的異常
* @param defaultToDo 這個框架里默認(rèn)的做法
*/
+ (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo {
//堆棧數(shù)據(jù)
NSArray *callStackSymbolsArr = [NSThread callStackSymbols];
//獲取在哪個類的哪個方法中實例化的數(shù)組 字符串格式 -[類名 方法名] 或者 +[類名 方法名]
NSString *mainCallStackSymbolMsg = [AvoidCrash getMainCallStackSymbolMessageWithCallStackSymbols:callStackSymbolsArr];
if (mainCallStackSymbolMsg == nil) {
mainCallStackSymbolMsg = @"崩潰方法定位失敗,請您查看函數(shù)調(diào)用棧來排查錯誤原因";
}
NSString *errorName = exception.name;
NSString *errorReason = exception.reason;
//errorReason 可能為 -[__NSCFConstantString avoidCrashCharacterAtIndex:]: Range or index out of bounds
//將avoidCrash去掉
errorReason = [errorReason stringByReplacingOccurrencesOfString:@"avoidCrash" withString:@""];
NSString *errorPlace = [NSString stringWithFormat:@"Error Place:%@",mainCallStackSymbolMsg];
NSString *logErrorMessage = [NSString stringWithFormat:@"\n\n%@\n\n%@\n%@\n%@\n%@",AvoidCrashSeparatorWithFlag, errorName, errorReason, errorPlace, defaultToDo];
logErrorMessage = [NSString stringWithFormat:@"%@\n\n%@\n\n",logErrorMessage,AvoidCrashSeparator];
AvoidCrashLog(@"%@",logErrorMessage);
//請忽略下面的賦值,目的只是為了能順利上傳到cocoapods
logErrorMessage = logErrorMessage;
NSDictionary *errorInfoDic = @{
key_errorName : errorName,
key_errorReason : errorReason,
key_errorPlace : errorPlace,
key_defaultToDo : defaultToDo,
key_exception : exception,
key_callStackSymbols : callStackSymbolsArr
};
//將錯誤信息放在字典里,用通知的形式發(fā)送出去
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AvoidCrashNotification object:nil userInfo:errorInfoDic];
});
}
多線程相關(guān)問題
參考:http://www.itdecent.cn/p/361e8a0a4e7e
- iOS中的多線程
. NSThread
. GCD
. NSOperationQueue
NSThread - 輕量級別的多線程技術(shù),需要我們自己管理線程
需要我們手動開辟子線程,如果使用init初始化方式則需要手動啟動,如果使用構(gòu)造器方式初始化則會自動啟動。
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(testThread:) object:@"我是參數(shù)"];
// 當(dāng)使用初始化方法出來的主線程需要start啟動
[thread start];
// 可以為開辟的子線程起名字
thread.name = @"NSThread線程";
// 調(diào)整Thread的權(quán)限 線程權(quán)限的范圍值為0 ~ 1 。越大權(quán)限越高,先執(zhí)行的概率就會越高,由于是概率,所以并不能很準(zhǔn)確的的實現(xiàn)我們想要的執(zhí)行順序,默認(rèn)值是0.5
thread.threadPriority = 1;
// 取消當(dāng)前已經(jīng)啟動的線程
[thread cancel];
// 通過遍歷構(gòu)造器開辟子線程
[NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"構(gòu)造器方式"];
performSelector:withObject:afterDelay:會在內(nèi)部創(chuàng)建一個NSTimer,然后添加到當(dāng)前的RunLoop中,如果當(dāng)前線程沒有開啟RunLoop(子線程默認(rèn)沒有開啟RunLoop),該方法會失效
[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];
performSelector:withObject:沒有添加timer,所以不需要添加子線程RunLoop也可以執(zhí)行
GCD對比NSOperationQueue
GCD是面向底層的C語言的API,NSOpertaionQueue用GCD構(gòu)建封裝的,是GCD的高級抽象。
它們的區(qū)別
- GCD執(zhí)行效率更高,而且由于隊列中執(zhí)行的是由block構(gòu)成的任務(wù),是一個輕量級的數(shù)據(jù)結(jié)構(gòu),寫起來更方便
- GCD只支持FIFO的隊列,而NSOperationQueue可以通過設(shè)置最大并發(fā)數(shù),設(shè)置優(yōu)先級,添加依賴關(guān)系等調(diào)整執(zhí)行順序
- NSOperationQueue甚至可以跨隊列設(shè)置依賴關(guān)系,但是GCD只能通過設(shè)置串行隊列,或者在隊列內(nèi)添加barrier(dispatch_barrier_async)任務(wù),才能控制執(zhí)行順序
- NSOperationQueue因為面向?qū)ο?,所以支持KVO,可以檢測operation是否正在執(zhí)行(isExecuted)、是否結(jié)束(isFinished)、是否取消(isCanceld)
探討
實際項目開發(fā)中,很多時候只是會用到異步操作,不會有特別復(fù)雜的線程關(guān)系管理,所以蘋果推崇的且優(yōu)化完善、運行快速的GCD是首選 如果考慮異步操作之間的事務(wù)性,順序行,依賴關(guān)系,比如多線程并發(fā)下載,GCD需要自己寫更多的代碼來實現(xiàn),而NSOperationQueue已經(jīng)內(nèi)建了這些支持 不論是GCD還是NSOperationQueue,我們接觸的都是任務(wù)和隊列,都沒有直接接觸到線程,事實上線程管理也的確不需要我們操心,系統(tǒng)對于線程的創(chuàng)建,調(diào)度管理和釋放都做得很好。而NSThread需要我們自己去管理線程的生命周期,還要考慮線程同步、加鎖問題,造成一些性能上的開銷
Q:假設(shè)有這么場景:有網(wǎng)絡(luò)請求A、網(wǎng)絡(luò)請求B,需要AB執(zhí)行完之后繼續(xù)進行下一步操作,怎么使用GCD實現(xiàn)?
A:
- 信號量(dispatch_semaphore)
- (void)GCD_Semaphore {
dispatch_semaphore_t sem = dispatch_semaphore_create(0);
NSLog(@"1");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"task1, %@",[NSThread currentThread]);
sleep(1);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"2");
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSLog(@"task2, %@",[NSThread currentThread]);
sleep(1);
dispatch_semaphore_signal(sem);
});
dispatch_semaphore_wait(sem, DISPATCH_TIME_FOREVER);
NSLog(@"3, %@",[NSThread currentThread]);
}
打印結(jié)果
2021-01-05 23:15:33 1
2021-01-05 23:15:33 task1, <NSThread: 0x600000eecd00>{number = 3, name = (null)}
2021-01-05 23:15:34 2
2021-01-05 23:15:34 task2, <NSThread: 0x600000eecd00>{number = 3, name = (null)}
2021-01-05 23:15:35 3, <NSThread: 0x600000eb01c0>{number = 1, name = main}
這里的打印結(jié)果是1->task1->2->task2->3順序執(zhí)行,相當(dāng)于加鎖?
- dispatch_group(基于dispatch_semaphore實現(xiàn)的)
- (void)GCD_Group {
dispatch_group_t group = dispatch_group_create();
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"task1");
dispatch_group_leave(group);
});
dispatch_group_enter(group);
dispatch_group_async(group, queue, ^{
NSLog(@"task2");
dispatch_group_leave(group);
});
dispatch_group_notify(group, queue, ^{
NSLog(@"notify");
});
NSLog(@"===");
}
- dispatch_barrier_async(同時也可以用來實現(xiàn)多讀單寫、加鎖、設(shè)置最大線程數(shù))
- (void)GCD_barrier {
dispatch_queue_t queue = dispatch_queue_create("barrier_queue", DISPATCH_QUEUE_CONCURRENT);
// 注意dispatch_barrier_async只在自己創(chuàng)建的并發(fā)隊列中才有效,在global_queue,串行隊列上效果跟dispatch_(a)sync一樣
// dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
dispatch_async(queue, ^{
NSLog(@"task1");
});
dispatch_async(queue, ^{
NSLog(@"task2");
});
dispatch_barrier_async(queue, ^{
NSLog(@"barrier");
});
NSLog(@"===");
dispatch_async(queue, ^{
NSLog(@"task3");
});
dispatch_async(queue, ^{
NSLog(@"task4");
});
}
多讀單寫
- (id)dataForKey:(NSString *)key {
__block id data;
//同步讀取指定數(shù)據(jù)
dispatch_sync(self.concurrentQueue, ^{
data = [self.dict objectForKey:key];
});
return data;
}
- (void)setData:(id)data forKey:(NSString *)key {
// 異步柵欄調(diào)用設(shè)置數(shù)據(jù)
dispatch_barrier_async(self.concurrentQueue, ^{
[self.dict setObject:data forKey:key];
});
}
單例模式
這篇文章介紹的還不錯
http://www.itdecent.cn/p/a92c0283f243
什么是單例模式?
簡單來說,一個單例類,在整個程序中只有一個實例,并且提供了類方法供全局調(diào)用,在編譯時初始化這個類,然后一直保存在內(nèi)存中,直到App退出時由系統(tǒng)自動釋放這一部分內(nèi)存
系統(tǒng)為我們提供的單例類有哪些?
- UIApplication(應(yīng)用程序?qū)嵗?
- NSNotificationCenter(消息中心類)
- NSFileManager(文件管理類)
- NSUserDefaults(應(yīng)用程序設(shè)置)
- NSURLCache(請求緩存類)
- NSHTTPCookieStorage(應(yīng)用程序cookies池)
單例的存放位置
全局區(qū)
變量的存放位置
| 位置 | 存放的變量 |
|---|---|
| 棧 | 臨時變量(由編譯器管理自動創(chuàng)建/分配/釋放的,棧中的內(nèi)存被調(diào)用時處于存儲空間中,調(diào)用完畢后由系統(tǒng)系統(tǒng)自動釋放內(nèi)存) |
| 堆 | 通過alloc、calloc、malloc或new申請內(nèi)存,由開發(fā)者手動在調(diào)用之后通過free或delete釋放內(nèi)存。動態(tài)內(nèi)存的生存期可以由我們決定,如果我們不釋放內(nèi)存,程序?qū)⒃谧詈蟛裴尫诺魟討B(tài)內(nèi)存,在ARC模式下,由系統(tǒng)自動管理。 |
| 全局區(qū)域 | 靜態(tài)變量(編譯時分配,APP結(jié)束時由系統(tǒng)釋放) |
| 常量 | 常量(編譯時分配,APP結(jié)束時由系統(tǒng)釋放) |
| 代碼區(qū) | 存放代碼 |
創(chuàng)建一個單例的方式
- 同步鎖:NSLock
- @synchronized(self) {}
- 信號量 dispatch_semaphore_t
- 條件鎖 NSConditionLock
- dispatch_once_t
單例注意事項-保證單例只被初始化一次
- 對alloc、new、copy、mutableCopy的處理
因為alloc] init 和 new都是調(diào)用的+ (instancetype)allocWithZone:(struct _NSZone *)zone方法,那么我們可以重寫這個方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
NSLog(@"allocWithZone");
@synchronized (self) {
if (instance == nil) {
instance = [super allocWithZone:zone];
return instance;
}
}
return nil;// 這里返回nil,那么后面初始化的對象就是nil了,返回instance的話其實就是同一個單例對象了
}
- 直接禁用對應(yīng)的方法
+(instancetype) new __attribute__((unavailable("OneTimeClass類只能初始化一次")));
-(instancetype) copy __attribute__((unavailable("OneTimeClass類只能初始化一次")));
-(instancetype) mutableCopy __attribute__((unavailable("OneTimeClass類只能初始化一次")));
(用NS_UNAVAILABLE也可以,但是這個就沒有提示了)
還要了解一下FMDB
NSMutableArray數(shù)據(jù)結(jié)構(gòu)分析
普通C數(shù)組是是一段能被方便讀寫的連續(xù)內(nèi)存空間,使用一段線性內(nèi)存空間的一個最明顯的缺點是,在下標(biāo)0插入一個元素時,需要移動其它元素,即memmove的原理:
https://blog.csdn.net/qq_27909209/article/details/82689322

移除元素時同理也要移動其它元素;
當(dāng)數(shù)組非常大的時候可能就會出現(xiàn)問題。
NSMutableArray是一個類簇,[NSMutableArray new]實際返回的是__NSArrayM
(lldb) po [[ NSMutableArray new] class]
__NSArrayM
__NSArrayM使用了環(huán)形緩沖區(qū) (circular buffer),這個數(shù)據(jù)結(jié)構(gòu)相當(dāng)簡單,只是比常規(guī)數(shù)組或緩沖區(qū)復(fù)雜點。環(huán)形緩沖區(qū)的內(nèi)容能在到達任意一端時繞向另一端。
環(huán)形緩沖區(qū)有一些非??岬膶傩?。尤其是,除非緩沖區(qū)滿了,否則在任意一端插入或刪除均不會要求移動任何內(nèi)存。我們來分析這個類如何充分利用環(huán)形緩沖區(qū)來使得自身比 C 數(shù)組強大得多。我們在這里知道了幾個有趣的東西:在刪除的時候不會清除指針。最有意思的一點,如果我們在中間進行插入或者刪除,只會移動最少的一邊的元素。
NSDictionary數(shù)據(jù)結(jié)構(gòu)
在內(nèi)部,字典使用哈希表來組織其存儲,并在給定相應(yīng)鍵的情況下快速訪問值
Crash類型
- Signal
- NSException
bugly需要使用符號表解析應(yīng)該是用了捕捉了Signal異常,Signal異常是需要配合符號表才能解析的,NSException的話可以直接拿到崩潰信息
關(guān)于RunLoop防止崩潰
https://cloud.tencent.com/developer/article/1192474
這還有一篇文章可以參考(關(guān)于Crash收集)·
http://www.cocoachina.com/articles/12301
圖像顯示原理
CPU生成位圖(bitmap)經(jīng)由總線在合適的時機傳給GPU;GPU拿到位圖之后會做相應(yīng)位圖的渲染,包括紋理的合成,之后把結(jié)果放到幀緩沖區(qū)(Frame Buffer),由視頻控制器,根據(jù)VSync信號在指定時間之前去提取幀緩沖區(qū)當(dāng)中的內(nèi)容,最終顯示到手機屏幕上。

如何定位內(nèi)存泄漏?
-
靜態(tài)分析 cmd+shift+B
會報Warning,定位到對應(yīng)位置修改即可
Warning Instruments Leak(cmd+i)
首先需要對工程進行設(shè)置
Build Settings - Debug Infomation Format 設(shè)置成DWARF with dSYM File
其次需要在真機上運行
這樣子才能定位到Xcode代碼具體位置
具體操作這里就不記錄了
冷啟動
pre-main
1、減少動態(tài)庫、合并一些動態(tài)庫(定期清理不必要的動態(tài)庫)
2、減少Objc類、分類的數(shù)量、減少Selector數(shù)量(定期清理不必要的類、分類)
3、減少C++虛函數(shù)數(shù)量
4、Swift盡量使用struct
5、用+initialize方法和dispatch_once取代所有的attribute((constructor))、>C++靜態(tài)構(gòu)造器、Objc的+load
main
1、在不影響用戶體驗的前提下,盡可能將一些操作延遲,不要全部都放在didFinishLaunching方法中
2、監(jiān)控、埋點、基礎(chǔ)功能設(shè)置 在willFinishLaunching
3、定位、網(wǎng)絡(luò)配置、基礎(chǔ)SDK 、必須的數(shù)據(jù) 在 didFinishLaunching
首頁渲染
1、避免使用xib
2、首頁一般關(guān)聯(lián)業(yè)務(wù)較多,優(yōu)先請求和渲染用戶可見的頁面
3、業(yè)務(wù)組件,業(yè)務(wù)相關(guān)配置等,在首頁渲染完成之后
內(nèi)存管理方案
NONPOINTER_ISA
散列表
TaggedPointer

