簡書博客已經暫停更新,想看更多技術博客請到:
總結了Effective Objective-C之后,還想讀一本進階的iOS書,毫不猶豫選中了《Objective-C 高級編程》:
這本書有三個章節,我針對每一章節進行總結并加上適當的擴展分享給大家。可以從下面這張圖來看一下這三篇的整體結構:
注意,這個結構并不和書中的結構一致,而是以書中的結構為參考,稍作了調整。
本篇是第一篇:引用計數,簡單說兩句:
Objective-C通過 retainCount 的機制來決定對象是否需要釋放。 每次runloop迭代結束后,都會檢查對象的 retainCount,如果retainCount等于0,就說明該對象沒有地方需要繼續使用它,可以被釋放掉了。無論是手動管理內存,還是ARC機制,都是通過對retainCount來進行內存管理的。
先看一下手動內存管理:
手動內存管理
我個人覺得,學習一項新的技術之前,需要先了解一下它的核心思想。理解了核心思想之后,對技術點的把握就會更快一些:
內存管理的思想
- 思想一:自己生成的對象,自己持有。
- 思想二:非自己生成的對象,自己也能持有。
- 思想三:不再需要自己持有的對象時釋放對象。
- 思想四:非自己持有的對象無法釋放。
從上面的思想來看,我們對對象的操作可以分為三種:生成,持有,釋放,再加上廢棄,一共有四種。它們所對應的Objective-C的方法和引用計數的變化是:
對象操作 | Objecctive-C方法 | 引用計數的變化 |
---|---|---|
生成并持有對象 | alloc/new/copy/mutableCopy等方法 | +1 |
持有對象 | retain方法 | +1 |
釋放對象 | release方法 | -1 |
廢棄對象 | dealloc方法 | 無 |
用書中的圖來直觀感受一下這四種操作:
下面開始逐一解釋上面的四條思想:
思想一:自己生成的對象,自己持有
在生成對象時,使用以下面名稱開頭的方法生成對象以后,就會持有該對象:
- alloc
- new
- copy
- mutableCopy
舉個??:
id obj = [[NSObject alloc] init];//持有新生成的對象
這行代碼過后,指向生成并持有[[NSObject alloc] init]的指針被賦給了obj,也就是說obj這個指針強引用[[NSObject alloc] init]這個對象。
同樣適用于new方法:
id obj = [NSObject new];//持有新生成的對象
注意:
這種將持有對象的指針賦給指針變量的情況不只局限于上面這四種方法名稱,還包括以他們開頭的所有方法名稱:
- allocThisObject
- newThatObject
- copyThisObject
- mutableCopyThatObject
舉個??:
id obj1 = [obj0 allocObject];//符合上述命名規則,生成并持有對象
它的內部實現:
- (id)allocObject
{
id obj = [[NSObject alloc] init];//持有新生成的對象
return obj;
}
反過來,如果不符合上述的命名規則,那么就不會持有生成的對象,
看一個不符合上述命名規則的返回對象的createObject方法的內部實現??:
- (id)createObject
{
id obj = [[NSObject alloc] init];//持有新生成的對象
[obj autorelease];//取得對象,但自己不持有
return obj;
}
經由這個方法返回以后,無法持有這個返回的對象。因為這里使用了autorelease。autorelease提供了這樣一個功能:在對象超出其指定的生存范圍時能夠自動并正確地釋放(詳細會在后面介紹)。
也就是說,生成一個調用方不持有的對象是可以通過autorelease來實現的(例如NSMutableArray的array類方法)。
我的個人理解是:通過autorelease方法,使對象的持有權轉移給了自動釋放池。所以實現了:調用方拿到了對象,但這個對象還不被調用方所持有。
由這個不符合命名規則的例子來引出思想二:
思想二:非自己生成的對象,自己也能持有
我們現在知道,僅僅通過上面那個不符合命名規則的返回對象實例的方法是無法持有對象的。但是我們可以通過某個操作來持有這個返回的對象:這個方法就是通過retain方法來讓指針變量持有這個新生成的對象:
id obj = [NSMutableArray array];//非自己生成并持有的對象
[obj retain];//持有新生成的對象
注意,這里[NSMutableArray array]返回的非自己持有的對象正是通過上文介紹過的autorelease方法實現的。所以如果想持有這個對象,需要執行retain方法才可以。
思想三:不再需要自己持有的對象時釋放對象
對象的持有者有義務在不再需要這個對象的時候主動將這個對象釋放。注意,是有義務,而不是有權利,注意兩個詞的不同。
來看一下釋放對象的例子:
id obj = [[NSObject alloc] init];//持有新生成的對象
[obj doSomething];//使用該對象做一些事情
[obj release];//事情做完了,釋放該對象
同樣適用于非自己生成并持有的對象(參考思想二):
id obj = [NSMutableArray array];//非自己生成并持有的對象
[obj retain];//持有新生成的對象
[obj soSomething];//使用該對象做一些事情
[obj release];//事情做完了,釋放該對象
可能遇到的面試題:調用對象的release方法會銷毀對象嗎?
答案是不會:調用對象的release方法只是將對象的引用計數器-1,當對象的引用計數器為0的時候會調用了對象的dealloc 方法才能進行釋放對象的內存。
思想四:無法釋放非自己持有的對象
在釋放對象的時候,我們只能釋放已經持有的對象,非自己持有的對象是不能被自己釋放的。這很符合常識:就好比你自己才能從你自己的銀行卡里取錢,取別人的卡里的錢是不對的(除非他的錢歸你管。。。只是隨便舉個例子)。
兩種不允許的情況:
1. 釋放一個已經廢棄了的對象
id obj = [[NSObject alloc] init];//持有新生成的對象
[obj doSomething];//使用該對象
[obj release];//釋放該對象,不再持有了
[obj release];//釋放已經廢棄了的對象,崩潰
2. 釋放自己不持有的對象
id obj = [NSMutableArray array];//非自己生成并持有的對象
[obj release];//釋放了非自己持有的對象
思考:哪些情況會使對象失去擁有者呢?
- 將指向某對象的指針變量指向另一個對象。
- 將指向某對象的指針變量設置為nil。
- 當程序釋放對象的某個擁有者時。
- 從collection類中刪除對象時。
現在知道了引用計數式內存管理的四個思想,我們再來看一下四個操作引用計數的方法:
alloc/retain/release/dealloc的實現
某種意義上,GNUstep 和 Foundation 框架的實現是相似的。所以這本書的作者通過GNUstep的源碼來推測了蘋果Cocoa框架的實現。
下面開始針對每一個方法,同時用GNUstep和蘋果的實現方式(追蹤程序的執行和作者的猜測)來對比一下各自的實現。
GNUstep實現:
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方法分配了對象,看一下它的內部實現:
//GNUstep/modules/core/base/Source/NSObject.m NSAllocateObject:
struct obj_layout {
NSUInteger retained;
};
NSAllocateObject(Class aClass, NSUInteger extraBytes, NSZone *zone)
{
int size = 計算容納對象所需內存大小;
id new = NSZoneMalloc(zone, 1, size);//返回新的實例
memset (new, 0, size);
new = (id)&((obj)new)[1];
}
- NSAllocateObject函數通過NSZoneMalloc函數來分配存放對象所需要的內存空間。
- obj_layout是用來保存引用計數,并將其寫入對象內存頭部。
對象的引用計數可以通過retainCount方法來取得:
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以后,通過訪問對象內存頭部的.retained變量,來獲取引用計數。
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
{
//如果當前的引用計數 = 0,調用dealloc函數
if (NSDecrementExtraRefCountWasZero(self))
{
[self dealloc];
}
}
BOOL NSDecrementExtraRefCountWasZero(id anObject)
{
//如果當前的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);//釋放
}
總結一下上面的幾個方法:
- Objective-C對象中保存著引用計數這一整數值。
- 調用alloc或者retain方法后,引用計數+1。
- 調用release后,引用計數-1。
- 引用計數為0時,調用dealloc方法廢棄對象。
下面看一下蘋果的實現:
蘋果的實現
alloc方法
通過在NSObject類的alloc類方法上設置斷點,我們可以看到執行所調用的函數:
- +alloc
- +allocWithZone:
- class_createInstance//生成實例
- calloc//分配內存塊
retainCount:
- __CFdoExternRefOperation
- CFBasicHashGetCountOfKey
retain方法
- __CFdoExternRefOperation
- CFBasicHashAddValue
release方法
- __CFdoExternRefOperation
- CFBasicHashRemoveValue
我們可以看到他們都調用了一個共同的 __CFdoExternRefOperation 方法。
看一下它的實現:
int __CFDoExternRefOperation(uintptr_t op, id obj) {
CFBasicHashRef table = 取得對象的散列表(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語句 針對不同的操作來進行具體的方法調用,如果 op 是 OPERATION_retain,就去掉用具體實現 retain 的方法,以此類推。
可以猜想上層的retainCount,retain,release方法的實現:
- (NSUInteger)retainCount
{
return (NSUInteger)____CFDoExternRefOperation(OPERATION_retainCount,self);
}
- (id)retain
{
return (id)____CFDoExternRefOperation(OPERATION_retain,self);
}
//這里返回值應該是id,原書這里應該是錯了
- (id)release
{
return (id)____CFDoExternRefOperation(OPERATION_release,self);
}
我們觀察一下switch里面每個語句里的執行函數名稱,似乎和散列表(Hash)有關,這說明蘋果對引用計數的管理應該是通過散列表來執行的。
在這張表里,key為內存塊地址,而對應的值為引用計數。也就是說,它保存了這樣的信息:一些被引用的內存塊各自對應的引用計數。
那么使用散列表來管理內存有什么好處呢?
因為計數表保存內存塊地址,我們就可以通過這張表來:
- 確認損壞內存塊的位置。
- 在檢測內存泄漏時,可以查看各對象的持有者是否存在。
autorelease
autorelease 介紹
當對象超出其作用域時,對象實例的release方法就會被調用,autorelease的具體使用方法如下:
- 生成并持有NSAutoreleasePool對象。
- 調用已分配對象的autorelease方法。
- 廢棄NSAutoreleasePool對象。
所有調用過autorelease方法的對象,在廢棄NSAutoreleasePool對象時,都將調用release方法(引用計數-1):
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];//相當于obj調用release方法
NSRunLoop在每次循環過程中,NSAutoreleasePool對象都會被生成或廢棄。
也就是說,如果有大量的autorelease變量,在NSAutoreleasePool對象廢棄之前(一旦監聽到RunLoop即將進入睡眠等待狀態,就釋放NSAutoreleasePool),都不會被銷毀,容易導致內存激增的問題:
for (int i = 0; i < imageArray.count; i++)
{
UIImage *image = imageArray[i];
[image doSomething];
}
因此,我們有必要在適當的時候再嵌套一個自動釋放池來管理臨時生成的autorelease變量:
for (int i = 0; i < imageArray.count; i++)
{
//臨時pool
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
UIImage *image = imageArray[i];
[image doSomething];
[pool drain];
}
可能會出的面試題:什么時候會創建自動釋放池?
答:運行循環檢測到事件并啟動后,就會創建自動釋放池,而且子線程的 runloop 默認是不工作的,無法主動創建,必須手動創建。
舉個??:
自定義的 NSOperation 類中的 main 方法里就必須添加自動釋放池。否則在出了作用域以后,自動釋放對象會因為沒有自動釋放池去處理自己而造成內存泄露。
autorelease實現
和上文一樣,我們還是通過GNUstep和蘋果的實現來分別看一下。
GNUstep 實現
//GNUstep/modules/core/base/Source/NSObject.m autorelease
- (id)autorelease
{
[NSAutoreleasePool addObject:self];
}
如果調用NSObject類的autorelease方法,則該對象就會被追加到正在使用的NSAutoreleasePool對象中的數組里(作者假想了一個簡化的源代碼):
//GNUstep/modules/core/base/Source/NSAutoreleasePool.m addObject
+ (void)addObject:(id)anObj
{
NSAutoreleasePool *pool = 取得正在使用的NSAutoreleasePool對象
if (pool != nil){
[pool addObject:anObj];
}else{
NSLog(@"NSAutoreleasePool對象不存在");
}
}
- (void)addObject:(id)anObj
{
[pool.array addObject:anObj];
}
也就是說,autorelease實例方法的本質就是調用NSAutoreleasePool對象的addObject類方法,然后這個對象就被追加到正在使用的NSAutoreleasePool對象中的數組里。
再來看一下NSAutoreleasePool的drain方法:
- (void)drain
{
[self dealloc];
}
- (void)dealloc
{
[self emptyPool];
[array release];
}
- (void)emptyPool
{
for(id obj in array){
[obj release];
}
}
我們可以看到,在emptyPool方法里,確實是對數組里每一個對象進行了release操作。
蘋果的實現
我們可以通過objc4/NSObject.mm來確認蘋果中autorelease的實現:
objc4/NSObject.mm AutoreleasePoolPage
class AutoreleasePoolPage
{
static inline void *push()
{
//生成或者持有 NSAutoreleasePool 類對象
}
static inline void pop(void *token)
{
//廢棄 NSAutoreleasePool 類對象
releaseAll();
}
static inline id autorelease(id obj)
{
//相當于 NSAutoreleasePool 類的 addObject 類方法
AutoreleasePoolPage *page = 取得正在使用的 AutoreleasePoolPage 實例;
autoreleaesPoolPage->add(obj)
}
id *add(id obj)
{
//將對象追加到內部數組中
}
void releaseAll()
{
//調用內部數組中對象的 release 方法
}
};
//壓棧
void *objc_autoreleasePoolPush(void)
{
if (UseGC) return nil;
return AutoreleasePoolPage::push();
}
//出棧
void objc_autoreleasePoolPop(void *ctxt)
{
if (UseGC) return;
AutoreleasePoolPage::pop(ctxt);
}
來看一下外部的調用:
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)
看函數名就可以知道,對autorelease分別執行push、pop操作。銷毀對象時執行release操作。
可能出現的面試題:蘋果是如何實現autoreleasepool的?
autoreleasepool以棧的數據結構實現,主要通過下列三個函數完成.
? objc_autoreleasepoolPush(壓入)
? objc_autoreleasepoolPop(彈出)
? objc_autorelease(釋放內部)
ARC內存管理
內存管理的思想
上面學習了非ARC機制下的手動管理內存思想,針對引用計數的操作和自動釋放池的相關內容。現在學習一下在ARC機制下的相關知識。
ARC和非ARC機制下的內存管理思想是一致的:
- 自己生成的對象,自己持有。
- 非自己生成的對象,自己也能持有。
- 不再需要自己持有的對象時釋放對象。
- 非自己持有的對象無法釋放。
在ARC機制下,編譯器就可以自動進行內存管理,減少了開發的工作量。但我們有時仍需要四種所有權修飾符來配合ARC來進行內存管理
四種所有權修飾符
但是,在ARC機制下我們有的時候需要追加所有權聲明(以下內容摘自官方文檔):
- __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.
下面分別講解一下這幾個修飾符:
__strong修飾符
__strong修飾符 是id類型和對象類型默認的所有權修飾符:
__strong使用方法:
id obj = [NSObject alloc] init];
等同于:
id __strong obj = [NSObject alloc] init];
看一下內存管理的過程:
{
id __strong obj = [NSObject alloc] init];//obj持有對象
}
//obj超出其作用域,強引用失效
__strong修飾符表示對對象的強引用。持有強引用的變量在超出其作用域時被廢棄。
在__strong修飾符修飾的變量之間相互賦值的情況:
id __strong obj0 = [[NSObject alloc] init];//obj0 持有對象A
id __strong obj1 = [[NSObject alloc] init];//obj1 持有對象B
id __strong obj2 = nil;//ojb2不持有任何對象
obj0 = obj1;//obj0強引用對象B;而對象A不再被ojb0引用,被廢棄
obj2 = obj0;//obj2強引用對象B(現在obj0,ojb1,obj2都強引用對象B)
obj1 = nil;//obj1不再強引用對象B
obj0 = nil;//obj0不再強引用對象B
obj2 = nil;//obj2不再強引用對象B,不再有任何強引用引用對象B,對象B被廢棄
而且,__strong可以使一個變量初始化為nil:id __strong obj0;
同樣適用于:id __weak obj1; id __autoreleasing obj2;
做個總結:被__strong修飾后,相當于強引用某個對象。對象一旦有一個強引用引用自己,引用計數就會+1,就不會被系統廢棄。而這個對象如果不再被強引用的話,就會被系統廢棄。
__strong內部實現:
生成并持有對象:
{
id __strong obj = [NSObject alloc] init];//obj持有對象
}
編譯器的模擬代碼:
id obj = objc_mesgSend(NSObject, @selector(alloc));
objc_msgSend(obj,@selector(init));
objc_release(obj);//超出作用域,釋放對象
再看一下使用命名規則以外的構造方法:
{
id __strong obj = [NSMutableArray array];
}
編譯器的模擬代碼:
id obj = objc_msgSend(NSMutableArray, @selector(array));
objc_retainAutoreleasedReturnValue(obj);
objc_release(obj);
objc_retainAutoreleasedReturnValue的作用:持有對象,將對象注冊到autoreleasepool并返回。
同樣也有objc_autoreleaseReturnValue,來看一下它的使用:
+ (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:返回注冊到autoreleasepool的對象。
__weak修飾符
__weak使用方法:
__weak修飾符大多解決的是循環引用的問題:如果兩個對象都互相強引用對方,同時都失去了外部對自己的引用,那么就會形成“孤島”,這個孤島將永遠無法被釋放,舉個??:
@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強引用對象A
id test1 = [[Test alloc] init];//test1強引用對象B
[test0 setObject:test1];//test0強引用對象B
[test1 setObject:test0];//test1強引用對象A
}
因為生成對象(第一,第二行)和set方法(第三,第四行)都是強引用,所以會造成兩個對象互相強引用對方的情況:
所以,我們需要打破其中一種強引用:
@interface Test:NSObject
{
id __weak obj_;//由__strong變成了__weak
}
- (void)setObject:(id __strong)obj;
@end
這樣一來,二者就只是弱引用對方了:
__weak內部實現
{
id __weak obj1 = obj;
}
編譯器的模擬代碼:
id obj1;
objc_initWeak(&obj1,obj);//初始化附有__weak的變量
id tmp = objc_loadWeakRetained(&obj1);//取出附有__weak修飾符變量所引用的對象并retain
objc_autorelease(tmp);//將對象注冊到autoreleasepool中
objc_destroyWeak(&obj1);//釋放附有__weak的變量
這確認了__weak的一個功能:使用附有__weak修飾符的變量,即是使用注冊到autoreleasepool中的對象。
這里需要著重講解一下objc_initWeak方法和objc_destroyWeak方法:
- objc_initWeak:初始化附有__weak的變量,具體通過執行objc_strongWeak(&obj1, obj)方法,將obj對象以&obj1作為key放入一個weak表(Hash)中。
- objc_destroyWeak:釋放附有__weak的變量。具體通過執行objc_storeWeak(&obj1,0)方法,在weak表中查詢&obj1這個鍵,將這個鍵從weak表中刪除。
注意:因為同一個對象可以賦值給多個附有__weak的變量中,所以對于同一個鍵值,可以注冊多個變量的地址。
當一個對象不再被任何人持有,則需要釋放它,過程為:
- objc_dealloc
- dealloc
- _objc_rootDealloc
- objc_dispose
- objc_destructInstance
- objc_clear_deallocating
- 從weak表中獲取廢棄對象的地址
- 將包含在記錄中的所有附有__weak修飾符變量的地址賦值為nil
- 從weak表中刪除該記錄
- 從引用計數表中刪除廢棄對象的地址
__autoreleasing修飾符
__autoreleasing使用方法
ARC下,可以用@autoreleasepool來替代NSAutoreleasePool類對象,用__autoreleasing修飾符修飾變量來替代ARC無效時調用對象的autorelease方法(對象被注冊到autoreleasepool)。
說到__autoreleasing修飾符,就不得不提__weak:
id __weak obj1 = obj0;
NSLog(@"class = %@",[obj1 class]);
等同于:
id __weak obj1 = obj0;
id __autoreleasing tmp = obj1;
NSLog(@"class = %@",[tmp class]);//實際訪問的是注冊到自動個釋放池的對象
注意一下兩段等效的代碼里,NSLog語句里面訪問的對象是不一樣的,它說明:在訪問__weak修飾符的變量(obj1)時必須訪問注冊到autoreleasepool的對象(tmp)。為什么呢?
因為__weak修飾符只持有對象的弱引用,也就是說在將來訪問這個對象的時候,無法保證它是否還沒有被廢棄。因此,如果把這個對象注冊到autoreleasepool中,那么在@autoreleasepool塊結束之前都能確保該對象存在。
__autoreleasing內部實現
將對象賦值給附有__autoreleasing修飾符的變量等同于ARC無效時調用對象的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入棧,執行autorelease,出棧的三個方法。
ARC下的規則
我們知道了在ARC機制下編譯器會幫助我們管理內存,但是在編譯期,我們還是要遵守一些規則,作者為我們列出了以下的規則:
- 不能使用retain/release/retainCount/autorelease
- 不能使用NSAllocateObject/NSDeallocateObject
- 必須遵守內存管理的方法名規則
- 不要顯式調用dealloc
- 使用@autorelease塊代替NSAutoreleasePool
- 不能使用區域(NSZone)
- 對象型變量不能作為C語言結構體的成員
- 顯式轉換id和void*
1. 不能使用retain/release/retainCount/autorelease
在ARC機制下使用retain/release/retainCount/autorelease方法,會導致編譯器報錯。
2. 不能使用NSAllocateObject/NSDeallocateObject
在ARC機制下使用NSAllocateObject/NSDeallocateObject方法,會導致編譯器報錯。
3. 必須遵守內存管理的方法名規則
對象的生成/持有的方法必須遵循以下命名規則:
- alloc
- new
- copy
- mutableCopy
- init
前四種方法已經介紹完。而關于init方法的要求則更為嚴格:
- 必須是實例方法
- 必須返回對象
- 返回對象的類型必須是id類型或方法聲明類的對象類型
4. 不要顯式調用dealloc
對象被廢棄時,無論ARC是否有效,系統都會調用對象的dealloc方法。
我們只能在dealloc方法里寫一些對象被廢棄時需要進行的操作(例如移除已經注冊的觀察者對象)但是不能手動調用dealloc方法。
注意在ARC無效的時候,還需要調用[super dealloc]:
- (void)dealloc
{
//該對象的處理
[super dealloc];
}
5. 使用@autorelease塊代替NSAutoreleasePool
ARC下須使用使用@autorelease塊代替NSAutoreleasePool。
6. 不能使用區域(NSZone)
NSZone已經在目前的運行時系統(OBC2被設定的環境)被忽略了。
7. 對象型變量不能作為C語言結構體的成員
C語言的結構體如果存在Objective-C對象型變量,便會引起錯誤,因為C語言在規約上沒有方法來管理結構體成員的生存周期 。
8. 顯式轉換id和void*
非ARC下,這兩個類型是可以直接賦值的
id obj = [NSObject alloc] init];
void *p = obj;
id o = p;
但是在ARC下就會引起編譯錯誤。為了避免錯誤,我們需要通過__bridege來轉換。
id obj = [[NSObject alloc] init];
void *p = (__bridge void*)obj;//顯式轉換
id o = (__bridge id)p;//顯式轉換
屬性
來看一下屬性的聲明與所有權修飾符的關系
屬性關鍵字 | 所有權 修飾符 |
---|---|
assign | __unsafe_unretained |
copy | __strong |
retain | __strong |
strong | __strong |
__unsafe_unretained | __unsafe_unretained |
weak | __weak |
說一下__unsafe_unretained:
__unsafe_unretained表示存取方法會直接為實例變量賦值。
這里的“unsafe”是相對于weak而言的。我們知道weak指向的對象被銷毀時,指針會自動設置為nil。而__unsafe_unretained卻不會,而是成為空指針。需要注意的是:當處理非對象屬性的時候就不會出現空指針的問題。
這樣第一章就介紹完了,第二篇會在下周一發布^^
擴展文獻:
- Apple:Transitioning to ARC Release Notes
- 蚊香醬:可能是史上最全面的內存管理文章
- 微笑和飛飛:可能碰到的iOS筆試面試題(6)--內存管理
- 《iOS編程(第4版)》
本文已經同步到個人博客:傳送門
本文已在版權印備案,如需轉載請訪問版權印。48422928
-------------------------------- 2018年7月17日更新 --------------------------------
注意注意!!!
筆者在近期開通了個人公眾號,主要分享編程,讀書筆記,思考類的文章。
- 編程類文章:包括筆者以前發布的精選技術文章,以及后續發布的技術文章(以原創為主),并且逐漸脫離 iOS 的內容,將側重點會轉移到提高編程能力的方向上。
- 讀書筆記類文章:分享編程類,思考類,心理類,職場類書籍的讀書筆記。
- 思考類文章:分享筆者平時在技術上,生活上的思考。
因為公眾號每天發布的消息數有限制,所以到目前為止還沒有將所有過去的精選文章都發布在公眾號上,后續會逐步發布的。
而且因為各大博客平臺的各種限制,后面還會在公眾號上發布一些短小精干,以小見大的干貨文章哦~
掃下方的公眾號二維碼并點擊關注,期待與您的共同成長~