1.一個(gè)objc對象的isa的指針指向什么?有什么作用?
指向他的類對象,從而可以找到對象上的方法
2.一個(gè) NSObject 對象占用多少內(nèi)存空間?
*一個(gè)NSObject對象占用的大小其實(shí)就是一個(gè)isa指針的大小。在64bit是8字節(jié)。 但是!!系統(tǒng)* *真正分配內(nèi)存的時(shí)候是分配了16字節(jié)!*
3.說一下對 class_rw_t 的理解?
rw代表可讀可寫,class_rw_t 中保存了Objc類的屬性,方法,遵循的協(xié)議等信息
// 可讀可寫
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
const class_ro_t *ro; // 指向只讀的結(jié)構(gòu)體,存放類初始信息
/*
這三個(gè)都是二位數(shù)組,是可讀可寫的,包含了類的初始內(nèi)容、分類的內(nèi)容。
methods中,存儲(chǔ) method_list_t ----> method_t
二維數(shù)組,method_list_t --> method_t
這三個(gè)二位數(shù)組中的數(shù)據(jù)有一部分是從class_ro_t中合并過來的。
*/
method_array_t methods; // 方法列表(類對象存放對象方法,元類對象存放類方法)
property_array_t properties; // 屬性列表
protocol_array_t protocols; //協(xié)議列表
Class firstSubclass;
Class nextSiblingClass;
//...
}
3.說一下對 class_ro_t 的理解?
存儲(chǔ)了當(dāng)前類在編譯器就已經(jīng)確定的屬性,方法以及遵循的協(xié)議
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
uint32_t reserved;
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
};
4.說一下對 isa 指針的理解, 對象的isa 指針指向哪里?isa 指針有哪兩種類型?
isa等價(jià)于is kind of,*實(shí)例對象isa指向類對象,類對象isa指向元類對象,元類對象isa指向元類的基類。
isa有兩種,1*純指針,指向內(nèi)存地址 2**NON_POINTER_ISA,除了內(nèi)存地址,還存有一些其他信息
5.實(shí)例對象的數(shù)據(jù)結(jié)構(gòu)?
本質(zhì)上objc_object的私有屬性只有一個(gè)isa指針,指向類對象的內(nèi)存地址
6.什么是method swizzling(俗稱黑魔法)
簡單來說就是方法交換,每個(gè)類都有一個(gè)方法列表,存著方法名字和實(shí)現(xiàn)的映射關(guān)系,selector的本質(zhì)其實(shí)就是方法名,IMP有點(diǎn)類似函數(shù)指針,指向具體的Method實(shí)現(xiàn),通過selector就可以找到對應(yīng)的IMP。換方法的幾種方式利用 method_exchangeImplementations 交換兩個(gè)方法的實(shí)現(xiàn),利用 class_replaceMethod替換方法的實(shí)現(xiàn),利用 method_setImplementation 來直接設(shè)置某個(gè)方法的IMP
7.什么時(shí)候會(huì)報(bào)unrecognized selector的異常?
objc在向一個(gè)對象發(fā)送消息時(shí),runtime庫會(huì)根據(jù)isa指針找到該對象實(shí)際所屬的類,然后再該類的方法列表及父類方法列表中尋找方法運(yùn)行,如果在最頂層父類依然找不到對應(yīng)方法,會(huì)進(jìn)入消息轉(zhuǎn)發(fā)階段,如果消息三次轉(zhuǎn)發(fā)流程任然未實(shí)現(xiàn),程序會(huì)拋出這個(gè)異常。
8.如何給 Category 添加屬性?關(guān)聯(lián)對象以什么形式進(jìn)行存儲(chǔ)?
關(guān)聯(lián)對象以哈希表的格式,存儲(chǔ)在一個(gè)全局的單例中
9.能否向編譯后得到的類中增加實(shí)例變量?能否向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量?為什么?
不能向編譯后得到的類中添加實(shí)例變量,能向運(yùn)行時(shí)創(chuàng)建的類中添加實(shí)例變量
1.因?yàn)榫幾g后的類已經(jīng)注冊在runtime中,類結(jié)構(gòu)體中的實(shí)例變量離岸邊和內(nèi)存大小已確定,同時(shí)runtime會(huì)調(diào)用class_setvarlayout 或 class_setWeaklvarLayout 來處理strong weak 引用,所以不能添加
2.運(yùn)行時(shí)創(chuàng)建的類是可以添加實(shí)例變量,調(diào)用class_addIvar函數(shù). 但是的在調(diào)用 objc_allocateClassPair 之后,objc_registerClassPair 之前,原因同上.
10.類對象的數(shù)據(jù)結(jié)構(gòu)?
類對象就是objc_class,是一個(gè)繼承自objc_object結(jié)構(gòu)體,所以包含isa指針
isa:指向元類
superClass:指向父類
Cache:方法的緩存列表
data:數(shù)據(jù),是一個(gè)被封裝好的class_rw_t
structobjc_class:objc_object{// Class ISA;Class superclass;//父類指針cache_t cache;// formerly cache pointer and vtable 方法緩存class_data_bits_t bits;// class_rw_t * plus custom rr/alloc flags 用于獲取地址class_rw_t*data(){returnbits.data();// &FAST_DATA_MASK 獲取地址值}
11.runtime如何通過selector找到對應(yīng)的IMP地址?
每個(gè)類對象都有一個(gè)方法列表,方法列表中記錄著方法的名稱,實(shí)現(xiàn)一鍵參數(shù)類型,通過方法名稱就可以在列表中找出對應(yīng)的方法實(shí)現(xiàn)
12.objc中向一個(gè)nil對象發(fā)送消息將會(huì)發(fā)生什么?
向一個(gè)nil對象發(fā)送消息,在尋找對象的isa指針的時(shí)候就0地址返回了,不會(huì)出現(xiàn)任何錯(cuò)誤
詳解:
如果一個(gè)方法返回值是一個(gè)對象,那么發(fā)送給nil的消息將返回0(nil);
如果方法返回值為指針類型,其指針大小為小于或者等于sizeof(void*) ,float,double,long double 或者long long的整型標(biāo)量,發(fā)送給nil的消息將返回0;
如果方法返回值為結(jié)構(gòu)體,發(fā)送給nil的消息將返回0。結(jié)構(gòu)體中各個(gè)字段的值將都是0;
如果方法的返回值不是上述提到的幾種情況,那么發(fā)送給nil的消息的返回值將是未定義的。
13.objc在向一個(gè)對象發(fā)送消息時(shí),發(fā)生了什么?
runtime會(huì)根據(jù)對象的isa指針找到該對象所屬的類,然后再方法列表以及父類方法列表中尋找方法實(shí)現(xiàn),如果一直到根類還沒找到,轉(zhuǎn)向攔截調(diào)用,走消息轉(zhuǎn)發(fā)機(jī)制,一旦找到,就去執(zhí)行他的IMP
14.isKindOfClass 與 isMemberOfClass
isKindOfClass 是確定一個(gè)對象是否是一個(gè)類的成員,或者是派生字該類
isMemberOfClass確定一個(gè)對象是否是當(dāng)前類的成員
15.Category 在編譯過后,是在什么時(shí)機(jī)與原有的類合并到一起的?
1.程序啟動(dòng)后,通過編譯后,runtime初始化,調(diào)用_objc_init
2.然后會(huì)map_images
3.接下來調(diào)用map_images_nolock
4.然后read_images,讀取所有的類的相關(guān)信息
5.最后調(diào)用reMethodizeClass,進(jìn)行重新方法化
6.在reMethodizeClass內(nèi)部會(huì)調(diào)用attachCategorise,這個(gè)方法會(huì)傳入Class和Category,講方法和協(xié)議列表與原有的類合并,最后加入到class_rw_t結(jié)構(gòu)體中
16.Category 有哪些用途?
給系統(tǒng)類添加方法和屬性(需要關(guān)聯(lián)對象),對某個(gè)類大量方法,可以實(shí)現(xiàn)按照不同的名稱歸類
17.為什么 NSTimer 有時(shí)候不好使?
因?yàn)閯?chuàng)建的NSTimer默認(rèn)被加入到defaultMode,當(dāng)Runloop的Mode變化時(shí),當(dāng)前timer不工作
18.RunLoop的Mode有幾種?
當(dāng)RunLoop在Mode1上時(shí),是無法接受Mode2或Mode3上的Source,Timer,Observer事件的
總共有五種CFRunLoopMode:
KCFRunLoopDefaultMode:默認(rèn)模式,主線程就是在這個(gè)模式下運(yùn)行
UITrackingRunLoopMode:跟蹤用戶交事件(用于ScrollView追蹤滑動(dòng))
UIInitiazationRunLoopMode:剛啟動(dòng)App時(shí)進(jìn)入的第一個(gè)Mode,啟動(dòng)完成后不再使用
GSEventReiviceRunLoopMode:接受系統(tǒng)內(nèi)部事件,通常用不到
KCFRunLoopCommonModes:偽模式,不是一種真正的運(yùn)行模式,是同步Source/Timer?Observer到多個(gè)Mode的一種解決方案
19.RunLoop與NSTimer
一個(gè)比較常見的問題:滑動(dòng)tableView時(shí),定時(shí)器還會(huì)生效嗎?
默認(rèn)情況下RunLoop運(yùn)行在kCFRunLoopDefaultMode下,而當(dāng)滑動(dòng)tableView時(shí),RunLoop切換到UITrackingRunLoopMode,而Timer是在kCFRunLoopDefaultMode下的,就無法接受處理Timer的事件。
怎么去解決這個(gè)問題呢?把Timer添加到UITrackingRunLoopMode上并不能解決問題,因?yàn)檫@樣在默認(rèn)情況下就無法接受定時(shí)器事件了。
所以我們需要把Timer同時(shí)添加到UITrackingRunLoopMode和kCFRunLoopDefaultMode上。
那么如何把timer同時(shí)添加到多個(gè)mode上呢?就要用到NSRunLoopCommonModes了
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
Timer就被添加到多個(gè)mode上,這樣即使RunLoop由kCFRunLoopDefaultMode切換到UITrackingRunLoopMode下,也不會(huì)影響接收Timer事件
20.AFNetworking 中如何運(yùn)用 Runloop?
AFURLConnectionOperation 這個(gè)類是基于 NSURLConnection 構(gòu)建的,其希望能在后臺(tái)線程接收 Delegate 回調(diào)。為此 AFNetworking 單獨(dú)創(chuàng)建了一個(gè)線程,并在這個(gè)線程中啟動(dòng)了一個(gè) RunLoop.
RunLoop 啟動(dòng)前內(nèi)部必須要有至少一個(gè) Timer/Observer/Source,所以 AFNetworking 在 [runLoop run] 之前先創(chuàng)建了一個(gè)新的 NSMachPort 添加進(jìn)去了。通常情況下,調(diào)用者需要持有這個(gè) NSMachPort (mach_port) 并在外部線程通過這個(gè) port 發(fā)送消息到 loop 內(nèi);但此處添加 port 只是為了讓 RunLoop 不至于退出,并沒有用于實(shí)際的發(fā)送消息。
21.PerformSelector 的實(shí)現(xiàn)原理?
當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 或performSelector:onThread:后,實(shí)際內(nèi)部會(huì)創(chuàng)建一個(gè)Timer并添加到當(dāng)前線程的RunLoop中,如果當(dāng)前線程沒有RunLoop,方法會(huì)失效。
22.PerformSelector:afterDelay:這個(gè)方法在子線程中是否起作用?為什么?怎么解決?
不起作用,子線程默認(rèn)沒有RunLoop,也就沒有Timer,辦法是使用GCD來實(shí)現(xiàn):
Dispath_after
23.RunLoop和線程
線程和RunLoop是意義對應(yīng)的,隱射關(guān)系保存在一個(gè)全局字典中
自己創(chuàng)建的線程默認(rèn)是不開啟RunLoop的
1、怎么創(chuàng)建一個(gè)常駐線程?
1.為當(dāng)前線程開啟一個(gè)RunLoop(第一次調(diào)用 [NSRunLoop currentRunLoop]方法時(shí)實(shí)際是會(huì)先去創(chuàng)建一個(gè)RunLoop)
2.向RunLoop中添加一個(gè)維持RunLoop的時(shí)間循環(huán)(如果RunLoop的mode中一個(gè)item都沒有,RunLoop會(huì)退出)
3.啟動(dòng)RunLoop
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
2.怎樣保證子線程數(shù)據(jù)回來更新UI的時(shí)候不打斷用戶的滑動(dòng)操作?
我們就可以將更新UI事件放在主線程的NSDefaultRunLoopMode上執(zhí)行即可,這樣就會(huì)等用戶不再滑動(dòng)頁面,主線程RunLoop由UITrackingRunLoopMode切換到NSDefaultRunLoopMode時(shí)再去更新UI
[self performSelectorOnMainThread:@selector(reloadData) withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
24.RunLoop的數(shù)據(jù)結(jié)構(gòu)
RunLoop設(shè)計(jì)五個(gè)類:
CFRunLoop:RunLoop對象
CFRunLoopMode:運(yùn)行模式
CFRunLoopSource:輸入源/事件源
CFRunLoopTimer:定時(shí)源
CFRunLoopObserver:觀察者
1、CFRunLoop
由pthread(線程對象,說明RunLoop和線程是一一對應(yīng)的)、currentMode(當(dāng)前所處的運(yùn)行模式)、modes(多個(gè)運(yùn)行模式的集合)、commonModes(模式名稱字符串集合)、commonModelItems(Observer,Timer,Source集合)構(gòu)成
2、CFRunLoopMode
由name、source0、source1、observers、timers構(gòu)成
3、CFRunLoopSource
分為source0和source1兩種
source0:
即非基于port的,也就是用戶觸發(fā)的事件。需要手動(dòng)喚醒線程,將當(dāng)前線程從內(nèi)核態(tài)切換到用戶態(tài)
source1:
基于port的,包含一個(gè) mach_port 和一個(gè)回調(diào),可監(jiān)聽系統(tǒng)端口和通過內(nèi)核和其他線程發(fā)送的消息,能主動(dòng)喚醒RunLoop,接收分發(fā)系統(tǒng)事件。
具備喚醒線程的能力
4、CFRunLoopTimer
基于時(shí)間的觸發(fā)器,基本上說的就是NSTimer。在預(yù)設(shè)的時(shí)間點(diǎn)喚醒RunLoop執(zhí)行回調(diào)。因?yàn)樗腔赗unLoop的,因此它不是實(shí)時(shí)的(就是NSTimer 是不準(zhǔn)確的。 因?yàn)镽unLoop只負(fù)責(zé)分發(fā)源的消息。如果線程當(dāng)前正在處理繁重的任務(wù),就有可能導(dǎo)致Timer本次延時(shí),或者少執(zhí)行一次)。
5、CFRunLoopObserver
監(jiān)聽以下時(shí)間點(diǎn):CFRunLoopActivity
kCFRunLoopEntry
RunLoop準(zhǔn)備啟動(dòng)
kCFRunLoopBeforeTimers
RunLoop將要處理一些Timer相關(guān)事件
kCFRunLoopBeforeSources
RunLoop將要處理一些Source事件
kCFRunLoopBeforeWaiting
RunLoop將要進(jìn)行休眠狀態(tài),即將由用戶態(tài)切換到內(nèi)核態(tài)
kCFRunLoopAfterWaiting
RunLoop被喚醒,即從內(nèi)核態(tài)切換到用戶態(tài)后
kCFRunLoopExit
RunLoop退出
kCFRunLoopAllActivities
監(jiān)聽所有狀態(tài)
25.RunLoop概念
RunLoop是通過維護(hù)內(nèi)部的時(shí)間循環(huán)來對事件/消息進(jìn)行管理的一個(gè)對象
1.沒有消息時(shí),休眠避免占用資源,有用戶態(tài)切換到內(nèi)核態(tài)
2.有消息處理,由內(nèi)核態(tài)切換到用戶態(tài)
為什么main函數(shù)不會(huì)退出?
UIApplicationMain內(nèi)部默認(rèn)開啟了主線程的RunLoop,并執(zhí)行了一段無限循環(huán)的代 碼(不是簡單的for循環(huán)或while循環(huán))
26.解釋一下 NSTimer
NSTimer 其實(shí)就是 CFRunLoopTimerRef,他們之間是 toll-free bridged 的。一個(gè) NSTimer 注冊到 RunLoop 后,RunLoop 會(huì)為其重復(fù)的時(shí)間點(diǎn)注冊好事件。例如 10:00, 10:10, 10:20 這幾個(gè)時(shí)間點(diǎn)。RunLoop 為了節(jié)省資源,并不會(huì)在非常準(zhǔn)確的時(shí)間點(diǎn)回調(diào)這個(gè)Timer。Timer 有個(gè)屬性叫做 Tolerance (寬容度),標(biāo)示了當(dāng)時(shí)間點(diǎn)到后,容許有多少最大誤差。
27.什么是異步繪制?
就是可以在子線程把需要繪制的圖形,提前在子線程處理好,講準(zhǔn)備好的圖像數(shù)據(jù)直接返回給主線程使用,降低主線程的壓力。
異步繪制過程:
通過系統(tǒng)的[view.delegate displayLayer:]入口實(shí)現(xiàn)異步繪制
*代理負(fù)責(zé)生成對應(yīng)的 Bitmap
*設(shè)置該 Bitmap 為 layer.contents 屬性的值。
28.利用 runloop 解釋一下頁面的渲染的過程?
當(dāng)我們調(diào)用 [UIView setNeedsDisplay] 時(shí),這時(shí)會(huì)調(diào)用當(dāng)前 View.layer 的 [view.layer setNeedsDisplay]方法。
這等于給當(dāng)前的 layer 打上了一個(gè)臟標(biāo)記,而此時(shí)并沒有直接進(jìn)行繪制工作。而是會(huì)到當(dāng)前的 Runloop 即將休眠,也就是 beforeWaiting 時(shí)才會(huì)進(jìn)行繪制工作。
緊接著會(huì)調(diào)用 [CALayer display],進(jìn)入到真正繪制的工作。CALayer 層會(huì)判斷自己的 delegate 有沒有實(shí)現(xiàn)異步繪制的代理方法 displayer:,這個(gè)代理方法是異步繪制的入口,如果沒有實(shí)現(xiàn)這個(gè)方法,那么會(huì)繼續(xù)進(jìn)行系統(tǒng)繪制的流程,然后繪制結(jié)束。
CALayer 內(nèi)部會(huì)創(chuàng)建一個(gè) Backing Store,用來獲取圖形上下文。接下來會(huì)判斷這個(gè) layer 是否有 delegate。
如果有的話,會(huì)調(diào)用 [layer.delegate drawLayer:inContext:],并且會(huì)返回給我們 [UIView DrawRect:] 的回調(diào),讓我們在系統(tǒng)繪制的基礎(chǔ)之上再做一些事情。
如果沒有 delegate,那么會(huì)調(diào)用 [CALayer drawInContext:]。
以上兩個(gè)分支,最終 CALayer 都會(huì)將位圖提交到 Backing Store,最后提交給 GPU。
至此繪制的過程結(jié)束。
29.KVO (Key-value observing)
KVO就是觀察者模式的另一實(shí)現(xiàn),使用了isa混寫(isa-swizzling)來實(shí)現(xiàn)KVO
使用setter方法改變值KVO會(huì)生效,使用setValue:forKey即KVC改變值KVO也會(huì)生效,因?yàn)镵VC會(huì)去調(diào)用setter方法
通過賦值成員變量不會(huì)觸發(fā)KVO,因?yàn)闆]有調(diào)用setter方法,需要加上willChangeValueForKey和didChangeValueForKey方法來手動(dòng)觸發(fā)才行
30.KVC(Key-value coding)
KVO可以允許開發(fā)者通過Key名直接訪問對象屬性或者給屬性賦值,而不需要調(diào)用存取方法。這樣就可以在運(yùn)行時(shí)動(dòng)態(tài)的訪問和修改對象屬性,而不是在編譯時(shí)確定。
31.分類、擴(kuò)展、代理
一、分類
1.分類作用?
聲明私有方法,分解體積大的類文件,把framework的私有方法公開
2.分類特點(diǎn)
運(yùn)行時(shí)決定,可以為系統(tǒng)類添加分類
32.請說一下對 CALayer 的認(rèn)識(shí)
layer是圖層繪制,渲染,以及動(dòng)畫的完成者,無法處理觸摸事件,layer常見的屬性有Frame、Bounds、Position、AnchorPoint、Contents 等等。
33.Block的幾種形式
分為全局Block,堆Block,棧Block三種,其中棧Block存儲(chǔ)在棧(stack)區(qū),堆Block存儲(chǔ)在堆(heap)區(qū),全局Block存儲(chǔ)在已初始化數(shù)據(jù)(.data)區(qū)
34.什么是Block?
Block是將函數(shù)及其執(zhí)行上下文封裝起來的對象。block內(nèi)部有isa指針,所以說其本質(zhì)也是OC對象
35.iOS 性能優(yōu)化面試題
在性能優(yōu)化中一個(gè)最具參考價(jià)值的屬性是FPS:Frames Per Second,其實(shí)就是屏幕刷新率,蘋果的iphone推薦的刷新率是60Hz,F(xiàn)PS值的大小體現(xiàn)了頁面的流暢程度高低,當(dāng)?shù)陀?5的時(shí)候卡頓會(huì)比較明顯。
一.入門級(jí)
1.用ARC管理內(nèi)存
2、在正確的地方使用 reuseIdentifier
3.盡量把views設(shè)置成透明
4.避免過大的XIB
5.不要阻塞主線程
6.在ImageViews中調(diào)整圖片大小,最好保證圖片大小和imageView大小一致,縮放圖片會(huì)耗費(fèi)資源,可以在下載完成后用backgroundthread,縮放一次,然后在UIImageView中使用縮放后的圖片。
7.正確使用Collection
8.打開gzip壓縮
二.中級(jí)
1.重用和延遲加載(lazy load) Views
2.Cache
緩存那些不大可能經(jīng)常改變但需要讀取的東西,一些選項(xiàng)是,遠(yuǎn)端服務(wù)器的響應(yīng),圖片,甚至計(jì)算結(jié)果,比如UITableView的行高。
3.權(quán)衡渲染方法.性能能還是要bundle保持合適的大小。
4.處理內(nèi)存警告,移除對緩存,圖片object和其他一些重創(chuàng)建的objects的strongreference
5.重用大開銷對象
6.一些objects的初始化很慢,比如NSDateFormatter和NSCalendar。然而,你又不可避免地需要使用它們,比如從JSON或者XML中解析數(shù)據(jù)。想要避免使用這個(gè)對象的瓶頸你就需要重用他們,可以通過添加屬性到你的class里或者創(chuàng)建靜態(tài)變量來實(shí)現(xiàn)。
7.避免反復(fù)處理數(shù)據(jù)庫,在服務(wù)端和客戶端使用相同的數(shù)據(jù)結(jié)構(gòu)
8.選擇正確的數(shù)據(jù)格式
9.正確設(shè)定背景圖片
10.減少使用Web特性,盡可能的移除不必要的js,避免使用過大的框架,盡可能的異步記載不影響頁面表達(dá)的js
11.、Shadow Path 。CoreAnimation不得不先在后臺(tái)得出你的圖形并加好陰影然后才渲染,這開銷是很大的。使用shadowPath的話就避免了這個(gè)問題。使用shadow path的話iOS就不必每次都計(jì)算如何渲染,它使用一個(gè)預(yù)先計(jì)算好的路徑。
12.優(yōu)化tableview
正確使用reuseIdentifier來重用cells
盡量使所有的view opaque,包括cell自身
避免漸變,圖片縮放,后臺(tái)選人
緩存行高
如果cell內(nèi)現(xiàn)實(shí)的內(nèi)容來自web,使用異步加載,緩存請求結(jié)果
使用shadowPath來畫陰影
減少subviews的數(shù)量
盡量不適用cellForRowAtIndexPath:,如果你需要用到它,只用-一次然后緩存結(jié)果
使用正確的數(shù)據(jù)結(jié)構(gòu)來存儲(chǔ)數(shù)據(jù)
使用rowHeight, sectionFooterHeight 和 sectionHeaderHeight來設(shè)定固定的高,不要請求delegate
13、選擇正確的數(shù)據(jù)存儲(chǔ)選項(xiàng)
三、高級(jí)
1.加速啟動(dòng)時(shí)間,盡可能多的異步任務(wù),編碼龐大的XIB
2.使用Autorelease Pool
3.選擇是否緩存圖片
4.避免日期格式轉(zhuǎn)化
平時(shí)你是如何對代碼進(jìn)行性能優(yōu)化的?
利用性能分析工具檢測,包括靜態(tài) Analyze 工具,以及運(yùn)行時(shí) Profile 工具,通過Xcode工具欄中Product->Profile可以啟動(dòng),
比如測試程序啟動(dòng)運(yùn)行時(shí)間,當(dāng)點(diǎn)擊Time Profiler應(yīng)用程序開始運(yùn)行后.就能獲取到整個(gè)應(yīng)用程序運(yùn)行消耗時(shí)間分布和百分比.為了保證數(shù)據(jù)分析在統(tǒng)一使用場景真實(shí)需要注意一定要使用真機(jī),因?yàn)榇藭r(shí)模擬器是運(yùn)行在Mac上,而Mac上的CPU往往比iOS設(shè)備要快。
為了防止一個(gè)應(yīng)用占用過多的系統(tǒng)資源,開發(fā)iOS的蘋果工程師門設(shè)計(jì)了一個(gè)“看門狗”的機(jī)制。在不同的場景下,“看門狗”會(huì)監(jiān)測應(yīng)用的性能。如果超出了該場景所規(guī)定的運(yùn)行時(shí)間,“看門狗”就會(huì)強(qiáng)制終結(jié)這個(gè)應(yīng)用的進(jìn)程。開發(fā)者們在crashlog里面,會(huì)看到諸如0x8badf00d這樣的錯(cuò)誤代碼。
36.光柵化
光柵化是將幾何數(shù)據(jù)經(jīng)過一系列變化后最終轉(zhuǎn)換成像素,從而呈現(xiàn)在顯示設(shè)備的過程,本質(zhì)是坐標(biāo)變化和幾何離散化。
當(dāng)UItableview和UICollectionView的cell樣式一樣是,可以使用這個(gè)屬性提高性能
cell.layer.shouldRasterize=YES;
cell.layer.rasterizationScale=[[UIScreenmainScreen]scale];
37.日常如何檢查內(nèi)存泄露?
目前我知道的方式有以下幾種:
Memory Leaks
Alloctions
Analyse
Debug Memory Graph
MLeaksFinder
泄露的內(nèi)存主要有一下兩種:
1.Laek Memory 忘記Relase操作所泄露的內(nèi)存
2.Abandon Memory 循環(huán)引用,無法釋放掉的內(nèi)存
38.如何高性能的畫一個(gè)圓角?
視圖和圓角的大小對幀率并沒有什么卵影響,數(shù)量才是傷害的核心輸出
label.layer.cornerRadius = 5
label.layer.masksToBounds = true
上面的方法不可取,會(huì)出發(fā)離屏渲染:
*如果能夠只用 cornerRadius 解決問題,就不用優(yōu)化。
*如果必須設(shè)置 masksToBounds,可以參考圓角視圖的數(shù)量,如果數(shù)量較少(一頁只有幾個(gè))也可以考慮不用優(yōu)化。
*UIImageView的圓角可以通過直接截取圖片實(shí)現(xiàn)。其他視圖的圓角可以通過Core Graphics畫出圓角矩形實(shí)現(xiàn)
39.如何提升 tableview 的流暢度?
本質(zhì)上是降低 CPU、GPU 的工作,從這兩個(gè)大的方面去提升性能。
*CPU:對象的創(chuàng)建和銷毀、對象屬性的調(diào)整、布局計(jì)算、文本的計(jì)算和排版、圖片的格式轉(zhuǎn)換和解碼、圖像的繪制
*GPU:紋理的渲染
40.如何優(yōu)化 APP 的電量?
程序的耗電主要在以下四個(gè)方面:CPU,定位,網(wǎng)絡(luò),圖像
*盡可能降低CPU,GPU的功耗
*少用定時(shí)器
*優(yōu)化I/O操作:
*不要頻繁寫入小數(shù)據(jù),積攢到一定數(shù)量在寫入
*讀寫大量數(shù)據(jù)可使用 Dispatch_io ,GCD 內(nèi)部已經(jīng)做了優(yōu)化。
*數(shù)據(jù)量比較大,建議使用數(shù)據(jù)庫
*網(wǎng)絡(luò)方面的優(yōu)化
*減少壓縮網(wǎng)絡(luò)數(shù)據(jù)
*請求數(shù)據(jù)返回相同,使用NSCache緩存
*使用斷點(diǎn)續(xù)傳,避免因網(wǎng)絡(luò)失敗后重新下載
*網(wǎng)絡(luò)不可用時(shí),不提供網(wǎng)絡(luò)請求
*長時(shí)間網(wǎng)絡(luò)請求,提供取消操作
*批量傳輸,下載視頻流的時(shí)候,盡量一大塊一大塊的進(jìn)行下載,廣告可以一次下
載多個(gè)
*定位層面的優(yōu)化
* 如果只是需要快速確定用戶位置,最好用 CLLocationManager 的 requestLocation 方法。定位完成后,會(huì)自動(dòng)讓定位硬件斷電
*如果不是導(dǎo)航應(yīng)用,盡量不要實(shí)時(shí)更新位置,定位完畢就關(guān)掉定位服務(wù)
*盡量降低定位精度,比如盡量不要使用精度最高的 kCLLocationAccuracyBest
*需要后臺(tái)定位時(shí),盡量設(shè)置 pausesLocationUpdatesAutomatically 為 YES,如果用戶不太可能移動(dòng)的時(shí)候系統(tǒng)會(huì)自動(dòng)暫停位置更新
*盡量不要使用 startMonitoringSignificantLocationChanges,優(yōu)先考慮 startMonitoringForRegion:
*硬件檢測優(yōu)化
*用戶移動(dòng)、搖晃、傾斜設(shè)備時(shí),會(huì)產(chǎn)生動(dòng)作(motion)事件,這些事件由加速度計(jì)、陀螺儀、磁力計(jì)等硬件檢測。在不需要檢測的場合,應(yīng)該及時(shí)關(guān)閉這些硬件
41.如何有效降低 APP 包的大小?
可執(zhí)行文件
*編譯器優(yōu)化
**Strip Linked Product、Make Strings Read-Only、Symbols Hidden by Default 設(shè)置為 YES
**去掉異常支持,Enable C++ Exceptions、Enable Objective-C Exceptions 設(shè)置為 NO, Other C Flags 添加 -fno-exceptions
*利用 AppCode 檢測未使用的代碼:菜單欄 -> Code -> Inspect Code
*編寫LLVM插件檢測出重復(fù)代碼、未被調(diào)用的代碼
資源
*資源包括 圖片、音頻、視頻 等
42.什么是 離屏渲染?什么情況下會(huì)觸發(fā)?該如何應(yīng)對?
離屏渲染就是在當(dāng)前屏幕緩沖區(qū)以外,新開辟一個(gè)緩沖器進(jìn)行操作
離屏渲染出發(fā)的場景有以下:
*圓角(maskToBounds并用才會(huì)觸發(fā))
*圖層蒙版
*陰影
*光柵化
為什么要避免離屏渲染?
CPU GPU 在繪制渲染視圖時(shí)做了大量的工作。離屏渲染發(fā)生在 GPU 層面上,會(huì)創(chuàng)建新的渲染緩沖區(qū),會(huì)觸發(fā) OpenGL 的多通道渲染管線,圖形上下文的切換會(huì)造成額外的開銷,增加 GPU 工作量。如果 CPU GPU 累計(jì)耗時(shí) 16.67 毫秒還沒有完成,就會(huì)造成卡頓掉幀。
43.NSThread+runloop實(shí)現(xiàn)常駐線程
*由于每次開辟子線程都會(huì)消耗cpu,在需要頻繁使用子線程的情況下,頻繁開辟子線程會(huì)消耗大量的cpu,而且創(chuàng)建線程都是任務(wù)執(zhí)行完成之后也就釋放了,不能再次利用,那么如何創(chuàng)建一個(gè)線程可以讓它可以再次工作呢?也就是創(chuàng)建一個(gè)常駐線程。
+ (NSThread *)shareThread {
static NSThread *shareThread = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
shareThread = [[NSThread alloc] initWithTarget:self selector:@selector(threadTest2) object:nil];
[shareThread setName:@"threadTest"];
[shareThread start];
});
return shareThread;
}
+ (void)threadTest
{
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
44.自旋鎖與互斥鎖
自旋鎖會(huì)忙等: 所謂忙等,即在訪問被鎖資源時(shí),調(diào)用者線程不會(huì)休眠,而是不停循環(huán)在那里,直到被鎖資源釋放鎖。
互斥鎖會(huì)休眠: 所謂休眠,即在訪問被鎖資源時(shí),調(diào)用者線程會(huì)休眠,此時(shí)cpu可以調(diào)度其他線程工作。直到被鎖資源釋放鎖。此時(shí)會(huì)喚醒休眠線程。
45.內(nèi)存中的5大區(qū)分別是什么?
棧區(qū)(stack):由編譯器自動(dòng)分配釋放 ,存放函數(shù)的參數(shù)值,局部變量的值等。其 操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
堆區(qū)(heap):一般由程序員分配釋放, 若程序員不釋放,程序結(jié)束時(shí)可能由OS回收 。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事,分配方式倒是類似于鏈表。
全局區(qū)(靜態(tài)區(qū))(static):全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的,初始化的 全局變量和靜態(tài)變量在一塊區(qū)域, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域。 - 程序結(jié)束后由系統(tǒng)釋放。
文字常量區(qū):常量字符串就是放在這里的。 程序結(jié)束后由系統(tǒng)釋放。
程序代碼區(qū):存放函數(shù)體的二進(jìn)制代碼。