Objective-C Runtime (一):類與物件
Runtime介紹
Objective-C語言是一門動態語言,它將很多靜態語言在編譯和連結時期做的事放到了執行時來處理。這意味著它不僅需要一個編譯器,也需要一個執行時系統來動態得建立類和物件、進行訊息傳遞和轉發。這個執行時系統即Objc Runtime。Objc Runtime其實是一個Runtime庫,它基本上是用C和彙編寫的,這個庫使得C語言有了物件導向的能力。
Runtime庫主要做下面幾件事:
封裝
:在這個庫中,物件可以用C語言中的結構體表示,而方法可以用C函式 來實現,另外再加上了一些額外的特性。這些結構體和函式被runtime函式封裝後,我們就可以在程式執行時建立,檢查,修改類、物件和它們的方法了。訊息傳遞
:當程式執行[object doSomething]時,會向訊息接收者(object)傳送一條訊息(doSomething),runtime會根據訊息接收者是否能響應該訊息而做出不同的反應。這將在後面詳細介紹。
在接下來的一系列文章中,我將介紹一下runtime
的基本工作原理以及在我們的程式碼裡可以用它來做什麼。在本文中,我們先來了解一下類與物件。
類與物件基礎資料結構
###OC物件 OC的物件主要可以分為三種:
- instance物件(例項物件)
- class物件(類物件)
- meta-class物件(元類物件):主要儲存著isa指標,superclass指標,類的類方法的資訊(class method)。
instance物件 它主要是通過類alloc出來的物件,每次呼叫alloc都會產生新的instance物件。
NSObjcet *object1 = [[NSObjcet alloc] init];
複製程式碼
instance物件
在記憶體中儲存的資訊包括:
- isa指標
- 成員變數
class物件 我們通過class方法或runtime方法得到一個class物件。class物件也就是類物件。==每一個類在記憶體中有且只有一個class物件==。
Class objectClass1 = [NSObject class];
// runtime
Class objectClass2 = object_getClass(object1);
複製程式碼
class物件
在記憶體中儲存的資訊主要包括:
- isa指標
- superclass指標
- 類的屬性資訊(@property)
- 類的成員變數資訊(ivar)
- 類的物件方法資訊(instance method)
- 類的協議資訊(protocol)
meta-class物件 我們通過runtime方法得到一個class物件。==每個類在記憶體中有且只有一個meta-class物件。==
//runtime中傳入類物件此時得到的就是元類物件
Class objectMetaClass = object_getClass([NSObject class]);
// 判斷該物件是否為元類物件
class_isMetaClass(objectMetaClass)
複製程式碼
meta-class物件
在記憶體中儲存的資訊主要包括:
- isa指標
- superclass指標
- 類的類方法的資訊(class method)
我們來看看下面的圖,來分析這三者的關係
從上圖中,我們可以分析出:
- instance物件的isa指向class
- class的isa指向meta-class
- meta-class的isa指向基類的meta-class,基類的isa指向自己
- class的superclass指向父類的class,如果沒有父類,superclass指標為nil
- meta-class的superclass指向父類的meta-class,基類的meta-class的superclass指向基類的class
- instance物件呼叫物件方法的軌跡,isa找到class,方法不存在,就通過superclass找父類
- 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 Encodingsclass_copyIvarLis
t函式,它返回一個指向成員變數資訊的陣列。這個陣列不包含在父類中宣告的變數。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_title
,TQ,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。
這些字串是什麼意思,通過蘋果官方文件我們可以分析出:
- 第一個位置是返回值型別,比如第一個方法返回值是V,第二個的是@,第三個的是v
- 第二/三個位置是第一/二個引數,引數列表從左到右算。分別是@ :,@ :,@ :,都是物件,其實第一個和第二個引數是固定的,第一個是接收訊息的物件,而第二個是方法選擇器SEL。
- 如果還有其它引數,依次…
- Type Encodings
###7. cache
用於快取最近使用的方法。一個接收者物件接收到一個訊息時,它會根據isa指標
去查詢能夠響應這個訊息的物件。在實際使用中,這個物件只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次訊息來時,我們都是methodLists
中遍歷一遍,效能勢必很差。這時,cache
就派上用場了。在我們每次呼叫過一個方法後,這個方法就會被快取到cache
列表中,下次呼叫的時候runtime就會優先去cache
中查詢,如果cache
沒有,才去methodLists
中查詢方法。這樣,對於那些經常用到的方法的呼叫,但提高了呼叫的效率。
參考: