学習ブログ: [iOS] - OC オブジェクトの基礎となるレイヤーの探索
1. クラスとオブジェクト
1.1 クラスとオブジェクトの性質
1.1.1 オブジェクト
オブジェクトの本質は構造です。
@interface TestPerson : NSObject {
// 成员变量
// @public
NSString *_age; // 4个字节
}
@property (nonatomic, copy) NSString *name; // 属性
@end
@implementation TestPerson
@end
コンパイルによって生成された main.cpp ファイルを分析します。
#ifndef _REWRITER_typedef_TestPerson
#define _REWRITER_typedef_TestPerson
typedef struct objc_object TestPerson;
typedef struct {
} _objc_exc_TestPerson;
#endif
extern "C" unsigned long OBJC_IVAR_$_TestPerson$_name;
struct TestPerson_IMPL {
struct NSObject_IMPL NSObject_IVARS;
NSString *_age;
NSString *_name;
};
// @property (nonatomic, copy) NSString *name;
/* @end */
// @implementation TestPerson
static NSString * _I_TestPerson_name(TestPerson * self, SEL _cmd) {
return (*(NSString **)((char *)self + OBJC_IVAR_$_TestPerson$_name)); }
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);
static void _I_TestPerson_setName_(TestPerson * self, SEL _cmd, NSString *name) {
objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct TestPerson, _name), (id)name, 0, 1); }
// @end
この Testperson クラスを通じて、次のことがわかります。
- OC の Testperson クラスの最下層は、struct Testperson_IMPL 構造体です。
- OC の @interface Testperson: NSObject、Testperson は NSObject を継承し、基礎となる層は typedef struct objc_object Testperson; です。
- まず、パラメータ Testperson * self, SEL _cmd をカウントします。すべての OC メソッドにはこれら 2 つの隠しパラメータがあるため、OC メソッドで self ポインタを使用できますが、これら 2 つのパラメータは外部には表示されません。
- ゲッターとセッターは、最初のアドレス ポインター + 対応するメンバー変数のアドレス値ポインターのオフセットによって取得および格納され、最終的に (*(NSString **)) によって文字列型に復元されることがわかります。値を取得するプロセスは次のとおりです。まず現在のメンバー変数のアドレスを取得し、次にこのアドレスに格納されている値を取得します。
_age
メンバー変数があり、TestPersonal_IMPL 構造体で定義されています_name
。構造体も定義されていますstruct NSObject_IMPL NSObject_IVARS;
。
NSObject_IMPL の実装 (ランタイム ソース コード) は次のようになります。
struct objc_object {
private:
isa_t isa;
public:
....省略方法部分(该部分非常多)
この構造体には、isa_t 型の isa が格納されます。
isa_t 型には、オブジェクトが属するクラスに関する情報を格納するメンバー変数が 1 つだけあります [Class cls]
- Testperson クラスは、最下位レベルの Testperson_IMP 構造にコンパイルされます。
- メンバー NSObjct_IVARS、これは isa のみを含む構造体です。
- メンバーの年齢は、クラスのメンバー変数です。
- メンバー _name はクラスの属性であり、アンダースコア _ を使用してメンバーにコンパイルされます。
- 関数 _I_Testperson_name は、実際にはゲッター メソッドであり、2 つのデフォルト パラメーター self と _cmd が含まれています。
- 関数 _I_Testperson_setName_ は、実際にはセッター メソッドであり、2 つのデフォルト パラメーター self、_cmd、および正式なパラメーター名が含まれています。
編集結果に基づいて、次の結論を導き出すことができます。
- オブジェクトの本質は、最下位レベルの objc_object 構造です。
- プロパティとメンバ変数の違い プロパティはメンバ変数+ゲッターメソッド+セッターメソッドで構成されます。
1.1.2 クラス構造
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// 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
objc_class は objc_object を継承します。つまり、クラスの本質もオブジェクトです。isa ポインタ型は objc_object から継承されるため、コードはコメント化されています // Class ISA (isa はクラスのアドレスを格納する構造体であり、ポインタではありません。厳密に言えば、「isa が指す」という文は次のようになります)正しくはありませんが、理解するのは簡単です)。
- メソッドはキャッシュ内のbucket_t構造にキャッシュされます。
- メソッドは、class_rw_t の method_array_t 2 次元配列に格納されます。
1.2 オブジェクト、クラス、親クラス、メタクラス間の関係
1.2.1 オブジェクト
オブジェクトの本質は構造体であり、id はこの構造体へのポインタです。
1.2.2 クラス
クラスの本質もオブジェクトであり、isa ポインタ型は objc_object を継承します。
1.2.3 メタクラス
原則として、OC オブジェクトがメッセージを送信すると、ランタイム ライブラリはオブジェクトの isa ポインタを追跡して、オブジェクトが属するクラスを取得します。このクラスには、このクラスに適用できるすべてのインスタンス メソッドと、親クラスのインスタンス メソッドを見つけることができるようにする親クラスへのポインターが含まれています。ランタイム ライブラリは、このクラスとそのスーパークラスのメソッド リストを調べて、メッセージに対応するメソッドを見つけます。コンパイラはメッセージをメッセージ関数 objc_msgSend に変換して呼び出します。
場合によっては、クラスにメッセージを送信することもあります。
NSString *string = [NSString stringFormFormat@"string"];
ここから、OC クラスが実際にはオブジェクトであることがわかります。オブジェクトには、それが属するクラスが必要です。つまり、クラスには、そのクラスが属するクラスを指す isa ポインターも必要です。それでは、クラスのクラスとは何ですか? これはメタクラス (MetaClass) と呼ばれるもので、メタクラスはクラス オブジェクトが属するクラスです。
したがって、メッセージメカニズムの観点から見ると、次のようになります。
- メッセージをオブジェクトに送信すると、メッセージはオブジェクトのクラスのメソッド リストを検索します。
- メッセージをクラスに送信すると、メッセージはこのクラスのメタクラスのメソッド リストを検索します。
OCのクラス情報はどこに保存されていますか?
回答: オブジェクトのメソッド、属性、メンバー変数、プロトコル情報はクラス オブジェクトに格納され、クラス
メソッドはメタクラス オブジェクトに格納されます (メタクラス オブジェクトとクラス メモリ構造は同じですが、用途が異なります。主にクラス情報が含まれます)クラスメソッドなどの.が空の場合)
メンバ変数の特定の値がインスタンスオブジェクトに格納されます。
1.2.4 メタクラスクラス
それがルートクラスです
クラスに従ったクラスがメタクラスであるという論理的な理解がある場合、ループは永遠に継続できるのでしょうか?という疑問があります。
- 答えは明らかにそうではありません。最もプリミティブなクラスが存在するはずであり、その場合、このクラスはメタクラスのクラスになります。すべてのメタクラスはルート メタクラスをクラスとして使用します。ルート メタクラスの isa ポインタはそれ自体を指します。
1.2.5 関係図
2.詳しい説明はこちら
objc_object から継承された isa ポインタは、インスタンス オブジェクトだけでなく、クラス オブジェクトにも存在します。
2.1 isa_t 構造体
isa
ここでポインタ、つまりisa_t
構造体について詳しく説明します。
各 OC オブジェクトにはisa
ポインタが含まれています。__arm64__ より前は、isa はオブジェクトまたはクラス オブジェクトのメモリ アドレスを保存する単なるポインタでした。__arm64__ アーキテクチャの後、Apple はそれを最適化し、ユニオン構造に変えました。同時に、ビットフィールドを使用しisa
ます(union)
。より多くの情報を保存します。
isa_t
まずコード定義を見てみましょう。
union isa_t
{
Class cls;
uintptr_t bits;
//bits的结构体
struct {
uintptr_t nonpointer : 1;//->表示使用优化的isa指针
uintptr_t has_assoc : 1;//->是否包含关联对象
uintptr_t has_cxx_dtor : 1;//->是否设置了析构函数,如果没有,释放对象更快
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针
uintptr_t magic : 6;//->固定值,用于判断是否完成初始化
uintptr_t weakly_referenced : 1;//->对象是否被弱引用
uintptr_t deallocating : 1;//->对象是否正在销毁
uintptr_t has_sidetable_rc : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1
uintptr_t extra_rc : 19; //->存储引用计数,实际的引用计数减一,存储的是其对象以外的引用计数
};
};
isa_t
の定義からもわかるように、
cls
との 2 つのメンバーが提供されます。bits
共用体から、これら 2 つのメンバーは相互に排他的であることがわかります。つまり、isa ポインターを初期化するときに、2 つの初期化メソッドがあることを意味します。
- 初期化では
cls
、bits
デフォルト値はありません。 bits
初期化ではcls
デフォルト値はありません。
bits
構造体変数:
nonpointer
tagpointer
: iOS は oc オブジェクトを最適化しているため、このオブジェクトが型付きオブジェクトであるかどうかをマークするために使用されます。一部のオブジェクトはtagpointer
型付きであるため、これらのオブジェクトにはポインターがありませんisa
。tagpointer
メモリは通常、ヒープではなくスタック内にあります。;tagpointer
オブジェクトは通常、NSNumber
型の数が少なくなるか、NSString
型の文字列が小さくなります。has_assoc
: 関連付けられたオブジェクトがあるかどうかをマークするために使用されます。has_cxx_dtor
: オブジェクトに C++ または Objc デストラクターがあるかどうか。デストラクターがある場合は、破棄ロジックを実行する必要があります。デストラクターがない場合は、オブジェクトをより速く解放できます。shiftcls
: 格納されるのは、クラス オブジェクトのアドレスであるポインター アドレスです。magic
: 現在のオブジェクトが実際のオブジェクトであるか初期化されていない空間であるかを判断するためにデバッガーによって使用されます。weakly_referenced
: オブジェクトが ARC 弱変数を指しているか、かつて指していたかどうか 弱参照のないオブジェクトはより速く解放できます。deallocating
: オブジェクトがメモリを解放しているかどうかを示します。has_sidetable_rc
: オブジェクトが使用されているかどうかをマークしますSidetable
。オブジェクトの参照数が 10 を超える場合、キャリーを格納するためにこの変数を借用する必要があります。extra_rc
: オブジェクトの参照カウント値を示す場合、実際には参照カウント値から 1 を引いた値になります。たとえば、オブジェクトの参照カウントが 10 の場合、extra_rc
9 になります。参照カウントが 10 を超える場合は、次の を使用する必要がありますhas_sidetable_rc
。
2.2isa
初期化プロセス
基礎となる構造を理解したので、基礎となる実装を調べるために初期化プロセスisa_t
を見てみましょう。初期化プロセスは、メソッドの基礎となるプロセスのこのステップで発生します。isa
isa_t
alloc
/// 将类和指针做绑定
obj->initInstanceIsa(cls, hasCxxDtor);
このプロセスにはisa
、 の初期プロセスが含まれています。isa
の基礎となる実装を調査するには、以下isa
の作成から開始します。
inline void
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{
ASSERT(!cls->instancesRequireRawIsa());
ASSERT(hasCxxDtor == cls->hasCxxDtor());
//下方函数调用就是isa的初始过程
initIsa(cls, true, hasCxxDtor);
}
initIsa()
内容は以下の通りです。
inline void
objc_object::initIsa(Class cls)
{
initIsa(cls, false, false);
}
inline void
objc_object::initIsa(Class cls, bool nonpointer, UNUSED_WITHOUT_INDEXED_ISA_AND_DTOR_BIT bool hasCxxDtor)
{
ASSERT(!isTaggedPointer());
isa_t newisa(0);
if (!nonpointer) {
newisa.setClass(cls, this);
} else {
ASSERT(!DisableNonpointerIsa);
ASSERT(!cls->instancesRequireRawIsa());
#if SUPPORT_INDEXED_ISA
ASSERT(cls->classArrayIndex() > 0);
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
newisa.has_cxx_dtor = hasCxxDtor;
newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else
newisa.bits = ISA_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
# if ISA_HAS_CXX_DTOR_BIT
newisa.has_cxx_dtor = hasCxxDtor;
# endif
newisa.setClass(cls, this);
#endif
newisa.extra_rc = 1;
}
// This write must be performed in a single store in some cases
// (for example when realizing a class because other threads
// may simultaneously try to use the class).
// fixme use atomics here to guarantee single-store and to
// guarantee memory order w.r.t. the class index table
// ...but not too atomic because we don't want to hurt instantiation
isa = newisa;
}
isa_t
型のインスタンスがメソッドで作成されnewisa
、代入操作が実行された後、 が返されることがわかりますnewisa
。isa_t
そこで、これの基礎となる実装を詳しく見てみましょう。ソースコードで定義されている共用体
を見てみましょう。runtime
isa_t
union isa_t {
isa_t() {
}
isa_t(uintptr_t value) : bits(value) {
}
uintptr_t bits;
private:
// Accessing the class requires custom ptrauth operations, so
// force clients to go through setClass/getClass by making this
// private.
Class cls;
public:
#if defined(ISA_BITFIELD)
struct {
ISA_BITFIELD; // defined in isa.h
};
bool isDeallocating() {
return extra_rc == 0 && has_sidetable_rc == 0;
}
void setDeallocating() {
extra_rc = 0;
has_sidetable_rc = 0;
}
#endif
void setClass(Class cls, objc_object *obj);
Class getClass(bool authenticated);
Class getDecodedClass(bool authenticated);
};
isa_t
これは 2 つのメンバー変数 (1 つは 、bits
もう 1 つは )を持つ共用体です cls
。ユニオン内の変数は相互に排他的であることがわかっており、その利点はメモリの使用がより正確かつ柔軟になることです。つまり、 isa_t
初期化には 2 つの方法があります。
bits
値が割り当てられていcls
ないか、値が上書きされています。cls
割り当てられているか、bits
値がないか、値が上書きされます。
2.3 ISA_BITFIELD
isa_t
構造体にはメンバ変数もありISA_BITFIELD
、このマクロ定義はarm64とx86_64、つまりiOSとMacOSの実装に対応しています。ISA_BITFIELD
情報はビット フィールドを通じて保存され、保存される具体的な情報は次のとおりです。
# if __arm64__
// ARM64 simulators have a larger address space, so use the ARM64e
// scheme even when simulators build for ARM64-not-e.
# if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
# define ISA_MASK 0x007ffffffffffff8ULL
# define ISA_MAGIC_MASK 0x0000000000000001ULL
# define ISA_MAGIC_VALUE 0x0000000000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 0
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t weakly_referenced : 1; \
uintptr_t shiftcls_and_sig : 52; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 33; /*MACH_VM_MAX_ADDRESS 0x1000000000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 19
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# endif
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_HAS_CXX_DTOR_BIT 1
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t unused : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
// SUPPORT_PACKED_ISA
#endif
上記のビットフィールド ISA_BITFIELD に格納されている情報をより直観的に理解するために、上記の長いコードを分析する図を描きます。
arm64 アーキテクチャの下isa
:
x86_64 アーキテクチャの場合isa
:
また、ソースコードではassert()関数がよく使われていることが分かりましたが、この関数の機能は、
条件がエラーを返した場合にプログラムの実行を終了する機能です。
Assert の機能は、まず式 expression を評価することであり、その値が false (つまり 0) の場合、まずエラー メッセージを stderr に出力し、次に abort を呼び出してプログラムを終了します。
追加: isa_t を初期化するコードは次のとおりです。
newisa.bits = ISA_INDEX_MAGIC_VALUE;
// isa.magic is part of ISA_MAGIC_VALUE
// isa.nonpointer is part of ISA_MAGIC_VALUE
このことから、news.bits を初期化すると、非ポインタとマジック部分のみが設定され、他の部分は設定されないため、値はすべて 0 であることがよくわかります。
2.4 クラスメソッド
まず、クラスについて注意すべき点がいくつかあります。
class ObjectClass = [[nsobject class]class];
返されるものはclass
オブジェクトであって、meta-class
オブジェクトではありません。- (Class)class, +(Class)class
返されるのはクラスオブジェクトです- メタクラス オブジェクトと
class
メモリ構造は同じですが、用途が異なり、主にクラス メソッドのクラス情報が含まれ、その他は空です。 - クラスオブジェクトはメモリ上に一つだけのオブジェクトを持ち、主に
isa
ポインタsuper Class
、クラスの属性情報、クラスのオブジェクトメソッド情報、クラスのプロトコル情報、クラスのメンバ変数情報が含まれます。
//instance实例对象
NSObject *object1 = [[NSObject alloc] init];
NSObject *object2 = [[NSObject alloc] init];
//class对象,类对象
//objectClass1~5都是NSObject的类对象
Class objectClass1 = [object1 class];
Class objectClass2 = [object2 class];
Class objectClass3 = [NSObject class];
Class objectClass4 = object_getClass(object1);
Class objectClass5 = object_getClass(object2);
//元类对象(将类对象当作参数传入进去)
Class objectMetaClass = object_getClass([NSObject class]);
Class objectMetaClass2 = [[NSObject class] class];
//判断是不是元类对象
NSLog(@"instance - %p %p", object1, object2);
NSLog(@"class - %p %p %p %p %p %d", objectClass1,objectClass2, objectClass3, objectClass4, objectClass5, class_isMetaClass(objectClass3 ));
NSLog(@"mateClass - %p %p %d",objectMetaClass, objectMetaClass2, class_isMetaClass(objectMetaClass));
输出情况:
instance - 0x100511920 0x10050e840
class - 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0x7fff91da2118 0
mateClass - 0x7fff91da20f0 0x7fff91da2118 1
无论多少次class方法得到的都还是类函数。
もう一度objc_class
ソースコード部分を見てみましょう
struct objc_class : objc_object {
objc_class(const objc_class&) = delete;
objc_class(objc_class&&) = delete;
void operator=(const objc_class&) = delete;
void operator=(objc_class&&) = delete;
// Class ISA;
Class superclass;
cache_t cache; // 方法缓存 formerly cache pointer and vtable
class_data_bits_t bits; // 用于获取具体的类信息 class_rw_t * plus custom rr/alloc flagsflags
...
bits
クラスのメソッドリストなどの情報を格納します。
ここにあるのはtypebits
でclass_data_bits_t
、上記のobjc_object
typeデータにもtypeisa_t
がありますが、これらは 2 つの構造です。uintptr_t
bits
最初に見るbits
データ構造class_data_bits_t
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
}
}
実際、最も重要なのはそのうちの 2 つです。data
と の場合、2 つのメソッドはsafe_ro
それぞれclass_rw_t
と を返しますclass_ro_t
。
ここでのro_t
取得もdata
メソッドによって取得されるものであり、すべてがrw_t
含まれると理解できますro_t
。
struct class_data_bits_t {
friend objc_class;
// Values are the FAST_ flags above.
uintptr_t bits;
public:
class_rw_t* data() const {
return (class_rw_t *)(bits & FAST_DATA_MASK);
}
// Get the class's ro data, even in the presence of concurrent realization.
// fixme this isn't really safe without a compiler barrier at least
// and probably a memory barrier when realizeClass changes the data field
const class_ro_t *safe_ro() const {
class_rw_t *maybe_rw = data();
if (maybe_rw->flags & RW_REALIZED) {
// maybe_rw is rw
return maybe_rw->ro();
} else {
// maybe_rw is actually ro
return (class_ro_t *)maybe_rw;
}
}
}
class_rw_t ビットと class_ro_t ビットでclass_rw_t と class_ro_t
を取得するには 2 つの方法があります。
クラス_rw_t
上方代码省略...
const method_array_t methods() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
} else {
return method_array_t{
v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods()};
}
}
const property_array_t properties() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
} else {
return property_array_t{
v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
}
}
const protocol_array_t protocols() const {
auto v = get_ro_or_rwe();
if (v.is<class_rw_ext_t *>()) {
return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
} else {
return protocol_array_t{
v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
}
}
};
この 3 つのメソッドがあることがわかりrw_t
、それぞれクラスのメソッド、属性、プロトコルを取得できます。
クラスロト
もう一度データ構造を見てみましょうclass_ro_t
。
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
union {
const uint8_t * ivarLayout;
Class nonMetaclass;
};
explicit_atomic<const char *> name;
// With ptrauth, this is signed if it points to a small list, but
// may be unsigned if it points to a big list.
void *baseMethodList;
protocol_list_t * baseProtocols;
const ivar_list_t * ivars;
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;
// This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
_objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
_objc_swiftMetadataInitializer swiftMetadataInitializer() const {
if (flags & RO_HAS_SWIFT_INITIALIZER) {
return _swiftMetadataInitializer_NEVER_USE[0];
} else {
return nil;
}
}
const char *getName() const {
return name.load(std::memory_order_acquire);
}
//methodListPointerDiscriminator是 方法列表指针鉴别器
static const uint16_t methodListPointerDiscriminator = 0xC310;
#if 0 // FIXME: enable this when we get a non-empty definition of __ptrauth_objc_method_list_pointer from ptrauth.h.
static_assert(std::is_same<
void * __ptrauth_objc_method_list_pointer *,
void * __ptrauth(ptrauth_key_method_list_pointer, 1, methodListPointerDiscriminator) *>::value,
"Method list pointer signing discriminator must match ptrauth.h");
#endif
method_list_t *baseMethods() const {
#if __has_feature(ptrauth_calls)
method_list_t *ptr = ptrauth_strip((method_list_t *)baseMethodList, ptrauth_key_method_list_pointer);
if (ptr == nullptr)
return nullptr;
// Don't auth if the class_ro and the method list are both in the shared cache.
// This is secure since they'll be read-only, and this allows the shared cache
// to cut down on the number of signed pointers it has.
bool roInSharedCache = objc::inSharedCache((uintptr_t)this);
bool listInSharedCache = objc::inSharedCache((uintptr_t)ptr);
if (roInSharedCache && listInSharedCache)
return ptr;
// Auth all other small lists.
if (ptr->isSmallList())
ptr = ptrauth_auth_data((method_list_t *)baseMethodList,
ptrauth_key_method_list_pointer,
ptrauth_blend_discriminator(&baseMethodList,
methodListPointerDiscriminator));
return ptr;
#else
return (method_list_t *)baseMethodList;
#endif
}
下方代码省略...
メソッド、プロパティ、プロトコル、メンバー変数があることがわかります。ただし、メソッド、属性、プロトコルの命名はすべて Base によって有効になります。
3 メモリ調整ルール
3.1 アライメント係数
特定のプラットフォーム上の各コンパイラには、独自のデフォルトの「アライメント係数」(アライメント係数とも呼ばれます) があります。この係数は、プリコンパイル コマンド #pragma Pack(n)、n=1、2、4、8、16 を通じて変更できます。ここで、n は指定する「アライメント係数」です。iOS コンパイラー Xcode のアライメント係数は 8 です。
3.2 配置ルール
データ メンバーの配置規則: (Struct または Union のデータ メンバー) 最初のデータ メンバーはオフセット 0 に配置されます。これ以降、各データ メンバーの位置は min (アライメント係数、自身の長さ) の整数倍となり、次の位置はこのデータ メンバーの位置の整数倍として自動的に埋められなくなります。
データ メンバーは構造体です。データ メンバーは、最大長の整数倍の位置から格納されます。
全体的な配置ルール: データ メンバーが手順 1 および 2 に従って配置された後、データ メンバー自体も配置する必要があります。配置原則は min (配置係数、データ メンバーの最大長) の整数倍です。
3.3 OC におけるメモリアライメントの利点
システムによって割り当てられたメモリ サイズが、申請したメモリ サイズよりも大きいのはなぜだろうかと考えることがあります。8 バイト アライメント方式によれば、要求されたメモリがすでに冗長である可能性があります。
- 8 バイト アラインメントによれば、オブジェクト内のメンバー メモリ アドレスは絶対に安全です。
要求された追加バイトがオブジェクト間にあるかどうかはわかりません。追加バイトはオブジェクト メモリ セグメント内のどこかに現れる可能性があります。このとき、2 つのオブジェクト メモリ セグメントが隣り合っている状況が存在する可能性があります。これは安全ではありません。システムは 16 バイト方式を採用してスペースを解放します。これにより、オブジェクトのメモリスペースがより大きくなり、オブジェクトの安全性が高まります。