お願い もっと良い方法があるよ!こうすると楽だよ!ここ間違ってるよ!等があればコメントください。やり方を模索中です
ある日ソースコードを見てると、こんなソースを見つけました。
-(void)methodA
{
NSDictionary *userInfo;
NSURL *imageUrl;
NSString *appid;
NSMutableDictionary *param = [NSMutableDictionary dictionary];
[param setObject:userInfo forKey:@"userInfo"];
[param setObject:imageUrl forKey:@"imageUrl"];
[param setObject:appid forKey:@"appid"];
[self methodBWithParam:param];
}
-(void)methodBWithParam:(NSDictionary *)param
{
// ここで別クラスのメソッドにparamを渡してる
}
methodBWithParamからは別クラスのメソッドを呼び、さらにその先は別クラスのメソッドを呼び、、、と、NSDictionaryをパラメータの集合として引きずり回しています。
結構な量のソースが↑の状態になっていて、これを書いてた本人も「なんとかしたい」と思っているので、専用クラスのプロパティ化する方法を考えました。
注意:まだ「こんな風にしたら間違えにくくて楽かなー」と考えをまとめているだけで、実際にはまだやっていません。実際にやった結果は後日追記したいと思います。
- paramのたどり着く先を探す
メソッドをどんどん追いかけ、paramが行き着く先のクラス/メソッドを探す。
- setObject:forKey: とobjectForKey: だけを持つ専用パラメタクラスを作る
1.で探し出したクラスの .h と .m の中に、 setObject:forKey: とobjectForKey: だけを持つ専用パラメタクラスを作る。
別ファイルにしない理由は、「そのクラス専用のパラメタクラスである」事が分かるようにしたいため。もしも複数のクラスで同じ専用パラメタクラスを使い回す場合は、それが分かった時に別ファイルに分ける。
@interface MyParamClass : NSObject
-(void)setObject:(id)obj forKey:(id)key;
-(id)objectForKey:(id)key;
@end
@implementation MyParamClass{
NSMutableDictionary *_dic;
}
-(id)init
{
self = [super init];
if( self ){
_dic = [NSMutableDictionary dictionary];
}
return self;
}
-(void)setObject:(id)obj forKey:(id)key
{
[_dic setObject:obj forKey:key];
}
-(id)objectForKey:(id)key
{
return [_dic objectForKey:key];
}
@end
- paramの型を専用パラメタクラスに書き換える
paramのたどり着く先のメソッドの引数の、(NSDictionary *)
だった部分を(MyParamClass*)
に書き換える。
すると他のクラスも、型が合わないというエラーを出し始めるので、エラーになっている箇所の型をMyParamClassに書き換えていく。
-(void)methodA
{
NSDictionary *userInfo;
NSURL *imageUrl;
NSString *appid;
MyParamClass *param = [[MyParamClass alloc]init]; // ←NSDictionaryだったのをMyParamClass alloc/initに書き換え
[param setObject:userInfo forKey:@"userInfo"];
[param setObject:imageUrl forKey:@"imageUrl"];
[param setObject:appid forKey:@"appid"];
[self methodBWithParam:param];
}
//
// ...
// ↓別クラスのメソッドのつもり
-(void)methodBWithParam:(MyParamClass *)param // ←NSDictionaryだったのをMyParamClassに書き換え
{
NSDictionary *userInfo = [param objectForKey:@"userInfo"];
NSURL *url = [param objectForKey:@"imageUrl"];
NSString *appid = [param objectForKey:@"appid"];
}
もしもsetObject:forKey:
や objectForKey
以外のメソッド(たとえばvalueForKey
とか)が使われていれば、この段階でエラーが残るはず。そうしたら一度立ち止まって考える。この方法では上手く出来ないかもしれない。
- 専用パラメタクラスにパラメタ毎のプロパティを追加
setObject: forKey
やobjectForKey
している箇所をざっと見て、そのKey毎にプロパティを追加します。プロパティ名にはkeyで使った文字列そのまま使うのが良いです(5.でのtypo防止のため)
@interface MyParamClass : NSObject
-(void)setObject:(id)obj forKey:(id)key;
-(id)objectForKey:(id)key;
@property(nonatomic,strong)id userInfo; // ←追加 ここのidは後で直す。
@property(nonatomic,strong)id imageUrl; // ←追加
@property(nonatomic,strong)id appid; // ←追加
@end
key部分をマクロで統一している場合は、まずはそのマクロ名を「key名」と見なしてプロパティを追加する。一時的におかしなプロパティ名になるけど、後でプロパティ名の一括変換で対応する。
key部分が「マクロ」と「文字列」で混ざっているなら、マクロを文字列に置き換える。
注意:ここで「keyの名前がおかしい、変えたい」と思ってもぐっと我慢する事 後の作業でtypoしやすくなるから。
- 専用パラメタクラスを呼び出している箇所を、プロパティアクセスに置き換える
setObject: forKey
やobjectForKey
している箇所を、プロパティアクセスに変更する。
-(void)methodA
{
NSDictionary *userInfo;
NSURL *imageUrl;
NSString *appid;
MyParamClass *param = [[MyParamClass alloc]init];
// [param setObject:userInfo forKey:@"userInfo"];
// [param setObject:imageUrl forKey:@"imageUrl"];
// [param setObject:appid forKey:@"appid"];
param.userInfo = userInfo; // ←プロパティアクセスに変更
param.imageUrl = imageUrl; // ←プロパティアクセスに変更
param.appid = appid; // ←プロパティアクセスに変更
[self methodBWithParam:param];
}
//
// ...
// ↓別クラスのメソッドのつもり
-(void)methodBWithParam:(MyParamClass *)param
{
// NSDictionary *userInfo = [param objectForKey:@"userInfo"];
// NSURL *url = [param objectForKey:@"imageUrl"];
// NSString *appid = [param objectForKey:@"appid"];
NSDictionary *userInfo = param.userInfo; // ←プロパティアクセスに変更
NSURL *url = param.imageUrl; // ←プロパティアクセスに変更
NSString *appid = param.appid; // ←プロパティアクセスに変更
}
- 専用パラメタクラスのsetObject:forKey: とobjectForKey: をコメントアウトしてビルド
プロパティアクセスに変更する箇所が抜け漏れしてると、ここでエラーになるはず。エラーになったら4.と5.を繰り返す。
- プロパティのid指定を、実際に使っているクラスに変更
idで渡してるプロパティを、実際に使っているクラスに変更する。
@interface MyParamClass : NSObject
// -(void)setObject:(id)obj forKey:(id)key;
// -(id)objectForKey:(id)key;
@property(nonatomic,strong)NSDictionary *userInfo;
@property(nonatomic,strong)NSURL *imageUrl;
@property(nonatomic,strong)NSString *appid;
@end
- プロパティ名がおかしければXCodeのRefactorで置き換える
プロパティ名を変えたい、マクロ名そのままになってておかしい等あれば、Edit→Refactor→Renameでプロパティ名を変更する。 手作業ではやらない。
- 6.のコメントを戻して、NSAssertを仕込む
これは、複数人で開発している場合に、「他の人の作業が止まらないように」するための仕込み。一人で開発/関係者全員が一気に対応できるなら不要。
XCode5から、QuickLookで見られるコメント表記が可能になったので、これにも説明をいれておきます。また、NS_DEPRECATED_IOSを仕込んで、コードコンプリートで目立つようにしておきます。
@interface MyParamClass : NSObject
// このコメントは、XCode5から使えるフォーマット。QuickLookで↓が表示される。
/*! NSDictionaryで使ってたsetObjectは、専用パラメタクラス化で使えなくなりました。
* プロパティを使ってください。呼び出すとNSAssert()で止まります。
* \param obj パラメタの内容
* \param key パラメタのキー
*/
-(void)setObject:(id)obj forKey:(id)key NS_DEPRECATED_IOS(3_0,3_0); // ←使えなくなった旨をコンパイル時に出す。
/*! NSDictionaryで使ってたobjectForKeyは、専用パラメタクラス化で使えなくなりました。プロパティを使ってください。呼び出すとNSAssert()で止まります。
* \param key パラメタのキー
*/
-(id)objectForKey:(id)key NS_DEPRECATED_IOS(3_0,3_0); // ←使えなくなった旨をコンパイル時に出す。
@property(nonatomic,strong)NSDictionary *userInfo;
@property(nonatomic,strong)NSURL *imageUrl;
@property(nonatomic,strong)NSString *appid;
@end
@implementation MyParamClass{
NSMutableDictionary *_dic;
}
-(id)init
{
self = [super init];
if( self ){
_dic = [NSMutableDictionary dictionary];
}
return self;
}
-(void)setObject:(id)obj forKey:(id)key
{
NSAssert(NO,@"setObject:forKey: is not use. Use properties. key=%@ object=%@",key,obj ); // NSAssert追加
[_dic setObject:obj forKey:key];
}
-(id)objectForKey:(id)key
{
NSAssert(NO,@"objectForKey: is not use. Use properties. key=%@",key); // NSAssert追加
return [_dic objectForKey:key];
}
@end
このようにコメントを書く事で、XCode5だと↓のように見えます。
10.後始末
- この変更がチームに行き渡ったら、プロパティ以外の部分(init,setObject,objectForKey,_dic)を全部削除する。
- せっかくなので、各プロパティにコメントで「何のパラメタなのか」の説明を書いておく。
- ここまでだと、プロパティの値がいつでも変更可能になってしまう。コンビニエンスコンストラクタで必要なパラメタを受け取り、プロパティはreadonlyにしておくと良いかも。
後日談
(後で書く)