OC 作為一種經(jīng)典的運(yùn)行時(shí)動(dòng)態(tài)語言, 底層的動(dòng)態(tài)運(yùn)行時(shí)、消息機(jī)制另無數(shù)iOS開發(fā)者癡迷.
Runtime:
結(jié)構(gòu)模型
- 我們先來看下類的定義:
typedef struct objc_class *Class;
struct objc_class {
Class *isa;
Class superClass;
const char* name;
long version;
long info;
long instance_size;
struct objc_ivar_list *ivar;
struct objc_method_list *methodLists;
struct objc_cache *cache;
struct objc_protocol_list *protocol;
}
2.對(duì)象的定義:
typedef struct objc_object *id;
3.對(duì)象的isa指針:
struct objc_object {
Class isa;
}
4.方法定義:
typedef struct objc_method *Method;
// objc_method 結(jié)構(gòu)體定義
struct objc_method {
SEL method_name; // 函數(shù)方法(選擇器)
char* method_types ; // 參數(shù)types是一個(gè)描述傳遞給方法的參數(shù)類型的字符數(shù)組披坏,這涉及到類型編碼雾叭。
IMP method_imp; // 函數(shù)指針, 函數(shù)方法通過IMP 指針,找到對(duì)應(yīng)的函數(shù)實(shí)現(xiàn)
}
5.SEL 的定義:
typedef struct objc_selector *SEL;(函數(shù)方法)
// IMP 的定義(函數(shù)指針)
typedef id(*IMP)(id, SEL,...)
6.ivar (成員變量的定義)
typedef struct objc_ivar *ivar;
struct objc_ivar {
char* objc_ivar;
char* ivar_type;
int ivar_offset;
int space;
}
7.屬性的定義
typedef struct objc_property *objc_property_t;
8.分類的定義:
typedef struct objc_category *Category;
struct objc_category {
char* category_name;
char* class_name;
struct objc_method_list *instance_methods;
struct objc_method_list *class_methods;
struct objc_protocol_list *protocols
}
9.protocol 協(xié)議的定義:
typedef struct objc_object *Protocol;
runtime 的一張經(jīng)典的關(guān)系圖:
為什么要設(shè)計(jì)metaClass?
Objective-C這種借鑒Smalltalk的乎婿,更注重的是消息傳遞,是動(dòng)態(tài)響應(yīng)消息。
消息傳遞對(duì)于面向?qū)ο蟮脑O(shè)計(jì)夺刑,其實(shí)在于給出一種對(duì)消息的解決方案。而面向?qū)ο髢?yōu)點(diǎn)之一的復(fù)用,在這種設(shè)計(jì)里遍愿,更多在于復(fù)用解決方案存淫,而不是單純的類本身。這種思想就如設(shè)計(jì)組件一般沼填,關(guān)心接口桅咆,關(guān)心組合而非類本身。其實(shí)之所以有MetaClass這種設(shè)計(jì)坞笙,我的理解并不是先有MetaClass岩饼,而是在萬物都是對(duì)象的Smalltalk里,向?qū)ο蟀l(fā)送消息的基本解決方案是統(tǒng)一的薛夜,希望復(fù)用的籍茧。而實(shí)例和類之間用的這一套通過isa指針指向的Class單例中存儲(chǔ)方法列表和查詢方法的解決方案的流程,是應(yīng)該在類上復(fù)用的梯澜,而MetaClass就順理成章出現(xiàn)罷了寞冯。
class_copyIvarList()& class_copyPropertyList()區(qū)別?
1). class_copyIvarList()返回的僅僅是對(duì)象類的屬性.
2). class_copyPropertyList()返回類的所有屬性和變量.(包括在@interface大括號(hào)中聲明的變量)
- class_rw_t 和 class_ro_t 的區(qū)別?
struct objc_object {
isa_t isa;
};
struct objc_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() {
return bits.data();
}
};
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro;
method_array_t methods;
property_array_t properties;
protocol_array_t protocols;
Class firstSubclass;
};
class_rw_t: 讀寫, OC的 屬性 方法 協(xié)議信息都保在class_rw_t中
class_ro_t: 只讀, 在編譯期class_ro_t 的結(jié)構(gòu)體就已經(jīng)確定不能更改.
category如何被加載的,兩個(gè)category的load方法的加載順序晚伙,兩個(gè)category的同名方法的加載順序?
1).類的初始化也是動(dòng)態(tài)的吮龄,根類NSObject 的+load 和+initilize兩個(gè)方法,用于類的初始化.
2). 加載順序是父類先+load咆疗,然后子類+load螟蝙,然后分類+load,那么如果分類重寫子類方法:首先子類+load,將方法添加到類的方法列表methodLists民傻,然后分類+load胰默,將重寫的方法添加到方法列表中.
4). 實(shí)際調(diào)用時(shí),調(diào)用的是后添加的方法漓踢,即后添加的方法在方法列表methodLists的這個(gè)數(shù)組的頂部,后+load的類的方法牵署,后添加到方法列表,而這時(shí)的添加方式又是插入頂部添加喧半,即[methodLists insertObject:category_method atIndex:0]; 所以objc_msgSend遍歷方法列表查找SEL 對(duì)應(yīng)的IMP時(shí)奴迅,會(huì)先找到分類重寫的那個(gè),調(diào)用執(zhí)行挺据。然后添加到緩存列表中取具,這樣主類方法實(shí)現(xiàn)永遠(yuǎn)也不會(huì)調(diào)到。
3). 類的加載順序扁耐,決定方法的添加順序暇检,調(diào)用的時(shí)候,后添加的方法會(huì)先被找到婉称,所以調(diào)用的始終是后加載的類的方法實(shí)現(xiàn),類似于入棧出棧.
category & extension區(qū)別块仆,能給NSObject添加Extension嗎构蹬,結(jié)果如何?
category: 分類,有名字, 屬性不實(shí)現(xiàn)setter getter 方法.
extension: 擴(kuò)展,無名字, 可擴(kuò)展 方法 屬性 成員變量.
消息轉(zhuǎn)發(fā)機(jī)制,消息轉(zhuǎn)發(fā)機(jī)制和其他語言的消息機(jī)制優(yōu)劣對(duì)比?
1).消息(傳遞)機(jī)制
RunTime簡稱運(yùn)行時(shí)悔据。就是系統(tǒng)在運(yùn)行的時(shí)候的一些機(jī)制庄敛,其中最主要的是消息機(jī)制。
對(duì)于C語言科汗,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)藻烤。編譯完成之后直接順序執(zhí)行,無任何二義性头滔。OC的函數(shù)調(diào)用稱為消息發(fā)送隐绵。屬于動(dòng)態(tài)調(diào)用過程。在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)(也就是說拙毫,在編譯階段依许,OC可以調(diào)用任何函數(shù),即使這個(gè)函數(shù)并未實(shí)現(xiàn)缀蹄,只要申明過就不會(huì)報(bào)錯(cuò)峭跳。而C語言在編譯階段就會(huì)報(bào)錯(cuò))。只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找 到對(duì)應(yīng)的函數(shù)來調(diào)用缺前。
來看 這個(gè)函數(shù)是怎么調(diào)用的[obj makeText];
首先蛀醉,編譯器將代碼[obj makeText]轉(zhuǎn)化為objc_msgSend(obj, @selector (makeText)),在objc_msgSend函數(shù)中衅码。首先通過obj的isa指針找到obj對(duì)應(yīng)的Class拯刁。在Class中先去cache中通過SEL查找對(duì)應(yīng)函數(shù)method,若 cache中未找到逝段。再去methodList中查找垛玻,若methodlist中未找到,則取superClass中查找奶躯。若能找到帚桩,則將method加入到cache中,以方便下次查找嘹黔,并通過method中的函數(shù)指針跳轉(zhuǎn)到對(duì)應(yīng)的函數(shù)中去執(zhí)行账嚎。
2).消息轉(zhuǎn)發(fā)機(jī)制(可以間接實(shí)現(xiàn)多重繼承)
在方法調(diào)用的時(shí)候,方法查詢-> 動(dòng)態(tài)解析-> 消息轉(zhuǎn)發(fā) 之前做了什么?
IMP儡蔓、SEL郭蕉、Method的區(qū)別和使用場(chǎng)景?
+(void)load、+(void)initialize方法的區(qū)別什么喂江?在繼承關(guān)系中他們有什么區(qū)別.
+(void)load程序剛啟動(dòng)就會(huì)調(diào)用這個(gè)方法.
在外部初始化本類的時(shí)候,就會(huì)馬上調(diào)用+(void)initialize方法.
程序啟動(dòng) -> load(自動(dòng)調(diào)用) -> [外部初始化本類方法] ->initialize(自動(dòng)調(diào)用) ->調(diào)用本類方法
- 說說消息轉(zhuǎn)發(fā)機(jī)制的優(yōu)劣.
內(nèi)存管理
- weak的實(shí)現(xiàn)原理召锈?SideTable的結(jié)構(gòu)是什么樣的?
- 關(guān)聯(lián)對(duì)象的應(yīng)用?系統(tǒng)如何實(shí)現(xiàn)關(guān)聯(lián)對(duì)象的?
- 關(guān)聯(lián)對(duì)象的如何進(jìn)行內(nèi)存管理的开呐?關(guān)聯(lián)對(duì)象如何實(shí)現(xiàn)weak屬性?
- Autoreleasepool的原理烟勋?所使用的的數(shù)據(jù)結(jié)構(gòu)是什么?
- ARC的實(shí)現(xiàn)原理?ARC下對(duì)retain & release做了哪些優(yōu)化?
- ARC下哪些情況會(huì)造成內(nèi)存泄漏?
其他
- Method Swizzle注意事項(xiàng)?
- 屬性修飾符atomic的內(nèi)部實(shí)現(xiàn)是怎么樣的?能保證線程安全嗎?
-(void)setName:(NSString *)name {
@synchronizaed(self) {
if(_name = name) {
[_name release];
[_name retain];
}
}
}
-(*NSString)name {
@synchronizaed(self) {
return _name;
}
}
原子屬性只能保證內(nèi)部set get 的完整性不受外部影響,但是不能保證線程的絕對(duì)安全, 線程安全還需要線程鎖.
- iOS 中內(nèi)省的幾個(gè)方法有哪些筐付?內(nèi)部實(shí)現(xiàn)原理是什么?
內(nèi)省是(對(duì)象)在(運(yùn)行時(shí))獲取其(類型)的能力;
-(BOOL)isKindOfClass:
-(BOOL)isMemberOfClass:
-(BOOL)respondsToSelector:
+(BOOL)instanceRespondsToSelector:
- class卵惦、objc_getClass、object_getclass 方法有什么區(qū)別?
NSNotification相關(guān)
蘋果并沒有開源相關(guān)代碼瓦戚,但是可以讀下GNUStep的源碼沮尿,基本上實(shí)現(xiàn)方式很具有參考性
- 實(shí)現(xiàn)原理(結(jié)構(gòu)設(shè)計(jì)、通知如何存儲(chǔ)的较解、
name&observer&SEL
之間的關(guān)系等) - 通知的發(fā)送時(shí)同步的畜疾,還是異步的
-
NSNotificationCenter
接受消息和發(fā)送消息是在一個(gè)線程里嗎?如何異步發(fā)送消息 -
NSNotificationQueue
是異步還是同步發(fā)送印衔?在哪個(gè)線程響應(yīng) -
NSNotificationQueue
和runloop
的關(guān)系 - 如何保證通知接收的線程在主線程
- 頁面銷毀時(shí)不移除通知會(huì)崩潰嗎
- 多次添加同一個(gè)通知會(huì)是什么結(jié)果啡捶?多次移除通知呢
Runloop & KVO
runloop
app如何接收到觸摸事件的?
為什么只有主線程的runloop是開啟的?
1).因?yàn)锳PP啟動(dòng)以后就開啟了主線程,主線程中需要接受各種的用戶操作, 如果不開啟主線程的runLoop 那主線程在執(zhí)行一次任務(wù)后就不是保活狀態(tài), 無法工作.
2). 大多數(shù)的子線程創(chuàng)建其實(shí)只處理了一件事情,并不需要奔楸海活, 節(jié)省CPU的資源,如果需要毕故睿活,那就手動(dòng)開啟runloop,也保證了runloop 的靈活性.為什么只在主線程刷新UI?
1). 整個(gè)程序的啟點(diǎn)UIApplication 是在主線程初始化的, 所有的用戶事件都在主線程完成.
2). 而在渲染方面由于圖像的渲染需要以60幀的刷新率在屏幕上 同時(shí)更新,在非主線程異步化的情況下無法確定這個(gè)處理過程能夠?qū)崿F(xiàn)同步更新与帆。
3).如果Main RunLoop 中的事件需要跨線程進(jìn)行傳輸,就會(huì)導(dǎo)致顯示和用戶事件不能同步.performSelector和runloop的關(guān)系?
1). 使用performSelector的話一定是在運(yùn)行時(shí)候才能發(fā)現(xiàn),如果此方法不存在就會(huì)crash.
2). 在子線程中無法使用performSelector 的afterDelay 方法,因?yàn)閍fterDelay的方式是使用當(dāng)前線程的runloop 根據(jù)afterDelay參數(shù)創(chuàng)建一個(gè)Timer定時(shí)器后調(diào)用SEL, 無afterDelay的方式是直接調(diào)用SEL, 所以需要在子線程創(chuàng)建runloop.
// 第一種, 子線程創(chuàng)建runloop
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[self performSelector:@selector(delayMethod) withObject:nil afterDelay:0];
[[NSRunLoop currentRunLoop] run];
NSLog(@"調(diào)用方法==開始");
sleep(5);
NSLog(@"調(diào)用方法==結(jié)束");
});
// 第二種,其實(shí)是GCD內(nèi)部已經(jīng)創(chuàng)建好了runloop
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_global_queue(0, 0), ^{
if ([self respondsToSelector:@selector(delayMethod)]) {
[self performSelector:@selector(delayMethod) withObject:nil];
}
});
NSLog(@"調(diào)用方法==開始");
sleep(5);
NSLog(@"調(diào)用方法==結(jié)束");
- 如何使線程绷硕模活?
在線程中開啟一個(gè)runloop
KVO
1.實(shí)現(xiàn)原理?
KVO 是基于強(qiáng)大的runtime
當(dāng)一個(gè)A類的屬性發(fā)生改變時(shí), 通過runtime會(huì)創(chuàng)建A的派生類B,B繼承于A.
通過runtime 的(isa-swapping)將A 的isa 指向B.
當(dāng)B的屬性發(fā)生改變后, 就會(huì)重寫A的屬性setter 方法,通知observer發(fā)生改變.
2.如何手動(dòng)關(guān)閉KVO?
- 通過KVC修改屬性會(huì)觸發(fā)KVO么?
會(huì)的, 修改屬性,會(huì)調(diào)用屬性的set 方法, 那么就會(huì)啟動(dòng)KVO的observer. - 哪些情況下使用KVO會(huì)崩潰,怎么預(yù)防崩潰?
KVO的添加次數(shù)與移出次數(shù)不匹配.
被觀察者提前釋放.(被觀察者在 dealloc 時(shí)仍然注冊(cè)著 KVO)
添加了觀察者, 但是未實(shí)現(xiàn)監(jiān)聽方法.
添加或者移出時(shí),keyPath 為空.
- kvo的優(yōu)缺點(diǎn)?
優(yōu)點(diǎn):能夠提供一種簡單的方法實(shí)現(xiàn)兩個(gè)對(duì)象間的同步.
用key paths來觀察屬性玄糟,因此也可以觀察嵌套對(duì)象
完成了對(duì)觀察對(duì)象的抽象勿她,因?yàn)椴恍枰~外的代碼來允許觀察值能夠被觀察.
缺點(diǎn):
我們觀察的屬性必須使用strings來定義.
對(duì)屬性重構(gòu)將導(dǎo)致我們的觀察代碼不再可用.
Block
- block的內(nèi)部實(shí)現(xiàn),結(jié)構(gòu)體是什么樣的?
- block是類嗎阵翎,有哪些類型?
- 一個(gè)int變量被 __block 修飾與否的區(qū)別逢并?block的變量截獲?
- block在修改NSMutableArray,需不需要添加__block?
- 怎么進(jìn)行內(nèi)存管理的?
- block可以用strong修飾嗎?
- 解決循環(huán)引用時(shí)為什么要用__strong郭卫、__weak修飾?
- block發(fā)生copy時(shí)機(jī)?
- Block訪問對(duì)象類型的auto變量時(shí)筒狠,在ARC和MRC下有什么區(qū)別?
多線程
iOS開發(fā)中有多少類型的線程?分別對(duì)比
GCD有哪些隊(duì)列箱沦,默認(rèn)提供哪些隊(duì)列
GCD有哪些方法api
GCD主線程 & 主隊(duì)列的關(guān)系
如何實(shí)現(xiàn)同步辩恼,有多少方式就說多少
dispatch_once實(shí)現(xiàn)原理
什么情況下會(huì)死鎖?
串行隊(duì)列中執(zhí)行同步線程的任務(wù).有哪些類型的線程鎖,分別介紹下作用和使用場(chǎng)景
NSOperationQueue中的maxConcurrentOperationCount默認(rèn)值
maxConcurrentOperationCount代表隊(duì)列同一時(shí)間允許執(zhí)行的最多的任務(wù)數(shù)谓形≡钜粒或者理解為同一時(shí)間允許執(zhí)行的最多線程數(shù)。
maxConcurrentOperationCount默認(rèn)為-1寒跳,代表不限制聘萨。
maxConcurrentOperationCount 必須要提前設(shè)置,如果隊(duì)列中添加了操作再設(shè)置maxConcurrentOperationCount就無效了童太。
- NSTimer米辐、CADisplayLink胸完、dispatch_source_t 的優(yōu)劣
視圖&圖像相關(guān)
- AutoLayout的原理,性能如何?
背景:
AutoLayout本質(zhì)就是一個(gè)線性方程解析Engine.
NSLayoutConstraint翘贮,本質(zhì)上是表示兩個(gè)視圖之間布局關(guān)系的一個(gè)線性方程赊窥,該方程可以是線性等式、也可以是線性不等式狸页。
多個(gè)約束對(duì)象組成是一個(gè)約束集合锨能,本質(zhì)上是表示某個(gè)界面上多個(gè)視圖之間布局關(guān)系的線性方程組。方程組中的多個(gè)線性方程芍耘,以數(shù)字標(biāo)識(shí)的優(yōu)先級(jí)進(jìn)行排序(UILayoutPriority址遇,本質(zhì)上是浮點(diǎn)型float.
UIView & CALayer的區(qū)別?
事件響應(yīng)者鏈?
把Event事件加入到UIApplication隊(duì)列中, UIResponser ->UIApplication-> UIWindow ->UIViewdrawrect & layoutsubviews調(diào)用時(shí)機(jī)?
drawRect調(diào)用時(shí)機(jī):
如果在UIView初始化時(shí)沒有設(shè)置frame,會(huì)導(dǎo)致drawRect不被自動(dòng)調(diào)用.
sizeToFit后會(huì)調(diào)用斋竞。這時(shí)候可以先用sizeToFit中計(jì)算出size倔约,然后系統(tǒng)自動(dòng)調(diào)用drawRect方法.
通過設(shè)置contentMode為.redraw時(shí),那么在每次設(shè)置或更改frame的時(shí)候自動(dòng)調(diào)用drawRect.
直接調(diào)用setNeedsDisplay坝初,或者setNeedsDisplayInRect會(huì)觸發(fā)drawRect.
layoutSubViews調(diào)用時(shí)機(jī):
init初始化不會(huì)調(diào)用layoutSubviews方法.
addSubview時(shí)會(huì)調(diào)用.
改變一個(gè)UIView的frame時(shí)會(huì)調(diào)用.
(滾動(dòng)一個(gè)UIScrollView導(dǎo)致UIView重新布局時(shí)會(huì)調(diào)用.
旋轉(zhuǎn)Screen會(huì)觸發(fā)父UIView上的事件.
手動(dòng)調(diào)用setNeedsLayout或者layoutIfNeeded.)
UI的刷新原理?
當(dāng)在操作 UI 時(shí)跺株,比如改變了 Frame、更新了 UIView/CALayer 的層次時(shí)脖卖,或者手動(dòng)調(diào)用了 UIView/CALayer 的 setNeedsLayout/setNeedsDisplay方法后乒省,這個(gè) UIView/CALayer 就被標(biāo)記為待處理,并被提交到一個(gè)全局的容器去畦木。
RunLoop的 Observer會(huì)監(jiān)聽 BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件袖扛,回調(diào)去執(zhí)行一個(gè)很長的函數(shù):
_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()。這個(gè)函數(shù)里會(huì)遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實(shí)際的繪制和調(diào)整十籍,并更新 UI 界面蛆封。隱式動(dòng)畫 & 顯示動(dòng)畫區(qū)別?
顯式動(dòng)畫是指用戶自己通過beginAnimations:context:和commitAnimations創(chuàng)建的動(dòng)畫。
隱式動(dòng)畫是指通過UIView的animateWithDuration:animations:方法創(chuàng)建的動(dòng)畫勾栗。
隱式動(dòng)畫是ios4之后引入sdk的惨篱,之前只有顯式動(dòng)畫。從官方的介紹來看围俘,兩者并沒有什么差別砸讳,甚至蘋果還推薦使用隱式 動(dòng)畫,但是這里面有一個(gè)問題界牡,就是使用隱式動(dòng)畫后簿寂,View會(huì)暫時(shí)不能接收用戶的觸摸、滑動(dòng)等手勢(shì)宿亡。
這就造成了當(dāng)一個(gè)列表滾動(dòng)時(shí)常遂,如果對(duì)其中的view使用了隱式動(dòng)畫,就會(huì)感覺滾動(dòng)無法主動(dòng)停止下來挽荠,必須等動(dòng)畫結(jié)束了才能停止克胳。什么是離屏渲染?
GPU在當(dāng)前顯示緩存區(qū)之外又開辟新的緩存區(qū),就是離屏渲染.imageName & imageWithContentsOfFile區(qū)別
多個(gè)相同的圖片平绩,會(huì)重復(fù)加載嗎?
圖片是什么時(shí)候解碼的,如何優(yōu)化?
一般我們使用的圖像是JPEG/PNG漠另,這些圖像數(shù)據(jù)不是位圖捏雌,而是是經(jīng)過編碼壓縮后的數(shù)據(jù),需要線將它解碼轉(zhuǎn)成位圖數(shù)據(jù)酗钞,然后才能把位圖渲染到屏幕上腹忽。
當(dāng)你用 UIImage 或 CGImageSource 的那幾個(gè)方法創(chuàng)建圖片時(shí)来累,圖片數(shù)據(jù)并不會(huì)立刻解碼砚作。圖片設(shè)置到 UIImageView 或者 CALayer.contents 中去,并且 CALayer 被提交到 GPU 前嘹锁,CGImage 中的數(shù)據(jù)才會(huì)得到解碼葫录。這一步是發(fā)生在主線程的,并且不可避免领猾。
圖片加載的工作流
概括來說米同,從磁盤中加載一張圖片,并將它顯示到屏幕上摔竿,中間的主要工作流如下:
- 假設(shè)我們使用
+imageWithContentsOfFile:
方法從磁盤中加載一張圖片面粮,這個(gè)時(shí)候的圖片并沒有解壓縮; - 然后將生成的
UIImage
賦值給UIImageView
继低; - 接著一個(gè)隱式的
CATransaction
捕獲到了UIImageView
圖層樹的變化熬苍; - 在主線程的下一個(gè) run loop 到來時(shí),Core Animation 提交了這個(gè)隱式的 transaction 袁翁,這個(gè)過程可能會(huì)對(duì)圖片進(jìn)行 copy 操作柴底,而受圖片是否字節(jié)對(duì)齊等因素的影響,這個(gè) copy 操作可能會(huì)涉及以下部分或全部步驟:
- 分配內(nèi)存緩沖區(qū)用于管理文件 IO 和解壓縮操作粱胜;
- 將文件數(shù)據(jù)從磁盤讀到內(nèi)存中柄驻;
- 將壓縮的圖片數(shù)據(jù)解碼成未壓縮的位圖形式,這是一個(gè)非常耗時(shí)的 CPU 操作焙压;
- 最后 Core Animation 使用未壓縮的位圖數(shù)據(jù)渲染
UIImageView
的圖層鸿脓。
在上面的步驟中,我們提到了圖片的解壓縮是一個(gè)非常耗時(shí)的 CPU 操作涯曲,并且它默認(rèn)是在主線程中執(zhí)行的答憔。那么當(dāng)需要加載的圖片比較多時(shí),就會(huì)對(duì)我們應(yīng)用的響應(yīng)性造成嚴(yán)重的影響掀抹,尤其是在快速滑動(dòng)的列表上虐拓,這個(gè)問題會(huì)表現(xiàn)得更加突出。
圖像的解碼
解碼操作是比較耗時(shí)的傲武,并且沒有GPU硬解碼蓉驹,只能通過CPU城榛,iOS默認(rèn)會(huì)在主線程對(duì)圖像進(jìn)行解碼。解碼過程是一個(gè)相當(dāng)復(fù)雜的任務(wù)态兴,需要消耗非常長的時(shí)間狠持。60FPS ≈ 0.01666s per frame = 16.7ms per frame,這意味著在主線程超過16.7ms的任務(wù)都會(huì)引起掉幀瞻润。很多庫都解決了圖像解碼的問題喘垂,不過由于解碼后的圖像太大,一般不會(huì)緩存到磁盤绍撞,SDWebImage的做法是把解碼操作從主線程移到子線程正勒,讓耗時(shí)的解碼操作不占用主線程的時(shí)間。
對(duì)于PNG圖片來說傻铣,因?yàn)槲募赡芨笳抡辏约虞d會(huì)比JPEG更長,但是解碼會(huì)相對(duì)較快非洲,而且Xcode會(huì)把PNG圖片進(jìn)行解碼優(yōu)化之后引入工程鸭限。JPEG圖片更小,加載更快两踏,但是解壓的步驟要消耗更長的時(shí)間败京,因?yàn)镴PEG解壓算法比基于zip的PNG算法更加復(fù)雜。
當(dāng)加載圖片的時(shí)候梦染,iOS通常會(huì)延遲解壓圖片的時(shí)間赡麦,直到加載到內(nèi)存之后。因?yàn)樾枰诶L制之前進(jìn)行解壓弓坞,這就會(huì)在準(zhǔn)備繪制圖片的時(shí)候影響性能隧甚。
iOS通常會(huì)延時(shí)解壓圖片,等到圖片在屏幕上顯示的時(shí)候解壓圖片渡冻。解壓圖片是非常耗時(shí)的操作戚扳。
圖像解碼的核心方法CGBitmapContextCreate
CGContextRef CGBitmapContextCreate(
void * data,
size_t width,
size_t height,
size_t bitsPerComponent,
size_t bytesPerRow,
CGColorSpaceRef _Nullable space,
uint32_t bitmapInfo)
- 圖片渲染怎么優(yōu)化?
圖片的顯示分為三步:加載、解碼族吻、渲染帽借。
通常,我們操作的只有加載超歌,解碼和渲染是由UIKit進(jìn)行砍艾。
什么是解碼?
以UIImageView為例巍举。當(dāng)其顯示在屏幕上時(shí)脆荷,需要UIImage作為數(shù)據(jù)源。
UIImage持有的數(shù)據(jù)是未解碼的壓縮數(shù)據(jù),能節(jié)省較多的內(nèi)存和加快存儲(chǔ)蜓谋。
解碼是一個(gè)計(jì)算量較大的任務(wù)梦皮,且需要CPU來執(zhí)行;并且解碼出來的圖片體積與圖片的寬高有關(guān)系桃焕,而與圖片原來的體積無關(guān)剑肯。
其體積大小可簡單描述為:寬 * 高 * 每個(gè)像素點(diǎn)的大小 = width * height * 4bytes。
圖像解碼操作會(huì)造成什么問題观堂?
以我們常見的UITableView和UICollectionView為例让网,假如我們?cè)谑褂靡粋€(gè)多圖片顯示的功能:
在上下滑動(dòng)顯示圖片的過程中,我們會(huì)在cellFor的方法加載UIImage圖片师痕、賦值給UIImageView溃睹,相當(dāng)于在主線程同時(shí)進(jìn)行IO操作、解碼操作等七兜,會(huì)造成內(nèi)存迅速增長和CPU負(fù)載瞬間提升丸凭。
并且內(nèi)存的迅速增加會(huì)觸發(fā)系統(tǒng)的內(nèi)存回收機(jī)制福扬,嘗試回收其他后臺(tái)進(jìn)程的內(nèi)存腕铸,增加CPU的工作量。如果系統(tǒng)無法提供足夠的內(nèi)存铛碑,則會(huì)先結(jié)束其他后臺(tái)進(jìn)程狠裹,最終無法滿足的話會(huì)結(jié)束當(dāng)前進(jìn)程。
那么如何對(duì)這種情況進(jìn)行優(yōu)化 汽烦?
優(yōu)化1:降采樣
在滑動(dòng)顯示的過程中涛菠,圖片顯示的寬高遠(yuǎn)比真實(shí)圖片要小,我們可以采用加載縮略圖的方式減少圖片的占用內(nèi)存撇吞。
如下圖所示:
我們加載jpeg的圖片俗冻,然后進(jìn)行相關(guān)設(shè)置,解碼后根據(jù)設(shè)置生成CGImage縮略圖牍颈,最后包裝成UIImage迄薄,最終傳遞給UIImageView渲染。
思考:這里的解碼步驟為何不是上文提到的imageView.image=image
時(shí)機(jī)煮岁?
func downsample(imageAt imageURL: URL, to pointSize: CGSize, scale: CGFloat) -> UIImage {
let imageSourceOptions = [kCGImageSourceShouldCache: false] as CFDictionary
let imageSource = CGImageSourceCreateWithURL(imageURL as CFURL, imageSourceOptions)!
let maxDimensionInPixels = max(pointSize.width, pointSize.height) * scale
let downsampleOptions = [kCGImageSourceCreateThumbnailFromImageAlways: true,
kCGImageSourceShouldCacheImmediately: true,
kCGImageSourceCreateThumbnailWithTransform: true,
kCGImageSourceThumbnailMaxPixelSize: maxDimensionInPixels] as CFDictionary
let downsampledImage = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, downsampleOptions)!
return UIImage(cgImage: downsampledImage)
}
我的理解:正常的UIImage加載是從APP本地讀取讥蔽,或者從網(wǎng)絡(luò)下載圖片,此時(shí)不涉及圖片內(nèi)容相關(guān)的操作画机,并不需要解碼冶伞;當(dāng)圖片被賦值給UIImageView時(shí),CALayer讀取圖片內(nèi)容進(jìn)行渲染步氏,所以需要對(duì)圖片進(jìn)行解碼响禽;
而上文的縮略圖生成過程中,已經(jīng)對(duì)圖片進(jìn)行解碼操作,此時(shí)的UIImage只是一個(gè)CGImage的封裝芋类,所以當(dāng)UIImage賦值給UIImageView時(shí)瀑焦,CALayer可以直接使用CGImage所持有的圖像數(shù)據(jù)。
優(yōu)化2:異步處理
從用戶的體驗(yàn)來分析梗肝,滑動(dòng)的操作往往是間斷性觸發(fā)榛瓮,在滑動(dòng)的瞬間有較大的工作量,而且由于都是在主線程進(jìn)行操作無法進(jìn)行任務(wù)分配巫击,CPU 2處于閑置禀晓。由此引申出兩種優(yōu)化手段:Prefetching(預(yù)處理)和
Background decoding/downsampling(子線程解碼和降采樣)。綜合起來坝锰,可以在Prefetching的時(shí)候把降采樣放到子線程進(jìn)行處理粹懒,因?yàn)榻挡蓸舆^程就包括解碼操作。
Prefetching回調(diào)中顷级,把降采樣的操作放到同步隊(duì)列serialQueue中凫乖,處理完畢之后拋給主線程進(jìn)行update操作。
需要特別注意弓颈,此處不能是并發(fā)隊(duì)列帽芽,否則會(huì)造成線程爆炸,原因見總結(jié)部分翔冀。
優(yōu)化3:使用Image Asset Catalogs
Apple推薦的圖片資源管理工具导街,壓縮效率更高,在iOS 12的機(jī)器上有10~20%的空間節(jié)約纤子,并且每個(gè)版本Apple都會(huì)持續(xù)對(duì)其進(jìn)行優(yōu)化搬瑰。
內(nèi)容較多,詳細(xì)可點(diǎn)Session控硼。
總結(jié)
應(yīng)用上述的優(yōu)化策略泽论,已經(jīng)能對(duì)圖片加載有比較好的優(yōu)化。
原文如下:
Thread Explosion(線程爆炸)
More images to decode than available CPUs(解碼圖像數(shù)量大于CPU數(shù)量)
GCD continues creating threads as new work is enqueued(GCD創(chuàng)建新線程處理新的任務(wù))
Each thread gets less time to actually decode images(每個(gè)線程獲得很少的時(shí)間解碼圖像)
從這個(gè)案例我們學(xué)習(xí)到如何避免圖像解碼的線程爆炸卡乾,但還能擴(kuò)散思維:
我們分析蘋果工程師的邏輯:
原因(解碼任務(wù)過多)==> 過程(GCD開啟更多線程) ==> 結(jié)果( 每個(gè)線程獲得更少的時(shí)間)
延伸出來的問題有:
GCD是如何處理并發(fā)隊(duì)列翼悴?為何會(huì)啟動(dòng)多個(gè)線程處理?
多少的線程數(shù)量是合適的说订?線程的cpu時(shí)間分配和切換代價(jià)如何抄瓦?
...
舉一反三,類似的問題太多陶冷。但是這樣的思考稍顯混亂钙姊,仍有優(yōu)化的空間。
把腦海關(guān)于GCD的認(rèn)知提煉出來:
1埂伦、GCD是用來處理一系列任務(wù)的同步和異步執(zhí)行煞额,隊(duì)列有串行和并發(fā)兩種,與線程的關(guān)系只有主線程和非主線程的區(qū)別;
2膊毁、串行隊(duì)列是執(zhí)行完當(dāng)前的任務(wù)胀莹,才會(huì)執(zhí)行下一個(gè)block任務(wù);并行隊(duì)列是多個(gè)block任務(wù)并行執(zhí)行婚温,GCD會(huì)根據(jù)任務(wù)的執(zhí)行情況分配線程描焰,原則是盡快完成所有任務(wù);
接下來的表現(xiàn)是操作系統(tǒng)相關(guān)的知識(shí):
1栅螟、iOS系統(tǒng)中進(jìn)程和線程的關(guān)聯(lián)荆秦,每個(gè)啟動(dòng)的APP都是一個(gè)進(jìn)程,其中有多個(gè)線程力图;
2步绸、cpu的時(shí)間是分為多個(gè)時(shí)間片,每個(gè)線程輪詢執(zhí)行吃媒;
3瓤介、線程切換執(zhí)行有代價(jià),但比進(jìn)程切換小得多赘那;
4刑桑、每個(gè)cpu核心在同一時(shí)刻只能執(zhí)行一個(gè)線程;
至此我們可以結(jié)合操作系統(tǒng)和GCD的知識(shí)漓概,猜測(cè)底層GCD的實(shí)現(xiàn)思路和線程爆炸情況下的表現(xiàn):
主線程把多個(gè)任務(wù)block放到并發(fā)隊(duì)列漾月,GCD先啟動(dòng)一個(gè)線程處理解碼任務(wù)病梢,線程執(zhí)行過程中遇到耗時(shí)操作時(shí)(IO等待胃珍、大量CPU計(jì)算),短時(shí)間內(nèi)無法完成蜓陌,為了不阻塞后續(xù)任務(wù)的執(zhí)行觅彰,GCD啟動(dòng)新的線程處理新的任務(wù)。
集合此案例钮热,我們能回答相關(guān)問題:
1填抬、現(xiàn)在有一個(gè)很復(fù)雜的計(jì)算任務(wù),例如是統(tǒng)計(jì)一個(gè)5000x5000圖片中像素點(diǎn)的RGB顏色通道隧期,如果用分為25個(gè)任務(wù)放到GCD并發(fā)隊(duì)列飒责,把大圖切分成25個(gè)1000x1000小圖分別統(tǒng)計(jì),是否會(huì)速度的提升仆潮?
2宏蛉、GCD的串行隊(duì)列和并發(fā)隊(duì)列的應(yīng)用場(chǎng)景有何不同?
- 如果GPU的刷新率超過了iOS屏幕60Hz刷新率是什么現(xiàn)象性置,怎么解決?
畫面撕裂
降低GPU刷新頻率為60Hz
GPU每秒鐘刷新屏幕60次拾并,這每刷新一次就是一幀frame,每一幀大概在1/60 = 16.67ms畫面最佳.
性能優(yōu)化
- 如何做啟動(dòng)優(yōu)化,如何監(jiān)控
- 如何做卡頓優(yōu)化嗅义,如何監(jiān)控
- 如何做耗電優(yōu)化屏歹,如何監(jiān)控
- 如何做網(wǎng)絡(luò)優(yōu)化,如何監(jiān)控
開發(fā)證書
- 蘋果使用證書的目的是什么
- AppStore安裝app時(shí)的認(rèn)證流程
- 開發(fā)者怎么在debug模式下把a(bǔ)pp安裝到設(shè)備呢
架構(gòu)設(shè)計(jì)
典型源碼的學(xué)習(xí)
- AFNetWorking
- SDWebImage
- JSPatch之碗、Aspects(雖然一個(gè)不可用蝙眶、另一個(gè)不維護(hù),但是這兩個(gè)庫都很精煉巧妙褪那,很適合學(xué)習(xí))
- Weex/RN, 筆者認(rèn)為這種前端和客戶端緊密聯(lián)系的庫是必須要知道其原理的
- CTMediator械馆、其他router庫,這些都是常見的路由庫武通,開發(fā)中基本上都會(huì)用到
架構(gòu)設(shè)計(jì)
手動(dòng)埋點(diǎn)霹崎、自動(dòng)化埋點(diǎn)、可視化埋點(diǎn)
MVC冶忱、MVP尾菇、MVVM
設(shè)計(jì)模式常見的設(shè)計(jì)模式
單利 工廠 門面 觀察者 通知單例的弊端
常見的路由方案,以及優(yōu)缺點(diǎn)對(duì)比
如果保證項(xiàng)目的穩(wěn)定性
設(shè)計(jì)一個(gè)圖片緩存框架(LRU)
如何設(shè)計(jì)一個(gè)
git diff
設(shè)計(jì)一個(gè)線程池囚枪?畫出你的架構(gòu)圖
你的app架構(gòu)是什么派诬,有什么優(yōu)缺點(diǎn)、為什么這么做链沼、怎么改進(jìn)
其他問題
-
PerformSelector & NSInvocation
優(yōu)劣對(duì)比 -
oc
怎么實(shí)現(xiàn)多繼承默赂?怎么面向切面(可以參考Aspects深度解析-iOS面向切面編程) - 哪些
bug
會(huì)導(dǎo)致崩潰,如何防護(hù)崩潰 - 怎么監(jiān)控崩潰
-
app
的啟動(dòng)過程(考察LLVM編譯過程括勺、靜態(tài)鏈接缆八、動(dòng)態(tài)鏈接、runtime初始化) - 沙盒目錄的每個(gè)文件夾劃分的作用
- 簡述下
match-o
文件結(jié)構(gòu)
系統(tǒng)基礎(chǔ)知識(shí)
進(jìn)程和線程的區(qū)別?
進(jìn)程是app 啟動(dòng)市創(chuàng)建的,一個(gè)app 只能有一個(gè)進(jìn)程.
線程是在進(jìn)程中的, 一個(gè)進(jìn)程可以有多個(gè)線程.HTTPS
的握手過程?
client發(fā)送url 請(qǐng)求.
server端收到請(qǐng)求,并且配置好數(shù)字證書(公鑰),并返回給client.
client驗(yàn)證數(shù)字證書的有效性,并生成隨機(jī)數(shù).
client 打包數(shù)字證書 + 隨機(jī)數(shù)發(fā)送給server.
server端解析.
server 返回信息給client.
client解鎖返回信息.
ps: server發(fā)送的簽名證書屬于非對(duì)稱加密, client 與server 通訊使用的是對(duì)稱加密.什么是
中間人攻擊
疾捍?怎么預(yù)防
就是第三方偽裝成client盜走了數(shù)字簽名證書.TCP
的握手過程奈辰?為什么進(jìn)行三次握手,四次揮手?堆和棧
區(qū)的區(qū)別乱豆?誰的占用內(nèi)存空間大?加密算法:
對(duì)稱加密算法和非對(duì)稱加密算法
區(qū)別?常見的
對(duì)稱加密和非對(duì)稱加密
算法有哪些?
非對(duì)稱加密算法:RSA奖恰,DSA/DSS
對(duì)稱加密算法:DES,RC4宛裕,3DESMD5瑟啃、Sha1、Sha256
區(qū)別?charles
抓包過程揩尸?不使用charles
蛹屿,4G
網(wǎng)絡(luò)如何抓包?
數(shù)據(jù)結(jié)構(gòu)與算法
- 八大排序算法
- 棧&隊(duì)列
- 字符串處理
- 鏈表
- 二叉樹相關(guān)操作
- 深搜廣搜
- 基本的動(dòng)態(tài)規(guī)劃題、貪心算法疲酌、二分查找
[原文章] (http://www.reibang.com/p/e87e0be2281f)