Block を使ってインスタンス毎にメソッド追加／上書きができるようになる REResponder

　Block を使ってインスタンス毎にメソッド追加／メソッド上書きができるようになる REResponder という NSObject の拡張を作ったので紹介します。REResponder は NSObject 拡張コレクション [REKit](https://github.com/zuccoi/REKit) の一部で、MIT ライセンスの下、GitHub にて公開しています。


# REResponder の基本


## 動的メソッド実装
　NSObject には -sayHello というメソッドはありませんが、以下のようにするとインスタンス obj に -sayHello メソッドを追加することができます。

```objectivec
id obj;
obj = [[NSObject alloc] init];
[obj respondsToSelector:@selector(sayHello) withKey:nil usingBlock:^(id receiver) {
   NSLog(@"Hello World!");
}];
[obj performSelector:@selector(sayHello)]; // Hello World!
```
obj 以外のインスタンスには影響を及ぼしません。


## 動的メソッド上書き
　-sayHello メソッドで "No!" をログる MyObject クラスのインスタンス obj があったとします。以下のようにすると "Hello World!" をログるように上書きすることができます。動的メソッド実装のときと同じ -respondsToSelector:withKey:usingBlock: を使用します。

```objectivec
MyObject *obj;
obj = [[MyObject alloc] init];
// [obj sayHello]; // No!
[obj respondsToSelector:@selector(sayHello) withKey:nil usingBlock:^(id receiver) {
   NSLog(@"Hello World!");
}];
[obj sayHello]; // Hello World!
```
こちらも、obj 以外のインスタンスには影響を及ぼしません。



# REResponder の活用例

　REResponder を使うと何が嬉しいのか？ 以下、GitHub 上の日本語 README で紹介している活用例のコードを転載します。面白そうだと思ったら、[日本語 README](https://github.com/zuccoi/REKit/blob/master/README_ja.md) 全文を読んでみてください。REResponder の機能、挙動、活用例の説明がもう少し詳しく書かれています。


## それ自身をデリゲートにする
　↓UIAlertView のデリゲートに UIAlertView 自身を設定しています。デリゲートメソッドが呼ばれたときの処理を続けて書けるなど、便利です。

```objectivec
UIAlertView *alertView;
alertView = [[UIAlertView alloc]
   initWithTitle:@"title"
   message:@"message"
   delegate:nil
   cancelButtonTitle:@"Cancel"
   otherButtonTitles:@"OK", nil
];
[alertView
   respondsToSelector:@selector(alertView:didDismissWithButtonIndex:)
   withKey:nil
   usingBlock:^(id receiver, UIAlertView *alertView, NSInteger buttonIndex) {
      // Do something…
   }
];
alertView.delegate = alertView;
```

##それ自身をターゲットにする
　↓UIButton のターゲットを UIButton 自身に設定しています。UIButton が沢山ある状況でアクションメソッドが呼ばれとき、まずはどのボタンが押されたのか調べる‥‥その手間がなくなります。

```objectivec
UIButton *button;
// …
[button respondsToSelector:@selector(buttonAction) withKey:@"key" usingBlock:^(id receiver) {
   // Do something…
}];
[button addTarget:button action:@selector(buttonAction) forControlEvents:UIControlEventTouchUpInside];
[cell.contentView addSubview:button];
```

##UnitTest で、モックオブジェクトを用意する
　↓BalloonController のデリゲートメソッドが呼ばれるかを、mock オブジェクトを用意することでテストしています。

```objective-c
__block BOOL called = NO;

// Make mock
id mock;
mock = [[NSObject alloc] init];
[mock
   respondsToSelector:@selector(balloonControllerDidDismissBalloon:)
   withKey:nil
   usingBlock:^(id receiver, BalloonController *balloonController) {
      called = YES;
   }
];
balloonController.delegate = mock;

// Dismiss balloon
[balloonController dismissBalloonAnimated:NO];
STAssertTrue(called, @"");
```

##UnitTest で、ハイコストな処理をスタブ化する
　↓AccountManager の画像ダウンロード処理をスタブ化して、accountViewController のテストをしています。


```objectivec
// Load sample image
__weak UIImage *sampleImage;
NSString *sampleImagePath;
sampleImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"sample" ofType:@"png"];
sampleImage = [UIImage imageWithContentsOfFile:sampleImagePath];

// Stub out download process
[[AccountManager sharedManager]
   respondsToSelector:@selector(downloadProfileImageWithCompletion:)
   withKey:@"key"
   usingBlock:^(id receiver, void (^completion)(UIImage*, NSError*)) {
      // Execute completion block with sampleImage
      completion(sampleImage, nil);
      
      // Remove current block
      [receiver removeCurrentBlock];
   }
];

// Call thumbnailButtonAction which causes download of profile image
[acccountViewController thumbnailButtonAction];
STAssertEqualObjects(accountViewController.profileImageView.image, sampleImage, @"");
```

##関心／機能をまとめる
　↓ノーティフィケーションの監視開始／終了コードを、普通ならファイル中いろいろなところに散在してしまうところ、-_manageKeyboardWillShowNotification メソッドにまとめています。

```objectivec
- (id)initWithCoder:(NSCoder *)aDecoder
{
   // super
   self = [super initWithCoder:aDecoder];
   if (!self) {
      return nil;
   }
   
   // Manage _keyboardWillShowNotificationObserver
   [self _manageKeyboardWillShowNotificationObserver];
   
   return self;
}

- (void)_manageKeyboardWillShowNotificationObserver
{
   __block id observer;
   observer = _keyboardWillShowNotificationObserver;
   
   #pragma mark └ [self viewWillAppear:]
   [self respondsToSelector:@selector(viewWillAppear:) withKey:nil usingBlock:^(id receiver, BOOL animated) {
      // supermethod
      REVoidIMP supermethod; // REVoidIMP is defined like this: typedef void (*REVoidIMP)(id, SEL, ...);
      if ((supermethod = (REVoidIMP)[receiver supermethodOfCurrentBlock])) {
         supermethod(receiver, @selector(viewWillAppear:), animated);
      }
      
      // Start observing
      if (!observer) {
         observer = [[NSNotificationCenter defaultCenter]
            addObserverForName:UIKeyboardWillShowNotification
            object:nil
            queue:[NSOperationQueue mainQueue]
            usingBlock:^(NSNotification *note) {
               // Do something…
            }
         ];
      }
   }];
   
   #pragma mark └ [self viewDidDisappear:]
   [self respondsToSelector:@selector(viewDidDisappear:) withKey:nil usingBlock:^(id receiver, BOOL animated) {
      // supermethod
      REVoidIMP supermethod;
      if ((supermethod = (REVoidIMP)[receiver supermethodOfCurrentBlock])) {
         supermethod(receiver, @selector(viewDidDisappear:), animated);
      }
      
      // Stop observing
      [[NSNotificationCenter defaultCenter] removeObserver:observer];
      observer = nil;
   }];
}
```

# リンク
[REKit](https://github.com/zuccoi/REKit)
[REKit 日本語 README](https://github.com/zuccoi/REKit/blob/master/README_ja.md)