Objective-C的內(nèi)存管理
引用計(jì)數(shù)機(jī)制
Objective-C 主要采用引用計(jì)數(shù)機(jī)制來(lái)管理對(duì)象的內(nèi)存讥珍。
運(yùn)行時(shí)系統(tǒng)維護(hù)有一個(gè)哈希表SideTablesMap
,SideTablesMap
中存儲(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
NSNumber
、NSDate
這類小對(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)存占用翻倍了耕捞。
而NSNumber
和NSDate
對(duì)象的值需要占用的內(nèi)存通常不用 8 個(gè)字節(jié),因?yàn)?4 個(gè)字節(jié)所能表示的整數(shù)值可以達(dá)到40億烫幕,這已經(jīng)可以滿足絕大多數(shù)需求了俺抽。
為了改進(jìn)NSNumber
和NSDate
在64位設(shè)備上的內(nèi)存占用,蘋果引入了 Tagged Pointer较曼,它被拆成兩部分磷斧,一部分直接保存數(shù)值,另一部分作為特俗標(biāo)記捷犹,表示這是一個(gè)特別的指針弛饭,不指向任何地址。
如果 8 個(gè)字節(jié)可以承載NSNumber
或NSDate
的值萍歉,那么在創(chuàng)建NSNumber
或NSDate
對(duì)象時(shí)侣颂,就會(huì)直接生成一個(gè) Tagged Pointer。這樣枪孩,NSNumber
和NSDate
在 64 位設(shè)備上的內(nèi)存占用就還是 8 個(gè)字節(jié)憔晒。同時(shí),還大大提高了讀寫它們的效率蔑舞。
由于 Tagged Pointer 的值不是內(nèi)存地址丛晌,而是真正的值,所以NSNumber
和NSDate
對(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)在代碼中插入retain
、release
操作佑稠,并使用運(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ì)在SideTable
的weak_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ò)誤信息娇斩。 -
AFJSONResponseSerializer
是AFHTTPResponseSerializer
的子類仁卷,其在驗(yàn)證服務(wù)器的響應(yīng)是否有效后穴翩,會(huì)將服務(wù)器返回的 JSON 數(shù)據(jù)反序列化為 Objective-C 對(duì)象。 -
AFURLSessionManager
是 AFNetworking 框架的核心類锦积,它創(chuàng)建并管理一個(gè)NSURLSession
對(duì)象芒帕。 -
AFHTTPSessionManager
是AFURLSessionManager
的子類,封裝了用于發(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
硝全。
圖片解碼(解壓縮)
SDWebImageDecoder
是UIImage
的一個(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)建并管理著SDImageCache
和SDWebImageDownloader
對(duì)象穆端。
調(diào)用UIImageView
的sd_setImageWithURL:
方法設(shè)置圖片時(shí)袱贮,SDWebImageManager
對(duì)象加載圖片的邏輯為:
- 首先使用
SDImageCache
對(duì)象根據(jù) url 在內(nèi)存緩存中查找是否存在圖片。如果存在体啰,則返回該圖片去顯示攒巍; - 如果內(nèi)存緩存中不存在圖片,則在子線程異步在硬盤緩存中查找是否存在圖片荒勇。如果存在柒莉,則將圖片緩存到內(nèi)存中并返回該圖片去顯示;
- 如果緩存中不存在圖片沽翔,或者緩存中存在圖片但是需要更新緩存,則會(huì)使用
SDWebImageDownloader
對(duì)象從遠(yuǎn)程下載圖片; - 圖片下載完成后跨蟹,會(huì)在子線程異步解碼圖片雳殊;
- 解碼完成后,先將圖片寫入到內(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
响蓉,還要重寫UIView
的drawRect:
方法來(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è)主要的類:
-
FMDatabase
:FMDatabase
對(duì)象維護(hù)著一個(gè) SQLite 數(shù)據(jù)庫(kù)赃份,用來(lái)執(zhí)行 SQL 語(yǔ)句; -
FMResultSet
:使用FMDatabase
對(duì)象執(zhí)行查詢操作后返回的結(jié)果集奢米; -
FMDatabaseQueue
:FMDatabaseQueue
對(duì)象維護(hù)著一個(gè)FMDatabase
對(duì)象和一個(gè)串行隊(duì)列抓韩,其使用dispatch_sync
函數(shù)向串行隊(duì)列中同步添加數(shù)據(jù)庫(kù)操作。
FMDB 將除查詢(query
)以外的所有操作都?xì)w為更新(update
)操作:create
鬓长、update
谒拴、delete
、insert
涉波。
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)如下:
- 新數(shù)據(jù)插入到鏈表頭部少态;
- 每當(dāng)緩存命中(即緩存數(shù)據(jù)被訪問(wèn))城侧,則將數(shù)據(jù)移到鏈表頭部;
- 當(dāng)鏈表滿的時(shí)候彼妻,將鏈表尾部的數(shù)據(jù)丟棄嫌佑。
iOS 開發(fā)中可以使用字典和數(shù)組配合來(lái)實(shí)現(xiàn) LRU 算法。