內(nèi)存管理
1.什么情況使用weak關(guān)鍵字,相比assign有什么不同缘眶?
-
什么情況使用 weak 關(guān)鍵字?
在 ARC 中,在有可能出現(xiàn)循環(huán)引用的時(shí)候,往往要通過讓其中一端使用 weak 來解決,比如: delegate 代理屬性
自身已經(jīng)對(duì)它進(jìn)行一次強(qiáng)引用,沒有必要再強(qiáng)引用一次,此時(shí)也會(huì)使用 weak,自定義 IBOutlet 控件屬性一般也使用 weak;當(dāng)然面殖,也可以使用strong。在下文也有論述:《IBOutlet連出來的視圖屬性為什么可以被設(shè)置成weak?》
-
不同點(diǎn):
weak 此特質(zhì)表明該屬性定義了一種“非擁有關(guān)系” (nonowning relationship)哭廉。為這種屬性設(shè)置新值時(shí)脊僚,設(shè)置方法既不保留新值,也不釋放舊值遵绰。此特質(zhì)同assign類似辽幌, 然而在屬性所指的對(duì)象遭到摧毀時(shí),屬性值也會(huì)清空(nil out)椿访。 而 assign 的“設(shè)置方法”只會(huì)執(zhí)行針對(duì)“純量類型” (scalar type乌企,例如 CGFloat 或 NSlnteger 等)的簡單賦值操作。
assign 可以用非 OC 對(duì)象,而 weak 必須用于 OC 對(duì)象
2.如何讓自己的類用copy修飾符成玫?如何重寫帶copy關(guān)鍵字的setter加酵?
-
若想令自己所寫的對(duì)象具有拷貝功能,則需實(shí)現(xiàn) NSCopying 協(xié)議哭当。如果自定義的對(duì)象分為可變版本與不可變版本猪腕,那么就要同時(shí)實(shí)現(xiàn) NSCopying 與 NSMutableCopying 協(xié)議。
具體步驟:
需聲明該類遵從 NSCopying 協(xié)議
實(shí)現(xiàn) NSCopying 協(xié)議钦勘。該協(xié)議只有一個(gè)方法:
- (id)copyWithZone:(NSZone *)zone;
注意:一提到讓自己的類用 copy 修飾符陋葡,我們總是想覆寫copy方法,其實(shí)真正需要實(shí)現(xiàn)的卻是 “copyWithZone” 方法个盆。
-
重寫帶 copy 關(guān)鍵字的 setter脖岛,例如:
- (void)setName:(NSString *)name { //[_name release]; _name = [name copy]; }
3.深拷貝與淺拷貝
淺拷貝只是對(duì)指針的拷貝,拷貝后兩個(gè)指針指向同一個(gè)內(nèi)存空間颊亮,深拷貝不但對(duì)指針進(jìn)行拷貝柴梆,而且對(duì)指針指向的內(nèi)容進(jìn)行拷貝,經(jīng)深拷貝后的指針是指向兩個(gè)不同地址的指針终惑。
當(dāng)對(duì)象中存在指針成員時(shí)绍在,除了在復(fù)制對(duì)象時(shí)需要考慮自定義拷貝構(gòu)造函數(shù),還應(yīng)該考慮以下兩種情形:
當(dāng)函數(shù)的參數(shù)為對(duì)象時(shí),實(shí)參傳遞給形參的實(shí)際上是實(shí)參的一個(gè)拷貝對(duì)象偿渡,系統(tǒng)自動(dòng)通過拷貝構(gòu)造函數(shù)實(shí)現(xiàn)臼寄;
當(dāng)函數(shù)的返回值為一個(gè)對(duì)象時(shí),該對(duì)象實(shí)際上是函數(shù)內(nèi)對(duì)象的一個(gè)拷貝溜宽,用于返回函數(shù)調(diào)用處吉拳。
copy方法:如果是非可擴(kuò)展類對(duì)象,則是淺拷貝适揉。如果是可擴(kuò)展類對(duì)象留攒,則是深拷貝。
mutableCopy方法:無論是可擴(kuò)展類對(duì)象還是不可擴(kuò)展類對(duì)象嫉嘀,都是深拷貝炼邀。
4.@property的本質(zhì)是什么?ivar剪侮、getter拭宁、setter是如何生成并添加到這個(gè)類中的
-
@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í)例變量一般通過“存取方法”(access method)來訪問。其中彩匕,“獲取方法” (getter)用于讀取變量值在旱,而“設(shè)置方法” (setter)用于寫入變量值。
-
ivar推掸、getter、setter 是自動(dòng)合成這個(gè)類中的
完成屬性定義后驻仅,編譯器會(huì)自動(dòng)編寫訪問這些屬性所需的方法谅畅,此過程叫做“自動(dòng)合成”(autosynthesis)。需要強(qiáng)調(diào)的是噪服,這個(gè)過程由編譯 器在編譯期執(zhí)行毡泻,所以編輯器里看不到這些“合成方法”(synthesized method)的源代碼。除了生成方法代碼 getter粘优、setter 之外仇味,編譯器還要自動(dòng)向類中添加適當(dāng)類型的實(shí)例變量,并且在屬性名前面加下劃線雹顺,以此作為實(shí)例變量的名字丹墨。在前例中,會(huì)生成兩個(gè)實(shí)例變量嬉愧,其名稱分別為 _firstName 與 _lastName贩挣。也可以在類的實(shí)現(xiàn)代碼里通過 @synthesize 語法來指定實(shí)例變量的名字.
5.@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
6.簡要說一下@autoreleasePool的數(shù)據(jù)結(jié)構(gòu)??
簡單說是雙向鏈表王财,每張鏈表頭尾相接卵迂,有 parent、child指針
每創(chuàng)建一個(gè)池子绒净,會(huì)在首部創(chuàng)建一個(gè) 哨兵 對(duì)象,作為標(biāo)記
最外層池子的頂端會(huì)有一個(gè)next指針见咒。當(dāng)鏈表容量滿了,就會(huì)在鏈表的頂端挂疆,并指向下一張表改览。
7.BAD_ACCESS在什么情況下出現(xiàn)?
訪問了懸垂指針囱嫩,比如對(duì)一個(gè)已經(jīng)釋放的對(duì)象執(zhí)行了release恃疯、訪問已經(jīng)釋放對(duì)象的成員變量或者發(fā)消息。 死循環(huán)
8.使用CADisplayLink墨闲、NSTimer有什么注意點(diǎn)今妄?
CADisplayLink、NSTimer會(huì)造成循環(huán)引用鸳碧,可以使用YYWeakProxy或者為CADisplayLink盾鳞、NSTimer添加block方法解決循環(huán)引用
9.iOS內(nèi)存分區(qū)情況
-
棧區(qū)(Stack)
由編譯器自動(dòng)分配釋放,存放函數(shù)的參數(shù)瞻离,局部變量的值等
棧是向低地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu)腾仅,是一塊連續(xù)的內(nèi)存區(qū)域
-
堆區(qū)(Heap)
由程序員分配釋放
是向高地址擴(kuò)展的數(shù)據(jù)結(jié)構(gòu),是不連續(xù)的內(nèi)存區(qū)域
-
全局區(qū)
全局變量和靜態(tài)變量的存儲(chǔ)是放在一塊的套利,初始化的全局變量和靜態(tài)變量在一塊區(qū)域推励,未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域
程序結(jié)束后由系統(tǒng)釋放
-
常量區(qū)
常量字符串就是放在這里的
程序結(jié)束后由系統(tǒng)釋放
-
代碼區(qū)
存放函數(shù)體的二進(jìn)制代碼
-
注:
在 iOS 中,堆區(qū)的內(nèi)存是應(yīng)用程序共享的肉迫,堆中的內(nèi)存分配是系統(tǒng)負(fù)責(zé)的
系統(tǒng)使用一個(gè)鏈表來維護(hù)所有已經(jīng)分配的內(nèi)存空間(系統(tǒng)僅僅記錄验辞,并不管理具體的內(nèi)容)
變量使用結(jié)束后,需要釋放內(nèi)存喊衫,OC 中是判斷引用計(jì)數(shù)是否為 0跌造,如果是就說明沒有任何變量使用該空間,那么系統(tǒng)將其回收
當(dāng)一個(gè) app 啟動(dòng)后族购,代碼區(qū)壳贪、常量區(qū)、全局區(qū)大小就已經(jīng)固定寝杖,因此指向這些區(qū)的指針不會(huì)產(chǎn)生崩潰性的錯(cuò)誤违施。而堆區(qū)和棧區(qū)是時(shí)時(shí)刻刻變化的(堆的創(chuàng)建銷毀,棧的彈入彈出)朝墩,所以當(dāng)使用一個(gè)指針指向這個(gè)區(qū)里面的內(nèi)存時(shí)醉拓,一定要注意內(nèi)存是否已經(jīng)被釋放伟姐,否則會(huì)產(chǎn)生程序崩潰(也即是野指針報(bào)錯(cuò))
10.iOS內(nèi)存管理方式
-
Tagged Pointer(小對(duì)象)
Tagged Pointer 專門用來存儲(chǔ)小的對(duì)象,例如 NSNumber 和 NSDate
Tagged Pointer 指針的值不再是地址了亿卤,而是真正的值愤兵。所以,實(shí)際上它不再是一個(gè)對(duì)象了排吴,它只是一個(gè)披著對(duì)象皮的普通變量而已秆乳。所以,它的內(nèi)存并不存儲(chǔ)在堆中钻哩,也不需要 malloc 和 free
在內(nèi)存讀取上有著 3 倍的效率屹堰,創(chuàng)建時(shí)比以前快 106 倍
objc_msgSend 能識(shí)別 Tagged Pointer,比如 NSNumber 的 intValue 方法街氢,直接從指針提取數(shù)據(jù)
使用 Tagged Pointer 后扯键,指針內(nèi)存儲(chǔ)的數(shù)據(jù)變成了 Tag + Data,也就是將數(shù)據(jù)直接存儲(chǔ)在了指針中
-
NONPOINTER_ISA (指針中存放與該對(duì)象內(nèi)存相關(guān)的信息) 蘋果將 isa 設(shè)計(jì)成了聯(lián)合體珊肃,在 isa 中存儲(chǔ)了與該對(duì)象相關(guān)的一些內(nèi)存的信息荣刑,原因也如上面所說,并不需要 64 個(gè)二進(jìn)制位全部都用來存儲(chǔ)指針伦乔。
isa 的結(jié)構(gòu):
// x86_64 架構(gòu) struct { uintptr_t nonpointer : 1; // 0:普通指針厉亏,1:優(yōu)化過,使用位域存儲(chǔ)更多信息 uintptr_t has_assoc : 1; // 對(duì)象是否含有或曾經(jīng)含有關(guān)聯(lián)引用 uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的dealloc uintptr_t shiftcls : 44; // 存放著 Class烈和、Meta-Class 對(duì)象的內(nèi)存地址信息 uintptr_t magic : 6; // 用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化 uintptr_t weakly_referenced : 1; // 是否被弱引用指向 uintptr_t deallocating : 1; // 對(duì)象是否正在釋放 uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 來存儲(chǔ)引用計(jì)數(shù) uintptr_t extra_rc : 8; // 引用計(jì)數(shù)能夠用 8 個(gè)二進(jìn)制位存儲(chǔ)時(shí)爱只,直接存儲(chǔ)在這里 }; // arm64 架構(gòu) struct { uintptr_t nonpointer : 1; // 0:普通指針,1:優(yōu)化過招刹,使用位域存儲(chǔ)更多信息 uintptr_t has_assoc : 1; // 對(duì)象是否含有或曾經(jīng)含有關(guān)聯(lián)引用 uintptr_t has_cxx_dtor : 1; // 表示是否有C++析構(gòu)函數(shù)或OC的dealloc uintptr_t shiftcls : 33; // 存放著 Class恬试、Meta-Class 對(duì)象的內(nèi)存地址信息 uintptr_t magic : 6; // 用于在調(diào)試時(shí)分辨對(duì)象是否未完成初始化 uintptr_t weakly_referenced : 1; // 是否被弱引用指向 uintptr_t deallocating : 1; // 對(duì)象是否正在釋放 uintptr_t has_sidetable_rc : 1; // 是否需要使用 sidetable 來存儲(chǔ)引用計(jì)數(shù) uintptr_t extra_rc : 19; // 引用計(jì)數(shù)能夠用 19 個(gè)二進(jìn)制位存儲(chǔ)時(shí),直接存儲(chǔ)在這里 };
這里的 has_sidetable_rc 和 extra_rc疯暑,has_sidetable_rc 表明該指針是否引用了 sidetable 散列表忘渔,之所以有這個(gè)選項(xiàng),是因?yàn)樯倭康囊糜?jì)數(shù)是不會(huì)直接存放在 SideTables 表中的缰儿,對(duì)象的引用計(jì)數(shù)會(huì)先存放在 extra_rc 中,當(dāng)其被存滿時(shí)散址,才會(huì)存入相應(yīng)的 SideTables 散列表中乖阵,SideTables 中有很多張 SideTable,每個(gè) SideTable 也都是一個(gè)散列表预麸,而引用計(jì)數(shù)表就包含在 SideTable 之中瞪浸。
-
散列表(引用計(jì)數(shù)表、弱引用表)
引用計(jì)數(shù)要么存放在 isa 的 extra_rc 中吏祸,要么存放在引用計(jì)數(shù)表中对蒲,而引用計(jì)數(shù)表包含在一個(gè)叫 SideTable 的結(jié)構(gòu)中,它是一個(gè)散列表,也就是哈希表事富。而 SideTable 又包含在一個(gè)全局的 StripeMap 的哈希映射表中畜眨,這個(gè)表的名字叫 SideTables。
當(dāng)一個(gè)對(duì)象訪問 SideTables 時(shí):
首先會(huì)取得對(duì)象的地址泛鸟,將地址進(jìn)行哈希運(yùn)算蝠咆,與 SideTables 中 SideTable 的個(gè)數(shù)取余,最后得到的結(jié)果就是該對(duì)象所要訪問的 SideTable
在取得的 SideTable 中的 RefcountMap 表中再進(jìn)行一次哈希查找北滥,找到該對(duì)象在引用計(jì)數(shù)表中對(duì)應(yīng)的位置
如果該位置存在對(duì)應(yīng)的引用計(jì)數(shù)刚操,則對(duì)其進(jìn)行操作,如果沒有對(duì)應(yīng)的引用計(jì)數(shù)再芋,則創(chuàng)建一個(gè)對(duì)應(yīng)的 size_t 對(duì)象菊霜,其實(shí)就是一個(gè) uint 類型的無符號(hào)整型
弱引用表也是一張哈希表的結(jié)構(gòu),其內(nèi)部包含了每個(gè)對(duì)象對(duì)應(yīng)的弱引用表 weak_entry_t济赎,而 weak_entry_t 是一個(gè)結(jié)構(gòu)體數(shù)組鉴逞,其中包含的則是每一個(gè)對(duì)象弱引用的對(duì)象所對(duì)應(yīng)的弱引用指針。
11.循環(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來修飾變量 在MRC下,__block不會(huì)增加其引用計(jì)數(shù)闪幽,避免了循環(huán)引用 在ARC下啥辨,__block修飾對(duì)象會(huì)被強(qiáng)引用,無法避免循環(huán)引用盯腌,需要手動(dòng)解除溉知。
2、在合適時(shí)機(jī)去手動(dòng)斷開循環(huán)引用腕够。 通常我們使用第一種级乍。
-
代理(delegate)循環(huán)引用屬于相互循環(huán)引用
delegate 是iOS中開發(fā)中比較常遇到的循環(huán)引用,一般在聲明delegate的時(shí)候都要使用弱引用 weak,或者assign,當(dāng)然怎么選擇使用assign還是weak帚湘,MRC的話只能用assign玫荣,在ARC的情況下最好使用weak,因?yàn)閣eak修飾的變量在釋放后自動(dòng)指向nil大诸,防止野指針存在
-
NSTimer循環(huán)引用屬于相互循環(huán)使用
在控制器內(nèi)捅厂,創(chuàng)建NSTimer作為其屬性贯卦,由于定時(shí)器創(chuàng)建后也會(huì)強(qiáng)引用該控制器對(duì)象,那么該對(duì)象和定時(shí)器就相互循環(huán)引用了焙贷。 如何解決呢撵割? 這里我們可以使用手動(dòng)斷開循環(huán)引用: 如果是不重復(fù)定時(shí)器,在回調(diào)方法里將定時(shí)器invalidate并置為nil即可盈厘。 如果是重復(fù)定時(shí)器睁枕,在合適的位置將其invalidate并置為nil即可
3、block循環(huán)引用
一個(gè)簡單的例子:
@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í)行開始時(shí)self對(duì)象還未被釋放,而執(zhí)行過程中捐晶,self被釋放了菲语,由于是用weak修飾的,那么weakSelf也被釋放了惑灵,此時(shí)在block里訪問weakSelf時(shí)山上,就可能會(huì)發(fā)生錯(cuò)誤(向nil對(duì)象發(fā)消息并不會(huì)崩潰,但也沒任何效果)英支。 對(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];
};
<<<<<<< HEAD
=======
>>>>>>> b3040bc46edebd638bd0855c05a48f9308af5781
推薦文集
注明:內(nèi)容摘自網(wǎng)絡(luò)池凄,如有侵權(quán)聯(lián)系小編刪除