關(guān)于運行時系統(tǒng)

用Objective-C等面向?qū)ο笳Z言編程時惹盼,“對象”就是“基本構(gòu)建單元”碍舍,開發(fā)者可以通過對象來存儲并傳遞數(shù)據(jù)埂奈。在對象之間傳遞數(shù)據(jù)并執(zhí)行任務(wù)的過程叫做“消息傳遞”若贮。當程序運行起來以后戳吝,為其提供相關(guān)支持的代碼叫做“Objective-C運行期系統(tǒng)”浩销,它提供了一些事得對象之間能夠傳遞消息的重要函數(shù),并且包括創(chuàng)建類實例所用的全部邏輯骨坑。

1.概括來說OC對象消息傳遞中具有下列關(guān)鍵元素

消息:向?qū)ο?類發(fā)送的名稱(選擇器)和一系列參數(shù)

方法:OC中的類或?qū)嵗椒ê成ぃ渎暶髦泻忻Q柬采、輸入?yún)?shù)、返回值和方法簽名(即輸入?yún)?shù)和返回值得數(shù)據(jù)類型)且警;

方法綁定:接收想指定接收器發(fā)送的消息并尋找和執(zhí)行適當方法的處理過程粉捻。OC運行時系統(tǒng)在調(diào)用方法時,會以動態(tài)綁定方式處理消息斑芜。


1.消息由接收者肩刃,選擇子及參數(shù)構(gòu)成。給某對象“發(fā)送消息”(invoke a method)也就相當于在該對象上調(diào)用方法杏头。

//1.self是接收者 2.messageName:是選擇子 3.選擇子和參數(shù)合起來稱為“消息”
id returnValue = [self messageName:@"messageValue"];

2.發(fā)給某對象的全部消息都要由“動態(tài)消息派發(fā)系統(tǒng)”來處理盈包,該系統(tǒng)會查出對應(yīng)的方法,并執(zhí)行其代碼醇王。
編譯器看到消息后呢燥,會將上面轉(zhuǎn)換為一條標準的C語言函數(shù)調(diào)用。
第一個參數(shù)是接收者 第二個參數(shù)是選擇子 后面的參數(shù)就是消息中的參數(shù)
void objc_msgSend(id self,SEL cmd,....);
id objc_returnValue = objc_msgSend(self,@selector(messageName:),@"messageValue");

執(zhí)行過程 objc_msgSend 會根據(jù)接收者和選擇子的類型來調(diào)用適當?shù)姆椒?br> * 1.在接收者所在的類中搜其“方法列表”寓娩,找到相符的方法叛氨,就跳至其實現(xiàn)代碼。
* 2.找不到就沿著繼承體系繼續(xù)往上找棘伴,等找到合適的方法后再跳轉(zhuǎn)寞埠;
* 3.如果最終還是找不到相符的,就執(zhí)行“消息轉(zhuǎn)發(fā)”操作


2.消息轉(zhuǎn)發(fā)機制

若想令類能理解某條消息焊夸,我們必須以程序代碼實現(xiàn)出對應(yīng)的方法才行仁连。但是,在編譯期向類發(fā)送了無法解讀的消息不會報錯阱穗,因為運行時系統(tǒng)可以繼續(xù)向類中添加方法饭冬,所以編譯器編譯時無法確定類中到底會不會有某個方法實現(xiàn)。當對象接受到無法解讀的消息后揪阶,就會啟動“消息轉(zhuǎn)發(fā)”機制伍伤。

消息轉(zhuǎn)發(fā).png

上圖是消息轉(zhuǎn)發(fā)過程:

1.對象在收到無法解讀的消息后,首先調(diào)用其所屬類的類方法遣钳。sel是無法響應(yīng)的選擇子扰魂。其返回值表示這個類是否能新增一個實例方法用以處理此選擇子。

//無法響應(yīng)的實例方法
+(BOOL)resolveInstanceMethod:(SEL)sel;
//無法響應(yīng)的類方法 
+(BOOL)resolveClassMethod:(SEL)sel;
id absoluteValue(id self,SEL _cmd, id value){
    NSInteger intVal = [value integerValue];
    if (intVal < 0) {
        return [NSNumber numberWithInteger:(intVal * -1)];
    }
    return value;
}

///動態(tài)添加實例方法方法
+(BOOL) resolveInstanceMethod:(SEL)sel{
    NSString *method = NSStringFromSelector(sel);
    if ([method hasPrefix:@"absoluteValue"]) {
        class_addMethod([self class], sel, (IMP)absoluteValue, "@@:@");
        NSLog(@"動態(tài)添加方法 方法名:%@",method);
        return YES;
    }
    return NO;
}

2.后備接收者

當前接受方法對象還有第二次機會處理未知的選擇子蕴茴,在這一步運行期系統(tǒng)會問該對象:能不能把這條消息轉(zhuǎn)給其他接收者處理劝评。

//快速轉(zhuǎn)發(fā)
//將該方法轉(zhuǎn)給其他對象,從而實現(xiàn)快速轉(zhuǎn)發(fā)
-(id)forwardingTargetForSelector:(SEL)aSelector;
- (id)forwardingTargetForSelector:(SEL)aSelector  
{  
    if (aSelector == @selector(sendMessage:)) {  
        return [LMMessageForwarding new];  
    }  
    return nil;  
}

3.啟動完整的消息轉(zhuǎn)發(fā)

//該方法能使對象能夠使用消息的全部內(nèi)容(目標倦淀,方法名蒋畜,參數(shù))
-(void)forwardInvocation:(NSInvocation *)anInvocation;

還有一個很重要的問題,我們必須重寫以下方法:

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

消息轉(zhuǎn)發(fā)機制使用從這個方法中獲取的信息來創(chuàng)建NSInvocation對象撞叽。因此我們必須重寫這個方法姻成,為給定的selector提供一個合適的方法簽名插龄。

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
 
    if (!signature) {
        if ([LMClass instancesRespondToSelector:aSelector]) {
            signature = [LMClass instanceMethodSignatureForSelector:aSelector];
        }
    }
 
    return signature;
}
 
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    if ([LMClass instancesRespondToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:_class];
    }
}

注意:forwardInvocation: 必須要經(jīng)過 methodSignatureForSelector:方法來獲得一個NSInvocation,開銷比較大科展。蘋果在 forwardingTargetForSelector 的discussion中也說這個方法是一個相對開銷多的多的方法

接受者在每一步中均有機會處理消息均牢。步驟越往后,處理消息的代價越大才睹。

2.運行時動態(tài)概念

運行時系統(tǒng)通過動態(tài)類型功能徘跪,可以在運行程序時決定對象的類型,因而可以使運行時因素能夠在程序中指定使用哪種對象琅攘。

//當使用靜態(tài)方式設(shè)置變量的類型時垮庐,變量的類型就由它的聲明決定。
LMClass *class

//動態(tài)聲明坞琴,該變量的類型是在運行時確定的
id class

動態(tài)綁定是指在運行程序時(而不是在編譯時)將消息與方法對應(yīng)起來的處理過程

--

OC對象收到消息后哨查,究竟會調(diào)用何種方法需要在運行期才能解析出來,所以與給定的選擇子名稱相對應(yīng)的方法可以在運行期改變剧辐,也可以在運行期添加新方法解恰。

動態(tài)創(chuàng)建方法
class_addMethod

    //使用運行時系統(tǒng)創(chuàng)建類
    //1.以動態(tài)方式穿件一個類
    Class dynaClass = objc_allocateClassPair([NSObject class], "LMDynaClass", 0);
    //2.以動態(tài)方式添加一個方法,使用已有方法(description)獲取特征
    Method description = class_getInstanceMethod([NSObject class], @selector(description));
    const char *types = method_getTypeEncoding(description);
    //class_addMethod(那個類添加新方法, SEL, 函數(shù)指針, 類型編碼) 動態(tài)在類同添加方法
    class_addMethod(dynaClass, @selector(getLMString), (IMP)getLMString, types);
    
    ///注冊這個動態(tài)類
    objc_registerClassPair(dynaClass);

動態(tài)跟換方法
method_exchangeImplementations

Method originalMethod = class_getInstanceMethod([NSStringclass], @selector(lowercaseString));
Method swappedMethod = class_getInstanceMethod([NSString class], @selector(uppercaseString));
//調(diào)用method_exchangeImplementations 交換
method_exchangeImplementations(originalMethod, swappedMethod);
//
NSString *string = @"This is The String";
//小寫變大寫方法
NSString *lowercaseString = [string lowercaseString];
//大寫方法變小寫
NSString *uppercaseString = [string uppercaseString];
   
NSLog(@"low= %@\n upp = %@",lowercaseString,uppercaseString);
   
Method lowString = class_getInstanceMethod([NSString class], @selector(lowercaseString));
Method lm_lowString = class_getInstanceMethod([NSString class], @selector(lm_lowercaseString));
   
method_exchangeImplementations(lowString, lm_lowString);
   
NSString *lowStr = [string lowercaseString];
   
NSLog(@"lowStr - >%@",lowStr)
-(NSString *)lm_lowercaseString{
    //看上去想死循環(huán) ,其實這方法是準備和loweercaseString互換浙于,運行時會調(diào)換
    //lm_lowercaseString 實際調(diào)用了 lowercaseString方法
    NSString *lowercase = [self lm_lowercaseString];
    NSLog(@"%@ => %@",self, lowercase);
    return lowercase;
}

3.運行時系統(tǒng)的結(jié)構(gòu)

OC的運行時系統(tǒng)由兩個主要部分構(gòu)成:編譯器和運行系統(tǒng)庫。

編譯器的作用是接收輸入的源代碼挟纱,生成使用了運行時系統(tǒng)庫的代碼羞酗,從而得到合法的、可執(zhí)行的OC程序紊服。

OC語言中的面向?qū)ο笤睾蛣討B(tài)特性都是通過運行時系統(tǒng)實現(xiàn)的檀轨。概括來說,運行時系統(tǒng)由下列部分組成:

類元素(接口欺嗤、實現(xiàn)代碼参萄、協(xié)議、分類煎饼、方法讹挎、屬性、實例變量)
類實例(對象)
對象消息傳遞(包括動態(tài)類型和動態(tài)綁定)
動態(tài)方法決議
動態(tài)加載
對象內(nèi)省

3.1測試類創(chuàng)建實例顯示其數(shù)據(jù)

關(guān)于實例與類的指針解釋可以看回這一章節(jié):對Objectiv-C的一些指針的理解

LMTestClass *tc1 = [[LMTestClass alloc] init];
tc1->myInt = 0xa5a5a5a5;
LMTestClass *tc2 = [[LMTestClass alloc] init];
tc2->myInt = 0xc3c3c3c3;
long tc1Size = class_getInstanceSize([LMTestClass class]);
NSData *obj1Data = [NSData dataWithBytes:(__bridge const void *)(tc1) length:tc1Size];
NSData *obj2Data = [NSData dataWithBytes:(__bridge const void*)(tc2)  length:tc1Size];
NSLog(@"LMTestClass object tc1 contains %@", obj1Data);
NSLog(@"LMTestClass object tc2 contains %@", obj2Data);
NSLog(@“LMTestClass Memory address = %p",[LMTestClass class]);

輸出結(jié)果
LMTestClass object tc1 contains <3874e000 01000000 a5a5a5a5 00000000> LMTestClass object tc2 contains <3874e000 01000000 c3c3c3c3 00000000 LMTestClass Memory address = 0x100e07438

從輸出結(jié)果可以看到3874e000 01000000 是isa指針吆玖,指向類筒溃。 a5a5a5a5 00000000是實例變量的值
0x100e074383874e000 01000000 地址是一樣,在mac pro中使用翻轉(zhuǎn)的字節(jié)順序存儲數(shù)據(jù)

從結(jié)果可以看出沾乘,實例對象中存在一個isa指針指向類怜奖,和存儲含有實例變量的長度可變數(shù)據(jù)指針。

3.2.實現(xiàn)運行時系統(tǒng)的對象消息傳遞

運行時系統(tǒng)庫數(shù)據(jù)類型分為下列幾類:
類定義數(shù)據(jù)結(jié)構(gòu)(類翅阵、方法歪玲、實例變量迁央、分類、IMP滥崩、和SEL等)
實例數(shù)據(jù)類型(id岖圈、objc_object和objc_super)
值(BOOL)

對象的類定義(objc_getClass)
類的父類(class_getSuperclass)
對象的元類定義(objc_getMetaClass)
類的名稱(class_getName)
類的版本信息(class_getVersion)
以字節(jié)為單位的類尺寸(class_getInstanceSize)
類的實例變量列表(class_copyIvarList)
類的方法列表(class_copyMethodList)
類的協(xié)議列表(class_copyProtocoList)
類的屬性列表(class_copyProperyList)

當程序向?qū)ο蟀l(fā)送消息時,運行時系統(tǒng)會通過自定義代碼中的類方法緩存和虛函數(shù)表查找類的實例方法夭委。為了找到相應(yīng)的方法幅狮,運行時系統(tǒng)會搜索整個類層次結(jié)構(gòu),找到該方法后株灸,它就會執(zhí)行該方法的實現(xiàn)代碼崇摄。

運行時系統(tǒng)中的消息傳遞操作.jpg

元類是一種特殊的類對象,運行時系統(tǒng)使用其中含有的信息能夠找到并調(diào)用類方法慌烧。每個類都擁有獨一無二的元類逐抑。

對象的isa指針指向描述該對象的類,因此可使用該變量訪問這個對象的實例方法屹蚊、實例變量等厕氨。

上碼

//獲取元類數(shù)據(jù)
id metaClass = objc_getMetaClass("LMTestClass");
long mclzSize = class_getInstanceSize([metaClass class]);
NSData *mclzData = [NSData dataWithBytes:(__bridge const void * _Nullable)(metaClass) length:mclzSize];
NSLog(@"LMTestClass metaClass address:%@",mclzData);
   
long objSize = class_getInstanceSize([NSObject class]);
NSData *objData = [NSData dataWithBytes:(__bridge const void * _Nullable)([NSObject class]) length:objSize];
NSLog(@"NSObject address : %@", objData);
   
id objMetaClass = objc_getMetaClass("NSObject");
long objMetaSize = class_getInstanceSize([objMetaClass class]);
NSData *objMetaData = [NSData dataWithBytes:(__bridge const void * _Nullable)(objMetaClass) length:objMetaSize];
NSLog(@"NSObject MetaClass : %@",objMetaData);

LMTestClass元類帶有一個指向父類元類的指針088eb808 01000000
//LMTestClass metaClass LMTestClass metaClass address:<088eb808 01000000 088eb808 01000000 e05d1100 80610000 07000000 01000000 00af0700 00610000>

//--NSObject 地址 NSObject address : <088eb808 01000000>

因為NSObject是根類,所以它的元類isa指針指向自己
//NSObject 元類 NSObject MetaClass : <088eb808 01000000 588eb808 01000000 e005d0ed c77f0000 0f000000 02000000 00b00700 80600000>

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末汹粤,一起剝皮案震驚了整個濱河市命斧,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌嘱兼,老刑警劉巖国葬,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異芹壕,居然都是意外死亡汇四,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門踢涌,熙熙樓的掌柜王于貴愁眉苦臉地迎上來通孽,“玉大人,你說我怎么就攤上這事睁壁”晨啵” “怎么了?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵潘明,是天一觀的道長糠惫。 經(jīng)常有香客問我,道長钉疫,這世上最難降的妖魔是什么硼讽? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮牲阁,結(jié)果婚禮上固阁,老公的妹妹穿的比我還像新娘壤躲。我一直安慰自己,他們只是感情好备燃,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布碉克。 她就那樣靜靜地躺著,像睡著了一般并齐。 火紅的嫁衣襯著肌膚如雪漏麦。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天况褪,我揣著相機與錄音撕贞,去河邊找鬼。 笑死测垛,一個胖子當著我的面吹牛捏膨,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播食侮,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼号涯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了锯七?” 一聲冷哼從身側(cè)響起链快,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎眉尸,沒想到半個月后域蜗,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡效五,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了炉峰。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畏妖。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖疼阔,靈堂內(nèi)的尸體忽然破棺而出戒劫,到底是詐尸還是另有隱情,我是刑警寧澤婆廊,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布迅细,位于F島的核電站,受9級特大地震影響淘邻,放射性物質(zhì)發(fā)生泄漏茵典。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一宾舅、第九天 我趴在偏房一處隱蔽的房頂上張望统阿。 院中可真熱鬧彩倚,春花似錦、人聲如沸扶平。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽结澄。三九已至哥谷,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間麻献,已是汗流浹背们妥。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留赎瑰,地道東北人王悍。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像餐曼,于是被迫代替她去往敵國和親压储。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

推薦閱讀更多精彩內(nèi)容