Edited at

クラスを継承せず既存クラスにオブジェクトを保持する機能を追加する

More than 5 years have passed since last update.

=====================================================

Objective-Cはruntimeの力を借りて動いています。runtimeはObjective-Cのオブジェクト指向の各種機能、クラスを扱ったりメソッド呼び出しを対応するC言語関数を呼び出しに変換したりといったことをしています。

このruntimeを活用し、インスタンスにオブジェクトを保持する機能を追加します。この機能を追加すると、元のソースコードに手を加えたり、継承して新たなクラスを作成しなくてもオブジェクトを保持することが可能になります。


runtime関数

runtime関数を使う場合以下のファイルをインポートします。

#import <objc/runtime.h>

今回はAssociatedObjectの機能を使用します。

// objectからkeyに対応するオブジェクトを取得します。

id objc_getAssociatedObject(id object, void *key);

/*
objectにkeyをキーとしてvalueを設定します。
objc_AssociationPolicy は保持する値をretainするかしないか、nonatomicにするかどうかといったフラグです。
*/

void objc_setAssociatedObject(id object, void *key, id value, objc_AssociationPolicy policy);

void *型のキーを使いNSMutableDictionaryに近い使い方をします。


NSObjectをカテゴリで拡張する

runtime関数をそのまま使用してももちろん問題ありませんが、今回はNSObjectにAssociatedObjectの機能を追加してみます。


NSObject+SIATools.h

@interface NSObject (SIATools)

- (id)sia_associatedObjectForKey:(const void *)key;
- (void)sia_setAssociatedObject:(id)object forKey:(const void *)key;

@end



NSObject+SIATools.m

@implementation NSObject (SIATools)

- (id)sia_associatedObjectForKey:(const void *)key
{
return objc_getAssociatedObject(self, key);
}

- (void)sia_setAssociatedObject:(id)object forKey:(const void *)key
{
objc_setAssociatedObject(self, key, object, OBJC_ASSOCIATION_RETAIN);
}

@end


実装ファイル(.m)ではobjc/runtime.hをインポートします。

短いコードですので、カテゴリをわざわざ作らず必要に応じてruntime関数で実装してもいいかもしれません。ですがカテゴリにした場合、コード中に唐突にruntime関数が現れるということは避けられるので自分はこれを使用しています。


例 UIViewを拡張

AssociatedObjectの機能を使った例として、UIViewにデータを保持できるようにしてみます。

カテゴリを使うと既存クラスにメソッドを追加できますが、インスタンス変数は追加できません。そこでAssociatedObjectを使うことでインスタンス変数が追加されたのと同等の機能が実現できます。


UIView+SIATools.m

#import "NSObject+SIATools.h"


#define UIVIEW_USERINFO_KEY "UIVIEW_USERINFO_KEY"

@implementation UIView (SIATools)

- (NSDictionary *)userInfo
{
return [self sia_associatedObjectForKey:UIVIEW_USERINFO_KEY];
}

- (void)setUserInfo:(NSDictionary *)userInfo
{
[self sia_setAssociatedObject:userInfo forKey:UIVIEW_USERINFO_KEY];
}

@end


AssociatedObjectのキーとしてC言語文字列を使用しています。文字列リテラルはメモリ上に1個だけ作られるため、同じ文字列であれば必ず同じポインタの値となります。


例2

この記事はBlocksKitもどきの作り方の一部として書いています。興味のあるかたはこちらも御覧ください。

AssociatedObjectを使うとこんなことができるという一例です。