ラベル メモリ管理 の投稿を表示しています。 すべての投稿を表示
ラベル メモリ管理 の投稿を表示しています。 すべての投稿を表示

viewDidUnload は呼ばれない(メモリ不足時だけ呼ばれる)

2010年10月8日金曜日 | Published in | 2 コメント

このエントリーをはてなブックマークに追加

UIViewControllerの画面を閉じる時に通常 viewDidUnloadが呼び出されることは無い。このメソッドが呼び出されるのはメモリ不足の時のみ。名前が viewDidLoad と対になっているが、動作は対になっていない。


UIViewController の各種メソッド呼び出しタイミング


通常の動作
viewDidLoad
 |
 |閉じる
 ↓
dealloc

メモリ不足発生時の動作
viewDidLoad
 |
 |メモリ不足発生
 ↓
didReceiveMemoryWarning
 |
viewDidUnload
 |
 :
 ↓
viewDidLoad
 |
 |閉じる
 ↓
dealloc
てっきり通常 viewDidUnload が呼び出されると勘違いしていたがそうではない。
となると、メモリの解放は viewDidUnload だけでは駄目で dealloc にも実装しないとメモリリークが発生することになる。サンプルを作って確認してみた。


サンプル


RootとSub、SubSubの3つの UIViewController を用意して、SubViewController上の UIImageViewの retainCount を確認する。


RootViewController 上の childImageView プロパティは、SubViewControllerを閉じて deallocが呼ばれた後に retainCountがどうなっているかを確認する為に assign で参照している(assignだと retainCountは増えない)。


RootViewControllerのコード
@interface RootViewController : UIViewController {
 UIImageView* childImageView;
}

@property (nonatomic, assign) UIImageView* childImageView;

- (IBAction)next:(id)sender;

@end
- (void)printRetainCount
{
 NSLog(@"[Root] retainCount=%d", [self.childImageView retainCount]-1);
}

- (void)viewDidAppear:(BOOL)animated {
 
 if (self.childImageView) {
  [self performSelector:@selector(printRetainCount)
       withObject:nil
       afterDelay:1.0];
 }
}
- (IBAction)next:(id)sender
{
 SubViewController* vc = [[SubViewController alloc]
            initWithNibName:@"SubViewController" bundle:nil];
 vc.rootViewController = self;
 [self.navigationController pushViewController:vc animated:YES];
 [vc release];
}
SubViewControllerを閉じて RootViewController へ戻ってきた時に viewDidAppear: で retainCountを表示している。なお SubViewControllerの dealloc の後で確実に確認するために1秒間の遅延をもたせている。

次に SubViewController。
@class RootViewController;
@interface SubViewController : UIViewController {
 
 UIImageView* imageView;
 RootViewController* rootViewController;

}

@property (nonatomic, retain) IBOutlet UIImageView* imageView;
@property (nonatomic, retain) RootViewController* rootViewController;

-(IBAction)next:(id)sender;

@end

@implementation SubViewController
@synthesize imageView;
@synthesize rootViewController;

- (void)viewDidLoad {
    [super viewDidLoad];

 // for debug
 [self.imageView retain];

 NSLog(@"[Sub ] viewDidLoad|retainCount=%d", [self.imageView retainCount]-1);
 
 rootViewController.childImageView = self.imageView;
}

- (void)didReceiveMemoryWarning {
    NSLog(@"[Sub ] didReceiveMemoryWarning|retainCount=%d", [self.imageView retainCount]-1);
    [super didReceiveMemoryWarning];    
}

- (void)viewDidUnload {
    [super viewDidUnload];
 NSLog(@"[Sub ] viewDidUnload|retainCount=%d", [self.imageView retainCount]-1);
 self.imageView = nil;
}

- (void)dealloc {
    NSLog(@"[Sub ] dealloc|retainCount=%d", [self.imageView retainCount]-1);
    [super dealloc];
}

- (IBAction)next:(id)sender
{
 SubSubViewController* vc = [[SubSubViewController alloc]
           initWithNibName:@"SubSubViewController" bundle:nil];
 [self.navigationController pushViewController:vc animated:YES];
 [vc release];
}
@end
retainCount==0 のケースはメモリ解放後のオブジェクトになる為、実行時には retainCountメッセージを送るとアプリがクラッシュする。そこで -[SubViewController viewDidLoad]内で retain して+1水増ししておき、表示する箇所で−1して正しい数値に直している。

実行してみよう。まず通常のケース。
(1)pushViewController:SubViewController
(2)popViewController

実行結果
[Sub ] viewDidLoad|retainCount=2
[Sub ] dealloc|retainCount=2
[Root] retainCount=1
retainCount=2の内訳は [1]UIImageViewの親ビューによるretain [2]@property(retain)宣言とIBOutlet接続によるretainによる。また、viewDidUnload が呼ばれていないことがわかる。

一方、RootViewController へ戻った時に retainCountが1残っている。SubViewController は dealloc が呼ばれているので破棄されたことになるが、IBOutlet(retain)で接続した UIImageViewはメモリに残ったまま、つまりメモリリークが起きているのがわかる。これは dealloc 内で IBOutlet(retain)接続した UIImageViewの解放を行っていないため。viewDidUnloadで解放(=nil)しているが、このメソッドは通常は呼ばれない。

つまり IBOutlet(retain)接続したオブジェクトは deallocで解放しない場合メモリリークを引き起こす。

dealloc内に解放コードを追加してみる。
- (void)dealloc {
 NSLog(@"[Sub ] dealloc|retainCount=%d", [self.imageView retainCount]-1);
 
 self.imageView = nil;
    [super dealloc];
}

実行結果
[Sub ] viewDidLoad|retainCount=2
[Sub ] dealloc|retainCount=2
[Root] retainCount=0
retainCountは0となり、メモリリークが起きていない。


最後にメモリ不足をシミュレートしたケース (dealloc内解放コードあり)。

(1)pushViewController:SubViewController
(2)pushViewController:SubSubViewController
(3)メモリ不足をシミュレート
(4)popViewController
(5)popViewController

[Sub ] viewDidLoad|retainCount=2
Received simulated memory warning.
[Sub ] didReceiveMemoryWarning|retainCount=3
[Sub ] viewDidUnload|retainCount=1
[Sub ] viewDidLoad|retainCount=2
[Sub ] dealloc|retainCount=2
[Root] retainCount=0

メモリ不足発生直後に viewDidUnload が呼ばれているのがわかる。


奇妙なのが didReceiveMemoryWarningが呼ばれた時点で retainCountが3に増えている。これはなんだろう?

試しに Root => Sub => SubSub のケースをメモリ不足なしで実行してみた。
[Sub ] viewDidLoad|retainCount=2
[Sub ] dealloc|retainCount=3
[Root] retainCount=0
すると SubViewController の dealloc 時点では retainCountが3になっている。Sub => SubSub へ遷移することによって何故か retainCountが増加している。しかも Rootに戻った時はきちんと0にもどっている。

retainCount がどの時点で増えたか確認するため、SubSubController が表示された時点での retainCountを確認するコードを追加する。
@interface SubSubViewController : UIViewController {
 UIImageView* parentImageView;
}
@property (nonatomic, assign) UIImageView* parentImageView;


@end
- (void)viewDidLoad {
    [super viewDidLoad];
 NSLog(@"[SubSub] viewDidLoad|retainCount=%d", [self.parentImageView retainCount]-1);
  
}
- (void)viewWillAppear:(BOOL)animated {
 NSLog(@"[SubSub] viewWillAppear|retainCount=%d", [self.parentImageView retainCount]-1);
 
}
- (void)viewDidAppear:(BOOL)animated {
 NSLog(@"[SubSub] viewDidAppear|retainCount=%d", [self.parentImageView retainCount]-1);
 
}

SubViewControllerへ以下を追加
- (void)viewWillDisappear:(BOOL)animated
{
 NSLog(@"[Sub ] viewWillDisappear|retainCount=%d", [self.imageView retainCount]-1);
}
- (void)viewDidDisappear:(BOOL)animated
{
 NSLog(@"[Sub ] viewDidDisappear|retainCount=%d", [self.imageView retainCount]-1);
}
- (IBAction)next:(id)sender
{
          :
 [self.navigationController pushViewController:vc animated:YES];
 vc.parentImageView = self.imageView;
          :
}

実行結果
[Sub ] viewDidLoad|retainCount=2
[SubSub] viewDidLoad|retainCount=3
[Sub ] viewWillDisappear|retainCount=3
[SubSub] viewWillAppear|retainCount=3
[Sub ] viewDidDisappear|retainCount=3
[SubSub] viewDidAppear|retainCount=3
[Sub ] viewWillDisappear|retainCount=3
[Sub ] viewDidDisappear|retainCount=3
[Sub ] dealloc|retainCount=3
[Root] retainCount=0
SubSubViewControllerを pushした時点で retainCountが1増加している。理由はわからず。うーむ?
(わかる方、どうぞコメントに書き込んで教えて下さい)

メモリ不足シミュレート時の動作も記録として残しておく。
[Sub ] viewDidLoad|retainCount=2
[SubSub] viewDidLoad|retainCount=3
[Sub ] viewWillDisappear|retainCount=3
[SubSub] viewWillAppear|retainCount=3
[Sub ] viewDidDisappear|retainCount=3
[SubSub] viewDidAppear|retainCount=3
Received simulated memory warning.
[Sub ] didReceiveMemoryWarning|retainCount=3
[Sub ] viewDidUnload|retainCount=1
[Sub ] viewDidLoad|retainCount=2
[Sub ] viewWillDisappear|retainCount=2
[Sub ] viewDidDisappear|retainCount=2
[Sub ] dealloc|retainCount=2
[Root] retainCount=0


ソースコード


GitHubからどうぞ。
ViewControllerMemorySample at 2010-10-08b from xcatsan's iOS-Sample-Code - GitHub


まとめ


viewDidLoad で確保したメモリは、viewDidUnload および dealloc 両方で解放する

以前書いた記事では viewDidLoadで確保したメモリの deallocでの解放には触れていなかった。これは後日訂正する。
Cocoaの日々: UIViewController でのメモリ管理見本
解放コードのサンプルは(後日)そちらを参照されたい。


参考情報


viewDidUnload と dealloc でのメモリ解放の話題は調べてみると結構見つかって、多くの人が戸惑っている様子が伺える。

OBJECTIVE C - Release in viewDidUnload and dealloc both? - efreedom

When should I release objects in -(void)viewDidUnload rather than in -dealloc? - Stack Overflow

答えはどれも先のまとめで示した通りで両方でメモリ解放することが推奨されている。


- - - -
今回の件はかなりショックだった。過去のプログラムを見直せねば。。

release vs drain

2010年10月6日水曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

NSAutoreleasePool の release と drain の違いについて調査中。リファレンスによればガベージコレクションを使わない場合、両者は同じ働きをするとある。

リファレンスの drainの説明。

NSAutoreleasePool Class Reference より抜粋:
In a reference-counted environment, releases and pops the receiver; in a garbage-collected environment, triggers garbage collection if the memory allocated since the last collection is greater than the current threshold.

(ガベージコレクションを使わない iOS4 などの)reference-countな環境では両者は同じ動作をするとある。

Memory Management Programming Guide: Autorelease Pools の Garbage Collection でも同様の記述。
NSAutoreleasePool therefore provides a drain method that in a reference-counted environment behaves the same as calling release, but which in a garbage collected environment triggers garbage collection


参考情報など。

How does the NSAutoreleasePool autorelease pool work? - Stack Overflow

What's the difference between sending -release or -drain to an Autorelease Pool? - Stack Overflow



- - - -
releaseとdrain でメモリ解放の仕方に違いが出たケースを見たのだが原因がわからず。

NSAutoreleasePool と UIImage

2010年10月5日火曜日 | Published in | 2 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: NSAutoreleasePool を使ってメモリ解放

NSAutorelesaePool 調査中。今回は気になるところがあって UIImage を調べてみた。

検証コード


90〜100KB程度のJPEG画像を16枚用意して前回の様に NSAutoreleasePoolの効果を見る。

なお UIImage にはキャッシュを使う imageNamed: があるので、これとキャッシュを使わない imageWithContentsOfFile: の2つについてそれぞれ調べる。

コードはこんな感じ。
- (void)test2
{ 
 u_int prev_rss = [self currentRegidentSize];
 for (int i=1; i <= 16; i++) {
  
  NSString* filename = [NSString stringWithFormat:@"image%02d.jpg", i];


#ifdef USE_POOL
  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
#endif
  
#ifdef USE_CACHE
  [UIImage imageNamed:filename];
#else
  NSString* filepath = [NSString stringWithFormat:@"%@/%@",
     [[NSBundle mainBundle] resourcePath], filename];
  UIImage* image = [UIImage imageWithContentsOfFile:filepath];
  
#endif
  
#ifdef USE_POOL
#ifdef USE_DRAIN
  [pool drain];
#else
  [pool release];
#endif
#endif
  u_int rss = [self currentRegidentSize];
  NSLog(@"%@ | RSS: %0.1f KB => %0.1f KB [%+0.1fKB]",
    filename, prev_rss/1024.0,rss/1024.0, (int)(rss-prev_rss)/1024.0);
  prev_rss = rss;
 }
 NSLog(@"end");
 // NSLog(@"%@", [NSAutoreleasePool showPools]);
}


結果


NSAutoreleasePool あり/なしと、imageNamed: / imageWithContentsOfFile: の組み合わせ計4種類の結果。

NSAutoreleasePoolなし / imageNamed:

image01.jpg | RSS: 11792.0 KB => 11936.0 KB [+144.0KB]
image02.jpg | RSS: 11936.0 KB => 12252.0 KB [+316.0KB]
image03.jpg | RSS: 12252.0 KB => 12272.0 KB [+20.0KB]
image04.jpg | RSS: 12272.0 KB => 12288.0 KB [+16.0KB]
image05.jpg | RSS: 12288.0 KB => 12308.0 KB [+20.0KB]
image06.jpg | RSS: 12308.0 KB => 12328.0 KB [+20.0KB]
image07.jpg | RSS: 12328.0 KB => 12340.0 KB [+12.0KB]
image08.jpg | RSS: 12340.0 KB => 12356.0 KB [+16.0KB]
image09.jpg | RSS: 12356.0 KB => 12372.0 KB [+16.0KB]
image10.jpg | RSS: 12372.0 KB => 12392.0 KB [+20.0KB]
image11.jpg | RSS: 12392.0 KB => 12408.0 KB [+16.0KB]
image12.jpg | RSS: 12408.0 KB => 12424.0 KB [+16.0KB]
image13.jpg | RSS: 12424.0 KB => 12440.0 KB [+16.0KB]
image14.jpg | RSS: 12440.0 KB => 12456.0 KB [+16.0KB]
image15.jpg | RSS: 12456.0 KB => 12484.0 KB [+28.0KB]
image16.jpg | RSS: 12484.0 KB => 12504.0 KB [+20.0KB]
end

NSAutoreleasePoolなし / imageWithContentsOfFile:

image01.jpg | RSS: 11800.0 KB => 11916.0 KB [+116.0KB]
image02.jpg | RSS: 11916.0 KB => 12232.0 KB [+316.0KB]
image03.jpg | RSS: 12232.0 KB => 12248.0 KB [+16.0KB]
image04.jpg | RSS: 12248.0 KB => 12264.0 KB [+16.0KB]
image05.jpg | RSS: 12264.0 KB => 12280.0 KB [+16.0KB]
image06.jpg | RSS: 12280.0 KB => 12296.0 KB [+16.0KB]
image07.jpg | RSS: 12296.0 KB => 12312.0 KB [+16.0KB]
image08.jpg | RSS: 12312.0 KB => 12328.0 KB [+16.0KB]
image09.jpg | RSS: 12328.0 KB => 12340.0 KB [+12.0KB]
image10.jpg | RSS: 12340.0 KB => 12360.0 KB [+20.0KB]
image11.jpg | RSS: 12360.0 KB => 12372.0 KB [+12.0KB]
image12.jpg | RSS: 12372.0 KB => 12388.0 KB [+16.0KB]
image13.jpg | RSS: 12388.0 KB => 12404.0 KB [+16.0KB]
image14.jpg | RSS: 12404.0 KB => 12420.0 KB [+16.0KB]
image15.jpg | RSS: 12420.0 KB => 12424.0 KB [+4.0KB]
image16.jpg | RSS: 12424.0 KB => 12436.0 KB [+12.0KB]
end

NSAutoreleasePoolあり [release] / imageNamed:

image01.jpg | RSS: 11804.0 KB => 11952.0 KB [+148.0KB]
image02.jpg | RSS: 11952.0 KB => 12280.0 KB [+328.0KB]
image03.jpg | RSS: 12280.0 KB => 12296.0 KB [+16.0KB]
image04.jpg | RSS: 12296.0 KB => 12308.0 KB [+12.0KB]
image05.jpg | RSS: 12308.0 KB => 12328.0 KB [+20.0KB]
image06.jpg | RSS: 12328.0 KB => 12332.0 KB [+4.0KB]
image07.jpg | RSS: 12332.0 KB => 12340.0 KB [+8.0KB]
image08.jpg | RSS: 12340.0 KB => 12348.0 KB [+8.0KB]
image09.jpg | RSS: 12348.0 KB => 12360.0 KB [+12.0KB]
image10.jpg | RSS: 12360.0 KB => 12376.0 KB [+16.0KB]
image11.jpg | RSS: 12376.0 KB => 12392.0 KB [+16.0KB]
image12.jpg | RSS: 12392.0 KB => 12408.0 KB [+16.0KB]
image13.jpg | RSS: 12408.0 KB => 12424.0 KB [+16.0KB]
image14.jpg | RSS: 12424.0 KB => 12440.0 KB [+16.0KB]
image15.jpg | RSS: 12440.0 KB => 12460.0 KB [+20.0KB]
image16.jpg | RSS: 12460.0 KB => 12472.0 KB [+12.0KB]
end

NSAutoreleasePoolあり [release] / imageWithContentsOfFile:

image01.jpg | RSS: 11784.0 KB => 11912.0 KB [+128.0KB]
image02.jpg | RSS: 11912.0 KB => 12224.0 KB [+312.0KB]
image03.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image04.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image05.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image06.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image07.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image08.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image09.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image10.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image11.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image12.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image13.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image14.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image15.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
image16.jpg | RSS: 12224.0 KB => 12224.0 KB [+0.0KB]
end

キャッシュを使わない imageWithContentsOfFile: は前回同様、NSAutoreleasePoolの有無でメモリの解放が状況が変わる。一方、キャッシュが使われる imageNamed: の場合は NSAutoreleasePoolを使用して解放を行ってもメモリの利用量は変わらない。つまりデータがキャッシュに残っている。


ソースコード


GitHubからどうぞ。
xcatsanのプロフィール - GitHub


- - - - -

-[NSAutoreleasePool drain] のケースも確認したが違いはなし。別件で解放に違いが出ていた原因はなんだったのだろうか。

NSAutoreleasePool を使ってメモリ解放

2010年10月4日月曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

autorelease


autorelease を使ってオブジェクトを生成した場合、そのオブジェクトはランループ(イベント処理の周期)終了時に解放される。
(例) NSMutableArray* array = [NSMutableArray array];

通常はこの仕組で問題ないが、バッチ的な処理を1箇所で行なう場合に autoreleaeを使うと解放されない大量の autoreleae属性のオブジェクトが残ってしまう場合がある。
(例) for (i=0; i < 100; i++) {
    NSMutableArray* array = [NSMutableArray array];
      :
    時間のかかる処理
      :
}
これは、処理が終わるまでランループが終了しないので autoreleae属性のオブジェクトを解放するタイミングがこない為に起こる。
これを回避するには autorelease を使うのをやめて自前で releaseする。
(例) for (i=0; i < 100; i++) {
    NSMutableArray* array = [[NSMutableArray alloc] init];
      :
    時間のかかる処理
      :
    [array release];
}

もしくは NSAutoreleasePoolを局所的に作る。
(例) for (i=0; i < 100; i++) {
    NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
    NSMutableArray* array = [NSMutableArray array];
      :
    時間のかかる処理
      :
    [pool release];
}
こうするとランループでの解放を待たずに autorelese属性のオブジェクトを解放することができる。NSAutoreleasePool はインスタンスが作成されると、それ以降の autorelease属性のオブジェクトを覚えていて、自分が releaseされるタイミングでこれらのオブジェクトへ releaseを投げて解放する。

なお NSAutorelesePoolで管理される autorelease属性のオブジェクトの一覧はプライベートメソッドを使うと見ることができる。
NSLog(@"%@", [NSAutoreleasePool showPools]);
こんな感じ。
- -- ---- -------- Autorelease Pools -------- ---- -- -
==== top of stack ================
  0x620d860 (__NSArrayM)
  0x6014b20 (NSCFNumber)
  0x6019ae0 (__NSArrayI)
  0x6019a70 (__NSArrayM)
  0x6019b90 (__NSArrayI)
  0x6002f00 (__NSArrayM)
  0x6018030 (__NSArrayI)
  0x60109b0 (__NSCFDictionary)
     :
     :
==== top of pool, 102 objects ================
  0x60172d0 (__NSCFData)
  0x6013210 (__NSCFData)
  0x60130d0 (NSPathStore2)
  0x60130b0 (NSCFString)
  0x6012fd0 (NSCFString)
==== top of pool, 5 objects ================
==== top of pool, 0 objects ================

[参考情報] [NSAutoReleasePool][CFRunLoop] NSAutoReleasePoolの管理者は誰であるべきか - Ni chicha, ni limona - 平均から抜けられない僕 - iPhoneアプリ開発グループ


サンプル


効果を確認する為サンプルを作ってみた。1MBの NSDataを連続で100回作成した時のメモリ利用量を NSLog()でデバッグコンソールへ書き出す。
こんな感じ。
#define BUFF_SIZE (1024*1024)
static char buff[BUFF_SIZE];


- (void)test
{
 for (int i=0; i < 100; i++) {
  
  NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init];
  [NSData dataWithBytes:buff length:BUFF_SIZE];
  メモリ利用量表示処理
  [pool release];
 }
 NSLog(@"end");
}

(1)NSAutoreleaePool なしの場合、(2)ありの場合、(3)ありで drainを使った場合の3つを調べた。

(1) NSAutoreleasePoolなしの場合

AutoreleasePoolTest[54677:207] RSS: 13.5 MB
AutoreleasePoolTest[54677:207] RSS: 14.8 MB
AutoreleasePoolTest[54677:207] RSS: 15.8 MB
AutoreleasePoolTest[54677:207] RSS: 16.8 MB
AutoreleasePoolTest[54677:207] RSS: 17.8 MB
    :
    :
AutoreleasePoolTest[54677:207] RSS: 110.9 MB
AutoreleasePoolTest[54677:207] RSS: 111.9 MB
AutoreleasePoolTest[54677:207] RSS: 112.9 MB
AutoreleasePoolTest[54677:207] end
forループを1回るたびに 1MB増加しているのがわかる。

(2) NSAutoreleasePoolありの場合(release解放)

AutoreleasePoolTest[54700:207] RSS: 13.5 MB
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
    :
    :
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
AutoreleasePoolTest[54700:207] end
forループを繰り返しても一定のメモリ利用量から変化しない。ループ内で確保された NSDataはループの終わりできちんと解放されている。

(3) NSAutoreleasePoolありの場合(drain解放)

AutoreleasePoolTest[54700:207] RSS: 13.5 MB
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
    :
    :
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
AutoreleasePoolTest[54700:207] RSS: 13.8 MB
AutoreleasePoolTest[54700:207] end
release使用時と変わらず。


と、NSAutoreleasePool の効果がよくわかった。


メモリ利用量の取得


現在使用しているメモリ量は task_info() を使うと取得できる。
[参考情報] 自分のアプリが使用しているメモリサイズを取得するには - The iPhone Development Playground

今回のコードはこんな感じ。
struct task_basic_info t_info;
  mach_msg_type_number_t t_info_count = TASK_BASIC_INFO_COUNT;
  
  if (task_info(current_task(), TASK_BASIC_INFO,
       (task_info_t)&t_info, &t_info_count)!= KERN_SUCCESS) {
   NSLog(@"%s(): Error in task_info(): %s",
      __FUNCTION__, strerror(errno));
  }
  
  u_int rss = t_info.resident_size;  
  NSLog(@"RSS: %0.1f MB", rss/1024.0/1024.0);


ソースコード


GitHubからどうぞ。
AutoreleasePoolTest at 2010-10-04 from xcatsan's iOS-Sample-Code - GitHub


参考情報


[NSAutoReleasePool][CFRunLoop] NSAutoReleasePoolの管理者は誰であるべきか - Ni chicha, ni limona - 平均から抜けられない僕 - iPhoneアプリ開発グループ

自分のアプリが使用しているメモリサイズを取得するには - The iPhone Development Playground

NSAutoreleasePool Class Reference

Memory Management Programming Guide: Autorelease Pools
- - - -
release と drain で挙動が違う現象に遭遇した。その為、今回サンプルを作って確認してみたのだがやはり差異はなかった。うーむ。

UIView上のコントロールへの IBOutlet接続は retain で(assign改め)

2010年6月29日火曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

[前回] Cocoaの日々: UIView上のコントロールへの IBOutlet接続は assignで

コメントで指摘を受け考え直した。
前回 assign と書いたが、これを改め retain にする。


見本


UIViewController でコントロールへの IBOutletを作成する時の見本
@property (nonatomic, retain) IBOutlet UITextField* textField;

あわせて viewDidUnload で開放する。

- (void)viewDidUnload {
    self.textField = nil;
}

この2つはセットで使う。


理由


iPhoneの場合メモリ不足などが発生した場合、UIViewControllerが使用している UIView が開放される場合がある。前回の assign の場合、明示的に開放しなくても親ビューの開放と共にその上のコントロールも開放されるのでメモリリークの観点からは問題ない。しかし、UIViewの開放は意図しないタイミングで起こるために、誤って親ビューの解放後にコントロールへアクセスすることが起きうる。この場合、親ビューの開放に伴ってコントロールも開放されているので例外が出てクラッシュしてしまう(開放済みのオブジェクトへのアクセス)。また場合によっては親ビューの突然の開放があった場合でも入力中の文字列を退避したいケースがあるかもしれない。これらの理由により、コントロールのメモリ管理のオーナーシップを自分で持ってコントロールした方が良いと思われる。


参考


Xcodeが生成するテンプレートのコメントにも IBOutlet接続された subviewsの開放の件は書いてありますな。
- (void)viewDidUnload {
 // Release any retained subviews of the main view.
 // e.g. self.myOutlet = nil;


ドキュメントにも。
Memory Management Programming Guide: Memory Management of Nib Objects

UIView上のコントロールへの IBOutlet接続は assignで

| Published in | 2 コメント

このエントリーをはてなブックマークに追加

[関連]Cocoaの日々: UIKit コントロールの View構成と retainCount

(追記)前提条件が抜けていました。UIViewController での話です。

(追記 6/29)assign はやめて retain にしました。下記を参照のこと。
Cocoaの日々: UIView上のコントロールへの IBOutlet接続は retain で(assign改め)


※以下、内容が古くなってます。


プロパティ見本


@property (nonatomic, assign) IBOutlet UILabel* label;
多分これでいいと思う。dealloc時の開放は不要。親の UIViewが Ownershipを持つので、親が relelaseされるタイミングで relelaseされる為。


retainしたら


もし retain した場合、dealloc の他、親 UIView が破棄されるタイミングで releaseが必要になる。後者は didReceiveMemoryWarning が該当する。※通常 retain は不要。


参考


Memory Management Programming Guide: Memory Management of Nib Objects
ここで言う Top-Level Object とは Interface Builder のウィンドウに表示されるレベルのオブジェクトを指す。これは通常 Super View を持たない。
UIView 上に配置されたコントロールはこれに該当しない。

- - - -
結局検証せず。多分これでいいと思うが..

UIKit コントロールの View構成と retainCount

2010年6月27日日曜日 | Published in | 0 コメント

このエントリーをはてなブックマークに追加

Nib利用時のメモリ管理について調査中。

こんなビューを作り、View構成とそれぞれのViewの retainCountを出してみた。

"display RetainCount" を押した時の処理(UIViewController内)
- (void)displayRetainCountOfView:(UIView*)view level:(int)level
{
 NSMutableString* indent = [NSMutableString string];
 for (int i=0; i < level; i++) {
  [indent appendString:@"  "];
 }

 for (UIView* subview in [view subviews]) {
  NSLog(@"%@[%@ retainCount]: %d", indent,
     [subview class], [subview retainCount]);
  if ([[subview subviews] count] > 0) {
   [self displayRetainCountOfView:subview level:level+1];
  }
 }
}

-(IBAction)display:(id)sender
{
 NSLog(@"[self.view  retainCount]: %d", [self.view retainCount]);
 [self displayRetainCountOfView:self.view level:0];
 
}

再帰的に回してサブビューも含めて表示している。

結果はこう。
[self.view  retainCount]: 4
[UILabel retainCount]: 2
[UIRoundedRectButton retainCount]: 7
  [UIButtonLabel retainCount]: 3
[UISwitch retainCount]: 2
  [_UISwitchSlider retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIView retainCount]: 3
      [UILabel retainCount]: 3
      [UILabel retainCount]: 3
    [UIImageView retainCount]: 3
[UISlider retainCount]: 2
  [UIImageView retainCount]: 3
  [UIImageView retainCount]: 3
  [UIImageView retainCount]: 3
[UIProgressView retainCount]: 2
[UITableView retainCount]: 2
  [UIImageView retainCount]: 3
  [_UITableViewSeparatorView retainCount]: 3
  [_UITableViewSeparatorView retainCount]: 3
  [_UITableViewSeparatorView retainCount]: 3
  [UIImageView retainCount]: 3
[UISegmentedControl retainCount]: 2
  [UISegment retainCount]: 3
    [UISegmentLabel retainCount]: 3
    [UIImageView retainCount]: 2
  [UISegment retainCount]: 3
    [UISegmentLabel retainCount]: 3
    [UIImageView retainCount]: 2
[UIRoundedRectButton retainCount]: 2
  [UIButtonLabel retainCount]: 3
[UITextField retainCount]: 2
  [UITextFieldRoundedRectBackgroundView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
    [UIImageView retainCount]: 3
[UIImageView retainCount]: 2

メインのビューに配置したコントロールの retainCount は 2となっている(一つはsuperviewによるもので、もう一つは不明)。
"diplay retainCount" ボタンのみ retainCountが 7となっているのがわかる。action-targetで接続するとその管理下におかれ、増えているのだと思われる。
[UIRoundedRectButton retainCount]: 7

- - - -

基本的に UIViewController.view 上に配置されたコントロールは、その UIViewController が破棄された時に UIViewController.view が release され、芋づる式にすべて開放(release)される。この為、これらのコントロールを UIViewController 内で使う場合、メモリのオーナーシップは考えなくて良いはず。
(例) @propety (nonatomic, assign) IBOutlet UILabel* label;

逆に reatain してしまった場合、その UIViewController がメモリ解放の責任の一旦を担わなければならない。
(例) @propety (nonatomic, retain) IBOutlet UILabel* label;

開放のタイミングは dealloc 以外に、UIViewController の場合はその生存期間と viewのそれとが異なるのでそれを考慮する必要がある。具体的にはメモリ不足が発生すると didReceiveMemoryWarning が呼ばれ、そのタイミングで viewが開放される。retainしている場合はこの時に開放しないと UIViewControllerのOultet参照が残ったままになってしまう。
(ただ、その後 viewの再生成で再び Outletが再設定されるので、その時に古い参照が開放される。なので viewの破棄・再作成毎に発生するメモリリークは起こっても限定的?)

。。。この辺りを現在検証中。

人気の投稿(過去 30日間)