一谜叹、 在 Obj-C 中廓旬,如何檢測(cè)內(nèi)存泄漏盯蝴?你知道哪些方式速侈?
目前我知道的方式有以下幾種
- Memory Leaks
- Alloctions
- Analyse
- Debug Memory Graph
- MLeaksFinder
泄露的內(nèi)存主要有以下兩種:
- Leak Memory 這種是忘記 Release 操作所泄露的內(nèi)存薄扁。
- Abandon Memory 這種是循環(huán)引用剪返,無(wú)法釋放掉的內(nèi)存。
上面所說(shuō)的五種方式邓梅,其實(shí)前四種都比較麻煩脱盲,需要不斷地調(diào)試運(yùn)行,第五種是騰訊閱讀團(tuán)隊(duì)出品日缨,效果好一些钱反,感興趣的可以看一下這兩篇文章:
二、 在 MRC 下如何重寫(xiě)屬性的 Setter 和 Getter ?
- setter
-(void)setBrand:(NSString *)brand{
//如果實(shí)例變量指向的地址和參數(shù)指向的地址不同
if (_brand != brand)
{
//將實(shí)例變量的引用計(jì)數(shù)減一
[_brand release];
//將參數(shù)變量的引用計(jì)數(shù)加一,并賦值給實(shí)例變量
_brand = [brand retain];
}
}
- getter
-(NSString *)brand{
//將實(shí)例變量的引用計(jì)數(shù)加1后,添加自動(dòng)減1
//作用,保證調(diào)用getter方法取值時(shí)可以取到值的同時(shí)在完全不需要使用后釋放
return [[_brand retain] autorelease];
}
- 重寫(xiě)dealloc
//MRC下 手動(dòng)釋放內(nèi)存 可重寫(xiě)dealloc但不要調(diào)用dealloc 會(huì)崩潰
-(void)dealloc{
[_string release];
//必須最后調(diào)用super dealloc
[super dealloc];
}
三匣距、循環(huán)引用
循環(huán)引用的實(shí)質(zhì):多個(gè)對(duì)象相互之間有強(qiáng)引用面哥,不能釋放讓系統(tǒng)回收。
如何解決循環(huán)引用毅待?
1尚卫、避免產(chǎn)生循環(huán)引用,通常是將 strong 引用改為 weak 引用恩静。
比如在修飾屬性時(shí)用weak
在block內(nèi)調(diào)用對(duì)象方法時(shí)焕毫,使用其弱引用蹲坷,這里可以使用兩個(gè)宏
#define WS(weakSelf) __weak __typeof(&*self)weakSelf = self; // 弱引用
#define ST(strongSelf) __strong __typeof(&*self)strongSelf = weakSelf; //使用這個(gè)要先聲明weakSelf
還可以使用__block來(lái)修飾變量
在 MRC 下,__block不會(huì)增加其引用計(jì)數(shù)邑飒,避免了循環(huán)引用
在 ARC 下循签,__block修飾對(duì)象會(huì)被強(qiáng)引用,無(wú)法避免循環(huán)引用疙咸,需要手動(dòng)解除县匠。
2、在合適時(shí)機(jī)去手動(dòng)斷開(kāi)循環(huán)引用撒轮。
通常我們使用第一種乞旦。
循環(huán)引用場(chǎng)景:
自循環(huán)引用:對(duì)象強(qiáng)持有的屬性同時(shí)持有該對(duì)象
-
相互循環(huán)引用
-
多循環(huán)引用
1、代理(delegate)循環(huán)引用屬于相互循環(huán)引用
delegate 是iOS中開(kāi)發(fā)中比較常遇到的循環(huán)引用题山,一般在聲明delegate的時(shí)候都要使用弱引用 weak,或者assign,當(dāng)然怎么選擇使用assign還是weak兰粉,MRC的話(huà)只能用assign,在ARC的情況下最好使用weak顶瞳,因?yàn)閣eak修飾的變量在釋放后自動(dòng)指向nil玖姑,防止野指針存在
2、NSTimer循環(huán)引用屬于相互循環(huán)引用
在控制器內(nèi)慨菱,創(chuàng)建NSTimer作為其屬性焰络,由于定時(shí)器創(chuàng)建后也會(huì)強(qiáng)引用該控制器對(duì)象,那么該對(duì)象和定時(shí)器就相互循環(huán)引用了符喝。
如何解決呢闪彼?
這里我們可以使用手動(dòng)斷開(kāi)循環(huán)引用:
如果是不重復(fù)定時(shí)器,在回調(diào)方法里將定時(shí)器invalidate并置為nil即可协饲。
如果是重復(fù)定時(shí)器畏腕,在合適的位置將其invalidate并置為nil即可
3、block循環(huán)引用
一個(gè)簡(jiǎn)單的例子:
@property (copy, nonatomic) dispatch_block_t myBlock;
@property (copy, nonatomic) NSString *blockString;
- (void)testBlock {
self.myBlock = ^() {
NSLog(@"%@",self.blockString);
};
}
由于block會(huì)對(duì)block中的對(duì)象進(jìn)行持有操作,就相當(dāng)于持有了其中的對(duì)象茉稠,而如果此時(shí)block中的對(duì)象又持有了該block郊尝,則會(huì)造成循環(huán)引用。
解決方案就是使用__weak修飾self即可
__weak typeof(self) weakSelf = self;
self.myBlock = ^() {
NSLog(@"%@",weakSelf.blockString);
};
- 并不是所有block都會(huì)造成循環(huán)引用战惊。
只有被強(qiáng)引用了的block才會(huì)產(chǎn)生循環(huán)引用
而比如dispatch_async(dispatch_get_main_queue(), ^ { }),[UIView animateWithDuration:1 animations:^ { }]這些系統(tǒng)方法等
或者block并不是其屬性而是臨時(shí)變量,即棧block
[self testWithBlock:^{
NSLog(@"%@",self);
}];
- (void)testWithBlock:(dispatch_block_t)block {
block();
}
還有一種場(chǎng)景流昏,在block執(zhí)行開(kāi)始時(shí)self對(duì)象還未被釋放,而執(zhí)行過(guò)程中吞获,self被釋放了况凉,由于是用weak修飾的,那么weakSelf也被釋放了各拷,此時(shí)在block里訪(fǎng)問(wèn)weakSelf時(shí)刁绒,就可能會(huì)發(fā)生錯(cuò)誤(向nil對(duì)象發(fā)消息并不會(huì)崩潰,但也沒(méi)任何效果)烤黍。
對(duì)于這種場(chǎng)景知市,應(yīng)該在block中對(duì) 對(duì)象使用__strong修飾傻盟,使得在block期間對(duì) 對(duì)象持有,block執(zhí)行結(jié)束后嫂丙,解除其持有娘赴。
__weak typeof(self) weakSelf = self;
self.myBlock = ^() {
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf test];
};
四、 說(shuō)一下什么是 懸垂指針跟啤?什么是 野指針?
懸垂指針
指針指向的內(nèi)存已經(jīng)被釋放了诽表,但是指針還存在,這就是一個(gè) 懸垂指針 或者說(shuō) 迷途指針
野指針
沒(méi)有進(jìn)行初始化的指針隅肥,其實(shí)都是 野指針
五竿奏、 說(shuō)一下對(duì) retain,copy,assign,weak,_Unsafe_Unretain 關(guān)鍵字的理解
Strong
Strong 修飾符表示指向并持有該對(duì)象,其修飾對(duì)象的引用計(jì)數(shù)會(huì)加1腥放。該對(duì)象只要引用計(jì)數(shù)不為0就不會(huì)被銷(xiāo)毀泛啸。當(dāng)然可以通過(guò)將變量強(qiáng)制賦值 nil 來(lái)進(jìn)行銷(xiāo)毀。
Weak
weak 修飾符指向但是并不持有該對(duì)象秃症,引用計(jì)數(shù)也不會(huì)加1平痰。在 Runtime 中對(duì)該屬性進(jìn)行了相關(guān)操作,無(wú)需處理伍纫,可以自動(dòng)銷(xiāo)毀。weak 用來(lái)修飾對(duì)象昂芜,多用于避免循環(huán)引用的地方莹规。weak 不可以修飾基本數(shù)據(jù)類(lèi)型。
assign
assign 主要用于修飾基本數(shù)據(jù)類(lèi)型泌神,
例如 NSInteger良漱,CGFloat,存儲(chǔ)在棧中欢际,內(nèi)存不用程序員管理母市。assign 是可以修飾對(duì)象的,但是會(huì)出現(xiàn)問(wèn)題损趋。
copy
copy 關(guān)鍵字和 strong 類(lèi)似患久,copy 多用于修飾有可變類(lèi)型的不可變對(duì)象 NSString, NSArray, NSDictionary上。
__unsafe_unretain
__unsafe_unretain 類(lèi)似于 weak 浑槽,但是當(dāng)對(duì)象被釋放后蒋失,指針依然保存著之前的地址,被釋放后的地址變?yōu)?僵尸對(duì)象桐玻,訪(fǎng)問(wèn)被釋放的地址就會(huì)出問(wèn)題篙挽,所以說(shuō)他是不安全的。
__autoreleasing
將對(duì)象賦值給附有 __autoreleasing 修飾的變量等同于 ARC 無(wú)效時(shí)調(diào)用對(duì)象的 autorelease 方法,實(shí)質(zhì)就是扔進(jìn)了自動(dòng)釋放池镊靴。
六铣卡、是否了解 深拷貝 和 淺拷貝 的概念链韭,集合類(lèi)深拷貝如何實(shí)現(xiàn)
簡(jiǎn)而言之:
1、對(duì)不可變的非集合對(duì)象煮落,copy是指針拷貝敞峭,mutablecopy是內(nèi)容拷貝
2、對(duì)于可變的非集合對(duì)象州邢,copy儡陨,mutablecopy都是內(nèi)容拷貝
3、對(duì)不可變的數(shù)組量淌、字典骗村、集合等集合類(lèi)對(duì)象,copy是指針拷貝呀枢,mutablecopy是內(nèi)容拷貝
4胚股、對(duì)于可變的數(shù)組、字典裙秋、集合等集合類(lèi)對(duì)象琅拌,copy,mutablecopy都是內(nèi)容拷貝
但是摘刑,對(duì)于集合對(duì)象的內(nèi)容復(fù)制僅僅是對(duì) 對(duì)象本身进宝,但是對(duì) 對(duì)象的里面的元素還是指針復(fù)制。要想復(fù)制整個(gè)集合對(duì)象枷恕,就要用集合深復(fù)制的方法党晋,有兩種:
(1)使用initWithArray:copyItems:方法,將第二個(gè)參數(shù)設(shè)置為YES即可
NSDictionary shallowCopyDict = [[NSDictionary alloc] initWithDictionary:someDictionary copyItems:YES];
(2)將集合對(duì)象進(jìn)行歸檔(archive)然后解歸檔(unarchive):
NSArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:oldArray]];
七徐块、 使用自動(dòng)引用計(jì)數(shù)應(yīng)遵循的原則
- 不能使用 retain未玻、release、retainCount胡控、autorelease扳剿。
- 不可以使用 NSAllocateObject庆聘、NSDeallocateObject踱蠢。
- 必須遵守內(nèi)存管理方法的命名規(guī)則。
- 不需要顯示的調(diào)用 Dealloc员凝。
- 使用 @autoreleasePool 來(lái)代替 NSAutoreleasePool橙困。
- 不可以使用區(qū)域 NSZone敛劝。
- 對(duì)象性變量不可以作為 C 語(yǔ)言的結(jié)構(gòu)體成員。
- 顯示轉(zhuǎn)換 id 和 void*纷宇。
八夸盟、 能不能簡(jiǎn)述一下 Dealloc 的實(shí)現(xiàn)機(jī)制
Dealloc 的實(shí)現(xiàn)機(jī)制是內(nèi)存管理部分的重點(diǎn),把這個(gè)知識(shí)點(diǎn)弄明白像捶,對(duì)于全方位的理解內(nèi)存管理的是很有必要上陕。
1.Dealloc 調(diào)用流程
- 1.首先調(diào)用 _objc_rootDealloc()
- 2.接下來(lái)調(diào)用 rootDealloc()
- 3.這時(shí)候會(huì)判斷是否可以被釋放桩砰,判斷的依據(jù)主要有5個(gè),判斷是否有以下五種情況
- NONPointer_ISA
- weakly_reference
- has_assoc
- has_cxx_dtor
- has_sidetable_rc
- 4-1.如果有以上五中任意一種释簿,將會(huì)調(diào)用 object_dispose()方法亚隅,做下一步的處理。
- 4-2.如果沒(méi)有之前五種情況的任意一種庶溶,則可以執(zhí)行釋放操作煮纵,C函數(shù)的 free()。
- 5.執(zhí)行完畢偏螺。
2.object_dispose() 調(diào)用流程行疏。
- 1.直接調(diào)用 objc_destructInstance()。
- 2.之后調(diào)用 C函數(shù)的 free()套像。
3.objc_destructInstance() 調(diào)用流程
- 1.先判斷 hasCxxDtor酿联,如果有 C++ 的相關(guān)內(nèi)容,要調(diào)用 object_cxxDestruct() 夺巩,銷(xiāo)毀 C++ 相關(guān)的內(nèi)容贞让。
- 2.再判斷 hasAssocitatedObjects,如果有的話(huà)柳譬,要調(diào)用 object_remove_associations()喳张,銷(xiāo)毀關(guān)聯(lián)對(duì)象的一系列操作。
- 3.然后調(diào)用 clearDeallocating()美澳。
- 4.執(zhí)行完畢销部。
4.clearDeallocating() 調(diào)用流程。
- 1.先執(zhí)行 sideTable_clearDellocating()人柿。
- 2.再執(zhí)行 weak_clear_no_lock,在這一步驟中,會(huì)將指向該對(duì)象的弱引用指針置為 nil忙厌。
- 3.接下來(lái)執(zhí)行 table.refcnts.eraser()凫岖,從引用計(jì)數(shù)表中擦除該對(duì)象的引用計(jì)數(shù)。
- 4.至此為止逢净,Dealloc 的執(zhí)行流程結(jié)束哥放。
九、 內(nèi)存中的5大區(qū)分別是什么爹土?
棧區(qū)(stack):由編譯器自動(dòng)分配釋放 甥雕,存放函數(shù)的參數(shù)值,局部變量的值等胀茵。其 操作方式類(lèi)似于數(shù)據(jù)結(jié)構(gòu)中的棧社露。
堆區(qū)(heap):一般由程序員分配釋放, 若程序員不釋放琼娘,程序結(jié)束時(shí)可能由OS回收 峭弟。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事附鸽,分配方式倒是類(lèi)似于鏈表。
全局區(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)制代碼俯在。
十竟秫、 內(nèi)存管理默認(rèn)的關(guān)鍵字是什么?
- MRC
@property (atomic,readWrite,retain) UIView *view;
- ARC
@property (atomic,readWrite,strong) UIView *view;
如果改為基本數(shù)據(jù)類(lèi)型朝巫,那就是 assign
鸿摇。
十一、內(nèi)存管理方案
- 1.taggedPointer :存儲(chǔ)小對(duì)象如NSNumber劈猿。深入理解Tagged Pointer
- 2.NONPOINTER_ISA(非指針型的isa):在64位架構(gòu)下拙吉,isa指針是占64比特位的,實(shí)際上只有30多位就已經(jīng)夠用了揪荣,為了提高利用率筷黔,剩余的比特位存儲(chǔ)了內(nèi)存管理的相關(guān)數(shù)據(jù)內(nèi)容。
- 散列表:復(fù)雜的數(shù)據(jù)結(jié)構(gòu)仗颈,包括了引用計(jì)數(shù)表和弱引用表
通過(guò)SideTables()結(jié)構(gòu)來(lái)實(shí)現(xiàn)的佛舱,SideTables()結(jié)構(gòu)下,有很多SideTable的數(shù)據(jù)結(jié)構(gòu)挨决。
而sideTable當(dāng)中包含了自旋鎖请祖,引用計(jì)數(shù)表,弱引用表脖祈。
SideTables()實(shí)際上是一個(gè)哈希表肆捕,通過(guò)對(duì)象的地址來(lái)計(jì)算該對(duì)象的引用計(jì)數(shù)在哪個(gè)sideTable中。
自旋鎖:
- 自旋鎖是“忙等”的鎖盖高。
- 適用于輕量訪(fǎng)問(wèn)慎陵。
引用計(jì)數(shù)表和弱引用表實(shí)際是一個(gè)哈希表,來(lái)提高查找效率喻奥。
十二席纽、內(nèi)存布局
- 棧(stack):方法調(diào)用,局部變量等撞蚕,是連續(xù)的润梯,高地址往低地址擴(kuò)展
- 堆(heap):通過(guò)alloc等分配的對(duì)象,是離散的,低地址往高地址擴(kuò)展仆救,需要我們手動(dòng)控制
- 未初始化數(shù)據(jù)(bss):未初始化的全局變量等
- 已初始化數(shù)據(jù)(data):已初始化的全局變量等
- 代碼段(text):程序代碼
2抒和、64bit和32bit下 long
和char*
所占字節(jié)是不同的
- char:1字節(jié)(ASCII 2 = 256個(gè)字符)
- char*(即指針變量):4個(gè)字節(jié)(32位的尋址空間是2,即32個(gè)bit,也就是4個(gè)字節(jié)彤蔽。同理64位編譯器為8個(gè)字節(jié))
- short int : 2個(gè)字節(jié) 范圍 -2~> 2 即 -32768~>32767
- int: 4個(gè)字節(jié) 范圍 -2147483648~>2147483647
- unsigned int : 4個(gè)字節(jié)
- long: 4個(gè)字節(jié) 范圍 和int一樣 64位下8個(gè)字節(jié)摧莽,范圍 -9223372036854775808~9223372036854775807
- long long: 8個(gè)字節(jié) 范圍-9223372036854775808~9223372036854775807
- unsigned long long: 8個(gè)字節(jié) 最大值:1844674407370955161
- float: 4個(gè)字節(jié)
- double: 8個(gè)字節(jié)。
3顿痪、static镊辕、const 和 sizeof 關(guān)鍵字
static關(guān)鍵字
答:Static的用途主要有兩個(gè),一是用于修飾存儲(chǔ)類(lèi)型使之成為靜態(tài)存儲(chǔ)類(lèi)型蚁袭,二是用于修飾鏈接屬性使之成為內(nèi)部鏈接屬性征懈。
- 1、靜態(tài)存儲(chǔ)類(lèi)型:
在函數(shù)內(nèi)定義的靜態(tài)局部變量揩悄,該變量存在內(nèi)存的靜態(tài)區(qū)卖哎,所以即使該函數(shù)運(yùn)行結(jié)束,靜態(tài)變量的值不會(huì)被銷(xiāo)毀删性,函數(shù)下次運(yùn)行時(shí)能仍用到這個(gè)值亏娜。
在函數(shù)外定義的靜態(tài)變量——靜態(tài)全局變量,該變量的作用域只能在定義該變量的文件中蹬挺,不能被其他文件通過(guò)extern引用维贺。
- 2、內(nèi)部鏈接屬性
靜態(tài)函數(shù)只能在聲明它的源文件中使用巴帮。
const關(guān)鍵字
- 1溯泣、聲明常變量,使得指定的變量不能被修改榕茧。
const int a = 5;/*a的值一直為5垃沦,不能被改變*/
const int b; b = 10;/*b的值被賦值為10后,不能被改變*/
const int *ptr; /*ptr為指向整型常量的指針用押,ptr的值可以修改肢簿,但不能修改其所指向的值*/
int *const ptr;/*ptr為指向整型的常量指針,ptr的值不能修改只恨,但可以修改其所指向的值*/
const int *const ptr;/*ptr為指向整型常量的常量指針译仗,ptr及其指向的值都不能修改*/
- 2抬虽、修飾函數(shù)形參官觅,使得形參在函數(shù)內(nèi)不能被修改,表示輸入?yún)?shù)阐污。
如
int fun(const int a);或int fun(const char *str);
- 3休涤、修飾函數(shù)返回值,使得函數(shù)的返回值不能被修改。
const char *getstr(void);使用:const *str= getstr();
const int getint(void); 使用:const int a =getint();
sizeof關(guān)鍵字
sizeof是在編譯階段處理功氨,且不能被編譯為機(jī)器碼序苏。sizeof的結(jié)果等于對(duì)象或類(lèi)型所占的內(nèi)存字節(jié)數(shù)。sizeof的返回值類(lèi)型為size_t捷凄。
變量:int a; sizeof(a)為4忱详;
指針:int *p; sizeof(p)為4;
數(shù)組:int b[10]; sizeof(b)為數(shù)組的大小跺涤,4*10匈睁;int c[0]; sizeof(c)等于0
結(jié)構(gòu)體:struct (int a; char ch;)s1; sizeof(s1)為8 與結(jié)構(gòu)體字節(jié)對(duì)齊有關(guān)。
對(duì)結(jié)構(gòu)體求sizeof時(shí)桶错,有兩個(gè)原則:
(1)展開(kāi)后的結(jié)構(gòu)體的第一個(gè)成員的偏移量應(yīng)當(dāng)是被展開(kāi)的結(jié)構(gòu)體中最大的成員的整數(shù)倍航唆。
(2)結(jié)構(gòu)體大小必須是所有成員大小的整數(shù)倍,這里所有成員計(jì)算的是展開(kāi)后的成員院刁,而不是將嵌套的結(jié)構(gòu)體當(dāng)做一個(gè)整體糯钙。
注意:不能對(duì)結(jié)構(gòu)體中的位域成員使用sizeof
sizeof(void)等于1
sizeof(void *)等于4
十三、 講一下 iOS
內(nèi)存管理的理解
實(shí)際上是三種方案的結(jié)合
1.TaggedPointer(針對(duì)類(lèi)似于
NSNumber
的小對(duì)象類(lèi)型)-
2.NONPOINTER_ISA(64位系統(tǒng)下)
- 第一位的 0 或 1 代表是純地址型 isa 指針退腥,還是 NONPOINTER_ISA 指針任岸。
- 第二位,代表是否有關(guān)聯(lián)對(duì)象
- 第三位代表是否有 C++ 代碼阅虫。
- 接下來(lái)33位代表指向的內(nèi)存地址
- 接下來(lái)有 弱引用 的標(biāo)記
- 接下來(lái)有是否 delloc 的標(biāo)記....等等
-
3.散列表(引用計(jì)數(shù)表演闭、weak表)
- SideTables 表在 非嵌入式的64位系統(tǒng)中,有 64張 SideTable 表
- 每一張 SideTable 主要是由三部分組成颓帝。自旋鎖米碰、引用計(jì)數(shù)表、弱引用表购城。
- 全局的 引用計(jì)數(shù) 之所以不存在同一張表中吕座,是為了避免資源競(jìng)爭(zhēng),解決效率的問(wèn)題瘪板。
- 引用計(jì)數(shù)表 中引入了 分離鎖 的概念吴趴,將一張表分拆成多個(gè)部分,對(duì)他們分別加鎖侮攀,可以實(shí)現(xiàn)并發(fā)操作锣枝,提升執(zhí)行效率
十四、 講一下 @dynamic 關(guān)鍵字兰英?
@dynamic 意味著編譯器不會(huì)幫助我們自動(dòng)合成 setter 和 getter 方法撇叁。我們需要手動(dòng)實(shí)現(xiàn)、這里就涉及到 Runtime 的動(dòng)態(tài)添加方法的知識(shí)點(diǎn)畦贸。
十五陨闹、 簡(jiǎn)要說(shuō)一下 @autoreleasePool 的數(shù)據(jù)結(jié)構(gòu)楞捂?
簡(jiǎn)單說(shuō)是雙向鏈表,每張鏈表頭尾相接趋厉,有 parent寨闹、child指針
每創(chuàng)建一個(gè)池子,會(huì)在首部創(chuàng)建一個(gè) 哨兵 對(duì)象,作為標(biāo)記
最外層池子的頂端會(huì)有一個(gè) next 指針君账。當(dāng)鏈表容量滿(mǎn)了繁堡,就會(huì)在鏈表的頂端,并指向下一張表乡数。
十六帖蔓、 訪(fǎng)問(wèn) __weak 修飾的變量,是否已經(jīng)被注冊(cè)在了 @autoreleasePool 中瞳脓?為什么塑娇?
答案是肯定的,__weak 修飾的變量屬于弱引用劫侧,如果沒(méi)有被注冊(cè)到 @autoreleasePool 中埋酬,創(chuàng)建之后也就會(huì)隨之銷(xiāo)毀,為了延長(zhǎng)它的生命周期烧栋,必須注冊(cè)到 @autoreleasePool 中写妥,以延緩釋放。
十七审姓、 retain珍特、release 的實(shí)現(xiàn)機(jī)制?
1.Retain 的實(shí)現(xiàn)機(jī)制魔吐。
SideTable& table = SideTables()[This];
size_t& refcntStorage = table.refcnts[This];
refcntStorage += SIZE_TABLE_RC_ONE;
2.Release 的實(shí)現(xiàn)機(jī)制扎筒。
SideTable& table = SideTables()[This];
size_t& refcntStorage = table.refcnts[This];
refcntStorage -= SIZE_TABLE_RC_ONE;
二者的實(shí)現(xiàn)機(jī)制類(lèi)似,概括講就是通過(guò)第一層 hash 算法酬姆,找到 指針變量 所對(duì)應(yīng)的 sideTable嗜桌。然后再通過(guò)一層 hash 算法,找到存儲(chǔ) 引用計(jì)數(shù) 的 size_t辞色,然后對(duì)其進(jìn)行增減操作骨宠。retainCount 不是固定的 1,SIZE_TABLE_RC_ONE 是一個(gè)宏定義相满,實(shí)際上是一個(gè)值為 4 的偏移量层亿。
十八、MRC(手動(dòng)引用計(jì)數(shù))和ARC(自動(dòng)引用計(jì)數(shù))
1立美、MRC:alloc匿又,retain,release悯辙,retainCount,autorelease,dealloc
2琳省、ARC:
- ARC是LLVM和Runtime協(xié)作的結(jié)果
- ARC禁止手動(dòng)調(diào)用retain,release躲撰,retainCount,autorelease關(guān)鍵字
- ARC新增weak针贬,strong關(guān)鍵字
3、引用計(jì)數(shù)管理:
- alloc: 經(jīng)過(guò)一系列函數(shù)調(diào)用拢蛋,最終調(diào)用了calloc函數(shù)桦他,這里并沒(méi)有設(shè)置引用計(jì)數(shù)為1
- retain: 經(jīng)過(guò)兩次哈希查找,找到其對(duì)應(yīng)引用計(jì)數(shù)值谆棱,然后將引用計(jì)數(shù)加1(實(shí)際是加偏移量)
- release:和retain相反快压,經(jīng)過(guò)兩次哈希查找,找到其對(duì)應(yīng)引用計(jì)數(shù)值垃瞧,然后將引用計(jì)數(shù)減1
-
dealloc:
4蔫劣、弱引用管理:
添加weak變量:通過(guò)哈希算法位置查找添加。如果查找對(duì)應(yīng)位置中已經(jīng)有了當(dāng)前對(duì)象所對(duì)應(yīng)的弱引用數(shù)組个从,就把新的弱引用變量添加到數(shù)組當(dāng)中脉幢;如果沒(méi)有,就創(chuàng)建一個(gè)弱引用數(shù)組嗦锐,并將該弱引用變量添加到該數(shù)組中嫌松。
當(dāng)一個(gè)被weak修飾的對(duì)象被釋放后,weak對(duì)象怎么處理的奕污?
清除weak變量萎羔,同時(shí)設(shè)置指向?yàn)閚il。當(dāng)對(duì)象被dealloc釋放后碳默,在dealloc的內(nèi)部實(shí)現(xiàn)中贾陷,會(huì)調(diào)用弱引用清除的相關(guān)函數(shù),會(huì)根據(jù)當(dāng)前對(duì)象指針查找弱引用表嘱根,找到當(dāng)前對(duì)象所對(duì)應(yīng)的弱引用數(shù)組昵宇,將數(shù)組中的所有弱引用指針都置為nil。
5儿子、自動(dòng)釋放池:
在當(dāng)前runloop將要結(jié)束的時(shí)候調(diào)用objc_autoreleasePoolPop瓦哎,并push進(jìn)來(lái)一個(gè)新的AutoreleasePool
AutoreleasePoolPage是以棧為結(jié)點(diǎn)通過(guò)雙向鏈表的形式組合而成,是和線(xiàn)程一一對(duì)應(yīng)的柔逼。
內(nèi)部屬性有parent蒋譬,child對(duì)應(yīng)前后兩個(gè)結(jié)點(diǎn),thread對(duì)應(yīng)線(xiàn)程 愉适,next指針指向棧中下一個(gè)可填充的位置犯助。
- AutoreleasePool實(shí)現(xiàn)原理?
編譯器會(huì)將 @autoreleasepool {} 改寫(xiě)為:
void * ctx = objc_autoreleasePoolPush;
{}
objc_autoreleasePoolPop(ctx);
objc_autoreleasePoolPush:
把當(dāng)前next位置置為nil维咸,即哨兵對(duì)象,然后next指針指向下一個(gè)可入棧位置剂买,
AutoreleasePool的多層嵌套惠爽,即每次objc_autoreleasePoolPush,實(shí)際上是不斷地向棧中插入哨兵對(duì)象瞬哼。objc_autoreleasePoolPop:
根據(jù)傳入的哨兵對(duì)象找到對(duì)應(yīng)位置婚肆。
給上次push操作之后添加的對(duì)象依次發(fā)送release消息。
回退next指針到正確的位置坐慰。
十九较性、 BAD_ACCESS 在什么情況下出現(xiàn)?
訪(fǎng)問(wèn)了已經(jīng)被銷(xiāo)毀的內(nèi)存空間,就會(huì)報(bào)出這個(gè)錯(cuò)誤结胀。
根本原因是有 懸垂指針 沒(méi)有被釋放赞咙。
二十、 autoReleasePool 什么時(shí)候釋放?
App 啟動(dòng)后糟港,蘋(píng)果在主線(xiàn)程 RunLoop 里注冊(cè)了兩個(gè) Observer攀操,其回調(diào)都是 _wrapRunLoopWithAutoreleasePoolHandler()。
第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop)秸抚,其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池崔赌。其 order 是 -2147483647,優(yōu)先級(jí)最高耸别,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前健芭。
第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池; Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來(lái)釋放自動(dòng)釋放池秀姐。這個(gè) Observer 的 order 是 2147483647慈迈,優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后省有。
在主線(xiàn)程執(zhí)行的代碼痒留,通常是寫(xiě)在諸如事件回調(diào)、Timer回調(diào)內(nèi)的蠢沿。這些回調(diào)會(huì)被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著伸头,所以不會(huì)出現(xiàn)內(nèi)存泄漏,開(kāi)發(fā)者也不必顯示創(chuàng)建 Pool 了舷蟀。
二十一恤磷、 ARC自動(dòng)內(nèi)存管理的原則
- 自己生成的對(duì)象,自己持有
- 非自己生成的對(duì)象野宜,自己可以持有
- 自己持有的對(duì)象不再需要時(shí)扫步,需要對(duì)其進(jìn)行釋放
- 非自己持有的對(duì)象無(wú)法釋放
二十二、 ARC 在編譯時(shí)做了哪些工作
根據(jù)代碼執(zhí)行的上下文語(yǔ)境匈子,在適當(dāng)?shù)奈恢貌迦?retain河胎,release
二十三、 ARC 在運(yùn)行時(shí)做了哪些工作虎敦?
主要是指 weak 關(guān)鍵字游岳。weak 修飾的變量能夠在引用計(jì)數(shù)為 0 時(shí)被自動(dòng)設(shè)置成 nil政敢,顯然是有運(yùn)行時(shí)邏輯在工作的。
為了保證向后兼容性胚迫,ARC 在運(yùn)行時(shí)檢測(cè)到類(lèi)函數(shù)中的 autorelease 后緊跟其后 retain喷户,此時(shí)不直接調(diào)用對(duì)象的 autorelease 方法,而是改為調(diào)用 objc_autoreleaseReturnValue晌区。
objc_autoreleaseReturnValue 會(huì)檢視當(dāng)前方法返回之后即將要執(zhí)行的那段代碼,若那段代碼要在返回對(duì)象上執(zhí)行 retain 操作通贞,則設(shè)置全局?jǐn)?shù)據(jù)結(jié)構(gòu)中的一個(gè)標(biāo)志位朗若,而不執(zhí)行 autorelease 操作,與之相似昌罩,如果方法返回了一個(gè)自動(dòng)釋放的對(duì)象哭懈,而調(diào)用方法的代碼要保留此對(duì)象,那么此時(shí)不直接執(zhí)行 retain 茎用,而是改為執(zhí)行 objc_retainAoutoreleasedReturnValue函數(shù)遣总。此函數(shù)要檢測(cè)剛才提到的標(biāo)志位,若已經(jīng)置位轨功,則不執(zhí)行 retain 操作旭斥,設(shè)置并檢測(cè)標(biāo)志位,要比調(diào)用 autorelease 和 retain 更快古涧。
二十四垂券、 ARC 的 retainCount 怎么存儲(chǔ)的?
存在64張哈希表中羡滑,根據(jù)哈希算法去查找所在的位置菇爪,無(wú)需遍歷,十分快捷
散列表(引用計(jì)數(shù)表柒昏、weak表)
- SideTables 表在 非嵌入式的64位系統(tǒng)中凳宙,有 64張 SideTable 表
- 每一張 SideTable 主要是由三部分組成。自旋鎖职祷、引用計(jì)數(shù)表氏涩、弱引用表。
- 全局的 引用計(jì)數(shù) 之所以不存在同一張表中有梆,是為了避免資源競(jìng)爭(zhēng)削葱,解決效率的問(wèn)題。
- 引用計(jì)數(shù)表 中引入了 分離鎖 的概念淳梦,將一張表分拆成多個(gè)部分析砸,對(duì)他們分別加鎖,可以實(shí)現(xiàn)并發(fā)操作爆袍,提升執(zhí)行效率
引用計(jì)數(shù)表(哈希表)
通過(guò)指針的地址首繁,查找到引用計(jì)數(shù)的地址作郭,大大提升查找效率
通過(guò) DisguisedPtr(objc_object) 函數(shù)存儲(chǔ),同時(shí)也通過(guò)這個(gè)函數(shù)查找弦疮,這樣就避免了循環(huán)遍歷夹攒。
二十五、 __weak 屬性修飾的變量胁塞,如何實(shí)現(xiàn)在變量沒(méi)有強(qiáng)引用后自動(dòng)置為 nil 咏尝?
用的弱引用 - weak表。也是一張 哈希表啸罢。
被 weak 修飾的指針變量所指向的地址是 key 编检,所有指向這塊內(nèi)存地址的指針會(huì)被添加在一個(gè)數(shù)組里,這個(gè)數(shù)組是 Value扰才。當(dāng)內(nèi)存地址銷(xiāo)毀允懂,數(shù)組里的所有對(duì)象被置為 nil。
二十六衩匣、 __weak 和 _Unsafe_Unretain 的區(qū)別蕾总?
weak 修飾的指針變量,在指向的內(nèi)存地址銷(xiāo)毀后琅捏,會(huì)在 Runtime 的機(jī)制下生百,自動(dòng)置為 nil。
_Unsafe_Unretain 不會(huì)置為 nil柄延,容易出現(xiàn) 懸垂指針置侍,發(fā)生崩潰。但是 _Unsafe_Unretain 比 __weak 效率高拦焚。
二十七蜡坊、 為什么已經(jīng)有了 ARC ,但還是需要 @AutoreleasePool 的存在?
避免內(nèi)存峰值赎败,及時(shí)釋放不需要的內(nèi)存空間
二十八秕衙、 函數(shù)返回一個(gè)對(duì)象時(shí),會(huì)對(duì)對(duì)象 autorelease 么僵刮?為什么据忘?
會(huì) ,為了延長(zhǎng)返回對(duì)象的生命周期搞糕,給其他使用者留足調(diào)用的時(shí)間
二十九勇吊、什么情況使用 weak 關(guān)鍵字,相比 assign 有什么不同?
- 什么情況使用 weak 關(guān)鍵字?
在 ARC 中,在有可能出現(xiàn)循環(huán)引用的時(shí)候,往往要通過(guò)讓其中一端使用 weak 來(lái)解決,比如: delegate 代理屬性:自身已經(jīng)對(duì)它進(jìn)行一次強(qiáng)引用,沒(méi)有必要再?gòu)?qiáng)引用一次,此時(shí)也會(huì)使用 weak;自定義 IBOutlet 控件屬性一般也使用 weak窍仰;當(dāng)然汉规,也可以使用strong - 不同點(diǎn):
weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)。為這種屬性設(shè)置新值時(shí),設(shè)置方法既不保留新值针史,也不釋放舊值晶伦。此特質(zhì)同assign類(lèi)似, 然而在屬性所指的對(duì)象遭到摧毀時(shí)啄枕,屬性值也會(huì)清空(nil out)婚陪。 而 assign 的“設(shè)置方法”只會(huì)執(zhí)行針對(duì)“純量類(lèi)型” (scalar type,例如 CGFloat 或 NSlnteger 等)的簡(jiǎn)單賦值操作频祝。
assign 可以用于非 OC 對(duì)象,而 weak 必須用于 OC 對(duì)象
三十泌参、 如何讓自己的類(lèi)用 copy 修飾符?即讓自己寫(xiě)的對(duì)象具備拷貝功能?如何重寫(xiě)帶copy 關(guān)鍵字的 setter 常空?
- 若想令自己所寫(xiě)的對(duì)象具有拷貝功能沽一,則需實(shí)現(xiàn) NSCopying 協(xié)議。如果自定義的對(duì)象分為可變版本與不可變版本窟绷,那么就要同時(shí)實(shí)現(xiàn) NSCopying 與 NSMutableCopying 協(xié)議锯玛。
-
具體步驟:
- 需聲明該類(lèi)遵從 NSCopying 協(xié)議
- 實(shí)現(xiàn) NSCopying 協(xié)議咐柜。該協(xié)議只有一個(gè)方法:
- (id)copyWithZone:(NSZone *)zone;
注意:一提到讓自己的類(lèi)用 copy 修飾符兼蜈,我們總是想覆寫(xiě)copy方法,其實(shí)真正需要實(shí)現(xiàn)的卻是 “copyWithZone” 方法拙友。
-
- 重寫(xiě)帶 copy 關(guān)鍵字的 setter为狸,例如:
- (void)setName:(NSString *)name {
//[_name release];
_name = [name copy];
}
三十一、@property的本質(zhì)是什么遗契?ivar辐棒、getter、setter是如何生成并添加到這個(gè)類(lèi)中的牍蜂?
- @property 的本質(zhì)是實(shí)例變量(ivar)+存取方法(access method = getter + setter),即 @property = ivar + getter + setter;
“屬性” (property)作為 Objective-C 的一項(xiàng)特性漾根,主要的作用就在于封裝對(duì)象中的數(shù)據(jù)。 Objective-C 對(duì)象通常會(huì)把其所需要的數(shù)據(jù)保存為各種實(shí)例變量鲫竞。實(shí)例變量一般通過(guò)“存取方法”(access method)來(lái)訪(fǎng)問(wèn)辐怕。其中,“獲取方法” (getter)用于讀取變量值从绘,而“設(shè)置方法” (setter)用于寫(xiě)入變量值寄疏。
- ivar、getter僵井、setter 是自動(dòng)合成這個(gè)類(lèi)中的
完成屬性定義后陕截,編譯器會(huì)自動(dòng)編寫(xiě)訪(fǎng)問(wèn)這些屬性所需的方法,此過(guò)程叫做“自動(dòng)合成”(autosynthesis)批什。需要強(qiáng)調(diào)的是农曲,這個(gè)過(guò)程由編譯器在編譯期執(zhí)行,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼驻债。
除了生成方法代碼 getter朋蔫、setter 之外罚渐,編譯器還要自動(dòng)向類(lèi)中添加適當(dāng)類(lèi)型的實(shí)例變量,并且在屬性名前面加上下劃線(xiàn)驯妄,以此作為實(shí)例變量的名字荷并。在前例中,會(huì)生成兩個(gè)實(shí)例變量青扔,其名稱(chēng)分別為 _firstName 與 _lastName源织。也可以在類(lèi)的實(shí)現(xiàn)代碼里通過(guò) @synthesize 語(yǔ)法來(lái)指定實(shí)例變量的名字.
三十二、@protocol和category中如何使用@property?
- 在 protocol 中使用 @property 只會(huì)生成 setter 和 getter 方法聲明,我們使用屬性的目的,是希望遵守我協(xié)議的對(duì)象能實(shí)現(xiàn)該屬性
- category 使用 @property 也是只會(huì)生成 setter 和 getter 方法的聲明,如果我們真的需要給 category 增加屬性的實(shí)現(xiàn),需要借助于運(yùn)行時(shí)的兩個(gè)函數(shù):objc_setAssociatedObject和objc_getAssociatedObject