[Objective-C] Associated Objectを使いランタイムにプロパティを追加する


[こちらの記事](http://qiita.com/edo_m18/items/c0722b41686191e83597)からの移植です。タイトルを付けたかったので。

----

[Effective Objective-C 2.0](http://www.amazon.co.jp/gp/product/4798134198/ref=as_li_ss_tl?ie=UTF8&camp=247&creative=7399&creativeASIN=4798134198&linkCode=as2&tag=edom18-22)を読んでいて見つけた方法。
ただし、書籍中ではできるだけ使わないように、との注意書きもあったのでメモとして書いています。

## Associated Objectって？

Associate Objectは既存のクラスにカスタムデータを追加する仕組みです。
JavaScriptであればいくらでもプロパティが追加できますが、それと同様にして動的にカスタムデータを追加できるようです。

この方法には、ランタイムであるC関数を利用して、以下のようにします。

```objc
#import <objc/runtime.h>

static void *HogeAlertViewKey = "HogeAlertViewKey";

- (void)alertTest
{
	UIAlertView *alert = [UIAlertView alloc] initWithTitle:@"Title"
                                                   message:@"message"
                                                  delegate:self
                                         cancelButtonTitle:@"Cancel"
                                         otherButtonTitles:@"Other1", @"Other2", nil];

    void (^blk)(NSInteger) = ^(NSInteger buttonIndex) {
        if (buttonIndex == 0) {
            [self doCancel];
        }
        else {
            [self doSomething];
        }
    };

	objc_setAssociatedObject(alert, HogeAlertViewKey, blk, OBJC_ASSOCIATION_COPY);
	
	[alert show];
}

- (void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
	void (^blk)(NSInteger) = objc_getAssociatedObject(alertView, HogeAlertViewKey);
	blk(buttonIndex);
}
```

### 解説

やっていることは単純で、生成した`UIAlertView`のインスタンスに動的に、カスタムデータとしてblockを追加しています。
そして、デリゲートメソッド内でそれを取り出し、押された`buttonIndex`を引数にして呼び出している、というわけです。

こうすることによってAlertViewの生成と処理を同じ場所に書くことができ、またデリゲートメソッド内で複数のAlertViewに対する処理を書かなくて済む、というメリットがあります。

ただ、AlertViewのサブクラスを作ってblockで処理を指定できるようにするか、BlockKitを使う方法のほうがよりスマートだとは思います。
あくまでイチ手法としてメモしておきます。


### Associated Objectで使える関数郡

| 関数プロトタイプ | 説明 |
|----------------|---------|
| void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy) | 指定したキーとポリシーのもとで、Associated Objectをセットする |
| id objc_getAssociatedObject(id object, void *key) | オブジェクトの指定したキーを持つAssociated Objectの値を返す（カスタムデータの取り出し） |
| void objc_removeAssociatedObject(id object) | オブジェクトからすべてのAssociated Objectを削除する |

#### void *keyについて

サンプルコードでは`static void *HogeAlertViewKey = "HogeAlertViewKey";`としてグローバル領域に`void *`型の変数を宣言していますが、以下のようにすることでも対応が可能なようです。
それ専用の変数などを作る必要がないのでいいかも。

```objc:other-key
- (void)alertTest
{
	// 中略
	objc_setAssociatedObject(alert, @selector(alertView:clickedButtonAtIndex:), blk, OBJC_ASSOCIATION_COPY);
}

////////////////////////////////////////

- (void)alertView:(UIAlertView *)alertView
clickedButtonAtIndex:(NSInteger)buttonIndex
{
	void (^blk)(NSInteger) = objc_getAssociatedObject(alertView, _cmd);
}
```

このように、`@selector(anyMethod)`と、`objc_getAssociatedObject`を実行するメソッド（anyMethod）内で`_cmd`を参照することで、どちらも同じキーを参照することができます。


### Associated Objectのタイプ

Associated Objectのタイプは、通常のObjective-Cと同様、以下のようにポリシーを設定できます。

| Associated Objectのタイプ | 同じ意味の@property属性 |
|--------------------------|----------------------|
| OBJC_ASSOCIATION_ASSIGN | assign |
| OBJC_ASSOCIATION_RETAIN_NONATOMIC | nonatomic, retain |
| OBJC_ASSOCIATION_COPY_NONATOMIC | nonatomic, copy |
| OBJC_ASSOCIATION_RETAIN | retain |
| OBJC_ASSOCIATION_COPY | copy |