iOS 類與對象底層原理

iOS 類與對象底層原理

文章目錄

  • iOS 類與對象底層原理
    • 探索對象本質
    • objc_setProperty 源碼
    • cls與類的關聯原理
      • 聯合體
      • isa的類型isa_t
    • 原理探索
        • initIsa方法
        • 通過setClass方法中的shiftcls來驗證綁定的一個流程
        • 通過 isa & ISA_MSAK
        • 通過object_getClass
        • 通過位運算
    • 類&類的結構
      • 從終端調試開始認識調用邏輯
        • 什么是元類
          • 小結
        • NSObject到底有幾個?
          • 類存在幾份
      • isa走位繼承圖
      • object_class & objc_object
        • 這里的objc_class 與objc_object有什么關系?
        • objc_object與對象的關系
        • 總結
    • 類結構分析
      • 類信息中有哪些內容
        • 計算cache類的內存大小
        • bits
        • 探索property_list(屬性列表)
        • 探索methods_list
        • 從編譯期開始介紹class_ro_t,class_rw_t,class_rw_ext_t
        • class_ro_t
        • class_rw_t
          • 區別
        • class_rw_ext_t
    • 總結

探索對象本質

  • 在main中自定義一個類GCObject,有一個屬性name
@interface GGObject : NSObject
@property (nonatomic, copy) NSString* name;
@end@implementation GGObject@end

通過終端將main.m編譯成main.cpp:

//1、將 main.m 編譯成 main.cpp
clang -rewrite-objc main.m -o main.cpp//2、將 ViewController.m 編譯成  ViewController.cpp
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot / /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.7.sdk ViewController.m//以下兩種方式是通過指定架構模式的命令行,使用xcode工具 xcrun
//3、模擬器文件編譯
- xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp //4、真機文件編譯
- xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main- arm64.cpp 

在編譯之后,我們可以開始看這里的類的一個定義:

//NSObject的定義
@interface NSObject <NSObject> {Class isa  OBJC_ISA_AVAILABILITY;
}//NSObject 的底層編譯
struct NSObject_IMPL {Class isa;
};struct GGObject_IMPL {struct NSObject_IMPL NSObject_IVARS; // 這里NSString *_name;
};static NSString * _I_GGObject_name(GGObject * self, SEL _cmd) { return (*(NSString **)((char *)self + OBJC_IVAR_$_GGObject$_name)); } //get方法
extern "C" __declspec(dllimport) void objc_setProperty (id, SEL, long, id, bool, bool);static void _I_GGObject_setName_(GGObject * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct GGObject, _name), (id)name, 0, 1); } //set方法

這里我們可以看到NSObject的定義會產生一個問題,isa的類型居然是Class,這里我們定義alloc放的核心之一的initInstance方法,通過拆看這個方法的源碼實現,我們發現在初始化isa指針的時候,是通過isa_t類型初始化的。

這里為了讓開發人員更加清晰明確,需要在isa返回的時候做了一個類型強制轉換.

這里其實我們可以看出:

  • OC對象的本質就是結構體
  • CGObject中的isa就是繼承自NSObject中的isa

objc_setProperty 源碼

在這之前我們可以看到這里的上面的一個set方法:

static void _I_GGObject_setName_(GGObject * self, SEL _cmd, NSString *name) { objc_setProperty (self, _cmd, __OFFSETOFIVAR__(struct GGObject, _name), (id)name, 0, 1); } //set方法

這里面可以看到他里面調用了一個objc_setProperty


void objc_setProperty(id self, SEL _cmd, ptrdiff_t offset, id newValue, BOOL atomic, signed char shouldCopy) 
{bool copy = (shouldCopy && shouldCopy != MUTABLE_COPY);bool mutableCopy = (shouldCopy == MUTABLE_COPY);reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy); //跳轉到真正的一個調用方法
}
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{if (offset == 0) {object_setClass(self, newValue);return;}id oldValue;id *slot = (id*) ((char*)self + offset);if (copy) {newValue = [newValue copyWithZone:nil];} else if (mutableCopy) {newValue = [newValue mutableCopyWithZone:nil];} else {if (*slot == newValue) return;newValue = objc_retain(newValue); // 新增retain}if (!atomic) {oldValue = *slot;*slot = newValue;} else {spinlock_t& slotlock = PropertyLocks[slot];slotlock.lock();oldValue = *slot;*slot = newValue;        slotlock.unlock();}objc_release(oldValue); //release 舊值
}

小結

  • objc_setProperty 方法的目的適用于關聯上層的set方法以及底層的set方法,其本質就是一接口
  • 這么設計的原因是,上層的set方法很多,如果直接調用底層set方法中,會有很多臨時變量。當你吵著一個set的時候非常麻煩
  • 這里其實采用了一種設計模式:適配器設計模式(即將底層接口適配位客戶段需要的接口),對外提供一個接口共上層的set方法使用,對內調用底層的set方法。讓他們兩者互不影響)

image-20250416202436992

cls與類的關聯原理

聯合體

聯合體是指把不同的數據合成一個整體,但是所有變量是互斥的,所有的成員共占一段內存

  • 缺點:包容性弱
  • 優點:成員共用一段內存,是內存的使用更為精細靈活,同時節省了一個內存空間

共用體的所有成員占用同一段內存,修改一個成員影響其他所有成員變量。

結構體各個成員會占用不同的內存,互相之間沒有影響。

結構體內存>=所有成員占用的內存總和。

共用體占用的內存等于最大成員占用的內存

isa的類型isa_t

以下isa指針的類型isa_t的定義,從定義中可以看到是通過聯合體來定義的。

union isa_t {isa_t() { } // 默認的一個構造函數isa_t(uintptr_t value) : bits(value) { }uintptr_t bits; // 這個和cls是一個互斥類型private:// Accessing the class requires custom ptrauth operations, so// force clients to go through setClass/getClass by making this// private.Class cls; // 這里把cls指針私有化,禁止直接訪問cls,必須通過getClass()和setClass()方法操作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;}
#endifvoid setClass(Class cls, objc_object *obj); //提供set方法獲取cls指針更加安全Class getClass(bool authenticated); //提供get方法獲取cls指針Class getDecodedClass(bool authenticated); 。。
}; //這里是因為C++支持里一個聯合體方法,c++11以上支持聯合體里面放方法,這里的方法并會被存儲到我們的一個代碼段,不會影響聯合體的一個內存

我們現在來簡單分析一個這個isa_t的一個定義可以看出:

  • 提供了兩個成員變量 clsbits由聯合體的一個定義所知,這里兩個成員是互斥的,所以這里有倆種初始化方式:
    • 通過cls初始化
    • 通過bits初始化
  • 這里提供了一個位域的內容,用于存儲類信息以及其他信息,結構體的成員ISA_BITFIEld這里我們就看一下他是怎么定義位域的:

有兩個版本 __arm64__(對應ios 移動端) 和 __x86_64__(對應macOS)

#   define ISA_BITFIELD         //   __x86_64__                                            \uintptr_t nonpointer        : 1; \ //是否開啟isa指針開啟指針優化                      uintptr_t has_assoc         : 1;  \//是否有關聯對象                       uintptr_t has_cxx_dtor      : 1;   \ //   是否有C++相關實現                                uintptr_t shiftcls          : 44; \/*MACH_VM_MAX_ADDRESS 0x7fffffe00000存儲類信息*/ uintptr_t magic             : 6;                                        \//判斷對象是真對象還是未初始化對象uintptr_t weakly_referenced : 1;                                         \//對象是否被指向一個或者曾經指向ARC的一個弱變量uintptr_t unused            : 1;                                         \ //未被使用uintptr_t has_sidetable_rc  : 1;                                         \ //是否外掛一個sidetbaleuintptr_t extra_rc          : 8 //額外的引用計數define ISA_BITFIELD                //__arm64__                                         \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
  1. nonpointer有兩個值,表示自定義的類等占1位
    • 0:純isa指針
    • 1:不只是類對象地址,isa中包含了類對象,對象的引用計數等
  2. has_assoc表示關聯對象標志位
    • 0:沒有關聯對象
    • 1: 存在關聯對象
  3. has_cxx_dtor表示是否有C++/OC析構器:類似于dealloc,占一位
    • 如果有則需要做析構邏輯
    • 沒有就直接可以釋放對象
  4. shifcls白哦是存儲類的指針的值
    • arm64占33位
    • x86_64中占44位
  5. magic用于調試器判斷當前對象是真的對象還是沒有初始化的空間,6位
  6. weakly_refrenced是指對象是否被指向或者曾經指向一個ARC的弱變量
  7. has_sidetable_rc表示 當對象引用計數大于10時,則需要借用該變量存儲進位
  8. extra_rc(額外的引用計數) ,表示該對象的引用計數值,實際上是引用計數值減1

isa存儲情況

原理探索

我們在之前講過alloc的一個調用流程,在里面有一個initInstanceIsa,現在我們來看這部分內容:

inline voi
objc_object::initInstanceIsa(Class cls, bool hasCxxDtor)
{ASSERT(!cls->instancesRequireRawIsa());ASSERT(hasCxxDtor == cls->hasCxxDtor());initIsa(cls, true, hasCxxDtor);
}
initIsa方法

這里面也和之前,有一個中間層調用這部分內容,也就是真正開始綁定的一部分內容

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); // 初始化cls指針,用之前的那個set方法} else {ASSERT(!DisableNonpointerIsa);ASSERT(!cls->instancesRequireRawIsa());#if SUPPORT_INDEXED_ISA //即isa由cls定義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_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.indexcls = (uintptr_t)cls->classArrayIndex();
#else //bits指向的流程newisa.bits = ISA_MAGIC_VALUE; // 對bits進行一個賦值// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUE
#   if ISA_HAS_CXX_DTOR_BITnewisa.has_cxx_dtor = hasCxxDtor;
#   endifnewisa.setClass(cls, this);
#endifnewisa.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 instantiationisa = newisa;
}

我們這里是通過InitIsa來綁定isa指針的,但objc838在這里把綁定具體存儲類信息的內容放在我們的setClass這個函數中.下面我們來看這部分代嗎內容.

clsisa 關聯原理就是isa指針中的shiftcls位域中存儲了類信息,其中initInstanceIsa的過程是將 calloc 指針 和當前的 類cls 關聯起來

通過setClass方法中的shiftcls來驗證綁定的一個流程

這里我們接著看setClass這個方法:

isa_t::setClass(Class newCls, UNUSED_WITHOUT_PTRAUTH objc_object *obj)
{// Match the conditional in isa.h.
#if __has_feature(ptrauth_calls) || TARGET_OS_SIMULATOR
#   if ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_NONE// No signing, just use the raw pointer.uintptr_t signedCls = (uintptr_t)newCls;#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ONLY_SWIFT// We're only signing Swift classes. Non-Swift classes just use// the raw pointeruintptr_t signedCls = (uintptr_t)newCls;if (newCls->isSwiftStable())signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));#   elif ISA_SIGNING_SIGN_MODE == ISA_SIGNING_SIGN_ALL// We're signing everythinguintptr_t signedCls = (uintptr_t)ptrauth_sign_unauthenticated((void *)newCls, ISA_SIGNING_KEY, ptrauth_blend_discriminator(obj, ISA_SIGNING_DISCRIMINATOR));#   else
#       error Unknown isa signing mode.
#   endifshiftcls_and_sig = signedCls >> 3;#elif SUPPORT_INDEXED_ISA// Indexed isa only uses this method to set a raw pointer class.// Setting an indexed class is handled separately.cls = newCls;#else // Nonpointer isa, no ptrauthshiftcls = (uintptr_t)newCls >> 3; // 這里會有shiftcls賦值的邏輯是將LGPerson進行編碼后,右移3位
#endif
}

image-20250419164656131

從上面這張圖可以看出我們這里是在shiftcls中存儲一個類信息的

運行至newisa.shiftcls = (uintptr_t)cls >> 3 其中shiftcls存儲當前類信息部分

  • shiftcls賦值的邏輯是將LGPerson進行編碼后,右移3位,這里為什么可以保證后三位沒有信息的原因是內存對齊后從二進制層面來說他一定是后三位為000的,(因為結構體內存對齊的一個機制,導致了我們的結構體的二進制后三位一定為0);

這個時候我們再運行會我們的一個initIsa這里的一個創建isa的內容:isa() = newisa;然后觀察這里的一個內存的情況:

image-20250419165633798

這里我們可以清楚的看到一個內容這里一個cls完美的指向了我們的一個類.

這里為什么shiftcls為什么會出現一個強制類型轉化的原因是,內存不可以存儲字符串,機械碼只可以識別01這種數字所以才強制類型轉化成一個uintptr_t數據類型

在64位的機器上,intptr_t和uintptr_t分別是long int、unsigned long int的別名;在32位的機器上,intptr_t和uintptr_t分別是int、unsigned int的別名。

這里為什么需要向右移動三位,是因為他的前面還有三個位域,需要右移動將其抹零.

主要是由于shiftcls處于isa指針地址的中間部分,前面還有3個位域,為了不影響前面的3個位域的數據,需要右移將其抹零

通過 isa & ISA_MSAK
  • 在方式一后,繼續執行,回到_class_createInstanceFromZone方法,此時cls 與 isa已經關聯完成,執行po objc

isa指針地址 & ISA_MASK (處于macOS,使用x86_64中的定義),即 po 0x0000000100008358 & 0x00007ffffffffff8 & 0x00007ffffffffff8 ,得出CGObject

在這里插入圖片描述

通過object_getClass

查看object_getClass的源碼實現,同樣可以驗證isa與類關聯的原理,這里的原理和上面的大致一樣,所以筆者就不多贅述了.

通過位運算

我們之前提到過shiftcls只有44位的大小.這44位其實存儲了一個類信息,需要經過位運算,將右邊三位,和去除44位的位置都抹零

位運算計算過程

所以我們只有這樣處理之后也可以得到shiftcls里面保存的是一個類,說明isa已經關聯成功了.

類&類的結構

這里主要分析的isa走向和繼承關系

在分析之前,我們先定義兩個類:

@interface CJLPerson : NSObject
{NSString *hobby;
}
@property (nonatomic, copy) NSString *cjl_name;
- (void)sayHello;
+ (void)sayBye;
@end@implementation CJLPerson
- (void)sayHello
{}
+ (void)sayBye
{}
@end
@interface CJLTeacher : CJLPerson
@end@implementation CJLTeacher
@end

從終端調試開始認識調用邏輯

image-20250419202458850

根圖中的p/x 0x0000000100008320 & 0x00007ffffffffff8ULLp/x 0x00000001000082f8 & 0x00007ffffffffff8ULL 中的類信息打印出來都是CJLPerson

前者打印的是person對象的一個isa指針地址,他&后的結果是創建person的類CJLPerson

后者打印的是isa中類信息所指的類的isa的指針地址,即通過CJLPerson類的類的isa地址,在蘋果中,我們把一個類的類叫做元類

什么是元類
  • 對象的isa是指向類,類其實也是一個對象,可以叫做類對象.類對象的位域3指向平果定義的元類
  • 元類是系統給的,他的定義和創建都是由編譯器玩測的,這個過程中,類的歸屬來自于元類
  • 元類是類對象的類,每一個類都有獨一無二的元類來存儲類方法的相國信息
  • 元類本身是沒有名字的,只是因為類與之關聯,所以直接采用了類的一個名字

這里我們繼續用調試來觀察

image-20250419203657748

我們可以發現NSObject的元類指向了自己,CJLPerson元類的元類指向的是NSObject,從上面的調試信息我們其實可以得到下面的圖片:

image-20250419203823782

小結
  • 對象的isa指向類
  • 類的isa指向元類
  • 元類的isa指向根元類
  • 根元類的isa指向自己
NSObject到底有幾個?

其實NSObject只有一個,我們可以打印出NSObject的元類的地址和前面的CJLPerson元類的地址,發現他其實都是同一個,所以可以得出一個結論,內存中只存在一份根元類NSObject,根元類是指向他自己

類存在幾份

由于類的信息在內存中只有一份,所以類對象只有一份

isa走位繼承圖

關系圖示

isa走位

isa的走向有以下幾點說明:

  • 實例對象(Instance of Subclass 的 isa 指向 類(class)
  • 類對象(class)isa 指向 元類(Meta class)
  • 元類(Meta class)的isa 指向 根元類(Root metal class)
  • 根元類(Root metal class) 的isa 指向它自己本身,形成閉環,這里的根元類就是NSObject

superclass走位

類之間的繼承關系:

  • 類繼承自父類superClass
  • 父類繼承自根類rootClass
  • 根力類繼承自nil,即根類可以理解為萬物起源,無中生有

元類也存在繼承,元類之間的繼承關系如下:

  • 自子類的元類元類繼承自父類的元類
  • 父類的元類繼承自根元類
  • 根元類繼承自根類,這里的根類是指NSObject

實例對象之間沒有繼承關系,類之間由繼承關系

object_class & objc_object

isa走位我們理清楚了,又來了一個新的問題:為什么 對象都有isa屬性呢?這里就不得不提到兩個結構體類型:objc_class & objc_object

這里我們重新回顧我們的一個NSObject定義的內容:

struct NSObject_IMPL {Class isa;
};
typedef struct objc_class *Class;

其中Class是用isa指針類型,是由objc_class定義的一個類型

objc_class是一個結構體,iOS中兵所有的Class都是以objc_class為模板創建的.

這里我們先認識一下這兩個結構體:

這里我們可以簡單看一下這兩個結構體:

struct objc_object {Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
};
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 vtableclass_data_bits_t bits;//這里代碼太長了就不展示了
這里的objc_class 與objc_object有什么關系?
  • 結構體類型objc_class繼承自objec_class類型.其中objc_obejct是一個結構體,他又一個isa屬性,所以objc_class也有了isa屬性
  • NSObject中的isa在底層是由Class 定義的,其中class的底層編碼來自 objc_class類型,所以NSObject也擁有了isa屬性
  • NSObject是一個類,用它初始化一個實例對象objc,objc滿足objc_object的一個特性,主要還是因為isa是由objc_class繼承過來的,而objce_class繼承自objc_object,因為objc_object由isa屬性,所以每一個對象都有一個isa,isa表示一個指向,來自當前的objc_object.
  • objc_object是當前的根對象,縮由對象都有這樣一個特性,即擁有isa屬性
objc_object與對象的關系
  • 所有的對象都是以objc_object繼承過來的
  • 所有的對象都是來源于NSObject,但是到真正的底層就是一個objc_object的一個結構體類型了

所以可以說對象是繼承于objc_object的

總結

所有的對象都是由objc_object繼承過來的

所有的對象,類,元類都有isa屬性.

簡單概括就是萬物都是對象,所有都來源于objc_object,有一下兩點結論:

  • 以objc_object為模板創建的對象,都有isa
  • 以objc_class為模板創建的類,都有isa

整體關系圖示

在結構層面可以通俗的理解為上層OC與底層對接:

  • 下層就是通過結構體定義的模板,例如objc_class,objc_object
  • 上層就是通過底層模板創建的一些類型

類結構分析

類信息中有哪些內容

探索類信息中有什么的時.事先我們并不清楚類的結構是什么樣,但是我們可以通過類得到一個首地址,然后通過地址平移去獲取里面所有的值.

開始認識我們的一個結構體:

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 vtableclass_data_bits_t bits;
  • isa屬性:繼承自objec_object的isa,占8個字節
  • superclass屬性:Class類型,Class實有objc_object定義的,是一個指針
  • cache方法緩存,提高方法的性能
  • bits屬性:只有首地址經過上面三爾個屬性的內存大小總和的平移,才能獲得到bits
計算cache類的內存大小

進入cache類cache_t的定義(只貼出了結構體中非static修飾的屬性,主要是因為static類型的屬性 不存在結構體的內存中)

struct cache_t {
private:explicit_atomic<uintptr_t> _bucketsAndMaybeMask; //是一個結構體指針類型,占8個字節 explicit_atomic 是將普通指針轉換為原子指針的操作,為了線程安全. _bucketsAndMaybeMask 字段通過預處理宏和 buckets() 方法間接實現了 _buckets 的功能 這種設計是為了 優化內存(共享 _bucketsAndMaybeMask 字段)并支持不同架構的掩碼存儲模式。union {struct {explicit_atomic<mask_t>    _maybeMask; 
#if __LP64__uint16_t                   _flags;
#endifuint16_t                   _occupied;};explicit_atomic<preopt_cache_t *> _originalPreoptCache;};

這個類在LP64位的一個情況應該是只有16個字節,上面包含一個一個結構體指針類型,和一個結構體類型.這個類的結構可以大致的簡單理解為下面這個樣式:

這里展示一下,這里和objc838有所出入,在objc838版本中把bucket和mask合并了,然后通過掩碼的一個方式去訪問這部分內存.

image-20250423164953976

bits

bits:封裝了類的其他信息,例如成員變量,方法列表,協議,屬性

這里我們可以看到這里的一個中的bits的內容,這里我們先來認識bits中非常重要的一個類別class_rw_t這是一個結構體類型,這里我們呢還沒有看到屬性列表以及方法列表:

image-20250422191812131

image-20250422195849256

所以我們還需要進一步探索,所以我們找一下這里最重要的內容bits中最重要的兩個方法:safe_ro data,后者返回的是class_rw_t前者返回的是class_ro_t

在64位架構CPU下,bits 的第3到第46字節存儲 class_rw_t class_rw_t 中存儲 flags 、witness、firstSubclass、nextSiblingClass 以及 class_rw_ext_t

在本文的最后幾章內容中會講幾個的一個區別

探索property_list(屬性列表)

通過查看class_rw_t這個類.我們可以發現這里的一個屬性列表和方法列表等多個列表

image-20250422192320343

這里注意一個內容,這里的存儲在class_rw_t這個類是存儲一個屬性的,并不存儲一個成員變量成員變量是存儲在另一個類中:class_ro_t的這里我們對比看一下這兩個類的區別:

image-20250422193310722

這個類也有方法,屬性,協議和成員變量.但方法,屬性,協議的開頭是用base開頭的

這里有一個成員變量列表也就是我們這里的ivars,這個列表包含的內容不僅僅包含一個成員變量列表,除了包括在{}中定義的一個成員變量,還包括通過屬性定義的成員變量.bits --> data() -->ro() --> ivars通過這個流程來獲取成員變量表.

通過@property定義的屬性,也會存儲在bits屬性中,通過bits --> data() --> properties() --> list獲取屬性列表,其中只包含屬性

探索methods_list

這里的methods_list是不存儲類方法的,他只存儲一個實例方法.那我們的類方法存儲在哪里呢?

類方法其實存儲在我們的一個元類的方法列表里面:

  • 類的實例方法存儲在類的bits屬性中,通過bits --> methods() --> list獲取實例方法列表,例如CJLPersong類的實例方法sayHello 就存儲在 CJLPerson類的bits屬性中,類中的方法列表除了包括實例方法,還包括屬性的set方法 和 `get方法

  • 類的類方法存儲在元類的bits屬性中,通過元類bits --> methods() --> list獲取類方法列表,例如CJLPerson中的類方法sayBye 就存儲在CJLPerson類的元類(名稱也是CJLPerson)的bits屬性中

從編譯期開始介紹class_ro_t,class_rw_t,class_rw_ext_t

首先我們從編譯期程序做了什么事情開始介紹:

當類被編譯的時候,二進制類在磁盤中的表示如下:

image-20250422222317161

首先是類對象本身,包含最常訪問的信息:指向元類(isa),超類(superclass)和方法緩存(cache)的指針,它還具有指向包含更多數據的結構體 class_ro_t 的指針,包含了類的名稱,方法,協議,實例變量等等 編譯期確定 的信息。其中 ro 表示 read only 的意思。

當類第一次從磁盤加載到內存時,它們總是以這樣的形式開始布局的,但是一旦使用它們,就會發生改變:

當類被 Runtime 加載之后,類的結構會發生一些變化,在了解這些變化之前,我們需要知道2個概念:

Clean Memory:加載后不會發生更改的內存塊,class_ro_t 屬于 Clean Memory,因為它是只讀的。
Dirty Memory:運行時會發生更改的內存塊,類結構一旦被加載,就會變成 Dirty Memory,因為運行時會向它寫入新的數據。例如,我們可以通過 Runtime 給類動態的添加方法。

這里要明確,Dirty MemoryClean Memory 要昂貴得多。因為它需要更多的內存信息,并且只要進程正在運行,就必須保留它。另一方面, Clean Memory 可以進行移除,從而節省更多的內存空間,因為如果你需要 Clean Memory ,系統可以從磁盤中重新加載。

Dirty Memory 是這個類數據 被分成兩部分的原因。

對于我們來說,越多的 Clean Memory 顯然是更好的,因為它可以 節約更多的內存。我們可以通過分離出永不更改的數據部分,將大多數類數據保留為 Clean Memory,應該怎么做呢?

在介紹優化方法之前,我們先來看一下,在類加載之后,類的結構會變成如何呢?

類分析

image-20250422223702206

在類首次被使用的時候,runtime會為它分配額外的存儲容量,用于 讀取/寫入 數據的一個結構體 class_rw_t

這個結構體用來存儲只有在運行時才會生成的新信息比方說所有類都會連接成一個樹狀結構,這里是通過firstSubclass和newxtSon;歐美化Class指針實現的,這樣runtime可以遍歷當前使用的所有的類.

這里之所以還會有該類的方法列表和屬性列表的信息是因為它們在運行時是可以更改的.當category被加載的時候,它可以想類中添加新的方法.而且程序員也可以通過runtimeAPI動態的添加.

class_ro_t 是只讀的,存放的是 編譯期間就確定 的字段信息;而class_rw_t 是在 runtime 時才創建的,它會先將 class_ro_t 的內容拷貝一份,再將類的分類的屬性、方法、協議等信息添加進去,之所以要這么設計是因為 Objective-C 是動態語言,你可以在運行時更改它們方法,屬性等,并且分類可以在不改變類設計的前提下,將新方法添加到類中。

在實際生活中,我們會發現class_rw_t會占用比class_ro_t更多的內存所以誕生了一個新的結構體class_rw_ext_t

大約只有10%左右的類實際會存在動態的一個更改行為,這樣減少了一個class_rw_t的大小減小了一半,所以會變成下面這個結構

image-20250423164835020

class_ro_t

這里先展示這個類的樣式

struct class_ro_t {uint32_t flags;uint32_t instanceStart;uint32_t instanceSize;
#ifdef __LP64__uint32_t reserved;
#endifunion {const uint8_t * ivarLayout;Class nonMetaclass;};explicit_atomic<const char *> name;WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;protocol_list_t * baseProtocols;const ivar_list_t * ivars;const uint8_t * weakIvarLayout;property_list_t *baseProperties;

這個類我們可以很清楚的看到他有屬性列表,方法列表.

這個類其實是在編譯期的時候就產生了,他會將本來編譯好的內容,放到我們的ro中.

class_rw_t

先展示一下這個結構體的一個樣式:

struct class_rw_t {// Be warned that Symbolication knows the layout of this structure.uint32_t flags;uint16_t witness;
#if SUPPORT_INDEXED_ISAuint16_t index;
#endifexplicit_atomic<uintptr_t> ro_or_rw_ext; //按需存儲靜態或動態元數據,兼顧性能和內存。//作用原子化指針,指向類的 只讀元數據(class_ro_t)或 動態擴展數據(class_rw_ext_t)。Class firstSubclass; //指向當前類的  第一個直接子類Class nextSiblingClass; //指向當前類的 下一個兄弟類,與 firstSubclass 共同維護類的 鏈表結構。

從他兩者的一個成員變量其實就可以看出兩者的一個區別,這里里面之所以沒有屬性的一個內容,是因為在iOS14之后屬性的部分被放到了我們之前提到的class_rw_ext_t中.

區別

從生成時機的角度來說, ro編譯階段生成,rw運行的時候生成。從存儲的內容角度來講,ro中有方法、屬性、協議和成員變量,而rw中并沒有成員變量。rw中的方法屬性協議的取值方法中,也是通過取ro或者rwe中的值來獲得。ro中的方法、屬性、協議都是base,也就是只有本類中的方法屬性和協議。

class_rw_ext_t

這里我們來看一下這個結構體的一個樣式:

struct class_rw_ext_t {DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)class_ro_t_authed_ptr<const class_ro_t> ro;method_array_t methods;property_array_t properties;protocol_array_t protocols;char *demangledName;uint32_t version;
};

這個結構體存儲了class_ro_t methods (方法列表)、properties (屬性列表)、protocols (協議列表)等信息。

class_ro_t 中也存儲了 baseMethodList (方法列表)、baseProperties (屬性列表)、baseProtocols (協議列表) 以及 實例變量、類的名稱、大小 等等信息。

但是我們這里要注意一下他的一個創建的前提條件:

  • 使用分類的類
  • 使用runtime API動態修改類的結構的時候

在遇到以上2種情況的時候,類的結構(屬性、協議、方法)發生改變,原有的ro(Claer Memory,便宜)已經不能繼續記錄類的屬性、協議、方法信息了,于是系統重新生成可讀可寫的內存結構rw_ext(Dirty Memory, 比較貴),來存放新的類結構。

這時候我們就很好理解下面這個方法列表了:

image-20250423185611660

const method_array_t methods() const {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) { // 判斷有沒有創建一個re_extreturn 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}; //如果沒有就在ro中讀取}}

總結

這是筆者對于iOS類與對象底層原理的一個學習,這里可能會有什么紕漏或者錯誤,還請不吝指出.

參考博客:

iOS-底層原理 08:類 & 類結構分析

iOS 類的結構分析

類結構中的class_rw_t與class_ro_t

【iOS】類與對象底層探索

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/77434.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/77434.shtml
英文地址,請注明出處:http://en.pswp.cn/web/77434.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

浮點數:IEEE 754標準

IEEE 754 標準是一種由電氣和電子工程師協會&#xff08;IEEE&#xff09;制定的浮點數表示的標準&#xff0c;廣泛應用于計算機系統中&#xff0c;下面是詳細介紹&#xff1a; 歷史背景 在 IEEE 754 標準出現之前&#xff0c;不同的計算機系統采用各自的浮點數表示方法&…

centos7部署k8s集群

環境準備 服務器三臺 10.0.0.70master 10.0.0.71worker1 10.0.0.72worker2 配置yum源&#xff08;集群機器執行&#xff09; wget -O /etc/yum.repos.d/CentOS-Base.repo http://mirrors.aliyun.com/repo/Centos-7.repo 安裝常用軟件 yum -y install lrzsz vim net-tools關閉f…

第三方軟件檢測報告:熱門辦公軟件評估及功能表現如何?

第三方軟件檢測報告是重要文件。它用于對軟件做專業評估。能反映軟件各項性能。能反映軟件安全性等指標。該報告為軟件使用者提供客觀參考。該報告為軟件開發者提供客觀參考。有助于發現問題。還能推動軟件改進。 檢測概述 本次檢測針對一款熱門辦公軟件。采用了多種先進技術…

Linux:41線程控制lesson29

1.線程的優點&#xff1a; ? 創建?個新線程的代價要?創建?個新進程?得多 創建好線程只要調度就好了 ? 與進程之間的切換相?&#xff0c;線程之間的切換需要操作系統做的?作要少很多 為什么&#xff1f; ? 最主要的區別是線程的切換虛擬內存空間依然是相同的&#x…

【MCP】從一個天氣查詢服務帶你了解MCP

1. 前言 這篇文章將通過一個集成高德天氣查詢的 MCP Server 用例&#xff0c;帶你上手開發自己的 MCP Server ,文章將通過以下三種方式&#xff08;自己編寫 Client 端代碼&#xff0c;使用 mcp-cli 自帶頁面&#xff0c;集成到 Claude 桌面版等&#xff09;帶你測試自己的 MC…

SHCTF-REVERSE

前言 之前寫的&#xff0c;一直沒發&#xff0c;留個記錄吧&#xff0c;萬一哪天記錄掉了起碼在csdn有個念想 1.ezapk 反編譯 快速定位關鍵函數 package com.mycheck.ezjv;import adrt.ADRTLogCatReader; import android.app.Activity; import android.content.Context; impo…

安卓觸摸事件分發機制分析

1. 前言 &#x1f3af; 一句話總結&#xff1a; 觸摸事件&#xff08;TouchEvent&#xff09;會從 Activity 層開始&#xff0c;按從外到內的方式傳遞給每一個 ViewGroup/View&#xff0c;直到某個 View 消費&#xff08;consume&#xff09; 它&#xff0c;事件傳遞就會停止…

Spring MVC 多個攔截器的執行順序

一、流程總覽 該流程圖描述了一個多層攔截器鏈的業務處理流程&#xff0c;核心邏輯為&#xff1a; 前置攔截&#xff1a;通過 predHandler1 和 predHandler2 逐層校驗請求合法性。核心處理&#xff1a;通過校驗后執行核心業務邏輯 handler()。后置處理與清理&#xff1a;按反…

django filter 排除字段

在Django中&#xff0c;當你使用filter查詢集&#xff08;QuerySet&#xff09;時&#xff0c;通常你會根據模型的字段來過濾數據。但是&#xff0c;有時你可能想要排除某些特定的字段&#xff0c;而不是過濾這些字段。這里有幾種方法可以實現這一點&#xff1a; 使用exclude方…

ByeCode,AI無代碼開發平臺,拖拽式操作構建應用

ByeCode是什么 ByeCode 是一款先進的 AI 無代碼平臺&#xff0c;旨在幫助企業迅速創建數字名片、網站、小程序、應用程序及內部管理系統&#xff0c;無需繁雜的編碼或開發工作。ByeCode 采用直觀的可視化界面和拖拽式操作&#xff0c;使得非技術用戶能夠輕松上手。同時&#x…

AI日報 - 2025年04月28日

&#x1f31f; 今日概覽(60秒速覽) ▎&#x1f916; 能力進展 | Gemini 2.5 Pro成功挑戰《口袋妖怪紅》8道館&#xff1b;AI推理器具備自我糾錯能力&#xff1b;LLM在游戲、多模態理解、代碼遷移等方面展現新能力。 ▎&#x1f4bc; 商業動向 | Google回應DOJ反壟斷案&#xff…

在Java中實現List按自定義順序排序的幾種方案

在Java中實現List按自定義順序排序的幾種方案 在實際開發中&#xff0c;我們經常需要對集合中的對象按照特定字段進行排序。當排序規則不是簡單的字母或數字順序&#xff0c;而是自定義的順序時&#xff0c;我們需要采用特殊的方法。本文將以一個List<Person>按省份特定…

微服務架構在云原生后端的深度融合與實踐路徑

??個人主頁??:一ge科研小菜雞-CSDN博客 ????期待您的關注 ???? 一、引言:后端架構的演變,走向云原生與微服務融合 過去十余年,后端架構經歷了從單體應用(Monolithic)、垂直切分(Modularization)、到微服務(Microservices)的演進,每一次變化都是為了解決…

Python中的Walrus運算符分析

Python中的Walrus運算符&#xff08;:&#xff09;是Python 3.8引入的一個新特性&#xff0c;允許在表達式中同時賦值和返回值。它的核心作用是減少重復計算&#xff0c;提升代碼簡潔性。以下是其適用的典型場景及示例&#xff1a; 1. 在循環中避免重復計算 當循環條件需要多次…

用Node.js施展文檔比對魔法:輕松實現Word文檔差異比較小工具,實現Word差異高亮標注(附完整實戰代碼)

引言&#xff1a;當「找不同」遇上程序員的智慧 你是否經歷過這樣的場景&#xff1f; 法務同事發來合同第8版修改版&#xff0c;卻說不清改了哪里 導師在論文修改稿里標注了十幾處調整&#xff0c;需要逐一核對 團隊協作文檔頻繁更新&#xff0c;版本差異讓人眼花繚亂 傳統…

前端瀏覽器窗口交互完全指南:從基礎操作到高級控制

瀏覽器窗口交互是前端開發中構建復雜Web應用的核心能力&#xff0c;本文深入探討23種關鍵交互技術&#xff0c;涵蓋從傳統API到最新的W3C提案&#xff0c;助您掌握跨窗口、跨標簽頁的完整控制方案。 一、基礎窗口操作體系 1.1 窗口創建與控制 // 新窗口創建&#xff08;現代瀏…

Git和Gitlab的部署和操作

一。GIT的基本操作 1.GIT的操作和查看內容 [rootmaster ~]# yum install git -y [rootmaster ~]# git config --list&#xff1a;查看所有配置 2.GIT倉庫初始化 [rootmaster ~]# mkdir /gittest&#xff1a;創建目錄 [rootmaster ~]# cd /gittest/&#xff1a;進入目錄 [rootm…

Linux中線程池的簡單實現 -- 線程安全的日志模塊,策略模式,線程池的封裝設計,單例模式,餓漢式單例模式,懶漢式單例模式

目錄 1. 對線程池的理解 1.1 基本概念 1.2 工作原理 1.3 線程池的優點 2. 日志與策略模式 2.1 日志認識 2.2 策略模式 2.2.1 策略模式的概念 2.2.2 工作原理 2.2 自定義日志系統的實現 3. 線程池設計 3.1 簡單線程池的設計 3.2 線程安全的單例模式線程池的設計 3…

量子力學:量子通信

量子通信是利用量子力學原理對信息進行編碼、傳輸和處理的新型通信方式&#xff0c;以下是其詳細介紹及業界發展現狀&#xff1a; 基本原理 量子疊加態 &#xff1a;量子系統可以處于多個狀態的疊加&#xff0c;如光子的偏振方向可以同時處于水平和垂直方向的疊加態&#xff…

企業架構之旅(1):TOGAF 基礎入門

大家好&#xff0c;我是沛哥兒。今天我們簡單聊下TOGAF哈。 文章目錄 一、TOGAF 是什么定義與核心定位發展歷程與行業地位與其他架構框架的區別 二、TOGAF 核心價值企業數字化轉型助力業務與 IT 的協同作用降本增效與風險管控 三、TOGAF 基礎術語解析架構域&#xff08;業務、…