面試題
-
一個NSObject對象占用多少內存?
- 實際上分配了16個字節(jié)的存儲空間給NSObject對象
- 真正有使用的空間是:一個指針變量所占用的大小(64位:8個字節(jié)惑艇,32位:4個字節(jié))
- 結構體:繼承遵循內存對齊原則:結構體的最終大小必須是最大成員大小的倍數。如果父類內存對齊后有多余的字節(jié),子類繼承后聲明的變量可以放到父類多余的字節(jié)當中管毙。并且OC底層定義小于16個字節(jié)的,都給分配16個字節(jié)桌硫;InstanceSize:最小對齊單位為isa指針的大小8夭咬。malloc_size:最小對齊單位為OC定義的最小字節(jié)大小16。
-
對象的isa指針指向哪里铆隘?
- instance的isa指向class卓舵;
- 當調用對象方法時,通過instance的isa找到class膀钠,最后找到對象方法的實現進行調用掏湾。
- class的isa指向meta-class;
- 當調用類方法時肿嘲,通過class的isa找到meta-class融击,最后找到類方法的實現進行調用。
- meta-class的isa指向基類的meta-class睦刃;
- 基類的meta-class的isa指向自己砚嘴;
- instance的isa指向class卓舵;
-
對象的superclass指針指向哪里?
- class的superclass指向父類的class涩拙;
- 如果沒有父類际长,superclass指針為nil。
- meta-class的superclass指向父類的meta-class兴泥;
- 基類的meta-class的superclass指向基類的class工育;
- class的superclass指向父類的class涩拙;
-
OC的類信息存放在哪里?
- 搓彻;
-
Objective-C中的對象如绸,簡稱OC對象,主要可以分為3種:
- instance對象(實例對象):通過類alloc出來的對象旭贬,每次調用alloc都會產生新的instance對象怔接。
- class對象(類對象):
- objectClass1 ~ objectClass5 都是NSObject的class對象(類對象)。
- 它們是同一個對象稀轨,每個類在內存中有且只有一個class對象扼脐。
- meta-class對象(元類對象):
- 每個類在內存中有且只有一個meta-class對象。
- meta-class對象和class對象的內存結構是一樣的奋刽,但用途不一樣瓦侮。
-
instance對象在內存中存儲的信息包括:
- isa指針艰赞;
- 其他成員變量;
-
class對象在內存中存儲的信息包括:
- isa指針肚吏;
- superclass指針方妖;
- 類的屬性信息(@property)
- 類的對象方法信息(instance method)
- 類的協(xié)議信息(protocol)
- 類的成員變量信息(ivar)
- ......
-
meta-class對象在內存中存儲的信息包括:
- isa指針
- superclas指針
- 類的類方法信息(class method)
- ......(其他類似class的信息,是空的)
-
iOS用什么方式實現對一個對象的KVO罚攀?(KVO的本質是什么党觅?)
- 利用Runtime的API動態(tài)生成一個子類,并且讓instance對象的isa指向這個全新的子類
- 當修改instance對象的屬性時坞生,會調用Foundation的_NSSetXXXValueAndNotify函數:
- willChangeValueForKey
- 父類原來的setter實現
- didChangeValueForKey仔役,這個方法內部又會調用監(jiān)聽器(observer)的監(jiān)聽方法
- 內部又會調用監(jiān)聽器(observer)的監(jiān)聽方法:observeValueForKeyPath:ofObject:change:context:
-
_NSSetXXXValueAndNotify的內部實現:
- 調用willChangeValueForKey
- 調用原來的setter實現
- 調用didChangeValueForKey
- didChangeValueForKey內部會調用observer的observeValueForKeyPath:ofObject:change:context方法
-
如何手動觸發(fā)KVO掷伙?
- 手動調用willChangeValueForKey和didChangeValueForKey是己;
-
通過KVC修改屬性會觸發(fā)KVO嗎?
- 會觸發(fā)KVO(相當于setValue:forKey:內部手動調用了KVO的_NSSetXXXValueAndNotify方法)
-
KVC:setValue:forkey:的原理:
- 按照setKey任柜、_setKey順序查找方法:
- 如果找到了傳遞參數卒废,調用方法。
- 如果找不到宙地,查看accessInstanceVariableDirectory方法的返回值:
- 返回NO:調用setValue:forUndefinedKey:并拋出異常NSUnknownKeyException
- 返回YES:按照_key摔认、_isKey、key宅粥、isKey順序查找成員變量参袱,查找到直接賦值,查找不到拋出同上NO的異常秽梅。
- 按照setKey任柜、_setKey順序查找方法:
-
KVC:valueForKey:的原理:
- 按照getKey抹蚀、key、isKey企垦、_key順序查找方法:
- 如果找到了环壤,調用方法。
- 如果找不到钞诡,查看accessInstanceVariableDirectory方法的返回值:
- 返回NO:調用valueForUndefinedKey:并拋出異常NSUnknownKeyException
- 返回YES:按照_key郑现、_isKey、key荧降、isKey順序查找成員變量接箫,查找到直接取值,查找不到拋出同上NO的異常朵诫。
- 按照getKey抹蚀、key、isKey企垦、_key順序查找方法:
-
KVC的賦值和取值過程是怎樣的辛友?原理是什么?
- 賦值過程即上面的setValue:forKey:的原理拗窃;
- 取值過程即上面的valueForKey:的原理瞎领;
-
什么是Runloop泌辫?
- 運行循環(huán)
- 在程序運行過程中循環(huán)做一些事情
-
runloop的基本作用:
- 保持程序的持續(xù)運行
- 處理APP中的各種事件(比如觸摸事件、定時器事件等)
- 節(jié)省CPU資源九默,提高程序性能:該做事時做事震放,該休息時休息
-
runloop內部實現邏輯?
- sources0:
- 觸摸事件處理驼修;
- performSelector:onThread:
- sources1:
- 基于port的線程間通信殿遂;
- 系統(tǒng)事件捕捉(比如點擊事件是sources1捕捉,然后分發(fā)給sources0去處理)乙各;
- timers:
- NSTimer墨礁;
- performSelector:withObject:afterDelay;
- observers:
- 用于監(jiān)聽RunLoop的狀態(tài)耳峦;
- UI刷新(BeforeWaiting)恩静;
- Autorelease pool;
- sources0:
-
runloop和線程的關系蹲坷?
- 每條線程都有唯一的一個與之對應的RunLoop對象
- RunLoop保存在一個全局的Dictionary里驶乾,線程作為key,RunLoop作為Value
- 線程剛創(chuàng)建時并沒有RunLoop對象循签,RunLoop會在第一次獲取它時創(chuàng)建
- RunLoop會在線程結束時銷毀
-
RunLoop休眠的實現原理:
- 休眠:從用戶態(tài)切換到內核態(tài):
- 內核態(tài):等待消息级乐;
- 沒有消息就讓線程休眠;
- 有消息就喚醒線程县匠;
- 喚醒:從內核態(tài)切換到用戶態(tài)风科,來處理消息;
-
RunLoop的幾種狀態(tài)乞旦?
- 贼穆;
-
Timer與RunLoop的關系?
- Timer是運行在RunLoop里面的杆查;
-
RunLoop是怎么響應用戶操作的扮惦,具體流程是什么樣的?
- sources1捕捉事件亲桦;
- 交給sources0去處理崖蜜;
-
Core Foundation中關于RunLoop的5個類:
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
-
CFRunLoopModeRef:
- CFRunLoopModeRef代表RunLoop的運行模式。
- 一個RunLoop包含若干個Mode客峭,每個Mode又包含若干個Source0/Source1/Timer/Observer豫领。
- RunLoop啟動時只能選擇其中一個Mode,作為currentMode舔琅。
- 如果需要切換Mode等恐,只能退出當前Loop,再重新選擇一個Mode進入。
- 不同組的Source0/Source1/Timer/Observer能分割開來课蔬,互不影響囱稽。
- 如果Mode里沒有任何Source0/Source1/Timer/Observer,RunLoop會立馬退出二跋。
-
CFRunLoopModeRef:目前一直的Mode有5種:
- kCFRunLoopDefaultMode:APP的默認Mode战惊,通常主線程是在這個Mode下運行。
- UITrackingRunLoopMode:界面跟蹤Mode扎即,用于ScrollView追蹤觸摸滑動吞获,保證界面滑動時不受其他Mode影響。
- kCFRunLoopCommonModes:這是一個占位用的Mode谚鄙,不是一個真正的Mode各拷。
- UIInitializationRunLoopMode:在剛啟動APP時進入的第一個Mode,啟動完成后就不再使用闷营。
- GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內部Mode烤黍,通常用不到。
-
Category的實現原理是什么粮坞?
- Category編譯之后的底層結構是struct category_t:里面存儲著分類的對象方法蚊荣、類方法初狰、屬性莫杈、協(xié)議信息。
- 在程序運行的時候奢入,Runtime會將Category的數據筝闹,喝杯冰島類信息中(類對象、元類對象中)
-
Category和Class Extension的區(qū)別是什么腥光?
- Class Extension在編譯的時候关顷,它的數據就已經包含在類信息中。
- Category是在運行時武福,才將數據合并到類信息中议双。
-
Category中有l(wèi)oad方法嗎?load方法是什么時候調用的捉片?load方法能繼承嗎平痰?
- 有l(wèi)oad方法;
- 在Runtime加載類伍纫、分類的時候調用宗雇;
- +load方法可以繼承,但是一般不會主動去調用load方法莹规,都是讓系統(tǒng)自動調用赔蒲。
分類的對象方法、類方法也是分別存放在類對象、元類對象的方法列表舞虱。類里面的方法是在編譯時就放進去欢际,分類是通過runtime動態(tài)將分類的方法合并到類對象、元類對象中矾兜。
-
分類里面添加屬性:
- 只會生成set幼苛、get方法的聲明;
- 不會生成set焕刮、get方法的具體實現舶沿;
- 不會生成屬性的成員變量;
-
Category的加載處理過程:
- 通過Runtime加載某個類的所有Category數據配并。
- 把所有Category的方法括荡、屬性、協(xié)議數據溉旋,合并到一個大數組中畸冲。
- 后面參與編譯的Category數據,會被放在數組的前面观腊。
- 將合并后的數據(方法邑闲、屬性、協(xié)議)梧油,插入到類原來數據的前面苫耸。
-
+load方法:
- +load方法會在Runtime加載類、分類時調用儡陨。(是通過指針直接找到方法調用的褪子,不是通過消息機制調用)
- 每個類、分類的+load骗村,在程序運行過程中只調用一次嫌褪。
- 調用順序:
- 先調用類的+load;
- 按照編譯先后順序調用(先編譯胚股,先調用)
- 調用子類的+load之前會先調用父類的+load笼痛;
- 再調用分類的+load;
- 按照編譯先后順序調用(先編譯琅拌,先調用)(不會先調用父類的分類)缨伊。
- 先調用類的+load;
-
load、initialize方法的區(qū)別是什么财忽?它們在category中的調用的順序倘核?以及出現繼承時他們之間的調用過程?
- 調用方式:
- +load是直接找到對應的方法地址直接調用即彪;
- +initialize是通過objc_msgSend調用的紧唱;
- 調用時刻:
- +load是Runtime加載類活尊、分類的時候調用(只會調用一次)
- +initialize是類第一次接收到消息的時候調用,每一個類只會initialize一次(父類的initialize方法可能會被調用多次)
- 調用方式:
-
+initialize方法:
- +initialize方法會在類第一次接收消息時調用漏益;(如果從來沒接收過消息蛹锰,就不會調用)
- 調用順序:
- 先初始化父類的+initialize,
- 再初始化子類的+initialize绰疤;(可能最終調用的是父類的initialize方法铜犬,但不代表又初始化了父類,只是調用了父類的方法轻庆,初始化的是子類癣猾,因為每個類只會被初始化一次)
- 只會初始化一次;(如果子類沒有實現+initialize余爆,會調用父類的+initialize纷宇;(所以父類的+initialize可能會被調用多次))
- +initialize是通過objc_msgSend進行調用的;所以具備以下特點:
- 如果分類實現了+initialize蛾方,就會覆蓋類本身的+initialize調用像捶。
- 如果子類沒有實現+initialize,會調用父類的+initialize桩砰;(所以父類的+initialize可能會被調用多次)
-
Category能否添加成員變量拓春?如果可以,如何給Category添加成員變量亚隅?
- 不可以直接給Category添加成員變量硼莽;
- 但是可以通過添加關聯對象,間接實現Category有成員變量的效果枢步;
-
如何實現給分類添加關聯對象沉删?
- ;
-
block的原理是怎樣的醉途?本質是什么?
- block本質上也是一個OC對象砖茸,它內部也有個isa指針隘擎;
- block是封裝了函數調用以及函數調用環(huán)境的OC對象;
-
block的底層結構如圖:截屏2021-07-25 上午11.59.07.png
-
block的變量捕獲(capture)
- 為了保證block內部能夠正常訪問外部的變量凉夯,block有個變量捕獲機制:
- 局部變量:auto:能夠捕獲到block內部货葬。訪問方式:值傳遞。
- 局部變量:static:能夠捕獲到block內部劲够。訪問方式:指針傳遞震桶。
- 全局變量:不能捕獲到block內部。訪問方式:直接訪問征绎;
- 為了保證block內部能夠正常訪問外部的變量凉夯,block有個變量捕獲機制:
-
Block的類型:Block有3種類型蹲姐,可以通過調用class方法或者isa指針查看具體類型,最終都是繼承自NSBlock類型:
- NSGlobalBlock (_NSConcreteGlobalBlock)(數據段:全局變量):只要沒有訪問auto變量的,都是Global柴墩。Global調用Copy后忙厌,什么也不必做。
- NSMallocBlock(_NSConcreteMallocBlock)(堆段:alloc出來的內容江咳,動態(tài)分配內存逢净,需要程序員申請內存、管理內存歼指,比如free〉粒現在有ARC): NSStackBlock 調用了Copy后,就是Malloc踩身。Malloc調用Copy后着饥,引用計數加1;
- NSStackBlock(_NSConcreteStackBlock)(棧段:局部變量惰赋,離開作用域自動銷毀):訪問了auto變量宰掉,默認就是Stack。(沒有ARC的情況下赁濒,因為ARC做了事情) NSStackBlock 調用了Copy轨奄,會從棧復制到堆,堆上的就變成了Malloc拒炎。
-
Block的Copy:在ARC環(huán)境下挪拟,編譯器會根據情況自動將棧上的block復制到堆上,比如以下情況:
- block作為函數返回值時击你;
- 將block賦值給__strong指針時玉组;
- block作為Cocoa API中方法名含有usingBlock的方法參數時;
- block作為GCD的方法參數時丁侄;
-
當block內部訪問了對象類型的auto變量時:
- 如果block是在棧上惯雳,將不會對auto變量產生強引用(不管是ARC、MRC都不會)
- 如果block被拷貝到堆上:
- 會調用block內部的Copy函數鸿摇;
- Copy函數內部會調用_Block_object_assign函數
- _Block_object_assign函數會根據auto變量的修飾符(__strong石景、__weak、__unsafe_unretained)做出相應的操作拙吉,類似于retain(形成強引用潮孽、弱引用)
- 如果block從堆上移除:
- 會調用block內部的dispose函數;
- dispose函數內部會調用_Block_object_dispose函數筷黔;
- _Block_object_dispose函數會自動釋放引用的auto變量往史,類似于release;
-
被__block修飾的對象類型:
__block MJPersion *person = [[MSPersion alloc] init];
- 當__block變量在棧上時佛舱,不會對指向的對象產生強引用椎例;
- 當__block變量Copy到堆時:
- 會調用__block變量內部的Copy函數挨决;
- Copy函數內部會調用_Block_object_assign函數;
- _Block_object_assign函數會根據所指向對象的修飾符(__strong粟矿、__weak凰棉、__unsafe_unretained)做出相應的操作,形成強引用(retain)或弱引用陌粹;(注意:這里僅限于ARC時會retain撒犀,MRC時不會retain)
- 如果__block變量從堆上移除:
- 會調用__block變量內部的dispose函數;
- dispose函數內部會調用_Block_object_dispose函數掏秩;
- _Block_object_dispose函數會自動釋放指向的對象(release)或舞;
-
__block的作用是什么?有什么使用注意點蒙幻?__block修飾符:
- __block可以用于解決block內部無法修改auto變量值的問題映凳;
- __block不能修飾全局變量、靜態(tài)變量(static)邮破;
- 編譯器會將__block變量包裝成一個對象诈豌;
-
__block的內存管理:
- 當block在棧上時,并不會對__block變量產生強引用抒和;
- 當block被Copy到堆時:
- 會調用block內部的Copy函數矫渔;
- Copy函數內部會調用_Block_object_assign函數;
- _Block_object_assign函數會對__block變量形成強引用(retain)(__block變量也會從棧上復制到堆上摧莽,是堆上的block對堆上的__block變量形成強引用)
- 當block從堆中移除時:
- 會調用block內部的dispose函數庙洼;
- dispose函數內部會調用_Block_object_dispose函數;
- _Block_object_dispose函數會自動釋放引用的__block變量(release)镊辕;
-
解決循環(huán)引用問題:
- ARC環(huán)境下:
- __weak解決:不會產生強引用油够,指向的對象銷毀時,會自動讓指針置為nil征懈;
- __unsafe_unretained解決:不會產生強引用石咬,不安全,指向的對象銷毀時受裹,指針存儲的地址值不變碌补;
- __block解決:必須要調用block,并且在block內部必須要把引用的對象置為nil棉饶;
- MRC環(huán)境下:不支持__weak;
- __unsafe_unretained解決镇匀;
- __block解決:不用調用block方法照藻,因為MRC下,__block變量不會對對象進行retain操作汗侵;
- ARC環(huán)境下:
-
block的屬性修飾詞為什么是copy幸缕?使用block有哪些使用注意群发?
- block一旦沒有進行Copy操作,就不會在堆上发乔;
- 使用注意:循環(huán)引用問題熟妓;
-
block在修飾NSMutableArray,需不需要添加__block栏尚?
- 不需要起愈;([array addObject:xxx],這個方法是對array里面的內容進行修改译仗,不是修改array本身抬虽,所以不需要。如果是修改array本身亏推,則是需要的公荧,比如在block里面執(zhí)行:array = nil融欧、array = [NSMutableArray alloc]);
-
isa詳解:
- 位域:
-
OC中的方法調用,其實都是轉換為objc_msgSend函數的調用笛辟。objc_msgSend執(zhí)行流程:
- 消息發(fā)送;
- 動態(tài)方法解析序苏;
- 消息轉發(fā)手幢;
-
[super message]的底層實現:
- 只是從父類開始查找方法的實現;
- 消息接收者仍然是子類對象杠览;
-
什么是Runtime弯菊?
- OC是一門動態(tài)性比較強的編程語言,允許很多操作推遲到程序運行時再進行踱阿;
- OC的動態(tài)性就是由Runtime來支撐和實現的管钳,Runtime是一套C語言的API,封裝了很多動態(tài)性相關的函數软舌;
- 平時編寫的OC代碼才漆,底層都是轉換成了RuntimeAPI進行調用;
-
Runtime平時項目中有用過么佛点?
- 利用關聯對象(AssociatedObject)給分類添加屬性醇滥;
- 遍歷類的所有成員變量(修改TextField的占位文字顏色、字典轉模型超营、自動歸檔解檔)鸳玩;
- 交換方法實現(交換系統(tǒng)的方法);
- 利用消息轉發(fā)機制解決方法找不到的異常演闭;
- weak的底層實現也是依賴于Runtime不跟;
- ...
類簇:NSString、NSArray米碰、NSDictionary窝革,真是類型是其他類型购城;
-
RunLoop的應用范疇?
- 定時器(Timer)虐译、PerformSelector瘪板;
- GCD Async Main Queue;
- 事件響應漆诽、手勢識別侮攀、界面刷新;
- 網絡請求拴泌;
- AutoreleasePool魏身;
-
RunLoop在實際開發(fā)中的應用:
- 控制線程生命周期(線程保活蚪腐,比如AFNetworking)箭昵;
- 解決NSTimer在滑動時停止工作的問題;
- 監(jiān)控應用卡頓回季;
- 性能優(yōu)化家制;
-
iOS中的常見多線程方案:
- pthread:
- C語言;
- 線程生命周期:程序員管理泡一;
- 簡介:
- 一套通用的多線程API颤殴;
- 適用于Unix、Linux鼻忠、Windows等系統(tǒng)涵但;
- 跨平臺、可移植帖蔓;
- 使用難度大矮瘟;
- 使用頻率:幾乎不用;
- NSThread塑娇;
- OC語言澈侠;
- 線程生命周期:程序員管理;
- 簡介:
- 使用更加面向對象埋酬;
- 簡單易用哨啃,可直接操作線程對象;
- 其實底層是pthread写妥;
- 使用頻率:幾乎不用拳球;
- GCD:
- C語言;
- 線程生命周期:自動管理珍特;
- 簡介:
- 旨在替代NSThread等線程技術醇坝;
- 充分利用設備的多核;
- 其實底層是pthread次坡;
- 使用頻率:經常使用呼猪;
- NSOperation:
- OC語言;
- 線程生命周期:自動管理砸琅;
- 簡介:
- 基于GCD(底層是GCD)宋距;
- 比GCD多了一些更簡單實用的功能;
- 使用更加面向對象症脂;
- 其實底層是pthread谚赎;
- 使用頻率:經常使用;
- pthread:
-
GCD的常用函數的執(zhí)行方式:
- 同步:dispatch_sync(dispatch_queue_t queue, dispatch_block_t block):
- queue:隊列诱篷;
- block:任務壶唤;
- 異步:dispatch_async(dispatch_queue_t queue, dispatch_block_t block):
- 同步:dispatch_sync(dispatch_queue_t queue, dispatch_block_t block):
-
GCD的隊列可以分為2大類型:
- 并發(fā)隊列(Concurrent Dispatch Queue):
- 可以讓多個任務并發(fā)(同時)執(zhí)行(自動開啟多線程同時執(zhí)行任務);
- 并發(fā)功能只有在異步(dispatch_async)函數下才有效棕所;
- 串行隊列(Serial Dispatch Queue)
- 讓任務一個接著一個地執(zhí)行(一個任務執(zhí)行完畢后闸盔,再執(zhí)行下一個任務);
- 主隊列:也是一種串行隊列琳省;
- 并發(fā)隊列(Concurrent Dispatch Queue):
-
容易混淆的術語:
-
同步和異步主要影響:能不能開啟新的線程迎吵;
- 同步:在當前線程中執(zhí)行任務,不具備開啟新線程的能力针贬;
- 異步:在新的線程中執(zhí)行任務击费,具備開啟新線程的能力;
-
并發(fā)和串行主要影響:任務的執(zhí)行方式桦他;
- 并發(fā):多個任務并發(fā)(同時)執(zhí)行蔫巩;
- 串行:一個任務執(zhí)行完畢后,再執(zhí)行下一個任務快压;
-
-
各種隊列的執(zhí)行效果:
- 同步(sync):
- 并發(fā)隊列:
- 沒有開啟新線程圆仔;
- 串行執(zhí)行任務;
- 串行隊列:
- 沒有開啟新線程嗓节;
- 串行執(zhí)行任務荧缘;
- 主隊列:
- 沒有開啟新線程;
- 串行執(zhí)行任務拦宣;
- 并發(fā)隊列:
- 異步(async):
- 并發(fā)隊列:
- 會開啟新線程截粗;
- 并發(fā)執(zhí)行任務;
- 手動創(chuàng)建的串行隊列:
- 會開啟新線程鸵隧;
- 串行執(zhí)行任務绸罗;
- 主隊列:
- 沒有開啟新線程;
- 串行執(zhí)行任務豆瘫;
- 并發(fā)隊列:
- 同步(sync):
使用sync函數往當前串行隊列中添加任務珊蟀,會卡住當前的串行隊列(產生死鎖);
-
多線程的安全隱患:
- 資源共享:
- 1塊資源可能會被多個線程共享,也就是多個線程可能會訪問同一塊資源育灸;
- 比如多個線程訪問同一個對象腻窒、同一個變量、同一個文件磅崭;
- 當多個線程訪問同一塊資源時儿子,很容易引發(fā)數據錯亂和數據安全問題;
- 資源共享:
-
多線程安全隱患的解決方案:
- 使用線程同步技術(同步砸喻,就是協(xié)同步調柔逼,按預定的先后次序進行運行);
- 常見的線程同步技術是:加鎖割岛;
- OSSpinLock:自旋鎖:等待鎖的線程會處于忙等(busy-wait)狀態(tài)愉适,一直占用著CPU資源;
- 目前已經不再安全癣漆,可能會出現優(yōu)先級反轉的問題维咸;如果等待鎖的線程優(yōu)先級較高,它會一直占用著CPU資源扑媚,優(yōu)先級低的線程就無法釋放鎖腰湾。
-
os_unfair_lock
:從底層調用看,等待os_unfair_lock
鎖的線程會處于休眠狀態(tài)疆股,并非忙等费坊。- 用于取代不安全的OSSpinLock,從iOS10開始才支持:
- pthread_mutex:互斥鎖:等待鎖的線程會處于休眠狀態(tài)旬痹;
- NSLock:是對pthread_mutex普通鎖的封裝附井;
- NSRecursiveLock:是對pthread_mutex遞歸鎖的封裝;
- NSCondition:是對pthread_mutex和cond(喚醒信號條件)的封裝两残;
- NSConditionLock:是對NSCondition的進一步封裝永毅,可以設置具體的喚醒條件值。
- dispatch_queue:直接使用GCD的串行隊列人弓,也是可以實現線程同步的沼死;
- dispatch_semaphore:信號量:信號量的初始值,可以用來控制線程并發(fā)訪問的最大數量崔赌;
- @synchronized:是對pthread_mutex遞歸鎖的封裝意蛀;
- 遞歸鎖:允許同一個線程對一把鎖進行重復加鎖;
- OSSpinLock:自旋鎖:等待鎖的線程會處于忙等(busy-wait)狀態(tài)愉适,一直占用著CPU資源;
-
iOS線程同步方案性能比較:性能從高到底排序:
-
os_unfair_lock
健芭; - OSSpinLock
- dispatch_semaphore县钥;
- pthread_mutex;
- dispatch_queue(DISPATCH_QUEUE_SERIAL)慈迈;
- NSLock若贮;
- NSCondition;
- pthread_mutex(recursive);
- NSRecursiveLock谴麦;
- NSConditionLock蠢沿;
- @synchronized;
-
-
自旋鎖细移、互斥鎖比較:
- 什么情況使用自旋鎖比較劃算搏予?
- 預計線程等待鎖的時間很短;
- 加鎖的代碼(臨界區(qū))經常被調用弧轧,但競爭情況很少發(fā)生;
- CPU資源不緊張碗殷;
- 多核處理精绎;
- 什么情況使用互斥鎖比較劃算?
- 預計線程等待鎖的時間較長锌妻;
- 單核處理器代乃;
- 臨界區(qū)有IO操作;
- 臨界區(qū)代碼復雜或循環(huán)量大仿粹;
- 臨界區(qū)競爭非常激烈搁吓;
- 什么情況使用自旋鎖比較劃算搏予?
-
atomic:
- 用于保證屬性setter、getter的原子性操作吭历,相當于在setter和getter內部加了線程同步的鎖堕仔;
- 它并不能保證使用屬性的過程是線程安全的;
-
iOS中的讀寫安全方案:
- 思考如何實現以下場景:
- 同一時間晌区,只能有1個線程進行寫的操作:
- 同一時間摩骨,允許有多個線程進行讀的操作:
- 同一時間,不允許既有寫的操作朗若,又有讀的操作恼五;
- 上面的場景就是典型的“多讀單寫”,經常用于文件等數據的讀寫操作哭懈,iOS中的實現方案有:
- pthread_rwlock:讀寫鎖灾馒;
- dispatch_barrier_async:異步柵欄調用:
- 這個函數傳入的并發(fā)隊列必須是自己通過dispatch_queue_create創(chuàng)建的;
- 如果傳入的是一個串行或是一個全局的并發(fā)隊列遣总,那這個函數便等同于dispatch_async函數的效果睬罗;
- 思考如何實現以下場景:
-
CADisplayLink、NSTimer使用注意:
- CADisplayLink彤避、NSTimer會對target產生強引用傅物,如果target又對它們產生強引用,那么就會引發(fā)循環(huán)引用琉预。
- CADisplayLink:保證調用頻率和屏幕的刷幀頻率一致董饰,60FPS(每秒60次)。但是會受主線程的影響,所以并不能保證每秒執(zhí)行60次卒暂;
- NSTimer:
-
GCD定時器:
- NSTimer依賴于RunLoop啄栓,如果RunLoop的任務過于繁重,可能會導致NSTimer不準時也祠;
- 而GCD的定時器會更加準時:跟內核掛鉤昙楚,并且不依賴于RunLoop;
-
iOS程序的內存布局:
- 保留區(qū)诈嘿;
- 代碼段堪旧;編譯之后的代碼;
- 數據段奖亚;
- 字符串常量:比如
NSString *str = @"123";
- 已初始化數據:已初始化的全局變量淳梦、靜態(tài)變量等;
- 未初始化數據:未初始化的全局變量昔字、靜態(tài)變量等爆袍;
- 字符串常量:比如
- 堆;通過alloc作郭、malloc陨囊、calloc等動態(tài)分配的空間;(分配地址:由低到高)
- 棧夹攒;函數調用開銷:比如函數里面的局部變量蜘醋;(分配地址:由高到低)
- 內核區(qū);
-
Tagged Pointer:
- 從64bit開始芹助,iOS引入了Tagged Pointer技術堂湖,用于優(yōu)化NSNumber、NSDate状土、NSString等小對象的存儲无蜂;
- 在沒有使用Tagged Pointer之前,NSNumber等對象需要動態(tài)分配內存蒙谓、維護引用計數等斥季,NSNumber指針存儲的是堆中NSNumber對象的地址值;
- 使用Tagged Pointer之后累驮,NSNumber指針里面存儲的數據變成了:Tag + Data酣倾,也就是將數據直接存儲在了指針中;
- 當指針不夠存儲數據時谤专,才會使用動態(tài)分配內存的方式來存儲數據躁锡;
- objc_msgSend能識別Tagged Pointer,比如NSNumber的intValue方法置侍,直接從指針提取數據映之,節(jié)省了以前的調用開銷拦焚;
- 如何判斷一個指針是否為Tagged Pointer?
- mac平臺:指針的最低有效位是1杠输;
- iOS平臺:指針的最高有效位是1赎败;(第64bit)
-
OC對象的內存管理:
- 在iOS中,使用引用計數來管理OC對象的內存蠢甲;
- 一個新創(chuàng)建的OC對象引用計數默認是1僵刮,當引用計數減為0,OC對象就會銷毀鹦牛,釋放其占用的內存空間搞糕;
- 調用retain會讓OC對象的引用計數+1,調用release會讓OC對象的引用計數-1能岩;
- 內存管理的經驗總結:
- 當調用alloc寞宫、new、copy拉鹃、mutableCopy方法返回了一個對象,在不需要這個對象時鲫忍,要調用release或autorelease來釋放它膏燕;
- 想擁有某個對象,就讓它的引用計數+1悟民;
- 不想擁有某個對象坝辫,就讓它的引用計數-1;
拷貝的目的:產生一個副本對象射亏,跟源對象互不影響近忙;修改了源對象,不會影響副本對象智润。修改了副本對象及舍,不會影響源對象。
-
深拷貝窟绷、淺拷貝:
- 深拷貝:
- 內容拷貝锯玛,有產生新對象;
- 淺拷貝:
- 指針拷貝兼蜈,沒有產生新對象攘残;
- 深拷貝:
-
copy和mutableCopy:NSString、NSMutableString为狸、NSArray歼郭、NSMutableArray、NSDictionary辐棒、NSMutableDictionary:
- 不可變對象:copy病曾,還是不可變對象牍蜂,跟原來指向同一個內存地址,是淺拷貝知态;mutable捷兰,是可變對象,是深拷貝负敏;
- 可變對象:copy贡茅,是不可變,是深拷貝其做;mutable顶考,是可變對象,是深拷貝妖泄;
-
引用計數的存儲:
- 在64bit中驹沿,引用計數可以直接存儲在優(yōu)化過的isa指針中,也可能存儲在sideTable類中蹈胡;
- sideTable是一個存放著對象引用計數的散列表渊季;
-
weak指針的實現原理:
- 將弱引用存到哈希表里面,當對象要銷毀時罚渐,就去除該對象對應的弱引用表却汉,把弱引用表里面存儲的弱引用都清除掉;
-
autorelease對象在什么時機會被調用release荷并?
- iOS在主線程的RunLoop中注冊了2個Observer合砂;
- 第1個Observer監(jiān)聽了kCFRunLoopEntry事件,會調用objc_autoreleasePoolPush()源织;
- 第2個Observer:
- 監(jiān)聽了kCFRunLoopBeforeWaiting事件翩伪,會調用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()谈息;
- 監(jiān)聽了kCFRunLoopBeforeExit事件缘屹,會調用objc_autoreleasePoolPop();
- iOS在主線程的RunLoop中注冊了2個Observer合砂;
-
方法里有局部對象黎茎,出了方法后會立即釋放嗎囊颅?
- 如果局部對象是通過autorelease釋放的話,不是立即釋放傅瞻,是在對象所處的RunLoop休眠前釋放踢代;
- 如果ARC是生成release代碼的話,是立即釋放嗅骄;
-
卡頓產生的原因:CPU胳挎、GPU執(zhí)行了比較耗時的操作:
- CPU(Central Processing Unit,中央處理器):
- 對象的創(chuàng)建和銷毀溺森;
- 對象屬性的調整慕爬;
- 布局計算窑眯;
- 文本的計算和排版、圖片的格式轉換和解碼医窿;
- 圖像的繪制(Core Graphics)磅甩;
- GPU(Graphics Processing Unit,圖形處理器):
- 紋理的渲染姥卢;
- CPU(Central Processing Unit,中央處理器):
在iOS中是雙緩沖機制:有前幀緩存卷要、后幀緩存;
-
卡頓解決的主要思路:
- 盡可能減少CPU独榴、GPU資源消耗僧叉;
按照60FPS的刷幀率,每隔16ms就會有一次VSync(垂直同步)信號棺榔;
-
卡頓優(yōu)化:
- CPU:
- 盡量用輕量級的對象瓶堕,比如用不到事件處理的地方,可以考慮使用CALayer取代UIView症歇;
- 不要頻繁的調用UIView的相關屬性郎笆,比如frame、bounds忘晤、transform等屬性题画,盡量減少不必要的修改;
- 盡量提前計算好布局德频,在有需要時一次性調整對應的屬性,不要多次修改屬性缩幸;
- Autolayout會比直接設置frame消耗更多的CPU資源壹置;
- 圖片的size最好剛好跟UIImageView的size保持一致;
- 控制一下線程的最大并發(fā)數量表谊;
- 盡量把耗時的操作放到子線程钞护;
- 文本處理(尺寸計數、繪制)爆办;
- 圖片處理(解碼难咕、繪制)
- GPU:
- 盡量避免短時間內大量圖片的顯示,盡可能將多張圖片合成一張進行顯示距辆;
- GPU能處理的最大紋理尺寸是4096x4096余佃,一旦超過這個尺寸,就會占用CPU資源進行處理跨算,所以紋理盡量不要超過這個尺寸爆土;
- 盡量減少視圖數量和層次;
- 減少透明的視圖(alpha < 1)诸蚕,不透明的就設置opaque為YES步势;
- 盡量避免出現離屏渲染氧猬;
- CPU:
-
離屏渲染:
- 在OpenGL中,GPU有2種渲染方式:
- On-Screen Rendering:當前屏幕渲染坏瘩,在當前用于顯示的屏幕緩沖區(qū)進行渲染操作盅抚;
- Off-Screen Rendering:離屏渲染,在當前屏幕緩沖區(qū)以外新開辟一個緩沖區(qū)進行渲染操作倔矾;
- 離屏渲染消耗性能的原因:
- 需要創(chuàng)建新的緩沖區(qū)妄均;
- 離屏渲染的整個過程,需要多次切換上下文環(huán)境破讨,先是從當前屏幕(On-Screen)切換到離屏(Off-Screen)丛晦;等到離屏渲染結束以后,將離屏緩沖區(qū)的渲染結果顯示到屏幕上提陶,又需要將上下文環(huán)境從離屏切換到當前屏幕烫沙;
- 哪些操作會觸發(fā)離屏渲染?
- 光柵化:layer.shouldRasterize = YES隙笆;
- 遮罩:layer.mask锌蓄;
- 圓角:同時設置layer.masksToBounds = YES、layer.cornerRadius > 0
- 優(yōu)化:可以考慮通過CoreGraphics繪制裁剪圓角撑柔,或者叫美工提供圓角圖片;
- 陰影:layer.shadowXXX瘸爽;
- 但是如果陰影設置了路線:layer.shadowPath就不會產生離屏渲染;
- 在OpenGL中,GPU有2種渲染方式:
-
卡頓檢測:
- 平時所說的卡頓铅忿,主要是因為在主線程執(zhí)行了比較耗時的操作剪决;
- 可以添加Observer到主線程RunLoop中,通過監(jiān)聽RunLoop狀態(tài)切換的耗時檀训,以達到監(jiān)控卡頓的目的柑潦;
- 有封裝好的可以參考:
LXDAppFluecyMonitor
;
知識點
- 花指令:破解峻凫、反匯編渗鬼;