日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

iOS之深入解析对象isa的底层原理

發布時間:2024/5/28 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 iOS之深入解析对象isa的底层原理 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

對象本質

一、NSObject 本質

OC代碼的底層實現實質是 C/C++代碼 ,繼而編譯成匯編代碼,最終變成機器語言。

① clang C/C++ 編譯器
  • Clang 是?個 C 語?、C++、Objective-C 語?的 輕量級編譯器 ,源代碼發布于 BSD 協議下。
  • Clang 將?持其 普通lambda表達式 ,返回類型的簡化處理以及更好的 處理constexpr關鍵字
  • Clang 是?個由 Apple 主導編寫,基于 LLVM的C/C++/Objective-C編譯器
  • 2013年4?,Clang 已經全??持 C++11標準 ,并開始實現 C++1y特性 (也就是 C++14,這是 C++ 的下?個?更新版本)。Clang 將?持其 普通lambda表達式 ,返回類型的簡化處理以及更好的處理 constexpr關鍵字
  • Clang 是?個 C++ 編寫,基于 LLVM 發布于 LLVM BSD 許可證下的 C/C++/Objective-C/Objective-C++ 編譯器。它與 GNU C 語?規范?乎完全兼容(當然也有部分不兼容的內容,包括編譯命令選項也會有點差異),并在此基礎上增加了額外的語法特性,?如 C 函數重載(通過 attribute((overloadable) )來修飾函數),其?標(之?)就是超越 GCC 。
  • clang -rewrite-objc main.m -o main.cpp 把?標?件編譯成c++?件。
  • UIKit報錯問題:Xcode安裝的時候順帶安裝了 xcrun 命令, xcrun 命令在 clang 的基礎上進?了?些封裝。
clang -rewrite-objc -fobjc-arc -fobjc-runtime=ios-13.0.0 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator13.0.sdk main.m
  • xcrun -sdk iphonesimulator clang -arch arm64 -rewrite-objc main.m -o
    main-arm64.cpp
    (模擬器)
  • xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o mainarm64.cpp (?機)
② 運用 clang 將目標文件編譯成 cpp(C++文件)
  • 在main.m中添加以下代碼:
#import <Foundation/Foundation.h> #import <objc/runtime.h>@interface YDWHandsomeBoy : NSObject @property (nonatomic, copy) NSString *name; @end@implementation YDWHandsomeBoy@endint main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...NSLog(@"Hello, World!");}return 0; }
  • 打開終端,進入main.m所在的文件夾,通過 clang rewirte-objc main.m -o main.cpp xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm64.cpp 命令,生成cpp文件。

  • 然后回到main.m的文件中,打開cpp文件,搜索“YDWHandsomeBoy”,即可看到,對象YDWHandsomeBoy在底層被編譯成了一個結構體:
#ifndef _REWRITER_typedef_YDWHandsomeBoy#define _REWRITER_typedef_YDWHandsomeBoytypedef struct objc_object YDWHandsomeBoy;typedef struct {} _objc_exc_YDWHandsomeBoy;#endifextern "C" unsigned long OBJC_IVAR_$_YDWHandsomeBoy$_name;struct YDWHandsomeBoy_IMPL {struct NSObject_IMPL NSObject_IVARS;NSString *_name;};// @property (nonatomic, copy) NSString *name;/* @end */// @implementation YDWHandsomeBoy
  • 可以看到上面的c++代碼中有個NSObject_IMPL NSObject_IVARS這個東西,然后搜索NSObject_IMPL可以發現:
struct NSObject_IMPL {Class isa;};
  • 由此可以得出:
    • NSObject的底層實現實質是一個 結構體 ,而結構體中的成員 isa是Class類型
    • 通過源碼 typedef struct objc_class *Class 可知它是一個指針,在64為環境下指針占8個字節,而在32位機下是占4個字節,因此該結構體占8個字節(因為該結構體只有一個成員)。

二、NSObject 對象內存

  • 初始化一個NSObject并打印:
NSObject *obj = [[NSObject alloc] init];NSLog(@"%zd",malloc_size((__bridge const void *)obj));
  • 可知NSObject對象占16個字節。那么與上文中NSObject結構體中占8個字節是否沖突?再次打印:
NSLog(@"%zd",class_getInstanceSize([NSObject class]))
  • 不難發現:獲取NSObject類的實例對象的成員變量所占用的(內存對齊之后)大小,顯示確實為8個字節。
  • 在objc的源碼中找到 class_getInstanceSize方法,發現它返回的是 cls->alignedInstanceSize() ,對它的描述為Class’s ivar size rounded up to a pointer-size boundary意指 返回成員變量占據的大小 。因此創建一個NSObject對象需要分配16個字節,只是真正利用的只有8個字節,即isa這個成員的大小。
  • 查看allocWithZone的源碼發現它最底層的創建實例的方法實際上是調用了C語言的 calloc方法 ,在該方法中,規定若 分配的字節不滿16將把它分配為16個字節

三、若一個YDWHandsomeBoy類繼承自NSObject類,那么YDWHandsomeBoy類的對象占多少內存?

  • 新建YDWHandsomeBoy類,添加成員變量,在main中實現以下代碼:
#import <Foundation/Foundation.h>#import <objc/runtime.h>#import <malloc/malloc.h>@interface YDWHandsomeBoy : NSObject@property (nonatomic, copy) NSString *name;@property (nonatomic, copy) NSString *nickName;@property (nonatomic, assign) int age;@property (nonatomic, assign) int address;@property (nonatomic, assign) int number;@end@implementation YDWHandsomeBoy@endint main(int argc, const char * argv[]) {@autoreleasepool {// insert code here...YDWHandsomeBoy *boy = [[YDWHandsomeBoy alloc] init];boy.name = @"Y";boy.nickName = @"D";boy.age = 18;boy.address = 10;boy.number = 11;NSLog(@"%zd",malloc_size((__bridge const void *)boy));}return 0;}打印結果如下:2020-09-02 15:44:27.345192+0800 iOS之對象isa[53982:3621024] 48
  • 通過以上代碼可以看出:
  • 若一個類繼承自另一個類,則它的底層會 將父類的成員變量放在結構體的最前面,此后依次放置本類的成員變量
  • 而從之前的分析可知,NSObject_IMPL的本質就是一個 裝有成員變量isa的結構體 ,因此,YDWHandsomeBoy類對象所占的內存為isa的內存8加上YDWHandsomeBoy類成員變量所占的空間,若不滿16個字節,會強制分配到16個字節。
  • 由于 內存對齊 的規定,結構體的最終大小必須是 最大成員的倍數

isa

一、isa 簡介

  • alloc初始化時不僅 創建對象并且分配內存 ,同時 初始化 isa 指針屬性
  • Objective-C 對象在底層本質上是 結構體 ,所有的對象里面都會包含有一個 isa ,isa 的定義是一個 聯合體 isa_t ,isa_t 包含了 當前對象指向類的信息
union isa_t {isa_t() { }isa_t(uintptr_t value) : bits(value) { }Class cls;uintptr_t bits;#if defined(ISA_BITFIELD)struct {ISA_BITFIELD; // defined in isa.h};#endif};
  • isa 是一個 聯合體,而這其實是從內存管理層面來設計的,因為聯合體是 所有成員共享一個內存,聯合體 內存的大小取決于內部成員內存大小最大的那個元素
  • 對于 isa 指針來說,就不用額外聲明很多的屬性,直接在 內部的 ISA_BITFIELD 保存信息
  • 由于聯合體 屬性間互斥 ,所以 cls 和 bits 在 isa 初始化流程時是在 兩個分支 中被賦值的。
union isa_t {isa_t() { }isa_t(uintptr_t value) : bits(value) { }Class cls;uintptr_t bits;#if defined(ISA_BITFIELD)struct {ISA_BITFIELD; // defined in isa.h};#endif};# define ISA_MASK 0x00007ffffffffff8ULL# define ISA_MAGIC_MASK 0x001f800000000001ULL# define ISA_MAGIC_VALUE 0x001d800000000001ULL# 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 deallocating : 1; \uintptr_t has_sidetable_rc : 1; \uintptr_t extra_rc : 8# define RC_ONE (1ULL<<56)# define RC_HALF (1ULL<<7)
  • isa_t 是一個聯合體,聯合體的特性就是 內部所有的成員共用一塊內存地址空間 ,也就是說isa_t、cls、bits會共用同一塊內存地址空間,這塊 內存地址空間大小取決于最大長度內部成員的大小 ,即64位8字節,由此可以知道isa 的所占的內存空間大小為8字節。isa_t聯合體如下:
struct {uintptr_t indexed : 1;uintptr_t has_assoc : 1;uintptr_t has_cxx_dtor : 1;uintptr_t shiftcls : 44;uintptr_t magic : 6;uintptr_t weakly_referenced : 1;uintptr_t deallocating : 1;uintptr_t has_sidetable_rc : 1;uintptr_t extra_rc : 8;};

二、isa 結構

isa 作為一個聯合體,有一個結構體屬性為 ISA_BITFIELD ,其大小為 8 個字節,也就是 64 位。基于__arm64__ 和 x86 64 架構如下:

# if __arm64__ # define ISA_MASK 0x0000000ffffffff8ULL # define ISA_MAGIC_MASK 0x000003f000000001ULL # define ISA_MAGIC_VALUE 0x000001a000000001ULL # 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 deallocating : 1; \uintptr_t has_sidetable_rc : 1; \uintptr_t extra_rc : 19 # define RC_ONE (1ULL<<45) # define RC_HALF (1ULL<<18)# elif __x86_64__ # define ISA_MASK 0x00007ffffffffff8ULL # define ISA_MAGIC_MASK 0x001f800000000001ULL # define ISA_MAGIC_VALUE 0x001d800000000001ULL # 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 deallocating : 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
  • union-isa_t 存儲分配如下:

  • nonpointer: 表示是否對 isa 指針 開啟指針優化
    0: 純 isa 指針;
    1: 不止是類對象地址, isa 中包含了類信息、對象的引用計數等。
  • has_assoc: 關聯對象標志位 ,0 沒有,1 存在。
  • has_cxx_dtor: 該對象 是否有 C++ 或者 Objc 的析構器 ,如果有析構函數,則需要做析構邏輯,如果沒有,則可以更快的釋放對象。
  • shiftcls: 存儲類指針的值 ,開啟指針優化的情況下,在 arm64 架構中有 33 位用來存儲類指針。
  • magic: 用于 調試器判斷當前對象是真的對象還是沒有初始化的空間
  • weakly_referenced: 標志對象是否被指向或者曾經 指向一個 ARC 的弱變量 ,沒有弱引用的對象可以更快釋放。
  • deallocating: 標志對象 是否正在釋放內存
  • has_sidetable_rc: 當對象引用技術大于 10 時,則需要借用該變量 存儲進位
  • extra_rc: 當表示該 對象的引用計數值 ,實際上是引用計數值減 1。 例如,如果對象的引用計數為 10,那么 extra_rc 為 9;如果引用計數大于 10, 則需要使用到has_sidetable_rc。

三、isa 初始化

① isa 源碼實現
  • 在objc的源碼中有isa的初始化方法:
inline void objc_object::initInstanceIsa(Class cls, bool hasCxxDtor) {assert(!cls->instancesRequireRawIsa());assert(hasCxxDtor == cls->hasCxxDtor());initIsa(cls, true, hasCxxDtor);}inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { assert(!isTaggedPointer()); if (!nonpointer) {isa.cls = cls;} else {assert(!DisableNonpointerIsa);assert(!cls->instancesRequireRawIsa());isa_t newisa(0);#if SUPPORT_INDEXED_ISAassert(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();#elsenewisa.bits = ISA_MAGIC_VALUE;// isa.magic is part of ISA_MAGIC_VALUE// isa.nonpointer is part of ISA_MAGIC_VALUEnewisa.has_cxx_dtor = hasCxxDtor;newisa.shiftcls = (uintptr_t)cls >> 3;#endif// 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;}}
  • 由于nonpointer傳入的是true,SUPPORT_INDEXED_ISA定義為0,所以可以對這段代碼簡化一下:
inline void objc_object::initIsa(Class cls, bool nonpointer, bool hasCxxDtor) { isa_t newisa(0);newisa.bits = ISA_MAGIC_VALUE;newisa.has_cxx_dtor = hasCxxDtor;newisa.shiftcls = (uintptr_t)cls >> 3;isa = newisa;}
② isa 初始化數據
  • 可以看到對bits的賦值ISA_MAGIC_VALUE = 0x001d800000000001ULL,將此轉為二進制,在結合isa_t的結構得出如下的isa_t的初始數據圖:

  • 對 isa 賦值ISA_MAGIC_VALUE初始化實際上只是設置了indexed和magic兩部分的數據:
    • indexed表示 isa_t 的類型 :0表示 raw isa,也就是沒有結構體的部分,訪問對象的 isa 會直接返回一個指向 cls 的指針,也就是在 iPhone 遷移到 64 位系統之前時 isa 的類型;1則表示當前 isa 不是指針,但是其中也有 cls 的信息,只是其中關于類的指針都是保存在 shiftcls 中。
    • magic 用于 調試器判斷當前對象是否有初始化空間
  • 在設置indexed和magic的值后會對has_cxx_dtor進行設值。has_cxx_dtor表示該對象是否有 C++ 或者 Objc 的析構器 ,如果有析構函數,則需要做析構邏輯,如果沒有,則可以更快的釋放對象。
newisa.has_cxx_dtor = hasCxxDtor;
  • 將當前對象的類指針存放在 shiftcls 中:
newisa.shiftcls = (uintptr_t)cls >> 3;
  • 對cls的地址右移動3位的目的是 為了減少內存的消耗 ,因為類的指針需要按照8字節對齊,也就是說類的指針的大小必定是8的倍數,其二進制后三位為0 ,右移三位抹除后面的3位0并不會產生影響。
③ isa的初始化流程示意

四、isa 關聯對象和類

isa 是對象中的第一個屬性,這是在繼承的時候發生的,要早于對象的成員變量,屬性列表,方法列表以及所遵循的協議列表。在 alloc 底層,有一個方法叫做 initIsa ,這個方法的作用就是 初始化 isa 聯合體位域 。上文中我們已經看到了這個方法:

newisa.shiftcls = (uintptr_t)cls >> 3;
① cls 存儲到 isa
  • isa 剛初始化時,還沒有被賦值,bits 全為空值,p newisa 如下:

  • 繼續向下執行,當斷點執行到如下位置的時候,bits 會被賦上默認值(nonpointer = 1,magic = 59),繼續p newisa如下:

  • 為什么 magic = 59 呢?其實,通過計算器可以轉換算出:0x001d800000000001 = 59,上面我們已經 po 0x001d800000000001ULL 了,可以看到這個默認值。
  • 繼續輸出 bits 二進制、輸出 cls 指針二進制可以看到:在未設置 shiftcls 時,bits 從右到左 [3, 46] 位都是0。如下:
(lldb) p/t 8303511812964353(long) $3 = 0b0000000000011101100000000000000000000000000000000000000000000001(lldb) p/t (uintptr_t)cls(uintptr_t) $4 = 0b0000000000000000000000000000000100000000010001110100000000111000(lldb) p/t (uintptr_t)cls >> 3(uintptr_t) $5 = 0b0000000000000000000000000000000000100000000010001110100000000111(lldb)
  • 為什么要右移三位?在 Objective-C 中,類的指針是按照字節(8 bits)對齊的,也就是說類指針地址轉化成十進制后,都是8的倍數,也就是說,類指針地址轉化成二進制后,后三位都是0。既然是沒有意義的0,那么在存儲時就可以省略,用節省下來的空間存儲一些其他信息。
  • 當 bits 被賦值之后,如下:

  • 可以看到,現在的 bits 的 [3, 46] 位正好是之前 cls 指針右移三位的內容。
② isa 關聯對象和類
  • 通過 LLDB 進行調試打印,就可以知道一個對象的 isa 會關聯到這個對象所屬的類:

  • LLDB 調試的時候左移右移操作其實很好理解,先觀察 isa 的 ISA_BITFIELD 位域的結構:ISA_BITFIELD 的前 3 位是 nonpointer、has_assoc、has_cxx_dtor ,中間 44 位是 shiftcls ,后面 17 位是剩余的內容,同時因為 iOS 是 小端模式 ,那么就需要去掉右邊的 3 位和左邊的 17位,所以就會采用 >> 3 << 3 然后 << 13 >> 13 的操作。

五、isa 走位分析

① class object(類對象)/ metaclass(元類)
  • Object-C的對象其本質就是結構體,前面也分析了每一個對象都會有一個isa。同時類的本質也是一個結構體,而且是繼承自objc_object的。
struct objc_object {private:isa_t isa;...};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...};
  • 在objc_class中也有isa:
struct objc_class : objc_object {isa_t isa;Class superclass;cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags...};
  • class_isMetaClass用于判斷Class對象是否為元類,object_getClass用于獲取對象的isa指針指向的對象。
OBJC_EXPORT BOOL class_isMetaClass(Class cls) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);OBJC_EXPORT Class object_getClass(id obj) OBJC_AVAILABLE(10.5, 2.0, 9.0, 1.0);
  • 我們知道:對象可以創建多個,但是類是否可以創建多個呢?其實答案是否定的,類在內存中只會存在一份。
Class class1 = [Boy class];Class class2 = [Boy alloc].class;Class class3 = object_getClass([Boy alloc]);Class class4 = [Boy alloc].class;NSLog(@"\n%p-\n%p-\n%p-\n%p",class1, class2, class3, class4);// 打印如下:0x10edbedc80x10edbedc80x10edbedc80x10edbedc8
  • 通過 LLDB 調試打印,其實可以發現:類的內存結構里面的第一個結構打印出來還是 Boy,那么是不是就意味著 對象 ->類->類 這樣的死循環呢?這里的第二個類其實是 元類,是由系統創建的,這個元類無法被我們實例化。

  • 一個實例對象通過class方法獲取的Class就是它的isa指針指向的類對象,而類對象不是元類,類對象的isa指針指向的對象是元類。關系如下:
② isa 走位
  • 官方的經典 isa 走位圖:
    • 實例對象的isa指向的是類;
    • 類的isa指向的元類;
    • 元類指向根元類;
    • 根元類指向自己;
    • NSObject的父類是nil,根元類的父類是NSObject。

  • LLDB 調試打印:

六、對象的本質 isa

  • OC 對象的本質就是一個結構體,在 libObjc 源碼的 objc-private.h 源文件中可以看到:
struct objc_object {private:isa_t isa;public:// ISA() assumes this is NOT a tagged pointer objectClass ISA();// getIsa() allows this to be a tagged pointer objectClass getIsa();......}
  • 對于對象所屬的類來說,也可以在 objc-runtime-new.h 源文件中找到(即 objc_class 內存中第一個位置是 isa,第二個位置是 superclass):
struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags......}
  • 對象在底層其實是一個結構體 objc_object ,而Class 在底層也是一個結構體 objc_class 。

總結

以上是生活随笔為你收集整理的iOS之深入解析对象isa的底层原理的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。