iOS面試總結(jié)-進(jìn)階

[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ò)重載 resolveInstanceMethodforwardInvocation 方法來(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)的

image.png

主線程的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
    攔截所有檢測(cè)崩潰類(lèi)型

    捕獲到異常之后的處理(其實(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:

  1. 信號(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)于加鎖惫撰?

  1. 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(@"===");
    
}
  1. 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)-保證單例只被初始化一次

  1. 對(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ì)象了
}
  1. 直接禁用對(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

image.png

移除元素時(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ī)屏幕上。

image.png

如何定位內(nèi)存泄漏量窘?

  • 靜態(tài)分析 cmd+shift+B
    會(huì)報(bào)Warning雇寇,定位到對(duì)應(yīng)位置修改即可


    Warning
  • 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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市捅彻,隨后出現(xiàn)的幾起案子组去,更是在濱河造成了極大的恐慌,老刑警劉巖步淹,帶你破解...
    沈念sama閱讀 216,544評(píng)論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件从隆,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡缭裆,警方通過(guò)查閱死者的電腦和手機(jī)键闺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評(píng)論 3 392
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)澈驼,“玉大人辛燥,你說(shuō)我怎么就攤上這事》炱洌” “怎么了挎塌?”我有些...
    開(kāi)封第一講書(shū)人閱讀 162,764評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)内边。 經(jīng)常有香客問(wèn)我勃蜘,道長(zhǎng),這世上最難降的妖魔是什么假残? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,193評(píng)論 1 292
  • 正文 為了忘掉前任缭贡,我火速辦了婚禮,結(jié)果婚禮上辉懒,老公的妹妹穿的比我還像新娘阳惹。我一直安慰自己,他們只是感情好眶俩,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,216評(píng)論 6 388
  • 文/花漫 我一把揭開(kāi)白布莹汤。 她就那樣靜靜地躺著,像睡著了一般颠印。 火紅的嫁衣襯著肌膚如雪纲岭。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 51,182評(píng)論 1 299
  • 那天线罕,我揣著相機(jī)與錄音止潮,去河邊找鬼。 笑死钞楼,一個(gè)胖子當(dāng)著我的面吹牛喇闸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 40,063評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼燃乍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼唆樊!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起刻蟹,我...
    開(kāi)封第一講書(shū)人閱讀 38,917評(píng)論 0 274
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤逗旁,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后舆瘪,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體痢艺,經(jīng)...
    沈念sama閱讀 45,329評(píng)論 1 310
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,543評(píng)論 2 332
  • 正文 我和宋清朗相戀三年介陶,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了堤舒。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,722評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡哺呜,死狀恐怖舌缤,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情某残,我是刑警寧澤国撵,帶...
    沈念sama閱讀 35,425評(píng)論 5 343
  • 正文 年R本政府宣布,位于F島的核電站玻墅,受9級(jí)特大地震影響介牙,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜澳厢,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,019評(píng)論 3 326
  • 文/蒙蒙 一环础、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧剩拢,春花似錦线得、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,671評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至办素,卻和暖如春角雷,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背性穿。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,825評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工勺三, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人季二。 一個(gè)月前我還...
    沈念sama閱讀 47,729評(píng)論 2 368
  • 正文 我出身青樓檩咱,卻偏偏與公主長(zhǎng)得像揭措,于是被迫代替她去往敵國(guó)和親胯舷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刻蚯,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,614評(píng)論 2 353

推薦閱讀更多精彩內(nèi)容

  • 內(nèi)存管理 1.什么情況使用weak關(guān)鍵字,相比assign有什么不同桑嘶? 什么情況使用 weak 關(guān)鍵字炊汹?在 ARC...
    刺骨寒閱讀 677評(píng)論 0 1
  • 原文地址:https://blog.csdn.net/wzc10101415/article/details/51...
    sunney0閱讀 119評(píng)論 0 1
  • 關(guān)于面試題 打個(gè)比方,如果把找工作理解成考大學(xué)逃顶,面試就是高考讨便,市面上的“真題”就是模擬試卷。我們會(huì)很容易傾向于在面...
    樂(lè)逍遙11閱讀 482評(píng)論 0 1
  • 推薦指數(shù): 6.0 書(shū)籍主旨關(guān)鍵詞:特權(quán)以政、焦點(diǎn)霸褒、注意力、語(yǔ)言聯(lián)想盈蛮、情景聯(lián)想 觀點(diǎn): 1.統(tǒng)計(jì)學(xué)現(xiàn)在叫數(shù)據(jù)分析废菱,社會(huì)...
    Jenaral閱讀 5,717評(píng)論 0 5
  • 城空了殊轴,有樹(shù)長(zhǎng)出來(lái) 我的城死了 鑄起它的人,殺死它的人 不愿因?yàn)檫@件事而驕傲 一座城的終結(jié) 永遠(yuǎn)因?yàn)榻K結(jié)這件事而顯...
    于十六閱讀 2,856評(píng)論 6 17