@taketo1024
話すこと
1. (中級) UIView を使いやすく
2. (上級) NSNull を黙らせる
1. UIView を使いやすく
初心者あるある
// myView を右に 10pt 移動[UIView animateWithDuration:0.25 animations:^{ myView.frame.origin.x += 10;}];
初心者あるある
// myView を右に 10pt 移動[UIView animateWithDuration:0.25 animations:^{ myView.frame.origin.x += 10;}];
え?
正しくは、
// myView を右に 10pt 移動[UIView animateWithDuration:0.25 animations:^{ CGRect frame = myView.frame; frame.origin.x += 10; myView.frame = frame;}];
または、
// myView を右に 10pt 移動[UIView animateWithDuration:0.25 animations:^{ myView.frame = CGRectMake(myView.frame.origin.x + 10, myView.frame.origin.y, myView.frame.size.width, myView.frame.size.height);}];
あるいは、
// myView を右に 10pt 移動[UIView animateWithDuration:0.25 animations:^{ myView.frame = CGRectOffset(myView.frame, 10, 0);}];
うーん…
そもそもなぜできない?
// myView を右に 10pt 移動[UIView animateWithDuration:0.25 animations:^{ myView.frame.origin.x += 10;}];
<UIKit/UIView.h>@interface UIView(UIViewGeometry)
@property(nonatomic) CGRect frame;@property(nonatomic) CGRect bounds;@property(nonatomic) CGPoint center;
...
@property(nonatomic,readonly) UIView *superview;@property(nonatomic,readonly,copy) NSArray *subviews;@property(nonatomic,readonly) UIWindow *window;
@end
<UIKit/UIView.h>@interface UIView(UIViewGeometry)
@property(nonatomic) CGRect frame;@property(nonatomic) CGRect bounds;@property(nonatomic) CGPoint center;
...
@property(nonatomic,readonly) UIView *superview;@property(nonatomic,readonly,copy) NSArray *subviews;@property(nonatomic,readonly) UIWindow *window;
@end
CGRect, CGPoint は構造体→ アクセスのたび値が生成されて返される
<UIKit/UIView.h>@interface UIView(UIViewGeometry)
@property(nonatomic) CGRect frame;@property(nonatomic) CGRect bounds;@property(nonatomic) CGPoint center;
...
@property(nonatomic,readonly) UIView *superview;@property(nonatomic,readonly,copy) NSArray *subviews;@property(nonatomic,readonly) UIWindow *window;
@end UIView, NSArray はオブジェクト→ 特定のメモリ領域を指すポインタ
とにかく、
こういう風に書きたい
// myView を右に 10pt 移動[UIView animateWithDuration:0.25 animations:^{ myView.x += 10;}];
できます!
予備知識
1) プロパティはアクセサメソッドの略記法
2) カテゴリで勝手にクラスを拡張できる
1) プロパティはアクセサメソッドの略記法
// このコードは… CGRect frame = myView.frame; // こう実行される CGRect frame = [myView frame];
1) プロパティはアクセサメソッドの略記法
// このコードは… myView.frame = CGRectMake(0, 0, 100, 200); // こう実行される [myView setFrame:CGRectMake(0, 0, 100, 200)];
2) カテゴリで勝手にクラスを拡張できる
// UIView クラスを勝手に拡張 @interface UIView(TSExtension)
- (CGFloat)x;- (void)setX:(CGFloat)x;
@end
UIView+TSExtension.h
2) カテゴリで勝手にクラスを拡張できる
// frame を使って getter / setter を定義@implementation UIView(TSExtension)
- (CGFloat)x{ return self.frame.origin.x;}
- (void)setX:(CGFloat)x { CGRect frame = self.frame; frame.origin.x = x; self.frame = frame;}
@end
UIView+TSExtension.m
2) カテゴリで勝手にクラスを拡張できる
#import "UIView+TSExtention.h"
...
[UIView animateWithDuration:0.25 animations:^{ [myView setX:([myView x] + 10)];}];
↑こう書ける
2つ合わせて、
こんなカテゴリを作れば、@interface UIView(TSExtention)
@property (nonatomic) CGFloat x;
@end
@implementation UIView (TSExtention)
- (CGFloat)x{ return self.frame.origin.x;}
- (void)setX:(CGFloat)x { CGRect frame = self.frame; frame.origin.x = x; self.frame = frame;}
@end
こう書ける!
#import "UIView+TSExtention.h"
...
[UIView animateWithDuration:0.25 animations:^{ myView.x += 10;}];
こう書ける!
#import "UIView+TSExtention.h"
...
[UIView animateWithDuration:0.25 animations:^{ myView.x += 10;}];
// こう実行される[myView setX:([myView x] + 10)];
こういう感じに作っとくと便利
@interface UIView(TSExtention)
@property (nonatomic) CGFloat x;@property (nonatomic) CGFloat y;@property (nonatomic) CGPoint origin;@property (nonatomic) CGFloat left;@property (nonatomic) CGFloat right;@property (nonatomic) CGFloat top;@property (nonatomic) CGFloat bottom;@property (nonatomic) CGSize size;@property (nonatomic) CGFloat width;@property (nonatomic) CGFloat height;
@end
Oh, 直観的!
#import "UIView+TSExtention.h"
...
// myView1, myView2 が横に並んで 10pt 平行移動[UIView animateWithDuration:0.25 animations:^{ myView1.left += 10; myView2.left = myView1.right + 5;}];
https://github.com/taketo1024/UIView-TSExtension
どうぞご利用下さい
2. NSNull を黙らせる
ありきたりなサーバ通信[NSURLConnection
sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
_label.text = result[@"text"];
}];
ありきたりなサーバ通信[NSURLConnection
sendAsynchronousRequest:request queue:queue completionHandler:^(NSURLResponse *response, NSData *data, NSError *connectionError) { NSDictionary *result = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
_label.text = result[@"text"];
}]; レスポンスの “text” の値を _label に表示
oh...
oh...
原因
{ "text": null, ...}
@{ @"text": [NSNull null], ...};
•[NSJSONSerialization JSONObject...] の結果:
•レスポンスのJSON:
こうなってた
レスポンスを片っ端からNSNull チェックをするのが正しいんだけども…
nil はメッセージ投げてもヌルポとか出さないのに、NSNull は自己主張が強い。
黙らせる!
NSNull サイレンサーを実装
#import <objc/runtime.h>
@implementation NSNull (TSSilencer)
- (void *)silentGetter{ NSLog(@"called: [NSNull %@]", NSStringFromSelector(_cmd)); return nil;}
- (void)silentSetter:(void *)value{ NSLog(@"called: [NSNull %@]", NSStringFromSelector(_cmd));}
...(続く)
NSNull サイレンサーを実装
#import <objc/runtime.h>
@implementation NSNull (TSSilencer)
- (void *)silentGetter{ NSLog(@"called: [NSNull %@]", NSStringFromSelector(_cmd)); return nil;}
- (void)silentSetter:(void *)value{ NSLog(@"called: [NSNull %@]", NSStringFromSelector(_cmd));}
...(続く)
nil を返すだけの getter
NSNull サイレンサーを実装
#import <objc/runtime.h>
@implementation NSNull (TSSilencer)
- (void *)silentGetter{ NSLog(@"called: [NSNull %@]", NSStringFromSelector(_cmd)); return nil;}
- (void)silentSetter:(void *)value{ NSLog(@"called: [NSNull %@]", NSStringFromSelector(_cmd));}
...(続く)何もしない setter
+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSString *selName = NSStringFromSelector(sel);
if([selName hasPrefix:@"set"]) { Method setter = class_getInstanceMethod(self, @selector(silentSetter:));
class_addMethod(self, sel, method_getImplementation(setter), method_getTypeEncoding(setter));
} else { Method getter = class_getInstanceMethod(self, @selector(silentGetter)); class_addMethod(self, sel, method_getImplementation(getter), method_getTypeEncoding(getter)); } return YES;}
NSNull サイレンサーを実装
+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSString *selName = NSStringFromSelector(sel);
if([selName hasPrefix:@"set"]) { Method setter = class_getInstanceMethod(self, @selector(silentSetter:));
class_addMethod(self, sel, method_getImplementation(setter), method_getTypeEncoding(setter));
} else { Method getter = class_getInstanceMethod(self, @selector(silentGetter)); class_addMethod(self, sel, method_getImplementation(getter), method_getTypeEncoding(getter)); } return YES;}
未定義のメッセージ受信時に必ず呼ばれる
NSNull サイレンサーを実装
+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSString *selName = NSStringFromSelector(sel);
if([selName hasPrefix:@"set"]) { Method setter = class_getInstanceMethod(self, @selector(silentSetter:));
class_addMethod(self, sel, method_getImplementation(setter), method_getTypeEncoding(setter));
} else { Method getter = class_getInstanceMethod(self, @selector(silentGetter)); class_addMethod(self, sel, method_getImplementation(getter), method_getTypeEncoding(getter)); } return YES;}
set*** なら silentSetter: を呼ばせ、
NSNull サイレンサーを実装
+ (BOOL)resolveInstanceMethod:(SEL)sel{ NSString *selName = NSStringFromSelector(sel);
if([selName hasPrefix:@"set"]) { Method setter = class_getInstanceMethod(self, @selector(silentSetter:));
class_addMethod(self, sel, method_getImplementation(setter), method_getTypeEncoding(setter));
} else { Method getter = class_getInstanceMethod(self, @selector(silentGetter)); class_addMethod(self, sel, method_getImplementation(getter), method_getTypeEncoding(getter)); } return YES;} それ以外は silentGetter を呼ばせる。
NSNull サイレンサーを実装
試しにやってみる
// 露骨にヤバい奴_label.text = (id)[NSNull null];
おぉ、クラッシュしない!
2014-02-25 13:39:02.348 called: [NSNull length]
2014-02-25 13:39:02.349 called: [NSNull length]
2014-02-25 13:39:02.349 called: [NSNull _fastCharacterContents]
↑ UILabel の中でなんかやってるのが分かる
こんなのも行ける
NSString *str = (id)[NSNull null]; NSLog(@"string: %@", [str stringByAppendingString:@"hoge"]);
NSArray *arr = (id)[NSNull null]; NSLog(@"array: %@", arr[1]);
NSDictionary *dic = (id)[NSNull null]; NSLog(@"dic: %@", dic[@"key"]);
余裕!
called: [NSNull stringByAppendingString:]string: (null)
called: [NSNull objectAtIndexedSubscript:]array: (null)
called: [NSNull objectForKeyedSubscript:]dic: (null)
こんなことしていいんだろうか…
いいんです!!!
大事なのは保守性と安全性。どこまでやるかはあなた次第。
ご利用は計画的に
https://github.com/taketo1024/NSNull-TSSilencer
こちらもよろしく
http://www.slideshare.net/taketo1024/ss-30036615
Thank you!@taketo1024