Runtime使用C語言結構體表示對象,用C語言函數表示方法,這些C語言函數和結構體被Runtime封裝後,咱們就能夠在程序中執行建立,檢查,修改類和對象和他們的方法面試
runtime
一、是由C、C++、彙編寫成的api
二、OC運行時,裝載到內存api
相對應的編譯時,源代碼翻譯緩存
OC SWIFT JAVA 高級語言,不被機器所識別,須要編譯成響應的機器語言,二進制併發
Objective-c程序有三種途徑和運行時系統交互
一、經過Objective-c源代碼,如@selector()
二、經過Foundation框架中NSObject的方法,如 iskindof
三、經過調用運行時系統給咱們提供的api接口,如objc_msgSend,objc_getClass框架
OC對象本質是結構體
調用方法就是發送消息 objc_msgSend
消息的組成:((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
第一個參數p消息的接收者,第二個參數sel_registerName("run")方法編號
imp 函數實現的指針,sel找到imp函數
查看關係圖atom
OC的Class實際上是一個objc_class結構體的指針,下面是Class類的定義spa
typedef struct objc_class *Class;
查看objc/runtime.h中objc_class結構體的定義以下翻譯
struct objc_class { Class isa OBJC_ISA_AVAILABILITY; //isa指針 #if !__OBJC2__ Class super_class OBJC2_UNAVAILABLE; // 父類 const char *name OBJC2_UNAVAILABLE; // 類名 long version OBJC2_UNAVAILABLE; // 類的版本信息,默認爲0 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指針的指向就是該類對象的元類,每個類都是它的元類的對象,元類是對類對象的描述,就像類是普通實例對象的描述同樣。設計
每個類裏面聲明的類方法,其本質就是把該類方法放到元類的方法列表上面,因此類在調用類方法時,能夠想象成是元類的對象在調用一個實例方法。
A的父類是B,A的元類的父類是B的元類,B的父類是NSObject,NSObject的父類是nil,B元類的父類是NSObject的元類;特別注意的一點,NSObject的元類的父類是NSObject,NSObject的isa指針又指向NSObject的元類,因此在NSObject裏面的全部方法,NSObject的元類也都擁有,一、因此用NSObject 調用任意NSObject裏面的實例方法都是能夠成功的,
類和元類是一個閉環,實例指向類,類指向元類,元類指向跟元類,跟元類指向自身,根元類的父類是NSObject
元類是 Class 對象的類。每一個類(Class)都有本身獨一無二的元類(每一個類都有本身第一無二的方法列表)。這意味着全部的類對象都不一樣。
NSObject裏面的全部實力方法,任意類均可以經過類方法調用。
全部的meta-class使用基類的meta-class做爲本身的基類,對於頂層基類的meta-class也是同樣,只是它指向本身而已
[obj foo] 等同於 obj_msgSend(obj,@selector(foo))
objc 在向一個對象發送消息時,runtime庫會根據對象的isa指針找到該對象實際所屬的類,而後在該類的方法列表以及其父類方法列表中尋找方法運行。若是在層層的尋找中均位找到方法的實現,
就會拋出unrecognized selector sent to XXX的異常,致使程序奔潰.
在這奔潰前,oc運行時提供了三次拯救程序的機會
一、Method resolution ,動態方法解析階段
對應的具體方法是+(BOOL)resolveInstanceMethod:(SEL)sel 和+(BOOL)resolveClassMethod:(SEL)sel,
當方法是實例方法時調用前者,當方法爲類方法時,調用後者。這個方法設計的目的是爲了給類利用 class_addMethod 添加方法的機會。
// void(*)() // 默認方法都有兩個隱式參數, void eat(id self,SEL sel) { NSLog(@"%@ %@",self,NSStringFromSelector(sel)); } pragma mark 消息轉發第一步(實例) 如此便達到了,當此類調用未定義的實例方法時,自動調用eat函數,而避免了崩潰的狀況。 // 當一個對象調用未實現的方法,會調用這個方法處理,而且會把對應的方法列表傳過來. // 恰好能夠用來判斷,未實現的方法是否是咱們想要動態添加的方法 + (BOOL)resolveInstanceMethod:(SEL)sel { if (sel == @selector(eat)) { // 動態添加eat方法 // 第一個參數:給哪一個類添加方法 // 第二個參數:添加方法的方法編號 // 第三個參數:添加方法的函數實現(函數地址) // 第四個參數:函數的類型,(返回值+參數類型) v:void @:對象->self :表示SEL->_cmd class_addMethod(self, @selector(eat), (IMP)eat, "v@:"); } return [super resolveInstanceMethod:sel]; }
二、fowarding 方法轉發,備援接收者階段
對象的具體方法是-(id)forwardingTargetForSelector:(SEL)aSelector ,
此時,運行時詢問可否把消息轉給其餘接收者處理,也就是此時系統給了個將這個 SEL 轉給其餘對象的機會。
#pragma mark 消息轉發第二步, 第一步失敗後執行 #pragma mark 其實只要返回對象不爲self 和 nil 就會把消息轉發給返回的對象 - (id)forwardingTargetForSelector:(SEL)aSelector { NSString * str = NSStringFromSelector(aSelector); NSString * obj = [NSString stringWithFormat:@"testClass"]; NSLog(@"方法 %@ 即將轉發給 Class %@",str,[obj class]); return obj; }
三、 fowarding 方法轉發,完整消息轉發階段
首先會調用- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 方法,假若返回值爲nil,則runtime會發出doesNotRecognizeSelector:消息,引起異常,程序崩潰。若是返回了一個合理的函數簽名,Runtime就會建立一個NSInvocation對象併發送-forwardInvocation:消息給目標對象。參數 anInvocation 中包含未處理消息的各類信息(selector\target\參數...)。
在這個方法中,能夠把 anInvocation 轉發給多個對象,與第二步不一樣,第二步只能轉給一個對象
若是上述3個方法都沒有來處理這個消息,就會進入 NSObject 的-(void)doesNotRecognizeSelector:(SEL)aSelector方法中,拋出異常
總結一下整個消息轉發的流程:
代碼:
#import "ViewController.h" @interface ViewController () @property (weak, nonatomic) IBOutlet UILabel *displayLabel; - (IBAction)buttonTest:(UIButton *)sender; @end @implementation ViewController - (IBAction)buttonTest:(UIButton *)sender { NSLog(@"--1--"); [self performSelector:@selector(setText:) withObject:@"hello"]; } +(BOOL)resolveInstanceMethod:(SEL)sel { NSLog(@"--2--"); return NO; } //+(BOOL)resolveClassMethod:(SEL)sel //{ // NSLog(@"--2--"); // return NO; //} -(id)forwardingTargetForSelector:(SEL)aSelector { NSLog(@"--3--"); return nil; } -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector { NSLog(@"--4--"); NSMethodSignature *signature= [super methodSignatureForSelector:aSelector]; if (!signature) { signature=[self.displayLabel methodSignatureForSelector:aSelector]; } return signature; } -(void)forwardInvocation:(NSInvocation *)anInvocation { NSLog(@"--5--"); SEL seletor=[anInvocation selector]; if([self.displayLabel respondsToSelector:seletor]){ [anInvocation invokeWithTarget:self.displayLabel]; } } @end
問題:那咱們只用最後一個接盤俠方法多好啊,爲何還須要前2個呢?
其實還與這3個方法的用途不一樣有關:
運行期添加方法,用1;
轉發給另1個對象、改變方法時,用2;
須要轉發給多個對象時,用3;
參考:
連接:iOS消息轉發機制詳解