前言
在學(xué)習(xí)ARC之前,先來(lái)復(fù)習(xí)一下內(nèi)存管理以及autorelease的實(shí)現(xiàn)
一、autorelease實(shí)現(xiàn)
先來(lái)看一下GNUstep源代碼:autorelease
其本質(zhì)就是調(diào)用NSAutoreleasePool
對(duì)象的addObject
類(lèi)方法,就是將對(duì)象加到自動(dòng)釋放池中
接下來(lái)再看一下廢棄自動(dòng)釋放池的一些功能函數(shù)
二、蘋(píng)果的實(shí)現(xiàn)
可使用showPools
輸出現(xiàn)在的NSAutoreleasePool的狀況輸出到控制臺(tái)
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
id obj2 = [[NSObject alloc] init];
id obj3 = [[NSObject alloc] init];
[obj autorelease];
[obj2 autorelease];
[obj3 autorelease];
_objc_autoreleasePoolPrint();
[pool drain];
三、內(nèi)存管理的思考方式
引用計(jì)數(shù)式內(nèi)存管理的思考方式就是思考ARC所引起的變化
ARC有效時(shí),id 類(lèi)型和對(duì)象類(lèi)型同C語(yǔ)言其他類(lèi)型不同,其類(lèi)型上必須附加所有權(quán)修飾符。 所有權(quán)修飾符一共有4 種
(ARC環(huán)境下特有)
- __strong修飾符
- __weak修飾符
- __unsafe_unretained修飾符
- __autoreleasing修飾符
__strong修飾符
_ strong修飾符是id類(lèi)型和對(duì)象類(lèi)型默認(rèn)的所有權(quán)修飾符
也就是說(shuō)id obj = [[NSObject alloc] init]; = id __strong obj = [[NSObject alloc] init];
再來(lái)看下面這段代碼
//ARC有效
{
id __strong obj = [[NSObject alloc] init]
}
此源代碼指定了變量的作用域,當(dāng)obj超出其變量作用域時(shí), obj會(huì)被廢棄,同時(shí)自動(dòng)釋放其被賦予的對(duì)象([[NSObject alloc] init])
而在MRC中,等效的代碼為
//ARC無(wú)效
{
id obj = [[NSObject alloc] init]
[obj release];
}
因?yàn)锳RC無(wú)效的時(shí)候,obj超出變量作用域時(shí),變量并不會(huì)被自動(dòng)廢棄,對(duì)象也會(huì)仍然存在,需要我們手動(dòng)減少對(duì)象的引用計(jì)數(shù)[obj release]
去銷(xiāo)毀對(duì)象
取得非自己生成并持有的對(duì)象
具體如下:
這里需要注意的一點(diǎn)是此處obj確實(shí)持有了對(duì)象,并且對(duì)象的引用計(jì)數(shù)為1,但是在目前版本的Xcode的MRC
環(huán)境中,
{
id obj = [NSMutableArray array];
NSLog(@"%lu", [obj retainCount]);
}
在MRC環(huán)境下輸出的值應(yīng)該為0,因?yàn)閍rray方法表明取得非自己生成并持有的對(duì)象,也就是說(shuō)obj并不持有對(duì)象,但是輸出如下:
我們來(lái)解釋一下輸出為1的原因:
當(dāng)我們調(diào)用 retainCount 方法時(shí),對(duì)于從自動(dòng)釋放池獲取的對(duì)象,它會(huì)臨時(shí)retained一次,以防止對(duì)象被過(guò)早釋放而導(dǎo)致訪問(wèn)過(guò)期數(shù)據(jù)。
1.array
方法創(chuàng)建了一個(gè)對(duì)象,并將其加到自動(dòng)釋放池中,此時(shí)retain count
為0
2.obj指向自動(dòng)釋放池中的那個(gè)對(duì)象,并沒(méi)有對(duì)對(duì)象進(jìn)行retain操作,只是持有了一個(gè)指向他的指針
3.調(diào)用 [obj retainCount]
時(shí) ,a.編譯器會(huì)臨時(shí)保留(retain)對(duì)象 b.獲得并輸出其retain count值 c.釋放對(duì)象
所以盡管對(duì)象最初的 retain count 為 0,但由于 retainCount 方法的實(shí)現(xiàn)機(jī)制,它會(huì)臨時(shí)保留對(duì)象來(lái)避免崩潰,這導(dǎo)致我們看到的輸出 retain count 為 1。
__strong 修飾符的變量之間可以相互賦值
id __strong obj0 = [[NSObject alloc] init];//對(duì)象A
id __strong obj1 = [[NSObject alloc] init];//對(duì)象B
id __strong obj2 = nil;
obj0 = obj1;//obj0持有有obj1賦值的對(duì)象B的強(qiáng)引用,obj0被賦值。所以原先持有的a的強(qiáng)引用失效,此時(shí)b的強(qiáng)引用變量為obj1,obj0
由此__strong 修飾符的變量,不僅只在變量作用域中,在賦值上也能夠正確地管理其對(duì)象的所有者。
類(lèi)的成員變量也可以使用strong修飾
重點(diǎn)是當(dāng)Test對(duì)象釋放時(shí),Test對(duì)象的obj_成員變量也會(huì)隨之被釋放
__weak修飾符
使用weak可以使我們?nèi)〉脤?duì)象但是并不持有對(duì)象
下面有一個(gè)代碼例子進(jìn)行解釋
id a = [[NSObject alloc] init];
id __weak b = a;
id c = a;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)a));
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
a = nil;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
NSLog(@"%@", b);
NSLog(@"%@", c);
c = nil;
NSLog(@"%@", b);
NSLog(@"%@", c);
可以看到在ARC環(huán)境下a,b,c都指向了對(duì)象,但是引用計(jì)數(shù)只有2,這是因?yàn)閣eak是指向?qū)ο蟮闹羔樀⒉怀钟袑?duì)象,并不會(huì)使引用計(jì)數(shù)加1
當(dāng)我們將weak修飾符改為strong時(shí),就會(huì)出現(xiàn)如下結(jié)果
id a = [[NSObject alloc] init];
id __strong b = a;
id c = a;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)a));
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
a = nil;
NSLog(@"%lu", CFGetRetainCount((__bridge CFTypeRef)c));
NSLog(@"%@", b);
NSLog(@"%@", c);
c = nil;
NSLog(@"%@", b);
NSLog(@"%@", c);
循環(huán)引用
內(nèi)存管理中會(huì)發(fā)生循環(huán)引用的問(wèn)題,此時(shí)就需要用到__weak修飾符
正確的內(nèi)存釋放過(guò)程:
首先B對(duì)象是A對(duì)象的一個(gè)屬性,也就是A持有B,現(xiàn)在要釋放掉A,需要給A發(fā)送一個(gè)release消息,這時(shí)A的引用計(jì)數(shù)變?yōu)?,就要走delloc方法,delloc方法會(huì)對(duì)A所持有的全部對(duì)象發(fā)送release消息,當(dāng)然也包括B,也就是對(duì)B進(jìn)行release,此時(shí)B的引用計(jì)數(shù)也變?yōu)?,然后執(zhí)行delloc,最后A與B都被釋放掉了
- (void)dealloc {
[_b release]; // 釋放持有的 B 實(shí)例
_b = nil;
[super dealloc];
}
循環(huán)引用的產(chǎn)生:
解釋?zhuān)簩?duì)象之間互相持有,形成閉環(huán),導(dǎo)致誰(shuí)也無(wú)法被正確釋放
循環(huán)引用容易發(fā)生內(nèi)存泄漏。所謂內(nèi)存泄漏就是應(yīng)當(dāng)廢棄的對(duì)象在超出其生存周期后繼續(xù)存在。
過(guò)程:
- a.例如我們現(xiàn)在想讓A釋放,也就是讓B給A發(fā)送release消息,此時(shí)B的屬性強(qiáng)持有A,所以需要B在delloc方法中對(duì)A進(jìn)行release
- b.我們想要讓B執(zhí)行delloc,就需要就持有B的A對(duì)象發(fā)送release消息給B
- c.想要A發(fā)送release消息給B,就需要A執(zhí)行delloc方法
- d.想要A執(zhí)行delloc方法就需要持有A的B對(duì)象發(fā)送release消息
如此循環(huán)往復(fù),對(duì)象之間都在等對(duì)方給自己發(fā)送release消息,導(dǎo)致誰(shuí)也無(wú)法執(zhí)行,如此往復(fù)便造成了循環(huán)引用
當(dāng)然循環(huán)引用并不只出現(xiàn)在變量中,還出現(xiàn)在協(xié)議與block中,后面在學(xué)習(xí)的過(guò)程中會(huì)專(zhuān)門(mén)寫(xiě)博客記錄
接下來(lái)我們談?wù)撘幌氯绾谓鉀Q這類(lèi)問(wèn)題:
因?yàn)槲覀冎纖eak可以使變量取得但并不持有對(duì)象,也就是說(shuō)不會(huì)增加對(duì)象的引用計(jì)數(shù),我們將對(duì)象中的屬性用weak修飾符修飾就可以解決這個(gè)問(wèn)題
使用weak時(shí)因?yàn)樽兞坎怀钟袑?duì)象,因此不會(huì)造成相互引用,當(dāng)對(duì)象釋放后weak變量會(huì)自動(dòng)置為nil,也避免了野指針的情況
#pragma mark 持有對(duì)象的弱引用 MRC 在MRC下,沒(méi)有__weak這樣的自動(dòng)nil置化特性 使用weak持有某對(duì)象弱引用時(shí),對(duì)象被廢棄,弱引用變量自動(dòng)只為nil
id __weak obj1 = nil;
{
id __strong obj0 = [[NSObject alloc] init];
obj1 = obj0;;
NSLog(@"%@", obj1);
}
NSLog(@"%@", obj1);
__unsafe_unretained修飾符
__unsafe_unretained
是一個(gè)不安全的所有權(quán)修飾符,在MRC下使用來(lái)避免循環(huán)引用
但是與weak相比,其會(huì)產(chǎn)生懸垂指針
因此我們?cè)谑褂?*__unsafe_unretained必須保證對(duì)象存在**
什么時(shí)候使用__unsafe_unretained
- 其與weak相比,可能會(huì)有一些更好的性能,追求極致性能便可以使用__unsafe_unretained修飾
- 以及在早版本的iOS中需要使用
__unsafe_unretained
來(lái)代替__weak
. - 比如我們?cè)谠L問(wèn)單例或者全局變量時(shí)就可以使用這個(gè)修飾
__autoreleasing修飾符
ARC中不能使用autorelease方法以及NSAutoreleasePool類(lèi),但是實(shí)際上ARC有效時(shí)autorelease功能還是有作用的
- 指定“@autoreleasepol 塊”來(lái)替代“NSAutoreleasePool 類(lèi)對(duì)象生成、持有以及廢棄”這一范圍
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
- 為對(duì)象附加__autoreleasing修飾符代替autorelease方法,等價(jià)于在ARC無(wú)效時(shí)調(diào)用
autorelease
,即將對(duì)象注冊(cè)到autoreleasepool
但是我們一般不會(huì)顯式地添加__autoreleasing,因?yàn)榫幾g器會(huì)檢查方法名是否以alloc/new/ copy/mutableCopy 開(kāi)始,如果不是則自動(dòng)將返回值的對(duì)象注冊(cè)到autoreleasepool。
例如:
@autoreleasepool f
id __strong obj = [NSMutableArray array];
}
訪問(wèn)附有__weak 修飾符的變量時(shí),實(shí)際上必定要訪問(wèn)注冊(cè)到autoreleasepool的對(duì)象
因?yàn)?strong>weak修飾符只持有對(duì)象的弱引用,因此訪問(wèn)引用對(duì)象時(shí)對(duì)象可能被遺棄。所以我們將對(duì)象注冊(cè)到autoreleasepool可以確保對(duì)象存在
當(dāng)將對(duì)象注冊(cè)在autoreleasepool中,autoreleasepool會(huì)臨時(shí)保留這個(gè)對(duì)象,直到作用域結(jié)束
另外在書(shū)上講id *obj = id __autoreleasing *obj
,以此類(lèi)推NSObject **obj
便成為了NSObject * _autoreleasing *obj
,這里我們需要知道NSObject *__autoreleasing t1與NSObject __autoreleasing *t1有本質(zhì)的不同:
- 前者指向?qū)ο蟮膶?duì)象會(huì)在被賦值時(shí)加入到自動(dòng)釋放池
- 后者常在NSError錯(cuò)誤處理中見(jiàn)到
在自動(dòng)引用計(jì)數(shù)(ARC)管理的 Objective-C 環(huán)境中,當(dāng)你使用雙重指針(比如 NSError **error)作為方法參數(shù)時(shí),ARC 會(huì)假定這個(gè)指針指向的對(duì)象是 __autoreleasing
總結(jié)來(lái)說(shuō),NSObject *__autoreleasing t2 是一個(gè)自動(dòng)釋放的對(duì)象指針,而 NSObject
__autoreleasing *t1 是指向一個(gè)自動(dòng)釋放對(duì)象指針的指針。
我們以一個(gè)代碼例子來(lái)實(shí)驗(yàn)一下
@interface ViewController : UIViewController
@property (nonatomic, weak)NSObject *Obj1;
@property (nonatomic, weak)NSObject *Obj2;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self test1];
}
- (void)test1 {
NSLog(@"%@ %@", self.Obj1, self.Obj2);
[self test2];
NSLog(@"%@ %@", self.Obj1, self.Obj2);
}
- (void)test2 {
NSObject *t1 = [[NSObject alloc] init];
NSObject *__autoreleasing t2 = [[NSObject alloc] init];
self.Obj1 = t1;
self.Obj2 = t2;
NSLog(@"%@ %@", self.Obj1, self.Obj2);
}
這段代碼中因?yàn)閠est2中,t1超出變量作用域,同時(shí)self.obj1
是被weak修飾的,并不持有對(duì)象,對(duì)象超出作用域自動(dòng)銷(xiāo)毀,因此第三行輸出null。
而t2由于使用了 __autoreleasing,它的生命周期被延長(zhǎng)到當(dāng)前的自動(dòng)釋放池結(jié)束
它的生命周期被延長(zhǎng)到當(dāng)前的自動(dòng)釋放池結(jié)束"這句話(huà)的含義是:
- 使用__autoreleasing修飾的對(duì)象不會(huì)在創(chuàng)建它的作用域(通常是一個(gè)函數(shù)或@autoreleasepool塊)結(jié)束時(shí)被立即釋放。
- 相反,這個(gè)對(duì)象會(huì)被自動(dòng)添加到當(dāng)前的Autorelease Pool中,延長(zhǎng)了它的生命周期。
- 直到當(dāng)前的Autorelease Pool被銷(xiāo)毀時(shí),這個(gè)對(duì)象才會(huì)被最終釋放。
@interface ViewController : UIViewController
@property (nonatomic, strong)NSObject *Obj1;
@property (nonatomic, strong)NSObject *Obj2;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self test1];
}
- (void)test1 {
NSLog(@"%@ %@", self.Obj1, self.Obj2);
[self test2];
NSLog(@"%@ %@", self.Obj1, self.Obj2);
}
- (void)test2 {
NSObject *t1 = [[NSObject alloc] init];
NSObject *__autoreleasing t2 = [[NSObject alloc] init];
self.Obj1 = t1;
self.Obj2 = t2;
NSLog(@"%@ %@", self.Obj1, self.Obj2);
}
而這段代碼中obj用了strong修飾,即使t1,t2作為局部變量超出了作用域,但是self.Obj1仍然持有這個(gè)對(duì)象,因此這個(gè)對(duì)象并不會(huì)被銷(xiāo)毀,因此其仍然存在
四、ARC規(guī)則
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 必須遵守內(nèi)存管理的方法名規(guī)則
- 不要顯式調(diào)用dealloc
- 使用@autorelease塊代替NSAutoreleasePool
- 不能使用區(qū)域(NSZone)
- 對(duì)象型變量不能作為C語(yǔ)言結(jié)構(gòu)體的成員
- 顯式轉(zhuǎn)換id和void*
不要顯式調(diào)用dealloc
dealloc 方法在大多數(shù)情況下還適用于刪除已注冊(cè)的代理或觀察者對(duì)象。文章來(lái)源:http://www.zghlxwxcb.cn/news/detail-840898.html
五、屬性
文章來(lái)源地址http://www.zghlxwxcb.cn/news/detail-840898.html
到了這里,關(guān)于【iOS】ARC學(xué)習(xí)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!