runtime機制基礎

Objective-C語言是一門動態語言,它將很多靜態語言在編譯和鏈接時期做的事放到了運行時來處理。這種動態語言的優勢在於:我們寫代碼時能夠更具靈活性。但是runtime會在一定程度影響到代碼的結構,在平時儘量減少使用runtime機制進行編碼;runtime應用的時機:(1)當需要非常高的性能開發時,使用runtime,註釋:oc的代碼已經無法滿足性能需求; (2)當我們對系統內部的實現很好奇的時候,可以用clang反編譯成c++去看底層的實現機制。
動態語言的特性使得oc不僅需要一個編譯器,還需要一個運行時系統來執行編譯的代碼,這個運行時系統即runtime。runtime其實是一個runtime庫,它基本上是用C和彙編寫的,這個庫使得C語言有了面向對象的能力。

runtime庫主要做下面幾件事:
(1)封裝:在這個庫中,對象可以用C語言中的結構體表示,而方法可以用C函數來實現,另外再加上了一些額外的特性。這些結構體和函數被runtime函數封裝後,我們就可以在程序運行時創建,檢查,修改類、對象和它們的方法了。
(2)找出方法的最終執行代碼:當程序執行[object doSomething]時,會向消息接收者(object)發送一條消息(doSomething),runtime會根據消息接收者是否能響應該消息而做出不同的反應。

一、OC的類與對象

1.1 類的結構

在OC中類是一個class類型,在本質上是一個指向objc_class結構體的指針,定義是:typedef struct objc_class *Class;

Class isa // isa指針
// 一下成員變量包含在oc2.0以上版本中
Class super_class  // 父類
const char *name   // 類名
long version       // 類的版本信息,默認爲0
long info        // 類信息,供運行期使用的一些位標識
long instance_size  // 該類的實例變量大小
struct objc_ivar_list *ivars // 該類的成員變量鏈表
struct objc_method_list **methodLists   // 方法定義的鏈表
struct objc_cache *cache  // 方法緩存

在這個定義中,有幾個字段需要特別關注:
1> 類自身也是一個對象,這個對象的Class裏面也有一個isa指針,它指向metaClass(元類)。
2> super_class:指向該類的父類,如果該類已經是最頂層的根類(如NSObject或NSProxy),則super_class爲NULL。
3> cache用於緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據isa指針去查找能夠響應這個消息的對象。在實際使用中,這個對象只有一部分方法是常用的,很多方法其實很少用或者根本用不上。在我們每次調用過一個方法後,這個方法就會被緩存到cache列表中,下次調用的時候runtime就會優先去cache中查找,如果cache沒有,纔去methodLists中查找方法。

1.2 objc_object 和 id

objc_object是一個類的實例的結構體,id是一個objc_object類型指針定義如下:
struct objc_object {
Class isa OBJC_ISA_AVAILABILITY;
};
typedef struct objc_object *id;
objc_object結構體只有一個字段,即指向其類的isa指針。當一個oc對象受到消息時,運行時庫會根據實例對象的isa指針找到這個實例對象所屬的類,之後在類或父類的方法列表中去尋找與消息對應的selector指向的方法,找到後即運行這個方法。當創建一個特定類的實例對象時,分配的內存包含一個objc_object數據結構,然後是類的實例變量的數據。NSObject類的alloc和allocWithZone:方法使用函數class_createInstance來創建objc_object數據結構。
id相當於C語言中的泛型指針void*。

1.3 objc_cache

objc_class中的方法緩存字段cache是一個指向objc_cache結構體的指針,定義如下
struct objc_cache {
unsigned int mask /* total = mask + 1 */ ;
unsigned int occupied ;
Method buckets[1] ;
};
mask:一個整數,指定分配的緩存bucket的總數。在方法查找過程中,Objective-C runtime使用這個字段來確定開始線性查找數組的索引位置。指向方法selector的指針與該字段做一個AND位操作(index = (mask & selector))。這可以作爲一個簡單的hash散列算法。
occupied:一個整數,指定實際佔用的緩存bucket的總數。
buckets:指向Method數據結構指針的數組。這個數組可能包含不超過mask+1個元素。需要注意的是,指針可能是NULL,表示這個緩存bucket沒有被佔用,另外被佔用的bucket可能是不連續的。這個數組可能會隨着時間而增長。

1.4 metal class 元類

類本身就是一個對象,本質上也是一個objc_object類型的指針,包含一個isa指針;爲了調用類方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體,也就是metal class(元類:類所屬的類)。
meta-class存儲着一個類的所有類方法。每個類都會有一個單獨的meta-class,因爲每個類的類方法基本不可能完全相同。
meta-class本身也是一個類,也包含一個isa指針,想想看,這個isa指針指向哪裏呢?爲了不讓這種現象無限延展下去,所有meta-class的isa指向基類的meta-class,以此作爲它們的所屬類。

二、類與對象的操作函數

runtime提供了大量的函數來操作類與對象。類的操作方法大部分是以class爲前綴的,而對象的操作方法大部分是以objc或object_爲前綴。

2.1 類操作函數

類操作函數都是針對objc_class結構體中的字段的。

2.1.1成員變量及屬性

成員變量操作:

// 獲取類中指定名稱實例成員變量的信息
Ivar class_getInstanceVariable ( Class cls, const char *name );
它返回一個指向包含name指定的成員變量信息的objc_ivar結構體的指針(Ivar)。

// 獲取類成員變量的信息
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 );
object-c不支持向已經存在的類中添加成員變量;該方法是向運行時創建的類中添加成員變量,需要注意的是,這個方法只能在objc_allocateClassPair函數與objc_registerClassPair之間調用。

// 獲取整個成員變量列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
outCount指針返回數組的大小。需要注意的是,我們必須使用free()來釋放這個數組。

屬性操作函數:

// 獲取指定的屬性
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 );

2.1.2 方法列表(methodLists)

方法操作函數主要如下:

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

一個object-C方法是一個簡單的C函數,它至少包含兩個參數—self和_cmd。所以,我們的實現函數(IMP參數指向的函數)至少需要兩個參數。
這兩個函數的區別:如果類中已經存在name指定的方法,add函數返回NO,replace則會替換方法的實現。如果類中不存在name指定的方法,他們都會創建,並且覆蓋父類同名方法的實現。

// 獲取實例方法
Method class_getInstanceMethod ( Class cls, SEL name );
// 獲取類方法
Method class_getClassMethod ( Class cls, SEL name );
// 獲取所有方法的數組
Method * class_copyMethodList ( Class cls, unsigned int *outCount );
前面兩個和後面的不同在於,前面兩個會搜索父類。

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

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

三、實例(instance)

四、動態創建類和實例

4.1 動態創建類

動態創建類主要有以下方法:
// 創建一個新類和元類
Class objc_allocateClassPair ( Class superclass, const char *name, size_t extraBytes );
extraBytes通常指定爲0,該參數是分配給類和元類對象尾部的索引ivars的字節數。

// 銷燬一個類及其相關聯的類
void objc_disposeClassPair ( Class cls );
如果程序運行中還存在類或其子類的實例,則不能調用針對類調用該方法。

// 在應用中註冊由objc_allocateClassPair創建的類
void objc_registerClassPair ( Class cls );
使用objc_allocateClassPair創建的新類,需要使用objc_registerClassPair註冊之後才能在程序中使用。

Class cls = objc_allocateClassPair(MyClass.class, “MySubClass”, 0);
class_addMethod(cls, @selector(submethod1), (IMP)imp_submethod1, “v@:”);
class_replaceMethod(cls, @selector(method1), (IMP)imp_submethod1, “v@:”);
class_addIvar(cls, “_ivar1”, sizeof(NSString ), log(sizeof(NSString )), “i”);

objc_property_attribute_t type = {“T”, “@\”NSString\”“};
objc_property_attribute_t ownership = { “C”, “” };
objc_property_attribute_t backingivar = { “V”, “_ivar1”};
objc_property_attribute_t attrs[] = {type, ownership, backingivar};

class_addProperty(cls, “property2”, attrs, 3);
objc_registerClassPair(cls);
之後新創建的類cls就能在程序中使用了。

4.2 動態創建對象

// 創建類實例
id class_createInstance ( Class cls, size_t extraBytes );

// 在指定位置創建類實例
id objc_constructInstance ( Class cls, void *bytes );

// 銷燬類實例
void * objc_destructInstance ( id obj );

五、實例操作函數

實例操作函數主要是針對我們創建的實例對象的一系列操作函數;分爲三類:針對整個對象進行操作的函數、針對對象實例變量進行操作的函數、針對對象的類進行操作的函數。

5.1 針對整個對象進行操作的函數

// 返回指定對象的一份拷貝
id object_copy ( id obj, size_t size );
// 釋放指定對象佔用的內存
id object_dispose ( id obj );

假設我們有類A和類B,且類B是類A的子類。類B通過添加一些額外的屬性來擴展類A。現在我們創建了一個A類的實例對象,並希望在運行時將這個對象轉換爲B類的實例對象,這樣可以添加數據到B類的屬性中。這種情況下,我們沒有辦法直接轉換,因爲B類的實例會比A類的實例更大,沒有足夠的空間來放置對象。此時,我們就要以使用以上幾個函數來處理這種情況,如下代碼所示:

NSObject *a = [[NSObject alloc] init];
id newB = object_copy(a, class_getInstanceSize(MyClass.class));
object_setClass(newB, MyClass.class);
object_dispose(a);

5.2 針對對象實例變量進行操作的函數

// 修改類實例的實例變量的值
Ivar object_setInstanceVariable ( id obj, const char *name, void *value );
// 獲取對象實例變量的值
Ivar object_getInstanceVariable ( id obj, const char *name, void **outValue );
// 返回指向給定對象分配的任何額外字節的指針
void * object_getIndexedIvars ( id obj );
// 返回對象中實例變量的值
id object_getIvar ( id obj, Ivar ivar );
// 設置對象中實例變量的值
void object_setIvar ( id obj, Ivar ivar, id value );

如果實例變量的Ivar已經知道,那麼調用object_getIvar會比object_getInstanceVariable函數快,相同情況下,object_setIvar也比object_setInstanceVariable快。

5.3 針對對象的類進行操作的函數

// 返回給定對象的類名
const char * object_getClassName ( id obj );
// 返回對象的類
Class object_getClass ( id obj );
// 設置對象的類
Class object_setClass ( id obj, Class cls );

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章