//聯系人:石虎QQ: 1224614774昵稱:嗡嘛呢叭咪哄
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;
isa:指向元類的objc_class結構體指針,iOS中的類也是對象,元類中儲存有類對象的類方法;
superclass:指向父類的objc_class結構體指針,可以通過父類的指針找到變量和方法;
name:類名;
version:版本號,默認為0
info:其他信息,運行期間的一些位標示
instance_size:類實例變量大小
ivars:該類的成員變量鏈表,是objc_ivar_list結構體指針,
[objc]view plaincopy
structobjc_ivar_list?{
intivar_count;
/*?variable?length?structure?*/
structobjc_ivar?ivar_list[1];
}
objc_var:變量結構體---名稱,類型,偏移字節和占用的空間
[objc]view plaincopy
structobjc_ivar?{
charchar*ivar_name??????????????????????????????????????????OBJC2_UNAVAILABLE;
charchar*ivar_type??????????????????????????????????????????OBJC2_UNAVAILABLE;
intivar_offset ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?OBJC2_UNAVAILABLE;
#ifdef?__LP64__
intspace ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?OBJC2_UNAVAILABLE;
#endif
}
objc_method_list:方法鏈表結構體
[objc]view plaincopy
structobjc_method_list?{
structobjc_method_list*obsolete????????????????????????OBJC2_UNAVAILABLE;
intmethod_count?????????????????????????????????????????OBJC2_UNAVAILABLE;
#ifdef?__LP64__
intspace????????????????????????????????????????????????OBJC2_UNAVAILABLE;
#endif
/*?variable?length?structure?*/
structobjc_method?method_list[1]????????????????????????OBJC2_UNAVAILABLE;
}
method:對象的每個方法的結構體,SEL是方法選擇器,是HASH后的值,可以通過這個值找到函數體的實現,IMP 是函數指針
[objc]view plaincopy
structobjc_method?{
SELmethod_name??????????????????????????????????????????OBJC2_UNAVAILABLE;
charchar*method_types ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? OBJC2_UNAVAILABLE;
IMP?method_imp???????????????????????????????????????????OBJC2_UNAVAILABLE;
}
cache:對象使用過的方法鏈表,
[objc]view plaincopy
structobjc_cache?{
unsignedintmask/*?total?=?mask?+?1?*/OBJC2_UNAVAILABLE;
unsignedintoccupied????????????????????????????????????OBJC2_UNAVAILABLE;
Method?buckets[1]????????????????????????????????????????OBJC2_UNAVAILABLE;
};
protocols:協議鏈表
[objc]view plaincopy
structobjc_protocol_list?{
structobjc_protocol_list*next;
longcount;
Protocol*list[1];
};
二、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<
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增加實例變量的原因,不過這個和一般的實例變量是不一樣的
參考
謝謝!!