Objective-Cのデザインパターン

クラスクラスタ

あるスーパークラスから派生したサブクラスが多数存在する場合、それぞれのサブクラスをダイレクトに利用しようとするとクラスの数が膨大になり利用しにくい。そこでサブクラスを非公開とし、サブクラスへのアクセスは公開されたスーパークラスへのアクセスによって実現する。これがクラスクラスタである。サブクラスはスーパークラスより生成されるが非公開であるため、外からはスーパークラスのインスタンスとして見える。

クラスクラスタに属するサブクラス

クラスクラスタは簡明さと拡張性のトレードオフである。拡張を必要とする場合はサブクラスを別途作成する必要がある。サブクラスを定義する際は以下のように作成する。

  • イニシャライザを全てオーバライドする
  • プリミティブメソッド(最低限実装しなければならないメソッド)をオーバライドする

他の派生メソッドはオーバライドしなくても使用することができるが、挙動を変更する場合はオーバライドして実装する。

ファクトリメソッド

ファクトリーメソッドとはクラスメソッド、つまりインスタンス化することなく使用することのできるメソッドである。

アウトレット

(ViewControllerなどの)オブジェクトのプロパティ(パーツ)のうち、(Storyboardなど)ほかのオブジェクトを参照するもの。Storyboadを読み込む際に接続が確立しなおされる。メモリ管理の観点より、他の設定項目と同様にクラスに含めるべき項目なのかどうか検討が必要である。

@propety(weak, nonatmic) IBOutlet UILabel *label;

オブジェクトの割り当てと初期化

オブジェクトは、alloc()メソッドにより仮想メモリ領域からオブジェクトを格納するために必要なメモリ量の割り当てを受け、このあとinit()メソッドによりインスタンス変数を初期化する。シングルトンインスタンスの場合、新たなインスタンスの生成を要求されても、既存のインスタンスを返すような初期化処理を実装しておく必要がある。返り値がnilの場合、初期化に失敗したことを示す。

UIView

アクションシート

デリゲートの記述を行う。

@interface MyViewController : UIViewController<UIActionSheetDelegate>{

アクションシートの初期化と貼付けを行う。初期化の際にデリゲートの宣言をする。

// 初期化(+デリゲートの設定)
UIActionSheet *aSheet = [[UIActionSheet alloc]
initWithTitle:@"選択項目"
delegate:self
cancelButtonTitle:@"キャンセル"
destructiveButtonTitle:nil
otherButtonTitles:@"ボタン1", @"ボタン2", @"ボタン3",nil];
// アクションシートのスタイルを指定
[aSheet setActionSheetStyle:UIActionSheetStyleDefault];
// ビューに貼付ける
[aSheet showInView:[self view]];
// メモリの解放
[aSheet release];

デリゲートメソッド内で処理を記述する

- (void)actionSheet:(UIActionSheet *)actionSheet clickedButtonAtIndex:(NSInteger)buttonIndex {
}

NavigationController

Xcodeのプロジェクト設定でNavigationControllerを選択した場合、RootViewController.xibとMainWindow.xibが作成される。MainWindowではUINavigationControllerが読み込まれており、このUINavigationControllerの中に上部のNavigation Barと下部のRootViewControllerがある。下部のRootViewControllerは、UITableViewControllerを継承して作成されており、テーブル状のメニュー画面を作成することが出来る。

この画面をタッチすると別のサブウインドウに移動できるようにするためには、

// NIBファイルを指定してビューコントローラを定義する
DetailViewController *dvc = [[DetailViewController alloc] initWithNibName:@"DetailViewController" bundle:nil];
// メニューを押すと上記ビューコントローラを表示する
[self.navigationController pushViewController:dvc animated:YES];
[dvc release];

とする。

イメージの取得と保存

イメージを取得する

カメラやアルバムからイメージを取得するにはUIImagePickerControllerを使用する。

// 使用できるか出来ないかの判定は、isSourceTypeAvailableメソッドを使う
if ( [UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary] ) {
	// UIImageControllerの初期化
	UIImagePickerController *ipc = [[UIImagePickerController alloc] init];
	/* UIImageControllerで取得できるデータは以下の3つ
	 *
	 * SourceTypeCamera             : カメラを起動する
	 * SourceTypePhotoLibrary       : フォトライブラリーを開く
	 * SourceTypeSavedPhotosAlbum   : (カメラがあるiPhoneは)カメラロールから選択する
	 */
	[ipc setSourceType:UIImagePickerControllerSourceTypePhotoLibrary];
	// Delegateをセット
	[ipc setDelegate:self];
	// NOを設定するとイメージを取得できない
	[ipc setAllowsEditing:YES];
	// 指定したViewを一番上に表示する
	[self presentModalViewController:ipc animated:YES];
	// 表示してしまった後はメモリから消去してよい
	[ipc release];
}

イメージを表示する

UIImagePickerControllerのイメージを取得した後に呼び出されるデリゲート関数(didFinishPickingMediaWithInfo)に、取得イメージをImageViewに表示する処理を追記する。UIImagePickerControllerをデリゲートする場合は、UIImagePickerControllerDelegateとUINavigationControllerDelegateをヘッダファイルに記述する必要がある。

- (void)imagePickerController:(UIImagePickerController *)picker didFinishPickingMediaWithInfo:(NSDictionary *)info {
	// 選択したイメージをUIImageにセットする
	UIImage *image = [info objectForKey:UIImagePickerControllerEditedImage];
	// UIImageをImageViewerにセットする
	[imageView setImage:image];
	// [picker parentViewController]の部分はselfでも良い?
	[[picker parentViewController] dismissModalViewControllerAnimated:YES];
}

イメージを保存する

ImageViewに表示されたイメージをフォトアルバムに保存する。イメージを端末内に保存される前にアプリを終了してしまうと、サムネイルだけが保存されイメージが保存されないので、保存終了後にアラートを表示させるようにする。

UIImage *image = [aImageView image];
if ( image != nil ) {
	// イメージを保存する
	UIImageWriteToSavedPhotosAlbum( image, self, @selector(image:didFinishSavingWithError:contextInfo:), nil );
}

アラート表示部分は以下のように記述する。

// アラートを表示させる部分は、image:didFinishSavingWithError:contextInfo:という書式が必須らしい
- (void)image:(UIImage *)image didFinishSavingWithError:(NSError *)error contextInfo:(void *)contextInfo {
	// アラートを表示する
	UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"保存終了" message:@"画像を保存しました" delegate:nil cancelButtonTitle:@"OK" otherButtonTitles:nil];
	[alert show];
	[alert release];
}

アプリ内部(アプリのホームディレクトリ内)に画像を保存する場合は、以下のように記述する。

UIImage *image = [aImageView image];
if ( image != nil ) {
	// 保存ディレクトリを指定
	NSString* docDir = [NSHomeDirectory() stringByAppendingPathComponent:@"Documents"];
	// 保存ディレクトリ+ファイル名を指定
	NSString* photoFilePath = [NSString stringWithFormat:@"%@/photo.jpg", docDir];
	// 圧縮率を指定しJPEGファイルで保存する
	[stringWithFormat(image, 0.7f) writeToFile:photoFilePath atomically:YES];
}

保存ディレクトリの指定には、文字列追加を行うことのできるstringByAppendingPathComponentメソッドを使用する。またファイル名の指定には、フォーマットを指定した文字列記述のできるstringWithFormatを使用する。最後にUIImageJPEGRepresentatio関数によって画像データをNSデータ型に変換したあと指定した保存先に保存を行う。

デリゲートとは

デリゲートとは、本来他のクラスのメソッドを用いて処理すべき内容を自分のクラス内で処理できるように、他のクラスのメソッドを自分のクラス内で呼び出して利用することを言う。例えば、ボタンを押せば録音を行うというプログラムで、録音が終了した際にひと手間処理を入れたい場合、本来であれば録音を行うクラスの録音終了メソッド内で処理が行われるが、デリゲートを宣言することでViewController上でこの録音終了処理を行うことが出来る。つまり、本来他のクラスがやるべき仕事を委譲されて自分自身で処理できるようになる。

まず、

@interface MyViewController : UIViewController <AVAudioRecorderDelegate>
{

と書くとこのViewControllerでAVAudioRecorderを代行すると宣言したことになる。次に処理を代行する部分をプログラムに明示する。例えば、

// 録音を行うクラス
AVAudioRecorder *recorder = [[AVAudioRecorder alloc] initWithURL:soundURL settings:nil error:&error];
// 各処理を記述
// self=MyViewControllerにrecorderのデリゲートをセットする
[recorder setDelegate:self];

と書くと、recorderが行う処理の一部をMyViewControllerが代行することが出来る。代行する部分の処理は、

- (void)audioRecorderDidFinishRecording:(AVAudioRecorder *)recorder successfully:(BOOL)flag {
}

などの部分に記述する。各クラスはどのメソッドをデリゲートで使って良いか定義している。デリゲートしたクラスのうち使用出来るメソッドのみ処理を代行できる。

画面の向きを固定する

info.plistを編集する

info.plistにinitial interface orientationの項目を追加し、起動時の画面の向きを設定する。

  • Portrait (bottom home button)
  • Portrait (top home button)
  • Landscape (left home button)
  • Landscape (right home button)

ステータスバーを表示させない場合は、status bar is initialy hiddenの項目も追加する。

IBでUIの構築を行う

Viewの右上の矢印をクリックするとViewの向きを変えることが出来る。横向きの画面を作成する場合は、Viewの向きを変えてから各パーツを貼付ける。ステータスバーを表示させない場合はViewのAttributesを開き、status barをNoneとする。ついでにViewの画面サイズも480×320に変更する。

画面の向きを固定する

UIViewControllerで定義されているshouldAutorotateToInterfaceOrientationというメソッド(コメントアウトされた状態で記述されている)を使い回転を許可する向きを定めることが出来る。固定したい向きを記述することでその向きのときだけ回転する、すなわちその向きに画面が固定される。

// 現在の向きがあらかじめ規定した向きと合致するとYESを返す
- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation {
	// YESを返すときのみ回転を許可する
	return (interfaceOrientation == UIInterfaceOrientationLandscapeRight);
}

InterfaceBuilderとイベント処理

IBで作成したオブジェクトを参照する

プログラムは、IBOutletを宣言したインスタンス変数(アウトレット)を通して、IBで作成したオブジェクトを参照することができる。プログラム側のヘッダファイルで以下のようにアウトレットを宣言する。

#import <UIKit/UIKit.h>

@interface DigitalClock1ViewController : UIViewController {
	// インスタンス変数の宣言
	UILabel *label;
}
@property(nonatomic, retain) IBOutlet UILabel *label;
@end

@propertyで記述されたアクセサに関しては、@implementation部に以下の記述が必要。

@synthesize label;

アウトレットがIB上のどのパーツを参照するのかはInterfaceBuilderで規定する。File’s Ownerを右クリックして先ほど宣言したアウトレットを選択し、これをView上の任意のパーツまでドラッグすることで、アウトレットとパーツとの参照関係が規定される。

オブジェクトに対するアクションを受け取る

ボタンが押された等のアクションをプログラム側で受け取る場合は、アクションを受け取るメソッドにIBActionを宣言する。プログラム側のヘッダファイルで以下のようにIBActionメソッドを宣言する。

#import <UIKit/UIKit.h>

@interface DigitalClock1ViewController : UIViewController {
}

- (IBAction) doKeyDown: (id) sender;

@end

また、@implementationにてメソッドを実装する。

- (IBAction) doKeyDown: (id) sender{
	// sender tagでアクションの発生したオブジェクトを特定することが可能
	NSLog:@"%d", [sender tag];
}

IBActionがIB上のどのパーツのアクションを受け取るのかはInterfaceBuilderで規定する。File’s Ownerを右クリックして先ほど宣言したメソッドを選択し、これをView上の任意のパーツまでドラッグすることで、IBActuionとパーツとの参照関係が規定される。

現在時刻を表示する

タイマーを用いて定期的に実行する

定期的にメソッドを実行するにはNSTimerクラスを利用する。

NSTimer *timer;

まずヘッダファイルでタイマーを定義する。タイマー処理はscheduledTimerWithTimeIntervalを用いる。

timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(tick:) userInfo:nil repeats:YES];

引数は、①呼び出し間隔(秒)②タイマー発火時に呼び出すメソッドのあるオブジェクト③タイマー発火時に呼び出すメソッド④タイマー発火時に呼び出すメソッドに渡す情報⑤繰り返しをするか否かの5項目。@selectorは、メソッド名を記述することでそのメソッドを呼び出すことができる。Objective-Cでは、Selectorを用いることで実行時に動的に呼び出すメソッドを変更できるとのこと。

- (void)viewDidUnload {
	[timer invalidate];
	timer = nil;
}
- (void)dealloc {
	if ( timer)
	[timer invalidate];
	[super dealloc];
}

なおタイマーを停止する際は、invalidateメソッドを使用する。invalidateはreleaseまでしてくれる。

現在時刻を表示する

最後に定期的に呼び出すメソッド内に現在の時刻を取得し、必要な形に整形する処理を記述する。

- (void)tick:(NSTimer *)theTimer {
	// 初期化(init)し現在の時刻を取得する
	NSDate *date = [NSDate date];
	// NSDateFomatter=NSDateの値を整形する
	NSDateFormatter *formatter = [[NSDateFormatter alloc] init];
	[formatter setLocale:[NSLocale currentLocale]];
	// 年月日曜日を全て表示する
	[formatter setDateStyle:NSDateFormatterFullStyle];
	// 時分秒タイムゾーンを全て表示する
	[formatter setTimeStyle:NSDateFormatterFullStyle];
	// Date型からString型に置換する
	NSString *dateStr = [formatter stringFromDate:date];
	// allocしたformatterをreleaseする
	// なおClassObjectである*dateは自動的にメモリ解放されるので記述は不要
	[formatter release];
}

引数のtheTimerは、呼び出し元のタイマー自身を示す。

SyntaxHighlighter+MovableType

SyntaxHighlighterをダウンロード

http://blog.makotokw.com/portfolio/movabletype/syntaxhighlighter/よりプラグインをダウンロードする。zip形式なので解凍。

Movable Type上に配置

解凍するとmt-static, pluginsの2つのファイルが生成されるので、MovableType上の同じ位置にコピーする。
+ plugins\SyntaxHighlighter
+ mt-static\plugins\SyntaxHighlighter

プラグインの修正

MovableTypeの[デザイン]->[テンプレート]から適当なテンプレートを選択し、HTMLのヘッダ部分に以下のタグを埋め込む。タグをヘッダに記述することで、各フォーマットに合わせた表示部分のjavascriptやcssを呼び出す記述がHTMLに加えられる。

<$mt:SyntaxHighlighterInclude brush="csharp,cpp,jscript,xml,java,php,sql,objc" theme="default"$>
<$mt:CCLicenseRDF$>

brushの部分が表示したいテキストのフォーマット。このjavascriptやcssを任意の場所に置きたい場合は、plugins\SyntaxHighlighter\lib\SyntaxHighlighter\Plugin.pmを書き換える必要がある。

ブログを書く

フォーマットで、SyntaxHighlighterかSyntaxHighlighter + Convert Line Breaksを選び、ブログ本文を記述する。コード部分を以下のタグで囲むことでプラグイン側でコードと認識され、整形されて表示が行われる。

標準外のフォーマットに対応させる

標準で対応してるフォーマットは、XMLやC+、phpなど数少ない。他のフォーマットにも対応させる場合は、http://www.undermyhat.org/blog/2009/09/list-of-brushes-syntaxhighligher/より必要な.jsファイルをダウンロードし、配置する。

@propertyとアクセサ

クラス内のインスタンス変数をIB(や他のクラス)で利用する場合、IBより値が参照できるようにアクセサ(putter, getter)を各クラスで実装しておく必要がある。しかし@propertyを記述することで、実際のアクセサを実装しなくても値の参照が可能となる。

#import <UIKit/UIKit.h>

@interface DigitalClock1ViewController : UIViewController {
	// インスタンス変数の宣言
	UILabel *label;
}
@property(weak, nonatmic) IBOutlet UILabel *label;
@property(strong, nonatmic) NSString *str;
@end

@propertyのあとの属性はいくつでも指定できる。

属性 挙動
nonatomic スレッドセーフじゃないけど動作は早い?
weak 弱参照。参照先のObjectは、強参照しているObjectが無くなると消滅する
strong 強参照。強参照している間は、対象先のObjectは消滅しない

Storyboad上のObjectはViewController自身が間接的に強参照している、したがってIBOutletで再度ダイレクトに強参照する必要は無い。ただしViewControllerが管轄していないObjectに関しては、Storyboad上にあったとしても強参照しなければならない場合がある。Storyboad以外のObjectを参照する場合も同様に強参照すべきかどうか検討する必要がある。

@synthesize label;

最後に@implementationに@synthesizeを記入することで、getメソッドとputメソッドが自動的に実装される

self.str = nil;
[self setLabel:nil];

viewUnloadedメソッドで各Objectの解放を行う