このライブラリの基本的な機能を理解してみましょう。 この資料は、 コアラの従業員を訓練するために使用する講義に基づいています。
ランタイムとは何ですか?
Objective-CはC言語のアドオンとして考えられ、オブジェクト指向のパラダイムのサポートを追加しました。 実際、構文の観点から見ると、Objective-Cは通常のCのキーワードと制御構造のかなり小さなセットです。ランタイムライブラリであるランタイムは、言語に命を吹き込む機能セットを提供し、その動的機能を実現し、OOPの機能を保証します。
基本的なデータ構造
ランタイムライブラリの関数と構造は、いくつかのヘッダーファイルで定義されています:
objc.h
、
runtime.h
および
message.h
。 まず、
objc.h
ファイルを見て、ランタイムに関してオブジェクトが何であるかを見てみましょう。
typedef struct objc_class *Class; typedef struct objc_object { Class isa; } *id;
プログラムの処理中のオブジェクトは、通常のC構造で表されることがわかります。 各Objective-Cオブジェクトには、そのクラスへの参照(いわゆるisaポインター)があります。 アプリケーションのデバッグ中にオブジェクトの構造を表示すると、誰もがそれを見たと思います。 また、クラスは同様の構造を表します。
struct objc_class { Class isa; };
Objective-Cのクラスは本格的なオブジェクトであり、「クラスのクラス」へのisaポインター、つまりObjective-Cの観点からはメタクラスも持っています。 同様に、C構造は言語の他のエンティティに対して定義されます。
typedef struct objc_selector *SEL; typedef struct objc_method *Method; typedef struct objc_ivar *Ivar; typedef struct objc_category *Category; typedef struct objc_property *objc_property_t;
ランタイムライブラリ関数
ライブラリには、言語の基本構造の定義に加えて、これらの構造で機能する一連の関数が含まれています。 それらは条件付きでいくつかのグループに分けることができます(関数の目的は、原則として、その名前から明らかです):
- クラス操作:
class_addMethod
、class_addIvar
、class_replaceMethod
- 新しいクラスの作成:
class_allocateClassPair
、class_registerClassPair
- イントロスペクション:
class_getName
、class_getSuperclass
、class_getInstanceVariable
、class_getProperty
、class_copyMethodList
、class_copyIvarList
、class_copyPropertyList
- オブジェクトの操作:
objc_msgSend
、objc_getClass
、object_copy
- 連想リンクを使用する
例1.オブジェクトのイントロスペクション
ランタイムライブラリを使用した例を考えてみましょう。 私たちのプロジェクトの1つでは、データモデルはいくつかのプロパティセットを持つ単純な古いObjective-Cオブジェクトです。
@interface COConcreteObject : COBaseObject @property(nonatomic, strong) NSString *name; @property(nonatomic, strong) NSString *title; @property(nonatomic, strong) NSNumber *quantity; @end
デバッグの便宜上、
<COConcreteObject: 0x71d6860>
ようなものではなく、ログに出力するときにオブジェクトプロパティの状態に関する情報を出力したいと思います。 データモデルは非常に分岐しているため、さまざまなサブクラスが多数存在するため、プロパティの値を手動で収集するクラスごとに個別の
description
メソッドを
description
することは望ましくありません。 Objective-Cランタイムが役立ちます:
@implementation COBaseObject - (NSString *)description { NSMutableDictionary *propertyValues = [NSMutableDictionary dictionary]; unsigned int propertyCount; objc_property_t *properties = class_copyPropertyList([self class], &propertyCount); for (unsigned int i = 0; i < propertyCount; i++) { char const *propertyName = property_getName(properties[i]); const char *attr = property_getAttributes(properties[i]); if (attr[1] == '@') { NSString *selector = [NSString stringWithCString:propertyName encoding:NSUTF8StringEncoding]; SEL sel = sel_registerName([selector UTF8String]); NSObject * propertyValue = objc_msgSend(self, sel); propertyValues[selector] = propertyValue.description; } } free(properties); return [NSString stringWithFormat:@"%@: %@", self.class, propertyValues]; } @end
モデルオブジェクトの一般的なスーパークラスで定義されたメソッドは、
class_copyPropertyList
関数を使用して、オブジェクトのすべてのプロパティのリストを受け取ります。 次に、プロパティ値が
NSDictionary
に収集されます。これは、オブジェクトの文字列表現を構築するときに使用されます。 このアルゴリズムは、Objective-Cオブジェクトのプロパティでのみ機能します。 型チェックは
property_getAttributes
関数を使用して行われます。 メソッドの結果は次のようになります。
2013-05-04 15:54:01.992テスト[40675:11303] COConcreteObject:{
name = Foo;
数量= 10;
title = bar;
}
メッセージ
Objective-Cのメソッド呼び出しシステムは、オブジェクトにメッセージを送信することにより実装されます。 各メソッド呼び出しは、対応する
objc_msgSend
関数
objc_msgSend
変換され
objc_msgSend
。
// [array insertObject:foo atIndex:1]; // Runtime- objc_msgSend(array, @selector(insertObject:atIndex:), foo, 1);
objc_msgSent
の呼び出しは、関数に渡されたセレクターに対応するメソッドの実装を見つけるプロセスを開始します。 メソッドの実装は、いわゆるクラスディスパッチテーブルで求められます。 このプロセスは非常に時間がかかるため、メソッドキャッシュは各クラスに関連付けられています。 メソッドの最初の呼び出しの後、その実装の検索結果はクラスにキャッシュされます。 メソッドの実装がクラス自体で見つからない場合、このクラスのスーパークラスで継承階層を検索し続けます。 ただし、階層内で検索が行われなかった場合は、動的検索メカニズムが機能します
resolveInstanceMethod
または
resolveClassMethod
という特別なメソッドの1つが呼び出さ
resolveClassMethod
ます。 これらのメソッドをオーバーライドすることは、ランタイムに影響を与える最後の可能性の1つです。
+ (BOOL)resolveInstanceMethod:(SEL)aSelector { if (aSelector == @selector(myDynamicMethod)) { class_addMethod(self, aSelector, (IMP)myDynamicIMP, "v@:"); return YES; } return [super resolveInstanceMethod:aSelector]; }
ここで、呼び出されたメソッドの実装を動的に示すことができます。 何らかの理由でこのメカニズムが適切でない場合は、メッセージ転送を使用できます。
例2.メソッドのスウィズリング
Objective-Cのカテゴリの機能の1つは、カテゴリで定義されたメソッドが基本クラスメソッドを完全にオーバーライドすることです。 オーバーライドする必要はないが、既存のメソッドの機能を拡張する必要がある場合があります。 たとえば、何らかの理由で、
NSMutableArray
配列に追加するすべての要素を誓約したいとします。 標準言語ツールはこれを行いません。 ただし、メソッドスウィズリングと呼ばれる手法を使用できます。
@implementation NSMutableArray (CO) + (void)load { Method addObject = class_getInstanceMethod(self, @selector(addObject:)); Method logAddObject = class_getInstanceMethod(self, @selector(logAddObject:)); method_exchangeImplementations(addObject, logAddObject); } - (void)logAddObject:(id)aObject { [self logAddObject:aObject]; NSLog(@" %@ %@", aObject, self); } @end
load
メソッドをオーバーロードし
load
-これは、他のメソッドが呼び出される前に、クラスで定義されている場合、このクラスの初期化中に呼び出される特別なコールバックです。 ここで、ベースの
addObject:
メソッドと
logAddObject:
addObject:
実装を交換します。
logAddObject:
「再帰的」呼び出しに注意して
logAddObject:
これは、mainメソッドのオーバーロード実装への呼び出しです。
例3.連想リンク
カテゴリのもう1つの既知の制限は、カテゴリ内に新しいインスタンス変数を作成できないことです。 たとえば、
UITableView
ライブラリクラスに新しいプロパティを追加する必要があるとし
UITableView
-テーブルが空のときに表示される「スタブ」へのリンク:
@interface UITableView (Additions) @property(nonatomic, strong) UIView *placeholderView; @end
そのままでは、このコードは機能せず、プログラムの実行中に例外が発生します。 この問題は、連想リンク機能を使用して回避できます。
static char key; @implementation UITableView (Additions) -(void)setPlaceholderView:(UIView *)placeholderView { objc_setAssociatedObject(self, &key, placeholderView, OBJC_ASSOCIATION_RETAIN_NONATOMIC); } -(UIView *) placeholderView { return objc_getAssociatedObject(self, &key); } @end
objc_setAssociatedObject
関数を使用して他のオブジェクトを関連付けることにより、任意のオブジェクトを連想配列として使用できます。 その操作には、
objc_getAssociatedObject
の呼び出しを使用して必要なオブジェクトを取得できるキーが必要です。 同時に、コピーされたキー値を使用することはできません
objc_setAssociatedObject
呼び出しで渡されたオブジェクト(この例ではポインター)でなければなりません。
おわりに
これで、Objective-Cランタイムとは何か、実際に開発者にとってどのように役立つかについての基本的な理解ができました。 ライブラリの可能性をより深く知りたい人のために、以下の追加リソースをアドバイスできます。
- Objective-Cランタイムプログラミングガイド -Appleドキュメント
- Mike Ashブログ -Objective-C開発の第一人者の記事