2.Runtime儲備知識
2.1.前言
? ? ? ?在體驗中,我寫過這樣一句話“并且我認為這個技術算是高階開發里面一個投機的技巧,絕大多數的UI開發都不會使用runtime,容易出現很嚴重的問題,并且官方也不是特別推薦使用”。
? ? ? ? 但是經過了第一張體驗,我會告訴你。雖然說第一在UI開發中你可能不會經常使用,第二使用不當時,會出現很嚴重的問題。第三官方也不是推薦使用的。但是?。〖词鼓悴⒉皇褂茫瑢W習Objective-C的運行時Runtime系統也是非常有必要的,并且當你學會了Runtime系統,你會發現你可以在任何地方使用到它。畢竟Runtime系統被稱之為iOS開發的黑魔法。
? ? ? ?Objective-C提供了編譯運行時,只要有可能,它都可以動態地運作。這意味著不僅需要編譯器,還需要運行時系統執行編譯的代碼。運行時系統充當Objective-C語言的操作系統,有了它才能運作。
? ? ? ?運行時系統所提供功能是非常強大的,在一些第三方庫開發中是經常使用到的。比如,蘋果不允許我們給Category追加擴展屬性,是因為它不會自動生成成員變量,那么我們通過運行時就可以很好的解決這個問題。另外,常見的模型轉字典或者字典轉模型,對象歸檔等。后續我們再來學習如何應用,本節只是講講理論。
2.2.與Runtime交互
? ? ? ?在使用runtime中,我們會通過如下三種方法來使用runtime。并且使用的難度級風險也越來越大。
通過Objective-C源代碼
通過Foundation庫中定義的NSObject提供的方法
通過直接調用runtime方法
安全系數及難度:零
? ? ? ?在我們日常開發中,所有編寫的Objective-C代碼系統都會自動幫助我們編譯成runtime代碼。我們使用它只是寫源代碼并編譯源代碼。當編譯包含Objective-C類和方法的代碼時,編譯器會創建實現了語言動態特性的數據結構和函數調用。
安全系數及難度:低級
? ? ? ? 在Cocoa編程中,大部分的類都繼承于NSObject,也就是說NSObject通常是根類,大部分的類都繼承于NSObject。有些特殊的情況下,NSObject只是提供了它應該要做什么的模板,卻沒有提供所有必須的代碼。
? ? ? ?有些NSObject提供的方法僅僅是為了查詢運動時系統的相關信息,這此方法都可以反查自己。比如-isKindOfClass:和-isMemberOfClass:都是用于查詢在繼承體系中的位置。-respondsToSelector:指明是否接受特定的消息。+conformsToProtocol:指明是否要求實現在指定的協議中聲明的方法。-methodForSelector:提供方法實現的地址。
安全系數及難度:高級
runtime庫函數在usr/include/objc目錄下,我們主要關注是這兩個頭文件:
#import <objc/runtime.h>
#import <objc/objc.h>
2.3.消息(Message)
? ? ? ?為什么叫消息呢?因為面向對象編程中,對象調用方法叫做發送消息。在編譯時,應用的源代碼就會被編將對象發送消息轉換成runtime的objc_msgSend函數調用。
? ? ? ?在Objective-C,消息在運行時并不要求實現。編譯器會轉換消息表達式:
[receiver message];
? ? ? ?在編譯時會轉換成類似這樣的函數調用:
id objc_msgSend(id self,SEL op,...)
? ? ? ? 具體會轉換成哪個,我們來看看官方的文章注釋:
/**
* Sends a message with a simple return value to an instance of a class.
* @param self A pointer to the instance of the class that is to receive the message.
* @param op The selector of the method that handles the message.
* @param ...
*?? A variable argument list containing the arguments to the method.
* @return The return value of the method.
* @note When it encounters a method call, the compiler generates a call to one of the
*??functions \c objc_msgSend, \c objc_msgSend_stret, \c objc_msgSendSuper, or \c objc_msgSendSuper_stret.
*??Messages sent to an object’s superclass (using the \c super keyword) are sent using \c objc_msgSendSuper;
*??other messages are sent using \c objc_msgSend. Methods that have data structures as return values
*??are sent using \c objc_msgSendSuper_stret and \c objc_msgSend_stret.
*/
? ? ? ? 也就是說,我們是通過編譯器來自動轉換成運行時代碼時,它會根據類型自動轉換成下面的其它一個函數:
objc_msgSend:其它普通的消息都會通過該函數來發送
objc_msgSend_stret:消息中需要有數據結構作為返回值時,會通過該函數來發送消息并接收返回值
objc_msgSendSuper:與objc_msgSend函數類似,只是它把消息發送給父類實例
objc_msgSendSuper_stret:與objc_msgSend_stret函數類似,只是它把消息發送給父類實例并接收數組結構作為返回值
? ? ? ?另外,如果函數返回值是浮點類型,官方說明如下:
? ? ? ?注意:有另外一個需要注意的地方是當函數返回的值是浮點類型是,官方注釋有這樣的解釋:
/* Floating-point-returning Messaging Primitives
*
* Use these functions to call methods that return floating-point values
* on the stack.
* Consult your local function call ABI documentation for details.
*
* arm:????objc_msgSend_fpret not used
* i386:?? objc_msgSend_fpret used for `float`, `double`, `long double`.
* x86-64: objc_msgSend_fpret used for `long double`.
*
* arm:????objc_msgSend_fp2ret not used
* i386:?? objc_msgSend_fp2ret not used
* x86-64: objc_msgSend_fp2ret used for `_Complex long double`.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/
? ? ? ?其實總來來說不用擔心這個問題,只需要調用objc_msgSend_fpret函數就好了。
? ? ? ? 注意事項:一定要調用所調用的API支持哪些平臺,亂調在導致部分平臺上不支持而崩潰的。
? ? ? ?現在我們來看看當消息被發送到實例對象時,它是如何處理的:
? ? ? ? 這個算是iOS開發消息傳遞的基礎知識了,就不在更多解釋。一直找到NSObject如果都還沒有找到,就會崩潰報錯Unreconized selector。
2.4.Message Forwarding
? ? ? ?當發送消息給一個不處理該消息的對象是錯誤的。然后在宣布錯誤之前,運行時系統給了接收消息的對象處理消息的第二個機會。
? ? ? ?當某對象不處理某消息時,可以通過重寫-forwardInvocation:方法來提供一個默認的消息響應或者避免出錯。當對象中找不到方法實現時,會按照類繼承關系一層層往上找。我們看看類繼承關系圖:
? ? ? ?所有元類中的isa指針都指向根元類,而根元類的isa指針則指向自身。根元類是繼承于根類的,與根類的結構體成員一致,都是objc_class結構體,不同的是根元類的isa指針指向自身,而根類的isa指針為nil
我們再看看消息處理流程:
? ? ? ? 當對象查詢不到相關的方法,消息得不到該對象處理,會啟動“消息轉發”機制。消息轉發還分為幾個階段:先詢問receiver或者說是它所屬的類是否能動態添加方法,以處理當前這個消息,這叫做“動態方法解析”,runtime會通過+resolveInstanceMethod:判斷能否處理。如果runtime完成動態添加方法的詢問之后,receiver仍然無法正常響應則Runtime會繼續向receiver詢問是否有其它對象即其它receiver能處理這條消息,若返回能夠處理的對象,Runtime會把消息轉給返回的對象,消息轉發流程也就結束。若無對象返回,Runtime會把消息有關的全部細節都封裝到NSInvocation對象中,再給receiver最后一次機會,令其設法解決當前還未處理的這條消息。
? ? ? ? 我們可以這樣理解:
? ? ? ? 向一個對象發送它不處理的消息是一個會報錯,但在報錯之前 Runtime系統會給接收對象來處理這些錯誤的機會。這個需要用到以下方法:
-(void) forwardInvocation: (NSInvocation*)invocation
? ? ? ? 如果對象沒有實現這個方法,就調用NSObject 的forwardInvocation:方法。那句不能識別消息的錯誤,實際就是NSObject 的forwardInvocation 拋出來的異常。
? ? ? ? 也就是說,Message Forwarding的作用就是你可以覆蓋forwardInvocation方法,來改變NSObject 的拋異常的處理方式。所以,你可以把A不能處理的消息轉發給B去處理。
提示:消息處理越往后,開銷也就會越大,因此最好直接在第一步就可以得到消息處理。
? ? ? ? 我們來看看NSInvocation官方頭文件:
@interfaceNSInvocation:
NSObject{
@private
__strongvoid*_frame;
__strongvoid*_retdata;
id_signature;
id_container;
uint8_t_retainedArgs;
uint8_t_reserved[15];
}
+(NSInvocation*)invocationWithMethodSignature:(NSMethodSignature*)sig;
@property(readonly,retain)NSMethodSignature*methodSignature;
-(void)retainArguments;
@property(readonly)BOOLargumentsRetained;
@property(nullable,assign)idtarget;
@propertySELselector;
-(void)getReturnValue:(void*)retLoc;
-(void)setReturnValue:(void*)retLoc;
-(void)getArgument:(void*)argumentLocationatIndex:(NSInteger)idx;
-(void)setArgument:(void*)argumentLocationatIndex:(NSInteger)idx;
-(void)invoke;
-(void)invokeWithTarget:(id)target;
@end
? ? ? ? 實際上NSInvocation是一個包含了target、selector ,Arguments,也就是它包含了向一個對象發送消息的所有元素:對象、方法名、參數序列。可以調用NSInvocation 的invoke 方法將這個消息激活。
? ? ? ? 后面Message Forwarding會單獨繼續精講。這里做一個理解。
2.5.類與對象基礎數據結構
2.5.1.Class
? ? ? ? Objective-C類是由Class類型來表示的,它實際上是一個指向objc_class結構體的指針。
? ? ? ? 查看objc/objc.h中Class的定義如下:
/// An opaque type that represents an Objective-C class.
typedefstructobjc_class*Class;
? ? ? ? ?查看objc/runtime.h中objc_class結構體的定義如下:
structobjc_class{
ClassisaOBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Classsuper_classOBJC2_UNAVAILABLE;//
父類
constchar*nameOBJC2_UNAVAILABLE;//
類名
longversionOBJC2_UNAVAILABLE;//
類的版本信息,默認為0
longinfoOBJC2_UNAVAILABLE;//
類信息,供運行期使用的一些位標識
longinstance_sizeOBJC2_UNAVAILABLE;//
該類的實例變量大小
structobjc_ivar_list*ivarsOBJC2_UNAVAILABLE;//
該類的成員變量鏈表
structobjc_method_list**methodListsOBJC2_UNAVAILABLE;//
方法定義的鏈表
structobjc_cache*cacheOBJC2_UNAVAILABLE;//
方法緩存
structobjc_protocol_list*protocolsOBJC2_UNAVAILABLE;//
協議鏈表
#endif
}OBJC2_UNAVAILABLE;
? ? ? ? ?我們可以看到每個類結構體都會有一個isa指針,它是指向元類的。它還有一個父類指針super_class,指針父類。還包含了類的名稱name、類的版本信息version、類的一些標識信息info、實例大小instance_size、成員變量地址列表ivars、方法地址列表methodLists、緩存最近使用的方法地址cache、協議列表protocols。其中有一下幾個字段是我們需要特別關注的:
isa:需要注意的是在Objective-C中,所有的類自身也是一個對象,這個對象的Class里面也有一個isa指針,它指向metaClass(元類),我們會在后面介紹它。
super_class:指向該類的父類,如果該類已經是最頂層的根類(如NSObject或NSProxy),則super_class為NULL。
cache:用于緩存最近使用的方法。一個接收者對象接收到一個消息時,它會根據isa指針去查找能夠響應這個消息的對象。在實際使用中,這個對象只有一部分方法是常用的,很多方法其實很少用或者根本用不上。這種情況下,如果每次消息來時,我們都是methodLists中遍歷一遍,性能勢必很差。這時,cache就派上用場了。在我們每次調用過一個方法后,這個方法就會被緩存到cache列表中,下次調用的時候runtime就會優先去cache中查找,如果cache沒有,才去methodLists中查找方法。這樣,對于那些經常用到的方法的調用,但提高了調用的效率。
version:我們可以使用這個字段來提供類的版本信息。這對于對象的序列化非常有用,它可是讓我們識別出不同類定義版本中實例變量布局的改變。
? ? ? ? ?針對cache,我們用下面例子來說明其執行過程:
NSArray*array=[[NSArrayalloc]init];
其流程是:
? ? ? ? [NSArray alloc]先被執行。因為NSArray沒有+alloc方法,于是去父類NSObject去查找。
? ? ? ? 檢測NSObject是否響應+alloc方法,發現響應,于是檢測NSArray類,并根據其所需的內存空間大小開始分配內存空間,然后把isa指針指向NSArray類。同時,+alloc也被加進cache列表里面。
? ? ? ? 接著,執行-init方法,如果NSArray響應該方法,則直接將其加入cache;如果不響應,則去父類查找。
? ? ? ? ?在后期的操作中,如果再以[[NSArray alloc] init]這種方式來創建數組,則會直接從cache中取出相應的方法,直接調用。
2.5.1.1.objc_object與id
? ? ? ? ?objc_object是表示一個類的實例的結構體,它的定義如下(objc/objc.h):
#if !OBJC_TYPES_DEFINED
/// An opaque type that represents an Objective-C class.
typedefstructobjc_class*Class;
/// Represents an instance of a class.
structobjc_object{
ClassisaOBJC_ISA_AVAILABILITY;
};
/// A pointer to an instance of a class.
typedefstructobjc_object*id;
#endif
? ? ? ?從官方的頭文件可以看到objc_object是一個結構體并且只有一個成員,即指向其類的isa指針。這樣,當我們向一個Objective-C對象發送消息時,runtime系統會根據實例對象的isa指針找到這個實例對象所屬的類。Runtime系統會在類的方法列表及父類的方法列表中去尋找與消息對應的selector指向的方法。找到后即運行這個方法。
? ? ? ?當創建一個特定類的實例對象時,分配的內存包含一個objc_object數據結構,然后是類的實例變量的數據。NSObject類的alloc和allocWithZone:方法使用函數class_createInstance來創建objc_object數據結構。
? ? ? ?另外還有我們常見的id,它是一個objc_object結構類型的指針。它的存在可以讓我們實現類似于C++中泛型的一些操作。該類型的對象可以轉換為任何一種對象,有點類似于C語言中void *指針類型的作用。
2.5.1.2.objc_cache
? ? ? ? 上面提到了objc_class結構體中的cache字段,它用于緩存調用過的方法。這個字段是一個指向objc_cache結構體的指針,其定義如下:
typedefstructobjc_cache*CacheOBJC2_UNAVAILABLE;
#define CACHE_BUCKET_NAME(B)??((B)->method_name)
#define CACHE_BUCKET_IMP(B)?? ((B)->method_imp)
#define CACHE_BUCKET_VALID(B) (B)
#ifndef __LP64__
#define CACHE_HASH(sel, mask) (((uintptr_t)(sel)>>2) & (mask))
#else
#define CACHE_HASH(sel, mask) (((unsigned int)((uintptr_t)(sel)>>3)) & (mask))
#endif
structobjc_cache{
unsignedintmask/* total = mask + 1 */OBJC2_UNAVAILABLE;
unsignedintoccupiedOBJC2_UNAVAILABLE;
Methodbuckets[1]OBJC2_UNAVAILABLE;
};
該結構體的字段描述如下:
mask:一個整數,指定分配的緩存bucket的總數。在方法查找過程中,Objective-C runtime使用這個字段來確定開始線性查找數組的索引位置。指向方法selector的指針與該字段做一個AND位操作(index = (mask & selector))。這可以作為一個簡單的hash散列算法。
occupied:一個整數,指定實際占用的緩存bucket的總數。
buckets:指向Method數據結構指針的數組。這個數組可能包含不超過mask+1個元素。需要注意的是,指針可能是NULL,表示這個緩存bucket沒有被占用,另外被占用的bucket可能是不連續的。這個數組可能會隨著時間而增長。
2.5.2.元類(Meta Class)
? ? ? ? 在上面我們提到,所有的類自身也是一個對象,我們可以向這個對象發送消息(即調用類方法)。如:
NSArray*array=[NSArrayarray];
? ? ? ? 這個例子中,+array消息發送給了NSArray類,而這個NSArray也是一個對象。既然是對象,那么它也是一個objc_object指針,它包含一個指向其類的一個isa指針。那么這些就有一個問題了,這個isa指針指向什么呢?為了調用+array方法,這個類的isa指針必須指向一個包含這些類方法的一個objc_class結構體。這就引出了meta-class的概念
meta-class是一個類對象的類。
? ? ? ? 當我們向一個對象發送消息時,runtime會在這個對象所屬的這個類的方法列表中查找方法;而向一個類發送消息時,會在這個類的meta-class的方法列表中查找。
? ? ? ? ?meta-class之所以重要,是因為它存儲著一個類的所有類方法。每個類都會有一個單獨的meta-class,因為每個類的類方法基本不可能完全相同。
? ? ? ? 其實,meta-class也是一個類,也可以向它發送一個消息,那么它的isa又是指向什么呢?為了不讓這種結構無限延伸下去,Objective-C的設計者讓所有的meta-class的isa指向基類的meta-class,以此作為它們的所屬類。即,任何NSObject繼承體系下的meta-class都使用NSObject的meta-class作為自己的所屬類,而基類的meta-class的isa指針是指向它自己。這樣就形成了一個完美的閉環。
? ? ? ? 通過上面的描述,再加上對objc_class結構體中super_class指針的分析,我們就可以描繪出類及相應meta-class類的一個繼承體系了,如下圖所示:
? ? ? ? 對于NSObject繼承體系來說,其實例方法對體系中的所有實例、類和meta-class都是有效的;而類方法對于體系內的所有類和meta-class都是有效的。
3.操作函數
3.1.類名(name)
/**
* Returns the name of a class.
*
* @param cls A class object.
*
* @return The name of the class, or the empty string if \e cls is \c Nil.
*/
OBJC_EXPORTconstchar*class_getName(Classcls)
__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
? ? ? ? 對于class_getName函數,如果傳入的cls為Nil,則返回一個char字字符串。
3.2.父類(super_class)和元類(meta-class)
/**
* Returns a Boolean value that indicates whether a class object is a metaclass.
*
* @param cls A class object.
*
* @return \c YES if \e cls is a metaclass, \c NO if \e cls is a non-meta class,
*??\c NO if \e cls is \c Nil.
*/
OBJC_EXPORTBOOLclass_isMetaClass(Classcls)
__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
/**
* Returns the superclass of a class.
*
* @param cls A class object.
*
* @return The superclass of the class, or \c Nil if
*??\e cls is a root class, or \c Nil if \e cls is \c Nil.
*
* @note You should usually use \c NSObject's \c superclass method instead of this function.
*/
OBJC_EXPORTClassclass_getSuperclass(Classcls)
__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
/**
* Sets the superclass of a given class.
*
* @param cls The class whose superclass you want to set.
* @param newSuper The new superclass for cls.
*
* @return The old superclass for cls.
*
* @warning You should not use this function.
*/
OBJC_EXPORTClassclass_setSuperclass(Classcls,ClassnewSuper)
__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_5,__MAC_10_5,__IPHONE_2_0,__IPHONE_2_0);
class_isMetaClass函數,如果是cls是元類,則返回YES;如果否或者傳入的cls為Nil,則返回NO。
class_getSuperclass函數,當cls為Nil或者cls為根類時,返回Nil。不過通常我們可以使用NSObject類的superclass方法來達到同樣的目的。
class_setSuperclass函數,為一個cls設置一個新的superclass,并且返回cls以前的superclass。
3.3.版本(version)
/**
* Returns the version number of a class definition.
*
* @param cls A pointer to a \c Class data structure. Pass
*??the class definition for which you wish to obtain the version.
*
* @return An integer indicating the version number of the class definition.
*
* @see class_setVersion
*/
OBJC_EXPORTintclass_getVersion(Classcls)
__OSX_AVAILABLE_STARTING(__MAC_10_0,__IPHONE_2_0);
/**
* Sets the version number of a class definition.
*
* @param cls A pointer to an Class data structure.
*??Pass the class definition for which you wish to set the version.
* @param version An integer. Pass the new version number of the class definition.
*
* @note You can use the version number of the class definition to provide versioning of the
*??interface that your class represents to other classes. This is especially useful for object
*??serialization (that is, archiving of the object in a flattened form), where it is important to
*??recognize changes to the layout of the instance variables in different class-definition versions.
* @note Classes derived from the Foundation framework \c NSObject class can set the class-definition
*??version number using the \c setVersion: class method, which is implemented using the \c class_setVersion function.
*/
OBJC_EXPORTvoidclass_setVersion(Classcls,intversion)
__OSX_AVAILABLE_STARTING(__MAC_10_0,__IPHONE_2_0);
class_getVersion函數可以獲取類定義的版本號,返回一個int類型。
class_setVersion函數對cls設置一個int類型的版本號,不過通常我們可以使用NSObject類的setVersion方法來達到同樣的目的。
3.4.實例變量大小(instance_size)
/**
* Returns the size of instances of a class.
*
* @param cls A class object.
*
* @return The size in bytes of instances of the class \e cls, or \c 0 if \e cls is \c Nil.
*/
OBJC_EXPORTsize_tclass_getInstanceSize(Classcls)
__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
3.5.成員變量(ivars)及屬性
/**
* Returns the \c Ivar for a specified instance variable of a given class.
*
* @param cls The class whose instance variable you wish to obtain.
* @param name The name of the instance variable definition to obtain.
*
* @return A pointer to an \c Ivar data structure containing information about
*??the instance variable specified by \e name.
*/
OBJC_EXPORTIvarclass_getInstanceVariable(Classcls,constchar*name)
__OSX_AVAILABLE_STARTING(__MAC_10_0,__IPHONE_2_0);
/**
* Returns the Ivar for a specified class variable of a given class.
*
* @param cls The class definition whose class variable you wish to obtain.
* @param name The name of the class variable definition to obtain.
*
* @return A pointer to an \c Ivar data structure containing information about the class variable specified by \e name.
*/
OBJC_EXPORTIvarclass_getClassVariable(Classcls,constchar*name)
__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
/**
* Describes the instance variables declared by a class.
*
* @param cls The class to inspect.
* @param outCount On return, contains the length of the returned array.
*??If outCount is NULL, the length is not returned.
*
* @return An array of pointers of type Ivar describing the instance variables declared by the class.
*??Any instance variables declared by superclasses are not included. The array contains *outCount
*??pointers followed by a NULL terminator. You must free the array with free().
*
*??If the class declares no instance variables, or cls is Nil, NULL is returned and *outCount is 0.
*/
OBJC_EXPORTIvar*class_copyIvarList(Classcls,unsignedint*outCount)
__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
/**
* Adds a new instance variable to a class.
*
* @return YES if the instance variable was added successfully, otherwise NO
*???????? (for example, the class already contains an instance variable with that name).
*
* @note This function may only be called after objc_allocateClassPair and before objc_registerClassPair.
*?????? Adding an instance variable to an existing class is not supported.
* @note The class must not be a metaclass. Adding an instance variable to a metaclass is not supported.
* @note The instance variable's minimum alignment in bytes is 1<
*?????? variable depends on the ivar's type and the machine architecture.
*?????? For variables of any pointer type, pass log2(sizeof(pointer_type)).
*/
OBJC_EXPORTBOOLclass_addIvar(Classcls,constchar*name,size_tsize,
uint8_talignment,constchar*types)
__OSX_AVAILABLE_STARTING(__MAC_10_5,__IPHONE_2_0);
class_getInstanceVariable函數,它返回一個指向包含name指定的成員變量信息的objc_ivar結構體的指針(Ivar)。
class_getClassVariable函數,目前沒有找到關于Objective-C中類變量的信息,一般認為Objective-C不支持類變量。注意,返回的列表不包含父類的成員變量和屬性。
class_copyIvarList函數,它返回一個指向成員變量信息的數組,數組中每個元素是指向該成員變量信息的objc_ivar結構體的指針。這個數組不包含在父類中聲明的變量。outCount指針返回數組的大小。需要注意的是,我們必須使用free()來釋放這個數組。
class_addIvar函數,我們知道Objective-C不支持往已存在的類中添加實例變量,因此不管是系統庫提供的提供的類,還是我們自定義的類,都無法動態添加成員變量。但如果我們通過運行時來創建一個類的話,又應該如何給它添加成員變量呢?這時我們就可以使用class_addIvar函數了。不過需要注意的是,這個方法只能在objc_allocateClassPair函數與objc_registerClassPair之間調用。另外,這個類也不能是元類。成員變量的按字節最小對齊量是1<