2011年5月23日 星期一

Memory Management - 1

  Objective-C 支援 Garbage Collection, 但 iPhone 並不支援 Garbage Collection, 所以 iPhone 開發者在對於沒有使用到的 Object 必須要自己釋放.

iPhone 使用到 Reference Counting 的機制來幫助開發者管理記憶體, 何謂 Reference Counting 呢 ? 簡單的說就是對於每個 Object 都有個 counter, 只要此 Object 被 alloc 內部的 counter 就會累計加 1, 而 Object 被 release 時, 內部 counter 就會減 1, 當 counter 被遞減成 0 時, 此 Object 就會被 OS 釋放.

iPhone 基本操作中會遞增 counter 的有 alloc, new, retain, 而會遞減 counter 則是 release.


NSObject *obj = [[NSObject alloc]init];
...
NSObject *obj = [NSObject new];
...
[obj retain];
...
[obj release];
...


一般來說, 當開發者配置 memory 之後, 就一定要釋放, 不然就會產生 memory leakage. 在 iPhone Programming 中, 針對 memory 的寫法, 大概有幾個簡單的原則, 只要遵循這些原則, 基本上就可以大幅降低 memory leakage 的問題.
1. 誰遞增 reference counter, 就由誰負責遞減 reference counter
    針對這點, 這邊舉了兩個例子來分析這彼此的差異
case 1:


Modude A:
- (NSString *)generateString
{
    myString = [[NSString alloc]initWithString:@"test"];
    return myString;
}

Module B:
-(void)function
{
    NSString *string = [ModuleA generateString];
    ...
    [string release];
}



case 2:


Modude A:
- (NSString *)generateString
{
    myString = [[NSString alloc]initWithString:@"test"];
    return myString;
}

- (void)releaseString:(NSString *)string
{
    [string release];
}

Module B:
-(void)function
{
    NSString *string = [ModuleA generateString];
    ...
    [ModuleA releaseString:string];
}


單看上述這兩個例子, 或許你可能會覺得兩個都沒錯, 記憶體的確也沒有遺漏, 實際上也是如此,兩個方式語法上都沒有錯誤, 但是一般不管是在寫遊戲還是應用程式, 期複雜度一定遠遠超過上面的例子, 當專案越大時, 在 case 1 的情況下, 要去檢查 [ModuleA generateString在使用上是否有遺漏 release 的情況, 這將會是一個麻煩的事情. 相反的, 若是使用 case 2 的情況下, 要做相同檢查的動作, 只要在 Module A 中做簡單的修改即可得知是否有少 call releaseString. 在 Module A 中簡單的增加一個 allocCounter 變數, 最終只要 allocCounter > 0, 就代表有 memory 的洩露.

Modude A:
- (NSString *)generateString
{
    myString = [[NSString alloc]initWithString:@"test"];
    allocCounter++;
    return myString;
}

- (void)releaseString:(NSString *)string
{
    [string release];
    allocCounter--;
}


2. 善用 autorelease
每個 iPhone 應用程式都會有一個基本的  Autorelease Pool, 也就是在  main.m 中所建立的

int main(int argc, char *argv[]) {
NSAutoreleasePool *pool = [NSAutoreleasePool new];
int retVal = UIApplicationMain(argc, argv, nil, @"XXXDelegate");
[pool release];
return retVal;
}


我們可以透過這個 Autorelease Pool 的協助, 來幫助我們更容易的管理記憶體, 使用方式很簡單, 只要在 object 之後呼叫 autorelease 即可.

NSString *myString = [[[NSString alloc]initWithString:@"test"]autorelease];


上述的 NSString 配置方式和先前的最大不同在於, myString 經過呼叫 autorelease 之後, 就不需要在自己呼叫 release 去做釋放的動作, 釋放 myString 會由 iOS 自動幫開發者執行. 那究竟 iOS 何時會去做這件事情呢 ? 簡單的說, 當 iOS 完成了一個 event loop 時, 就會去檢查 Autorelease Pool 是否有需要"自動遞減" counter 的物件, 然後會將 counter 變為  0 的物件釋放. 我們可以做一個簡單的測試, 在 iPhone 中開啓一新專案, 並透過 UIButton 的點擊去呼叫下面測試函式. 使用 UIButton 主要目的是為了讓兩次的呼叫是在不同的 event loop 中執行.

- (void)testAutorelease

{
    static NSObject *obj = nil;
    if (!obj)
    {
        obj = [[[NSObject alloc]init]autorelease];
        [obj retain];
    }
    NSLog(@"obj counter : %d\n",[obj retainCount]);
}



在連續兩次叫用 testAutorelease 時所出現的結果, 第一次顯示的 counter 值是 2, 而第二次顯示的 counter 是 1. 這結果很明顯的告知我們, 當 event loop 結束時, iOS 會針對 autorelease pool 裡的物件自動去做 counter 遞減動作.

由這結果可以得知, 若能善用 autorelease 則可以大幅降低維護記憶體的複雜度. 但是有一個需要額外注意的是, 因為 autorelease 的是放時間點是在 event loop 執行完成之後, 在這過程中是不會有釋放的動作, 所以若太大量的物件都交給 Autorelease Pool 管理, 很有可能會在執行其間遇到記憶體不足的問題, 所以要如何利用 autorelease 是每一個 iPhone 開發者都要思考的.


沒有留言:

張貼留言