1.KVC的底層實(shí)現(xiàn)组民?
當(dāng)一個(gè)對(duì)象調(diào)用setValue方法時(shí)黄伊,方法內(nèi)部會(huì)做以下操作:
1). 檢查是否存在相應(yīng)的key的set方法舱痘,如果存在徘钥,就調(diào)用set方法衔蹲。
2). 如果set方法不存在,就會(huì)查找與key相同名稱(chēng)并且?guī)聞澗€(xiàn)的成員變量呈础,如果有舆驶,則直接給成員變量屬性賦值。
3). 如果沒(méi)有找到_key而钞,就會(huì)查找相同名稱(chēng)的屬性key沙廉,如果有就直接賦值。
4). 如果還沒(méi)有找到臼节,則調(diào)用valueForUndefinedKey:和setValue:forUndefinedKey:方法蓝仲。
這些方法的默認(rèn)實(shí)現(xiàn)都是拋出異常,我們可以根據(jù)需要重寫(xiě)它們官疲。
2.KVO的底層實(shí)現(xiàn)袱结?
KVO-鍵值觀(guān)察機(jī)制,原理如下:
1.當(dāng)給A類(lèi)添加KVO的時(shí)候途凫,runtime動(dòng)態(tài)的生成了一個(gè)子類(lèi)NSKVONotifying_A垢夹,讓A類(lèi)的isa指針指向NSKVONotifying_A類(lèi),重寫(xiě)class方法维费,隱藏對(duì)象真實(shí)類(lèi)信息
2.重寫(xiě)監(jiān)聽(tīng)屬性的setter方法果元,在setter方法內(nèi)部調(diào)用了Foundation 的 _NSSetObjectValueAndNotify 函數(shù)
3._NSSetObjectValueAndNotify函數(shù)內(nèi)部
a) 首先會(huì)調(diào)用 willChangeValueForKey
b) 然后給屬性賦值
c) 最后調(diào)用 didChangeValueForKey
d) 最后調(diào)用 observer 的 observeValueForKeyPath 去告訴監(jiān)聽(tīng)器屬性值發(fā)生了改變 .
4.重寫(xiě)了dealloc做一些 KVO 內(nèi)存釋放
3.說(shuō)一下工作中你怎么做性能優(yōu)化的
#造成tableView卡頓的原因
1.沒(méi)有使用cell的重用標(biāo)識(shí)符促王,導(dǎo)致一直創(chuàng)建新的cell
2.cell的重新布局
3.沒(méi)有提前計(jì)算并緩存cell的屬性及內(nèi)容
4.cell中控件的數(shù)量過(guò)多
5.使用了ClearColor,無(wú)背景色而晒,透明度為0
6.更新只使用tableView.reloadData()(如果只是更新某組的話(huà)蝇狼,使用reloadSection進(jìn)行局部更新)
7.加載網(wǎng)絡(luò)數(shù)據(jù),下載圖片倡怎,沒(méi)有使用異步加載迅耘,并緩存
8.使用addView 給cell動(dòng)態(tài)添加view
9.沒(méi)有按需加載cell(cell滾動(dòng)很快時(shí),只加載范圍內(nèi)的cell)
10.實(shí)現(xiàn)無(wú)用的代理方法(tableView只遵守兩個(gè)協(xié)議)
11.沒(méi)有做緩存行高(estimatedHeightForRow不能和HeightForRow里面的layoutIfNeed同時(shí)存在监署,這兩者同時(shí)存在才會(huì)出現(xiàn)“竄動(dòng)”的bug颤专。
建議是:只要是固定行高就寫(xiě)預(yù)估行高來(lái)減少行高調(diào)用次數(shù)提升性能。如果是動(dòng)態(tài)行高就不要寫(xiě)預(yù)估方法了钠乏,用一個(gè)行高的緩存字典來(lái)減少代碼的調(diào)用次數(shù)即可)
12.做了多余的繪制工作(在實(shí)現(xiàn)drawRect:的時(shí)候栖秕,它的rect參數(shù)就是需要繪制的區(qū)域,這個(gè)區(qū)域之外的不需要進(jìn)行繪制)
13.沒(méi)有預(yù)渲染圖像晓避。(當(dāng)新的圖像出現(xiàn)時(shí)簇捍,仍然會(huì)有短暫的停頓現(xiàn)象。解決的辦法就是在bitmap context里先將其畫(huà)一遍俏拱,導(dǎo)出成UIImage對(duì)象垦写,然后再繪制到屏幕)
#提升tableView的流暢度
*本質(zhì)上是降低 CPU、GPU 的工作彰触,從這兩個(gè)大的方面去提升性能。
1.CPU:對(duì)象的創(chuàng)建和銷(xiāo)毀命辖、對(duì)象屬性的調(diào)整况毅、布局計(jì)算、文本的計(jì)算和排版尔艇、圖片的格式轉(zhuǎn)換和解碼尔许、圖像的繪制
2.GPU:紋理的渲染
#卡頓優(yōu)化在 CPU 層面
1.盡量用輕量級(jí)的對(duì)象,比如用不到事件處理的地方终娃,可以考慮使用 CALayer 取代 UIView
2.不要頻繁地調(diào)用 UIView 的相關(guān)屬性味廊,比如 frame、bounds棠耕、transform 等屬性余佛,盡量減少不必要的修改
3.盡量提前計(jì)算好布局,在有需要時(shí)一次性調(diào)整對(duì)應(yīng)的屬性窍荧,不要多次修改屬性
4.Autolayout 會(huì)比直接設(shè)置 frame 消耗更多的 CPU 資源
5.圖片的 size 最好剛好跟 UIImageView 的 size 保持一致
6.控制一下線(xiàn)程的最大并發(fā)數(shù)量
7.盡量把耗時(shí)的操作放到子線(xiàn)程
8.文本處理(尺寸計(jì)算辉巡、繪制)
9.圖片處理(解碼、繪制)
#卡頓優(yōu)化在 GPU層面
1.盡量避免短時(shí)間內(nèi)大量圖片的顯示蕊退,盡可能將多張圖片合成一張進(jìn)行顯示
2.GPU能處理的最大紋理尺寸是 4096x4096郊楣,一旦超過(guò)這個(gè)尺寸憔恳,就會(huì)占用 CPU 資源進(jìn)行處理,所以紋理盡量不要超過(guò)這個(gè)尺寸
3.盡量減少視圖數(shù)量和層次
4.減少透明的視圖(alpha<1)净蚤,不透明的就設(shè)置 opaque 為 YES
5.盡量避免出現(xiàn)離屏渲染
5.Runtime實(shí)現(xiàn)的機(jī)制是什么钥组?能做什么事情呢?
runtime簡(jiǎn)稱(chēng)運(yùn)行時(shí)今瀑。OC是運(yùn)行時(shí)機(jī)制程梦,也就是在運(yùn)行時(shí)才做一些處理。例如:C語(yǔ)言在編譯的時(shí)候就知道要調(diào)用
哪個(gè)方法函數(shù)放椰,而OC在編譯的時(shí)候并不知道要調(diào)用哪個(gè)方法函數(shù)作烟,只有在運(yùn)行的時(shí)候才知道調(diào)用的方法函數(shù)名稱(chēng),
來(lái)找到對(duì)應(yīng)的方法函數(shù)進(jìn)行調(diào)用砾医。
1.發(fā)送消息
【場(chǎng)景:方法調(diào)用】
2.交換方法實(shí)現(xiàn)(交換系統(tǒng)的方法)
【場(chǎng)景:當(dāng)?shù)谌娇蚣芑蛘呦到y(tǒng)原生方法功能不能滿(mǎn)足我們的時(shí)候拿撩,我們可以在保持系統(tǒng)原有方法功能的基礎(chǔ)上,添加額外的功能如蚜⊙购悖】
3.動(dòng)態(tài)添加方法
【場(chǎng)景:如果一個(gè)類(lèi)方法非常多,加載類(lèi)到內(nèi)存的時(shí)候也比較耗費(fèi)資源错邦,需要給每個(gè)方法生成映射表探赫,可以使用動(dòng)態(tài)給某個(gè)類(lèi),添加方法解決撬呢÷追停】
4.利用關(guān)聯(lián)對(duì)象(AssociatedObject)給分類(lèi)添加屬性
【場(chǎng)景:分類(lèi)是不能自定義屬性和變量的,這時(shí)候可以使用runtime動(dòng)態(tài)添加屬性方法魂拦;
原理:給一個(gè)類(lèi)聲明屬性毛仪,其實(shí)本質(zhì)就是給這個(gè)類(lèi)添加關(guān)聯(lián),并不是直接把這個(gè)值的內(nèi)存空間添加到類(lèi)存空間芯勘。 】
5.遍歷類(lèi)的所有成員變量
【1.NSCoding自動(dòng)歸檔解檔
場(chǎng)景:如果一個(gè)模型有許多個(gè)屬性箱靴,實(shí)現(xiàn)自定義模型數(shù)據(jù)持久化時(shí),需要對(duì)每個(gè)屬性都實(shí)現(xiàn)一遍encodeObject 和 decodeObjectForKey方法荷愕,比較麻煩衡怀。我們可以使用Runtime來(lái)解決。
原理:用runtime提供的函數(shù)遍歷Model自身所有屬性安疗,并對(duì)屬性進(jìn)行encode和decode操作抛杨。
2.字典轉(zhuǎn)模型
原理:利用Runtime,遍歷模型中所有屬性荐类,根據(jù)模型的屬性名蝶桶,去字典中查找key,取出對(duì)應(yīng)的值掉冶,給模型的屬性賦值真竖。
3.訪(fǎng)問(wèn)私有變量
場(chǎng)景:修改textfield的占位文字顏色】
6.利用消息轉(zhuǎn)發(fā)機(jī)制解決方法找不到的異常問(wèn)題
6.iOS圖片設(shè)置圓角性能問(wèn)題
1.直接使用setCornerRadius
【這樣設(shè)置會(huì)觸發(fā)離屏渲染脐雪,比較消耗性能。比如當(dāng)一個(gè)頁(yè)面上有十幾頭像這樣設(shè)置了圓角會(huì)明顯感覺(jué)到卡頓恢共。
注意:png圖片UIImageView處理圓角是不會(huì)產(chǎn)生離屏渲染的战秋。(ios9.0之后不會(huì)離屏渲染,ios9.0之前還是會(huì)離屏渲染)】
2.setCornerRadius設(shè)置圓角之后讨韭,shouldRasterize=YES光柵化
【avatarImageView.layer.shouldRasterize = YES;
avatarImageViewUrl.layer.rasterizationScale=[UIScreen mainScreen].scale; //UIImageView不加這句會(huì)產(chǎn)生一點(diǎn)模糊
shouldRasterize=YES設(shè)置光柵化脂信,可以使離屏渲染的結(jié)果緩存到內(nèi)存中存為位圖,
使用的時(shí)候直接使用緩存透硝,節(jié)省了一直離屏渲染損耗的性能狰闪。
但是如果layer及sublayers常常改變的話(huà),它就會(huì)一直不停的渲染及刪除緩存重新
創(chuàng)建緩存濒生,所以這種情況下建議不要使用光柵化埋泵,這樣也是比較損耗性能的∽镏危】
3.直接覆蓋一張中間為圓形透明的圖片(推薦使用)
4.UIImage drawInRect繪制圓角
【這種方式GPU損耗低內(nèi)存占用大丽声,而且UIButton上不知道怎么繪制,可以用
UIimageView添加個(gè)點(diǎn)擊手勢(shì)當(dāng)做UIButton使用觉义⊙闵纾】
5.SDWebImage處理圖片時(shí)Core Graphics繪制圓角(暫時(shí)感覺(jué)是最優(yōu)方法)
7.什么是 RunLoop?
從字面上講就是運(yùn)行循環(huán)晒骇,它內(nèi)部就是do-while循環(huán)霉撵,在這個(gè)循環(huán)內(nèi)部不斷地處理各種任務(wù)。
一個(gè)線(xiàn)程對(duì)應(yīng)一個(gè)RunLoop洪囤,基本作用就是保持程序的持續(xù)運(yùn)行徒坡,處理app中的各種事件。通過(guò)runloop箍鼓,有事運(yùn)行,沒(méi)事就休息呵曹,可以節(jié)省cpu資源款咖,提高程序性能。
主線(xiàn)程的run loop默認(rèn)是啟動(dòng)的奄喂。iOS的應(yīng)用程序里面铐殃,程序啟動(dòng)后會(huì)有一個(gè)如下的main()函數(shù)
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
@RunLoop,是多線(xiàn)程的法寶跨新,即一個(gè)線(xiàn)程一次只能執(zhí)行一個(gè)任務(wù)富腊,執(zhí)行完任務(wù)后就會(huì)退出線(xiàn)程。主線(xiàn)程執(zhí)行完即
時(shí)任務(wù)時(shí)會(huì)繼續(xù)等待接收事件而不退出域帐。非主線(xiàn)程通常來(lái)說(shuō)就是為了執(zhí)行某一任務(wù)的赘被,執(zhí)行完畢就需要?dú)w還資源是整,
因此默認(rèn)是不運(yùn)行RunLoop的;
@每一個(gè)線(xiàn)程都有其對(duì)應(yīng)的RunLoop民假,只是默認(rèn)只有主線(xiàn)程的RunLoop是啟動(dòng)的浮入,其它子線(xiàn)程的RunLoop默認(rèn)是不
啟動(dòng)的,若要啟動(dòng)則需要手動(dòng)啟動(dòng)羊异;
@在一個(gè)單獨(dú)的線(xiàn)程中事秀,如果需要在處理完某個(gè)任務(wù)后不退出,繼續(xù)等待接收事件野舶,則需要啟用RunLoop易迹;
@NSRunLoop提供了一個(gè)添加NSTimer的方法,可以指定Mode平道,如果要讓任何情況下都回調(diào)睹欲,則需要設(shè)置Mode為Common模式;
實(shí)質(zhì)上巢掺,對(duì)于子線(xiàn)程的runloop默認(rèn)是不存在的句伶,因?yàn)樘O(píng)果采用了懶加載的方式。如果我們沒(méi)有手動(dòng)調(diào)用
[NSRunLoop currentRunLoop]的話(huà)陆淀,就不會(huì)去查詢(xún)是否存在當(dāng)前線(xiàn)程的RunLoop考余,也就不會(huì)去加載,更不會(huì)
創(chuàng)建轧苫。
8.以scheduledTimerWithTimeInterval的方式觸發(fā)的timer楚堤,在滑動(dòng)頁(yè)面上的列表時(shí),timer會(huì)暫停含懊,為什么身冬?該如何解決?
原因在于滑動(dòng)時(shí)當(dāng)前線(xiàn)程的runloop切換了mode用于列表滑動(dòng)岔乔,導(dǎo)致timer暫停酥筝。
runloop中的mode主要用來(lái)指定事件在runloop中的優(yōu)先級(jí),有以下幾種:
* Default(NSDefaultRunLoopMode):默認(rèn)雏门,一般情況下使用嘿歌;
* Connection(NSConnectionReplyMode):一般系統(tǒng)用來(lái)處理NSConnection相關(guān)事件,開(kāi)發(fā)者一般用不到茁影;
* Modal(NSModalPanelRunLoopMode):處理modal panels事件宙帝;
* Event Tracking(NSEventTrackingRunLoopMode):用于處理拖拽和用戶(hù)交互的模式。
* Common(NSRunloopCommonModes):模式合集募闲。默認(rèn)包括Default步脓,Modal,Event Tracking三大模式,可以處理幾乎所有事件靴患。
回到題中的情境仍侥。滑動(dòng)列表時(shí)蚁廓,runloop的mode由原來(lái)的Default模式切換到了Event Tracking模式访圃,timer原來(lái)好好的運(yùn)行在Default模式中,被關(guān)閉后自然就停止工作了相嵌。
解決方法其一是將timer加入到NSRunloopCommonModes中腿时。其二是將timer放到另一個(gè)線(xiàn)程中,然后開(kāi)啟另一個(gè)線(xiàn)程的runloop饭宾,這樣可以保證與主線(xiàn)程互不干擾批糟,而現(xiàn)在主線(xiàn)程正在處理頁(yè)面滑動(dòng)。
方法1
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
方法2
dispatch_async(dispatch_get_global_queue(0, 0), ^{
timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(repeat:) userInfo:nil repeats:true];
[[NSRunLoop currentRunLoop] run];
});
9.進(jìn)程與線(xiàn)程
進(jìn)程:
1.進(jìn)程是一個(gè)具有一定獨(dú)立功能的程序關(guān)于某次數(shù)據(jù)集合的一次運(yùn)行活動(dòng)看铆,它是操作系統(tǒng)分配資源的基本單元.
2.進(jìn)程是指在系統(tǒng)中正在運(yùn)行的一個(gè)應(yīng)用程序徽鼎,就是一段程序的執(zhí)行過(guò)程,我們可以理解為手機(jī)上的一個(gè)app.
3.每個(gè)進(jìn)程之間是獨(dú)立的,每個(gè)進(jìn)程均運(yùn)行在其專(zhuān)用且受保護(hù)的內(nèi)存空間內(nèi)弹惦,擁有獨(dú)立運(yùn)行所需的全部資源
線(xiàn)程
1.程序執(zhí)行流的最小單元否淤,線(xiàn)程是進(jìn)程中的一個(gè)實(shí)體.
2.一個(gè)進(jìn)程要想執(zhí)行任務(wù),必須至少有一條線(xiàn)程.應(yīng)用程序啟動(dòng)的時(shí)候,系統(tǒng)會(huì)默認(rèn)開(kāi)啟一條線(xiàn)程,也就是主線(xiàn)程
進(jìn)程和線(xiàn)程的關(guān)系
1.線(xiàn)程是進(jìn)程的執(zhí)行單元棠隐,進(jìn)程的所有任務(wù)都在線(xiàn)程中執(zhí)行
2.線(xiàn)程是 CPU 分配資源和調(diào)度的最小單位
3.一個(gè)程序可以對(duì)應(yīng)多個(gè)進(jìn)程(多進(jìn)程),一個(gè)進(jìn)程中可有多個(gè)線(xiàn)程,但至少要有一條線(xiàn)程
4.同一個(gè)進(jìn)程內(nèi)的線(xiàn)程共享進(jìn)程資源
10.iOS中實(shí)現(xiàn)多線(xiàn)程的幾種方案石抡,各自有什么特點(diǎn)?講一下具體使用場(chǎng)景/在項(xiàng)目什么時(shí)候選擇使用 GCD助泽,什么時(shí)候選 擇 NSOperation?
pthread 是一套通用的多線(xiàn)程的 API啰扛,可以在Unix / Linux / Windows 等系統(tǒng)跨平臺(tái)使用,使用 C 語(yǔ)言編寫(xiě)嗡贺,
需要程序員自己管理線(xiàn)程的生命周期隐解,使用難度較大,我們?cè)?iOS 開(kāi)發(fā)中幾乎不使用 pthread
NSThread 面向?qū)ο蟮慕氩牵枰绦騿T手動(dòng)創(chuàng)建線(xiàn)程煞茫,但不需要手動(dòng)銷(xiāo)毀。子線(xiàn)程間通信很難摄凡。
GCD c語(yǔ)言续徽,充分利用了設(shè)備的多核,自動(dòng)管理線(xiàn)程生命周期架谎。比NSOperation效率更高炸宵。
NSOperation 基于gcd封裝辟躏,更加面向?qū)ο蠊瓤郏萭cd多了一些功能。
場(chǎng)景:1.多個(gè)網(wǎng)絡(luò)請(qǐng)求完成后執(zhí)行下一步
2.多個(gè)網(wǎng)絡(luò)請(qǐng)求順序執(zhí)行后執(zhí)行下一步
3.異步操作兩組數(shù)據(jù)時(shí), 執(zhí)行完第一組之后, 才能執(zhí)行第二組
@項(xiàng)目中使用 NSOperation 的優(yōu)點(diǎn)是 NSOperation 是對(duì)線(xiàn)程的高度抽象,在項(xiàng)目中使 用它会涎,會(huì)使項(xiàng)目的程序
結(jié)構(gòu)更好裹匙,子類(lèi)化 NSOperation 的設(shè)計(jì)思路,是具有面向?qū)?象的優(yōu)點(diǎn)(復(fù)用末秃、封裝)概页,使得實(shí)現(xiàn)是多線(xiàn)程支持,而接口簡(jiǎn)單练慕,建議在復(fù)雜項(xiàng)目中 使用惰匙。
@項(xiàng)目中使用 GCD 的優(yōu)點(diǎn)是 GCD 本身非常簡(jiǎn)單、易用铃将,對(duì)于不復(fù)雜的多線(xiàn)程操 作项鬼,會(huì)節(jié)省代碼量,而 Block
參數(shù)的使用劲阎,會(huì)是代碼更為易讀绘盟,建議在簡(jiǎn)單項(xiàng)目中 使用。