Objective-C是一種很優美的語言,至少在我使用其進行編程的過程當中,是很享受他那近乎天然語言的函數命名、靈活多樣的方法調用方式以及配合IDE流順暢快編寫體驗。Objective-C是擴展與C面向對象的編程語言,然而其方法的調用方式又和大多面向對象語言大有不一樣,其採用的是消息傳遞、轉發的方式進行方法的調用。所以在Objective-C中對象的真正行爲每每是在運行時肯定而非在編譯時肯定,因此Objective-C又被稱爲是一種運行時的動態語言。編程
本篇博客既不介紹iOS開發,也不說起MacOS開發,只對Objective-C語言的這種消息機制與運行時動態進行探討,所說起的內容也都是我開發中的我的積累與經驗,若是偏頗之處,歡迎討論指正。數組
許多面向對象語言中方法的調用都是採用obj.function這樣的方式,在Objective-C語言中倒是採用中括號包裹的方式進行方法調用,例如[obj function]。實際上,Objective-C中的每一句方法調用最後都會轉換成一條消息進行發送。一條消息包含3部份內容:方法選擇器、接收消息的對象以及參數。objc_msgSend函數就是用來發送這種消息。例如,建立一個Xcode命令行工程,咱們建立一個類,命名爲MyObject,以下:緩存
MyObject.h文件:框架
#import <Foundation/Foundation.h> @interface MyObject : NSObject @end
MyObject.m文件:編程語言
#import "MyObject.h" @implementation MyObject -(void)showSelf{ NSLog(@"MyObject"); } @end
首先在MyObject.h文件中並無暴漏任何方法,MyObject.m文件中添加了一個showSelf方法,這個方法只是作了簡單的打印操做。函數
將main.m文件修改以下:佈局
#import <Foundation/Foundation.h> #import "MyObject.h" #import <objc/message.h> int main(int argc, const char * argv[]) { @autoreleasepool { MyObject * obj = [[MyObject alloc]init]; [obj class]; //爲了消除未定義選擇器的警告 #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wundeclared-selector" //進行消息發送 ((void(*)(id,SEL))objc_msgSend)(obj,@selector(showSelf)); #pragma clang diagnostic pop } return 0; }
運行工程,能夠看到控制檯執行了MyObject類的示例方法showSelf。若是要進行傳參,在objc_msgSend方法中繼續添加參數,而且指定對應的函數類型便可,例如:字體
MyObject.m文件:ui
#import "MyObject.h" @implementation MyObject -(void)showSelf:(NSString*)name age:(int)age{ NSLog(@"MyObject:%@,%d",name,age); } @end
main.m文件:編碼
#import <Foundation/Foundation.h> #import "MyObject.h" #import <objc/message.h> int main(int argc, const char * argv[]) { @autoreleasepool { MyObject * obj = [[MyObject alloc]init]; [obj class]; //爲了消除未定義選擇器的警告 #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wundeclared-selector" //進行消息發送 ((void(*)(id,SEL,NSString*,int))objc_msgSend)(obj,@selector(showSelf:age:),@"琿少",25); #pragma clang diagnostic pop } return 0; }
運行工程能夠看到方法被調用,參數被正確傳入。
上面代碼只是簡單演示了消息發送的效果,下面咱們來剖析下消息發送的過程與原理,明白了這個原理,對Objective-C中許多神奇的現象你將會豁然開朗,後面我會再具體向你介紹這些現象。
在介紹消息機制以前,我仍是要再囉嗦一點,關於@selector()咱們還須要深刻理解一下,經過@selector(方法名)能夠獲取到一個SEL類型的對象,SEL其實是objc_selector結構體指針,在Objective-C庫頭文件中沒有找到objc_selector結構體的定義,但咱們能夠合理猜想,其中頗有可能包含的是一個函數指針。所以SEL也能夠理解爲函數簽名,在程序的編譯階段,咱們定義類中全部所發會生成一個方法簽名列表,這個列表時類直接關聯的(原則上來講,類的本質也是對象,它是一個單例對象),在運行時經過方法簽名表來找到具體要執行的函數。
咱們再來看objc_msgSend()函數,前面說過,它的第一個參數爲接收消息的對象,第2個參數爲方法簽名,以後爲傳遞的參數。那麼Objective-C運行時是如何根據一個對象實例來找到方法簽名表,再找到要執行的方法呢,看似麻煩的事情其實原理也很是簡單,細心觀察,你會發現全部的NSObject子類對象中都包含一個isa成員變量,請看NSObject類的定義:
@interface NSObject <NSObject> { Class isa OBJC_ISA_AVAILABILITY; }
這個isa變量是Class類型,咱們的主角終於來了,Class顧名思義就是「類」類型,其實質是objc_class結構體指針:
typedef struct objc_class *Class;
有些蒙圈了吧,不用着急,撥開層層迷霧,你就會發現Objective-C中類本質上只是結構體而已,下面是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; //信息 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指針,這個指針指向的類其實是元類,即構造「類」的類。如今你無須糾結這些概念,舉一個例子你就能明白,在Objective-C開發中有加方法與減方法,減方法是實例對象調用的方法,每個「類」中都包含一個函數列表,就是上面的objc_method_list結構體數組指針,一樣若是調用加方法,其實是從類的元類中找到對應的方法列表,這個列表就是咱們前面提到的方法簽名列表,進行方法的執行。關於實例對象,「類」對象和元類,下圖很好的表現了他們之間的關係:
須要注意,使用LLDB調試器咱們是能夠拿到對象的isa指針的,而且能夠看出它的確爲Class類型,可是咱們缺沒法經過isa指針繼續向下取抓取更多類的信息,其所在的內存是禁止咱們訪問的。可是Objective-C運行時提供了一些方法能夠獲取到這些信息,後面咱們會一一介紹。
上面咱們介紹的消息發送機制其實十分不完整,首先Objective-C是支持繼承的,所以若是在當前對象的類的方法列表中沒有找到此消息對應的方法簽名,系統會經過super_class一層層繼續向上,直到找到相應的方法或者到達繼承鏈的頂端。
有了上面的理論知識做爲基礎,咱們就能夠更深刻的分析消息傳遞的過程了,首先,若是消息的接收對象恰好能夠處理這個消息,即其isa指針對應的類中能夠查找到這個方法,那麼萬事大吉,找到對應方法直接執行就大功告成,能夠若是接收對象沒法處理,其父類,父父類...等都沒法處理,那麼該怎麼辦呢,Objective-C爲了加強語言的動態性,若是真的出現了這種狀況,程序並不會立刻crash,在crash前,有3次機會能夠挽救本條消息的命運。
第一根救命稻草:
如上所說,若是對象整個繼承鏈都沒法處理當前消息,那麼首先會調用接收對象所屬類的resolveInstanceMethod方法(這個對應實例方法,若是是沒法處理的類方法消息,則會調用resolveClassMethod方法),在這個方法中,開發者有機會爲類動態添加方法,若是動態添加了方法,能夠在這個方法中返回YES,那麼此條消息依然會被成功處理。例如咱們將main.m文件修改以下:
#import <Foundation/Foundation.h> #import "MyObject.h" #import <objc/message.h> int main(int argc, const char * argv[]) { @autoreleasepool { MyObject * obj = [[MyObject alloc]init]; [obj class]; //爲了消除未定義選擇器的警告 #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wundeclared-selector" //進行消息發送 ((void(*)(id,SEL))objc_msgSend)(obj,@selector(showSelf)); #pragma clang diagnostic pop } return 0; }
MyObject類不作任何修改,當咱們運行程序,程序會直接crash掉,如今咱們在MyObject類中添加以下方法:
+(BOOL)resolveInstanceMethod:(SEL)sel{ NSLog(@"resolveInstanceMethod"); if ([NSStringFromSelector(sel) isEqualToString:@"showSelf"]) { class_addMethod(self, sel, newFunc, "v@:"); } return [super resolveInstanceMethod:sel]; }
其中class_addMethod函數用來向類中動態添加方法,第一個參數爲Class對象,第二個參數爲方法選擇器,第三個參數爲IMP類型的函數指針,第四個參數爲指定方法的返回值和參數類型。這個參數採用的是C字符串的形式來指定返回值和參數的類型,第1個字符爲返回值類型,其後都爲參數類型,須要注意,使用這種方式添加方法的時候系統會默認傳入兩個參數,分別是調用此方法的實例對象和方法選擇器,上面示例代碼中的"@"表示第1個id類型的參數,":"表示第2個選擇器類型的參數,後面我會把字符所表示的參數類型映射表提供給你們。
抽絲剝繭一下,IMP和SEL並不一樣,SEL能夠理解爲函數簽名,其與函數名相關聯,而IMP是函數所在地址的指針,其定義以下:
typedef void (*IMP)(void /* id, SEL, ... */ );
簡單理解,經過IMP咱們能夠直接拿到函數的地址,後面會對函數作更深刻的剖析,到時候你能就能豁然你開朗。
運行工程,根據打印信息能夠看到showSelf方法被添加並正常執行了。
第二根救命稻草:
拋開運行時添加方法這一手段,將resolveInstanceMethod方法刪去,是否是咱們的程序就必然走進crash的深淵了,其實否則,上帝還會給你另外一根救命稻草,當經過運行時添加方法被否認後,系統會接着調用forwardingTargetForSelector方法,這個方法用來對消息進行轉發,沒錯,重點來了,Objective-C中強大的消息轉發機制的奧妙就在這裏。forwardingTargetForSelector方法須要返回一個id類型的對象,系統會將當前對象服務處理的消息轉發給這個方法返回的對象,若是這個返回的對象能夠處理,那麼程序依然能夠很好的執行下去。
例如,在咱們的命令行工程中新添加一個類,命名爲SubObject,實現以下:
SubObject.h文件:
#import <Foundation/Foundation.h> @interface SubObject : NSObject @end
SubObject.m文件:
#import "SubObject.h" @implementation SubObject -(void)showSelf{ NSLog(@"subObject"); } @end
在MyObject類中實現以下方法:
-(id)forwardingTargetForSelector:(SEL)aSelector{ NSLog(@"forwardingTargetForSelector"); if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) { return [SubObject new]; } return [super forwardingTargetForSelector:aSelector]; }
forwardingTargetForSelector方法能夠返回一個對象,Objective-C會將當前對象沒法處理的消息轉發給這個方法返回的對象,若是返回nil,則表示不進行消息轉發,這時你若是還想挽救這次crash,你就須要用到第三根救命稻草了。咱們能夠這種消息轉發的機制來模擬Objective-C中的多繼承。
第三根救命稻草:
若是你不幸錯過了前兩次拯救未知消息的機會,那麼你還有最後一次機會(中國有句古話,事不過三,世間萬事也果然如此...)。當消息轉發策略也被否認後,系統會調用methodSignatureForSelector方法,這個方法的主要用途是詢問這個選擇器是不是有效的,咱們須要返回一個NSMethodSignature,顧名思義,這個對象是函數簽名的抽象。若是咱們返回了有效的函數簽名,那麼接着系統會調用forwardInvocation方法,這裏是拯救應用程序的最後一根稻草了,這個函數會直接將消息包裝成NSInvocation對象傳入,咱們直接將其發送給能夠處理此消息的對象便可(固然你也能夠直接拋棄,不理會這條未知的消息)。
例如,在MyObject類中將forwardingTargetForSelector方法刪去,實現以下兩個方法:
//詢問此選擇器是不是有效的 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{ NSLog(@"methodSignatureForSelector"); if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) { return [[SubObject new] methodSignatureForSelector:aSelector]; } return [super methodSignatureForSelector:aSelector]; } //處理消息 -(void)forwardInvocation:(NSInvocation *)anInvocation{ NSLog(@"forwardInvocation"); if ([NSStringFromSelector(anInvocation.selector) isEqualToString:@"showSelf"]) { [anInvocation invokeWithTarget:[SubObject new]]; }else{ [super forwardInvocation:anInvocation]; } }
再次運行工程,程序又被你挽救了一次。
你真的須要救命稻草麼?
經過上面的三根救命稻草,我相信你必定對Objective-C消息機制有了全面而深刻的瞭解,上面的代碼也只是爲了示例所用,正常狀況下,你都不會使用到這些函數(畢竟若是你須要救命稻草,說明你已經落水了)。除非某些特殊需求或者作一些調試框架的開發,不然儘可能不要介入消息的發送機制,就像生病就醫,發現問題總比逃避治療要好。順便說一下,若是你沒有使用任何救命稻草,當向某個對象發送了沒法處理的消息時,系統會最終調用到NSObject類的doesNotRecognizeSelector方法,這個方法會拋出異常信息,正因如此,你在Xcode的控制檯會常常看到以下圖所示的crash信息:
你也能夠重寫這個方法來自定義輸出信息,例如:
-(void)doesNotRecognizeSelector:(SEL)aSelector{ NSLog(@"doesNotRecognizeSelector"); if ([NSStringFromSelector(aSelector) isEqualToString:@"showSelf"]) { NSLog(@"not have a method named showSelf"); return; } [super doesNotRecognizeSelector:aSelector]; }
下圖完整展現了Objective-C整個消息發送與轉發機制:
既然Objective-C函數最終的調用都是要轉換成消息發送,那麼瞭解下面這些消息發送函數是十分必要的,這些方法都定義在objc/message.h文件中,其中最重要的兩個方法是:
//發送消息的函數 /* self:消息的接收對象 op:方法選擇器 ...:參數 */ id objc_msgSend(id self, SEL op, ...); //發送消息給父類 /* super:父類對象結構體 op:方法選擇器 ...:參數 */ id objc_msgSendSuper(struct objc_super *super, SEL op, ...);
objc_msgSend函數前面已經有過介紹,objc_msgSendSuper函數則是從父類中找方法的實現進行執行。須要注意,這個函數很是重要,理解了這個這個函數進行消息發送的原理,你就明白super關鍵字的某些使人疑惑的行爲了。
作了這麼久的Objective-C開發,你是否真的理解super關鍵字的含義?你必定會說,這很簡單啊,self調用本類的方法,super調用父類的方法。那麼咱們來看一個小案例:
在前面建立的命令行工程中新建一個類,使其繼承於MyObject類,命名爲MyObjectSon,在其中提供兩個方法,以下:
MyObjectSon.h文件:
#import "MyObject.h" @interface MyObjectSon : MyObject -(void)showClass; -(void)showSuperClass; @end
MyObjectSon.m文件:
#import "MyObjectSon.h" @implementation MyObjectSon -(void)showClass{ NSLog(@"%@",[self className]); } -(void)showSuperClass{ NSLog(@"%@",[super className]); } @end
分別調用兩個方法,你會驚奇的發現,打印結構都是「MyObjectSon」,super關鍵字失效了麼?非也非也,下面咱們來用消息發送機制從新模擬這兩個方法的調用。
首先[self className]在調用時會採用前面介紹的消息發送機制先從當前類中找className函數,當前類中並無提供className函數,因此消息會隨着繼承鏈向上傳遞,找到MyObject類中也沒有className函數的實現,會繼續向上,最終在NSObject類中找到這個方法,記住,這條消息處理的兩個要素是:當前MyObjectSon實例對象做爲接收者,NSObject類中的className方法做爲調用函數。
當調用[super className]時,首先會使用objc_msgSendSuper方法進行消息的發送,等價於以下代碼:
-(void)showSuperClass{ //建立父類接收對象結構體 struct objc_super superObj = {self, object_getClass([MyObject new])}; NSString * name = ((id(*)(struct objc_super*,SEL))objc_msgSendSuper)(&superObj,@selector(className)); NSLog(@"%@",name); }
objc_msgSendSuper函數第一個參數爲一個父類接收者結構體指針,objc_super結構體定義以下:
struct objc_super { //接收者 __unsafe_unretained id receiver; //接收者類型 __unsafe_unretained Class super_class; };
在構造objc_super這個結構體時,receive爲接收消息的對象,super_class爲從哪一個類中查方法。如此來看一些都清楚了,系統首先從MyObject類中找className方法,沒有相應的實現,會繼續向上直到找到NSObject類中的className方法,以後進行執行。這條消息處理的兩個要素是:當前MyObjectSon實例對象做爲接收者,NSObject類中的className方法做爲調用函數。這樣分析下來,不管是使用self執行的className方法仍是使用super執行的className方法,行爲實質上是徹底一致的!
特殊返回值類型對應不一樣的發送消息函數:
//返回值爲結構體時使用此方法發送消息 void objc_msgSend_stret(id self, SEL op, ...); void objc_msgSendSuper_stret(struct objc_super *super, SEL op, ...); //返回值爲浮點數時使用此方法發送消息 double objc_msgSend_fpret(id self, SEL op, ...);
除了使用SEL方法選擇器來發送消息,也能夠直接使用Method來發送消息:
//進行函數的調用 /* receiver:接收者 m:函數 ...:參數 */ id method_invoke(id receiver, Method m, ...); //返回結構體數據的函數調用 void method_invoke_stret(id receiver, Method m, ...);
Method也是一種結構體指針,其定義以下:
struct objc_method { //選擇器 SEL method_name OBJC2_UNAVAILABLE; //參數類型 char *method_types OBJC2_UNAVAILABLE; //函數地址 IMP method_imp OBJC2_UNAVAILABLE; }
示例代碼以下:
int main(int argc, const char * argv[]) { @autoreleasepool { MyObject * obj = [[MyObject alloc]init]; [obj class]; //爲了消除未定義選擇器的警告 #pragma clang diagnostic push #pragma clang diagnostic ignored"-Wundeclared-selector" //進行消息發送 Method method = class_getInstanceMethod([MyObject class], @selector(showSelf:age:)); ((void(*)(id,Method,NSString *,int))method_invoke)(obj,method,@"琿少",25); #pragma clang diagnostic pop } return 0; }
下面這些方法能夠跳過當前對象,直接進行消息轉發:
//跳過當前對象直接進行消息轉發機制 id _objc_msgForward(id receiver, SEL sel, ...); void _objc_msgForward_stret(id receiver, SEL sel, ...);
一點建議,上面兩個方法都是如下劃線開頭,這也代表設計者並不想讓你直接調用這個方法,確實如此,這兩個方法會直接出發對象的消息轉發流程,即使當前對象類已經實現了相應的方法也不會進行查找。
所謂運行時是針對於編譯時而言的,本篇文章的開頭,咱們就說過Objective-C是一種極動態的運行時語言。對象的行爲是在運行時被決定的,咱們前邊也瞭解了有關isa指針即Class的內容,雖然咱們並不能直接訪問isa指針,可是咱們能夠經過objc/runtime.h文件中定義的運行時方法來獲取或改變類與對象的行爲。
//對OC對象進行內存拷貝 在ARC環境下不可用 /* obj:要拷貝的對象 size:內存大小 */ id object_copy(id obj, size_t size); //進行OC對象內存的釋放 在ARC環境下不可用 id object_dispose(id obj); //獲取OC對象的類 注意 這個返回值和isa指針並非同一個指針 Class object_getClass(id obj); //重建對象的類 Class object_setClass(id obj, Class cls); //判斷一個OC對象是不是類或元類(前面說過類實際上也是對象) BOOL object_isClass(id obj); //獲取OC對象的類名 const char *object_getClassName(id obj); //經過類名獲取「類」對象 Class objc_getClass(const char *name); //經過類名獲取元類對象 Class objc_getMetaClass(const char *name); //這個方法也是返回類的定義 只是若是是未註冊的 會返回nil Class objc_lookUpClass(const char *name); //這個方法也是返回類的定義 只是若是是未註冊的 會直接殺死進程 Class objc_getRequiredClass(const char *name); /* 獲取全部已經註冊的類 會返回已經註冊的類的個數 一般使用以下示例代碼: int numClasses; Class * classes = NULL; numClasses = objc_getClassList(NULL, 0); if (numClasses > 0) { classes = (Class*)malloc(sizeof(Class) * numClasses); numClasses = objc_getClassList(classes, numClasses); NSLog(@"number of classes: %d", numClasses); for (int i = 0; i < numClasses; i++) { Class cls = classes[i]; NSLog(@"class name: %s", class_getName(cls)); } free(classes); } */ int objc_getClassList(Class *buffer, int bufferCount); //拷貝全部註冊過的類列表 參數爲輸出類的個數 Class *objc_copyClassList(unsigned int *outCount); //獲取Class類名字符串 const char *class_getName(Class cls); //判斷一個Class是否爲元類 BOOL class_isMetaClass(Class cls); //獲取一個類的父類 Class class_getSuperclass(Class cls); //修改一個類的父類 Class class_setSuperclass(Class cls, Class newSuper); //獲取一個類的版本 int class_getVersion(Class cls); //設置一個類的版本 void class_setVersion(Class cls, int version); //獲取類的內存佈局 size_t class_getInstanceSize(Class cls);
上面列舉的方法都和類相關,你沒看錯,經過object_setClass()動態改變對象所屬的類,可是須要注意,對象的成員變量並不會受到影響,方法則所有替換爲新類的方法。若是你喜歡,你甚至能夠運行時動態修改類的父類,這十分酷吧。下面這些方法則與類中的變量有關:
//獲取類額外分配內存的指針 void *object_getIndexedIvars(id obj); //根據變量名獲取實例變量指針 /* Ivar實質上是一個結構體指針,存放變量信息,以下: struct objc_ivar { //變量名 char *ivar_name OBJC2_UNAVAILABLE; //變量類型 char *ivar_type OBJC2_UNAVAILABLE; int ivar_offset OBJC2_UNAVAILABLE; #ifdef __LP64__ int space OBJC2_UNAVAILABLE; #endif } */ Ivar class_getInstanceVariable(Class cls, const char *name); //根據變量名獲取類變量指針 Ivar class_getClassVariable(Class cls, const char *name); //獲取全部實例變量指針 outCount輸出實例變量個數 Ivar *class_copyIvarList(Class cls, unsigned int *outCount); //經過變量指針獲取具體變量的值 id object_getIvar(id obj, Ivar ivar); //經過變量指針設置具體變量的值 void object_setIvar(id obj, Ivar ivar, id value); //經過變量指針設置變量的值 並進行強引用 void object_setIvarWithStrongDefault(id obj, Ivar ivar, id value); //直接經過變量名修改實例變量的值 會返回變量指針 Ivar object_setInstanceVariable(id obj, const char *name, void *value); //用法同上 Ivar object_setInstanceVariableWithStrongDefault(id obj, const char *name, void *value); //經過變量名直接獲取示例變量的值 Ivar object_getInstanceVariable(id obj, const char *name, void **outValue); //獲取類變量內存佈局 const uint8_t *class_getIvarLayout(Class cls); //設置類變量內存佈局 void class_setIvarLayout(Class cls, const uint8_t *layout); //獲取類變量佈局 弱引用 const uint8_t *class_getWeakIvarLayout(Class cls); //同上 void class_setWeakIvarLayout(Class cls, const uint8_t *layout); //經過屬性名獲取屬性 /* 屬性特指使用@prototype定義的 objc_property_t是一個結構體指針,其描述的是屬性的信息,以下: typedef struct { const char *name; /**屬性名 */ const char *value; /**屬性值 */ } objc_property_attribute_t; */ objc_property_t class_getProperty(Class cls, const char *name); //獲取全部屬性列表 objc_property_t *class_copyPropertyList(Class cls, unsigned int *outCount); //向類添加一個實例變量 /* 須要注意,已經註冊存在的類是不能經過這個方法追加實例變量的 這個方法只能在objc_allocateClassPair函數執行後而且objc_registerClassPair執行前進行調用 即這個函數是用來動態生成類的 */ BOOL class_addIvar(Class cls, const char *name, size_t size, uint8_t alignment, const char *types); //向類中添加屬性 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); //獲取變量指針對應的變量名 const char *ivar_getName(Ivar v); //獲取編碼後的變量類型 const char *ivar_getTypeEncoding(Ivar v); //獲取屬性名 const char *property_getName(objc_property_t property); //獲取屬性attribute const char *property_getAttributes(objc_property_t property); char *property_copyAttributeValue(objc_property_t property, const char *attributeName); //獲取屬性attribute列表 objc_property_attribute_t *property_copyAttributeList(objc_property_t property, unsigned int *outCount); //進行屬性關聯 這種方式能夠爲已經存在的類的實例擴展屬性 /* object:要添加屬性的對象 key:關聯的鍵 value:添加的屬性的值 policy:添加屬性的策略 typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) { //assign OBJC_ASSOCIATION_ASSIGN = 0, /**< Specifies a weak reference to the associated object. */ //retain nonatimic OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. * The association is not made atomically. */ //copy nonatomic OBJC_ASSOCIATION_COPY_NONATOMIC = 3, /**< Specifies that the associated object is copied. * The association is not made atomically. */ //retain OBJC_ASSOCIATION_RETAIN = 01401, /**< Specifies a strong reference to the associated object. * The association is made atomically. */ //copy OBJC_ASSOCIATION_COPY = 01403 /**< Specifies that the associated object is copied. * The association is made atomically. */ }; */ void objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy); //獲取關聯屬性的值 id objc_getAssociatedObject(id object, const void *key); //移除一個關聯的屬性 void objc_removeAssociatedObjects(id object);
//經過選擇器獲取某個類的實例方法 Method class_getInstanceMethod(Class cls, SEL name); //經過選擇器定義某個類的類方法 Method class_getClassMethod(Class cls, SEL name); //經過選擇器獲取某個類的方法函數指針 IMP class_getMethodImplementation(Class cls, SEL name); //同上 IMP class_getMethodImplementation_stret(Class cls, SEL name); //判斷某個類是否能夠相應選擇器 BOOL class_respondsToSelector(Class cls, SEL sel); //獲取某個類的實例方法列表 Method *class_copyMethodList(Class cls, unsigned int *outCount); //爲某個類動態添加一個實例方法 /* cls:添加方法的類 SEL:添加的方法選擇器 IMP:方法實現 types:參數類型字符串 */ BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types); //替換一個方法的實現 /* cls:類 SEL:要替換實現的選擇器 IMP:實現 types:參數類型字符串 */ IMP class_replaceMethod(Class cls, SEL name, IMP imp, const char *types); //獲取函數的選擇器 SEL method_getName(Method m); //獲取函數的實現 IMP method_getImplementation(Method m); //獲取函數的參數類型 const char *method_getTypeEncoding(Method m); //獲取函數的參數個數 unsigned int method_getNumberOfArguments(Method m); //獲取函數的返回值類型 char *method_copyReturnType(Method m); //拷貝參數類型列表 char *method_copyArgumentType(Method m, unsigned int index); //獲取返回值類型 void method_getReturnType(Method m, char *dst, size_t dst_len) ; //獲取參數類型 void method_getArgumentType(Method m, unsigned int index, char *dst, size_t dst_len); //獲取函數描述信息 /* objc_method_description結構體描述函數信息 以下: struct objc_method_description { //函數名 SEL name; /**< The name of the method */ //參數類型 char *types; /**< The types of the method arguments */ }; */ struct objc_method_description *method_getDescription(Method m); //修改某個函數的實現 IMP method_setImplementation(Method m, IMP imp); //交換兩個函數的實現 void method_exchangeImplementations(Method m1, Method m2); //獲取選擇器名稱 const char *sel_getName(SEL sel); //經過名稱獲取選擇器 SEL sel_getUid(const char *str); //註冊一個選擇器 SEL sel_registerName(const char *str); //判斷兩個選擇器是否相等 BOOL sel_isEqual(SEL lhs, SEL rhs); //將block做爲IMP的實現 IMP imp_implementationWithBlock(id block); //獲取IMP的實現block id imp_getBlock(IMP anImp); //刪除IMP的block實現 BOOL imp_removeBlock(IMP anImp);
上面列舉的函數中不少都用到參數類型的指定,types須要設置爲C風格的字符數組,即C字符串,其中第1個字符表示返回值類型,其他字符依次表示參數類型,參數類型與字符的映射表以下:
//判斷某個類是否遵照某個協議 BOOL class_conformsToProtocol(Class cls, Protocol *protocol); //拷貝某個類的協議列表 Protocol * __unsafe_unretained *class_copyProtocolList(Class cls, unsigned int *outCount); //動態向類中添加協議 BOOL class_addProtocol(Class cls, Protocol *protocol); //經過協議名獲取某個協議指針 Protocol *objc_getProtocol(const char *name); //拷貝全部協議列表 Protocol * __unsafe_unretained *objc_copyProtocolList(unsigned int *outCount); //判斷某個協議是否繼承於另外一個協議 BOOL protocol_conformsToProtocol(Protocol *proto, Protocol *other); //判斷兩個協議是否相同 BOOL protocol_isEqual(Protocol *proto, Protocol *other); //獲取協議名 const char *protocol_getName(Protocol *p); //獲取協議中某個函數的描述 /* p:協議指針 aSel:方法選擇器 isRequiredMethod:是不是必實現的 isInstanceMehod:是不是實例方法 */ struct objc_method_description protocol_getMethodDescription(Protocol *p, SEL aSel, BOOL isRequiredMethod, BOOL isInstanceMethod); //獲取協議方法描述列表 struct objc_method_description *protocol_copyMethodDescriptionList(Protocol *p, BOOL isRequiredMethod, BOOL isInstanceMethod, unsigned int *outCount); //獲取協議中的屬性描述 objc_property_t protocol_getProperty(Protocol *proto, const char *name, BOOL isRequiredProperty, BOOL isInstanceProperty); //獲取協議中的屬性描述列表 objc_property_t *protocol_copyPropertyList(Protocol *proto, unsigned int *outCount); //同上 objc_property_t *protocol_copyPropertyList2(Protocol *proto, unsigned int *outCount, BOOL isRequiredProperty, BOOL isInstanceProperty); //獲取適配協議列表 Protocol * __unsafe_unretained *protocol_copyProtocolList(Protocol *proto, unsigned int *outCount); //建立一個協議 Protocol *objc_allocateProtocol(const char *name); //進行協議註冊 void objc_registerProtocol(Protocol *proto); //向協議中添加一個方法描述 void protocol_addMethodDescription(Protocol *proto, SEL name, const char *types, BOOL isRequiredMethod, BOOL isInstanceMethod); //向協議中添加另外一個協議 void protocol_addProtocol(Protocol *proto, Protocol *addition); //向協議中添加屬性描述 void protocol_addProperty(Protocol *proto, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount, BOOL isRequiredProperty, BOOL isInstanceProperty);
協議實質也是一個Objective-C對象。
//動態建立一個類實例 /* cls:類名 size_t:分配額外的內存 用來存放類定義以外的變量 */ id class_createInstance(Class cls, size_t extraBytes); //同上 id objc_constructInstance(Class cls, void *bytes); //銷燬一個實例 void *objc_destructInstance(id obj); //動態定義一個類 /* superClass:指定父類 name:類名 extraBytes:額外的內存空間 */ Class objc_allocateClassPair(Class superclass, const char *name,size_t extraBytes); //進行類的註冊 須要注意 要添加屬性 必須在註冊類以前添加 void objc_registerClassPair(Class cls); //銷燬一個類 void objc_disposeClassPair(Class cls);
到此本篇文章終於要告一段落了,相信你若是能看到這裏,你必定有超凡的耐心。可是切記Objective-C的消息機制配合運行時是能夠給開發者極大的元編程自由,可是不適當的使用也會形成破壞性的後果。下面幾篇博客從一些方面介紹了Runtime的幾點應用,你能夠從中管中窺豹,可見一斑。
1.runtime基礎應用:http://www.javashuo.com/article/p-dlbbjtue-cc.html
2.使用runtime全局修改UILabel字體:http://www.javashuo.com/article/p-yxowtpff-be.html
3.使用runtime自動化歸檔:http://www.javashuo.com/article/p-amqfxthi-ms.html
4.代碼調試框架的設計:http://www.javashuo.com/article/p-yeekvfpt-hv.html