UIViewController でのメモリ管理見本
2010年7月10日土曜日 | Published in iOS 4.0, iPhone, UIKit | 2 コメント
[2010年10月19日訂正] [a][b]のケースでも deallocでの解放が必要なことが判明。それに適した記述に訂正してあります。
[関連情報] Cocoaの日々: viewDidUnload は呼ばれない(メモリ不足時だけ呼ばれる)
UIViewController 内で使うオブジェクトのメモリ管理(作成と開放のタイミング)と初期設定についてまとめてみた。
4種類のインスタンス変数をもつケースについて考えてみる。
[a] nibから生成されるコントロール
[b] 実行時に作成するコントロール
[c] 他クラスへの公開プロパティ
[d] 内部で使うインスタンス変数
ヘッダはこんな感じ。
続いて実装。メモリ管理にのみフォーカスして書いたコードなので意味のある処理は行っていない。
4種類のインスタンス変数が参照するオブジェクトはそれぞれの生成タイミングの違いから、開放タイミングも異なる。順に追って見る。
[作成] UIKitが自動的に nibから読み込んでメモリ内にインスタンス化する。その後 UIViewControllerのインスタンス変数へ設定してくれる。
[初期設定] 通常 InterfaceBuilderのインスペクタを使って設定を行うが、プログラムで初期設定を行う場合は viewDidLoadで行う。
[開放] viewDidUnload と dealloc の両方で行う。
コントロールの親viewはメモリ不足になると一旦開放されることがある。この為、UIViewControllerに比べて生存期間が短い上に、何度も作成と破棄が繰り返される場合が起こりうる。viewの開放タイミング(viewDidUnload)で、開放を行う。
[作成] viewと連携するコントロールの場合は、viewがインスタンス化して使えるようになっている必要がある。この為、作成は viewが使えるようになった時点、すなわち viewDidLoad で行う。
[初期設定] 作成と同様 viewDidLoad で行う。
[開放] viewDidUnLoad と dealloc の両方で行う。
作成を UIViewController の初期化タイミングで行うケースもある。この場合、開放は dealloc で行う。
[作成] 作成は UIViewController の初期化タイミングで行う。作成しないこともある(初期値 nilの場合など)。
[初期設定] 作成と同様。
[開放] dealloc で行う。
[作成] 作成は UIViewController の初期化タイミングで行う。そうでないとクラス内で利用できない。
[初期設定] 作成と同様。
[開放] dealloc で行う。
以上を表にまとめると次のようになる。
UINavigationBar を使っている場合は大抵の場合 init を使うので initで初期化を行う。一方、nibから UIViewController を作成する場合は initの代わりに initWithCoder: で初期化を行う(initや initWithNibName:bundle: は呼び出されない)。
メモリ不足になった場合に didReceiveMemoryWarning が呼ばれる。この時、インスタンス変数で大きなサイズのデータを扱っている場合は必要に応じて開放する。次の2つが対象。
[c] 他クラスへの公開プロパティ
[d] 内部で使うインスタンス変数
viewに紐付けられる [a][b]などは、viewが開放された時に viewDidUnload が呼び出されるのでそのタイミングで開放される(このブログで示した実装になっているとして)。
[関連情報] Cocoaの日々: viewDidUnload は呼ばれない(メモリ不足時だけ呼ばれる)
UIViewController 内で使うオブジェクトのメモリ管理(作成と開放のタイミング)と初期設定についてまとめてみた。
実装見本
4種類のインスタンス変数をもつケースについて考えてみる。
[a] nibから生成されるコントロール
[b] 実行時に作成するコントロール
[c] 他クラスへの公開プロパティ
[d] 内部で使うインスタンス変数
ヘッダはこんな感じ。
@interface SampleViewController : UIViewController {
UITextField* textField; // [a] nibから生成されるコントロール
UIImageView* imageView; // [b] 実行時に作成するコントロール
NSString* name; // [c] 他クラスへの公開プロパティ
NSMutableArray* history; // [d] 内部で使うインスタンス変数
}
@property (nonatomic, retain) IBOutlet UITextField* textField;
@property (nonatomic, retain) UIImageView* imageView;
@property (nonatomic, retain) NSString* name;
@end続いて実装。メモリ管理にのみフォーカスして書いたコードなので意味のある処理は行っていない。
#import "SampleViewController"
@implementation SampleViewController
@synthesize textField;
@synthesize name;
@synthesize imageView;
- (id)init
{
if (self = [super init]) {
// [c] 他クラスへの公開プロパティ [作成][初期設定]
self.name = @"no name";
// [d] 内部で使うインスタンス変数 [作成][初期設定]
history = [[NSMutableArray alloc] init];
}
return self;
}
- (void)viewDidLoad {
[super viewDidLoad];
// [a] nibから生成されるコントロール [初期設定]
self.textField.text = @"(non)";
// [b] 実行時に作成するコントロール [作成][初期化]
self.imageView = [[[UIImageView alloc]
initWithImage:[UIImage imageNamed:@"sample1.png"]] autorelease];
[self.view addSubview:self.imageView];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
self.textField.text = self.name;
}
- (void)viewDidUnload {
self.textField = nil; // [a] nibから生成されるコントロール [開放]
self.imageView = nil; // [b] 実行時に作成するコントロール [開放]
}
- (void)dealloc {
self.textField = nil; // [a] nibから生成されるコントロール [開放]
self.imageView = nil; // [b] 実行時に作成するコントロール [開放]
self.name = nil; // [c] 他クラスへの公開プロパティ [開放]
[history release]; // [d] 内部で使うインスタンス変数 [開放]
[super dealloc];
}
@end4種類のインスタンス変数が参照するオブジェクトはそれぞれの生成タイミングの違いから、開放タイミングも異なる。順に追って見る。
[a] nibから生成されるコントロール
[作成] UIKitが自動的に nibから読み込んでメモリ内にインスタンス化する。その後 UIViewControllerのインスタンス変数へ設定してくれる。
[初期設定] 通常 InterfaceBuilderのインスペクタを使って設定を行うが、プログラムで初期設定を行う場合は viewDidLoadで行う。
[開放] viewDidUnload と dealloc の両方で行う。
コントロールの親viewはメモリ不足になると一旦開放されることがある。この為、UIViewControllerに比べて生存期間が短い上に、何度も作成と破棄が繰り返される場合が起こりうる。viewの開放タイミング(viewDidUnload)で、開放を行う。
[b] 実行時に作成するコントロール
[作成] viewと連携するコントロールの場合は、viewがインスタンス化して使えるようになっている必要がある。この為、作成は viewが使えるようになった時点、すなわち viewDidLoad で行う。
[初期設定] 作成と同様 viewDidLoad で行う。
[開放] viewDidUnLoad と dealloc の両方で行う。
作成を UIViewController の初期化タイミングで行うケースもある。この場合、開放は dealloc で行う。
[c] 他クラスへの公開プロパティ
[作成] 作成は UIViewController の初期化タイミングで行う。作成しないこともある(初期値 nilの場合など)。
[初期設定] 作成と同様。
[開放] dealloc で行う。
[d] 内部で使うインスタンス変数
[作成] 作成は UIViewController の初期化タイミングで行う。そうでないとクラス内で利用できない。
[初期設定] 作成と同様。
[開放] dealloc で行う。
まとめ
以上を表にまとめると次のようになる。
| 種類 | init* | viewDidLoad | viewDidUnLoad | dealloc |
|---|---|---|---|---|
| [a] nibから生成される コントロール | - | [初期設定] | [開放] | [開放] |
| [b] 実行時に作成する コントロール | - | [作成] [初期設定] | [開放] | [開放] |
| [c] 他クラスへの 公開プロパティ | [作成] [初期化] | - | - | [開放] |
| [d] 内部で使う インスタンス変数 | [作成] [初期化] | - | - | [開放] |
メモリ開放の基本ルール
- nib 内にあるコントロールの開放は viewDidUnloadとdealloc両方で行う
- viewDidLoad で作成したコントロールの開放は viewDidUnloadとdealloc両方で行う
- init* で作成したインスタンスの開放は deallocで行う
- 他クラスから代入されたプロパティの開放は deallocで行う
その他
UIViewControllerの初期化
UINavigationBar を使っている場合は大抵の場合 init を使うので initで初期化を行う。一方、nibから UIViewController を作成する場合は initの代わりに initWithCoder: で初期化を行う(initや initWithNibName:bundle: は呼び出されない)。
メモリ不足
メモリ不足になった場合に didReceiveMemoryWarning が呼ばれる。この時、インスタンス変数で大きなサイズのデータを扱っている場合は必要に応じて開放する。次の2つが対象。
[c] 他クラスへの公開プロパティ
[d] 内部で使うインスタンス変数
viewに紐付けられる [a][b]などは、viewが開放された時に viewDidUnload が呼び出されるのでそのタイミングで開放される(このブログで示した実装になっているとして)。
参考情報
- Resource Programming Guide: Nib Files
- Nib経由でインスタンス化されるオブジェクトの初期化順序が解説されている。
- Cocoaの日々: UIView上のコントロールへの IBOutlet接続は retain で(assign改め)
- nib上のコントロールへの接続方法についての記事
登録:
コメントの投稿 (Atom)
人気の投稿(過去 30日間)
-
公式リファレンスの Q&A に解説がある。 Technical Q&A QA1551: Detecting the start and end edit sessions of a cell in NSTableView. 方法は Delegate と Not...
-
2011年06月09日 追記 UITableViewCell の Identifier 設定を忘れてたので追記しました。 UINib を使うと簡単に Nib で定義した UITableViewCell が使える。 今回のサンプル: [関連] Cocoaの日々: [iO...
-
Asset Catalogには画像以外のデータも置ける。サウンドファイル(.aif)を置いてみた。 取り出すには NSDataAsset を使う。 let sound = NSDataAsset(name: name) // use sound.data 取り出したサウ...
-
パスワードを暗号化して安全に iPhone/iPad へ保管したい。iOS はこの用途の為に Keychain Services を提供している。今回は Keychain Services について調べてみた。リファレンスの内容に加え、独自に調査・検証した結果をまとめてある。動作...
-
Core Data を使ったアプリケーションで下のような検索機能を実装している。 設定された値を元に NSPredicate を作成し、Core Data に対して検索をかけるのだが、こういう場合に NSCompoundPredicate が役に立つ。 NSCompound...
Responses
ぽげむた
2010年10月17日 10:32
分かりやすい記事をありがとうございます。とても納得できました。
ところで、[a]と[b]については、viewDidUnLoadのみで解放処理を行っていますが、viewDidUnLoadはメモリ不足の時に呼ばれると理解しています。ですので、deallocでも[a]と[b]の解放を行わないとメモリリークしてしまうのではないかと考えてしまうのですが、これは不要なのでしょうか?
ぽげむた
2010年10月17日 10:32
分かりやすい記事をありがとうございます。とても納得できました。
ところで、[a]と[b]については、viewDidUnLoadのみで解放処理を行っていますが、viewDidUnLoadはメモリ不足の時に呼ばれると理解しています。ですので、deallocでも[a]と[b]の解放を行わないとメモリリークしてしまうのではないかと考えてしまうのですが、これは不要なのでしょうか?
xcatsan says:
2010年10月19日 16:33
ぽげむたさん、こんにちは。
指摘の通り dealloc での解放が必要で、このままではメモリリークが発生します。後ほど記事を見なおして描き直しておきます。
# viewDidUnloadは毎回呼ばれるものと勘違いしていました。
# メモリリーク時しか呼ばれないことを今更ながら知ってちょっとショックをうけています。。
有益な情報ありがとうございました。
xcatsan says:
2010年10月19日 16:33
ぽげむたさん、こんにちは。
指摘の通り dealloc での解放が必要で、このままではメモリリークが発生します。後ほど記事を見なおして描き直しておきます。
# viewDidUnloadは毎回呼ばれるものと勘違いしていました。
# メモリリーク時しか呼ばれないことを今更ながら知ってちょっとショックをうけています。。
有益な情報ありがとうございました。
Leave a Response
[フレーム]