2010年7月15日 星期四

[IPhone] iphone UIView 記憶體管理

簡介
這篇文章是在紀錄一些我在開發 IPhone 遊戲的時候遇到的一些記憶體管理問題.

背景
用Object-C開發程式, 記憶體管理是最重要的一環, 但是這幾個月的經驗告訴我,並不是單純的 alloc 跟 release 就能夠處理好記憶體管理.
在Object-C的記憶體管理中, 所要把握的最重要的重點, 就是要釋放的時候, retainCount 必須是0,只有在 retainCount = 0 的情況下, 才會去呼叫 dealloc.
而呼叫 release 是降低 retainCount 最常用的方法, 但是由於 Object-C 中某些 method 會回傳 autorelease object, 這時候呼叫 release 反而會造成應用程式當掉.

範例1
UIImage *tmp = [UIImage imageNamed:@"logo.png"]; // imageNamed will return an autorelease object
bigpicture = [[UIImageView alloc] initWithImage:tmp];
//[tmp release];
// if you release a autorelease object, you application may be crash.
// so it dose not need release

在範例1中, imageNamed 會回傳一個 autorelease object, 因此若開發者去呼叫 release釋放記憶體.
將有可能導致程式當掉.
根據Apple文件的說法, 透過類似 [NSxxx xxxGenerate] 命名規則產生的 object, 都會被加入 autorelease pool, 所以不需要再去 release 它.
例如上例中的 imageNamed, 以及NSString *str = [NSString stringWithString: @"string managed by the pool"]; 都會自動加入 autorelease pool.

範例2
class SuccessView
{
private:
UIImageView* m_View;
public:
SuccessView(UIView* parentView);
~SuccessView();
};

SuccessView::SuccessView(UIView* parentView)
{
m_View = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
// after alloc, m_View's retainCount = 1
[parentView addSubView:m_View];
// after addSubView, m_View's retainCount = 2
}

SuccessView::~SuccessView()
{
[m_View removeFromSuperview];
// after removeFromSuperview, m_View's retainCount = 1
[m_View release];
// after release, m_View's retainCount = 0, release success.
}

class CrashView
{
private:
UIImageView* m_View;
public:
CrashView(UIView* parentView);
~CrashView();
};

CrashView::CrashView(UIView* parentView)
{
m_View = [[UIImageView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
// after alloc, m_View's retainCount = 1
[parentView addSubView:m_View];
// after addSubView, m_View's retainCount = 2
[m_View release];
// after release, m_View's retainCount = 1
}

CrashView::~CrashView()
{
[m_View release];
// this release may cause application crash.
}

class DemoSample
{
private:
UIView* m_MainView;
SuccessView* success;
CrashView* crash;
public:
DemoSample();
~DemoSample();

void TestRemoveSuccessView();
void TestRemoveCrashView();
};

DemoSample::DemoSample()
{
m_MainView = [[UIView alloc] initWithFrame:CGRectMake(0.0, 0.0, 100.0, 100.0)];
success = new SuccessView(m_MainView); // draw success view on m_MainView
crash = new CrashView(m_MainView); // draw crash view on m_MainView
}

DemoSample::~DemoSample()
{
[m_MainView release];
}

void DemoSample::TestRemoveSuccessView()
{
delete success; // it will success to remove this view from m_MainView
}
void DemoSample::TestRemoveCrashView()
{
delete crash; // it will cause crash
}

在遊戲開發中, 很常會遇到需要動態產生/動態釋放記憶體的情況, 例如前方出現一個敵人, 主角把敵人打死後或著敵人離開畫面後消失.
可是在測試的時候, 卻遇到了程式當掉的情況, 我一直在想我都是照著使用, 為啥會當掉, 參照 CrashView 的用法, 很多網路上的範例, 都是如此使用 addSubView 後, release view 一次, 因為 addSubview 會增加 retainCount.
這幾天總結出一些重點.
1.要將 Sub View從 Parent View 移除,一定要透過 removeFromSuperview, 若是直接 release 會造成Crash
2. addSubview 會讓 retainCount + 1
3. removeFromSuperview 會讓 retainCount - 1
總結這三個重點, 再將我的寫作方法改成 SuccessView 的做法, View的記憶體釋放到此總算解決了.

參考資料
IPhone Memory Management
官方文件

4 則留言:

  1. 可否將官方文件的網址波一下哩???^^

    回覆刪除
  2. http://developer.apple.com/mac/library/documentation/Cocoa/Conceptual/MemoryMgmt/MemoryMgmt.html#//apple_ref/doc/uid/10000011i
    官方文件是說這個嗎???

    回覆刪除
  3. 請問你所說的一定要用removeFromSuperview而不用release這個是為什麼阿?兩者都是把retaincount減一,差別在於哪邊?
    謝謝

    回覆刪除
  4. 使用removeFromSuperview是為了要解除與parentViewd的關連性,順便將retainCount減1,最後呼叫release是為了將retainCount再減1,retainCount等於0就釋放記憶體了^o^

    回覆刪除

嘎嘎嘎