Classの構造およびメソッドキャッシュ
9583 ワード
runtimeソースコードから見たClassの構造は次の通りです.
次のフィールドをそれぞれ説明します. superclass:親へのポインタ cache:呼び出されたメソッドキャッシュ bits:特定のクラス情報を取得するための class_rw_t:クラス具体情報の構造体は、bits&FAST_DATA_MASKは を得た
次にclassを見てみましょうrw_tにはどのような情報が含まれていますか、class_rw_t構造は以下の通りである.
class_rw_tにはメソッド、プロパティ、プロトコルなどが含まれており、roがあります.このroはclass_を指します.ro_tオブジェクト、class_ro_tにはクラス初期の情報が含まれており,読み取り専用である.
class_ro_tの構成は以下の通りである.
class_rw_t中のmethods、properties、protocolsは2次元配列であり、クラスの初期および分類の方法、属性、プロトコルを含む読み書き可能である.最初はclassがなかったrw_t,class_rw_tは実行時に作成されclass_ro_tの内容と分類の内容が追加されます.
上記の結論は、次のruntimeのソースコードから見ることができます.一部のコードを削除し、上記のフローのみを保持します.
上記のソースコード注釈から、この関数はクラスの最初の初期化時に実行され、最初はrwがなく、classのbitsはroを指していることがわかります.rwはroをrwに付与するroを作成し、clsにrwを付与するbitsを作成し、最後に注釈から分類を処理する内容であることがわかる.
関数methodizeClassのソースコードは次のとおりです.
上記のソースコードから分かるように、rwからroを見つけて中のbaseMethods、baseProperties、baseProtocolsを取り出し、rw対応のmethods、properties、protocolsに追加します.最後に追加されていない分類内容を取り出して追加します.分類メソッドを追加する方法、すなわちattachCategories関数の具体的な実装については、AboutCategoryを参照してください.
メソッドのキャッシュ
メソッドの呼び出しは、毎回クラス、親クラス、メタクラスの検索効率が低い場合、runtimeで呼び出されたメソッドをキャッシュし、クラスのcacheに格納します.
メソッドをキャッシュする前に、メソッドの最下位構造を理解します. imp:関数の具体的な実装 SEL:メソッド関数名を表し、メソッドアドレッサとも呼ばれ、下位構造はchar*と似ています. types:戻り値とパラメータ符号化を含む文字列iOSは@encodeの命令を提供し、具体的なタイプを文字列符号化として表すことができる.下表(一部code) を参照.
code
Meaning
c
A char
i
An int
s
A short
l
A long
q
A long long
c
An unsigned char
I
An unsigned int
S
An unsigned short
L
An unsigned long
Q
An unsigned long long
f
A float
d
A double
B
A C++ bool or a C99 _Bool
V
A void
*
A charactor string(char *)
@
An object(whether statically typed or typed id)
:
A method selector(SEL)
^type
A pointer to type
例を挙げるとtypes="i20@0:8 i 16",OCのメソッドはidタイプのselfとSELにデフォルトで入力され,次の意味を表す.
戻り値のタイプ
パラメータの全長
パラメータ1タイプおよび開始位置
パラメータ2タイプおよび開始位置
パラメータ3タイプおよび開始位置
intタイプ
20
idタイプ
SEL
intタイプ
キャッシュcache_を見てみましょうtの構造:
Classのメソッドキャッシュ(cache_t)はハッシュテーブルで実現され,メソッドの検索効率を向上させることができる.
ハッシュテーブルとも呼ばれるハッシュテーブル(Hash table)は、キー値(Key value)に基づいて直接アクセスするデータ構造である.つまり、キー値をテーブルの1つの場所にマッピングすることでレコードにアクセスし、検索を高速化します.このマッピング関数をハッシュ関数と呼び,記録を格納する配列をハッシュリストと呼ぶ.与えられたテーブルMには、関数f(key)が存在し、任意の与えられたキーワード値keyに対して、関数を代入した後、そのキーワードを含むテーブルに記録されたアドレスを得ることができれば、テーブルMをハッシュ(Hash)テーブル、関数f(key)をハッシュ(Hash)関数と呼ぶ.
一般的なハッシュテーブルは、ターゲットkey&または%前の値によって、ハッシュテーブルの位置の下付きラベルを得る.クラスメソッドキャッシュはメソッドSELをkeyとして使用します&maskは位置の下付きラベルを得て、メソッドアドレスをハッシュテーブルに格納する.ハッシュ・テーブルの下付きラベルは0から始まり、最大ハッシュ・テーブルの長さを1つ減らします.これも_maskサイズがハッシュテーブルの長さが1つ減少した理由.
メソッドキャッシュ関数のソースコードを次に示します.
上記のソースコードから、ハッシュリストの最小長さが4であり、3/4容量に達したときに拡張され、テーブルに必ず挿入可能な位置があることがわかる.挿入前に、メソッドがテーブルに格納されているかどうかを検索します.保存されていません.occupiedに1を追加します.
キャッシュメソッドを検索する関数を見てみましょう.
異なるkey&maskで得られる下付き文字は同じである可能性があるため、key&maskで初期下付き文字を得た後、ハッシュリストの下付き文字の対を持つbucketオブジェクトを手に入れてkeyをターゲットkeyと比較し、等しい表示が私たちが望むbucketである.異なる場合はi-1の位置をループし、i=0の場合はmaskの位置のbucketが望ましいかどうかを確認します.遍歴した後にまだ正しいものが見つからない場合は、いくつかのエラー処理をして、具体的にcache_を検索します.t::bad_Cache関数を表示します.
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();
}
......
}
// bits.data();
class_rw_t* data() {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
次のフィールドをそれぞれ説明します.
次にclassを見てみましょうrw_tにはどのような情報が含まれていますか、class_rw_t構造は以下の通りである.
struct class_rw_t {
uint32_t flags;
uint32_t version;
const class_ro_t *ro; //
method_array_t methods; //
property_array_t properties; //
protocol_array_t protocols; //
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
......
};
class_rw_tにはメソッド、プロパティ、プロトコルなどが含まれており、roがあります.このroはclass_を指します.ro_tオブジェクト、class_ro_tにはクラス初期の情報が含まれており,読み取り専用である.
class_ro_tの構成は以下の通りである.
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize; //instance
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name; //
method_list_t * baseMethodList; //
protocol_list_t * baseProtocols; //
const ivar_list_t * ivars; //
const uint8_t * weakIvarLayout;
property_list_t *baseProperties; //
method_list_t *baseMethods() const {
return baseMethodList;
}
};
class_rw_t中のmethods、properties、protocolsは2次元配列であり、クラスの初期および分類の方法、属性、プロトコルを含む読み書き可能である.最初はclassがなかったrw_t,class_rw_tは実行時に作成されclass_ro_tの内容と分類の内容が追加されます.
上記の結論は、次のruntimeのソースコードから見ることができます.一部のコードを削除し、上記のフローのみを保持します.
/***********************************************************************
* realizeClass
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClass(Class cls)
{
runtimeLock.assertWriting();
const class_ro_t *ro;
class_rw_t *rw;
Class supercls;
Class metacls;
bool isMeta;
if (!cls) return nil;
if (cls->isRealized()) return cls;
assert(cls == remapClass(cls));
ro = (const class_ro_t *)cls->data(); // bits ro
if (ro->flags & RO_FUTURE) {
// This was a future class. rw data is already allocated.
rw = cls->data();
ro = cls->data()->ro;
cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {
// Normal class. Allocate writeable class data.
rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
rw->ro = ro;
rw->flags = RW_REALIZED|RW_REALIZING;
cls->setData(rw); // rw ro rw ro, rw cls bits
}
// Attach categories
methodizeClass(cls);
return cls;
}
上記のソースコード注釈から、この関数はクラスの最初の初期化時に実行され、最初はrwがなく、classのbitsはroを指していることがわかります.rwはroをrwに付与するroを作成し、clsにrwを付与するbitsを作成し、最後に注釈から分類を処理する内容であることがわかる.
関数methodizeClassのソースコードは次のとおりです.
static void methodizeClass(Class cls)
{
runtimeLock.assertWriting();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
}
上記のソースコードから分かるように、rwからroを見つけて中のbaseMethods、baseProperties、baseProtocolsを取り出し、rw対応のmethods、properties、protocolsに追加します.最後に追加されていない分類内容を取り出して追加します.分類メソッドを追加する方法、すなわちattachCategories関数の具体的な実装については、AboutCategoryを参照してください.
メソッドのキャッシュ
メソッドの呼び出しは、毎回クラス、親クラス、メタクラスの検索効率が低い場合、runtimeで呼び出されたメソッドをキャッシュし、クラスのcacheに格納します.
メソッドをキャッシュする前に、メソッドの最下位構造を理解します.
struct method_t {
SEL name; //
const char *types; // ( 、 )
IMP imp; // ( )
};
typedef id (*IMP)(id, SEL, ...);
code
Meaning
c
A char
i
An int
s
A short
l
A long
q
A long long
c
An unsigned char
I
An unsigned int
S
An unsigned short
L
An unsigned long
Q
An unsigned long long
f
A float
d
A double
B
A C++ bool or a C99 _Bool
V
A void
*
A charactor string(char *)
@
An object(whether statically typed or typed id)
:
A method selector(SEL)
^type
A pointer to type
例を挙げるとtypes="i20@0:8 i 16",OCのメソッドはidタイプのselfとSELにデフォルトで入力され,次の意味を表す.
戻り値のタイプ
パラメータの全長
パラメータ1タイプおよび開始位置
パラメータ2タイプおよび開始位置
パラメータ3タイプおよび開始位置
intタイプ
20
idタイプ
SEL
intタイプ
キャッシュcache_を見てみましょうtの構造:
struct cache_t {
struct bucket_t *_buckets; // ,
mask_t _mask; // - 1
mask_t _occupied; //
}
struct bucket_t {
cache_key_t _key; // SEL key
IMP _imp; //
}
Classのメソッドキャッシュ(cache_t)はハッシュテーブルで実現され,メソッドの検索効率を向上させることができる.
ハッシュテーブルとも呼ばれるハッシュテーブル(Hash table)は、キー値(Key value)に基づいて直接アクセスするデータ構造である.つまり、キー値をテーブルの1つの場所にマッピングすることでレコードにアクセスし、検索を高速化します.このマッピング関数をハッシュ関数と呼び,記録を格納する配列をハッシュリストと呼ぶ.与えられたテーブルMには、関数f(key)が存在し、任意の与えられたキーワード値keyに対して、関数を代入した後、そのキーワードを含むテーブルに記録されたアドレスを得ることができれば、テーブルMをハッシュ(Hash)テーブル、関数f(key)をハッシュ(Hash)関数と呼ぶ.
一般的なハッシュテーブルは、ターゲットkey&または%前の値によって、ハッシュテーブルの位置の下付きラベルを得る.クラスメソッドキャッシュはメソッドSELをkeyとして使用します&maskは位置の下付きラベルを得て、メソッドアドレスをハッシュテーブルに格納する.ハッシュ・テーブルの下付きラベルは0から始まり、最大ハッシュ・テーブルの長さを1つ減らします.これも_maskサイズがハッシュテーブルの長さが1つ減少した理由.
メソッドキャッシュ関数のソースコードを次に示します.
static void cache_fill_nolock(Class cls, SEL sel, IMP imp, id receiver)
{
cacheUpdateLock.assertLocked();
// Never cache before +initialize is done
if (!cls->isInitialized()) return;
// Make sure the entry wasn't added to the cache by some other thread
// before we grabbed the cacheUpdateLock.
if (cache_getImp(cls, sel)) return;
cache_t *cache = getCache(cls);
cache_key_t key = getKey(sel);
// Use the cache as-is if it is less than 3/4 full
mask_t newOccupied = cache->occupied() + 1;
mask_t capacity = cache->capacity();
if (cache->isConstantEmptyCache()) {
// Cache is read-only. Replace it.
cache->reallocate(capacity, capacity ?: INIT_CACHE_SIZE);
}
else if (newOccupied <= capacity / 4 * 3) {
// Cache is less than 3/4 full. Use it as-is.
// 3/4
}
else {
// Cache is too full. Expand it.
cache->expand();
}
// Scan for the first unused slot and insert there.
// There is guaranteed to be an empty slot because the
// minimum size is 4 and we resized at 3/4 full.
// , 。 4 3/4 ,
bucket_t *bucket = cache->find(key, receiver);
if (bucket->key() == 0) cache->incrementOccupied();// _occupied
bucket->set(key, imp);
}
上記のソースコードから、ハッシュリストの最小長さが4であり、3/4容量に達したときに拡張され、テーブルに必ず挿入可能な位置があることがわかる.挿入前に、メソッドがテーブルに格納されているかどうかを検索します.保存されていません.occupiedに1を追加します.
キャッシュメソッドを検索する関数を見てみましょう.
bucket_t * cache_t::find(cache_key_t k, id receiver)
{
assert(k != 0);
bucket_t *b = buckets();
mask_t m = mask();
mask_t begin = cache_hash(k, m); //key & mask
mask_t i = begin;
do {
if (b[i].key() == 0 || b[i].key() == k) {
return &b[i];
}
} while ((i = cache_next(i, m)) != begin);
// hack
Class cls = (Class)((uintptr_t)this - offsetof(objc_class, cache));
cache_t::bad_cache(receiver, (SEL)k, cls);
}
// arm64 cache_next
static inline mask_t cache_next(mask_t i, mask_t mask) {
return i ? i-1 : mask;
}
異なるkey&maskで得られる下付き文字は同じである可能性があるため、key&maskで初期下付き文字を得た後、ハッシュリストの下付き文字の対を持つbucketオブジェクトを手に入れてkeyをターゲットkeyと比較し、等しい表示が私たちが望むbucketである.異なる場合はi-1の位置をループし、i=0の場合はmaskの位置のbucketが望ましいかどうかを確認します.遍歴した後にまだ正しいものが見つからない場合は、いくつかのエラー処理をして、具体的にcache_を検索します.t::bad_Cache関数を表示します.