簡(jiǎn)書博客已經(jīng)暫停更新闽巩,想看更多技術(shù)博客請(qǐng)到:
總結(jié)了Effective Objective-C之后爱榔,還想讀一本進(jìn)階的iOS書准夷,毫不猶豫選中了《Objective-C 高級(jí)編程》:
這本書有三個(gè)章節(jié)双抽,我針對(duì)每一章節(jié)進(jìn)行總結(jié)并加上適當(dāng)?shù)臄U(kuò)展分享給大家×找可以從下面這張圖來(lái)看一下這三篇的整體結(jié)構(gòu):
注意寡具,這個(gè)結(jié)構(gòu)并不和書中的結(jié)構(gòu)一致,而是以書中的結(jié)構(gòu)為參考稚补,稍作了調(diào)整童叠。
本篇是第一篇:引用計(jì)數(shù),簡(jiǎn)單說(shuō)兩句:
Objective-C通過 retainCount 的機(jī)制來(lái)決定對(duì)象是否需要釋放课幕。 每次runloop迭代結(jié)束后厦坛,都會(huì)檢查對(duì)象的 retainCount,如果retainCount等于0乍惊,就說(shuō)明該對(duì)象沒有地方需要繼續(xù)使用它杜秸,可以被釋放掉了。無(wú)論是手動(dòng)管理內(nèi)存润绎,還是ARC機(jī)制撬碟,都是通過對(duì)retainCount來(lái)進(jìn)行內(nèi)存管理的。
先看一下手動(dòng)內(nèi)存管理:
手動(dòng)內(nèi)存管理
我個(gè)人覺得莉撇,學(xué)習(xí)一項(xiàng)新的技術(shù)之前呢蛤,需要先了解一下它的核心思想。理解了核心思想之后稼钩,對(duì)技術(shù)點(diǎn)的把握就會(huì)更快一些:
內(nèi)存管理的思想
- 思想一:自己生成的對(duì)象顾稀,自己持有。
- 思想二:非自己生成的對(duì)象坝撑,自己也能持有静秆。
- 思想三:不再需要自己持有的對(duì)象時(shí)釋放對(duì)象。
- 思想四:非自己持有的對(duì)象無(wú)法釋放巡李。
從上面的思想來(lái)看抚笔,我們對(duì)對(duì)象的操作可以分為三種:生成,持有侨拦,釋放殊橙,再加上廢棄,一共有四種。它們所對(duì)應(yīng)的Objective-C的方法和引用計(jì)數(shù)的變化是:
對(duì)象操作 | Objecctive-C方法 | 引用計(jì)數(shù)的變化 |
---|---|---|
生成并持有對(duì)象 | alloc/new/copy/mutableCopy等方法 | +1 |
持有對(duì)象 | retain方法 | +1 |
釋放對(duì)象 | release方法 | -1 |
廢棄對(duì)象 | dealloc方法 | 無(wú) |
用書中的圖來(lái)直觀感受一下這四種操作:
下面開始逐一解釋上面的四條思想:
思想一:自己生成的對(duì)象膨蛮,自己持有
在生成對(duì)象時(shí)叠纹,使用以下面名稱開頭的方法生成對(duì)象以后,就會(huì)持有該對(duì)象:
- alloc
- new
- copy
- mutableCopy
舉個(gè)??:
id obj = [[NSObject alloc] init];//持有新生成的對(duì)象
這行代碼過后敞葛,指向生成并持有[[NSObject alloc] init]的指針被賦給了obj誉察,也就是說(shuō)obj這個(gè)指針強(qiáng)引用[[NSObject alloc] init]這個(gè)對(duì)象。
同樣適用于new方法:
id obj = [NSObject new];//持有新生成的對(duì)象
注意:
這種將持有對(duì)象的指針賦給指針變量的情況不只局限于上面這四種方法名稱惹谐,還包括以他們開頭的所有方法名稱:
- allocThisObject
- newThatObject
- copyThisObject
- mutableCopyThatObject
舉個(gè)??:
id obj1 = [obj0 allocObject];//符合上述命名規(guī)則持偏,生成并持有對(duì)象
它的內(nèi)部實(shí)現(xiàn):
- (id)allocObject
{
id obj = [[NSObject alloc] init];//持有新生成的對(duì)象
return obj;
}
反過來(lái),如果不符合上述的命名規(guī)則氨肌,那么就不會(huì)持有生成的對(duì)象鸿秆,
看一個(gè)不符合上述命名規(guī)則的返回對(duì)象的createObject方法的內(nèi)部實(shí)現(xiàn)??:
- (id)createObject
{
id obj = [[NSObject alloc] init];//持有新生成的對(duì)象
[obj autorelease];//取得對(duì)象,但自己不持有
return obj;
}
經(jīng)由這個(gè)方法返回以后怎囚,無(wú)法持有這個(gè)返回的對(duì)象卿叽。因?yàn)檫@里使用了autorelease绞幌。autorelease提供了這樣一個(gè)功能:在對(duì)象超出其指定的生存范圍時(shí)能夠自動(dòng)并正確地釋放(詳細(xì)會(huì)在后面介紹)。
也就是說(shuō)湃缎,生成一個(gè)調(diào)用方不持有的對(duì)象是可以通過autorelease來(lái)實(shí)現(xiàn)的(例如NSMutableArray的array類方法)暴氏。
我的個(gè)人理解是:通過autorelease方法,使對(duì)象的持有權(quán)轉(zhuǎn)移給了自動(dòng)釋放池赴蝇。所以實(shí)現(xiàn)了:調(diào)用方拿到了對(duì)象,但這個(gè)對(duì)象還不被調(diào)用方所持有。
由這個(gè)不符合命名規(guī)則的例子來(lái)引出思想二:
思想二:非自己生成的對(duì)象颗圣,自己也能持有
我們現(xiàn)在知道,僅僅通過上面那個(gè)不符合命名規(guī)則的返回對(duì)象實(shí)例的方法是無(wú)法持有對(duì)象的屁使。但是我們可以通過某個(gè)操作來(lái)持有這個(gè)返回的對(duì)象:這個(gè)方法就是通過retain方法來(lái)讓指針變量持有這個(gè)新生成的對(duì)象:
id obj = [NSMutableArray array];//非自己生成并持有的對(duì)象
[obj retain];//持有新生成的對(duì)象
注意在岂,這里[NSMutableArray array]返回的非自己持有的對(duì)象正是通過上文介紹過的autorelease方法實(shí)現(xiàn)的。所以如果想持有這個(gè)對(duì)象蛮寂,需要執(zhí)行retain方法才可以蔽午。
思想三:不再需要自己持有的對(duì)象時(shí)釋放對(duì)象
對(duì)象的持有者有義務(wù)在不再需要這個(gè)對(duì)象的時(shí)候主動(dòng)將這個(gè)對(duì)象釋放。注意酬蹋,是有義務(wù)及老,而不是有權(quán)利,注意兩個(gè)詞的不同范抓。
來(lái)看一下釋放對(duì)象的例子:
id obj = [[NSObject alloc] init];//持有新生成的對(duì)象
[obj doSomething];//使用該對(duì)象做一些事情
[obj release];//事情做完了骄恶,釋放該對(duì)象
同樣適用于非自己生成并持有的對(duì)象(參考思想二):
id obj = [NSMutableArray array];//非自己生成并持有的對(duì)象
[obj retain];//持有新生成的對(duì)象
[obj soSomething];//使用該對(duì)象做一些事情
[obj release];//事情做完了,釋放該對(duì)象
可能遇到的面試題:調(diào)用對(duì)象的release方法會(huì)銷毀對(duì)象嗎匕垫?
答案是不會(huì):調(diào)用對(duì)象的release方法只是將對(duì)象的引用計(jì)數(shù)器-1僧鲁,當(dāng)對(duì)象的引用計(jì)數(shù)器為0的時(shí)候會(huì)調(diào)用了對(duì)象的dealloc 方法才能進(jìn)行釋放對(duì)象的內(nèi)存。
思想四:無(wú)法釋放非自己持有的對(duì)象
在釋放對(duì)象的時(shí)候,我們只能釋放已經(jīng)持有的對(duì)象寞秃,非自己持有的對(duì)象是不能被自己釋放的斟叼。這很符合常識(shí):就好比你自己才能從你自己的銀行卡里取錢,取別人的卡里的錢是不對(duì)的(除非他的錢歸你管春寿。犁柜。。只是隨便舉個(gè)例子)堂淡。
兩種不允許的情況:
1. 釋放一個(gè)已經(jīng)廢棄了的對(duì)象
id obj = [[NSObject alloc] init];//持有新生成的對(duì)象
[obj doSomething];//使用該對(duì)象
[obj release];//釋放該對(duì)象馋缅,不再持有了
[obj release];//釋放已經(jīng)廢棄了的對(duì)象,崩潰
2. 釋放自己不持有的對(duì)象
id obj = [NSMutableArray array];//非自己生成并持有的對(duì)象
[obj release];//釋放了非自己持有的對(duì)象
思考:哪些情況會(huì)使對(duì)象失去擁有者呢绢淀?
- 將指向某對(duì)象的指針變量指向另一個(gè)對(duì)象萤悴。
- 將指向某對(duì)象的指針變量設(shè)置為nil。
- 當(dāng)程序釋放對(duì)象的某個(gè)擁有者時(shí)皆的。
- 從collection類中刪除對(duì)象時(shí)覆履。
現(xiàn)在知道了引用計(jì)數(shù)式內(nèi)存管理的四個(gè)思想,我們?cè)賮?lái)看一下四個(gè)操作引用計(jì)數(shù)的方法:
alloc/retain/release/dealloc的實(shí)現(xiàn)
某種意義上费薄,GNUstep 和 Foundation 框架的實(shí)現(xiàn)是相似的硝全。所以這本書的作者通過GNUstep的源碼來(lái)推測(cè)了蘋果Cocoa框架的實(shí)現(xiàn)。
下面開始針對(duì)每一個(gè)方法楞抡,同時(shí)用GNUstep和蘋果的實(shí)現(xiàn)方式(追蹤程序的執(zhí)行和作者的猜測(cè))來(lái)對(duì)比一下各自的實(shí)現(xiàn)伟众。
GNUstep實(shí)現(xiàn):
alloc方法
//GNUstep/modules/core/base/Source/NSObject.m alloc:
+ (id) alloc
{
return [self allocWithZone: NSDefaultMallocZone()];
}
+ (id) allocWithZone: (NSZone*)z
{
return NSAllocateObject(self, 0, z);
}
這里NSAllocateObject方法分配了對(duì)象,看一下它的內(nèi)部實(shí)現(xiàn):
//GNUstep/modules/core/base/Source/NSObject.m NSAllocateObject:
struct obj_layout {
NSUInteger retained;
};
NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
{
int size = 計(jì)算容納對(duì)象所需內(nèi)存大小;
id new = NSZoneMalloc(zone, 1, size);//返回新的實(shí)例
memset (new, 0, size);
new = (id)&((obj)new)[1];
}
- NSAllocateObject函數(shù)通過NSZoneMalloc函數(shù)來(lái)分配存放對(duì)象所需要的內(nèi)存空間召廷。
- obj_layout是用來(lái)保存引用計(jì)數(shù)凳厢,并將其寫入對(duì)象內(nèi)存頭部。
對(duì)象的引用計(jì)數(shù)可以通過retainCount方法來(lái)取得:
GNUstep/modules/core/base/Source/NSObject.m retainCount:
- (NSUInteger) retainCount
{
return NSExtraRefCount(self) + 1;
}
inline NSUInteger
NSExtraRefCount(id anObject)
{
return ((obj_layout)anObject)[-1].retained;
}
我們可以看到竞慢,給NSExtraRefCount傳入anObject以后先紫,通過訪問對(duì)象內(nèi)存頭部的.retained變量,來(lái)獲取引用計(jì)數(shù)筹煮。
retain方法
//GNUstep/modules/core/base/Source/NSObject.m retain:
- (id)retain
{
NSIncrementExtraRefCount(self);
return self;
}
inline void NSIncrementExtraRefCount(id anObject)
{
//retained變量超出最大值,拋出異常
if (((obj)anObject)[-1].retained == UINT_MAX - 1){
[NSException raise: NSInternalInconsistencyException
format: @"NSIncrementExtraRefCount() asked to increment too far”];
}
((obj_layout)anObject)[-1].retained++;//retained變量+1
}
release方法
//GNUstep/modules/core/base/Source/NSObject.m release
- (void)release
{
//如果當(dāng)前的引用計(jì)數(shù) = 0遮精,調(diào)用dealloc函數(shù)
if (NSDecrementExtraRefCountWasZero(self))
{
[self dealloc];
}
}
BOOL NSDecrementExtraRefCountWasZero(id anObject)
{
//如果當(dāng)前的retained值 = 0.則返回yes
if (((obj)anObject)[-1].retained == 0){
return YES;
}
//如果大于0,則-1败潦,并返回NO
((obj)anObject)[-1].retained--;
return NO;
}
dealloc方法
//GNUstep/modules/core/base/Source/NSObject.m dealloc
- (void) dealloc
{
NSDeallocateObject (self);
}
inline void NSDeallocateObject(id anObject)
{
obj_layout o = &((obj_layout)anObject)[-1];
free(o);//釋放
}
總結(jié)一下上面的幾個(gè)方法:
- Objective-C對(duì)象中保存著引用計(jì)數(shù)這一整數(shù)值本冲。
- 調(diào)用alloc或者retain方法后,引用計(jì)數(shù)+1变屁。
- 調(diào)用release后眼俊,引用計(jì)數(shù)-1。
- 引用計(jì)數(shù)為0時(shí)粟关,調(diào)用dealloc方法廢棄對(duì)象疮胖。
下面看一下蘋果的實(shí)現(xiàn):
蘋果的實(shí)現(xiàn)
alloc方法
通過在NSObject類的alloc類方法上設(shè)置斷點(diǎn)环戈,我們可以看到執(zhí)行所調(diào)用的函數(shù):
- +alloc
- +allocWithZone:
- class_createInstance//生成實(shí)例
- calloc//分配內(nèi)存塊
retainCount:
- __CFdoExternRefOperation
- CFBasicHashGetCountOfKey
retain方法
- __CFdoExternRefOperation
- CFBasicHashAddValue
release方法
- __CFdoExternRefOperation
- CFBasicHashRemoveValue
我們可以看到他們都調(diào)用了一個(gè)共同的 __CFdoExternRefOperation 方法。
看一下它的實(shí)現(xiàn):
int __CFDoExternRefOperation(uintptr_t op, id obj) {
CFBasicHashRef table = 取得對(duì)象的散列表(obj);
int count;
switch (op) {
case OPERATION_retainCount:
count = CFBasicHashGetCountOfKey(table, obj);
return count;
break;
case OPERATION_retain:
count = CFBasicHashAddValue(table, obj);
return obj;
case OPERATION_release:
count = CFBasicHashRemoveValue(table, obj);
return 0 == count;
}
}
可以看出澎灸,__CFDoExternRefOperation通過switch語(yǔ)句 針對(duì)不同的操作來(lái)進(jìn)行具體的方法調(diào)用院塞,如果 op 是 OPERATION_retain,就去掉用具體實(shí)現(xiàn) retain 的方法性昭,以此類推拦止。
可以猜想上層的retainCount,retain,release方法的實(shí)現(xiàn):
- (NSUInteger)retainCount
{
return (NSUInteger)____CFDoExternRefOperation(OPERATION_retainCount,self);
}
- (id)retain
{
return (id)____CFDoExternRefOperation(OPERATION_retain,self);
}
//這里返回值應(yīng)該是id,原書這里應(yīng)該是錯(cuò)了
- (id)release
{
return (id)____CFDoExternRefOperation(OPERATION_release,self);
}
我們觀察一下switch里面每個(gè)語(yǔ)句里的執(zhí)行函數(shù)名稱糜颠,似乎和散列表(Hash)有關(guān)汹族,這說(shuō)明蘋果對(duì)引用計(jì)數(shù)的管理應(yīng)該是通過散列表來(lái)執(zhí)行的。
在這張表里其兴,key為內(nèi)存塊地址顶瞒,而對(duì)應(yīng)的值為引用計(jì)數(shù)。也就是說(shuō)元旬,它保存了這樣的信息:一些被引用的內(nèi)存塊各自對(duì)應(yīng)的引用計(jì)數(shù)榴徐。
那么使用散列表來(lái)管理內(nèi)存有什么好處呢?
因?yàn)橛?jì)數(shù)表保存內(nèi)存塊地址匀归,我們就可以通過這張表來(lái):
- 確認(rèn)損壞內(nèi)存塊的位置坑资。
- 在檢測(cè)內(nèi)存泄漏時(shí),可以查看各對(duì)象的持有者是否存在穆端。
autorelease
autorelease 介紹
當(dāng)對(duì)象超出其作用域時(shí)袱贮,對(duì)象實(shí)例的release方法就會(huì)被調(diào)用,autorelease的具體使用方法如下:
- 生成并持有NSAutoreleasePool對(duì)象徙赢。
- 調(diào)用已分配對(duì)象的autorelease方法字柠。
- 廢棄NSAutoreleasePool對(duì)象。
所有調(diào)用過autorelease方法的對(duì)象狡赐,在廢棄NSAutoreleasePool對(duì)象時(shí),都將調(diào)用release方法(引用計(jì)數(shù)-1):
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];//相當(dāng)于obj調(diào)用release方法
NSRunLoop在每次循環(huán)過程中钦幔,NSAutoreleasePool對(duì)象都會(huì)被生成或廢棄枕屉。
也就是說(shuō),如果有大量的autorelease變量鲤氢,在NSAutoreleasePool對(duì)象廢棄之前(一旦監(jiān)聽到RunLoop即將進(jìn)入睡眠等待狀態(tài)搀擂,就釋放NSAutoreleasePool),都不會(huì)被銷毀卷玉,容易導(dǎo)致內(nèi)存激增的問題:
for (int i = 0; i < imageArray.count; i++)
{
UIImage *image = imageArray[i];
[image doSomething];
}
因此哨颂,我們有必要在適當(dāng)?shù)臅r(shí)候再嵌套一個(gè)自動(dòng)釋放池來(lái)管理臨時(shí)生成的autorelease變量:
for (int i = 0; i < imageArray.count; i++)
{
//臨時(shí)pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = imageArray[i];
[image doSomething];
[pool drain];
}
可能會(huì)出的面試題:什么時(shí)候會(huì)創(chuàng)建自動(dòng)釋放池?
答:運(yùn)行循環(huán)檢測(cè)到事件并啟動(dòng)后相种,就會(huì)創(chuàng)建自動(dòng)釋放池威恼,而且子線程的 runloop 默認(rèn)是不工作的,無(wú)法主動(dòng)創(chuàng)建,必須手動(dòng)創(chuàng)建箫措。
舉個(gè)??:
自定義的 NSOperation 類中的 main 方法里就必須添加自動(dòng)釋放池腹备。否則在出了作用域以后,自動(dòng)釋放對(duì)象會(huì)因?yàn)闆]有自動(dòng)釋放池去處理自己而造成內(nèi)存泄露斤蔓。
autorelease實(shí)現(xiàn)
和上文一樣植酥,我們還是通過GNUstep和蘋果的實(shí)現(xiàn)來(lái)分別看一下。
GNUstep 實(shí)現(xiàn)
//GNUstep/modules/core/base/Source/NSObject.m autorelease
- (id)autorelease
{
[NSAutoreleasePool addObject:self];
}
如果調(diào)用NSObject類的autorelease方法弦牡,則該對(duì)象就會(huì)被追加到正在使用的NSAutoreleasePool對(duì)象中的數(shù)組里(作者假想了一個(gè)簡(jiǎn)化的源代碼):
//GNUstep/modules/core/base/Source/NSAutoreleasePool.m addObject
+ (void)addObject:(id)anObj
{
NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool對(duì)象
if (pool != nil){
[pool addObject:anObj];
}else{
NSLog(@"NSAutoreleasePool對(duì)象不存在");
}
}
- (void)addObject:(id)anObj
{
[pool.array addObject:anObj];
}
也就是說(shuō)友驮,autorelease實(shí)例方法的本質(zhì)就是調(diào)用NSAutoreleasePool對(duì)象的addObject類方法,然后這個(gè)對(duì)象就被追加到正在使用的NSAutoreleasePool對(duì)象中的數(shù)組里驾锰。
再來(lái)看一下NSAutoreleasePool的drain方法:
- (void)drain
{
[self dealloc];
}
- (void)dealloc
{
[self emptyPool];
[array release];
}
- (void)emptyPool
{
for(id obj in array){
[obj release];
}
}
我們可以看到卸留,在emptyPool方法里,確實(shí)是對(duì)數(shù)組里每一個(gè)對(duì)象進(jìn)行了release操作稻据。
蘋果的實(shí)現(xiàn)
我們可以通過objc4/NSObject.mm來(lái)確認(rèn)蘋果中autorelease的實(shí)現(xiàn):
objc4/NSObject.mm AutoreleasePoolPage
class AutoreleasePoolPage
{
static inline void *push()
{
//生成或者持有 NSAutoreleasePool 類對(duì)象
}
static inline void pop(void *token)
{
//廢棄 NSAutoreleasePool 類對(duì)象
releaseAll();
}
static inline id autorelease(id obj)
{
//相當(dāng)于 NSAutoreleasePool 類的 addObject 類方法
AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 實(shí)例;
autoreleaesPoolPage->add(obj)
}
id *add(id obj)
{
//將對(duì)象追加到內(nèi)部數(shù)組中
}
void releaseAll()
{
//調(diào)用內(nèi)部數(shù)組中對(duì)象的 release 方法
}
};
//壓棧
void *objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
//出棧
void objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
AutoreleasePoolPage::pop(ctxt);
}
來(lái)看一下外部的調(diào)用:
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 等同于 objc_autoreleasePoolPush
id obj = [[NSObject alloc] init];
[obj autorelease];
// 等同于 objc_autorelease(obj)
[NSAutoreleasePool showPools];
// 查看 NSAutoreleasePool 狀況
[pool drain];
// 等同于 objc_autoreleasePoolPop(pool)
看函數(shù)名就可以知道艾猜,對(duì)autorelease分別執(zhí)行push、pop操作捻悯。銷毀對(duì)象時(shí)執(zhí)行release操作匆赃。
可能出現(xiàn)的面試題:蘋果是如何實(shí)現(xiàn)autoreleasepool的今缚?
autoreleasepool以棧的數(shù)據(jù)結(jié)構(gòu)實(shí)現(xiàn),主要通過下列三個(gè)函數(shù)完成.
? objc_autoreleasepoolPush(壓入)
? objc_autoreleasepoolPop(彈出)
? objc_autorelease(釋放內(nèi)部)
ARC內(nèi)存管理
內(nèi)存管理的思想
上面學(xué)習(xí)了非ARC機(jī)制下的手動(dòng)管理內(nèi)存思想算柳,針對(duì)引用計(jì)數(shù)的操作和自動(dòng)釋放池的相關(guān)內(nèi)容⌒昭裕現(xiàn)在學(xué)習(xí)一下在ARC機(jī)制下的相關(guān)知識(shí)瞬项。
ARC和非ARC機(jī)制下的內(nèi)存管理思想是一致的:
- 自己生成的對(duì)象,自己持有何荚。
- 非自己生成的對(duì)象囱淋,自己也能持有。
- 不再需要自己持有的對(duì)象時(shí)釋放對(duì)象餐塘。
- 非自己持有的對(duì)象無(wú)法釋放妥衣。
在ARC機(jī)制下,編譯器就可以自動(dòng)進(jìn)行內(nèi)存管理戒傻,減少了開發(fā)的工作量税手。但我們有時(shí)仍需要四種所有權(quán)修飾符來(lái)配合ARC來(lái)進(jìn)行內(nèi)存管理
四種所有權(quán)修飾符
但是,在ARC機(jī)制下我們有的時(shí)候需要追加所有權(quán)聲明(以下內(nèi)容摘自官方文檔):
- __strong:is the default. An object remains “alive” as long as there is a strong pointer to it.
- __weak:specifies a reference that does not keep the referenced object alive. A weak reference is set to nil when there are no strong references to the object.
- __unsafe_unretained:specifies a reference that does not keep the referenced object alive and is not set to nil when there are no strong references to the object. If the object it references is deallocated, the pointer is left dangling.
- __autoreleasing:is used to denote arguments that are passed by reference (id *) and are autoreleased on return.
下面分別講解一下這幾個(gè)修飾符:
__strong修飾符
__strong修飾符 是id類型和對(duì)象類型默認(rèn)的所有權(quán)修飾符:
__strong使用方法:
id obj = [NSObject alloc] init];
等同于:
id __strong obj = [NSObject alloc] init];
看一下內(nèi)存管理的過程:
{
id __strong obj = [NSObject alloc] init];//obj持有對(duì)象
}
//obj超出其作用域需纳,強(qiáng)引用失效
__strong修飾符表示對(duì)對(duì)象的強(qiáng)引用芦倒。持有強(qiáng)引用的變量在超出其作用域時(shí)被廢棄。
在__strong修飾符修飾的變量之間相互賦值的情況:
id __strong obj0 = [[NSObject alloc] init];//obj0 持有對(duì)象A
id __strong obj1 = [[NSObject alloc] init];//obj1 持有對(duì)象B
id __strong obj2 = nil;//ojb2不持有任何對(duì)象
obj0 = obj1;//obj0強(qiáng)引用對(duì)象B不翩;而對(duì)象A不再被ojb0引用兵扬,被廢棄
obj2 = obj0;//obj2強(qiáng)引用對(duì)象B(現(xiàn)在obj0麻裳,ojb1,obj2都強(qiáng)引用對(duì)象B)
obj1 = nil;//obj1不再?gòu)?qiáng)引用對(duì)象B
obj0 = nil;//obj0不再?gòu)?qiáng)引用對(duì)象B
obj2 = nil;//obj2不再?gòu)?qiáng)引用對(duì)象B周霉,不再有任何強(qiáng)引用引用對(duì)象B掂器,對(duì)象B被廢棄
而且,__strong可以使一個(gè)變量初始化為nil:id __strong obj0;
同樣適用于:id __weak obj1; id __autoreleasing obj2;
做個(gè)總結(jié):被__strong修飾后俱箱,相當(dāng)于強(qiáng)引用某個(gè)對(duì)象国瓮。對(duì)象一旦有一個(gè)強(qiáng)引用引用自己,引用計(jì)數(shù)就會(huì)+1狞谱,就不會(huì)被系統(tǒng)廢棄乃摹。而這個(gè)對(duì)象如果不再被強(qiáng)引用的話,就會(huì)被系統(tǒng)廢棄跟衅。
__strong內(nèi)部實(shí)現(xiàn):
生成并持有對(duì)象:
{
id __strong obj = [NSObject alloc] init];//obj持有對(duì)象
}
編譯器的模擬代碼:
id obj = objc_mesgSend(NSObject, @selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);//超出作用域孵睬,釋放對(duì)象
再看一下使用命名規(guī)則以外的構(gòu)造方法:
{
id __strong obj = [NSMutableArray array];
}
編譯器的模擬代碼:
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
objc_retainAutoreleasedReturnValue的作用:持有對(duì)象,將對(duì)象注冊(cè)到autoreleasepool并返回伶跷。
同樣也有objc_autoreleaseReturnValue掰读,來(lái)看一下它的使用:
+ (id)array
{
return [[NSMutableArray alloc] init];
}
編譯器的模擬代碼:
+ (id)array
{
id obj = objc_msgSend(NSMutableArray, @selector(alloc));
objc_msgSend(obj,, @selector(init));
return objc_autoreleaseReturnValue(obj);
}
objc_autoreleaseReturnValue:返回注冊(cè)到autoreleasepool的對(duì)象。
__weak修飾符
__weak使用方法:
__weak修飾符大多解決的是循環(huán)引用的問題:如果兩個(gè)對(duì)象都互相強(qiáng)引用對(duì)方叭莫,同時(shí)都失去了外部對(duì)自己的引用蹈集,那么就會(huì)形成“孤島”,這個(gè)孤島將永遠(yuǎn)無(wú)法被釋放雇初,舉個(gè)??:
@interface Test:NSObject
{
id __strong obj_;
}
- (void)setObject:(id __strong)obj;
@end
@implementation Test
- (id)init
{
self = [super init];
return self;
}
- (void)setObject:(id __strong)obj
{
obj_ = obj;
}
@end
{
id test0 = [[Test alloc] init];//test0強(qiáng)引用對(duì)象A
id test1 = [[Test alloc] init];//test1強(qiáng)引用對(duì)象B
[test0 setObject:test1];//test0強(qiáng)引用對(duì)象B
[test1 setObject:test0];//test1強(qiáng)引用對(duì)象A
}
因?yàn)樯蓪?duì)象(第一拢肆,第二行)和set方法(第三,第四行)都是強(qiáng)引用靖诗,所以會(huì)造成兩個(gè)對(duì)象互相強(qiáng)引用對(duì)方的情況:
所以郭怪,我們需要打破其中一種強(qiáng)引用:
@interface Test:NSObject
{
id __weak obj_;//由__strong變成了__weak
}
- (void)setObject:(id __strong)obj;
@end
這樣一來(lái),二者就只是弱引用對(duì)方了:
__weak內(nèi)部實(shí)現(xiàn)
{
id __weak obj1 = obj;
}
編譯器的模擬代碼:
id obj1;
objc_initWeak(&obj1,obj);//初始化附有__weak的變量
id tmp = objc_loadWeakRetained(&obj1);//取出附有__weak修飾符變量所引用的對(duì)象并retain
objc_autorelease(tmp);//將對(duì)象注冊(cè)到autoreleasepool中
objc_destroyWeak(&obj1);//釋放附有__weak的變量
這確認(rèn)了__weak的一個(gè)功能:使用附有__weak修飾符的變量刊橘,即是使用注冊(cè)到autoreleasepool中的對(duì)象鄙才。
這里需要著重講解一下objc_initWeak方法和objc_destroyWeak方法:
- objc_initWeak:初始化附有__weak的變量,具體通過執(zhí)行objc_strongWeak(&obj1, obj)方法促绵,將obj對(duì)象以&obj1作為key放入一個(gè)weak表(Hash)中咒循。
- objc_destroyWeak:釋放附有__weak的變量。具體通過執(zhí)行objc_storeWeak(&obj1,0)方法绞愚,在weak表中查詢&obj1這個(gè)鍵,將這個(gè)鍵從weak表中刪除颖医。
注意:因?yàn)橥粋€(gè)對(duì)象可以賦值給多個(gè)附有__weak的變量中位衩,所以對(duì)于同一個(gè)鍵值,可以注冊(cè)多個(gè)變量的地址熔萧。
當(dāng)一個(gè)對(duì)象不再被任何人持有糖驴,則需要釋放它僚祷,過程為:
- objc_dealloc
- dealloc
- _objc_rootDealloc
- objc_dispose
- objc_destructInstance
- objc_clear_deallocating
- 從weak表中獲取廢棄對(duì)象的地址
- 將包含在記錄中的所有附有__weak修飾符變量的地址賦值為nil
- 從weak表中刪除該記錄
- 從引用計(jì)數(shù)表中刪除廢棄對(duì)象的地址
__autoreleasing修飾符
__autoreleasing使用方法
ARC下,可以用@autoreleasepool來(lái)替代NSAutoreleasePool類對(duì)象贮缕,用__autoreleasing修飾符修飾變量來(lái)替代ARC無(wú)效時(shí)調(diào)用對(duì)象的autorelease方法(對(duì)象被注冊(cè)到autoreleasepool)辙谜。
說(shuō)到__autoreleasing修飾符,就不得不提__weak:
id __weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
等同于:
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@",[tmp class]);//實(shí)際訪問的是注冊(cè)到自動(dòng)個(gè)釋放池的對(duì)象
注意一下兩段等效的代碼里感昼,NSLog語(yǔ)句里面訪問的對(duì)象是不一樣的装哆,它說(shuō)明:在訪問__weak修飾符的變量(obj1)時(shí)必須訪問注冊(cè)到autoreleasepool的對(duì)象(tmp)。為什么呢定嗓?
因?yàn)開_weak修飾符只持有對(duì)象的弱引用蜕琴,也就是說(shuō)在將來(lái)訪問這個(gè)對(duì)象的時(shí)候,無(wú)法保證它是否還沒有被廢棄宵溅。因此凌简,如果把這個(gè)對(duì)象注冊(cè)到autoreleasepool中,那么在@autoreleasepool塊結(jié)束之前都能確保該對(duì)象存在恃逻。
__autoreleasing內(nèi)部實(shí)現(xiàn)
將對(duì)象賦值給附有__autoreleasing修飾符的變量等同于ARC無(wú)效時(shí)調(diào)用對(duì)象的autorelease方法雏搂。
@autoreleasepool{
id __autoreleasing obj = [[NSObject alloc] init];
}
編譯器的模擬代碼:
id pool = objc_autoreleasePoolPush();//pool入棧
id obj = objc_msgSend(NSObject, @selector(alloc));
objc_msgSend(obj, @selector(init));
objc_autorelease(obj);
objc_autoreleasePoolPop(pool);//pool出棧
在這里我們可以看到pool入棧,執(zhí)行autorelease寇损,出棧的三個(gè)方法凸郑。
ARC下的規(guī)則
我們知道了在ARC機(jī)制下編譯器會(huì)幫助我們管理內(nèi)存,但是在編譯期润绵,我們還是要遵守一些規(guī)則线椰,作者為我們列出了以下的規(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*
1. 不能使用retain/release/retainCount/autorelease
在ARC機(jī)制下使用retain/release/retainCount/autorelease方法,會(huì)導(dǎo)致編譯器報(bào)錯(cuò)尘盼。
2. 不能使用NSAllocateObject/NSDeallocateObject
在ARC機(jī)制下使用NSAllocateObject/NSDeallocateObject方法憨愉,會(huì)導(dǎo)致編譯器報(bào)錯(cuò)。
3. 必須遵守內(nèi)存管理的方法名規(guī)則
對(duì)象的生成/持有的方法必須遵循以下命名規(guī)則:
- alloc
- new
- copy
- mutableCopy
- init
前四種方法已經(jīng)介紹完卿捎。而關(guān)于init方法的要求則更為嚴(yán)格:
- 必須是實(shí)例方法
- 必須返回對(duì)象
- 返回對(duì)象的類型必須是id類型或方法聲明類的對(duì)象類型
4. 不要顯式調(diào)用dealloc
對(duì)象被廢棄時(shí)配紫,無(wú)論ARC是否有效,系統(tǒng)都會(huì)調(diào)用對(duì)象的dealloc方法午阵。
我們只能在dealloc方法里寫一些對(duì)象被廢棄時(shí)需要進(jìn)行的操作(例如移除已經(jīng)注冊(cè)的觀察者對(duì)象)但是不能手動(dòng)調(diào)用dealloc方法躺孝。
注意在ARC無(wú)效的時(shí)候,還需要調(diào)用[super dealloc]:
- (void)dealloc
{
//該對(duì)象的處理
[super dealloc];
}
5. 使用@autorelease塊代替NSAutoreleasePool
ARC下須使用使用@autorelease塊代替NSAutoreleasePool底桂。
6. 不能使用區(qū)域(NSZone)
NSZone已經(jīng)在目前的運(yùn)行時(shí)系統(tǒng)(OBC2被設(shè)定的環(huán)境)被忽略了植袍。
7. 對(duì)象型變量不能作為C語(yǔ)言結(jié)構(gòu)體的成員
C語(yǔ)言的結(jié)構(gòu)體如果存在Objective-C對(duì)象型變量,便會(huì)引起錯(cuò)誤籽懦,因?yàn)镃語(yǔ)言在規(guī)約上沒有方法來(lái)管理結(jié)構(gòu)體成員的生存周期 于个。
8. 顯式轉(zhuǎn)換id和void*
非ARC下,這兩個(gè)類型是可以直接賦值的
id obj = [NSObject alloc] init];
void *p = obj;
id o = p;
但是在ARC下就會(huì)引起編譯錯(cuò)誤暮顺。為了避免錯(cuò)誤厅篓,我們需要通過__bridege來(lái)轉(zhuǎn)換秀存。
id obj = [[NSObject alloc] init];
void *p = (__bridge void*)obj;//顯式轉(zhuǎn)換
id o = (__bridge id)p;//顯式轉(zhuǎn)換
屬性
來(lái)看一下屬性的聲明與所有權(quán)修飾符的關(guān)系
屬性關(guān)鍵字 | 所有權(quán) 修飾符 |
---|---|
assign | __unsafe_unretained |
copy | __strong |
retain | __strong |
strong | __strong |
__unsafe_unretained | __unsafe_unretained |
weak | __weak |
說(shuō)一下__unsafe_unretained:
__unsafe_unretained表示存取方法會(huì)直接為實(shí)例變量賦值。
這里的“unsafe”是相對(duì)于weak而言的羽氮。我們知道weak指向的對(duì)象被銷毀時(shí)或链,指針會(huì)自動(dòng)設(shè)置為nil。而__unsafe_unretained卻不會(huì)档押,而是成為空指針澳盐。需要注意的是:當(dāng)處理非對(duì)象屬性的時(shí)候就不會(huì)出現(xiàn)空指針的問題。
這樣第一章就介紹完了汇荐,第二篇會(huì)在下周一發(fā)布^^
擴(kuò)展文獻(xiàn):
- Apple:Transitioning to ARC Release Notes
- 蚊香醬:可能是史上最全面的內(nèi)存管理文章
- 微笑和飛飛:可能碰到的iOS筆試面試題(6)--內(nèi)存管理
- 《iOS編程(第4版)》
本文已經(jīng)同步到個(gè)人博客:傳送門
本文已在版權(quán)印備案洞就,如需轉(zhuǎn)載請(qǐng)?jiān)L問版權(quán)印。48422928
-------------------------------- 2018年7月17日更新 --------------------------------
注意注意O铺浴Q!
筆者在近期開通了個(gè)人公眾號(hào)革娄,主要分享編程倾贰,讀書筆記,思考類的文章拦惋。
- 編程類文章:包括筆者以前發(fā)布的精選技術(shù)文章匆浙,以及后續(xù)發(fā)布的技術(shù)文章(以原創(chuàng)為主),并且逐漸脫離 iOS 的內(nèi)容厕妖,將側(cè)重點(diǎn)會(huì)轉(zhuǎn)移到提高編程能力的方向上首尼。
- 讀書筆記類文章:分享編程類,思考類言秸,心理類软能,職場(chǎng)類書籍的讀書筆記。
- 思考類文章:分享筆者平時(shí)在技術(shù)上举畸,生活上的思考查排。
因?yàn)楣娞?hào)每天發(fā)布的消息數(shù)有限制,所以到目前為止還沒有將所有過去的精選文章都發(fā)布在公眾號(hào)上抄沮,后續(xù)會(huì)逐步發(fā)布的跋核。
而且因?yàn)楦鞔蟛┛推脚_(tái)的各種限制,后面還會(huì)在公眾號(hào)上發(fā)布一些短小精干叛买,以小見大的干貨文章哦~
掃下方的公眾號(hào)二維碼并點(diǎn)擊關(guān)注砂代,期待與您的共同成長(zhǎng)~