JSONパース等で発生するNSDictionary中のNSNull対応
REST API等の結果をJSONパース等でNSDictionaryに格納した時に
値が無いケースだとnilではなくNSNullオブジェクトがセットされる。
(NSDictionaryにはオブジェクトしかセット出来ないため)
この結果をアプリケーション側で利用する時にNSDictionaryが入れ子に
なっている場合以下の様なコードはエラーとなる。
[[hoge objectForKey:@"key1"]moge objectForKey:@"key2"]
これはhogeをNSDictionaryと想定しているが値がないためNSNullが設定されており
NSNullに対してobjectForKey:が送られ、そのようなメソッドが無いために発生する。
アプリケーション中で毎回nullチェックをするのがつらい場合は以下のように
forwardInvocation:メソッドをオーバーライドする事で回避出来る。
forwardInvocation:をオーバーライドする場合にはmethodSignatureForSelector:も
実装する必要がある。
これはNSInvocationだけではメソッドシグネチャ(戻り値や引数の型)が判断出来ないため、
アプリ側で設定する必要があるためと思われる。
処理の概要としては
1.あるインスタンスに対して存在しないメッセージが送信される。
2.メソッドが存在しない場合、まずmethodSignatureForSelector:が呼ばれる。
3.つづいてforwardInvocation:が呼ばれ、適当な処理を行う。
以下、サンプルコード
NSNull+IgnoreMessage.h
#import <Foundation/Foundation.h> @interface NSNull (IgnoreMessage) - (void)forwardInvocation:(NSInvocation *)anInvocation; - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector; @end
NSNull+IgnoreMessage.m
#import "NSNull+IgnoreMessage.h" @implementation NSNull (IgnoreMessage) - (void)forwardInvocation:(NSInvocation *)anInvocation { if ([self respondsToSelector:[anInvocation selector]]) { [anInvocation invokeWithTarget:self]; } } - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSMethodSignature *signature = [[NSNull class] instanceMethodSignatureForSelector:aSelector]; if(signature == nil) { //signatureはnilを返すとエラーになるので適当な値を設定して返す signature = [NSMethodSignature signatureWithObjCTypes:"@@:"]; } return signature; } @end
main.m
#import <Foundation/Foundation.h> int main(int argc, const char * argv[]) { @autoreleasepool { NSMutableDictionary *dic1 = [NSMutableDictionary dictionary]; [dic1 setObject:@"test1" forKey:@"key1"]; [dic1 setObject:[NSNumber numberWithInt:2] forKey:@"key2"]; // child dictionary // NSArray *vals = [NSArray arrayWithObjects:@"c1",@"c2", nil]; // NSArray *keys = [NSArray arrayWithObjects:@"ckey1",@"ckey2", nil]; // NSMutableDictionary *dic2 = [NSMutableDictionary dictionaryWithObjects:vals forKeys:keys]; [dic1 setObject:[NSNull null] forKey:@"key3"]; // nsnullエラーテスト // output NSLog(@"key1 = %@\n",[dic1 objectForKey:@"key1"]); NSLog(@"key2 = %@\n",[dic1 objectForKey:@"key2"]); NSLog(@"key3 = %@\n",[dic1 objectForKey:@"key3"]); NSLog(@"key3ckey1 = %@\n",[[dic1 objectForKey:@"key3"]objectForKey:@"ckey1"]); NSLog(@"key3ckey2 = %@\n",[[dic1 objectForKey:@"key3"]objectForKey:@"ckey2"]); } return 0; }
NSNullクラスに対してcategoryを作成する等して上記2メソッドを実装する。
methodSignatureForSelectorに関してはnil以外の値をNSMethodSignatureにセットして
返却すれば良い(実際この値は使用されないのでダミーとなる)
そして、forwardInvocation:のなかでrespondsToSelector:でメソッドが存在しない場合は
空振り(何もしない)ようにしておく。
こうする事でエラーになるのを回避出来る。
ちなみに、methodSignatureに設定しているC文字列の"@@:"は以下の様なルールで記述する
最初の@は戻り値の型を表している。
次は暗黙の引数が二つ。
一つ目はメソッドを実行するオブジェクト、もう一つはメソッドのセレクタとなる。
つまり上記の"@@:"は何らかのオブジェクトを返す引数無しのメソッドと言う事になる。
今回は空振りさせる事が目的なので何でも良かったが、何らかの処理を振り分ける様な
用途で使用する場合には、ここで設定したシグネチャと呼び出すメソッドがずれていると
エラーになるので注意が必要。
以下、型エンコーディングの一覧
C char
s short
i Int
l long
q long long
f float
d double
C unsigned char
S unsigned short
I unsigned int
L unsigned long
Q unsigned long long
V void
* C 文字列(char *)
@ オブジ工クト
# クラスオブジェクト
: セレクタ
[ 型] 配列
{ 名前= 型. •• } 構造体
^型 型へのポインタ
? 不明な型や要素
NSOperationでマルチスレッド処理
NSOperationでマルチスレッド処理を構築する場合、
NSOperationのサブクラスを作成しその中のmainで処理を実装することになる。
作成したoperationをNSOperationQueueにaddOperationして実行する。
もう少し簡単に実装する場合NSBlockOperationやNSInvocationOperationが使える。
名前の通りNSBlockOperationは処理の内容をblockで記述しoperationとして登録する。
NSInvocationOperationは@selectorで処理対象メソッドを指定しoperationに登録する。
#import <Foundation/Foundation.h> @interface InvocationTest : NSObject -(void)test; @end @implementation InvocationTest -(void)test { sleep(1); NSLog(@"invocation\n"); } @end int main(int argc, const char * argv[]) { @autoreleasepool { NSLog(@"start\n"); NSOperationQueue *queue = [[NSOperationQueue alloc]init]; NSOperation *op1 = [NSBlockOperation blockOperationWithBlock:^{ [NSThread sleepForTimeInterval:2.0]; NSLog(@"block\n"); }]; [queue addOperation:op1]; // blockオペレーション追加 InvocationTest *test = [[InvocationTest alloc]init]; NSOperation *op2 = [[NSInvocationOperation alloc]initWithTarget:test selector:@selector(test) object:nil]; [queue addOperation:op2]; // invocationオペレーション追加 NSLog(@"end\n"); [queue waitUntilAllOperationsAreFinished]; } return 0; }
UIControlのイベントハンドラをBlocksにする方法
UIControlのカテゴリを作成し、UIbutton等のイベントハンドラをBlocksで受け取り
内部的にaddTargetすることで実現する。
ポイントはobjc_getAssociatedObjectを使って引数で受け取ったblocksをcopyして
自身に保持するところ。
categoryではインスタンス変数を保持出来ない問題を回避している。
------------------------------------------------------------------------------------------------------------ UIControl+UIControl_BlockCallBack.h ------------------------------------------------------------------------------------------------------------ #import <UIKit/UIKit.h> @interface UIControl (UIControl_BlockCallBack) - (void)addHandlerWithEvent:(UIControlEvents)event Block:(void (^)())block; - (void)callBack:(id)sender; @end ------------------------------------------------------------------------------------------------------------ UIControl+UIControl_BlockCallBack.h ------------------------------------------------------------------------------------------------------------ #import "UIControl+UIControl_BlockCallBack.h" #import <objc/runtime.h> @implementation UIControl (UIControl_BlockCallBack) static const NSString *key = @"key"; -(void)addHandlerWithEvent:(UIControlEvents)event Block:(void (^)())block { objc_setAssociatedObject(self, key, block, OBJC_ASSOCIATION_COPY); [self addTarget:self action:@selector(callBack:) forControlEvents:event]; } - (void)callBack:(id)sender { void (^block)() = objc_getAssociatedObject(self, key); if(block){ block(); } } @end ------------------------------------------------------------------------------------------------------------ 使用例: ------------------------------------------------------------------------------------------------------------ #import "UIControl+UIControl_BlockCallBack.h" ・ ・ ・ UIButton *testButton = [UIButton buttonWithType:UIButtonTypeRoundedRect]; testButton.frame = CGRectMake(self.view.center.x-50.0, 20.0, 100.0, 50.0); [testButton setTitle:@"test" forState:UIControlStateNormal]; [testButton addHandlerWithEvent:UIControlEventTouchUpInside Block:^{ NSLog(@"Simple CallBack"); }]; [self.view addSubview:testButton]; ------------------------------------------------------------------------------------------------------------
iOSアプリのアップデート申請
iOS Dev CenterからiTunes Connectを選択。
Add version を選択してバージョン番号、アップデート内容等を入力する。
ステータスがWaiting For Uploadになる。
xcodeに戻ってリリース用のプロビジョニングファイルの設定、
バージョン番号の変更を行った後、アーカイブを作成する。
作成したアーカイブに対してvalidate…を行う。
検証を通過したらDistribute…を選択。
Submit to the iOS App Storeを選択してアプリをアップロードする。
iTunes Connect上のステータスがWaiting For Reviewになっていれば
レビュー待ちなので、後はAppleのレビューを待つ。
カスタムフォントを使用する
iPhoneのデフォルトで登録してあるフォントでは間に合わない時に、カスタムフォントを追加する。
手順としてはttfファイル等のフォントファイルをプロジェクトに追加
※追加する時に下記図のように[Add to targets]へのチェックを忘れずに!
後は通常通り
label.font = [UIFont fontWithName:@"_custom_font_name" size:48.0];
といったコードでカスタムフォントを使用できる。