Objective-C Runtime (一):類與物件

雲本尊發表於2018-05-24

Objective-C Runtime (一):類與物件

Runtime介紹

Objective-C語言是一門動態語言,它將很多靜態語言在編譯和連結時期做的事放到了執行時來處理。這意味著它不僅需要一個編譯器,也需要一個執行時系統來動態得建立類和物件、進行訊息傳遞和轉發。這個執行時系統即Objc Runtime。Objc Runtime其實是一個Runtime庫,它基本上是用C和彙編寫的,這個庫使得C語言有了物件導向的能力。

Runtime庫主要做下面幾件事:

  1. 封裝:在這個庫中,物件可以用C語言中的結構體表示,而方法可以用C函式 來實現,另外再加上了一些額外的特性。這些結構體和函式被runtime函式封裝後,我們就可以在程式執行時建立,檢查,修改類、物件和它們的方法了。
  2. 訊息傳遞:當程式執行[object doSomething]時,會向訊息接收者(object)傳送一條訊息(doSomething),runtime會根據訊息接收者是否能響應該訊息而做出不同的反應。這將在後面詳細介紹。

在接下來的一系列文章中,我將介紹一下runtime的基本工作原理以及在我們的程式碼裡可以用它來做什麼。在本文中,我們先來了解一下類與物件。

類與物件基礎資料結構

###OC物件 OC的物件主要可以分為三種:

  1. instance物件(例項物件)
  2. class物件(類物件)
  3. meta-class物件(元類物件):主要儲存著isa指標,superclass指標,類的類方法的資訊(class method)。

instance物件 它主要是通過類alloc出來的物件,每次呼叫alloc都會產生新的instance物件。

NSObjcet *object1 = [[NSObjcet alloc] init];
複製程式碼

instance物件在記憶體中儲存的資訊包括:

  1. isa指標
  2. 成員變數

class物件 我們通過class方法或runtime方法得到一個class物件。class物件也就是類物件。==每一個類在記憶體中有且只有一個class物件==。

Class objectClass1 = [NSObject class];
// runtime
Class objectClass2 = object_getClass(object1);
複製程式碼

class物件在記憶體中儲存的資訊主要包括:

  1. isa指標
  2. superclass指標
  3. 類的屬性資訊(@property)
  4. 類的成員變數資訊(ivar)
  5. 類的物件方法資訊(instance method)
  6. 類的協議資訊(protocol)

meta-class物件 我們通過runtime方法得到一個class物件。==每個類在記憶體中有且只有一個meta-class物件。==

//runtime中傳入類物件此時得到的就是元類物件
Class objectMetaClass = object_getClass([NSObject class]);
// 判斷該物件是否為元類物件
class_isMetaClass(objectMetaClass) 
複製程式碼

meta-class物件在記憶體中儲存的資訊主要包括:

  1. isa指標
  2. superclass指標
  3. 類的類方法的資訊(class method)

我們來看看下面的圖,來分析這三者的關係

Objective-C Runtime (一):類與物件

從上圖中,我們可以分析出:

  1. instance物件的isa指向class
  2. class的isa指向meta-class
  3. meta-class的isa指向基類的meta-class,基類的isa指向自己
  4. class的superclass指向父類的class,如果沒有父類,superclass指標為nil
  5. meta-class的superclass指向父類的meta-class,基類的meta-class的superclass指向基類的class
  6. instance物件呼叫物件方法的軌跡,isa找到class,方法不存在,就通過superclass找父類
  7. class呼叫類方法的軌跡,isa找meta-class,方法不存在,就通過superclass找父類

###Class

Objective-C類是由Class型別來表示的,它實際上是一個指向objc_class結構體的指標。在objc/objc.h它的定義如下:

/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;
複製程式碼

objc/runtime.h中我們可以看到結構體objc_class的定義如下:

struct objc_class {
    Class _Nonnull isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class _Nullable super_class                              OBJC2_UNAVAILABLE;// 父類
    const char * _Nonnull name                               OBJC2_UNAVAILABLE;// 類名
    long version                                             OBJC2_UNAVAILABLE;// 類的版本資訊,預設為0
    long info                                                OBJC2_UNAVAILABLE;// 類資訊,供執行期使用的一些位標識
    long instance_size                                       OBJC2_UNAVAILABLE;// 該類的例項變數大小
    struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;//該類的成員變數連結串列
    struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;// 方法定義的連結串列
    struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;// 方法快取
    struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;// 協議連結串列
#endif

} OBJC2_UNAVAILABLE;
複製程式碼

###1. name 類名

// 獲取類的類名
const char * class_getName ( Class cls );
複製程式碼

###2. isa 在Objective-C中,類自身也是一個物件,我們稱之為類物件,在這個類物件中,也有一個isa指標,它指向了metaClass(元類)

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

###4. instance_size

// 獲取例項大小
size_t class_getInstanceSize ( Class cls );
複製程式碼

###5. objc_ivar_list 成員變數和屬性資訊。ivars是一個陣列,所有的成員變數、屬性的資訊是放在連結串列ivars中的。

######獲取成員變數資訊

// 獲取整個成員變數列表
Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
//獲取一個成員變數的名字
const char * _Nullable ivar_getName(Ivar _Nonnull v) 
//獲取一個成員變數的
const char * _Nullable ivar_getTypeEncoding(Ivar _Nonnull v) 
複製程式碼
  • ivar_getTypeEncoding函式獲取的是變數的資料型別,詳細的說明可以看蘋果官方文件Type Encodings
  • class_copyIvarList函式,它返回一個指向成員變數資訊的陣列。這個陣列不包含在父類中宣告的變數。outCount指標返回陣列的大小。需要注意的是,我們必須使用free()來釋放這個陣列。

######獲取成員變數屬性資訊

/// An opaque type that represents an Objective-C declared property.
typedef struct objc_property *objc_property_t;
//獲取整個成員變數屬性列表
objc_property_t _Nonnull *_Nullable class_copyPropertyList(Class _Nullable cls, unsigned int * _Nullable outCount)

/// Defines a property attribute
typedef struct {
    const char * _Nonnull name;           /**< The name of the attribute */
    const char * _Nonnull value;          /**< The value of the attribute (usually empty) */
} objc_property_attribute_t;

objc_property_attribute_t * _Nullable property_copyAttributeList(objc_property_t _Nonnull property,unsigned int * _Nullable outCount)
複製程式碼
  • objc_property_attribute_t其實是對屬性的詳細描述,包括屬性名稱、屬性編碼型別、原子型別/非原子型別等。如T@"NSString",C,N,V_titleTQ,N,V_age

下面詳細地說明property_getAttributes獲取到的屬性的語義:

  • 第一:T總是第一個,它代表型別。 對於類型別,它都是T@型別,其中@表示物件型別;對於id型別,它就是@;而對於基本資料型別,它都是T加上編碼型別(@encode(type)),比如int型別就是Ti。
  • 第二:表示屬性編碼型別,比如是C表示copy,&表示strong,W表示weak等。若是基本型別,它預設是assign,因此此時它是空的。
  • 第三:指定是nonatomic還是atomic,若是nonatomic,則用N表示,若是atomic,則值為空。比如的count屬性的詳細描述為:Ti,N,V_count,它表示int型別、nonatomic、成員變數名為_count;tomicProperty屬性描述為:T@NSNumber,&,V_atomicProperty,它表示型別為NSNumber且為物件型別、strong、atomic、成員變數名為_atomicProperty。
  • 第四:在詳細描述中,屬性名稱是V開頭,後面跟著成員變數名稱。

屬性的詳細說明Property Type

###6. methodLists 物件方法列表。

/// An opaque type that represents a method in a class definition.
typedef struct objc_method *Method;

struct objc_method {
    SEL method_name;        // 方法名稱
    char *method_typesE;    // 引數和返回型別的描述字串
    IMP method_imp;         // 方法的具體的實現的指標
}

複製程式碼

獲取method資訊:

//方法列表
Method _Nonnull * _Nullable class_copyMethodList(Class _Nullable cls, unsigned int * _Nullable outCount) 
//方法名
SEL _Nonnull method_getName(Method _Nonnull m) 
//方法實現
IMP _Nonnull method_getImplementation(Method _Nonnull m) 
//方法引數和返回型別的資訊
const char * _Nullable method_getTypeEncoding(Method _Nonnull m) 
//方法引數數量
unsigned int method_getNumberOfArguments(Method _Nonnull m)
//方法引數型別
char * _Nullable method_copyArgumentType(Method _Nonnull m, unsigned int index) 
複製程式碼
  • method_getTypeEncoding獲取函式的編碼,其結果是一串值,如:v16@0:8;@16@0:8;v24@0:8@16。

這些字串是什麼意思,通過蘋果官方文件我們可以分析出:

  1. 第一個位置是返回值型別,比如第一個方法返回值是V,第二個的是@,第三個的是v
  2. 第二/三個位置是第一/二個引數,引數列表從左到右算。分別是@ :,@ :,@ :,都是物件,其實第一個和第二個引數是固定的,第一個是接收訊息的物件,而第二個是方法選擇器SEL。
  3. 如果還有其它引數,依次…
  4. Type Encodings

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

參考:

  1. tech.yunyingxbs.com/article/det…
  2. developer.apple.com/library/con…

相關文章