ぴよログ

↓に移転したのでこっちは更新されません、多分。

カテゴリを使って既存クラスにプロパティを追加する

移転しました →

Objective-Cではカテゴリを使って既存のクラスを拡張してメソッドを追加することができるが、メソッドだけでなくプロパティを追加することもできる。

例えばこんな風にUIViewのカテゴリを新規作成し、ヘッダーにプロパティを定義する。今回はNSStringにした。

// UIView+PropertySample.h
#import <UIKit/UIKit.h>

@interface UIView (PropertySample)

@property (nonatomic, weak) NSString *value;

@end

これだけ書くと以下のような警告が出る。

Property 'value' requires method 'value' to be defined - use @dynamic or provide a method implementation in this category
Property 'value' requires method 'setValue:' to be defined - use @dynamic or provide a method implementation in this category

@dynamicを使うか、valuesetValue:を定義しろと言われている。@dynamicはアクセサを自動生成しないという指定だった気がするから、今回は@dynamicを書いた上で、value/setValue:を定義した。

// UIView+PropertySample.m
#import "UIView+PropertySample.h"
#import <objc/runtime.h>

@implementation UIView (PropertySample)

@dynamic value;

- (void)setValue:(NSString*)value
{
    objc_setAssociatedObject(self, _cmd, value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSString*)value
{
    return objc_getAssociatedObject(self, @selector(setValue:));
}

@end

objc_getAssociatedObjectobjc_setAssociatedObjectという関数を使うためにobjc/runtime.hをインポートしておく。

これらの関数は既存のオブジェクトに他のオブジェクトを関連付けるというもので、ここではプロパティを擬似的に表現するために使っている。

ポイントはsetterの_cmdとgetterの@selector(setValue:)。同じオブジェクトに複数のオブジェクトを関連付けられるため、どの関連付けに対するset/getなのかを明確にするためのキーが必要になる。ここではsetValue:のアドレスを用いている。キーには一意なポインタが必要であり、セレクタを使うのは理にかなっているような気がする。

ひとまずこれでカテゴリに使ったプロパティの追加ができた。