LoginSignup
10
11

More than 5 years have passed since last update.

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

Posted at

こちらの記事からの移植です。タイトルを付けたかったので。


Effective Objective-C 2.0を読んでいて見つけた方法。
ただし、書籍中ではできるだけ使わないように、との注意書きもあったのでメモとして書いています。

Associated Objectって?

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

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

#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 *型の変数を宣言していますが、以下のようにすることでも対応が可能なようです。
それ専用の変数などを作る必要がないのでいいかも。

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
10
11
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
11