Skip to content
This repository has been archived by the owner on Jun 20, 2023. It is now read-only.

8.1 Blocks

ginrou edited this page May 14, 2013 · 3 revisions

BlocksはiOS4.0以降で使うことの出来る機能で、他のプログラミング言語ではクロージャやラムダ式と呼ばれるものです。 通信完了後に実行したい、アニメーション完了後に実行したい、あるいは配列のソートを行う時の順序の判定として使いたい、などのように様々な箇所で用いることができます。この章では、このBlocksの使い方と注意すべきポイントについて説明します。

Blocksの定義

Blocksの構文

Blocksの構文は以下のように書きます。

^戻り値の型(引数,..){ 式 }

と書いても、分かりづらいのでいくつか例を紹介します。

^BOOL(int a){ return a < 0; } // 基本的な使い方1

^int(NSString *str){ return str.length; } // 基本的な使い方2

// 返り値のないものも可能
^void(NSString *str){ NSLog("%@", str);}
^(NSString *str){ NSLog("%@", str);} // 返り値がない場合は省略できる

// 引数を取らない場合
^int(void){ return 10; }

// 返り値も引数もない場合
^void(void){ NSLog(@"blocks"); }
^{ NSLog(@"blocks"); } // 引数も返り値も省略できる

ちなみにこのコードは、コード中で 10; と書くようなもので意味はありません。

変数への代入と実行

Block構文をBlock型として宣言された変数に代入することもできます。Blockの実行はC言語における関数と同じように実行することができます。

void (^b)(int); // 戻り値がvoid型で引数としてint型の変数を取るblock型変数bの宣言

b = ^void(int a){ NSLog(@"%d", a); }; // b へ blockを代入

b(10); // bに代入されたblockを実行


BOOL (^b1)(int, int); // 戻り値がBOOL型で引数として二つのint型を取るblock型変数b1の宣言
b1 = ^BOOL(int x, int y){ return x < y; }; // block構文を変数へ代入

BOOL result = b1(10, 20); // block型変数b1を実行。resultにはYESが入る。

Blocksの実装はC言語に対する拡張機能のため、Block型の変数はC言語の変数と全く同じように使用することができます。 自動変数、static変数、グローバル変数などに使うことができます。

また、関数の引数, 戻り値としても利用することができます。

void func1( int (^tmp)(int) ) { // 引数として利用
    tmp(10);
}

// "int型を引数としてとり、double型"を返すBlock型を返り値として、float型の引数aを取る関数func2の定義
double (^func2(float a))(int) {
    return ^double(int b){ return a + b;};
}

// func2の使い方 //
double ^(my_block)(int) = func2(10);  
double val = my_block(20); // val = 10 + 20 が代入される

typedefを用いたBlockの型宣言

このBlock構文は複雑なため、ソースコードの可読性を下げてしまうなどの問題を引き起こしてしまいます。そのため、C言語の関数ポインタと同様にtypedefを用いて型宣言を行うことがよく行われます。

typedef 戻り値の型 ^(型の名前)(引数列)

例えば上記func1の場合、

typedef int (^blk1_t)(int)

とすることで

void func1( int (^tmp)(int) ) { // 引数として利用
    tmp(10);
}
↓
void func1(blk1_t tmp) {
    tmp(10);
}

とすることができます。

func2の場合,

typedef double (^blk2_t)(int)

とすることで、上記のコードは

blk2_t func2(float a){
    return ^double (int b){ return a + b};
}

// func2の使い方 //
blk2_t my_block = func2(10);
double val = my_block(20); // val = 10 + 20 が代入される

と書き直すことができます。

自動変数の値をキャプチャ

クロージャやラムダ式同様、Blocksも自動変数の値をキャプチャして取り込むことができます。

int a = 10;
void (^b1)() = ^{NSLog(@"a = %d", a);};
b1(); // コンソールに a = 10 と表示される

変数の値のキャプチャは、Block構文が実行されるタイミングに行われ、その時の変数の値がBlockの中では使われます。 例えば

int a = 10;
void (^b1)() = ^{NSLog(@"a = %d", a);};
b1(); // a = 10 と表示

a = 20;
b1(); // a = 10 と表示

このようにBlockオブジェクトを変数b1に代入した後にaの値を変化させても、Block内での値は変化しません

int a = 10;
void (^b1)() = ^{NSLog(@"a = %d", a);};
b1(); // コンソールに a = 10 と表示される

a = 20;
b1 = ^{NSLog(@"a = %d", a);};
b1(); // コンソールに a = 20 と表示される

aの値を変化させたあとに、もう一度Blockオブジェクトを代入すると、もちろん結果も変わります。Blockオブジェクトが実行されるタイミングでの値がキャプチャされます

__block

キャプチャした変数は、値を覗くことは出来ても書き換えることは出来ません。たとえば以下のようなコードをコンパイルすると

int a = 10;
void (^b1)() = ^{
    a = 20;
};

次のようにエラーが出ます。

 Variable is not assignable (missing __block type specifier)

Block内でキャプチャした変数の書き換えを行うには、変数宣言の際に __block指定子をつけます。

__block int a = 10;
void (^b1)() = ^{
    a = 20;
    NSLog(@"a = %d", a);
};
b1(); // コンソールに a = 20 と表示される

NSLog(@"a = %d", a); // ちなみにここでも a = 20 と表示されます

block内でのオブジェクトの操作

Blockの中でNSMutableArrayなどを操作したらどうなるでしょうか

NSMutableArray *array = [NSMutableArray arrayWithArray:@[@"hoge", @"fuga"]];
void (^b2)() = ^{
    [array addObject:@"piyo"];
};
b2();
NSLog(@"%@", array); // @"hoge", @"fuga", @"piyo"

エラーも発生せず、さらに三つ目のオブジェクト,"piyo"も代入されています。 このソースコードでキャプチャした自動変数はNSMutableArrayクラスのオブジェクトです。よりプリミティブな表現では、arrayはNSMutableArrayクラスの構造体へのポインタを指しています。そのため、array自体への代入はエラーとなりますが、キャプチャした自動変数を書き換えている訳ではないのでエラーとはなりません。

Blockオブジェクトをクラスのプロパティとして持つ場合の注意点

あるクラスのプロパティとしてBlockオブジェクトを持たせることはありえると思います。例えばjob queueのクラスを作った際にタスクをBlockで記述する、何かのタスクの実行完了後の処理をBlockで記述する、などのことがあり得ると思います。

そのようなとき、Blockプロパティからselfを参照すると循環参照に陥る危険性があります。

例として、次のようなクラスを考えてみます。

typedef void (^blk_t)();

@interface BlockSample : NSObject
@property (nonatomic, strong) NSString *name;
@property (nonatomic, strong) blk_t name_print_block;
@end

プロパティとしてnameと、シンプルなBlockオブジェクトを持ちます。 実装を以下のようにしてみました。

@implementation BlockSample

- (id)init
{
    self = [super init];
    if (self) {
        _name_print_block = ^{
            NSLog(@"%@", self.name);
        };
    }
    return self;
}

@end

name_print_blockでは、self.nameを出力するようにしています。 この状態でコンパイルすると、次のような警告が出ます。

Capturing 'self' strongly in this block is likely to lead to a retain cycle

これは次のことに起因します。

BlockSampleクラスはstrongプロパティとしてBlockを持っています。

@property (nonatomic, strong) blk_t name_print_block;

https://raw.github.com/mixi-inc/iOSTraining/master/Doc/Images/8.1/blocks_1.png

Blockが自動変数をキャプチャするタイミングで、オブジェクトはBlockから所有されます。 https://raw.github.com/mixi-inc/iOSTraining/master/Doc/Images/8.1/blocks_1.png

BlockSampleのインスタンスがBlockを所有し、キャプチャのタイミングでBlockオブジェクトがインスタンスを所有するために循環参照が発生します。 https://raw.github.com/mixi-inc/iOSTraining/master/Doc/Images/8.1/blocks_1.png

この循環参照を避けるためには、delegateメソッドパターンなどで用いられるように片方の参照をstrongからweakに変えてやればOKです。つまり、インスタンスとBlockのうち片方は所有し、もう片方は参照だけ持つ、という形にすればいいのです。 プロパティをweakにしてしまうと、スコープを抜けた時に解放されてしまうので、Blockからの参照を弱参照にします。

そのためには、__weak指定子を用いて弱参照のオブジェクトをBlockの中から参照するようにします。

__weak BlockSample *weakSelf = self;
_name_print_block = ^{
    NSLog(@"%@", weakSelf.name);
};

こうすることで、selfへの参照を弱参照にすることができ、循環参照を避けることができます。 https://raw.github.com/mixi-inc/iOSTraining/master/Doc/Images/9.1/blocks_1.png

※今回は説明の簡略化のために、Blocksの確保される領域については説明を省略しました。より正しくこの循環参照の問題について知りたい場合はこちらの本をお勧めします。

http://tatsu-zine.com/books/objc

宿題として

perlにあるgrepやmapはNSArrayにはありません。grepを行うNSArrayの拡張 NSArray(grep)を作ってください。

使用例

NSArray *before = @[ @"hoge", @"fuga", @"piyo", @1, @2, @3, @"foo", @"bar"];
NSArray *after = [before grep:^BOOL(id obj) {
                     return [obj isKindOfClass:[NSString class]];
                 }];
NSLog(@"%@", after); // @"hoge", @"fuga", @"piyo", @"foo", @"bar"

はじめに

  1. iOSについて

  2. Xcode最初のステッフ

  3. 導入

  4. Objective C の基礎

  5. メモリ管理

  6. 1.3 UIViewController1 UIViewController のカスタマイズ(xib, autoresizing)

  7. 1.3 UIViewController1 UIViewController のカスタマイズ(storyboard)

  8. UIViewController2 - ModalViewController

  9. UIViewController2 - ModalViewController(storyboard)

  10. UIViewController3 - ライフサイクル

  11. HomeWork 1 Objective C の基本文法

  12. HomeWork 2 UIViewControllerとModalViewController

  13. HomeWork 3 UIViewController + Animation

  14. UIKit 1 - container, rotate-

  15. UINavigationController

  16. UITabController

  17. Custom Container View Controller

  18. Supporting Multiple Interface Orientations

  19. HomeWork 1 - タブバーからモーダルビューを表示する

  20. HomeWork 2 - NavigationController

  21. HomeWork 2.3 デバイスことに回転対応

  22. UIKit 2- UIView -

  23. UIView

  24. UIView のカスタマイズ

  25. UIView Animation

  26. HomeWork 1 - UIScrollView

  27. UIKit 3 - table view -

  28. UITableView について

  29. UITableViewとNavigationController

  30. custom UITableViewCell の作成

  31. UITableViewのその他のオプション、カスタマイズ

  32. HomeWork 1 - Dynamic height with a custom uitableviewcell

  33. UIKit 4 - image and text -

  34. UIImagePickerController

  35. Assets Library

  36. UITextFiled, UITextView

  37. KeyboardNotification

  38. Homework 1 - フォトの複数枚選択

  39. ネットワーク処理

  40. NSURLConnection

  41. JSONのシリアライズとデシリアライズ

  42. UIWebView

  43. ローカルキャッシュと通知

  44. NSUserDefaults, Settings Bundle

  45. NSFileManager

  46. Key Value Observing

  47. NSNotification、NSNotificationCenter を用いた通知

  48. UILocalNotification

  49. Blocks, GCD

  50. Blocks

  51. GCD

  52. 【演習】GCD,-Blocksを用いたHTTPリクエストマネージャの作成

  53. 設計とデザインパターン

  54. クラス設計 1

  55. クラス設計 2

  56. [クラス設計演習] (https://github.com/mixi-inc/iOSTraining/wiki/9.3-%E3%82%AF%E3%83%A9%E3%82%B9%E8%A8%AD%E8%A8%88%E6%BC%94%E7%BF%92)

  57. 開発ツール

  58. Instruments, デバッガ

  59. CocoaPods

  60. テスト

  61. iOS開発におけるテスト

  62. GHUnit

  63. Kiwi

  64. KIF

  65. In-App Purchase

  66. In-App Purchase

  67. 付録

  68. Tips of Xcode

  69. Auto Layout 入門

  70. Auto Layout ドリル

Edit sidebar

Clone this wiki locally