Effective? Objective-C

54. 永続化が必要なら,NSCoding Protocolを実装する

Posted in Effective? Objective-C on 1月 9th, 2007 by nagano – 1 Comment

まともなApplicationを作ろうとするとDocument-BaseだUndoだNSCodingだと結構面倒くさいです.
けど,どれも上手いこと出来ているので,Cocoaな仕様から外れなければ楽ですが,Audio ApplicationだとAudio UnitのPresetの保存だなんだとCocoaじゃない部分がもりもりでそれなりに大変.

さて,久々更新,Effective? Objective-C.やっと8回目.

54. 永続化が必要なら,NSCoding Protocolを実装する
(J. Serializableを注意して実装する)

Javaのシリアライズがどんなだったか忘れましたが,ようはオブジェクトをファイルに書き出したり,ファイルから復元したりするということです.
Cocoaではどうなのか.
cocoa_breakさんが翻訳されているので(素晴らしい)参照してみると,

任意のオブジェクトをシリアル化することはできません。NSArray、NSDictionary、NSString、NSDate、NSNumber、NSData(とそれらのサブクラスのいくつか)のインスタンスだけがシリアル化できます。配列やディクショナリオブジェクトの内容は、また、これらの数少ないクラスのオブジェクトだけを含んでいなければなりません。

アーカイブをサポートするには、オブジェクトは NSCoding プロトコルを実装しなければなりません。これは2つのメソッドからなります。一方のメソッドは、オブジェクトの重要なインスタンス変数をアーカイブのなかにコード化 (encode) し、もう一方はアーカイブからインスタンス変数をコード復元 (decode) して元に戻します。

とあります.
NSCoding ProtocolはNSObject.hに定義されています.

@protocol NSCoding

- (void)encodeWithCoder:(NSCoder *)aCoder;
- (id)initWithCoder:(NSCoder *)aDecoder;

@end

この2つを実装すれば,シリアライズできるという事です.
どう実装するのか.

encodeWithCoderでは,NSCoderは,encode***というMethodを持つので,オブジェクトのインスタンスの型に合わせてMethodを呼んでやればOK.

encodeするインスタンスがint hogeというインスタンス変数を持つなら

[aCoder encodeInt:hoge forKey:@"hoge"];

これで,aCoderにencodeされたhogeが渡される.
initWithCoderでは,aDecoderからdecode***ForKeyで値を取り出してやればOK.

hoge = [aCoder decodeIntForKey:@"hoge"];

例のDavinci Classで実装してみます.
workOfArtだけだとつまらないのでIQというセンスゼロなインスタンス変数を追加してみた.

@interface Davinci : NSObject {
    NSString *workOfArt;
    double IQ;
}
@end

@implementation Davinci
- (void)encodeWithCoder:(NSCoder*)coder
{
    [coder encodeObject:workOfArt forKey:@"workOfArt"];
    [coder encodeDouble:IQ forKey:@"IQ"];
}
- (id)initWithCoder:(NSCoder*)decoder
{
    self = [super init];
    workOfArt = [decoder decodeObjectForKey:@"workOfArt"];
    [workOfArt retain];
    IQ = [decoder decodeDoubleForKey:@"IQ"];

    return self;
}

-(NSString*)description{
    return [NSString stringWithFormat:@"%@ :%p ,workOfArt:%@, IQ:%f>",[self className],self,workOfArt,IQ];
}
@end

これだけでシリアライズ/デシリアライズできる.

NSCoder Protocolを実装したオブジェクトはNSArchiver,NSKeyedArchiverでシリアライズ出来て,シリアライズしたデータは,NSUnarchiver,NSKeyedUnarchiverでデシリアライズ(復元)できる.

NSKeyedArchiverを使うとdecodeの時にデータを順番にdecodeしなくてもいいので主流らしい.

#import <Cocoa/Cocoa.h>
#import "Davinci.h"
int main(int argc, char *argv[])
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc]init];
    Davinci *davinci = [[Davinci alloc] init];
    [davinci setValue:@"Mona-Lisa" forKey:@"workOfArt"];
    [davinci setValue:[NSNumber numberWithDouble:250.123456] forKey:@"IQ"];
    NSLog(@" encodeDavinci = %@",davinci);

    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:davinci];

    Davinci *decodedDavinci = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    NSLog(@"decodedDavinci = %@",decodedDavinci);

    [pool release];
    return 0;
}

result….

 encodeDavinci = Davinci :0x306f10 ,workOfArt:Mona-Lisa, IQ:250.123456>
decodedDavinci = Davinci :0x30d520 ,workOfArt:Mona-Lisa, IQ:250.123456>

ということで,ちゃんと動きます.

Fileに書き出して,また読み込んでも

    NSString *path = @"/tmp/hoge";
    [NSKeyedArchiver archiveRootObject:davinci toFile:path];

    Davinci *decodedDavinci = [NSKeyedUnarchiver unarchiveObjectWithFile:path];
    NSLog(@"decodedDavinci = %@",decodedDavinci);

ちゃんと動きます.

もしDavinci ClassがNSArray、NSDictionary、NSString、NSDate、NSNumber、NSData以外の他のClassをインスタンス変数に持っていたら,そのClassでもNSCoder Protocolを実装しないといけません.
何かをセーブしたいときに芋づる式にNSCoder Protocolをもりもり実装する羽目になるかもしれません.
しかしながら,これぐらいの手間で永続化出来るのはだいぶ便利な気がします.
ちなみにNSArray,NSDictionaryの場合,その持っているオブジェクトがNSCodingを実装していればそのままシリアライズ出来るので,DocumentのSave等で便利.
もう一つ.Objective-Cはインスタンス変数に何を持っているのか動的に取得出来るので,その型も取得しちゃえばencodeWithCoderもinitWithCoderも自動で実装出来ると思うのですが,なんで出来ないんでしょうか.
きっと出来るな.誰かやって.

というわけで,EffectiveというよりはNSCoderの初歩になってしまいましたが,
お題通り,永続化が必要なら,NSCoding Protocolを実装するでどうでしょうか.

NSCoding関係は拾いきれていない項目があるので,その内追加予定.

Effective? Objective-C 54.zip

25. 命名規則を考える

Posted in Effective? Objective-C on 12月 7th, 2006 by nagano – Be the first to comment

Effective? Objective-C,7回目.

GSWAU(なんだこの略)を中心にやっていたので,随分期間が空いてしまいました.
コードを書かなくていい項目をささっと片づけちゃいます.
といいつつ,この辺りはその人の信条が表れる大事なポイントじゃないでしょうか.
僕なんかは,Audio / Visualの実験が対象なので,動かすまでが必死で,動くと「うお〜!できた!」という感じで,それまでのグチャグチャなコードの整理なんか忘れちゃいます & それまではとにかくガチャガチャ書くという感じですが,よいコードを書くというのは,それはそれで深いテーマであります.

25. 命名規則などを考える
(J. メソッドのシグニチャを注意深く設計する)

Effective Javaで扱ってある項目は主に
1. 命名規則
2. 提供するメソッドの数
3. 引数の数
です.

2.と3.は,(Publicに)提供するメソッドの数,引数の数は出来るだけ少なくしろという話ですが,そうした方がいいですよという話で,特に書くことはありません.

なので,命名規則を考えます.

命名規則については,ずばりのドキュメントがあります.
Naming Methods

Objective-C プログラミング言語:命名規則

クラス名だと
トーフサロンさんのクラス命名のアンチパターンObjective-Cにおけるクラス命名パターン

が非常に面白い.すばらしい.

もっと広くコーディングスタイルについては,きりかノートさんのCocoa のコーディングスタイルが参考になる.

ラベル (引数前の hoge: ってやつ)があるのがObjective-Cのソースの見やすさに繋がっていると思うのですが,最初はこれがなんとも分かりにくかったのは僕だけでしょうか.

ドキュメントを見ていていつも思うのが,Objective-C プログラミング言語の日本語版のPDFを用意してください,Appleジャパンさん.印刷してくれるところに出して本にしたいです.
英語でいいから印刷しようかな〜と探したら
http://www.digitao.net/
というのがありました.200ページで1260円だと自宅プリンタでやるより安いかも?
やってみようかな.
XcodeUserGuideが先か.いや,Leopard出るから無駄になるか.

ダイナミック Objective-Cも本にしてください.(MYCOMの方)

というわけで雑談に終始しましたが,結論としては
“↑の命名規則を参照する”でどうでしょうか.

12. クラスとメンバーへのアクセスはアクセス制御以外の方法で検討する

Posted in Effective? Objective-C on 11月 23rd, 2006 by nagano – Be the first to comment

Effective? Objective-C,6回目.

最近,ちょっと真面目にグラフィックをやっているのですが,あまりにも項目が膨大で途方に暮れております.しかしながら,Programming With Quartzで覚えた事がかなり役に立っています.Quartzでささっとグラフなんかが書けると勉強の理解が早いです.そんなわけで,Programming With Quartzお勧め.

そういえば,前検索したときには,出てこなかったのですが,Effective Objective-Cを書いている人が居ました.Effective Obj-C.毎回参考にしてパクっていこうと思います.

さて前回,Objective-Cにはprivateメソッドはありませんと書いたので,インスタンス変数についても書いておこうと思います.

12.クラスとメンバーへのアクセスはアクセス制御以外の方法で検討する
(J. クラスとメンバーへのアクセス可能性を最小限にする)

Objective-Cでは,インスタンス変数へのアクセス制御には3つのディレクティブがあります.

@private	インスタンス変数を宣言するクラス内でのみ当該変数にアクセス可能。
@protected	インスタンス変数を宣言するクラス内および継承するクラス内で当該変数にアクセス可能。
@public	対象インスタンス変数にどこからでもアクセス可能。

デフォルトでは(何も書かなければ)@protectedです.
(とドキュメントに書いてあります.)

しかし,privateにしようが,publicにしようが
instance->hoge
とやるとアクセスできてしまうのです.

@interface Davinci : NSObject {
	@private
	NSString *workOfArt;
}
@end
@implementation Davinci
@end
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
	Davinci *davinci = [[Davinci alloc]init];
	[davinci setValue:@"Monalisa" forKey:@"workOfArt"];

	NSLog(@"%@",[davinci valueForKey:@"workOfArt"]);
	NSLog(davinci->workOfArt);

    [pool release];
	return 0;
}

しかしながら,

warning: instance variable 'workOfArt' is @private; this will be a hard error in the future

と警告は出ます.@publicにすると警告は出ません.

@privateなインスタンス変数にKeyValueCodingでアクセスしたら,どうなるか初めてやってみましたが,何も起こりませんでした.object_getInstanceVariableを使っているんじゃないかと予測します.警告もへったくれもありません.というわけで,ディレクティブの存在意義すらよく分かりません.これにはアクセスして欲しくない!というときに@privateと書くという感じでしょうか.warningをコンパイルエラーにする設定にしておけばいくらかマシかもしれません.

お題で”検討する”と書いたのですが,検討しようがありません.

あえて代替案を書くならば,privateにしたいようなきっちりした設計をやりたい部分はC++で書いて,そのクラスにごちゃごちゃやらせるObjective-CのクラスをObjective-C++で書くのはどうでしょうか.テンプレートで意味の分からないコードを書きたくなることもたまにはありますし(書けませんが).
ControllerとViewはObjective-CでModelはC++でという設計をやれば,移植性もいくらかマシな気がします.
あとは,目印程度に@privateなどを書く.

なんだか全然Effectiveじゃない気がしてきました.

オブジェクト指向も含めた高度な議論は第31回 ランタイムAPIでさらに動的に(5) – インスタンス変数に動的にアクセスが詳しい.@defはCore Audioで使いますので,Getting Started With Audio Unitを読んでいらっしゃる方も要チェック.

というわけで結論は,不本意ながら,アクセス制御はとりあえず考えないで,他の部分に時間を割くどうでしょうか.

2. allocWithZoneをオーバーライドしてシングルトン特性を強制する

Posted in Effective? Objective-C on 11月 22nd, 2006 by nagano – Be the first to comment

Effective? Objective-C,5回目.
今回は(も?)かなり手抜きですが,結構重要だと思います.

お題.allocWithZoneをオーバーライドしてシングルトン特性を強制する
(J. privateのコンストラクタでシングルトン特性を強制する)

Objective-Cにはprivateメソッドはありません.苦肉の策でprivateカテゴリーにしても,class-dumpで発見されてしまいます.C++ばりばりな人はこれが気持ち悪かったりするんでしょうか.

では,どうするのかというと,ずばりの答えがデザインパターンをObjective-Cで – Singleton (2)(& 「Cocoa Fundamental Guide: シングルトンインスタンスの作成」)に書いてあります.allocWithZoneのオーバーライドに留まらず,インスタンス管理に関するメソッドも片っ端から上書きするという凝りよう.
なので書くことは何もありません.笑
あえて書くなら,シングルトンと聞くたびに思うのは,言語仕様に組み込んでしまえば?ということ.

@interface Hoge : NSObject @annotation(Singleton){
}

こんな感じで.

上記記事のソースをコピペすればいいだけの話ではありますが.
そういえば,たしかDIContainerではそういう機能がありました(Seasar,Spring).なつかしい.

というわけで,今回はすんなりお題通りでいいんじゃないでしょうか.

11. compareを実装することを検討する

Posted in Effective? Objective-C on 11月 11th, 2006 by nagano – Be the first to comment

今日は多摩美術館に行ってきました.
コンピューターグラフィクスを対象とする人は行く価値大です.
というより見ておいた方がいいと思います.

さて,なんだかもう4回目のEffective? Objective-C.今日も更新.
Effective-C++をちょっと見直したのですが,Effective Javaの方がObjective-Cには合っている気がしました.Smalltalkがキーワードか.

お題.compareを実装することを検討する
(J. Comparableを実装することを検討する)
compareを実装するということはNSComparisonResultを返す,同じクラスのインスタンスの比較methodを実装するということです.
NSDateでは
- (NSComparisonResult)compare:(NSDate *)other;
こういうmethodが定義されていて,比較が簡単です.
NSStringだと
- (NSComparisonResult)compare:(NSString *)string;
NSNumber
- (NSComparisonResult)compare:(NSNumber *)otherNumber;

- (NSComparisonResult)compare:(クラス名*)other***
という命名規則のようです.

NSComparisonResultは

typedef enum _NSComparisonResult {
    NSOrderedAscending = -1,    //上になる
    NSOrderedSame,
    NSOrderedDescending           //下になる
} NSComparisonResult;

J. アルファベット,数値,年代などの明らかで自然な順序を持つ値クラスを書くのであれば,このインターフェイスを実装することを真剣に検討すべきです.
とあります.
そういったクラスはコレクション的クラスに入れて使うケースが多々あるので,ソートで便利.

Javaだと,Comparableインターフェイスを実装しているとArrays.sort(a)でsortできるらしい.
Objective-C (Cocoa)ではどうか.

NSArray
- (NSArray *)sortedArrayUsingSelector:(SEL)comparator;

NSMutableArray
- (void)sortUsingSelector:(SEL)comparator;

あたりでしょうか.

例のDavinci Classで実装してみます.

- (NSComparisonResult)compare:(Davinci*)otherDavinci{
	return [workOfArt compare:[otherDavinci valueForKey:@"workOfArt"]];
}
	Davinci *davinci1 = [[Davinci alloc]init];
	Davinci *davinci2 = [[Davinci alloc]init];
	Davinci *davinci3 = [[Davinci alloc]init];

	[davinci1 setValue:@"The Last Supper" forKey:@"workOfArt"];
	[davinci2 setValue:@"Mona-Lisa"       forKey:@"workOfArt"];
	[davinci3 setValue:@"John the Baptist" forKey:@"workOfArt"];

	NSArray *array = [NSArray arrayWithObjects:davinci1,davinci2,davinci3,nil];
	NSLog([array description]);

	NSArray * sortedArray = [array sortedArrayUsingSelector:@selector(compare:)];
	NSLog([sortedArray description]);
result....
array = (
    <Davinci :0x306ec0 ,workOfArt:The Last Supper>,
    <Davinci :0x306ef0 ,workOfArt:Mona-Lisa>,
    <Davinci :0x306f00 ,workOfArt:John the Baptist>
)
sortedArray = (
    <Davinci :0x306f00 ,workOfArt:John the Baptist>,
    <Davinci :0x306ef0 ,workOfArt:Mona-Lisa>,
    <Davinci :0x306ec0 ,workOfArt:The Last Supper>
)

(おおっと,前回のdescriptionが思わず活躍.やはり”常”に実装すべきか?)

これはソート出来るようなClassならやる価値大だし,コストもそんなにかからなそう.
ちなみに僕は今までsortUsingSelectorでソートするのではなく,sortedArrayUsingFunctionでソートしていました.
配列に入れるClassにcompareを実装すればよかったのです.

例がNSStringなので注意したいのは
Cocoa Programming Tips 1001 : 文字列を比較する
にあるように,比較のoptionがあるので,
- (NSComparisonResult)compare:(NSString*)string options:(unsigned)mask;
を使うcompareも実装すべきかもしれません.

そんなわけで今回はお題通り,”compareを実装することを検討する”
でいいんじゃないでしょうか.

9. descriptionを常にオーバーライドする

Posted in Effective? Objective-C on 11月 10th, 2006 by nagano – Be the first to comment

なぜか毎日更新.ここ数日で以前の3ヶ月分は書いております.
そろそろ飽きるのじゃないかと思います.

お題.descriptionを常にオーバーライドする
(J. toStringを常にオーバーライドする)

ドキュメントによると

NSObject クラスには、クラスの内容を説明する文字列を返す description インスタンスメソッドが定義されています。これは主にデバッグに使用します(このメソッドが返す文字列は、GDB print-object コマンドで出力されます)。このメソッドの NSObject における実装は、対象クラスに何が含まれているかを知らないので、オブジェクトの名前とアドレスが一体となった文字列を返します。NSObject のサブクラスでは、より詳細な情報を返すために、このメソッドを実装することができます。たとえば、Foundation クラスの NSArray は、そのクラスに含まれるオブジェクトの説明のリストを返します。

ということになっています.
NSLog Debugでは使いまくりです.

NSLog([hoge description]);

これは

NSLog(@"%@",hoge);

と書く事もできます.
NSArray,NSDictionaryあたりはdescriptionで内容を一斉に出力できて便利なのでよく使います.

僕はNSLog Debugが有効である(と思う)時は多々ありますので,その時にdescriptionをオーバーライドすることはあります.
たとえば,Davinci Classのインスタンスが2個入ったNSArrayをNSLog + descriptionしたときに
descriptionをオーバーライドしてなければ,NSObjectデフォルト(className + インスタンスのアドレス) が2つ出力されます.

(<davinci : 0x30abb0>, </davinci><davinci : 0x30ab80>)

そこでDavinci Classに

-(NSString*)description{
	return [NSString stringWithFormat:@"%@ :%p ,workOfArt:%@>",[self className],self,workOfArt];
}

を実装すると

(<davinci :0x30abb0 ,workOfArt:Mona-Lisa><davinci :0x30ab80 ,workOfArt:最後の晩餐>)

と出て分かりやすい!

が,”常に”オーバーライドが必要という程ではありません.

ちなみにデバッガでも出力可能.
(GDB print-object コマンドで出力されますというのがこれと思われます)
20061110_0.png

他の用法を考えよう
descriptionが返すNSStringがそのクラス(インスタンス)の情報を正確に返すならば,initWithStringというmethodを作って,そのNSStringから同じデータを持つインスタンスを作れるようにすれば便利かもしれません.

たとえばNSDate,NSCalendarDateでこれが出来ます.

	NSDate *date1 = [NSDate date];
	NSString *description = [date1 description];
	NSLog(description);
	NSDate *date2 = [[NSDate alloc]initWithString:description];
	NSLog([date2 description]);
result...
2006-11-09 23:06:03 +0900
2006-11-09 23:06:03 +0900

しかし,ここで問題なのは,Classが自身のdescriptionに大きく依存するし,Effective Javaに書いてあるように,形式を変更すると大変なことになるということです.
この辺は掘り下げると,NSCoderとか,copyとか関係してきて難しそうなので,そのうちということで.

ということで

<davinci : 0x306ec0>

って出るより

<davinci : 0x306ec0,workOfArt:Mona-Lisa>

と出た方が便利に決まっているので,”よく使うClassで,しかも文字で確認したい(出来る)情報を持つならdescriptionのオーバーライドを検討するし,さらに気が向いたら,可能であればdescriptionの返すNSStringからインスタンス作成が可能なinitWithStringを作るのもいいかもしれない”

でどうでしょうか.

長い結論はEffectiveでは無い気がする.

8. isEqualをオーバーライドする時は常にhashをオーバーライドする

Posted in Effective? Objective-C on 11月 9th, 2006 by nagano – Be the first to comment

お題.isEqualをオーバーライドする時は常にhashをオーバーライドする.
(j. equalsをオーバーライドするときは常にhashCodeをオーバーライドする)

Effectiveっていうぐらいなので,効果的な必要があると思うのですが,今回はそれが甚だ疑問であります.
というのも,僕は今日の今日まで,-(unsigned)hash; このNSObjectのmethodを呼んだ事がありませんでしたし,オーバーライドした事もありません.
つまり,hashはCocoaやるのにあまり関係ないんじゃないのかという疑問があります.
しかし,関係無いならNSObjectが持ってたりしないはず.

appleのドキュメント
によると

hashメソッドとisEqual:メソッドはどちらもNSObjectプロトコルによって宣言されており、密接に関連しています。hashメソッドは、ハッシュテーブル構造のテーブルアドレスとして使用できる整数を返すように実装する必要があります。2つのオブジェクトが(isEqual:メソッドの判定によって)等しい場合、それらは必ず同じハッシュ値を持ちます。オブジェクトをNSSetなどのコレクションに含める可能性がある場合は、hashを定義し、2つのオブジェクトが等しい場合に同じハッシュ値を返すという不変条件を確かめる必要があります。NSObjectにおけるisEqual:のデフォルトの実装は、単純にポインタの等価性を確認するだけです。

らしいです.
その下には,ポインタを確認して,それが違えばインスタンス変数をisEqualで比較する例が載っています.
これはめんどくさい.ですが,ポインタ比較じゃない比較が必要なら頑張って書くべきなのか.書いたとして,NSSetで問題が出るのか?
というわけでやってみました.

@interface Davinci : NSObject {
	NSString *workOfArt;
}
@end
@implementation Davinci
-(BOOL)isEqual:(id)other{
	NSLog(@"%s",__PRETTY_FUNCTION__);
	if (other == self)
        return YES;
	if (!other || ![other isKindOfClass:[self class]])
        return NO;

	if(![workOfArt isEqualToString:[other valueForKey:@"workOfArt"]]){
		return NO;
	}
	return YES;
}
-(unsigned)hash{
	NSLog(@"%s",__PRETTY_FUNCTION__);
	return [super hash];
}
@end
int main(int argc, char *argv[]){
	NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];

	Davinci *davinci1 = [[Davinci alloc]init];
	Davinci *davinci2 = [[Davinci alloc]init];

	[davinci1 setValue:@"Mona-Lisa" forKey:@"workOfArt"];
	[davinci2 setValue:@"Mona-Lisa" forKey:@"workOfArt"];

	NSLog(@"%d",[davinci1 hash]);
	NSLog(@"%d",[davinci2 hash]);

	NSSet *set = [NSSet setWithObjects:davinci1,davinci2,nil];
	NSLog(@"count = %d",[[set allObjects]count]);

	NSEnumerator *objectEnumerator = [set objectEnumerator];
	id obj;
	while(obj = [objectEnumerator nextObject]){
		NSLog([obj className]);
	}
	[pool release];
}
result.....
 -[Davinci hash]
 787988
 -[Davinci hash]
 790368
 -[Davinci hash]
 -[Davinci hash]
 -[Davinci isEqual:]
 count = 1
 Davinci

hashは違いますが,NSSetには正常に1個のインスタンスが入っています.
NSSetのsetWithObjectsを呼んだ時にhash,hash, isEqualと呼ばれているので,hashが同じならisEqualを呼ばないのかなと思って,推奨のごとく,hashでreturn [workOfArt hash];とやって同じhashを返すようにしても,結局isEqualが呼ばれるので,速度が向上するわけでもなさそう.
他にhash使うのだと変わってくるのか?

というわけで
isEqualをオーバーライドする時は常にhashをオーバーライドする,必要も無い気がするけど,パフォーマンスにこだわる人,もしくはhashが関係ありそうな何かを使う人は,hashを計算するべきなのかも.hashもオーバーライドするのがプロフェッショナル?
ということでどうでしょうか.

1. イニシャライザと共にクラスメソッドの提供を検討する

Posted in Effective? Objective-C on 11月 8th, 2006 by nagano – Be the first to comment

Effective Javaに習って,そのObjective-C版
を考えてみようという企画.
Effective C++も使うかもしれないけど,性質の異なる言語の方が比較として面白いかもしれないので,とりあえずJavaでやってみます.
Javaをさっぱり忘れているので,(Objective-Cもたいして分かってないので)大幅に間違った事を書く可能性があります.
ご指摘,追加等々歓迎.面白いかもと思った人は是非記事を書いてください.

お題.イニシャライザの代わりにクラスメソッドの提供を検討する
(J. コンストラクタの代わりにstaticファクトリーメソッドの提供を検討する)

Java

public static Boolean ValueOf(boolean b){
      return (b ? Boolean.TRUE : Boolean.FALSE);
}

Objective-Cだと

+ (NSNumber *)numberWithBool:(BOOL)value;

になると思われます.
実装はたぶん

+ (NSNumber *)numberWithBool:(BOOL)value{
	return [[[NSNumber alloc]initWithBool:value] autorelease];
}

こうじゃないかと.

Javaだと,コンストラクタはClass名となっているが,staticファクトリーメソッドだと名前が付けられるのがメリットという事ですが,Objective-CはinitWith***で色々名前が付けられるので特にそれはメリットじゃない.

ではメリットは何か.
その場でしか使わないなら,release不要(autoreleaseしてあれば).
なので,releaseを忘れてメモリリークというミスが減るかもしれない.
retainしないと居なくなるので,その時はエラーが発生するので発見しやすいし
そのインスタンスがその後も必要なのかどうかを明確にするかもしれない.
記述からallocとautoreleaseが無くなるので
[[[]]]的Chaosticな記述がシンプルになる.
なので,]で閉め忘れた!コンパイルエラーでイライラという現象が減る.笑

これは当たりかもしれない.

- (id)initWith***
+ (id)***With***

実際,両方実装してあるClassは多い.

名前を考えるならば

型変換的性質を持つなら(J. valueOf)
Class名 – prefix(NS) + With = numberWith***
e.x., NSStringのstringWithCString,NSURLのURLWithString

その他は(J. getInstance)
Singletonぐらいか.

そう考えると
“イニシャライザの代わりにクラスメソッドの提供を検討する”ではなく

“イニシャライザと共にクラスメソッドの提供を検討する”
(検討どころか頻繁に使うClassなら積極的に実装)

がぴったりくるかもしれない.