iOS 面試知識(shí)點(diǎn)(三)

Objective-C的內(nèi)存管理

引用計(jì)數(shù)機(jī)制

Objective-C 主要采用引用計(jì)數(shù)機(jī)制來(lái)管理對(duì)象的內(nèi)存讥珍。

運(yùn)行時(shí)系統(tǒng)維護(hù)有一個(gè)哈希表SideTablesMapSideTablesMap中存儲(chǔ)著許多個(gè)SideTable窄瘟,每個(gè)SideTable由1個(gè)自旋鎖衷佃、1個(gè)引用計(jì)數(shù)表(RefcountMap)和1個(gè)弱引用表(weak_table_t)構(gòu)成,引用計(jì)數(shù)表和弱引用表都是哈希表蹄葱。引用計(jì)數(shù)表中存儲(chǔ)著對(duì)象的引用計(jì)數(shù)纲酗,弱引用表中存儲(chǔ)著指向?qū)ο蟮?weak 指針數(shù)組,它們的 key 都是對(duì)象的內(nèi)存地址新蟆。

新建一個(gè)對(duì)象,該對(duì)象的初始引用計(jì)數(shù)值為1右蕊。

當(dāng)調(diào)用對(duì)象的retain方法時(shí)琼稻,會(huì)根據(jù)對(duì)象的內(nèi)存地址到SideTablesMap中查找該對(duì)象對(duì)應(yīng)的SideTable,再根據(jù)對(duì)象的內(nèi)存地址到SideTable的引用計(jì)數(shù)表中查找對(duì)象的引用計(jì)數(shù)饶囚,并對(duì)該引用計(jì)數(shù)執(zhí)行加1操作帕翻。

調(diào)用對(duì)象的release方法時(shí),同樣會(huì)經(jīng)過(guò)兩次哈希查找找出對(duì)象的引用計(jì)數(shù)萝风,并對(duì)該引用計(jì)數(shù)執(zhí)行減1操作嘀掸。

當(dāng)對(duì)象的引用計(jì)數(shù)為0時(shí),會(huì)調(diào)用對(duì)象的dealloc方法來(lái)銷毀對(duì)象规惰。dealloc方法內(nèi)部會(huì)調(diào)用_objc_rootDealloc函數(shù)睬塌,_objc_rootDealloc函數(shù)會(huì)調(diào)用object_dispose函數(shù),object_dispose函數(shù)會(huì)先調(diào)用objc_destructInstance函數(shù)歇万,然后調(diào)用free函數(shù)釋放對(duì)象占用的內(nèi)存空間揩晴。

objc_destructInstance函數(shù)內(nèi)部首先會(huì)判斷當(dāng)前對(duì)象是否包含 C++ 內(nèi)容,如果包含贪磺,則銷毀 C++ 內(nèi)容硫兰。然后,判斷當(dāng)前對(duì)象的屬性是否有關(guān)聯(lián)對(duì)象寒锚,如果有劫映,則從關(guān)聯(lián)哈希表中移除當(dāng)前對(duì)象所關(guān)聯(lián)的所有對(duì)象。接著刹前,會(huì)調(diào)用clearDeallocating函數(shù)泳赋。clearDeallocating函數(shù)首先會(huì)將指向當(dāng)前對(duì)象的所有 weak 指針指向nil,并從弱引用表中移除當(dāng)前對(duì)象的所有 weak 指針腮郊,然后再?gòu)囊糜?jì)數(shù)表中移除當(dāng)前對(duì)象的引用計(jì)數(shù)摹蘑。

Tagged Pointer

NSNumberNSDate這類小對(duì)象是使用 Tagged Pointer 來(lái)管理內(nèi)存的轧飞。

NSNumber對(duì)象包含一個(gè)long類型的成員變量和一個(gè)void *類型的isa指針衅鹿。在32位架構(gòu)下撒踪,long類型和void *類型各占 4 個(gè)字節(jié),NSNumber對(duì)象總共占用 8 個(gè)字節(jié)的內(nèi)存空間大渤。但是在64位架構(gòu)下制妄,long類型和void *類型各占 8 個(gè)字節(jié),NSNumber對(duì)象總共占用了 16 個(gè)字節(jié)的內(nèi)存空間泵三,其內(nèi)存占用翻倍了耕捞。

NSNumberNSDate對(duì)象的值需要占用的內(nèi)存通常不用 8 個(gè)字節(jié),因?yàn)?4 個(gè)字節(jié)所能表示的整數(shù)值可以達(dá)到40億烫幕,這已經(jīng)可以滿足絕大多數(shù)需求了俺抽。

為了改進(jìn)NSNumberNSDate在64位設(shè)備上的內(nèi)存占用,蘋果引入了 Tagged Pointer较曼,它被拆成兩部分磷斧,一部分直接保存數(shù)值,另一部分作為特俗標(biāo)記捷犹,表示這是一個(gè)特別的指針弛饭,不指向任何地址。

如果 8 個(gè)字節(jié)可以承載NSNumberNSDate的值萍歉,那么在創(chuàng)建NSNumberNSDate對(duì)象時(shí)侣颂,就會(huì)直接生成一個(gè) Tagged Pointer。這樣枪孩,NSNumberNSDate在 64 位設(shè)備上的內(nèi)存占用就還是 8 個(gè)字節(jié)憔晒。同時(shí),還大大提高了讀寫它們的效率蔑舞。

由于 Tagged Pointer 的值不是內(nèi)存地址丛晌,而是真正的值,所以NSNumberNSDate對(duì)象是一個(gè)偽對(duì)象斗幼。它們不是存儲(chǔ)在堆區(qū)澎蛛,不需要手動(dòng)分配和釋放內(nèi)存,所以不需要使用引用計(jì)數(shù)來(lái)管理內(nèi)存蜕窿。如果 tagged pointer 是一個(gè)局部變量谋逻,則它是存儲(chǔ)在棧區(qū)的,函數(shù)運(yùn)行結(jié)束時(shí)會(huì)被系統(tǒng)自動(dòng)銷毀桐经;如果 tagged pointer 是一個(gè)對(duì)象的屬性值毁兆,那么它會(huì)在對(duì)象被銷毀時(shí),一起被銷毀阴挣。

當(dāng) Tagged Pointer 存放不下值時(shí)气堕,就還是會(huì)以原來(lái)的方式返回一個(gè)對(duì)象指針。

NONPOINTER_ISA(非指針型的isa)

在64位架構(gòu)下,isa指針是占64個(gè)比特位(1 byte = 8 bit)的茎芭,實(shí)際上33或44個(gè)比特位(arm64為33個(gè)揖膜,x86_64為44個(gè))就已經(jīng)夠用了。為了提高內(nèi)存利用率梅桩,NONPOINTER_ISA 除了存儲(chǔ)有isa指針指向的內(nèi)存地址壹粟,剩余的比特位還存儲(chǔ)了內(nèi)存管理相關(guān)的數(shù)據(jù)內(nèi)容,第1個(gè)比特位用來(lái)標(biāo)識(shí)這個(gè)isa指針是否為 NONPOINTER_ISA宿百,第2個(gè)比特位用來(lái)標(biāo)識(shí)對(duì)象是否有關(guān)聯(lián)對(duì)象趁仙,第3個(gè)比特位標(biāo)識(shí)對(duì)象是否有使用 C++ 析構(gòu)函數(shù)。

arm64 架構(gòu)下垦页,第4到第36個(gè)比特位是對(duì)象的內(nèi)存雀费,第37到第42個(gè)比特位用來(lái)標(biāo)識(shí)對(duì)象是真的對(duì)象還是一個(gè)沒(méi)有初始化的內(nèi)存空間,第43個(gè)比特位用來(lái)標(biāo)識(shí)對(duì)象是否有弱引用指針痊焊,第44個(gè)比特位用來(lái)標(biāo)識(shí)對(duì)象是否正在被釋放坐儿,第45個(gè)比特位用來(lái)標(biāo)識(shí)是是否有超出的引用計(jì)數(shù)存儲(chǔ)在引用計(jì)數(shù)表中,第46到64的比特位是對(duì)象的引用計(jì)數(shù)宋光。

x86_64 架構(gòu)下,第4到第47個(gè)比特位是對(duì)象的內(nèi)存炭菌,第48到第53個(gè)比特位用來(lái)標(biāo)識(shí)對(duì)象是真的對(duì)象還是一個(gè)沒(méi)有初始化的內(nèi)存空間罪佳,第54個(gè)比特位用來(lái)標(biāo)識(shí)對(duì)象是否有弱引用指針,第55個(gè)比特位用來(lái)標(biāo)識(shí)對(duì)象是否正在被釋放黑低,第56個(gè)比特位用來(lái)標(biāo)識(shí)是是否有超出的引用計(jì)數(shù)存儲(chǔ)在引用計(jì)數(shù)表中赘艳,第57到64的比特位是對(duì)象的引用計(jì)數(shù)。

什么是MRC克握?什么是ARC蕾管?

MRC,手動(dòng)引用計(jì)數(shù)菩暗,由開發(fā)者手動(dòng)調(diào)用retain掰曾、release方法來(lái)管理對(duì)象的引用計(jì)數(shù)。

ARC停团,自動(dòng)引用計(jì)數(shù)旷坦,是通過(guò)由編譯器自動(dòng)在代碼中插入retainrelease操作佑稠,并使用運(yùn)行時(shí)系統(tǒng)管理弱引用指針秒梅,來(lái)實(shí)現(xiàn)自動(dòng)管理對(duì)象的引用計(jì)數(shù)的。

什么是循環(huán)引用舌胶?

在 ARC 模式下捆蜀,只有當(dāng)一個(gè)對(duì)象的引用計(jì)數(shù)為0并被釋放時(shí),才會(huì)對(duì)該對(duì)象所持有的屬性執(zhí)行一次release操作。當(dāng)兩個(gè)對(duì)象相互直接或間接持有對(duì)方作為自己的屬性時(shí)辆它,這兩個(gè)對(duì)象會(huì)一直等待對(duì)方先被釋放后誊薄,其引用計(jì)數(shù)才能為0并被釋放。這樣就導(dǎo)致兩個(gè)對(duì)象永遠(yuǎn)無(wú)法被釋放娩井,從而產(chǎn)生循環(huán)引用暇屋。

AutoreleasePool的實(shí)現(xiàn)原理

@autoreleasepool {
      ...
}
// 以上代碼被轉(zhuǎn)化為
void * atautoreleasepoolobj = objc_autoreleasePoolPush();
...
objc_autoreleasePoolPop(atautoreleasepoolobj);



// 棧節(jié)點(diǎn)的數(shù)據(jù)結(jié)構(gòu)
class AutoreleasePoolPage {
    id *next;   // 棧中下一個(gè)可填充的位置
    AutoreleasePoolPage * const parent;  // 父節(jié)點(diǎn)
    AutoreleasePoolPage *child;    // 子節(jié)點(diǎn)
    pthread_t const thread; // 所在線程
    ... // 還有其他參數(shù)沒(méi)有列出
};

自動(dòng)釋放池是一個(gè)以AutoreleasePoolPage對(duì)象為節(jié)點(diǎn)的雙向鏈表,AutoreleasePoolPage對(duì)象是一個(gè)先進(jìn)后出的棧洞辣。

在線程進(jìn)入 runloop 時(shí)咐刨,會(huì)調(diào)用objc_autoreleasePoolPush()函數(shù)。該函數(shù)會(huì)獲取當(dāng)前線程綁定的AutoreleasePoolPage對(duì)象扬霜,如果獲取到的AutoreleasePoolPage對(duì)象為nil定鸟,則會(huì)創(chuàng)建一個(gè)AutoreleasePoolPage對(duì)象,并將哨兵對(duì)象nil添加到AutoreleasePoolPage中著瓶,然后將新建的AutoreleasePoolPage與當(dāng)前線程綁定起來(lái)联予;如果AutoreleasePoolPage對(duì)象不為nil并且還有可用空間,則直接將哨兵對(duì)象nil添加到AutoreleasePoolPage中材原;如果AutoreleasePoolPage對(duì)象不為nil沸久,但是卻沒(méi)有可用空間了,則會(huì)新建一個(gè)AutoreleasePoolPage節(jié)點(diǎn)余蟹,并將哨兵對(duì)象nil添加到這個(gè)AutoreleasePoolPage中缸兔,然后將新建的AutoreleasePoolPage與當(dāng)前線程綁定起來(lái)米诉。

當(dāng)調(diào)用對(duì)象的autorelease方法時(shí)毙沾,會(huì)獲取與當(dāng)前線程綁定的AutoreleasePoolPage归形。如果AutoreleasePoolPage還有可用空間,則直接將該對(duì)象添加到這個(gè)AutoreleasePoolPage中葵孤;如果AutoreleasePoolPage沒(méi)有可用空間了担钮,則新建一個(gè)AutoreleasePoolPage節(jié)點(diǎn),并將這個(gè)對(duì)象添加到新建的AutoreleasePoolPage中尤仍,然后將新建的AutoreleasePoolPage與當(dāng)前線程綁定起來(lái)箫津。

在 runloop 進(jìn)入休眠狀態(tài)前,會(huì)調(diào)用objc_autoreleasePoolPop()函數(shù)宰啦。該函數(shù)會(huì)獲取當(dāng)前線程綁定的AutoreleasePoolPage鲤嫡,然后以先進(jìn)后出的順序移除AutoreleasePoolPage中的對(duì)象,并對(duì)對(duì)象執(zhí)行一次release操作绑莺。當(dāng)這個(gè)AutoreleasePoolPage被清空后暖眼,會(huì)繼續(xù)移除上一個(gè)AutoreleasePoolPage中的對(duì)象,并對(duì)對(duì)象執(zhí)行一次release操作纺裁。當(dāng)遇到哨兵對(duì)象nil時(shí)诫肠,會(huì)移除nil對(duì)象并終止移除操作司澎。 最后,銷毀所有已經(jīng)清空的AutoreleasePoolPage栋豫。

接著挤安,又會(huì)重新調(diào)用objc_autoreleasePoolPush()函數(shù)。然后丧鸯,runloop 進(jìn)入休眠狀態(tài)蛤铜。

weak的實(shí)現(xiàn)原理

運(yùn)行時(shí)系統(tǒng)維護(hù)著一個(gè)SideTablesMap哈希表,其 key 是對(duì)象的內(nèi)存地址丛肢,value 是對(duì)象對(duì)應(yīng)的SideTable围肥。SideTable中包含一個(gè)自旋鎖,一個(gè)引用計(jì)數(shù)表RefcountMap和一個(gè)弱引用表weak_table_t蜂怎。weak_table_t是一個(gè)哈希表穆刻,其 key 是對(duì)象的內(nèi)存地址,value 是指向該對(duì)象的弱引用指針數(shù)組weak_entry_t杠步。

對(duì) weak 變量的賦值操作會(huì)被轉(zhuǎn)換為objc_initWeak()函數(shù)的調(diào)用氢伟,并傳遞弱引用指針的地址和值對(duì)象作為該函數(shù)的參數(shù)。

NSObject *obj = [[NSObject alloc] init];
__weak id weakObj = obj;

// `__weak id weakObj = obj;`代碼會(huì)被編譯器轉(zhuǎn)換為
__weak id weakObj;
objc_initWeak(&weakObj, obj);

objc_initWeak()函數(shù)內(nèi)部會(huì)調(diào)用storeWeak()函數(shù)幽歼,在storeWeak()函數(shù)內(nèi)部實(shí)現(xiàn)中朵锣,如果弱引用指針已經(jīng)有指向一個(gè)舊值對(duì)象,則會(huì)獲取該舊值對(duì)象甸私,并在SideTablesMap中查找舊值對(duì)象對(duì)應(yīng)的SideTable诚些,并調(diào)用weak_unregister_no_lock函數(shù),該函數(shù)會(huì)在舊值對(duì)象對(duì)應(yīng)的SideTable的弱引用表weak_table_t中查找舊值對(duì)象對(duì)應(yīng)的weak_entry_t數(shù)組颠蕴,并從weak_entry_t中移除該弱引用指針。

如果弱引用指針將要指向的新值對(duì)象不為nil助析,則會(huì)在SideTablesMap中查找新值對(duì)象對(duì)應(yīng)的SideTable犀被,并調(diào)用weak_register_no_lock函數(shù),該函數(shù)會(huì)在新值對(duì)象對(duì)應(yīng)的SideTable的弱引用表weak_table_t中查找新值對(duì)象對(duì)應(yīng)的weak_entry_t數(shù)組外冀,并將該弱引用指針添加到weak_entry_t中寡键。最后,將弱引用指針指向新值對(duì)象雪隧;如果新值對(duì)象為nil西轩,則會(huì)直接將弱引用指針指向nil

當(dāng)調(diào)用dealloc方法釋放值對(duì)象時(shí)脑沿,會(huì)調(diào)用sidetable_clearDeallocating()函數(shù)藕畔,該函數(shù)會(huì)在SideTablesMap中查找值對(duì)象對(duì)應(yīng)的SideTable。接著庄拇,調(diào)用weak_clear_no_lock()函數(shù)注服,該函數(shù)會(huì)在SideTableweak_table_t中查找對(duì)應(yīng)的weak_entry_t數(shù)組韭邓,然后遍歷該數(shù)組,將所有弱引用指針指向nil溶弟。最后女淑,調(diào)用weak_entry_remove()函數(shù)從weak_table_t中刪除該值對(duì)象的weak_entry_t

__strong的實(shí)現(xiàn)原理

在 ARC 模式下辜御,在某個(gè)對(duì)象持有的 block 中訪問(wèn)該對(duì)象時(shí)鸭你,會(huì)產(chǎn)生循環(huán)引用。我們通常在 block 中使用__weak指針來(lái)打破循環(huán)引用擒权,__weak指針不會(huì)使其所指對(duì)象的引用計(jì)數(shù)加1袱巨。但是,在異步執(zhí)行 block 的過(guò)程中菜拓,如果__weak指針?biāo)笇?duì)象在此時(shí)釋放了瓣窄,會(huì)引發(fā)異常。為了解決這個(gè)問(wèn)題纳鼎,我們可以在 block 開始執(zhí)行時(shí)俺夕,在 block 中使用__strong指針來(lái)強(qiáng)引用一次__weak指針?biāo)笇?duì)象,使其引用計(jì)數(shù)加1贱鄙,這樣對(duì)象就不會(huì)在 block 執(zhí)行過(guò)程中釋放了劝贸。同時(shí),由于__strong指針在其作用域被釋放時(shí)逗宁,也會(huì)一起被自動(dòng)釋放映九,從而使該對(duì)象的引用計(jì)數(shù)減1,也就不會(huì)產(chǎn)生循環(huán)引用了瞎颗。

屬性和成員變量之間有什么聯(lián)系件甥?

編譯器會(huì)自動(dòng)為屬性生成對(duì)應(yīng)的成員變量,并生成 set 和 get 方法來(lái)讀寫成員變量的值哼拔。使用.語(yǔ)法訪問(wèn)屬性時(shí)引有,實(shí)際上訪問(wèn)的是與其對(duì)應(yīng)的成員變量。

如何給分類添加“成員變量”倦逐?

分類中添加的屬性譬正,編譯器是不會(huì)為這些屬性生成對(duì)應(yīng)的成員變量的,可以通過(guò)關(guān)聯(lián)對(duì)象來(lái)讓分類的屬性具有與類的屬性相同的效果檬姥。

static NSString *nameKey = @"nameKey";

- (void)setName:(NSString *)name
{
    objc_setAssociatedObject(self, &nameKey, name, OBJC_ASSOCIATION_COPY);
}

- (NSString *)name
{
    return objc_getAssociatedObject(self, &nameKey);
}

關(guān)聯(lián)對(duì)象的實(shí)現(xiàn)原理

運(yùn)行時(shí)系統(tǒng)使用AssociationsManager對(duì)象(非唯一)維護(hù)著一個(gè)唯一的AssociationsHashMap曾我,這個(gè)哈希表的 key 是屬性所屬對(duì)象的內(nèi)存地址,value 是一個(gè)ObjectAssociationMap健民,其 key 是屬性標(biāo)識(shí)符抒巢,value 是一個(gè)ObjcAssociation對(duì)象,ObjcAssociation對(duì)象中封裝著關(guān)聯(lián)對(duì)象和屬性關(guān)聯(lián)策略秉犹。

調(diào)用objc_setAssociatedObject函數(shù)時(shí)虐秦,會(huì)首先根據(jù)傳遞的關(guān)聯(lián)對(duì)象和屬性關(guān)聯(lián)策略創(chuàng)建一個(gè)ObjcAssociation對(duì)象平酿。

如果關(guān)聯(lián)對(duì)象不為nil,運(yùn)行時(shí)系統(tǒng)會(huì)根據(jù)屬性所屬對(duì)象的內(nèi)存地址到AssociationsHashMap中查找是否存在與屬性所屬對(duì)象對(duì)應(yīng)的ObjectAssociationMap悦陋。如果不存在蜈彼,則會(huì)創(chuàng)建一個(gè)ObjectAssociationMap,并將這個(gè)ObjectAssociationMap存到AssociationsHashMap中俺驶。然后以屬性標(biāo)識(shí)符為 key幸逆,將ObjcAssociation對(duì)象存到ObjectAssociationMap

如果關(guān)聯(lián)對(duì)象為nil,運(yùn)行時(shí)系統(tǒng)會(huì)根據(jù)屬性所屬對(duì)象的內(nèi)存地址到AssociationsHashMap中讀取對(duì)應(yīng)的ObjectAssociationMap暮现。然后根據(jù)屬性標(biāo)識(shí)符到這個(gè)ObjectAssociationMap中查找對(duì)應(yīng)的ObjcAssociation對(duì)象还绘,如果有,則會(huì)從ObjectAssociationMap中移除這個(gè)ObjcAssociation對(duì)象栖袋。

擴(kuò)展(Extension)

用處

  • 聲明私有屬性拍顷;
  • 聲明私有方法;
  • 聲明私有成員變量塘幅。

特點(diǎn)

  • 編譯時(shí)決議
  • 只是以聲明的形式存在昔案,多數(shù)情況下寄生于宿主類的 .m 文件中;
  • 不能為系統(tǒng)類添加擴(kuò)展电媳。

與分類的區(qū)別

分類是在運(yùn)行時(shí)決議踏揣,可以有自己的 .h 和 .m 文件,能為系統(tǒng)類添加分類匾乓。

AFNetworking源碼分析

  • AFNetworkReachabilityManager是一個(gè)獨(dú)立的類捞稿,用于監(jiān)聽域名,以及 WWAN 和 WiFi 網(wǎng)絡(luò)接口地址的可訪問(wèn)性拼缝。
  • AFSecurityPolicy使用證書和公鑰娱局,通過(guò)安全連接來(lái)驗(yàn)證服務(wù)器是否可信。
  • AFHTTPRequestSerializer封裝了為 HTTP 請(qǐng)求設(shè)置請(qǐng)求頭(HTTP Header)咧七,并將傳遞給服務(wù)端的參數(shù)編碼為查詢字符串(Query String)或者請(qǐng)求體(HTTP Body)的邏輯衰齐。
  • AFHTTPResponseSerializer用于驗(yàn)證服務(wù)器的響應(yīng)是否有效,如果響應(yīng)無(wú)效的話猪叙,其會(huì)返回相應(yīng)的錯(cuò)誤信息娇斩。
  • AFJSONResponseSerializerAFHTTPResponseSerializer的子類仁卷,其在驗(yàn)證服務(wù)器的響應(yīng)是否有效后穴翩,會(huì)將服務(wù)器返回的 JSON 數(shù)據(jù)反序列化為 Objective-C 對(duì)象。
  • AFURLSessionManager是 AFNetworking 框架的核心類锦积,它創(chuàng)建并管理一個(gè)NSURLSession對(duì)象芒帕。
  • AFHTTPSessionManagerAFURLSessionManager的子類,封裝了用于發(fā)送 HTTP 請(qǐng)求的便捷方法丰介。

創(chuàng)建并初始化一個(gè)AFHTTPSessionManager對(duì)象背蟆,AFHTTPSessionManager對(duì)象會(huì)創(chuàng)建并持有一個(gè)NSURLSession對(duì)象鉴分、一個(gè)AFSecurityPolicy對(duì)象、一個(gè)AFNetworkReachabilityManager對(duì)象带膀、一個(gè)AFHTTPRequestSerializer對(duì)象和一個(gè)AFJSONResponseSerializer對(duì)象志珍。

調(diào)用AFHTTPSessionManager對(duì)象的相關(guān)方法發(fā)送一個(gè) HTTP 請(qǐng)求時(shí),其持有的AFHTTPRequestSerializer對(duì)象會(huì)根據(jù)給定的url創(chuàng)建一個(gè)NSMutableRequest對(duì)象垛叨,并為NSMutableRequest對(duì)象設(shè)置請(qǐng)求頭伦糯。如果該請(qǐng)求是一個(gè) GET 請(qǐng)求,則AFHTTPRequestSerializer對(duì)象還會(huì)將需要傳遞給服務(wù)端的參數(shù)編碼為查詢字符串嗽元,并將此查詢字符串拼接到NSMutableRequest對(duì)象的 url 后面敛纲;如果該請(qǐng)求是一個(gè) POST 請(qǐng)求,AFHTTPRequestSerializer對(duì)象還會(huì)將需要傳遞的參數(shù)編碼為二進(jìn)制數(shù)據(jù)剂癌,并將該二進(jìn)制數(shù)據(jù)設(shè)置為NSMutableRequest對(duì)象的請(qǐng)求體(HTTP Body)淤翔。

接著,AFHTTPSessionManager對(duì)象持有的NSURLSession對(duì)象會(huì)根據(jù)前面的NSMutableRequest對(duì)象創(chuàng)建一個(gè)NSURLSessionDataTask對(duì)象來(lái)向服務(wù)器發(fā)送 HTTP 請(qǐng)求佩谷。

在啟用NSURLSessionDataTask對(duì)象發(fā)出 HTTP 請(qǐng)求之前旁壮,AFHTTPSessionManager對(duì)象會(huì)創(chuàng)建一個(gè)AFURLSessionManagerTaskDelegate對(duì)象來(lái)保存外部傳遞的在請(qǐng)求完成時(shí)執(zhí)行的 block,AFURLSessionManagerTaskDelegate對(duì)象還會(huì)弱引用AFHTTPSessionManager對(duì)象琳要。

AFHTTPSessionManager對(duì)象將創(chuàng)建的AFURLSessionManagerTaskDelegate對(duì)象保存到一個(gè)字典中寡具,并以與其對(duì)應(yīng)的NSURLSessionDataTask對(duì)象的taskIdentifier作為 key。

如果這是一個(gè) HTTPS 請(qǐng)求稚补,AFHTTPSessionManager對(duì)象在收到 SSL 服務(wù)器信任質(zhì)詢代理回調(diào)時(shí)童叠,會(huì)使用其持有的AFSecurityPolicy對(duì)象來(lái)驗(yàn)證證書和公鑰。如果驗(yàn)證失敗课幕,則斷開連接厦坛。

AFHTTPSessionManager對(duì)象是其創(chuàng)建并管理的NSURLSession對(duì)象的delegate,啟用NSURLSessionDataTask對(duì)象發(fā)送 HTTP 請(qǐng)求之后乍惊,AFHTTPSessionManager對(duì)象會(huì)收到相關(guān)代理回調(diào)杜秸。

AFHTTPSessionManager對(duì)象接收到服務(wù)器返回二進(jìn)制數(shù)據(jù)的代理回調(diào)后,會(huì)將數(shù)據(jù)傳遞給對(duì)應(yīng)的AFURLSessionManagerTaskDelegate對(duì)象保存润绎。

AFHTTPSessionManager對(duì)象接收到請(qǐng)求完成代理回調(diào)后撬碟,會(huì)從字典中取出對(duì)應(yīng)的AFURLSessionManagerTaskDelegate對(duì)象來(lái)處理其保存的從服務(wù)器返回的二進(jìn)制數(shù)據(jù)。

AFURLSessionManagerTaskDelegate對(duì)象在處理服務(wù)器返回的二進(jìn)制數(shù)據(jù)時(shí)莉撇,會(huì)使用AFJSONResponseSerializer對(duì)象來(lái)驗(yàn)證服務(wù)器的響應(yīng)是否有效呢蛤,并將服務(wù)器返回的二進(jìn)制數(shù)據(jù)反序列化為數(shù)組、字典或者字符串對(duì)象棍郎,并在指定的調(diào)度隊(duì)列中調(diào)用其保存的 block 來(lái)傳遞該結(jié)果對(duì)象其障。

AFNetworking的圖片緩存是如何處理的

AFNetworking使用NSURLCache來(lái)自動(dòng)管理圖片的硬盤緩存。當(dāng)客戶端從服務(wù)器下載圖片時(shí)涂佃,NSURLCache會(huì)自動(dòng)將圖片緩存到客戶端的硬盤中励翼。當(dāng)客戶端再次請(qǐng)求從服務(wù)器下載同一張圖片時(shí)蜈敢,會(huì)直接返回保存在硬盤中的圖片。

AFNetworking使用自定義的AFAutoPurgingImageCache來(lái)管理圖片的內(nèi)存緩存汽抚。

AFAutoPurgingImageCache使用并行調(diào)度隊(duì)列和 GCD 柵欄函數(shù)來(lái)實(shí)現(xiàn)數(shù)據(jù)的多讀單寫抓狭。

AFAutoPurgingImageCache有一個(gè)最大緩存容量,當(dāng)圖片緩存超過(guò)最大容量時(shí)造烁,會(huì)刪除近期未被使用的圖片辐宾。其緩存淘汰算法實(shí)現(xiàn)原理為:

  • 圖片下載完成并被解碼顯示后,使用解碼后的圖片創(chuàng)建一個(gè)AFCachedImage對(duì)象膨蛮。AFCachedImage對(duì)象中保存著解碼后的圖片叠纹,并記錄著圖片的最近訪問(wèn)日期;
  • 使用一個(gè)字典來(lái)存儲(chǔ)AFCachedImage對(duì)象敞葛,以 URL 作為 Key誉察。每次從字典中取出AFCachedImage對(duì)象使用時(shí),會(huì)更新圖片的訪問(wèn)日期惹谐;
  • 定義了一個(gè)常量屬性來(lái)記錄當(dāng)前圖片緩存大谐制;
  • 每次往字典中添加新的AFCachedImage對(duì)象之后氨肌,會(huì)更新記錄的當(dāng)前圖片緩存大小鸿秆。如果當(dāng)前圖片緩存大小超過(guò)了最大緩存容量,則根據(jù)字典中的AFCachedImage對(duì)象創(chuàng)建一個(gè)數(shù)組怎囚,并根據(jù)AFCachedImage對(duì)象中記錄的最近訪問(wèn)日期對(duì)數(shù)組中的AFCachedImage對(duì)象排序卿叽。然后遍歷排序后的數(shù)組,從字典中刪除對(duì)應(yīng)的AFCachedImage對(duì)象恳守,直到當(dāng)前圖片緩存大小達(dá)到設(shè)定的緩存容量考婴。

如何對(duì)AFNetworking進(jìn)行再次封裝的?

  • 定義一個(gè)網(wǎng)絡(luò)請(qǐng)求類催烘,由該類來(lái)負(fù)責(zé)創(chuàng)建并管理一個(gè)AFHTTPSessionManager對(duì)象沥阱。
  • 由于在應(yīng)用程序運(yùn)行期間,需要多次發(fā)送網(wǎng)絡(luò)請(qǐng)求伊群,所以需要為該類實(shí)現(xiàn)一個(gè)單例構(gòu)造方法考杉。
  • 在網(wǎng)絡(luò)請(qǐng)求類的初始化方法中,創(chuàng)建一個(gè)AFHTTPSessionManager對(duì)象舰始,并初始化AFHTTPSessionManager對(duì)象的某些屬性崇棠,例如responseSerializer(JSON 或者 XML 解析,默認(rèn) JSON 解析)蔽午,securityPolicy(是否使用證書驗(yàn)證服務(wù)器的可信度易茬,默認(rèn)不使用)酬蹋,requestSerializer(可設(shè)置超時(shí)時(shí)長(zhǎng))及老。
  • 封裝 GET抽莱,POST,Upload骄恶,Download 請(qǐng)求的便捷方法食铐。

SDWebImage源碼分析

從遠(yuǎn)程下載圖片

  • SDWebImageDownloader對(duì)象創(chuàng)建并管理著一個(gè)NSURLSession對(duì)象和一個(gè)downloadOperationQueue,并且SDWebImageDownloader對(duì)象是NSURLSession對(duì)象的代理僧鲁。
  • SDWebImageDownloaderOperation對(duì)象是一個(gè)自定義的并發(fā)操作對(duì)象虐呻,其是用于從本地發(fā)送HTTP請(qǐng)求到遠(yuǎn)程下載圖片數(shù)據(jù)的,它還封裝了用于響應(yīng)NSURLSession對(duì)象HTTP請(qǐng)求相關(guān)代理方法的邏輯寞秃。

當(dāng)需要從給定的URL下載圖片數(shù)據(jù)時(shí)斟叼,SDWebImageDownloader對(duì)象會(huì)創(chuàng)建一個(gè)相應(yīng)的SDWebImageDownloaderOperation對(duì)象。

SDWebImageDownloaderOperation對(duì)象會(huì)弱引用SDWebImageDownloader對(duì)象創(chuàng)建的NSURLSession對(duì)象春寿,還會(huì)保存外部傳遞的圖片下載完成后執(zhí)行的block回調(diào)朗涩。

SDWebImageDownloaderOperation對(duì)象會(huì)被添加到一個(gè)并行操作隊(duì)列中,當(dāng)并行操作隊(duì)列調(diào)度SDWebImageDownloaderOperation對(duì)象到線程上執(zhí)行時(shí)绑改,SDWebImageDownloaderOperation對(duì)象會(huì)使用弱引用的NSURLSession對(duì)象來(lái)發(fā)送HTTP請(qǐng)求谢床。

當(dāng)SDWebImageDownloader對(duì)象收到NSURLSession對(duì)象的服務(wù)器已返回?cái)?shù)據(jù)代理回調(diào)時(shí),會(huì)將每次接收的圖片數(shù)據(jù)傳遞給對(duì)應(yīng)的SDWebImageDownloaderOperation對(duì)象保存厘线。

當(dāng)SDWebImageDownloader對(duì)象收到NSURLSession對(duì)象的請(qǐng)求完成代理回調(diào)時(shí)识腿,會(huì)從并行操作隊(duì)列中取出對(duì)應(yīng)的未完成的SDWebImageDownloaderOperation對(duì)象來(lái)處理已下載的圖片數(shù)據(jù)。

SDWebImageDownloaderOperation對(duì)象會(huì)根據(jù)已接收的圖片數(shù)據(jù)創(chuàng)建一個(gè)UIImage并且糾正該UIImage的方向造壮。接著渡讼,其使用SDWebImageDecoder中實(shí)現(xiàn)的方法來(lái)解壓縮UIImage。最后耳璧,SDWebImageDownloaderOperation對(duì)象將其自身的isFinished屬性標(biāo)記為YES硝全。

圖片解碼(解壓縮)

SDWebImageDecoderUIImage的一個(gè)分類,封裝了實(shí)現(xiàn)解壓縮普通圖片和高分辨率大圖片的方法楞抡。

其解壓縮圖片的原理是:將圖片繪制到一個(gè)位圖圖形上下文中伟众,繪圖系統(tǒng)在繪制該圖片時(shí),會(huì)解壓縮圖片召廷。接著凳厢,使用這個(gè)位圖圖形上下文來(lái)生成一張位圖,并根據(jù)這個(gè)位圖創(chuàng)建一個(gè)UIImage竞慢,然后返回這個(gè)UIImage先紫。

圖片緩存

SDImageCache用于管理圖片緩存,圖片緩存由內(nèi)存緩存和硬盤緩存組成筹煮。

當(dāng)圖片被使用一次后遮精,將其緩存到內(nèi)存中后,在應(yīng)用程序運(yùn)行期間,下一次再使用時(shí)本冲,就不用再?gòu)挠脖P中去讀取圖片數(shù)據(jù)了准脂,提高了程序運(yùn)行效率。

當(dāng)從遠(yuǎn)程下載完圖片后檬洞,將其保存到硬盤緩存中后狸膏,下次再使用該圖片時(shí),可以直接從硬盤中讀取添怔,而不用再?gòu)倪h(yuǎn)程下載湾戳,節(jié)省了客戶端數(shù)據(jù)流量。

SDImageCache使用NSCache來(lái)管理圖片的內(nèi)存緩存广料,當(dāng)應(yīng)用程序的可用內(nèi)存不足時(shí)砾脑,NSCache會(huì)自動(dòng)清理數(shù)據(jù)。

SDImageCache使用NSFileManager來(lái)管理圖片的硬盤緩存艾杏,當(dāng)應(yīng)用程序?qū)⒁K止運(yùn)行或者進(jìn)入到后臺(tái)時(shí)拦止,會(huì)讀取圖片緩存目錄下所有文件的地址、最后修改日期和文件大小信息糜颠,并遍歷所有文件信息汹族,計(jì)算出總的緩存文件大小。在遍歷過(guò)程中其兴,如果文件的最后修改日期已經(jīng)超過(guò)了硬盤緩存有效期(默認(rèn)為一周)顶瞒,則會(huì)刪除該文件。接著元旬,如果總的緩存文件大小超過(guò)了最大硬盤緩存大辛裥臁(默認(rèn)為不設(shè)限),則會(huì)將文件信息按照其最后修改日期距離此時(shí)最遠(yuǎn)到最近的順序排列匀归,并依次刪除文件坑资,直到剩余的總的文件緩存大小小于設(shè)置的最大硬盤緩存大小。

圖片加載邏輯

SDWebImageManager對(duì)象創(chuàng)建并管理著SDImageCacheSDWebImageDownloader對(duì)象穆端。

調(diào)用UIImageViewsd_setImageWithURL:方法設(shè)置圖片時(shí)袱贮,SDWebImageManager對(duì)象加載圖片的邏輯為:

  1. 首先使用SDImageCache對(duì)象根據(jù) url 在內(nèi)存緩存中查找是否存在圖片。如果存在体啰,則返回該圖片去顯示攒巍;
  2. 如果內(nèi)存緩存中不存在圖片,則在子線程異步在硬盤緩存中查找是否存在圖片荒勇。如果存在柒莉,則將圖片緩存到內(nèi)存中并返回該圖片去顯示;
  3. 如果緩存中不存在圖片沽翔,或者緩存中存在圖片但是需要更新緩存,則會(huì)使用SDWebImageDownloader對(duì)象從遠(yuǎn)程下載圖片;
  4. 圖片下載完成后跨蟹,會(huì)在子線程異步解碼圖片雳殊;
  5. 解碼完成后,先將圖片寫入到內(nèi)存緩存中喷市,然后在將圖片異步寫入到硬盤緩存中。最后威恼,返回該圖片去顯示品姓。

SDWebImage 是如何解決 Cell 重用時(shí)圖片加載錯(cuò)亂問(wèn)題的?

SDWebImage 會(huì)為UIImageView創(chuàng)建一個(gè)字典來(lái)保存加載圖片的 operation 箫措,在使用 SDWebImage 加載圖片時(shí)腹备,會(huì)從字典中取出還未完成的 operation ,并取消這個(gè) operation斤蔓。

為什么要對(duì)圖片進(jìn)行解碼(解壓縮)植酥?

計(jì)算機(jī)屏幕在顯示圖像時(shí),是以單個(gè)像素點(diǎn)來(lái)代表圖像中的某個(gè)點(diǎn)弦牡,對(duì)一組像素點(diǎn)進(jìn)行排列和染色就能構(gòu)成圖像了友驮。

由像素點(diǎn)組成的圖像,叫做位圖驾锰,又稱為點(diǎn)陣圖卸留。

由于 PNG、JPEG 圖片格式是壓縮之后的位圖圖像格式椭豫,所以在將 PNG耻瑟、JPEG 圖片顯示到屏幕上時(shí),必須先對(duì) PNG赏酥、JPEG 格式的圖像數(shù)據(jù)進(jìn)行解壓縮喳整,從而得到圖像的原始像素點(diǎn)數(shù)據(jù)。

SDWebImage的圖片解碼(解壓縮)是如何做的裸扶?

從遠(yuǎn)程下載或者本地加載的圖片一般都是 PNG 或 JPEG 格式框都,使用它們所創(chuàng)建的UIImage對(duì)象的圖片數(shù)據(jù)是還沒(méi)有經(jīng)過(guò)解壓縮的。

使用UIImageView顯示UIImage時(shí)呵晨,系統(tǒng)會(huì)在主線程對(duì)UIImage的圖片數(shù)據(jù)進(jìn)行解壓縮瞬项。而圖片的解壓縮操作是比較耗時(shí)的,如果同時(shí)有多個(gè)UIImage需要顯示何荚,那么就會(huì)在主線程多次執(zhí)行圖片的解壓縮操作囱淋,這樣就會(huì)導(dǎo)致主線程的執(zhí)行效率降低,使程序變得卡頓餐塘。

SDWebImage 為了提高應(yīng)用程序運(yùn)行效率妥衣,會(huì)在圖片數(shù)據(jù)下載完成之后,在子線程強(qiáng)制解壓縮圖片數(shù)據(jù)。其強(qiáng)制解壓縮圖片數(shù)據(jù)的原理是:將未解碼的圖片繪制到一個(gè)位圖圖形上下文中税手,繪圖系統(tǒng)在繪制該圖片時(shí)蜂筹,會(huì)解壓縮圖片。接著芦倒,使用這個(gè)位圖圖形上下文來(lái)生成一張位圖艺挪,并根據(jù)這個(gè)位圖創(chuàng)建一個(gè)新的UIImage,然后返回這個(gè)UIImage兵扬。

使用UIImageView顯示由 SDWebImage 返回的UIImage時(shí)麻裳,就不需要再執(zhí)行解壓縮圖片數(shù)據(jù)的操作了。

由于UIImage的圖片數(shù)據(jù)已經(jīng)被解壓縮器钟,因此會(huì)導(dǎo)致UIImage所占用的內(nèi)存增大津坑,這是一種以空間換時(shí)間的做法。

SDWebImage對(duì)高分辨大圖片的解碼(解壓縮)是如何處理的傲霸?

PNG疆瑰、JPEG 格式是壓縮后的位圖圖像格式,解壓縮 PNG昙啄、JPEG 圖片獲取圖像的原始像素點(diǎn)數(shù)據(jù)時(shí)穆役,內(nèi)存消耗會(huì)暴漲。而 iOS 系統(tǒng)為每個(gè)應(yīng)用程序分配的內(nèi)存是有限的梳凛,當(dāng)解壓縮高分辨大圖片時(shí)孵睬,極有可能出現(xiàn)內(nèi)存溢出,從而導(dǎo)致應(yīng)用程序崩潰伶跷。

SDWebImage 解碼高分辨率大圖片時(shí)使用的策略是將大圖片切割成一個(gè)又一個(gè)的小圖塊掰读,并將這些小圖塊依次壓縮繪制到同一個(gè)位圖圖形上下文中。繪制小圖塊到位圖圖形上下文中時(shí)叭莫,繪圖系統(tǒng)會(huì)解壓縮小圖塊蹈集。相比一次就解壓縮完整的圖片數(shù)據(jù),多次解壓縮圖片的部分?jǐn)?shù)據(jù)會(huì)大大降低內(nèi)存消耗的峰值雇初。當(dāng)所有的小圖塊都壓縮繪制到同一個(gè)位圖圖形上下文中后拢肆,使用這個(gè)位圖圖形上下文生成一個(gè)位圖,然后根據(jù)這個(gè)位圖創(chuàng)建一個(gè)解碼后的UIImage靖诗。由于是壓縮繪制郭怪,所以解碼后的圖片的分辨率會(huì)降低。這是一種以時(shí)間換空間的做法刊橘。

將大圖片切割成小圖塊時(shí)鄙才,由于 iOS 系統(tǒng)檢索圖像數(shù)據(jù)的方式是一行一行的檢索像素點(diǎn)數(shù)據(jù),并且促绵,在將小圖塊繪制到位圖圖形上下文時(shí)攒庵,即使壓縮后的小圖塊的寬度小于位圖圖形上下文的設(shè)定的寬度嘴纺,iOS 系統(tǒng)也必須以位圖圖形上下文中設(shè)定的寬度來(lái)解碼圖像,因此將大圖片橫向切割成多個(gè)小圖塊浓冒,保證小圖塊的寬度和大圖片的寬度一致栽渴,可以大大提高程序執(zhí)行效率。為了避免小圖塊合成完整圖像后稳懒,圖像出現(xiàn)細(xì)線闲擦,在橫向切割大圖片的時(shí)候,還要為小圖塊在其上方多切割1~2行像素點(diǎn)來(lái)覆蓋上一個(gè)圖塊场梆。

計(jì)算壓縮之后的圖片分辨率:

單個(gè)像素點(diǎn)所占字節(jié)(byte)數(shù)為:kBytesPerPixel = 4 Byte

1 MB = 1024 KB = 1024 * 1024 Byte = 1024 * 1024 * 8 Bit

圖片的原始數(shù)據(jù)總大小為:width(分辨率高度) * height(分辨率寬度) * 4 Byte

圖片包含的總像素點(diǎn)個(gè)數(shù):kSourceTotalPixels = 分辨率高度 * 分辨率寬度

需要將圖片解碼后的原始數(shù)據(jù)總大小壓縮至:kDestImageSizeMB = 20 MB(根據(jù)自己的需要去設(shè)置該值)

壓縮后的圖片包含的像素點(diǎn)個(gè)數(shù):kDestTotalPixels = (kDestImageSizeMB * 1024 * 1024)/ 4

根據(jù)以上已知數(shù)據(jù)可以計(jì)算出:
- 圖片的壓縮比例為:imageScale = kDestTotalPixels / kSourceTotalPixels
- 壓縮后的圖片分辨率寬度為:width * imageScale
- 壓縮后的圖片分辨率高度為:height * imageScale

有哪些壓縮圖片的方式墅冷?

使用 UIKit 框架提供的UIImagePNGRepresentation函數(shù)壓縮圖片,這種方式不會(huì)改變圖片的分辨率辙谜,但壓縮是有限度的俺榆。例如感昼,我們想將圖片數(shù)據(jù)的大小壓縮成原來(lái)的二分之一装哆,但實(shí)際可能最多只能壓縮到原來(lái)的三分之二。該方式壓縮的大小與圖片本身有關(guān)定嗓,所以每個(gè)圖片的壓縮結(jié)果各不相同蜕琴。

使用UIImageJPEGRepresentation函數(shù)壓縮圖片,這種方式會(huì)改變圖片的分辨率宵溅,但可以將圖片壓縮到任意大小凌简。

使用UIImage對(duì)象的drawInRect:方法以指定的分辨率將圖片繪制到圖像上下文中,然后從圖像上下文中獲取一張新的圖片恃逻。這種方式可以縮小圖片到任意大小雏搂,但會(huì)改變圖片的分辨率,會(huì)大大降低圖片的質(zhì)量寇损。

另外凸郑,對(duì)于高分辨率的大圖片,如果使用drawInRect:方法以指定的分辨率重新繪制的話矛市,在繪圖系統(tǒng)解壓縮圖片時(shí)芙沥,極有可能出現(xiàn)內(nèi)存溢出(OOM),從而導(dǎo)致應(yīng)用程序崩潰浊吏《颍可以創(chuàng)建一個(gè)位圖圖形上下文,然后使用CGImageCreateWithImageInRect函數(shù)將高分率的大圖片切割成一個(gè)又一個(gè)的小圖塊找田,并使用CGContextDrawImage函數(shù)依次將這些小圖塊壓縮繪制到位圖圖形上下文中歌憨。所有小圖塊繪制完畢后,再使用CGBitmapContextCreateImage函數(shù)根據(jù)該位圖圖形上下文創(chuàng)建一個(gè)位圖墩衙,然后根據(jù)位圖創(chuàng)建一個(gè)已經(jīng)被壓縮和解碼的UIImage躺孝。最后享扔,調(diào)用UIImageJPEGRepresentation函數(shù)將這個(gè)UIImage壓縮成 JPEG 格式的UIImage

壓縮高分率大圖片的另外一種方式是使用更底層的 ImageIO 接口植袍,避免將圖片解碼成位圖惧眠,具體代碼可以參看這篇Blog

如何在不改變圖片分辨率的情況下顯示一張超清大圖片于个?

JPEG/PNG 圖像格式是壓縮后的位圖圖像格式氛魁,在加載 JPEG/PNG 格式的圖片時(shí),需要先將圖片解碼成位圖厅篓,圖片的分辨率越高秀存,解碼操作的內(nèi)存消耗就越大。而 iOS 系統(tǒng)分配給每個(gè)應(yīng)用程序的內(nèi)存是有限的羽氮,所以對(duì)高分辨率大圖片的解碼操作極有可能導(dǎo)致系統(tǒng)強(qiáng)制殺死應(yīng)用程序或链。

蘋果官方提供了一個(gè)CATiledLayer類來(lái)解決加載大圖片引起的性能問(wèn)題,CATiledLayer會(huì)將大圖片切割成一個(gè)又一個(gè)的小圖塊档押,并在主線程之外的其他線程異步繪制這些圖塊澳盐。原本需要一次性在主線程解碼的高分辨率大圖片,現(xiàn)在被分為多次異步解碼低分辨率的小圖塊令宿,當(dāng)每個(gè)小圖塊被渲染到屏幕后叼耙,就會(huì)立即釋放掉,這樣就能分散內(nèi)存的壓力粒没。同時(shí)筛婉,由于CATiledLayer異步繪制這些小圖塊,應(yīng)用程序的主線程不會(huì)被阻塞癞松。

CATiledLayer不能直接使用爽撒,需要子類化UIView,并重寫UIView+(Class)layerClass方法來(lái)將UIView的圖層設(shè)置為CATiledLayer响蓉,還要重寫UIViewdrawRect:方法來(lái)將圖片繪制到UIView關(guān)聯(lián)的圖形上下文中硕勿。

[UIImage imageNamed:@"name"]方法和[UIImage imageWithContentsOfFile:file]方法有什么區(qū)別?

使用imageNamed:方法創(chuàng)建UIImage對(duì)象時(shí)厕妖,會(huì)首先到系統(tǒng)緩存中查找給定名稱的UIImage對(duì)象首尼,如果存在,則直接返回該對(duì)象言秸。否則软能,該方法會(huì)從與給定名稱對(duì)應(yīng)的本地文件中加載圖像數(shù)據(jù),并將其緩存举畸,然后返回結(jié)果對(duì)象查排。

使用imageWithContentsOfFile:方法創(chuàng)建UIImage對(duì)象時(shí),會(huì)直接從指定的本地文件中加載圖片數(shù)據(jù)抄沮,然后返回結(jié)果對(duì)象跋核。

當(dāng)圖片資源需要被反復(fù)使用時(shí)岖瑰,使用imageNamed:方法能提高加載效率。例如砂代,UIButton的背景圖片蹋订。

如果圖片只是偶爾使用一次或者圖片文件體積較大,則使用imageWithContentsOfFile:方法刻伊,這樣可以節(jié)省內(nèi)存空間露戒。

會(huì)導(dǎo)致App崩潰的場(chǎng)景有哪些?

  • 野指針類型的 crash:對(duì)象已經(jīng)被釋放了捶箱,還繼續(xù)給對(duì)象發(fā)送消息智什;
  • unrecognized selector crash:給對(duì)象發(fā)送一個(gè)其不能響應(yīng)的消息;
  • container crash:數(shù)組越界丁屎,字典插入nil荠锭;
  • KVO crash:觀察者對(duì)象被釋放時(shí),沒(méi)有移除其自身來(lái)取消注冊(cè)觀察者晨川。被觀察對(duì)象繼續(xù)發(fā)送通知時(shí)证九,會(huì)產(chǎn)生野指針類型的 crash;
  • NSNotification crash:對(duì)象被釋放時(shí)础爬,沒(méi)有移除通知甫贯。當(dāng)下一個(gè)通知觸發(fā)時(shí)吼鳞,會(huì)產(chǎn)生野指針類型的 crash看蚜。

[NSArray array] 和 [[NSArray alloc] init] 有什么區(qū)別?

在 MRC 模式下赔桌,使用[NSArray array]方法或者@[obj1,obj2,obj3]方式創(chuàng)建數(shù)組對(duì)象時(shí)供炎,會(huì)對(duì)數(shù)組對(duì)象發(fā)送一個(gè)autorelease消息,在結(jié)束使用該數(shù)組對(duì)象時(shí)疾党,就不用再手動(dòng)對(duì)該數(shù)組對(duì)象執(zhí)行一次release操作了音诫,該數(shù)組對(duì)象會(huì)在其棧幀被釋放時(shí)一同被釋放。而使用[NSArray new]或者[[NSArray alloc] init]方法創(chuàng)建的數(shù)組對(duì)象雪位,在結(jié)束使用該數(shù)組對(duì)象時(shí)竭钝,需要我們手動(dòng)執(zhí)行一次release操作來(lái)釋放它。否則蒂教,會(huì)出現(xiàn)內(nèi)存泄漏窗市。

@2x圖片和@3x圖片的區(qū)別是什么逸绎?

相同的屏幕開發(fā)尺寸在不同 iOS 設(shè)備屏幕上對(duì)應(yīng)的像素尺寸是不同的,在 iPhone 4 之前庇茫,屏幕開發(fā)尺寸中的1個(gè)點(diǎn)就等于1個(gè)像素,但 iPhone 4 以及之后的設(shè)備開始使用像素密度更高的 Retina 屏幕螃成,這些設(shè)備的屏幕上的1個(gè)點(diǎn)可能等于2個(gè)像素旦签,也可能等于3個(gè)像素查坪。例如,iPhone 11 的屏幕的1個(gè)點(diǎn)等于2個(gè)像素宁炫,而 iPhone 11 pro 的屏幕的1個(gè)點(diǎn)等于3個(gè)像素偿曙。

一張分辨率為 120x120 的圖片在 iPhone 11 的屏幕上以 60x60 的開發(fā)尺寸顯示是沒(méi)有任何問(wèn)題的,但如果在 iPhone 11 pro 屏幕上以 60x60 的開發(fā)尺寸顯示就會(huì)模糊羔巢,這是因?yàn)?iPhone 11 pro 屏幕上 60x60 的開發(fā)尺寸對(duì)應(yīng)的像素尺寸是 180x180遥昧,120x120 的圖片放到 180x180 的像素尺寸上會(huì)失真。所以在開發(fā)過(guò)程中朵纷,應(yīng)該使用分辨率不同的 @2x 和 @3x 圖片來(lái)適配不同的設(shè)備炭臭。

如何使用SQLite數(shù)據(jù)庫(kù)?

  • 導(dǎo)入 libsqlite3.0.tbd 框架袍辞;
  • 使用NSSearchPathForDirectoriesInDomains函數(shù)創(chuàng)建一個(gè).sqlite 類型的數(shù)據(jù)庫(kù)文件鞋仍;
  • 調(diào)用sqlite3_open函數(shù)打開數(shù)據(jù)庫(kù);
  • 使用sqlite3_exec函數(shù)創(chuàng)建一個(gè)表格搅吁,創(chuàng)建表格的 SQL 語(yǔ)句為create table if not exists t_student (id integer primary key, name text, sex text, age integer)威创;
    • create table,創(chuàng)建表格谎懦;
    • if not exists肚豺,告知系統(tǒng)當(dāng)這個(gè)表格不存在時(shí)才創(chuàng)建,可以不用寫界拦;
    • t_student吸申,表格名稱;
    • ()里的語(yǔ)句是表格中所包含的字段享甸,每一個(gè)字段用,隔開截碴;
    • integer表示整數(shù),real表示浮點(diǎn)數(shù)蛉威,text表示字符串日丹,blob表示二進(jìn)制數(shù)據(jù)。
  • 使用sqlite3_exec函數(shù)向表格中插入數(shù)據(jù)蚯嫌,插入語(yǔ)句為insert into t_student (name, sex, age) values ('tom', 'man', 12)哲虾;
    • insert into,插入數(shù)據(jù)择示;
  • 使用sqlite3_exec函數(shù)更新表格數(shù)據(jù)束凑,更新語(yǔ)句為update t_student set name = 'tom', sex = 'man', age = 15 where id = 6
    • update对妄,更新數(shù)據(jù)湘今;
    • set,要更新的字段剪菱;
    • where摩瞎,根據(jù)條件更新拴签,多條件用and組合。
  • 使用sqlite3_exec函數(shù)刪除數(shù)據(jù)旗们,刪除語(yǔ)句為delete from t_student where id = 4蚓哩;
    • delete from,刪除數(shù)據(jù)上渴。
  • 使用sqlite3_prepare_v2函數(shù)查詢數(shù)據(jù)岸梨,查詢語(yǔ)句為select * from t_student where name = 'tom'
    • select * from稠氮,查詢數(shù)據(jù)曹阔;
    • *,表示查詢所有字段隔披。

FMDB源碼分析

FMDB 有三個(gè)主要的類:

  • FMDatabaseFMDatabase對(duì)象維護(hù)著一個(gè) SQLite 數(shù)據(jù)庫(kù)赃份,用來(lái)執(zhí)行 SQL 語(yǔ)句;
  • FMResultSet:使用FMDatabase對(duì)象執(zhí)行查詢操作后返回的結(jié)果集奢米;
  • FMDatabaseQueueFMDatabaseQueue對(duì)象維護(hù)著一個(gè)FMDatabase對(duì)象和一個(gè)串行隊(duì)列抓韩,其使用dispatch_sync函數(shù)向串行隊(duì)列中同步添加數(shù)據(jù)庫(kù)操作。

FMDB 將除查詢(query)以外的所有操作都?xì)w為更新(update)操作:create鬓长、update谒拴、deleteinsert涉波。

LRU算法

LRU 算法根據(jù)數(shù)據(jù)的歷史訪問(wèn)記錄來(lái)淘汰數(shù)據(jù)英上,其核心思想是“如果數(shù)據(jù)最近被訪問(wèn)過(guò),那么將來(lái)被訪問(wèn)的幾率也更高”怠蹂。最常見的實(shí)現(xiàn)是使用一個(gè)鏈表保存緩存數(shù)據(jù)善延,詳細(xì)算法實(shí)現(xiàn)如下:

  1. 新數(shù)據(jù)插入到鏈表頭部少态;
  2. 每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問(wèn))城侧,則將數(shù)據(jù)移到鏈表頭部;
  3. 當(dāng)鏈表滿的時(shí)候彼妻,將鏈表尾部的數(shù)據(jù)丟棄嫌佑。

iOS 開發(fā)中可以使用字典和數(shù)組配合來(lái)實(shí)現(xiàn) LRU 算法。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末侨歉,一起剝皮案震驚了整個(gè)濱河市屋摇,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幽邓,老刑警劉巖炮温,帶你破解...
    沈念sama閱讀 219,490評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異牵舵,居然都是意外死亡柒啤,警方通過(guò)查閱死者的電腦和手機(jī)倦挂,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,581評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)担巩,“玉大人方援,你說(shuō)我怎么就攤上這事√伟” “怎么了犯戏?”我有些...
    開封第一講書人閱讀 165,830評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)拳话。 經(jīng)常有香客問(wèn)我先匪,道長(zhǎng),這世上最難降的妖魔是什么弃衍? 我笑而不...
    開封第一講書人閱讀 58,957評(píng)論 1 295
  • 正文 為了忘掉前任胚鸯,我火速辦了婚禮,結(jié)果婚禮上笨鸡,老公的妹妹穿的比我還像新娘姜钳。我一直安慰自己,他們只是感情好形耗,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,974評(píng)論 6 393
  • 文/花漫 我一把揭開白布哥桥。 她就那樣靜靜地躺著,像睡著了一般激涤。 火紅的嫁衣襯著肌膚如雪拟糕。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,754評(píng)論 1 307
  • 那天倦踢,我揣著相機(jī)與錄音送滞,去河邊找鬼。 笑死辱挥,一個(gè)胖子當(dāng)著我的面吹牛犁嗅,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播晤碘,決...
    沈念sama閱讀 40,464評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼褂微,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了园爷?” 一聲冷哼從身側(cè)響起宠蚂,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎童社,沒(méi)想到半個(gè)月后求厕,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,847評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,995評(píng)論 3 338
  • 正文 我和宋清朗相戀三年呀癣,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了旅东。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,137評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡十艾,死狀恐怖抵代,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情忘嫉,我是刑警寧澤荤牍,帶...
    沈念sama閱讀 35,819評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站庆冕,受9級(jí)特大地震影響康吵,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜访递,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,482評(píng)論 3 331
  • 文/蒙蒙 一晦嵌、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧拷姿,春花似錦惭载、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,023評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至踪古,卻和暖如春含长,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背伏穆。 一陣腳步聲響...
    開封第一講書人閱讀 33,149評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工拘泞, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人枕扫。 一個(gè)月前我還...
    沈念sama閱讀 48,409評(píng)論 3 373
  • 正文 我出身青樓陪腌,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親铡原。 傳聞我的和親對(duì)象是個(gè)殘疾皇子偷厦,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,086評(píng)論 2 355

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