Objective-C
Xcode
iOS

Blocksを活用する事例と小技

More than 3 years have passed since last update.


概要

Blocksはみなさんよく使いますでしょうか。

私は最初objective-cを勉強したときにBlocksのややこしい書き方を見て

ああこれは絶対使えるようにならないと思ったのですが、

使えば使うほど便利な場面が多くて今ではなるべく使えないか考えるようにしています。

そこで基本をおさらいつつ、どんなときに使っているか書きたいと思います。


そもそもBlocksってなに?

ざっくり言うと関数を式として宣言、作成することが出来る仕組みです。

なので、関数をそのまま変数のように関数に受け渡すことが出来ます。

調べると結構説明が難しい説明がたくさん書いてあるのですが、

「関数を変数として扱える仕組み」、と覚えておけばひとまずは良いかなと思います。

書き方はこのあたりを参考にすると良いかと思います。

■ブロック構文の基本的な使い方

http://www.objectivec-iphone.com/introduction/block/block.html

書き方がややこしいのですが、軽く説明すると

typedef void (^AlertCompletion)(int); //ブロックの型を定義

typedef 戻り値 (^変数名)(引数);

AlertCompletionという名前の変数があり、

この変数の戻り値はvoid、intが渡す引数の型になります。

使う時はこんな感じです。

- (void)didTapAlertViewButton:(AlertComletion)completion {

completion(self.index);
}

self.indexが渡され、あとはAlertCompletionを定義した側の処理が実行されます。


何がいいのよ

公式ドキュメントにはこう書かれています。

https://developer.apple.com/jp/documentation/Blocks.pdf

ブロックを使用して関数を記述し、それをAPIに渡したり、必要に応じて格納したり、マルチスレッ

ドで使用することもできます。ブロックは、コールバックとして特に便利です。それは、ブロック
が、コールバックで実行するコードと、その実行中に必要なデータの両方を保持するからです。

そう、コールバックの時にとても便利なのです。

例えばAlertViewを出したあとにボタンを押された時、とか何かの動作のあとや

オブジェクトが生成されたとき、とかにクラスを使う側が処理をかけるのがとても便利なのです。


実例

AlertView(今はdeprecateですが)を例にします。

もし、AlertViewを普通に使おうとしたらこんな感じになります。


A.m

- (void)showErrorAlert {

UIAlertView *alert = [[UIAlertView alloc] initWithTitle:@"エラー"
message:@"アイテムがありません!確認してください。" 
delegate:self
cancelButtonTitle:@"確認する"
otherButtonTitles:nil];
[alert show];
}

// delegate
-(void)alertView:(UIAlertView*)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex {
NSLog(@"hogehoge");
}


1回しか使わない、とかならよいんですが、Alert系ってどのアプリでもほとんどの場合

複数回使うと思いますし、そのたびにクラスにデリゲートメソッドを書くとコード量も多くなりますし

いちいちどんな処理をするか確認しなければいけないのであまり良くありません。

そこでブロックの出番です!!

ブロックを使うと以下のようにかけます。


A.m

- (void)showErrorAlert {

[Alert show:@"エラー"
message:@"アイテムがありません!確認してください。" 
completion:^(NSInteger buttonIndex) {
NSLog(@"hogehoge");
}
}


とってもかんたん!

デリゲートをいちいち指定する必要もないし、アラートで何か操作をしたあとに

次に何が起こるかすぐ書けるし、読む側も何が起こるかわかるので読みやすい。

それに、忘れがちなエラー判定もこれで防げる。

Alertクラスは以下のように実装しておく。


Alert.h

// AlertViewのラッパークラス

@interface AlertUtil : NSObject
typedef void (^AlertCompletion) (NSInteger buttonIndex); // ブロックの型宣言

+ (void)show:(NSString *)title
message:(NSString *)message
completion:(AlertCompletion)completion;



```Alert.m
// ブロック型のプロパティにはcopyを指定します
@propety (copy,nonatomic) AlertCompletion completion;

+ (void)show:(NSString *)title
message:(NSString *)message
completion:(AlertCompletion)completion {

//alertViewの初期化など色々
~~~
~~~
self.completin = completion
}

- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex {
if(!self.completion) return;
self.completion(buttonIndex);
}

ポイントとしてAlertViewのデリゲートは使った側のクラスに設定するのではなく

Alertクラス内で持たせるようにします


Alert.m

- (instansType)initWith~

self.delegate = self;
}

こうするとことでAlertクラスを使ってshowした時は全てAlertクラスのデリゲートに入り、

そこでblocksを使うことによりクラスを使う側でかんたんに処理が書けるようになります。


blocksの型が合わない時の小技

ViewControllerのdismissViewControllerAnimatedメソッドのcompletionに

blocksの型が合わない時がたまーにあります。

その時は、blocksの型をラップしてあげることで引き渡せるようになります。


ReviewSuggestViewController.h

typedef void (^ReviewSuggestCompletion) (BOOL result);



ReviewSuggestViewController.m

- (void)close:(BOOL)result {

// dissmiss~~の返り値がvoid、引数がvoidなのでラップしてあげる
void (^wrapper) (void) = ^(void){
wself.comletion(result);
}

[self dissmissViewControllerAnimated:NO completion:wrapper];
}


これでもともと用意されているものにもblocksを引き渡せます。便利ですね!


他にもこんな時に使えます


  • 通信処理が終わった時

  • ViewControllerが立ち上がった時

などなど…。

通信処理系のOSSではよく使われていますね。


注意事項

もう散々書かれていることだとは思うのですが、blocksをメンバ変数に持っているクラスで

selfをキャプチャするとそれだけで循環参照になるので、__weakプロパティをつけてwselfみたいな形にしてからblocksに渡してあげてください。


まとめ

書き方や注意事項が多いのですが、これをうまく使えると

コードがすっきり書けるので積極的に使えるところでは使っていきたいですね!