Objective-C Language
低レベルのランタイム環境
サーチ…
備考
Objective-Cランタイムを使用するには、それをインポートする必要があります。
#import <objc/objc.h>
オブジェクトを別の既存のオブジェクトにアタッチする(アソシエーション)
あたかも新しいプロパティがあるかのように、オブジェクトを既存のオブジェクトにアタッチすることは可能です。これを関連といい、既存のオブジェクトを拡張することができます。これは、クラス拡張を介してプロパティを追加するときにストレージを提供したり、既存のオブジェクトに追加情報を追加したりするために使用できます。
関連付けられたオブジェクトは、ターゲットオブジェクトの割り当てが解除されると、ランタイムによって自動的に解放されます。
#import <objc/runtime.h>
// "Key" for association. Its value is never used and doesn't
// matter. The only purpose of this global static variable is to
// provide a guaranteed unique value at runtime: no two distinct
// global variables can share the same address.
static char key;
id target = ...;
id payload = ...;
objc_setAssociateObject(target, &key, payload, OBJC_ASSOCIATION_RETAIN);
// Other useful values are OBJC_ASSOCIATION_COPY
// and OBJ_ASSOCIATION_ASSIGN
id queryPayload = objc_getAssociatedObject(target, &key);
メソッドスウィズリングを使用したメソッドの拡張
Objective-Cランタイムでは、実行時にメソッドの実装を変更できます。これはメソッドスウィージングと呼ばれ、2つのメソッドの実装を交換するためによく使用されます。たとえば、メソッドfoo
とbar
が交換された場合、メッセージfoo
を送信すると、 bar
の実装が実行され、その逆も実行されbar
。
このテクニックは、システムが提供するクラスのメソッドなど、直接編集できない既存のメソッドを拡張または「パッチする」ために使用できます。
次の例では、 -[NSUserDefaults synchronize]
メソッドが拡張され、元の実装の実行時間を出力します。
重要:多くの人が、 method_exchangeImplementations
を使用してmethod_exchangeImplementations
をmethod_exchangeImplementations
うとします。ただし、置換するメソッドを呼び出す必要がある場合は、受け取る予定のセレクターとは異なるセレクターを使用して呼び出すため、この方法は危険です。結果として、あなたのコードが奇妙で予期しない方法で壊れる可能性があります。代わりに、C関数と組み合わせてsetImplementation
を使用して常にスウィズリングを行い、元のセレクタでメソッドを呼び出すことができます。
#import "NSUserDefaults+Timing.h"
#import <objc/runtime.h> // Needed for method swizzling
static IMP old_synchronize = NULL;
static void new_synchronize(id self, SEL _cmd);
@implementation NSUserDefaults(Timing)
+ (void)load
{
Method originalMethod = class_getInstanceMethod([self class], @selector(synchronize:));
IMP swizzleImp = (IMP)new_synchronize;
old_synchronize = method_setImplementation(originalMethod, swizzleImp);
}
@end
static void new_synchronize(id self, SEL _cmd);
{
NSDate *started;
BOOL returnValue;
started = [NSDate date];
// Call the original implementation, passing the same parameters
// that this function was called with, including the selector.
returnValue = old_synchronize(self, _cmd);
NSLog(@"Writing user defaults took %f seconds.", [[NSDate date] timeIntervalSinceDate:started]);
return returnValue;
}
@end
パラメータを取るメソッドを振る舞わせる必要がある場合は、それらを追加のパラメータとして関数に追加するだけです。例えば:
static IMP old_viewWillAppear_animated = NULL;
static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated);
...
Method originalMethod = class_getClassMethod([UIViewController class], @selector(viewWillAppear:));
IMP swizzleImp = (IMP)new_viewWillAppear_animated;
old_viewWillAppear_animated = method_setImplementation(originalMethod, swizzleImp);
...
static void new_viewWillAppear_animated(id self, SEL _cmd, BOOL animated)
{
...
old_viewWillAppear_animated(self, _cmd, animated);
...
}
メソッドの直接呼び出し
Objective-CメソッドをCコードから呼び出す必要がある場合は、 objc_msgSend
を使用するか、 IMP
(メソッド実装関数ポインタ)を取得して呼び出します。
#import <objc/objc.h>
@implementation Example
- (double)negate:(double)value {
return -value;
}
- (double)invert:(double)value {
return 1 / value;
}
@end
// Calls the selector on the object. Expects the method to have one double argument and return a double.
double performSelectorWithMsgSend(id object, SEL selector, double value) {
// We declare pointer to function and cast `objc_msgSend` to expected signature.
// WARNING: This step is important! Otherwise you may get unexpected results!
double (*msgSend)(id, SEL, double) = (typeof(msgSend)) &objc_msgSend;
// The implicit arguments of self and _cmd need to be passed in addition to any explicit arguments.
return msgSend(object, selector, value);
}
// Does the same as the above function, but by obtaining the method's IMP.
double performSelectorWithIMP(id object, SEL selector, double value) {
// Get the method's implementation.
IMP imp = class_getMethodImplementation([self class], selector);
// Cast it so the types are known and ARC can work correctly.
double (*callableImp)(id, SEL, double) = (typeof(callableImp)) imp;
// Again, you need the explicit arguments.
return callableImp(object, selector, value);
}
int main() {
Example *e = [Example new];
// Invoke negation, result is -4
double x = performSelectorWithMsgSend(e, @selector(negate:), 4);
// Invoke inversion, result is 0.25
double y = performSelectorWithIMP(e, @selector(invert:), 4);
}
objc_msgSend
は、メソッドのIMPを取得して呼び出します。最後に呼び出されたいくつかのメソッドのIMP
がキャッシュされるため、Objective-Cメッセージを非常にタイトなループで送信しても、許容できるパフォーマンスを得ることができます。場合によっては、IMPを手動でキャッシュすると、最後の最適化の最適化ではあるが、わずかに優れたパフォーマンスが得られることがあります。