ラベル Objective-C Runtime API の投稿を表示しています。 すべての投稿を表示
ラベル Objective-C Runtime API の投稿を表示しています。 すべての投稿を表示

2011年11月27日日曜日

IMP, SEL, Method

IMP
まず IMP です。メソッド実装の先頭部分を指すポインタです。

This data type is a pointer to the start of the function that implements the method. This function uses standard C calling conventions as implemented for the current CPU architecture. The first argument is a pointer to self (that is, the memory for the particular instance of this class, or, for a class method, a pointer to the metaclass). The second argument is the method selector. The method arguments follow.
IMP はメソッドを実装する関数の先頭部分を指すポインタです。この関数は現在の CPU アーキテクテャのために実装された標準 C の呼出規約(calling convention)を使います。最初の引数は self(クラスの特定の具体的なインスタンスのためのメモリ、クラスメソッドのためのメモリ、メタクラスを指すポインタのためのメモリ)へのポインタです。2番目の引数はメソッドセレクタです。メソッドの引数は以下の通り。


SEL
そして SEL です。セレクタを表現する構造体で Opaque Type です。

Method selectors are used to represent the name of a method at runtime. A method selector is a C string that has been registered (or “mapped“) with the Objective-C runtime. Selectors generated by the compiler are automatically mapped by the runtime when the class is loaded.

You can add new selectors at runtime and retrieve existing selectors using the function sel_registerName.

When using selectors, you must use the value returned from sel_registerName or the Objective-C compiler directive @selector(). You cannot simply cast a C string to SEL.
メソッドのセレクタはランタイムでメソッドの名前表現として使われます。メソッドのセレクタは Objective-C に登録(またはマッピング)されている C 文字列です。コンパイラによって生成されたセレクタは、クラスがロードされるとき、自動的にランタイムによってマッピングされます。

実行時に新しいセレクタを追加し、sel_registerName 関数を使って存在しているセレクタを取得できます。

セレクタを使うとき、sel_registerName が返す値か、Objective-C のコンパイラディレクティブ @selector() を使わなければなりません。単純に C 文字列を SEL にキャストすることはできません。


Method
Method はクラスで定義されているメソッドを表現する構造体で Opaque Type です。リファレンスには

typedef struct objc_method *Method;
としかありませんが runtime.h にはさらに
struct objc_method {
    SEL method_name                                          OBJC2_UNAVAILABLE;
    char *method_types                                       OBJC2_UNAVAILABLE;
    IMP method_imp                                           OBJC2_UNAVAILABLE;
}
とあります。OBJC2_UNAVAILABLE は Objective-C 2.0 では無効ということのようで、Objective-C 2.0 の環境ではこの構造体に直接アクセスはできません。Method 構造体にのデータにアクセスするには専用(method_*)のランタイム関数を使ってやればよしです。

めも
http://opensource.apple.com/

2011年11月22日火曜日

Method Swizzling

Mac OSX で NSColor -> CGColor の変換をよく使いますが NSColor は CGColor を返すメソッドがありません(*1)せめて NSColor のメソッドが CGColor を返してくれるようになったら嬉しい。そんなわけで NSColor が CGColor を返してくれるようにカテゴリで拡張します。まずはソースコードから。

*1 NSColor のメソッドに - (CGColorRef)CGColor が追加されました。10.8 以降 使用できます。

NSColor+CGColor.h
#import <Foundation/Foundation.h>

@interface NSColor (CGColor)
- (CGColorRef)CGColor;
@end


NSColor+CGColor.m
#import "NSColor+CGColor.h"
#import <objc/runtime.h>

@interface NSColor (Swizzling)

- (void)methodToExchangeImplementation;
- (void)preDealloc;

@end

@interface NSColor (Accessor)

- (void)initializeCGColorCollection;
- (void)releaseColorCollection;
- (void)setCGColorRef:(CGColorRef)colorRef;

@end

@implementation NSColor (Swizzling)

static IMP PRE_DEALLOC_IMPLEMENTATION = NULL;
static IMP DEALLOC_IMPLEMENTATION = NULL;

- (void)methodToGetImplementation {
    
    // メソッド構造体を取得する
    Method preDealloc = class_getInstanceMethod([self class], @selector(preDealloc));
    Method dealloc = class_getInstanceMethod([self class], @selector(dealloc));
    
    // メソッド構造体からメソッド実装を取得する
    PRE_DEALLOC_IMPLEMENTATION = method_getImplementation(preDealloc);
    DEALLOC_IMPLEMENTATION = method_getImplementation(dealloc);
    
}

- (void)methodToExchangeImplementation {
    
    if (PRE_DEALLOC_IMPLEMENTATION == NULL || DEALLOC_IMPLEMENTATION == NULL) {
        
        [self methodToGetImplementation];
        
    }
    
    // メソッド構造体を取得する
    Method preDealloc = class_getInstanceMethod([self class], @selector(preDealloc));
    Method dealloc = class_getInstanceMethod([self class], @selector(dealloc));
    
    // メソッドの実装を互いに入れかえる
    method_exchangeImplementations(preDealloc, dealloc);
    
}

- (void)preDealloc {
    
    // 辞書に CGColorRef を加えたオブジェクトは dealloc の前に解放する。
    [self setCGColorRef:NULL];
    [self releaseColorCollection];
    
    // dealloc を実装から呼ぶ。
    DEALLOC_IMPLEMENTATION(self, _cmd);

}
@end

@implementation NSColor (Accessor)

static NSMutableDictionary *CGColorCollection = nil;

- (void)initializeCGColorCollection {
    
    @synchronized (self) {
        
        if (!CGColorCollection) {
            
            CGColorCollection = [[NSMutableDictionary dictionary] retain];
            [self methodToExchangeImplementation];
        }
    }
}

- (void)releaseColorCollection {
    
    @synchronized (self) {
        
        if ([CGColorCollection count] == 0) {
            
            [CGColorCollection release];
            CGColorCollection = nil;
            
            [self methodToExchangeImplementation];
        }
    }
}

- (void)setCGColorRef:(CGColorRef)newColorRef {
    
    NSString *aKey = [NSString stringWithFormat:@"%p",self];
    
    @synchronized (self) {
        
        NSValue *value = [CGColorCollection valueForKey:aKey];
        
        if (value) {
            
            CGColorRef colorRef = (CGColorRef )[value pointerValue];
            [CGColorCollection removeObjectForKey:aKey];
            CGColorRelease(colorRef);
            
        }
        
        if (newColorRef != NULL) {
            
            [CGColorCollection setValue:[NSValue valueWithPointer:newColorRef]
                                 forKey:aKey];
        } 
    }
}
@end

@implementation NSColor (CGColor)
- (CGColorRef)CGColor {
    
    [self initializeCGColorCollection];
    
    // 現在のカラーの情報を取得する。
    NSColorSpace *colorSpace = [self colorSpace];
    size_t numberOfComponents = (size_t)[self numberOfComponents];
    CGFloat *components = (CGFloat *)calloc(numberOfComponents, sizeof(CGFloat));
    [self getComponents:components];
    
    // CGColorRef を生成する。
    CGColorRef colorRef = CGColorCreate([colorSpace CGColorSpace], components);
    free(components);
    
    // 自身をキーに生成した CGColorRef を辞書に加える。
    [self setCGColorRef:colorRef];
    
    return colorRef;
}
@end

こう考えてみた
  1. 単に NSColor からCGColor オブジェクトのインスタンスを生成するのでなく、NSColor の寿命に応じて 生成した CGColor の寿命もシンクロさせる。
  2. カテゴリはインスタンス変数の追加が出来ないので CGColor オブジェクトを適当なスコープ内に定義したグローバル変数で管理する。
生成した CGColor を個別に解放してやるのはスマートではないと思ったので、NSColor の寿命に応じて生成した CGColor の寿命もシンクロさたいと思いました。そうすると、どのタイミングで CGColor オブジェクトを解放するかですが、インスタンスの参照カウンタが 0 になると -(void) dealloc が自動で呼ばれることを利用して、ランタイム API で CGColor が生成されているあいだ -(void) dealloc の実装を -(void) preDealloc の実装で入れかえることで解決します。

preDealloc で擬似的なクラス変数に見立てた CGColorCollection を管理してやります。CGColorCollection が CGColor を持たなければメソッドの実装を元に戻してやります。ランタイムは NSColor の dealloc だと思って preDealloc を呼出しているので、最終的に本来呼ばれるべき dealloc を実装から直接呼んでやります。