本篇主要學習以下幾個知識點
- alloc/reatin/release/dealloc 理解
- autorelease 理解
- autorelease GUN 實現
- autorelease 蘋果 實現
alloc/reatin/release/dealloc 實現
我們來看看 GUNstep 源代碼中 NSObject 類的的 alloc 類方法。
id obj = [NSObject alloc];
上述調用 NSObject 類的 alloc 類方法在 NSObjecr.m 源代碼中的實現如下。
+ (id) alloc
{
return [self allocWithZone: NSDefaultMallocZone()];
}
+ (id) allocWithZone:(NSZone *)z
{
return NSAllocateObject(self,0,z);
}
通過 allocWithZone: 類方法調用 N\NSAllocateObject 函數分配對象。下面我們查看 NSAllocateObject 函數.
struct obj_layout {
NSUInterger retained;
}
inline id NSAllocateObject (Class aClass, NSUInterger extraBytes, NSZone *zone) {
int size = 計算容納對象所需內存大小;
id new = NSZoneMalloc(zone, size);
memset(new, 0, size);
new = (id)&((struct obj_layout *) new)[1];
}
NSAllocateObject 函數通過調用 NSZoneMalloc 函數來分配存放對象所需的內存空間,之後將改內存空間置0,最後返回作爲對象而使用的指針。
以下是去掉 NSZone 後簡化了源代碼:
struct obj_layout {
NSUInterger retained;
}
+ (id) alloc {
int size = sizeof(struct obj_layout) + 對象大小;
struct obj_layout *p = (struct obj_layout *)calloc(1, size);
return (id)(p+1);
}
alloc 類方法用 struct obj_layout 中的 retained 整數來保存引用計數,並將其寫入對象內存頭部,該對象內存全部置0後返回。
以下用圖來展示有關 GUNstep 的實現,alloc類方法返回對象,如圖
對象的引用計數可通過 retainCount 實例方法來取得。
id obj = [NSObject alloc];
NSLog(@"retainCount=%d", [obj retainCount]); // => 1
執行alloc後對象的 retainCount 是 “1”。下面通過 GUNstep 的源代碼來確認。
- (NSUInteger)retainCount {
return NSExtraRefCount(self) + 1;
}
inline NSUInterger NSExtraRefCount(id anObject) {
return ((struct obj_layout *) anObject)[-1].retained;
}
由對象尋址找到對象內存頭部,從而訪問其中的retained變量。如圖:
autorelease
autorelease 就是自動釋放。這看上去很像ARC,但實際上它更類似於C語言中的自動變量(局部變量)的特性。
{
int a;
}
/*
因爲超出變量作用域,自動變量 "int a" 被廢棄,不可再訪問
*/
autorelease 會像 C 語言的自動變量那樣來對待對象實例。當超出其作用域(相當於變量作用域)時,對象實例的 release 實例方法被調用。另外,同 C 語言的自動變量不同的是,編程人員可以設定變量的作用域。
autorelease 的具體使用方法如下:
- 生成並持有 NSAutoreleasePool 對象
- 調用已分配對象的 autorelease 實例方法
- 廢棄 NSAutoreleasePood 對象
NSAutoreleasePool 對象的聲明週期相當於 C 語言變量的作用域。對於所有調用過 autorelease 實例方法的對象,在廢棄 NSAutoreleasePool 對象時,都將調用 release 實例方法。如上圖。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
id obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];
“[pool drain]” 等同於 “[obj release]”。
在 Cocoa 框架中,相當於程序主循環的 NSRunLoop 或者在其他程序可運行的地方,對 NSAutoreleasePool 對象進行生成、持有和廢棄處理。因此,應用程序開發者不一定非得使用 NSAutoreleasePool 對象來進行開發工作。
意思就是我們不一定需要去管理 NSAutoreleasePool。
儘管如此,但在大量產生 autorelease 的對象時,只要不廢棄 NSAutoreleasePool 對象,那麼生成的對象就不能被釋放,因此有時會產生內存不足的現象。
典型的例子:讀入大量圖像的同時改變尺寸。圖像文件讀入到 NSData 對象,並從中生成 UIImage 對象,改變該對象尺寸後生成新的 UIImage 對象。這種情況下,就會大量生成 autorelease 對象。
for (int i = 0; i < 圖像數; ++i){
/*
讀入圖像
大量產生 autorelease 對象
由於沒有廢棄 NSAutoreleasePool 對象
最終導致內存不足
*/
}
在此情況下,有必要在適當的地方生成,持有或者廢棄 NSAutoreleasePool 對象。
for (int i = 0; i < 圖像數; ++i){
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/*
讀入圖像
大量產生 autorelease 對象
*/
[pool drain];
}
另外,Cocoa 框架中也有很多類方法用於返回 autorelease 的對象。比如 NSMutableArray 類的 arrayWithCapacity 類方法。
id array = [NSMutableArray arrayWithCapacity:1];
此源代碼等同於以下源代碼
id array = [[[NSMutableArray alloc] initWithCapacity:1] autorelease];
autorelease 實現 - GUNstep是如何實現的
[obj autorelease];
此源代碼調用 NSObject 類的 autorelease 實例方法。
GUNstep實現:
- (id) autorelease {
[NSAutoreleasePool addObject:self];
}
autorelease 實例方法的本質就是調用 NSAutoreleasePool 對象的 addObject 類方法。
+ (void) addObject:(id)anObj {
NSAutoreleasePool *pool = 取得正在使用的 NSAutoreleasePool 對象;
if (pool != nil) {
[pool addObject: anObjc];
}else{
NSLog(@"NSAutoreleasePool 對象非存在狀態下調用 autorelease")
}
}
addObject 類方法調用正在使用的 NSAutoreleasePool 對象的 addObject 實例方法。
如果嵌套或者持有 NSAutoreleasePool 對象,理所當然會使用最內側的對象。
- (void) drain {
[self dealloc];
}
- (void) dellloc {
[self emptyPool];
[array release];
}
- (void) emptyPool {
for (id obj in array){
[obj release];
}
}
雖然調用了好幾個方法,但是可以確定對於數組中的所以對象都調用了 release 實例方法。
蘋果 autorelease 的實現
objc4 庫的 runtime/objc-arr.mm 來確認蘋果中 autorelease 的實現
Class AutoreleasePoolPage
{
static inline void *push (){
相當於生成或者持有 NSAutoreleasePool 對象
}
static inline void *pop (void *token){
releaseAll();
}
static inline id autorelease (id obj){
相當於 NSAutoreleasePool 類的 addObject 類方法
NSAutoreleasePoolPage *autoreleasePoolPage = 取得正在使用的實例;
autoreleasePoolPage ->add(obj);
}
id *add (id obj) {
將對象追加到內部數組中
}
void releaseAll(){
調用內部數組中對象的release實例方法
}
}
void *objc_autoreleasePoolPush(void){
return AutoreleasePoolPage::push();
}
void *objc_autoreleasePoolPop(void *ctxt){
return AutoreleasePoolPage::pop(ctxt);
}
id *obj_autorelease(id obj) {
return AutoreleasePoolPage:autorelease(obj);
}
C++類中雖然有動態數組的實現,但其行爲和GUNstep的實現完全相同。
我們使用調試器來觀察一下NSAutoreleasePool類方法和 autorelease 方法的運行過程。如下所示,這些方法調用了關聯於 objc4 庫的 autorelease 實現的函數。
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
/*= objc_autoreleasePoolPush()*/
id obj = [[NSObject alloc] init];
[obj autorelease];
/*= obj_autorelease(obj)*/
[pool drain];
/*= objc_autoreleasePoolPop(pool)*/
另外,可通過 NSAutoreleasePool 類中的調試用非公開類方法 showPools 來確認已被 autorelease 的對象情況。
[NSAutoreleasePool showPools];
NSAutoreleasePool 類的 showPools 類方法只能在 iOS 中使用,作爲替代,在現在的運行時系統中我們使用調試用非公開函數 _objc_autoreleasePoolPrint()
。
/* 函數聲明 */
extend void _objc_autoreleasePoolPrint();
/* autorelesepool 調試用輸出開始*/
_objc_autoreleasePoolPrint();
如果運行此函數,就能像下面這樣在控制檯中來確認 AutoreleasePoolPage 類的情況。
NSAutoreleasePool 調用 autorelease 會如何?發生異常
通常在使用 Obejctive-C,也就是 Foundation 框架時,無論調用哪一個對象的 autorelease 實例方法,實際上調用都是 NSObject 類的 autorelease 實例方法。但是對於 NSAutoreleasePool 類,autorelease 實例方法已被該類重載,因此運行時就會報錯。