● Carbon視点でiPhone探求(2010/03/15)

  この記事は、MOSAの会員にのみ読むことができるデベロッパー向けの
  ウェブサイトMOSADeN Onlineのに掲載された記事です。ほぼ一ヶ月
  遅れでここに掲載されて行きます

  〜 アプリケーションを再構成する 〜


今回は、前回に追加したSymmetryViewへの描画処理に伴う他のクラスの修正個所を解説します。また、画像を扱う場合のメモリ管理についての重要ポイントについてもお話します。

まず最初に、ImageViewControllerクラスの変更です。以前までのインスタンス変数の定義とオーバライドしたviewDidLoadメソッドは以下の通りでした。

@interface ImageViewController : UIViewController
{
  IBOutlet UIImageView     *im_view; // イメージビュー
  IBOutlet UIBarButtonItem   *im_save; // 保存ボタン
  IBOutlet UISegmentedControl *im_type; // タイプグメント
}
@property(nonatomic,assign)UIImageView *im_view;

@synthesize im_view;

- (void)viewDidLoad
{
   [super viewDidLoad];
  self.navigationItem.rightBarButtonItem=im_save; // 保存ボタン追加
  im_type.tintColor=[UIColor grayColor]; // タイプセグメントカラー
}

これを以下のように変更しました。

@interface ImageViewController : UIViewController
{
  IBOutlet SymmetryView    *im_view; // 対称イメージビュー
  IBOutlet UIBarButtonItem   *im_save; // 保存ボタン
  IBOutlet UISegmentedControl *im_type; // タイプグメント
  Model            *im_model; // 操作対象モデルオブジェクト
}
@property(nonatomic,assign)SymmetryView    *im_view;
@property(nonatomic,assign)UIBarButtonItem   *im_save;
@property(nonatomic,assign)UISegmentedControl *im_type;
@property(nonatomic,assign)Model        *im_model;


#import "SymmetryView.h"
#import "Model.h"

@synthesize im_view,im_save,im_type,im_model;

- (void)viewDidLoad
{
  [super viewDidLoad];
  im_view.sy_image=[im_model loadImage]; // 画像を読み込みセットする
  self.navigationItem.rightBarButtonItem=im_save; // 保存ボタン追加
  im_type.tintColor=[UIColor grayColor]; // タイプセグメントカラー
}

今まで利用していたUIImageViewの代わりに、SymmetryViewをIBOutletとして定義しています。そして、そのビューに画像をセットするために必要なModelオブジェクトをインスタンス変数に保存できるようにします。両クラスを利用するために#importするヘッダーファイルも2つ追加します。また、すべてのインスタンス変数のプロパティをassign定義にします。今回の処理では特別retainしておく理由もありませんし、こうすることでdeallocメソッドでこれらを解放する必要がなくなります。IBOutlet定義の各オブジェクトは、このビューコントローラが消去(解放)される時に同時に解放されます。

新しいviewDidLoadメソッドでは、SymmetryViewのsy_imageプロパティにModelオブジェクトから得られた画像ファイルを読み込みUIImageとして代入します。この処理の詳細については後述いたします。続いて、このImageViewControllerを表示させているRootViewControllerクラスのUITableViewデリゲートメソッド側の変更です。今まではこうでした。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath // 行がタップされた場合
{
  Model         *model;
  UIImage        *image;
  SymmetryAppDelegate *app;

  if( model=[rt_array objectAtIndex:indexPath.row] ) // Modelを得る
  {
    if( image=[model loadImage] ) // UIImageを得る
    {
      app=[[UIApplication sharedApplication] delegate]; // アプリケーションデリゲート
      [app pushImageViewControllerWithImage:image]; // イメージビューをオープン
    }
  }
}

これを以下のように変更します。メソッド内での画像読み込みは行わず、行のタップで選択されたModelオブジェクトだけをpushImageViewControllerWithModel:メソッドに渡すようにします。

- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath // 行がタップされた場合
{
  Model         *model;
  SymmetryAppDelegate *app;

  if( model=[rt_array objectAtIndex:indexPath.row] ) // Modelを得る
  {
    app=[[UIApplication sharedApplication] delegate]; // アプリケーションデリゲート
    [app pushImageViewControllerWithModel:model]; // イメージビューをオープン
  }
}

Modelオブジェクトを渡すメソッドは、以前はpushImageViewControllerWithImage:という名称でした。この時は、一度nibファイルからビューコントローラを得たら、そのままインスタンス変数に保存し(メモリ内に常駐)、2回目からはそれを再利用していました。これは、メモリ使用量の削減よりも処理スピードを上げる方を優先した処理内容となります。

- (void)pushImageViewControllerWithImage:(UIImage *)image
{
  if( ! ap_imagevctr )  // イメージビューコントローラインスタンス変数
    ap_imagevctr=[[ImageViewController alloc] initWithNibName:@"ImageViewController" bundle:nil];
  if( ap_imagevctr )
  {
    [navigationController pushViewController:ap_imagevctr animated:YES];
    ap_imagevctr.im_view.image=image;
  }
}

今回は、メモリ使用量削減を優先した方法へ変更します。こちらですと、表示メソッドが呼ばれる度にImageViewControllerがnibファイルから読み込まれます。この場合、pushViewController:は引き渡すビューコントローラのretainカウントを1つ増やします(つまり2になる)。よって、すぐにreleaseします。これでビューコントローラの所有権はナビゲーションコントローラへ移り、その制御によりビューコントローラが消された時に、dealloメソッドが呼ばれてその分の使用メモリが解放されることになります。

iPhone 3GSなら前回までの方法でほとんど問題ないのですが、3Gの方は使用可能メモリが極端に少なくなるケースがありますので、ターゲットデバイスの状況によってはこちらの方法がお勧めだと思われます。

- (void)pushImageViewControllerWithModel:(Model *)model
{
  ImageViewController *imagevctr;

  if( imagevctr=[[ImageViewController alloc] initWithNibName:@"ImageViewController" bundle:nil] )
  {
    imagevctr.im_model=model;
    [navigationController pushViewController:imagevctr animated:YES];
    [imagevctr release];
  }
}

他のビューコントローラ( DetailViewControllerや AboutViewController)についても同様な処理を施します。これに伴い、SymmetryAppDelegate.hで定義していた以下のプロパティをすべて削除します。

DetailViewController  *ap_detailvctr; // 詳細ビューコントローラ ImageViewController  *ap_imagevctr; // イメージビューコントローラ
AboutViewController  *ap_aboutvctr; // アバウトビューコントローラ

続いて、設定されているパス情報からJPEG画像を得るModelクラスのloadImageメソッドの変更です。今までは以下の通りでした。

- (UIImage *)loadImage
{
  NSString  *str,*name,*path;
  UIImage  *image;

  str=[NSString stringWithFormat:@"Photo-%d",md_id];
  name=[str stringByAppendingString:@".jpg"];
  path=getDocumentPath( name );
  image=[[UIImage alloc] initWithContentsOfFile:path];
  return image;
}

今回、以下のように変更します。大変微妙な変更ですが、initWithContentsOfFile:で画像(UIImage)オブジェクトを作成した後にautoreleaseを行います。こうすることで、このクラスはそのオブジェクトの所有権を放棄したことになります。

- (UIImage *)loadImage
{
  NSString *str,*name,*path;
  UIImage  *image;

  str=[NSString stringWithFormat:@"Photo-%d",md_id];
  name=[str stringByAppendingString:@".jpg"];
  path=getDocumentPath( name );
  image=[[[UIImage alloc] initWithContentsOfFile:path] autorelease];
  return image;
}

その後、この画像オブジェクトはSymmetryViewのsy_imageプロパティに代入されてretainされますので、所有権がSymmetryViewに移動したことになります。

  im_view.sy_image=[im_model loadImage]; // 画像を読み込む

つまりオブジェクト解放の責任はSymmetryViewが所有します。もし、先ほどの処理でautoreleaseされていないと、ビューコントローラの消去でSymmetryViewが解放(dealloc)された時に、sy_imageプロパティのretainカウントはゼロとはならず、メモリーリークが起こります。この場合リーク対象が画像データですので大容量メモリーリークとなり、何度も画像表示を繰り返しているうちにアプリが落ちるという現象を引き起こします。ご注意ください。

次回は、いよいよSymmetryView上での対称表示処理にチャレンジしてみます。ユーザインターフェースとしてのタッチイベントの仕組みも同時に解説いたします。

copyright 2010 Ottimo, Inc. All rights reserved
無断転載・引用禁止