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
來源:簡書