===============================================
前提
この記事は以下の記事を前提としています。
上記の記事の内容を利用するとあらゆるdelegateをBlockで書けます。しかし本来書きたいcallback以外の記述が多く、結構面倒です。
そこでよく使うものはもっと気軽に書けるようにする方法を考えます。といっても難しいことはありません。カテゴリで拡張してしまえばいいわけです。
シンプルにBlockでcallbackを書く
callbackをBlockで書く、しかしその実現のための部分(SIABlockProtocol等)は見せないようにする。それを実現するために以下のようにかけるとよさそうです。
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:@"msg" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
[alertView sia_setClickedUsingBlock:^(NSInteger buttonIndex) {
NSLog(@"%s, %d", __PRETTY_FUNCTION__, buttonIndex);
}];
[alertView sia_setWillPresentUsingBlock:^{
NSLog(@"%s", __PRETTY_FUNCTION__);
}];
[alertView show];
上記でも十分かと思うのですが、UIAlertViewに関してはalertView:clickedButtonAtIndex:とshowの呼び出しさえできれば大抵はこと足りるので、以下の様なコンビニエンスメソッドもあると便利です。
UIAlertView *alertView = [[UIAlertView alloc] initWithTitle:nil message:@"msg" delegate:nil cancelButtonTitle:nil otherButtonTitles:@"OK", nil];
[alertView sia_showWithClicked:^(NSInteger buttonIndex) {
NSLog(@"%s, %d", __PRETTY_FUNCTION__, buttonIndex);
}];
少し冗長な書き方
あらゆるdelegateをBlockで実装するためのクラスの作り方 をご確認ください。
この記事ではsia_implementProtocol:setterSelector:usingBlock:
を使います。ただ自分でsetDelegate:
を呼ぶ書き方の方がインデントが減って見やすい場合もあります。
カテゴリで実装する
冗長な書き方をシンプルにするため、以下の様なカテゴリを作成します。
@interface UIAlertView (SIABlocks)
- (void)sia_showWithClicked:(void (^)(NSInteger buttonIndex))button;
- (void)sia_setClickedUsingBlock:(void (^)(NSInteger buttonIndex))block;
- (void)sia_setWillPresentUsingBlock:(void (^)())block;
@end
@implementation UIAlertView (SIABlocks)
- (void)sia_showWithClicked:(void (^)(NSInteger buttonIndex))block
{
[self sia_setClickedUsingBlock:block];
[self show];
}
- (void)sia_setClickedUsingBlock:(void (^)(NSInteger buttonIndex))block;
{
[self sia_implementProtocol:@protocol(UIAlertViewDelegate)
setterSelector:@selector(setDelegate:)
usingBlock:^(SIABlockProtocol *protocol)
{
[protocol addMethodWithSelector:@selector(alertView:clickedButtonAtIndex:)
block:^ void (SIABlockProtocol *protocol, UIAlertView *alertView, NSInteger buttonIndex)
{
block(buttonIndex);
}];
}];
}
- (void)sia_setWillPresentUsingBlock:(void (^)())block
{
[self sia_implementProtocol:@protocol(UIAlertViewDelegate)
setterSelector:@selector(setDelegate:)
usingBlock:^(SIABlockProtocol *protocol)
{
[protocol addMethodWithSelector:@selector(willPresentAlertView:)
block:^ void (SIABlockProtocol *protocol, UIAlertView *alertView)
{
block();
}];
}];
}
sia_showWithClicked:
は順番にメソッドを呼ぶだけ、sia_setClickedUsingBlock:
とsia_setWillPresentUsingBlock:
は内容がほぼ同じためsia_setClickedUsingBlock:
のみ説明します。
sia_setClickedUsingBlock:
の内容は少し冗長な書き方とほぼ同じです。ポイントはsia_setClickedUsingBlock:
の引数のBlockの型をどうするか? です。
addMethodWithSelector:
で登録するBlockの型はvoid (SIABlockProtocol *protocol, UIAlertView *alertView, NSInteger buttonIndex)
となります。このBlockの引数のうち本当に必要なもののみsia_setClickedUsingBlock:
の引数のBlockに渡します。
protocol
はUIAlertViewのdelegateと同等なのでそこから取得できますし、使う必要もおそらくないでしょう。
alertView
はBlock内から外のUIAlertViewの変数を参照できるため必要ありません。delegateのようにどのUIAlertViewからのcallbackかを判断する必要もなくなります。(callbackの引数でalertView
を渡してしまえば__weak変数を書く必要がないという利点があるのため、お好みで付けてもいいかもしれません。)
最終的に引数はNSInteger buttonIndex
さえあれば十分、となります。戻り値がある場合はそこももちろんあわせる必要があります。最終的にsia_setClickedUsingBlock:
の引数は (void (^)(NSInteger buttonIndex))block
とすればよさそうです。
- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex
というメソッドであれば、戻り値型を同じにして、SIABlockProtocolとdelegate通知元は捨ててしまい、それ以外の引数をBlockに渡すようにすれば必要なもののみ残したシンプルな形になります。それ以外のdelegateのメソッドも同じ考え方でシンプルにできます。
よく使うdelegateのcallbackをあらかじめ用意する
カテゴリを実装し不要な引数を省くことで、callbackを簡単に記述できるようになります。
- Blockを使えた方が便利で、比較的よく使うものはあらかじめ用意しておく
- あるプロジェクトでのみよく使うものはそのプロジェクト用に用意する
とすると作業が捗ります。以下のライブラリでは
UIActionSheet, UIAlertView, UIPickerView, UITextField と UIBarPositioningDelegate(UINavigationBar, UISearchBar, UIToolbar が実装している)をあらかじめ用意しています。
参考
この記事はBlocksKitもどきの作り方の一部として書いています。興味のあるかたはこちらも御覧ください。
この記事の内容の他に、UIControlやKVO、NSTimerのBlockでの実装等があります。