[iOS] 学習の基礎となるオブジェクト

学習ブログ: [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 つの初期化メソッドがあることを意味します。

  • 初期化ではclsbitsデフォルト値はありません。
  • bits初期化ではclsデフォルト値はありません。

ここに画像の説明を挿入します

bits構造体変数:

  • nonpointertagpointer: iOS は oc オブジェクトを最適化しているため、このオブジェクトが型付きオブジェクトであるかどうかをマークするために使用されます。一部のオブジェクトはtagpointer型付きであるため、これらのオブジェクトにはポインターがありませんisatagpointerメモリは通常、ヒープではなくスタック内にあります。;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_rc9 になります。参照カウントが 10 を超える場合は、次の を使用する必要がありますhas_sidetable_rc

2.2isa初期化プロセス

基礎となる構造を理解したので、基礎となる実装を調べるために初期化プロセスisa_tを見てみましょう。初期化プロセスは、メソッドの基礎となるプロセスのこのステップで発生します。isaisa_talloc

    /// 将类和指针做绑定
    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、代入操作が実行された後、 が返されることがわかりますnewisaisa_tそこで、これの基礎となる実装を詳しく見てみましょう。ソースコードで定義されている共用体
を見てみましょう。runtimeisa_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クラスのメソッドリストなどの情報を格納します。

ここにあるのはtypebitsclass_data_bits_t、上記のobjc_objecttypeデータにもtypeisa_tがありますが、これらは 2 つの構造です。uintptr_tbits

最初に見る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 バイト方式を採用してスペースを解放します。これにより、オブジェクトのメモリスペースがより大きくなり、オブジェクトの安全性が高まります。

おすすめ

転載: blog.csdn.net/m0_63852285/article/details/131808859