iOS Class結構分析

来源:https://www.cnblogs.com/jiuyi/archive/2019/01/03/10213688.html
-Advertisement-
Play Games

objc_class結構體 類在OC中是objc_class的結構體指針 typedef struct objc_class *Class; 在objc/runtime.h中objc_class結構體的定義如下: 下麵我們來看一下objc_class的定義,我們在使用runtime以class為首碼 ...


objc_class結構體

類在OC中是objc_class的結構體指針

typedef struct objc_class *Class;

在objc/runtime.h中objc_class結構體的定義如下:

 

struct objc_class {

        Class isa  OBJC_ISA_AVAILABILITY;


        #if !__OBJC2__

        Class super_class                       OBJC2_UNAVAILABLE;  // 父類

        const char *name                        OBJC2_UNAVAILABLE;  // 類名

        long version                            OBJC2_UNAVAILABLE;  // 類的版本信息,預設為0

        long info                               OBJC2_UNAVAILABLE;  // 類信息,供運行期使用的一些位標識

        long instance_size                      OBJC2_UNAVAILABLE;  // 該類的實例變數大小

        struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 該類的成員變數鏈表

        struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定義的鏈表

        struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法緩存

        struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 協議鏈表

        #endif

} OBJC2_UNAVAILABLE;

下麵我們來看一下objc_class的定義,我們在使用runtime以class為首碼的方法時主要就是針對這個結構體中的各個欄位的。

指向元類的指針(isa)

在OC中所有的類其實也是一個對象,那麼這個對象也會有一個所屬的類,這個類就是元類也就是結構體裡面isa指針所指的類。

那什麼是元類呢?
元類的定義:元類就是類對象的類。每個類都有自己的元類,因為每個類都有自己獨一無二的方法。

簡單點說就是:

  • 當你給對象發送消息時,消息是在尋找這個對象的類的方法列表。(實例方法)
  • 當你給類發消息時,消息是在尋找這個類的元類的方法列表。(類方法)

那元類的類是什麼呢?
元類,就像之前的類一樣,它也是一個對象。你也可以調用它的方法。自然的,這就意味著他必須也有一個類。

所有的元類都使用根元類(繼承體系中處於頂端的類的元類)作為他們的類。這就意味著所有NSObject的子類(大多數類)的元類都會以NSObject的元類作為他們的類

根據這個規則,所有的元類使用根元類作為他們的類,根元類的元類則就是它自己。也就是說基類的元類的isa指針指向他自己。

這裡有一副圖可以很好的展現這些關係:


   

runtime方法

// 判斷給定的Class是否是一個元類
BOOL class_isMetaClass ( Class cls );
class_isMetaClass函數,如果是cls是元類,則返回YES;如果否或者傳入的cls為Nil,則返回NO。

指向父類的指針(super_class)

指向該類的父類,如果該類已經是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。

// 獲取類的父類
Class class_getSuperclass ( Class cls );

class_getSuperclass函數,當cls為Nil或者cls為根類時,返回Nil。不過通常我們可以使用NSObject類的superclass方法來達到同樣的目的。

 

 

類名(name)

// 獲取類的類名
const char * class_getName ( Class cls );

對於class_getName函數,如果傳入的cls為Nil,則返回一個字字元串。

版本(version)

版本相關的操作包含以下函數:

 

// 獲取版本號
int class_getVersion ( Class cls );
// 設置版本號
void class_setVersion ( Class cls, int version );

 

實例變數大小(instance_size)

// 獲取實例大小
size_t class_getInstanceSize ( Class cls );

 

成員變數(ivars)及屬性

objc_class中,所有的成員變數、屬性的信息是放在鏈表ivars中的。ivars是一個數組,數組中每個元素是指向Ivar(變數信息)的指針。runtime提供了豐富的函數來操作這一欄位。大體上可以分為以下幾類:

 

 

1.成員變數操作函數,主要包含以下函數:

// 獲取類中指定名稱實例成員變數的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );

// 獲取類成員變數的信息
Ivar class_getClassVariable ( Class cls, const char *name );

// 添加成員變數
BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );

// 獲取整個成員變數列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );

 

class_getInstanceVariable函數,它返回一個指向包含name指定的成員變數信息的objc_ivar結構體的指針(Ivar)。

 

 

class_getClassVariable函數,目前沒有找到關於Objective-C中類變數的信息,一般認為Objective-C不支持類變數。註意,返回的列表不包含父類的成員變數和屬性。

 

 

Objective-C不支持往已存在的類中添加實例變數,因此不管是系統庫提供的提供的類,還是我們自定義的類,都無法動態添加成員變數。但如果我們通過運行時來創建一個類的話,又應該如何給它添加成員變數呢?這時我們就可以使用class_addIvar函數了。不過需要註意的是,這個方法只能在objc_allocateClassPair函數與objc_registerClassPair之間調用。另外,這個類也不能是元類。成員變數的按位元組最小對齊量是1<<alignment。這取決於ivar的類型和機器的架構。如果變數的類型是指針類型,則傳遞log2(sizeof(pointer_type))。

 

 

class_copyIvarList函數,它返回一個指向成員變數信息的數組,數組中每個元素是指向該成員變數信息的objc_ivar結構體的指針。這個數組不包含在父類中聲明的變數。outCount指針返回數組的大小。需要註意的是,我們必須使用free()來釋放這個數組。

 

2.屬性操作函數,主要包含以下函數:

// 獲取指定的屬性
objc_property_t class_getProperty ( Class cls, const char *name );


// 獲取屬性列表 objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount ); // 為類添加屬性 BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount ); // 替換類的屬性 void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );

 

這一種方法也是針對ivars來操作,不過只操作那些是屬性的值。

 

方法(methodLists)

 

objc_method_list方法鏈表中存放的是該類的成員方法(-方法),類方法(+方法)存在meta-class的objc_method_list鏈表中。

 

方法操作主要有以下函數:

// 添加方法
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );

// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );

// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );

// 獲取所有方法的數組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );

// 替代方法的實現
IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );

// 返回方法的具體實現
IMP class_getMethodImplementation ( Class cls, SEL name );

IMP class_getMethodImplementation_stret ( Class cls, SEL name );

// 類實例是否響應指定的selector
BOOL class_respondsToSelector ( Class cls, SEL sel );

 

class_addMethod的實現會覆蓋父類的方法實現,但不會取代本類中已存在的實現,如果本類中包含一個同名的實現,則函數會返回NO。如果要修改已存在實現,可以使用method_setImplementation。一個Objective-C方法是一個簡單的C函數,它至少包含兩個參數—self和_cmd。所以,我們的實現函數(IMP參數指向的函數)至少需要兩個參數,如下所示:

void myMethodIMP(id self, SEL _cmd)
{
// implementation ....
}

與成員變數不同的是,我們可以為類動態添加方法,不管這個類是否已存在。

另外,參數types是一個描述傳遞給方法的參數類型的字元數組,這就涉及到類型編碼,我們將在後面介紹。

這裡我們的void的前面沒有+、-號,因為只是C的代碼。

 

class_getInstanceMethod、class_getClassMethod函數,與class_copyMethodList不同的是,這兩個函數都會去搜索父類的實現。

 

class_copyMethodList函數,返回包含所有實例方法的數組,如果需要獲取類方法,則可以使用class_copyMethodList(object_getClass(cls), &count)(一個類的實例方法是定義在元類裡面)。該列表不包含父類實現的方法。outCount參數返回方法的個數。在獲取到列表後,我們需要使用free()方法來釋放它。

 

class_replaceMethod函數,該函數的行為可以分為兩種:如果類中不存在name指定的方法,則類似於class_addMethod函數一樣會添加方法;如果類中已存在name指定的方法,則類似於method_setImplementation一樣替代原方法的實現。

 

class_getMethodImplementation函數,該函數在向類實例發送消息時會被調用,並返回一個指向方法實現函數的指針。這個函數會比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函數指針可能是一個指向runtime內部的函數,而不一定是方法的實際實現。例如,如果類實例無法響應selector,則返回的函數指針將是運行時消息轉發機制的一部分。

 

class_respondsToSelector函數,我們通常使用NSObject類的respondsToSelector:或instancesRespondToSelector:方法來達到相同目的。

 

緩存(cache)

 

用於緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據isa指針去查找能夠響應這個消息的對象。在實際使用中,這個對象只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次消息來時,我們都是methodLists中遍歷一遍,性能勢必很差。這時,cache就派上用場了。在我們每次調用過一個方法後,這個方法就會被緩存到cache列表中,下次調用的時候runtime就會優先去cache中查找,如果cache沒有,才去methodLists中查找方法。這樣,對於那些經常用到的方法的調用,提高了調用的效率。

 

協議(objc_protocol_list)

協議相關的操作包含以下函數:

// 添加協議
BOOL class_addProtocol ( Class cls, Protocol *protocol );

// 返回類是否實現指定的協議
BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );

// 返回類實現的協議列表
Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );

 

class_conformsToProtocol函數可以使用NSObject類的conformsToProtocol:方法來替代。

 

class_copyProtocolList函數返回的是一個數組,在使用後我們需要使用free()手動釋放。

 

objc_class結構體的應用

 

沒有實際應用的知識講解都是耍流氓

 

@property的本質

這裡有一個孫源的面試題是:@property 的本質是什麼?ivar、getter、setter 是如何生成並添加到這個類中的。

簡單點說就是:@property = ivar + getter + setter;

也就是生成實例變數及對應的存取方法。

詳細的回答請看面試題第六題。

那這跟我們這裡所講的objc_class結構體有什麼關係呢?

因為@property對應的ivar、getter和setter都會對應添加到我們結構體中的ivar_list、method_list中。也就是說我們每次增加一個屬性,系統都會在ivar_list添加一個成員變數的描述,在 method_list 中增加 setter 與 getter 方法的描述。

其他Runtime結構體

objc_object結構體

除了類有對應的結構體,對象也有對應的結構體。

typedef struct objc_object *id;

id就是指向對象對應的結構體。對象的結構體只有 isa 指針,指向它所屬的類。而類的結構體也有 isa 指針指向它的元類。

所以在OC中objc_class 結構體是繼承自 objc_object:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

struct objc_class : objc_object {
    Class superclass;
    cache_t cache;             // formerly cache pointer and vtable
    class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
    class_rw_t *data() { 
        return bits.data();
    }
}; 

Category結構體

Category的定義如下:

typedef struct objc_category *Category;

 

Category是一個objc_category結構體的指針,objc_category的定義如下:

struct objc_category {
    char *category_name                                      OBJC2_UNAVAILABLE;
    char *class_name                                         OBJC2_UNAVAILABLE;
    struct objc_method_list *instance_methods                OBJC2_UNAVAILABLE;
    struct objc_method_list *class_methods                   OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
}                                                            OBJC2_UNAVAILABLE;

通過上面的結構體,大家可以很清楚的看出存儲的內容。我們繼續往下看,打開objc源代碼,在 objc-runtime-new.h中我們可以發現如下定義:

struct category_t {
    const char *name;
    classref_t cls;
    struct method_list_t *instanceMethods;
    struct method_list_t *classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
};

上面的定義需要提到的地方有三點:

 

name 是指 class_name 而不是 category_name

cls是要擴展的類對象,編譯期間是不會定義的,而是在Runtime階段通過name對應到對應的類對象

instanceProperties表示Category里所有的properties,這就是我們可以通過objc_setAssociatedObject和objc_getAssociatedObject增加實例變數的原因,不過這個和一般的實例變數是不一樣的

 

 

作者:齊滇大聖

鏈接:https://www.jianshu.com/p/73e454178e77

來源:簡書


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • MYSQL的優化一個很棘手的問題,也是一個公司最想處理得當的問題. 那麼今天,本人為大家帶來幾點優化資料庫的方法: 1.選取最適用的欄位屬性 一般來說,資料庫的的表越小,在其上面執行的查詢也會越快.因此,我們在設計表的時候可以將表中的寬度設的儘可能的小. 對於欄位來說,,我們儘量和值為NOT NUL ...
  • 一、中文寫入亂碼問題 我輸入的中文編碼是 urf8 的,建的庫是 urf8 的,但是插入MySQL總是亂碼,一堆"???????????????????????"。可以使用以下的方式試試決解: 原url地址是 改為 就OK了。 二、Incorrect string value: '\xF0\x9F. ...
  • 因為重度嫌棄Mysql 8.0.xxx的各種妖魔鬼怪,所以想卸載了。但是,百度了很多文章,日期也是近幾個月,但是卻並不適用。所以特寫此文記錄一下。 按照百度萬金油通用第一步,就是要停止MySQL的服務。 網上給的方法是,net stop mysql,但是我發現到我這裡就變成無法識別的指令了。在排除了... ...
  • 1、LIKE 通常與 % 一同使用,類似於一個元字元的搜索。若substr不在str中,則返回0。 SELECT 'test' LIKE '%e%' as `ret`; # 1 SELECT 'test' LIKE '%a%' as `ret`; # 0 2、INSTR(str,substr) 返回... ...
  • §歷史回顧 2018年歲末,李大胖朦朧中上了開往Hbase王國的車,伴著一聲長鳴,列出緩緩駛出站臺,奔向無垠的廣袤。 (圖片來自於網路) 如不熟悉劇情的,可觀看文章: 五分鐘輕鬆瞭解Hbase列式存儲 Hbase給初學者的“下馬威” §生逢其時 隨著改革開放的持續推進,移動互聯網的長足發展,以及物聯 ...
  • 數據倉庫是伴隨著信息技術和決策支持系統(DSS,Decision Support System)的發展而產生的,利用歷史的操作數據進行管理和決策。 數據倉庫是一個面向主題的、集成的、非易失的、隨著時間變化的,用於支持管理人員決策的數據集合,數據倉庫包含粒度化的企業數據,在不同的粒度級別上對數據進行聚 ...
  • 1、首先講下row_number() over() 是乾什麼的? 是一個分析函數,會在數據表生成一個排序列。 案例SQL: 如下圖實例: 2、使用row_number() over()分頁查詢數據 本人覺得這種分頁方法簡便,一直也在用這個方法(使用row_number() over()產生的排序列也 ...
  • 在iOS開發中經常會用到UIlabel來展示一些文字性的內容,但是預設的文字排版會覺得有些擠,為了更美觀也更易於閱讀我們可以通過某些方法將UIlabel的行間距和字間距按照需要調節。 比如一個Label的預設間距效果是這樣: 然後用一個封裝起來的Category來調整這部分文字的行間距,其中5.0就 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...