「OC」源碼學習——對象的底層探索

「OC」源碼學習——對象的底層探索

前言

上次我們說到了源碼里面的調用順序,現在我們繼續了解我們上一篇文章沒有講完的關于對象的內容函數,完整了解對象的產生對于isa賦值以及內存申請的內容

函數內容

先把_objc_rootAllocWithZone函數的內容先貼上來

static ALWAYS_INLINE id
_class_createInstance(Class cls, size_t extraBytes,int construct_flags = OBJECT_CONSTRUCT_NONE,bool cxxConstruct = true,size_t *outAllocatedSize = nil)
{ASSERT(cls->isRealized());//斷言確保類已加載并完成內存布局(即 "realized" 狀態)bool hasCxxCtor = cxxConstruct && cls->hasCxxCtor(); //查看父類是否存在C++的析構函數bool hasCxxDtor = cls->hasCxxDtor();//查看是否存在C++的析構函數bool fast = cls->canAllocNonpointer();//是否支持指針優化size_t size;size = cls->instanceSize(extraBytes);//計算所需要的字節數if (outAllocatedSize) *outAllocatedSize = size;//返回計算的字節數id obj = objc::malloc_instance(size, cls);//根據類對象申請對應的內存if (slowpath(!obj)) {if (construct_flags & OBJECT_CONSTRUCT_CALL_BADALLOC) {return _objc_callBadAllocHandler(cls);}return nil;}if (fast) {obj->initInstanceIsa(cls, hasCxxDtor);//如果是優化的isa指針先進入進行判斷再進入initIsa} else {// Use raw pointer isa on the assumption that they might be// doing something weird with the zone or RR.obj->initIsa(cls);}if (fastpath(!hasCxxCtor)) {return obj;//沒有析構函數就可以直接返回了}construct_flags |= OBJECT_CONSTRUCT_FREE_ONFAILURE;//添加一個flagreturn object_cxxConstructFromClass(obj, cls, construct_flags);
}

我們來看看instanceSize這個函數究竟實現了什么,按照instanceSize->fastInstanceSize->align16,依次步入,我們可以看到一下內容

static inline size_t align16(size_t x) {return (x + size_t(15)) & ~size_t(15);
}

這個程序就是一個16字節對齊的算法,先拋開這個算法,若是由我們來設計這個算法我們的思路是什么呢?最簡單的就是

(x + 15) / 16 * 16

當然很快我們就意識到了,使用乘法和除法運算的效率和位運算相比實在太低了,正好16是2的4次方,位運算一樣的解決啊

(x + 15) >> 4 << 4

OK現在設計完了我們在來看看蘋果設計的代碼

(x + 15) & ~15

~有按位取反的意思通過按位與操作,將低 4 位(二進制 1111)清零,得到最近的 16 的倍數。用size(15)是為了兼容32位和64機型,因為size_t 是無符號整數類型,其位數由平臺決定(32 位系統為 4 字節,64 位系統為 8 字節)。當 xsize_t 類型時,size_t(15) 確保運算中的右操作數(15)與左操作數(x類型一致,避免隱式轉換帶來的風險

內存內容探究

GGObject *obj = [GGObject alloc];
NSLog(@"%lu", sizeof(obj));
NSLog(@"%lu", class_getInstanceSize(obj.class));
NSLog(@"%lu", malloc_size((__bridge const void*)(obj)));

根據這個內容我們可以看到自定義類對應的大小

image-20250424205643745

當我們的類被編譯了之后,底層會類編譯成 isa + 成員變量,所以在給類的實例分配內存的話這個內存塊存儲的就是 isa + 成員變量的值

內存對齊

學過C語言我們都知道,結構體是有內存對齊這個概念的,當然不同的排列所實現的內存對齊是不同的,例如

struct Mystruct1{char a;     //1字節double b;   //8字節int c;      //4字節short d;    //2字節
}Mystruct1;struct Mystruct2{double b;   //8字節int c;      //4字節short d;    //2字節char a;     //1字節
}Mystruct2;//計算 結構體占用的內存大小
NSLog(@"%lu-%lu",sizeof(Mystruct1),sizeof(Mystruct2));// 打印:24 - 16

結構體安排的順序會關系到結構體內存的大小,我們知道OC之中的對象其實就是由結構體構成的,那我們來探究一下不同的屬性對對象的內存是否有影響呢

內存重排

對象的本質 = isa + 成員變量的值。

#import <Foundation/Foundation.h>
@interface MyPerson : NSObject
// isa 8字節
@property (nonatomic, copy) NSString *name; // 8字節
@property (nonatomic, copy) NSString *hobby; // 8字節
@property (nonatomic, assign) int age; // 4字節
@property (nonatomic, assign) double height; // 8字節
@property (nonatomic, assign) short number; // 2字節
@end

一共38字節,16字節內存對齊得到總的字節數為48字節,在探究內存問題之前,先學幾個lldb命令

lldb指令:p   輸出10進制p/x 輸出16進制p/0 輸出8進制p/t 輸出2進制p/f 輸出浮點數x   輸出地址x/4gx  輸出4個字節地址x/6gx  輸出6個字節地址

現在我們用lldb進行調試

 MyPerson *p = [GGObject alloc];p.name = @"bb";p.hobby = @"吃吃睡睡喝喝";p.height = 1.80;p.age = 26;p.number = 123;

其實嘗試著將類之中的屬性內容,打印出來可以看到,無論屬性怎么排列,這個MyPerson類age和number這兩個屬性永遠會排在一起,這個就是編譯器的內存優化,對屬性進行重排

image-20250425194336343

我們看到在類的內部,我們的屬性會通過重新排列獲得最小內存,接下來我們看看能能不能重排父類的屬性和子類屬性吧

image-20250425200345352

還是可以看到子類屬性并不會跟父類屬性合并

@interface JCTestObject : NSObject
{@publicint count;// 若count在此處,為JCTestSubObject實例實際需要40字節,系統為實例分配48字節NSObject *obj1;NSObject *obj2;
//    int count; // 若count在此處,為JCTestSubObject實例實際需要32字節,系統為實例分配32字節}
@end
@interface JCTestSubObject : JCTestObject
{@publicint count2;
}

isa走向

之前學習過關于isa指針的部分內容,

img

結論:

類的實例isa --> 類對象;
類對象isa --> 元類對象;
元類對象isa --> 根元類對象;
根元類對象isa --> 根元類對象自己。

objc_class 與 `objc_object

objc_object 是所有 Objective-C 對象的底層結構體,其定義如下

struct objc_object {isa_t isa;  // 指向類對象或元類的指針
};

objc_class 是類的底層結構體,類對象(如 [LGPerson class])和元類(metaclass)均為此類型。其繼承自 objc_object,定義如下:

struct objc_class : objc_object {Class superclass;          // 父類指針cache_t cache;             // 方法緩存(哈希表)class_data_bits_t bits;    // 指向類信息(如方法列表、屬性等)
};

1

img

class_getClassMethodclass_getInstanceMethod

先定義模版類

@interface JCObject : NSObject
{int _sum;
}
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *hobby;
@property (nonatomic, assign) int age;
@property (nonatomic, assign) double height;
@property (nonatomic, assign) short number;
- (void)speak;
- (void)sayHello;
+ (void)walk;
@end@implementation JCObject- (void)speak {NSLog(@"%s", __func__);
}
+ (void)walk {NSLog(@"%s", __func__);
}-(void) sayHello {NSLog(@"%s", __func__);
}

在完成兩個函數

void jcInstanceMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getInstanceMethod(pClass, @selector(speak));Method method2 = class_getInstanceMethod(metaClass, @selector(speak));Method method3 = class_getInstanceMethod(pClass, @selector(walk));Method method4 = class_getInstanceMethod(metaClass, @selector(walk));NSLog(@"%s - %p-%p-%p-%p",__func__,method1,method2,method3,method4);
}void jcClassMethod_classToMetaclass(Class pClass){const char *className = class_getName(pClass);Class metaClass = objc_getMetaClass(className);Method method1 = class_getClassMethod(pClass, @selector(speak));Method method2 = class_getClassMethod(metaClass, @selector(speak));Method method3 = class_getClassMethod(pClass, @selector(walk));Method method4 = class_getClassMethod(metaClass, @selector(walk));NSLog(@"%s-%p-%p-%p-%p",__func__,method1,method2,method3,method4);
}//main函數
JCObject *person = [JCObject alloc];
Class pClass = object_getClass(person);
jcObjc_copyMethodList(pClass);
jcInstanceMethod_classToMetaclass(pClass);
jcClassMethod_classToMetaclass(pClass);

得到的結果

image-20250502230010829

jcInstanceMethod_classToMetaclass

要了解jcInstanceMethod_classToMetaclass之中的內容就要從理解class_getInstanceMethod開始,顧名思義class_getInstanceMethod這個方法,主要是用于獲取實例方法,如果在傳入的類或者類的父類中沒有找到指定的實例方法,則返回NULL。從實例方法存儲在類中,類方法存儲在元類中可以得知,JCObject的方法列表打印結果只有sayHello方法

jcClassMethod_classToMetaclass

需要先了解class_getClassMethod這個方法,看該方法的源碼實現,可以得出class_getClassMethod的實現是獲取類的類方法,其本質就是獲取元類的實例方法,最終還是會走到class_getInstanceMethod

//獲取類方法
Method class_getClassMethod(Class cls, SEL sel)
{if (!cls  ||  !sel) return nil;return class_getInstanceMethod(cls->getMeta(), sel);
}??
//獲取元類
//在getMeta源碼中,如果判斷出cls是元類,那么就不會再繼續往下遞歸查找,會直接返回this,其目的是為了防止元類的無限遞歸查找
Class getMeta() {if (isMetaClass()) return (Class)this;else return this->ISA();
}

img

所以我們再來看看結果

對于speak方法存在于類對象之中,而walk方法存在于元類對象之中,所以使用class_getInstanceMethod可以在類對象之中找到speak方法,而在元類對象之中找到walk的類方法

而對于class_getClassMethod來說如果不是元類會先找到元類,然后再在元類之中查找類方法

iskindOfClass & isMemberOfClass

  • iskindOfClass & isMemberOfClass 類方法調用
//-----使用 iskindOfClass & isMemberOfClass 類方法
BOOL re1 = [(id)[NSObject class] isKindOfClass:[NSObject class]];       //
BOOL re2 = [(id)[NSObject class] isMemberOfClass:[NSObject class]];     //
BOOL re3 = [(id)[LGPerson class] isKindOfClass:[LGPerson class]];       //
BOOL re4 = [(id)[LGPerson class] isMemberOfClass:[LGPerson class]];     //
NSLog(@" re1 :%hhd\n re2 :%hhd\n re3 :%hhd\n re4 :%hhd\n",re1,re2,re3,re4);
  • iskindOfClass & isMemberOfClass 實例方法調用
//------iskindOfClass & isMemberOfClass 實例方法
BOOL re5 = [(id)[NSObject alloc] isKindOfClass:[NSObject class]];       //
BOOL re6 = [(id)[NSObject alloc] isMemberOfClass:[NSObject class]];     //
BOOL re7 = [(id)[LGPerson alloc] isKindOfClass:[LGPerson class]];       //
BOOL re8 = [(id)[LGPerson alloc] isMemberOfClass:[LGPerson class]];     //
NSLog(@" re5 :%hhd\n re6 :%hhd\n re7 :%hhd\n re8 :%hhd\n",re5,re6,re7,re8);

img

  • isKindOfClass
    檢查對象是否屬于指定類或其繼承鏈中的任意父類。例如,若對象是 Person 類的子類實例(如 Student),則 [student isKindOfClass:[Person class]] 返回 YES
//--isKindOfClass---類方法、對象方法
//+ isKindOfClass:第一次比較是 獲取類的元類 與 傳入類對比,再次之后的對比是獲取上次結果的父類 與 傳入 類進行對比
+ (BOOL)isKindOfClass:(Class)cls {// 獲取類的元類 vs 傳入類// 根元類 vs 傳入類// 根類 vs 傳入類// 舉例:LGPerson vs 元類 (根元類) (NSObject)for (Class tcls = self->ISA(); tcls; tcls = tcls->superclass) {if (tcls == cls) return YES;}return NO;
}//- isKindOfClass:第一次是獲取對象類 與 傳入類對比,如果不相等,后續對比是繼續獲取上次 類的父類 與傳入類進行對比
- (BOOL)isKindOfClass:(Class)cls {
/*
獲取對象的類 vs 傳入的類 
父類 vs 傳入的類
根類 vs 傳入的類
nil vs 傳入的類
*/for (Class tcls = [self class]; tcls; tcls = tcls->superclass) {if (tcls == cls) return YES;}return NO;
}
  • isMemberOfClass
    嚴格檢查對象是否直接屬于指定類,不包含繼承鏈。例如,Student 實例調用 isMemberOfClass:[Person class] 會返回 NO,因為其直接類是 Student 而非 Person
//-----類方法
//+ isMemberOfClass : 獲取類的元類,與 傳入類對比
+ (BOOL)isMemberOfClass:(Class)cls {return self->ISA() == cls;
}
//-----實例方法
//- isMemberOfClass : 獲取對象的類,與 傳入類對比
- (BOOL)isMemberOfClass:(Class)cls {return [self class] == cls;
}

參考文章

iOS-底層原理 09:類 & isa 經典面試題分析

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

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

相關文章

【C++指南】STL list容器完全解讀(一):從入門到掌握基礎操作

. &#x1f493; 博客主頁&#xff1a;倔強的石頭的CSDN主頁 &#x1f4dd;Gitee主頁&#xff1a;倔強的石頭的gitee主頁 ? 文章專欄&#xff1a;《C指南》 期待您的關注 文章目錄 一、初識list容器1.1 什么是list&#xff1f;1.2 核心特性1.3 典型應用場景 二、核心成員函數…

labelimg快捷鍵

一、核心標注快捷鍵 ?W?&#xff1a;調出標注十字架&#xff0c;開始繪制矩形框&#xff08;最常用功能&#xff09;?A/D?&#xff1a;切換上一張(A)或下一張(D)圖片&#xff0c;實現快速導航?Del?&#xff1a;刪除當前選中的標注框 二、文件操作快捷鍵 ?CtrlS?&…

linux-文件操作

在 Linux 系統中&#xff0c;文件操作與管理是日常使用和系統管理的重要組成部分。下面將詳細介紹文件的復制、移動、鏈接創建&#xff0c;以及文件查找、文本處理、排序、權限管理等相關知識。 一、文件的復制 在 Linux 里&#xff0c;cp 命令可用于復制文件或目錄&#xff…

C++ 復習

VS 修改 C 語言標準 右鍵項目-屬性 輸入輸出 //引用頭文件&#xff0c;用<>包裹起來的一般是系統提供的寫好的代碼 編譯器會在專門的系統路徑中去進行查找 #include <iostream> //自己寫的代碼文件一般都用""包裹起來 編譯器會在當前文件所在的目錄中査…

openGauss新特性 | HTAP新特性介紹

一、行列融合功能簡介 HTAP 行列融合特性在單機、主備場景下&#xff0c;通過節點的行列雙格式內存模式&#xff0c;實現openGauss HTAP一體化數據庫架構。 通過高效的行列轉換技術方案&#xff0c;節點讀取磁盤行存數據&#xff0c;生成列存儲單元&#xff08;Column Unit&am…

雙目測量中的將視差圖重投影成三維坐標圖

雙目測距主要步驟如下&#xff1a; 左右兩張圖片 → 匹配 → 得到視差圖 disp&#xff1b; 使用 cv2.reprojectImageTo3D(disp, Q) 將視差圖 重投影 成三維坐標圖 → 得到 points_3d 什么是 points_3d&#xff1f; points_3d cv2.reprojectImageTo3D(disp, Q)points_3d.shap…

《深度剖析:SOAP與REST,API集成的兩極選擇》

API作為不同系統之間交互的橋梁&#xff0c;其設計與實現的優劣直接影響著整個軟件生態的運轉效率。而在API的設計領域&#xff0c;SOAP和REST猶如兩座巍峨的山峰&#xff0c;各自代表著截然不同的設計理念與應用方向&#xff0c;成為開發者在構建API時必須慎重權衡的關鍵選項。…

非對稱加密算法(RSA、ECC、SM2)——密碼學基礎

對稱加密算法&#xff08;AES、ChaCha20和SM4&#xff09;Python實現——密碼學基礎(Python出現No module named “Crypto” 解決方案) 這篇的續篇&#xff0c;因此實踐部分少些&#xff1b; 文章目錄 一、非對稱加密算法基礎二、RSA算法2.1 RSA原理與數學基礎2.2 RSA密鑰長度…

Pillow 玩圖術:輕松獲取圖片尺寸和顏色模式

前言 在這個“圖像為王”的時代,誰還敢說自己沒被一張圖折磨過?一張圖片不講武德,說崩就崩,說卡就卡,仿佛像素里藏著程序員的眼淚。不管你是網頁設計師、AI煉丹師,還是只是想把貓片修得像藝術品,圖片的尺寸和顏色模式都是你必須掌握的第一手情報。如果你不知道它有多寬…

下載core5compat 模塊時,被禁止,顯示 - servese replied: Forbbidden. -->換鏡像源

怎么解決&#xff1f; --->換鏡像源 方法 1&#xff1a;使用命令行參數指定鏡像源 在運行 Qt 安裝器時&#xff0c;通過 --mirror 參數指定鏡像源&#xff1a; # Windows qt-unified-windows-x64-online.exe --mirror https://mirrors.ustc.edu.cn/qtproject# Linux/macO…

WPF中Behaviors

行為的好處 可以把復雜的界面邏輯抽象出去&#xff0c;讓xaml的界面設計更簡單&#xff0c;更清爽 1.安裝包 Microsoft.Xaml.Behaviors.Wpf2.簡單實現拖動效果 <Border Width"100"Height"100"Background"Red"><i:Interaction.Behav…

GitHub 趨勢日報 (2025年05月03日)

本日報由 TrendForge 系統生成 https://trendforge.devlive.org/ &#x1f4c8; 今日整體趨勢 Top 10 排名項目名稱項目描述今日獲星總星數語言1hacksider/Deep-Live-Camreal time face swap and one-click video deepfake with only a single image? 1582? 59337Python2aip…

Oracle OCP認證考試考點詳解083系列08

題記&#xff1a; 本系列主要講解Oracle OCP認證考試考點&#xff08;題目&#xff09;&#xff0c;適用于19C/21C,跟著學OCP考試必過。 36. 第36題&#xff1a; 題目 解析及答案&#xff1a; 關于數據庫閃回&#xff08;FLASHBACK DATABASE&#xff09;功能&#xff0c;以下…

優化01-統計信息

Oracle 的統計信息是數據庫優化器生成高效執行計劃的核心依據。它記錄了數據庫對象&#xff08;如表、索引、列等&#xff09;的元數據信息&#xff0c;幫助優化器評估查詢成本并選擇最優執行路徑。以下是關于 Oracle 統計信息的詳細介紹&#xff1a; 一、統計信息的分類 表統…

動態規劃-面試題08.01三步問題-力扣(LeetCode)

一、題目解析 此題可以類比第N個泰波那契數 二、算法解析 1、狀態表示 根據上面的分析和題目要求&#xff0c;dp[i]表示&#xff1a;到達i位置&#xff0c;一共有多少種方法 2、狀態轉移方程 以i位置的狀態&#xff0c;以最近一步劃分問題 dp[i] 從i-1->i dp[i-1] 從…

kotlin中枚舉帶參數和不帶參數的區別

一 ? 代碼對比總結 第一段&#xff08;帶參數 工具方法&#xff09; enum class SeatPosition(val position: Int) {DRIVER_LEFT(0),DRIVER_RIGHT(1),SECOND_LEFT(2),SECOND_RIGHT(3);companion object {fun fromPosition(position: Int): SeatPosition? {return SeatPosi…

Java使用JDBC操作數據庫

1.創建一個數據庫一會用來連接 2.使用idea新建一個Java項目 3.在pom文件中加上相關依賴&#xff0c;并配置Maven路徑 <dependencies><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>…

重名導致does not name a type

今天在Ubuntu24.04上編成時&#xff0c;makefile編譯報錯: falsecolor.h:48:9: error: ‘FalseColor’ does not name a type48 | FalseColor* content ;| ^~~~~~~~~~falsecolor.h的部分代碼如下: class FalseColor {public:FalseColor(int w, int h){width …

Vue3 后臺管理系統模板

Vue3 后臺管理系統模板 gie倉庫地址 一個基于 Vue3 TypeScript Element Plus 的后臺管理系統模板&#xff0c;集成了動態路由和權限管理功能。 技術棧 Vue 3.2TypeScript 4.5Vue Router 4Vuex 4Element Plus 2.9AxiosLess 功能特性 &#x1f680; 基于 Vue3 最新技術棧開…

林業數智化轉型初步設計方案

最近應林業方面的朋友要求,幫助其設計了林業方面的數字化智能化轉型的方案設計,編寫了如下內容,供大家參考,林業方面主要有三大方向,即林業生態、生物災害和疫源疫病,目前已經建成了一些信息化系統,但在數字化智能化方面偏弱,就想著如何借助人工智能、物聯網、大數據和…