Obj-Cクラスカテゴリーでインスタンス変数を追加する風な事。(特別付録:AppCode用スニペッド)


クラスカテゴリーでインスタンス変数の追加

・・・はできません。
しかし値を持たせるという意味では実現が可能です。
プロパティを利用するというか厳密にはプロパティではなくても大丈夫です。

Objective-Cでは例えば、setValue:(value), value()のようなメッセージを宣言すれば
インスタンス変数.valueの形で値の取得、代入(get, set)が可能です。

この場合、ヘッダーの方にsetValue:(value), value()の二つを宣言する必要がありますが、代わりに
@property(nonatomic)でプロパティを宣言しても良いといった感じです。(この方が可読性的にもよろしいかと思います。)

ヘッダー側
@property(nonatomic) NSValue value;
ボディ側
#import <objc/runtime.h>

// ~~~中略~~~

- (void)setValue:(NSValue *)value {
    objc_setAssociatedObject(self, @selector(value), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (NSValue *)value {
    return objc_getAssociatedObject(self, @selector(value));
}

objc_setAssociatedObject, objc_getAssociatedObjectの各第2引数は(const void*)型の共通のものを与えてやる必要がありますが、前述したようにプロパティを宣言してある場合、例のようにすれば、それを共通のキーにしてやることができます。

実用例

以下の例ではUIWebViewから呼び出されたアラートをブロックして完全に制御を上書きしてしまう実装を行ないます。
制御を奪った際に実行するブロックをカテゴリーに持たせます。

UIWebView+Override.h
#import <Foundation/Foundation.h>

typedef void (^JSAlertCallback)(UIWebView *webView, NSString *message, id webFrame);

@interface UIWebView (Override)
@property(nonatomic) JSAlertCallback jsAlertCallback;
@end
UIWebView+Override.m
#import <objc/runtime.h>
#import "UIWebView+Override.h"

#define __category_override __unused

@implementation UIWebView (Override)

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"

- (__category_override void)webView:(UIWebView *)sender runJavaScriptAlertPanelWithMessage:(NSString *)message initiatedByFrame:(id)frame {
    if (self.jsAlertCallback) self.jsAlertCallback(sender, message, frame);
}

#pragma GCC diagnostic pop

#pragma mark - Getters & Setters

- (void)setJsAlertCallback:(JSAlertCallback)callback {
    objc_setAssociatedObject(self, @selector(jsAlertCallback), callback, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

- (JSAlertCallback)jsAlertCallback {
    return objc_getAssociatedObject(self, @selector(jsAlertCallback));
}

#undef __category_override

@end

ちなみにこの例のコードは次のようにして利用します。(あくまで利用例ですので標準APIに含まれていないクラスやメソッドが出てきます。) JavascriptからObjective-Cの機能を呼び出したい時などに利用出来ます。実行したい機能名、引数等をカンマ区切りなどでアラートを利用して送信するなどの工夫をしてください。

// intercept alerts from javascript and hook actions
webView.jsAlertCallback = ^(UIWebView *webView, NSString *message, id webFrame) {
    DDLogVerbose(@"Alert from javascript is intercepted (blocked). Received message: %@", message);

    // separate with comma and remove unnecessary whitespaces
    NSMutableArray *components = [NSMutableArray arrayWithArray:[message componentsSeparatedByString:@","]];
    for (int i = 0; i < [components count]; i++) {
        components[i] = [CKStringUtils stringByTrimmingLeadingAndTrailingWhitespaceCharactersInString:components[i]];
    }

    if ([components count] > 0) {
        NSString *method = components[0];
        [components removeObject:method];
        [self performActionRequestedByJS:method args:components];
    }
};

付録

AppCode用スニペッドです。

catprop
// Put on header
@property(nonatomic) $PROP_CLASS$ *$PROPERTY_NAME$;

// Put on body
-(void)set$CAPITALIZED_PROPERTY_NAME$:($COPY_PROP_CLASS$ *) $COPY_PROPERTY_NAME$ {
    objc_setAssociatedObject(self, @selector($COPY_PROPERTY_NAME$), $COPY_PROPERTY_NAME$, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

-($COPY_PROP_CLASS$ *) $COPY_PROPERTY_NAME$ {
    return objc_getAssociatedObject(self, @selector($COPY_PROPERTY_NAME$));
}
$END$

こんな感じの設定画面ですね。
catpropって名前にしてます。(カテゴリープロパティの略。)

穴埋め用変数たち($で囲われているところ)は次のように設定しておけばおk。

AppCode用スニペッドはこっちにもあり〼。
http://qiita.com/GeneralD/items/d3fc624898dc05c58d2a

アディオスアミーゴ。