Objective-C が好きになる Tips & Hack

33,446 views
33,550 views

Published on

ヤフー vs クラスメソッド「iOS 炎の7番勝負」にて発表
http://dev.classmethod.jp/news/yxcm/

Published in: Technology
0 Comments
56 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total views
33,446
On SlideShare
0
From Embeds
0
Number of Embeds
14,305
Actions
Shares
0
Downloads
63
Comments
0
Likes
56
Embeds 0
No embeds

No notes for slide

Objective-C が好きになる Tips & Hack

  1. 1. @taketo1024
  2. 2. 話すこと 1. (中級) UIView を使いやすく 2. (上級) NSNull を黙らせる
  3. 3. 1. UIView を使いやすく
  4. 4. 初心者あるある // myView を右に 10pt 移動 [UIView animateWithDuration:0.25 animations:^{ myView.frame.origin.x += 10; }];
  5. 5. 初心者あるある // myView を右に 10pt 移動 [UIView animateWithDuration:0.25 animations:^{ myView.frame.origin.x += 10; }]; え?
  6. 6. 正しくは、 // myView を右に 10pt 移動 [UIView animateWithDuration:0.25 animations:^{ CGRect frame = myView.frame; frame.origin.x += 10; myView.frame = frame; }];
  7. 7. または、 // 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); }];
  8. 8. あるいは、 // myView を右に 10pt 移動 [UIView animateWithDuration:0.25 animations:^{ myView.frame = CGRectOffset(myView.frame, 10, 0); }];
  9. 9. うーん…
  10. 10. そもそもなぜできない? // myView を右に 10pt 移動 [UIView animateWithDuration:0.25 animations:^{ myView.frame.origin.x += 10; }];
  11. 11. <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
  12. 12. <UIKit/UIView.h> @interface UIView(UIViewGeometry) @property(nonatomic) CGRect frame; @property(nonatomic) CGRect bounds; @property(nonatomic) CGPoint center; ... CGRect, CGPoint は構造体 → アクセスのたび値が生成されて返される @property(nonatomic,readonly) UIView *superview; @property(nonatomic,readonly,copy) NSArray *subviews; @property(nonatomic,readonly) UIWindow *window; @end
  13. 13. <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 はオブジェクト → 特定のメモリ領域を指すポインタ
  14. 14. とにかく、
  15. 15. こういう風に書きたい // myView を右に 10pt 移動 [UIView animateWithDuration:0.25 animations:^{ myView.x += 10; }];
  16. 16. できます!
  17. 17. 予備知識 1) プロパティはアクセサメソッドの略記法 2) カテゴリで勝手にクラスを拡張できる
  18. 18. 1) プロパティはアクセサメソッドの 略記法 // このコードは… CGRect frame = myView.frame; // こう実行される CGRect frame = [myView frame];
  19. 19. 1) プロパティはアクセサメソッドの 略記法 // このコードは… myView.frame = CGRectMake(0, 0, 100, 200); // こう実行される [myView setFrame:CGRectMake(0, 0, 100, 200)];
  20. 20. 2) カテゴリで勝手にクラスを拡張できる // UIView クラスを勝手に拡張 @interface UIView(TSExtension) - (CGFloat)x; - (void)setX:(CGFloat)x; @end UIView+TSExtension.h
  21. 21. 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
  22. 22. 2) カテゴリで勝手にクラスを拡張できる #import "UIView+TSExtention.h" ... [UIView animateWithDuration:0.25 animations:^{ [myView setX:([myView x] + 10)]; }]; ↑こう書ける
  23. 23. 2つ合わせて、
  24. 24. こんなカテゴリを作れば、 @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
  25. 25. こう書ける! #import "UIView+TSExtention.h" ... [UIView animateWithDuration:0.25 animations:^{ myView.x += 10; }];
  26. 26. こう書ける! #import "UIView+TSExtention.h" ... [UIView animateWithDuration:0.25 animations:^{ myView.x += 10; }]; // こう実行される [myView setX:([myView x] + 10)];
  27. 27. こういう感じに作っとくと便利 @interface UIView(TSExtention) @property @property @property @property @property @property @property @property @property @property @end (nonatomic) (nonatomic) (nonatomic) (nonatomic) (nonatomic) (nonatomic) (nonatomic) (nonatomic) (nonatomic) (nonatomic) CGFloat x; CGFloat y; CGPoint origin; CGFloat left; CGFloat right; CGFloat top; CGFloat bottom; CGSize size; CGFloat width; CGFloat height;
  28. 28. Oh, 直観的! #import "UIView+TSExtention.h" ... // myView1, myView2 が横に並んで 10pt 平行移動 [UIView animateWithDuration:0.25 animations:^{ myView1.left += 10; myView2.left = myView1.right + 5; }];
  29. 29. どうぞご利用下さい https://github.com/taketo1024/ UIView-TSExtension
  30. 30. 2. NSNull を黙らせる
  31. 31. ありきたりなサーバ通信 [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"]; }];
  32. 32. ありきたりなサーバ通信 [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 に表示
  33. 33. oh...
  34. 34. oh...
  35. 35. 原因 •レスポンスのJSON: { } "text": null, ... •[NSJSONSerialization @{ }; JSONObject...] の結果: @"text": [NSNull null], ... こうなってた
  36. 36. レスポンスを片っ端から NSNull チェックをする のが正しいんだけども…
  37. 37. nil はメッセージ投げても ヌルポとか出さないのに、 NSNull は自己主張が強い。
  38. 38. 黙らせる!
  39. 39. 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)); } ...(続く)
  40. 40. NSNull サイレンサーを実装 #import <objc/runtime.h> @implementation NSNull (TSSilencer) - (void *)silentGetter { NSLog(@"called: [NSNull %@]", NSStringFromSelector(_cmd)); return nil; } nil を返すだけの getter - (void)silentSetter:(void *)value { NSLog(@"called: [NSNull %@]", NSStringFromSelector(_cmd)); } ...(続く)
  41. 41. 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 ...(続く)
  42. 42. 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;
  43. 43. 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;
  44. 44. 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 { set*** なら silentSetter: Method getter = class_getInstanceMethod(self, @selector(silentGetter)); class_addMethod(self, sel, method_getImplementation(getter), method_getTypeEncoding(getter)); } return YES; を呼ばせ、
  45. 45. 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 を呼ばせる。
  46. 46. 試しにやってみる // 露骨にヤバい奴 _label.text = (id)[NSNull null];
  47. 47. おぉ、クラッシュしない! 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 の中でなんかやってるのが分かる
  48. 48. こんなのも行ける 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"]);
  49. 49. 余裕! called: [NSNull stringByAppendingString:] string: (null) called: [NSNull objectAtIndexedSubscript:] array: (null) called: [NSNull objectForKeyedSubscript:] dic: (null)
  50. 50. こんなことしていいんだろうか…
  51. 51. いいんです!!!
  52. 52. 大事なのは保守性と安全性。 どこまでやるかはあなた次第。
  53. 53. ご利用は計画的に https://github.com/taketo1024/ NSNull-TSSilencer
  54. 54. こちらもよろしく http://www.slideshare.net/taketo1024/ss-30036615
  55. 55. Thank you! @taketo1024

×