[toc]
主要是一些視頻筆記和面試時(shí)候常問(wèn)到的問(wèn)題記錄夫壁。(持續(xù)更新)
Runtime
什么是 Runtime?它的作用是什么庆械?
Runtime 是 Objective-C 的運(yùn)行時(shí)系統(tǒng)薇溃,它包含一系列的 API,允許在運(yùn)行時(shí)創(chuàng)建類(lèi)缭乘、調(diào)用方法沐序、訪問(wèn)屬性等。其作用是實(shí)現(xiàn)動(dòng)態(tài)消息傳遞和運(yùn)行時(shí)類(lèi)型識(shí)別堕绩。
消息傳遞策幼、消息轉(zhuǎn)發(fā)
消息傳遞
objc_msgSend(obj, SEL/@selector(aMethod)/);
1、從消息緩存列表里面通過(guò)哈希表查找對(duì)應(yīng)的方法(哈希沖突怎么辦奴紧?應(yīng)該是通過(guò)再哈希的方式解決的)
2垄惧、在當(dāng)前類(lèi)方法列表中查找
對(duì)于已排序好的列表,采用二分查找算法查找方法對(duì)應(yīng)執(zhí)行函數(shù)
對(duì)于沒(méi)有排序的列表绰寞,采用一般遍歷查找方法對(duì)應(yīng)執(zhí)行函數(shù)
類(lèi)方法是沒(méi)有排序的到逊,所以是使用遍歷查找方法
3、從父類(lèi)逐級(jí)查找
判斷父類(lèi)是否是nil
有父類(lèi)
在緩存中查找
緩存中無(wú)滤钱,則從父類(lèi)方法列表中查找觉壶,直至父類(lèi)為nil,進(jìn)入消息轉(zhuǎn)發(fā)流程
消息轉(zhuǎn)發(fā)
當(dāng)一個(gè)對(duì)象接收到無(wú)法解讀的消息時(shí)件缸,Runtime 會(huì)調(diào)用消息轉(zhuǎn)發(fā)機(jī)制铜靶。這包括三個(gè)步驟:動(dòng)態(tài)方法解析、備用接收者和完整轉(zhuǎn)發(fā)他炊。開(kāi)發(fā)者可以通過(guò)重載 resolveInstanceMethod
和 forwardInvocation
方法來(lái)自定義消息的處理過(guò)程争剿。
resolveInstanceMethod:
(resolveClassMethod:)
為類(lèi)添加一個(gè)方法,返回YES
forwardingTargetForSelector:
返回一個(gè)其他對(duì)象去處理這個(gè)消息(備用receiver)
forwardInvocation:
如果上面兩種情況沒(méi)有執(zhí)行痊末,就會(huì)執(zhí)行通過(guò)forwardInvocation進(jìn)行消息轉(zhuǎn)發(fā)
方法替換(Method-Swizzling)
Method Swizzing是發(fā)生在運(yùn)行時(shí)的蚕苇,在運(yùn)行時(shí)將一個(gè)方法的實(shí)現(xiàn)替換成另一個(gè)方法的實(shí)現(xiàn);
每個(gè)類(lèi)都維護(hù)著一個(gè)方法列表,即methodList凿叠,methodList中有不同的方法涩笤,每個(gè)方法中包含了方法的SEL和IMP嚼吞,方法交換就是將原本的SEL和IMP對(duì)應(yīng)斷開(kāi),并將SEL和新的IMP生成對(duì)應(yīng)關(guān)系蹬碧;
RunLoop
什么是RunLoop舱禽?
RunLoop是通過(guò)內(nèi)部維護(hù)的事件循環(huán)
對(duì)消息/事件進(jìn)行管理
的對(duì)象
**事件循環(huán)(Event Loop)
沒(méi)有消息需要處理的時(shí)候,休眠以避免資源占用恩沽;【用戶(hù)態(tài)】->【內(nèi)核態(tài)】
有消息需要處理的時(shí)候誊稚,立刻被喚醒【內(nèi)核態(tài)】->【用戶(hù)態(tài)】
RunLoop的數(shù)據(jù)結(jié)構(gòu)
Runloop和線程是一一對(duì)應(yīng)的
主線程的runloop自動(dòng)啟動(dòng),而子線程的runloop需要手動(dòng)啟動(dòng)
Timer與RunLoop的面試題
問(wèn)題:定時(shí)器有個(gè)RunLoop mode罗心,默認(rèn)是在defaultMode里伯,scrollView滾動(dòng)的時(shí)候,主線程的RunLoop會(huì)轉(zhuǎn)到UITrackingRunLoopMode协屡,這時(shí)候定時(shí)器就會(huì)失效
解決:將定時(shí)器添加到CommonMode上
思考:為什么?
NSRunloopCommonModes
- CommonMode不是實(shí)際存在的一種Mode
- 是同步source/Timer/Observer到多個(gè)mode的一種技術(shù)解決方案
Block
這篇文章講的挺透徹
iOS-Block本質(zhì)
什么是Block全谤?
- Block是將
函數(shù)
及執(zhí)行上下文
封裝起來(lái)的對(duì)象
block的幾種形式肤晓?
堆block(__NSMallocBlock__)
,
棧block(__NSStackBlock__)
认然,使用外部變量并且未進(jìn)行copy操作的block是棧block
全局block(__NSGlobalBlock__)
不使用外部變量的block是全局block
block變量截獲?
- 局部變量(截獲其值)
基本數(shù)據(jù)類(lèi)型
對(duì)象類(lèi)型(連同所有權(quán)修飾符一同截獲)
- 靜態(tài)局部變量(指針形式截獲)
- 全局變量(不截獲)
- 靜態(tài)全局變量(不截獲)
一般block會(huì)在棧區(qū)补憾,經(jīng)過(guò)copy之后,會(huì)拷貝到堆區(qū)卷员,棧區(qū)的block的__forwarding指針指向拷貝后的堆區(qū)的block盈匾,而堆區(qū)的__forwarding指針會(huì)指向自己
為什么要用__block修飾局部變量?
__block修飾之后的局部變量實(shí)際變成了一個(gè)結(jié)構(gòu)體毕骡,它內(nèi)部有一個(gè)isa指針削饵,這個(gè)結(jié)構(gòu)體會(huì)被block捕獲,成為其成員變量未巫;block內(nèi)部修改的時(shí)候窿撬,實(shí)際是通過(guò)這個(gè)結(jié)構(gòu)體的isa指針去修改所修飾的局部變量的值的
弱引用管理
如何添加一個(gè)weak變量到弱引用表
一個(gè)被聲明為_(kāi)_weak的對(duì)象指針,經(jīng)過(guò)編譯器編譯之后叙凡,調(diào)用objc_initweak()
劈伴,經(jīng)過(guò)一些列的函數(shù)調(diào)用(storeWeak()
),最終在weak_register_no_lock()
函數(shù)中進(jìn)行弱引用變量的添加握爷;具體添加的位置是通過(guò)哈希算法進(jìn)行位置查找跛璧,如果說(shuō)查找對(duì)應(yīng)位置當(dāng)中已經(jīng)有當(dāng)前對(duì)象對(duì)應(yīng)的弱引用數(shù)組,那么就把新的弱引用變量添加到這個(gè)數(shù)組當(dāng)中新啼,如果沒(méi)有追城,重新創(chuàng)建一個(gè)弱引用數(shù)組,然后第0個(gè)位置添加上最新的weak指針燥撞,后面的都初始化為0或者nil漓柑。
weak如何置nil
當(dāng)一個(gè)對(duì)象被dealloc
之后,在dealloc
的內(nèi)部實(shí)現(xiàn)當(dāng)中,會(huì)調(diào)用弱引用清除的相關(guān)函數(shù)weak_clear_no_lock()
辆布,在這個(gè)函數(shù)內(nèi)部實(shí)現(xiàn)當(dāng)中會(huì)根據(jù) 當(dāng)前對(duì)象指針
查找弱引用表瞬矩,把當(dāng)前對(duì)象相對(duì)應(yīng)的弱引用(數(shù)組)都拿出來(lái),遍歷數(shù)組當(dāng)中所有的弱引用指針锋玲,置為nil景用。
weak自動(dòng)置nil的原理(簡(jiǎn)書(shū)1,做參考)
runtime維護(hù)著一個(gè)weak表即hash表惭蹂,用于存儲(chǔ)指向?qū)ο蟮膚eak指針
Weak表是Hash表伞插,Key是所指對(duì)象的地址,Value是Weak指針地址的數(shù)組
以對(duì)象的地址作為key盾碗,去找weak指針
觸發(fā)調(diào)用arr_clear_deallocating 函數(shù) 媚污,根據(jù)對(duì)象的地址將所有weak指針地址的數(shù)組,遍歷數(shù)組把其中的數(shù)據(jù)置為nil廷雅。
weak自動(dòng)置nil的原理(簡(jiǎn)書(shū)2耗美,做參考)
一 、實(shí)現(xiàn)
runtime在注冊(cè)類(lèi)時(shí)航缀,會(huì)布局一個(gè)weak表(hash表)商架,key是所指對(duì)象的地址,value是weak指針的地址的數(shù)組芥玉;當(dāng)對(duì)象釋放時(shí)蛇摸,層層調(diào)用后,通過(guò)arr_clear_deallocating釋放灿巧;
二赶袄、weak實(shí)現(xiàn)原理步驟:通過(guò)clang可以分析源碼;
objc_initWeak//初始化weak;
objc_storeWeak()//修更新指針指向抠藕,創(chuàng)建對(duì)應(yīng)的弱引用表;
clearDeallocating//通過(guò)key找到weak數(shù)組弃鸦,然后對(duì)數(shù)組里的weak指針置nil,把這個(gè)entry(入口幢痘,記錄)從weak表刪除唬格;
自動(dòng)釋放池問(wèn)題
- (void)viewDidLoad {
[super viewDidLoad];
NSMutableArray *array = [NSMutableArray array];
NSLog(@"%@",array);
}
Q: array在什么時(shí)候釋放?
A: 在當(dāng)前RunLoop將要結(jié)束的時(shí)候調(diào)用AutoreleasePoolPage::pop()來(lái)對(duì)其進(jìn)行釋放颜说。
(實(shí)際上在每一次的RunLoop循環(huán)當(dāng)中都會(huì)在將要結(jié)束的時(shí)候?qū)η耙淮蝿?chuàng)建的AutoreleasePool進(jìn)行pop操作购岗,同時(shí)會(huì)push進(jìn)來(lái)一個(gè)新的AutoreleasePool)
問(wèn)題拓展:
要回答這個(gè)問(wèn)題需要知道RunLoop和AutoReleasePool的關(guān)系。
Runloop每次循環(huán)都是被一個(gè)AutoReleasePool包圍著的门粪,具體說(shuō)每次Runloop循環(huán)將要結(jié)束的時(shí)候會(huì)釋放當(dāng)前runloop的內(nèi)存占用喊积。再創(chuàng)建好一個(gè)AutoReleasePool給下一次Runloop循環(huán)使用。(慕課網(wǎng)6-7
)
ViewDidLoad是在主線程執(zhí)行玄妈,在該方法中創(chuàng)建的array會(huì)加入到當(dāng)次RunLoop的AutoReleasePool中乾吻,array會(huì)在當(dāng)前RunLoop將要結(jié)束的時(shí)候得到內(nèi)存釋放髓梅。
一般錯(cuò)誤的回答都是viewDidLoad方法結(jié)束就釋放了。
AutoreleasePool原理绎签?
數(shù)據(jù)結(jié)構(gòu):是以棧
為節(jié)點(diǎn)枯饿,通過(guò)雙向鏈表
的形式組合而成。和線程
是一一對(duì)應(yīng)的诡必。
objc_autoreleasePoolPush()
objc_autoreleasePoolPop()
objc_autorelease()
AutoreleasePool為什么可以嵌套調(diào)用奢方?
A:多層嵌套就是多次插入哨兵對(duì)象
AutoreleasePool使用場(chǎng)景?
在for循環(huán)中alloc圖片數(shù)據(jù)等內(nèi)存消耗較大的場(chǎng)景手動(dòng)插入autoreleasePool
組件化
組件化的好處爸舒?
- 業(yè)務(wù)分層蟋字、解耦,使代碼變得可維護(hù)扭勉;
- 有效的拆分鹊奖、組織日益龐大的工程代碼,使工程目錄變得可維護(hù)涂炎;
- 便于各業(yè)務(wù)功能拆分忠聚、抽離,實(shí)現(xiàn)真正的功能復(fù)用璧尸;
- 業(yè)務(wù)隔離咒林,跨團(tuán)隊(duì)開(kāi)發(fā)代碼控制和版本風(fēng)險(xiǎn)控制的實(shí)現(xiàn)熬拒;
- 模塊化對(duì)代碼的封裝性爷光、合理性都有一定的要求,提升開(kāi)發(fā)同學(xué)的設(shè)計(jì)能力澎粟;
- 在維護(hù)好各級(jí)組件的情況下蛀序,隨意組合滿(mǎn)足不同客戶(hù)需求;(只需要將之前的多個(gè)業(yè)務(wù)組件模塊在新的主App中進(jìn)行組裝即可快速迭代出下一個(gè)全新App)
如何實(shí)現(xiàn)解耦活烙?
-
分層
基礎(chǔ)功能組件:按功能分庫(kù)徐裸,不涉及產(chǎn)品業(yè)務(wù)需求,跟庫(kù)Library類(lèi)似啸盏,通過(guò)良好的接口供上層業(yè)務(wù)組件調(diào)用重贺;不寫(xiě)入產(chǎn)品定制邏輯,通過(guò)擴(kuò)展接口完成定制回懦;
(網(wǎng)絡(luò)組件气笙、彈框組件、工具組件怯晕、)
基礎(chǔ)UI組件:各個(gè)業(yè)務(wù)模塊依賴(lài)使用潜圃,但需要保持好定制擴(kuò)展的設(shè)計(jì)業(yè)務(wù)組件:業(yè)務(wù)功能間相對(duì)獨(dú)立,相互間沒(méi)有Model共享的依賴(lài)舟茶;業(yè)務(wù)之間的頁(yè)面調(diào)用只能通過(guò)UIBus進(jìn)行跳轉(zhuǎn)谭期;業(yè)務(wù)之間的邏輯Action調(diào)用只能通過(guò)服務(wù)提供堵第;
中間件:target-action,url-block隧出,protocol-class
http://www.reibang.com/p/464a8f1ab949
CTMeditor
- 通過(guò)反射機(jī)制利用字符串找到相對(duì)應(yīng)的target然后向它發(fā)送消息
AvoidCrash
Foundation框架潛在的崩潰的危險(xiǎn)比如:
- 將 nil 插入可變數(shù)組中會(huì)導(dǎo)致崩潰踏志。
- 數(shù)組越界會(huì)導(dǎo)致崩潰。
- 根據(jù)key給字典某個(gè)元素重新賦值時(shí)鸳劳,若key為 nil 會(huì)導(dǎo)致崩潰狰贯。
- ......
利用runtime的特性,使用方法替換
赏廓,在即將發(fā)生崩潰的位置給它替換成默認(rèn)實(shí)現(xiàn)涵紊,防止崩潰,同時(shí)上報(bào)這個(gè)錯(cuò)誤到bugly
捕獲到異常之后的處理(其實(shí)就是獲取出現(xiàn)異常的堆棧幔摸,最后以通知的形式發(fā)送出去)
/**
* 提示崩潰的信息(控制臺(tái)輸出摸柄、通知)
*
* @param exception 捕獲到的異常
* @param defaultToDo 這個(gè)框架里默認(rèn)的做法
*/
+ (void)noteErrorWithException:(NSException *)exception defaultToDo:(NSString *)defaultToDo {
//堆棧數(shù)據(jù)
NSArray *callStackSymbolsArr = [NSThread callStackSymbols];
//獲取在哪個(gè)類(lèi)的哪個(gè)方法中實(shí)例化的數(shù)組 字符串格式 -[類(lèi)名 方法名] 或者 +[類(lèi)名 方法名]
NSString *mainCallStackSymbolMsg = [AvoidCrash getMainCallStackSymbolMessageWithCallStackSymbols:callStackSymbolsArr];
if (mainCallStackSymbolMsg == nil) {
mainCallStackSymbolMsg = @"崩潰方法定位失敗,請(qǐng)您查看函數(shù)調(diào)用棧來(lái)排查錯(cuò)誤原因";
}
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);
//請(qǐng)忽略下面的賦值,目的只是為了能順利上傳到cocoapods
logErrorMessage = logErrorMessage;
NSDictionary *errorInfoDic = @{
key_errorName : errorName,
key_errorReason : errorReason,
key_errorPlace : errorPlace,
key_defaultToDo : defaultToDo,
key_exception : exception,
key_callStackSymbols : callStackSymbolsArr
};
//將錯(cuò)誤信息放在字典里既忆,用通知的形式發(fā)送出去
dispatch_async(dispatch_get_main_queue(), ^{
[[NSNotificationCenter defaultCenter] postNotificationName:AvoidCrashNotification object:nil userInfo:errorInfoDic];
});
}
多線程相關(guān)問(wèn)題
參考:http://www.reibang.com/p/361e8a0a4e7e
- iOS中的多線程
. NSThread
. GCD
. NSOperationQueue
NSThread - 輕量級(jí)別的多線程技術(shù)驱负,需要我們自己管理線程
需要我們手動(dòng)開(kāi)辟子線程,如果使用init初始化方式則需要手動(dòng)啟動(dòng)患雇,如果使用構(gòu)造器方式初始化則會(huì)自動(dòng)啟動(dòng)跃脊。
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(testThread:) object:@"我是參數(shù)"];
// 當(dāng)使用初始化方法出來(lái)的主線程需要start啟動(dòng)
[thread start];
// 可以為開(kāi)辟的子線程起名字
thread.name = @"NSThread線程";
// 調(diào)整Thread的權(quán)限 線程權(quán)限的范圍值為0 ~ 1 。越大權(quán)限越高苛吱,先執(zhí)行的概率就會(huì)越高酪术,由于是概率,所以并不能很準(zhǔn)確的的實(shí)現(xiàn)我們想要的執(zhí)行順序翠储,默認(rèn)值是0.5
thread.threadPriority = 1;
// 取消當(dāng)前已經(jīng)啟動(dòng)的線程
[thread cancel];
// 通過(guò)遍歷構(gòu)造器開(kāi)辟子線程
[NSThread detachNewThreadSelector:@selector(testThread:) toTarget:self withObject:@"構(gòu)造器方式"];
performSelector:withObject:afterDelay:會(huì)在內(nèi)部創(chuàng)建一個(gè)NSTimer绘雁,然后添加到當(dāng)前的RunLoop中,如果當(dāng)前線程沒(méi)有開(kāi)啟RunLoop(子線程默認(rèn)沒(méi)有開(kāi)啟RunLoop)援所,該方法會(huì)失效
[self performSelector:@selector(aaa) withObject:nil afterDelay:1];
[[NSRunLoop currentRunLoop] run];
performSelector:withObject:沒(méi)有添加timer庐舟,所以不需要添加子線程RunLoop也可以執(zhí)行
GCD對(duì)比NSOperationQueue
GCD是面向底層的C語(yǔ)言的API,NSOpertaionQueue用GCD構(gòu)建封裝的住拭,是GCD的高級(jí)抽象挪略。
它們的區(qū)別
- GCD執(zhí)行效率更高,而且由于隊(duì)列中執(zhí)行的是由block構(gòu)成的任務(wù)滔岳,是一個(gè)輕量級(jí)的數(shù)據(jù)結(jié)構(gòu)杠娱,寫(xiě)起來(lái)更方便
- GCD只支持FIFO的隊(duì)列,而NSOperationQueue可以通過(guò)設(shè)置最大并發(fā)數(shù)澈蟆,設(shè)置優(yōu)先級(jí)墨辛,添加依賴(lài)關(guān)系等調(diào)整執(zhí)行順序
- NSOperationQueue甚至可以跨隊(duì)列設(shè)置依賴(lài)關(guān)系,但是GCD只能通過(guò)設(shè)置串行隊(duì)列趴俘,或者在隊(duì)列內(nèi)添加barrier(dispatch_barrier_async)任務(wù)睹簇,才能控制執(zhí)行順序
- NSOperationQueue因?yàn)槊嫦驅(qū)ο笞嘧福灾С諯VO,可以檢測(cè)operation是否正在執(zhí)行(isExecuted)太惠、是否結(jié)束(isFinished)磨淌、是否取消(isCanceld)
探討
實(shí)際項(xiàng)目開(kāi)發(fā)中,很多時(shí)候只是會(huì)用到異步操作凿渊,不會(huì)有特別復(fù)雜的線程關(guān)系管理梁只,所以蘋(píng)果推崇的且優(yōu)化完善、運(yùn)行快速的GCD是首選 如果考慮異步操作之間的事務(wù)性埃脏,順序行搪锣,依賴(lài)關(guān)系,比如多線程并發(fā)下載彩掐,GCD需要自己寫(xiě)更多的代碼來(lái)實(shí)現(xiàn)构舟,而NSOperationQueue已經(jīng)內(nèi)建了這些支持 不論是GCD還是NSOperationQueue,我們接觸的都是任務(wù)和隊(duì)列堵幽,都沒(méi)有直接接觸到線程狗超,事實(shí)上線程管理也的確不需要我們操心,系統(tǒng)對(duì)于線程的創(chuàng)建朴下,調(diào)度管理和釋放都做得很好努咐。而NSThread需要我們自己去管理線程的生命周期,還要考慮線程同步殴胧、加鎖問(wèn)題渗稍,造成一些性能上的開(kāi)銷(xiāo)
Q:假設(shè)有這么場(chǎng)景:有網(wǎng)絡(luò)請(qǐng)求A、網(wǎng)絡(luò)請(qǐng)求B溃肪,需要AB執(zhí)行完之后繼續(xù)進(jìn)行下一步操作免胃,怎么使用GCD實(shí)現(xiàn)?
A:
- 信號(hào)量(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實(shí)現(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(同時(shí)也可以用來(lái)實(shí)現(xiàn)多讀單寫(xiě)、加鎖躺涝、設(shè)置最大線程數(shù))
- (void)GCD_barrier {
dispatch_queue_t queue = dispatch_queue_create("barrier_queue", DISPATCH_QUEUE_CONCURRENT);
// 注意dispatch_barrier_async只在自己創(chuàng)建的并發(fā)隊(duì)列中才有效厨钻,在global_queue,串行隊(duì)列上效果跟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");
});
}
多讀單寫(xiě)
- (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];
});
}
單例模式
這篇文章介紹的還不錯(cuò)
http://www.reibang.com/p/a92c0283f243
什么是單例模式?
簡(jiǎn)單來(lái)說(shuō)坚嗜,一個(gè)單例類(lèi)夯膀,在整個(gè)程序中只有一個(gè)實(shí)例,并且提供了類(lèi)方法供全局調(diào)用苍蔬,在編譯時(shí)初始化這個(gè)類(lèi)诱建,然后一直保存在內(nèi)存中,直到App退出時(shí)由系統(tǒng)自動(dòng)釋放這一部分內(nèi)存
系統(tǒng)為我們提供的單例類(lèi)有哪些碟绑?
- UIApplication(應(yīng)用程序?qū)嵗?lèi))
- NSNotificationCenter(消息中心類(lèi))
- NSFileManager(文件管理類(lèi))
- NSUserDefaults(應(yīng)用程序設(shè)置)
- NSURLCache(請(qǐng)求緩存類(lèi))
- NSHTTPCookieStorage(應(yīng)用程序cookies池)
單例的存放位置
全局區(qū)
變量的存放位置
位置 | 存放的變量 |
---|---|
棧 | 臨時(shí)變量(由編譯器管理自動(dòng)創(chuàng)建/分配/釋放的俺猿,棧中的內(nèi)存被調(diào)用時(shí)處于存儲(chǔ)空間中茎匠,調(diào)用完畢后由系統(tǒng)系統(tǒng)自動(dòng)釋放內(nèi)存) |
堆 | 通過(guò)alloc、calloc押袍、malloc或new申請(qǐng)內(nèi)存诵冒,由開(kāi)發(fā)者手動(dòng)在調(diào)用之后通過(guò)free或delete釋放內(nèi)存。動(dòng)態(tài)內(nèi)存的生存期可以由我們決定谊惭,如果我們不釋放內(nèi)存汽馋,程序?qū)⒃谧詈蟛裴尫诺魟?dòng)態(tài)內(nèi)存,在ARC模式下圈盔,由系統(tǒng)自動(dòng)管理豹芯。 |
全局區(qū)域 | 靜態(tài)變量(編譯時(shí)分配,APP結(jié)束時(shí)由系統(tǒng)釋放) |
常量 | 常量(編譯時(shí)分配驱敲,APP結(jié)束時(shí)由系統(tǒng)釋放) |
代碼區(qū) | 存放代碼 |
創(chuàng)建一個(gè)單例的方式
- 同步鎖:NSLock
- @synchronized(self) {}
- 信號(hào)量 dispatch_semaphore_t
- 條件鎖 NSConditionLock
- dispatch_once_t
單例注意事項(xiàng)-保證單例只被初始化一次
- 對(duì)alloc告组、new、copy癌佩、mutableCopy的處理
因?yàn)閍lloc] init 和 new都是調(diào)用的+ (instancetype)allocWithZone:(struct _NSZone *)zone
方法木缝,那么我們可以重寫(xiě)這個(gè)方法
+ (instancetype)allocWithZone:(struct _NSZone *)zone {
NSLog(@"allocWithZone");
@synchronized (self) {
if (instance == nil) {
instance = [super allocWithZone:zone];
return instance;
}
}
return nil;// 這里返回nil,那么后面初始化的對(duì)象就是nil了围辙,返回instance的話其實(shí)就是同一個(gè)單例對(duì)象了
}
- 直接禁用對(duì)應(yīng)的方法
+(instancetype) new __attribute__((unavailable("OneTimeClass類(lèi)只能初始化一次")));
-(instancetype) copy __attribute__((unavailable("OneTimeClass類(lèi)只能初始化一次")));
-(instancetype) mutableCopy __attribute__((unavailable("OneTimeClass類(lèi)只能初始化一次")));
(用NS_UNAVAILABLE也可以我碟,但是這個(gè)就沒(méi)有提示了)
還要了解一下FMDB
NSMutableArray數(shù)據(jù)結(jié)構(gòu)分析
普通C數(shù)組是是一段能被方便讀寫(xiě)的連續(xù)內(nèi)存空間,使用一段線性?xún)?nèi)存空間的一個(gè)最明顯的缺點(diǎn)是姚建,在下標(biāo)0插入一個(gè)元素時(shí)矫俺,需要移動(dòng)其它元素,即memmove的原理:
https://blog.csdn.net/qq_27909209/article/details/82689322
移除元素時(shí)同理也要移動(dòng)其它元素掸冤;
當(dāng)數(shù)組非常大的時(shí)候可能就會(huì)出現(xiàn)問(wèn)題厘托。
NSMutableArray是一個(gè)類(lèi)簇,[NSMutableArray new]實(shí)際返回的是__NSArrayM
(lldb) po [[ NSMutableArray new] class]
__NSArrayM
__NSArrayM
使用了環(huán)形緩沖區(qū) (circular buffer)稿湿,這個(gè)數(shù)據(jù)結(jié)構(gòu)相當(dāng)簡(jiǎn)單铅匹,只是比常規(guī)數(shù)組或緩沖區(qū)復(fù)雜點(diǎn)。環(huán)形緩沖區(qū)的內(nèi)容能在到達(dá)任意一端時(shí)繞向另一端饺藤。
環(huán)形緩沖區(qū)有一些非嘲撸酷的屬性。尤其是涕俗,除非緩沖區(qū)滿(mǎn)了罗丰,否則在任意一端插入或刪除均不會(huì)要求移動(dòng)任何內(nèi)存。我們來(lái)分析這個(gè)類(lèi)如何充分利用環(huán)形緩沖區(qū)來(lái)使得自身比 C 數(shù)組強(qiáng)大得多再姑。我們?cè)谶@里知道了幾個(gè)有趣的東西:在刪除的時(shí)候不會(huì)清除指針萌抵。最有意思的一點(diǎn),如果我們?cè)谥虚g進(jìn)行插入或者刪除,只會(huì)移動(dòng)最少的一邊的元素绍填。
NSDictionary數(shù)據(jù)結(jié)構(gòu)
在內(nèi)部萎坷,字典使用哈希表來(lái)組織其存儲(chǔ),并在給定相應(yīng)鍵的情況下快速訪問(wèn)值
Crash類(lèi)型
- Signal
- NSException
bugly需要使用符號(hào)表解析應(yīng)該是用了捕捉了Signal異常沐兰,Signal異常是需要配合符號(hào)表才能解析的哆档,NSException的話可以直接拿到崩潰信息
關(guān)于RunLoop防止崩潰
https://cloud.tencent.com/developer/article/1192474
這還有一篇文章可以參考(關(guān)于Crash收集)
·
http://www.cocoachina.com/articles/12301
圖像顯示原理
CPU生成位圖(bitmap)經(jīng)由總線在合適的時(shí)機(jī)傳給GPU;GPU拿到位圖之后會(huì)做相應(yīng)位圖的渲染住闯,包括紋理的合成瓜浸,之后把結(jié)果放到幀緩沖區(qū)(Frame Buffer
),由視頻控制器比原,根據(jù)VSync信號(hào)在指定時(shí)間之前去提取幀緩沖區(qū)當(dāng)中的內(nèi)容插佛,最終顯示到手機(jī)屏幕上。
如何定位內(nèi)存泄漏量窘?
-
靜態(tài)分析 cmd+shift+B
會(huì)報(bào)Warning雇寇,定位到對(duì)應(yīng)位置修改即可
Instruments Leak(cmd+i)
首先需要對(duì)工程進(jìn)行設(shè)置
Build Settings - Debug Infomation Format 設(shè)置成DWARF with dSYM File
其次需要在真機(jī)上運(yùn)行
這樣子才能定位到Xcode代碼具體位置
具體操作這里就不記錄了
冷啟動(dòng)
pre-main
1、減少動(dòng)態(tài)庫(kù)蚌铜、合并一些動(dòng)態(tài)庫(kù)(定期清理不必要的動(dòng)態(tài)庫(kù))
2锨侯、減少Objc類(lèi)、分類(lèi)的數(shù)量冬殃、減少Selector數(shù)量(定期清理不必要的類(lèi)囚痴、分類(lèi))
3、減少C++虛函數(shù)數(shù)量
4审葬、Swift盡量使用struct
5深滚、用+initialize方法和dispatch_once取代所有的attribute((constructor))、>C++靜態(tài)構(gòu)造器涣觉、Objc的+load
main
1痴荐、在不影響用戶(hù)體驗(yàn)的前提下,盡可能將一些操作延遲官册,不要全部都放在didFinishLaunching方法中
2生兆、監(jiān)控、埋點(diǎn)攀隔、基礎(chǔ)功能設(shè)置 在willFinishLaunching
3皂贩、定位栖榨、網(wǎng)絡(luò)配置昆汹、基礎(chǔ)SDK 、必須的數(shù)據(jù) 在 didFinishLaunching
首頁(yè)渲染
1婴栽、避免使用xib
2满粗、首頁(yè)一般關(guān)聯(lián)業(yè)務(wù)較多,優(yōu)先請(qǐng)求和渲染用戶(hù)可見(jiàn)的頁(yè)面
3愚争、業(yè)務(wù)組件映皆,業(yè)務(wù)相關(guān)配置等挤聘,在首頁(yè)渲染完成之后
內(nèi)存管理方案
NONPOINTER_ISA
散列表
TaggedPointer