blog

ランタイム(1)の概念と原則

Obj-Cは動的言語です。コーディングの最初の段階では、変数の正確な型はわかりませんし、どの関数が呼び出されているかもわかりません。実行時のみ変数のデータ型をチェックし、同時に実行時のみ関数名に従って...

Jul 2, 2020 · 9 min. read
シェア

I. 概念

Obj-Cは動的言語です。変数の正確な型はコーディングの最初の段階ではわかりませんし、どの関数が呼び出されるのかもわかりません。変数のデータ型がチェックされるのは実行時だけであり、呼び出される特定の関数を見つけるために関数名が使われるのも実行時だけです。

Obj-Cが、コンパイルとリンクの段階から実行の段階にいくつかの決定的な作業を延期するメカニズムによって、Obcj-Cはより柔軟になります。

ランタイムはObj-Cの動的実装の基本です。

メッセージング・メカニズムの基礎

1.コンパイル段階: [レシーバーセレクタ]; メソッドはコンパイラによって

  • objc_msgSend(receiver, selector)
  • objc_msgSend(recevier, selector, org1, org2, ...)

2.ランタイムフェーズ:メッセージ 受信 者が対応する セレクタを探します。

  • 1.受信機の isa ポインタから、受信機に対応する クラスを探します;
  • 2. クラス・ キャッシュの リストから、対応する IMP を探します;
  • 3.対応する IMP が キャッシュに 見つからない場合は、 クラスの メソッド・リストで探し続け、見つかればキャッシュに入れ、セレクタに戻ります;
  • 4.クラス内に セレクタが 見つからない場合は、その スーパークラスで 探し続けます。
  • 5. セレクタが 見つからない場合は、メッセージを転送するか、対応する セレクタの実装メソッドを一時的に 受信 機に追加します。

objc/runtime.hでは 、classは objc_class構造体への ポインタとして定義されています。

ソースコードは以下の通りです。

struct objc_class {
 Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
 Class super_class OBJC2_UNAVAILABLE;
 const char *name OBJC2_UNAVAILABLE;
 long version OBJC2_UNAVAILABLE;
 long info OBJC2_UNAVAILABLE;
 long instance_size OBJC2_UNAVAILABLE;
 struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
 struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
 struct objc_cache *cache OBJC2_UNAVAILABLE;
 struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;

上記は古いもので、新しいものは以下のように実装されています。


struct objc_class : objc_object {
 // Class ISA;
 Class superclass;
 cache_t cache; // formerly cache pointer and vtable
 class_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags
 class_rw_t *data() { 
 return bits.data();
 }
 void setData(class_rw_t *newData) {
 bits.setData(newData);
 }
 void setInfo(uint32_t set) {
 assert(isFuture() || isRealized());
 data()->setFlags(set);
 }
 void clearInfo(uint32_t clear) {
 assert(isFuture() || isRealized());
 data()->clearFlags(clear);
 }
 // set and clear must not overlap
 void changeInfo(uint32_t set, uint32_t clear) {
 assert(isFuture() || isRealized());
 assert((set & clear) == 0);
 data()->changeFlags(set, clear);
 }
 bool hasCustomRR() {
 return ! bits.hasDefaultRR();
 }
 void setHasDefaultRR() {
 assert(isInitializing());
 bits.setHasDefaultRR();
 }
 void setHasCustomRR(bool inherited = false);
 void printCustomRR(bool inherited);
 bool hasCustomAWZ() {
 return ! bits.hasDefaultAWZ();
 }
 void setHasDefaultAWZ() {
 assert(isInitializing());
 bits.setHasDefaultAWZ();
 }
 void setHasCustomAWZ(bool inherited = false);
 void printCustomAWZ(bool inherited);
 bool requiresRawIsa() {
 return bits.requiresRawIsa();
 }
 void setRequiresRawIsa(bool inherited = false);
 void printRequiresRawIsa(bool inherited);
 bool canAllocIndexed() {
 assert(!isFuture());
 return !requiresRawIsa();
 }
 bool canAllocFast() {
 assert(!isFuture());
 return bits.canAllocFast();
 }
 bool hasCxxCtor() {
 // addSubclass() propagates this flag from the superclass.
 assert(isRealized());
 return bits.hasCxxCtor();
 }
 void setHasCxxCtor() { 
 bits.setHasCxxCtor();
 }
 bool hasCxxDtor() {
 // addSubclass() propagates this flag from the superclass.
 assert(isRealized());
 return bits.hasCxxDtor();
 }
 void setHasCxxDtor() { 
 bits.setHasCxxDtor();
 }
 bool isSwift() {
 return bits.isSwift();
 }
#if SUPPORT_NONPOINTER_ISA
 // Tracked in non-pointer isas; not tracked otherwise
#else
 bool instancesHaveAssociatedObjects() {
 // this may be an unrealized future class in the CF-bridged case
 assert(isFuture() || isRealized());
 return data()->flags & RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS;
 }
 void setInstancesHaveAssociatedObjects() {
 // this may be an unrealized future class in the CF-bridged case
 assert(isFuture() || isRealized());
 setInfo(RW_INSTANCES_HAVE_ASSOCIATED_OBJECTS);
 }
#endif
 bool shouldGrowCache() {
 return true;
 }
 void setShouldGrowCache(bool) {
 // fixme good or bad for memory use?
 }
 bool shouldFinalizeOnMainThread() {
 // finishInitializing() propagates this flag from the superclass.
 assert(isRealized());
 return data()->flags & RW_FINALIZE_ON_MAIN_THREAD;
 }
 void setShouldFinalizeOnMainThread() {
 assert(isRealized());
 setInfo(RW_FINALIZE_ON_MAIN_THREAD);
 }
 bool isInitializing() {
 return getMeta()->data()->flags & RW_INITIALIZING;
 }
 void setInitializing() {
 assert(!isMetaClass());
 ISA()->setInfo(RW_INITIALIZING);
 }
 bool isInitialized() {
 return getMeta()->data()->flags & RW_INITIALIZED;
 }
 void setInitialized();
 bool isLoadable() {
 assert(isRealized());
 return true; // any class registered for +load is definitely loadable
 }
 IMP getLoadMethod();
 // Locking: To prevent concurrent realization, hold runtimeLock.
 bool isRealized() {
 return data()->flags & RW_REALIZED;
 }
 // Returns true if this is an unrealized future class.
 // Locking: To prevent concurrent realization, hold runtimeLock.
 bool isFuture() { 
 return data()->flags & RW_FUTURE;
 }
 bool isMetaClass() {
 assert(this);
 assert(isRealized());
 return data()->ro->flags & RO_META;
 }
 // NOT identical to this->ISA when this is a metaclass
 Class getMeta() {
 if (isMetaClass()) return (Class)this;
 else return this->ISA();
 }
 bool isRootClass() {
 return superclass == nil;
 }
 bool isRootMetaclass() {
 return ISA() == (Class)this;
 }
 const char *mangledName() { 
 // fixme can't assert locks here
 assert(this);
 if (isRealized() || isFuture()) {
 return data()->ro->name;
 } else {
 return ((const class_ro_t *)data())->name;
 }
 }
 
 const char *demangledName(bool realize = false);
 const char *nameForLogging();
 // May be unaligned depending on class's ivars.
 uint32_t unalignedInstanceSize() {
 assert(isRealized());
 return data()->ro->instanceSize;
 }
 // Class's ivar size rounded up to a pointer-size boundary.
 uint32_t alignedInstanceSize() {
 return word_align(unalignedInstanceSize());
 }
 size_t instanceSize(size_t extraBytes) {
 size_t size = alignedInstanceSize() + extraBytes;
 // CF requires all objects be at least 16 bytes.
 if (size < 16) size = 16;
 return size;
 }
 void setInstanceSize(uint32_t newSize) {
 assert(isRealized());
 if (newSize != data()->ro->instanceSize) {
 assert(data()->flags & RW_COPIED_RO);
 *const_cast<uint32_t *>(&data()->ro->instanceSize) = newSize;
 }
 bits.setFastInstanceSize(newSize);
 }
};

お分かりのように、 objc_classの古い書き方では、多くの変数を定義していました:

ivars 自身のインスタンス変数
methodLists メソッド一覧
protocols プロトコル
...

objc_class 構造体に格納されているデータをメタデータと呼びます。 objc_class 構造 体に格納されているデータは メタデータと呼ばれます。

objc_class 構造体の最初のメンバ変数は isa ポインタです。 isa ポインタは、objc_class 構造体のインスタンスへのポインタです。言い換えれば、クラスの本質は実際にはクラス・オブジェクトと呼ばれるオブジェクトなのです。

新しいランタイムはメソッド・リストと変数を class_rw_tに 入れます

オブジェクト

Objectのソースコードは以下の通りです。

/// Represents an instance of a class.
struct objc_object {
 Class isa OBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;

idはobjc_objectへのポインタとして定義され、ソースコードからわかるように、objc_objectはClass型のisaポインタだけを含んでいます。

オブジェクトに対してメソッドが呼び出されると、 objc_objectのisaポインタをたどって対応するobjc_classを見つけ、 objc_classの中の対応するメソッドやインスタンス変数などを探します。

メタクラス

メタクラスとは 、クラスオブジェクトが属するクラスのことです。オブジェクトが属するクラスをクラスオブジェクト、クラスオブジェクトが属するクラスをメタクラスと呼びます。

Runtimeでは、クラスオブジェクトが属する型をメタクラスと呼び、クラスオブジェクト自体の特徴を記述するために使用します。メタクラスのmethodListsには、クラスのメソッドチェインリストが保持され、これを「クラスメソッド」と呼びます。メタクラスのメソッドリストには、クラスのメソッドチェインリストが格納されます。各クラス・オブジェクトには、1 つのメタクラスしか関連付けられていません。

クラス・メソッドの呼び出し方は、オブジェクト・メソッドの呼び出し方と似ています:

  • 1.クラス・オブジェクトのisaポインタを通してメタクラスを検索します;
  • 2.メタクラスのメソッドリストから、対応するセレクタを見つけます。
  • 次の章では、ランタイムの仕組みと実際の応用について説明します。
インスタンスオブジェクト、クラス、メタクラスの関係

上の図は3つの関係を示しています。

isa ポインタ:

  • 1.水平方向では、各レベルのインスタンス・オブジェクトの isa ポインタは対応するクラス・オブジェクトを指し、クラス・オブジェクトの isa ポインタは対応するメタクラスを指します。すべてのメタクラスの isa ポインタは最終的に NSObject メタクラスを指すため、NSObject メタクラスはルート・メタクラスとも呼ばれます。
  • 2.縦方向では、メタクラスの isa ポインタと親のメタクラスの isa ポインタは、ルートのメタクラスを指します。ルート・メタクラスの isa ポインタは、それ自身を指します。
メソッド

object_class構造体のmethodListsに格納されている要素はMethodsです。

ソースコードは以下の通りです。

///末尾のコメントからわかるように、これは古い書き方である。
struct objc_method {
 SEL method_name OBJC2_UNAVAILABLE;
 char *method_types OBJC2_UNAVAILABLE;
 IMP method_imp OBJC2_UNAVAILABLE;
} 

新しい記事は以下の通りです。

struct method_t {
 SEL name;
 const char *types;
 IMP imp;
 struct SortBySELAddress :
 public std::binary_function<const method_t&,
 const method_t&, bool>
 {
 bool operator() (const method_t& lhs,
 const method_t& rhs)
 { return lhs.name < rhs.name; }
 };
};

構造はほとんど変わらず、ソート構造が追加されました。

見ての通り、method_name、method_types、method_impは新旧の書き方に関係なく含まれています。

1. SEL メソッド名; // メソッド名

/// An opaque type that represents a method selector.
typedef struct objc_selector *SEL;

SELはobjc_selectorへのポインターで、SELの中身はメソッド名を格納した文字列です。

2, IMP method_imp; // メソッドの実装

/// A pointer to the function of a method implementation. 
#if !OBJC_OLD_DISPATCH_PROTOTYPES
typedef void (*IMP)(void /* id, SEL, ... */ ); 
#else
typedef id (*IMP)(id, SEL, ...); 
#endif

IMPは基本的に、メソッドの実装を指す関数ポインタです。IMPは関数のアドレスを見つけ、関数を実行するために使用されます。

3. char *method_types.

メソッド型 method_types は、メソッドのパラメータ型と戻り値型を格納する文字列です。

ランタイムの概念と基本は上記の通りですが、ここではランタイムの仕組みと実際の応用について書いていきます。
Read next

Picassoソースコード commit 注:メインスレッドかどうかを判断するには?

画像の取得がImageViewに表示されることを意図していない場合があることを考慮し、ここでは特別にお願いします。Bitmapのget()メソッドでは、ImageViewが設定されていないリクエストオブジェクトを生成し、picassoのrunメソッドを直接呼び出しています。このサブミッションは ...

Jul 2, 2020 · 2 min read