Objective-C Runtime(一)

Objective-c runtime (一)

一 運(yùn)行時(shí)系統(tǒng)相關(guān)特性

  • 使用Objective-C中的消息傳遞特性可以調(diào)用類和對(duì)象中的方法坏瞄。對(duì)象消息傳遞是一種動(dòng)態(tài)特性,既接收器和接收器中被調(diào)用的方法是在運(yùn)行時(shí)確定的。
  • 消息傳遞表達(dá)式包含接收器(接受消息的對(duì)象/類)和消息秃踩,而消息又由選擇器和相應(yīng)的輸入?yún)?shù)構(gòu)成衷快。如圖。


  • 選擇器是一種分段的文本字符串搔啊,每個(gè)分段以冒號(hào)結(jié)尾并且后跟參數(shù)柬祠。如上圖。
  • 選擇器數(shù)據(jù)類型(SEL)是一種特殊的Objective-C數(shù)據(jù)類型负芋,它用于在編譯源代碼時(shí)使用具有唯一性的標(biāo)志符替換選擇器漫蛔。使用 @selector關(guān)鍵字或Foundation框架中的NSSelectorFromString()函數(shù),可以創(chuàng)建類型為 SEL的變量旧蛾。
  • 使用動(dòng)態(tài)類型功能可以在運(yùn)行時(shí)決定對(duì)象的類型莽龟,因而能夠由運(yùn)行時(shí)因素決定在程序中使用哪種類型的對(duì)象,Objective-C通過(guò)id數(shù)據(jù)類型支持動(dòng)態(tài)類型锨天。
  • 使用動(dòng)態(tài)方法決議能夠以動(dòng)態(tài)方式實(shí)現(xiàn)方法毯盈。使用Objective-C@dynamic指令可以告知編譯器與某個(gè)屬性關(guān)聯(lián)的方法會(huì)以動(dòng)態(tài)方式實(shí)現(xiàn)。NSObject中含有resolveInstanceMethod:resolveClassMethod:方法病袄,使用他們能夠以動(dòng)態(tài)方式分別實(shí)現(xiàn)由選擇器指定的實(shí)例和類方法搂赋。
  • Foundation框架中NSObject類的API含有非常多用于執(zhí)行對(duì)象內(nèi)省的方法。在運(yùn)行程序時(shí)益缠,這些API能夠以動(dòng)態(tài)方式查詢方法的信息脑奠。它們還可以測(cè)試方法的繼承性、行為和一致性左刽。

二 運(yùn)行時(shí)系統(tǒng)的結(jié)構(gòu)

運(yùn)行時(shí)系統(tǒng)的組成部分

Objective-C 的運(yùn)行時(shí)系統(tǒng)由兩個(gè)主要部分構(gòu)成:編譯器和運(yùn)行時(shí)系統(tǒng)庫(kù)捺信。

編譯器

Objective-C語(yǔ)言中的面向?qū)ο笤睾蛣?dòng)態(tài)特性都是通過(guò)運(yùn)行時(shí)系統(tǒng)實(shí)現(xiàn)的。概括來(lái)講,運(yùn)行時(shí)系統(tǒng)由下列部分組成:

  • 類元素(接口迄靠、實(shí)現(xiàn)代碼秒咨、協(xié)議、分類掌挚、方法雨席、屬性、實(shí)例變量)
    • 類實(shí)例(對(duì)象)
    • 對(duì)象消息傳遞(包括動(dòng)態(tài)類型和動(dòng)態(tài)綁定)
    • 動(dòng)態(tài)方法決議
    • 動(dòng)態(tài)加載
    • 對(duì)象內(nèi)省
      當(dāng)編譯器解析使用了這些語(yǔ)言元素和特性的Objective-C源代碼時(shí)吠式,它會(huì)使用適當(dāng)?shù)倪\(yùn)行時(shí)系統(tǒng)庫(kù)數(shù)據(jù)結(jié)構(gòu)和實(shí)現(xiàn)該語(yǔ)言特定行為的函數(shù)陡厘,生成可執(zhí)行代碼。例如
  1. 生成對(duì)象消息傳遞代碼

    當(dāng)編譯解析對(duì)象消息時(shí)特占,如:
    [obj show]
    它會(huì)生成調(diào)用運(yùn)行時(shí)系統(tǒng)庫(kù)中函數(shù)objc_msgSend()的代碼糙置。該函數(shù)將接收器、選擇器和消息傳遞的參數(shù)作為輸入?yún)?shù)是目。因此編譯器會(huì)將源代碼中的所有消息傳遞表達(dá)式轉(zhuǎn)換為調(diào)用運(yùn)行時(shí)系統(tǒng)庫(kù)函數(shù)objc_msgSend()的代碼谤饭。
    源文件1.m內(nèi)容如下:
    #import <Foundation/Foundation.h>

    @interface MyClass:NSObject
    
    - (void)show;
    @end
    
    @implementation MyClass
    
    - (void)show{
        NSLog(@"%@", @"show");
    }
    
    @end
    
    
    int main(int argc, char *argv[]) {
        @autoreleasepool {
            MyClass *obj = [[MyClass alloc] init];
            
            [obj show];
        }
    }
    

    經(jīng)過(guò)clang -rewrite-objc 1.m會(huì)得到編譯后的cpp文件,相關(guān)內(nèi)容如下:

  2. **生成類和對(duì)象的代碼
    當(dāng)編譯器解析含有類定義和對(duì)象的源代碼時(shí)懊纳,它會(huì)生成相應(yīng)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)揉抵。
    Objective-C中的類與運(yùn)行時(shí)系統(tǒng)中的Class數(shù)據(jù)結(jié)構(gòu)對(duì)應(yīng)。Class是指向objc_class不透明數(shù)據(jù)類型的指針嗤疯。
    typedef struct objc_class Class
    Objective-C對(duì)形象與運(yùn)行時(shí)系統(tǒng)中的objc_object數(shù)據(jù)結(jié)構(gòu)對(duì)應(yīng)冤今。
    struct objc_object
    {
    Class isa;
    //含有實(shí)例變量的長(zhǎng)度可變數(shù)據(jù)
    }
    實(shí)際上所有的Objective-C對(duì)象的類和對(duì)象對(duì)應(yīng)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)(objc_objectobjc_class)都是以isa指針開(kāi)頭(對(duì)應(yīng)的運(yùn)行時(shí)數(shù)據(jù)結(jié)構(gòu)的包含的第一個(gè)元素是isa)。
    下面的實(shí)例程序可以驗(yàn)證茂缚。
    #import <Foundation/Foundation.h>
    #import "LZPerson.h"
    #import <objc/message.h>
    #import <objc/runtime.h>
    @interface LZDog: NSObject{
    NSInteger _age;
    }

    @end
    
    @implementation LZDog
    
    - (instancetype)initWithAge:(NSInteger) age{
        
        self = [super init];
        if (self) {
            _age = age;
        }
        return self;
    }
    
    @end
        
    
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LZDog *dog1 = [[LZDog alloc] initWithAge:0x123456780a0b0c0d];
            
            LZDog *dog2 = [[LZDog alloc] initWithAge:0xaabbccdd12345678];
            //get the objec size
            NSInteger objcSize = class_getInstanceSize([LZDog class]);
            
            //打印對(duì)象的內(nèi)存數(shù)據(jù)
            NSData *dog1Data = [NSData dataWithBytes:(__bridge void*)dog1 length:objcSize];
            
            NSData *dog2Data = [NSData dataWithBytes:(__bridge void*)dog2 length:objcSize];
            
            //查看對(duì)象內(nèi)存
            NSLog(@"dog1: %@", dog1Data);
            NSLog(@"dog2: %@", dog2Data);
            //打印類地址
            NSLog(@"class address:%p", [LZDog class]);
            
            //查看類內(nèi)存
            id dogClass = objc_getClass("LZDog");
            NSInteger classSize = class_getInstanceSize([dogClass class]);
            
            NSData *classData = [NSData dataWithBytes:(__bridge void*)dogClass length:classSize];
            
            NSLog(@"dogclass: %@", classData);
            
            //打印父類(NSobject)地址
            NSLog(@"superclass address: %p", [LZDog superclass]);
            
            
            
        }
       
        return 0;
    }
    

    運(yùn)行結(jié)果如下:
    2015-10-04 11:25:01.297 testRuntime01[1186:87309] dog1: <78240000 01000000 0d0c0b0a 78563412>
    2015-10-04 11:25:01.298 testRuntime01[1186:87309] dog2: <78240000 01000000 78563412 ddccbbaa>
    2015-10-04 11:25:01.298 testRuntime01[1186:87309] class address:0x100002478
    2015-10-04 11:25:01.298 testRuntime01[1186:87309] dogclass: <a0240000 01000000 f0300c78 ff7f0000>
    2015-10-04 11:25:01.298 testRuntime01[1186:87309] superclass address: 0x7fff780c30f0
    分析結(jié)果如圖:


運(yùn)行時(shí)系統(tǒng)庫(kù)

蘋(píng)果公司提供的Objective-C運(yùn)行時(shí)系統(tǒng)庫(kù)實(shí)現(xiàn)了Objective-C的面向?qū)ο筇匦院蛣?dòng)態(tài)屬性戏罢。我們可以在代碼中直接運(yùn)用運(yùn)行時(shí)系統(tǒng)提供的API。下面代碼實(shí)例展示了如何在代碼中使用運(yùn)行時(shí)系統(tǒng)API阱佛。
import <Foundation/Foundation.h>
import "LZPerson.h"
import <objc/message.h>
import <objc/runtime.h>

/*
1.  動(dòng)態(tài)創(chuàng)建一個(gè)類 LZStudent(繼承自NSObject)
2.  動(dòng)態(tài)添加實(shí)例變量 name
3.  動(dòng)態(tài)添加成員方法 show
 */

static void show(id self, SEL _cmd){
NSLog(@"show");
}

int main(int argc, const char * argv[]) {
@autoreleasepool {
//創(chuàng)建一個(gè)類帖汞,繼承自NSobject
  Class LZStudent =   objc_allocateClassPair([NSObject class], "LZStudent", 0);

BOOL isOk;
//向該類添加一個(gè)實(shí)例變量 NSString *_name
isOk = class_addIvar(LZStudent, "_name", sizeof(NSString *), log2(sizeof(id)), @encode(NSString));

if (!isOk) {
NSLog(@"addivar fail");
}
//想該類添加一個(gè)實(shí)例方法戴而。
isOk = class_addMethod(LZStudent, NSSelectorFromString(@"show"), (IMP)show, "v@:");

if (!isOk) {
NSLog(@"addMethod fail");
}
objc_registerClassPair(LZStudent);

unsigned int count ;
Ivar *arr = class_copyIvarList(LZStudent, &count);
//打印類變量的個(gè)數(shù)
NSLog(@"variable count: %u", count);
//打印變量的名字和類型
for (NSInteger i = 0; i < count; i ++) {
const char* ivarName = ivar_getName(arr[i]);
const char* ivarType = ivar_getTypeEncoding(arr[i]);
NSLog(@"name: %s, type: %s", ivarName, ivarType);
}


id obj = [[LZStudent alloc] init];
[obj performSelector:NSSelectorFromString(@"show")];
}
   
return 0;
}

運(yùn)行結(jié)果如下
2015-10-04 15:25:19.057 testRuntime01[1537:152205] variable count: 1
2015-10-04 15:25:19.058 testRuntime01[1537:152205] name: _name, type: {NSString=#}
2015-10-04 15:25:19.058 testRuntime01[1537:152205] show

運(yùn)行時(shí)系統(tǒng)庫(kù)數(shù)據(jù)類型和函數(shù)為運(yùn)行時(shí)系統(tǒng)庫(kù)提供了實(shí)現(xiàn)各種Objective-C特性(如消息傳遞)所必須的數(shù)據(jù)類型核函數(shù)凑术。如圖


當(dāng)程序想對(duì)象發(fā)送消息時(shí),運(yùn)行時(shí)系統(tǒng)會(huì)通過(guò)自定義代碼中的類方法緩存和虛函數(shù)表所意,查找類的實(shí)例方法淮逊。為了找到相應(yīng)的方法運(yùn)行時(shí)系統(tǒng)會(huì)搜索整個(gè)類的層次結(jié)構(gòu),找到方法后扶踊,它就會(huì)執(zhí)行方法的實(shí)現(xiàn)代碼泄鹏。運(yùn)行時(shí)系統(tǒng)庫(kù)含有非常多用于實(shí)現(xiàn)消息傳遞的設(shè)計(jì)機(jī)制。下面分別介紹這幾個(gè)機(jī)制:

  1. 通過(guò)虛函數(shù)表查找方法
    運(yùn)行時(shí)系統(tǒng)定義了一種方法數(shù)據(jù)類型(objc_method)代碼如下
    struct objc_method
    {
    SEL method_name;
    char *method_type;
    IMP method_imp;
    }
    typedef struct objc_method Method;
    因?yàn)樵趫?zhí)行程序的過(guò)程中調(diào)用方法的操作可能執(zhí)行數(shù)百萬(wàn)次秧耗,所以運(yùn)行時(shí)系統(tǒng)使用了一種快速高效的方法查詢和調(diào)用機(jī)制备籽。虛函數(shù)表是運(yùn)行時(shí)系統(tǒng)使用的動(dòng)態(tài)綁定的支持機(jī)制。Objective-C運(yùn)行時(shí)系統(tǒng)實(shí)現(xiàn)了一種自定義的虛函數(shù)分派機(jī)制,專門用于最大限度的提高性能和靈活性车猬。
    虛函數(shù)表是一個(gè)用于存儲(chǔ)IMP (Objective-C方法的實(shí)現(xiàn)代碼)的數(shù)據(jù)數(shù)組霉猛。每個(gè)運(yùn)行時(shí)系統(tǒng)類實(shí)例(obcj_class)都有一個(gè)紙向虛函數(shù)表的指針。
    每個(gè)尅實(shí)例還擁有最近調(diào)用過(guò)方法的指針緩存珠闰。運(yùn)行時(shí)系統(tǒng)庫(kù)先搜索緩存惜浅,如果沒(méi)找到,再去虛函數(shù)表去查找伏嗜,如果在虛函數(shù)表找到了就將IMP存儲(chǔ)到緩存中備用坛悉。
  2. 消息分派
    當(dāng)我們向?qū)ο蟀l(fā)送消息時(shí)比如[obj message ],編譯器會(huì)根據(jù)對(duì)象找到對(duì)應(yīng)的類承绸,然后從類(及其父類)的緩存和虛函數(shù)表(參考1)中查找裸影,并將其轉(zhuǎn)換為一條標(biāo)準(zhǔn)的C語(yǔ)言函數(shù)調(diào)用objc_msgSend()()。如果沒(méi)有找到符合的方法军熏,那就執(zhí)行消息轉(zhuǎn)發(fā)空民。
    消息轉(zhuǎn)發(fā)分為兩大階段。第一階段先征詢接受者所屬的類羞迷,看其能否動(dòng)態(tài)添加方法界轩,這叫做動(dòng)態(tài)方法決議(resolveInstanceMethod:)。如果我們沒(méi)有動(dòng)態(tài)的添加方法實(shí)現(xiàn)那么熱就會(huì)進(jìn)入第二階段衔瓮。首先運(yùn)行時(shí)系統(tǒng)請(qǐng)接受著看看能否將消息轉(zhuǎn)發(fā)給其他的對(duì)象浊猾,如能則該消息有其他對(duì)象處理。這成為快速轉(zhuǎn)發(fā)(forwardingTargetForSelector:)热鞍。如果沒(méi)有合適的對(duì)象處理消息葫慎,運(yùn)行時(shí)系統(tǒng)會(huì)把與消息有關(guān)的全部細(xì)節(jié)都封裝到NSInvocation中,再給接受者最后一次機(jī)會(huì)薇宠。整個(gè)過(guò)程如圖所示偷办。

    示例代碼
  3. 訪問(wèn)類實(shí)例方法
    當(dāng)我們向類發(fā)送消息時(shí),如[NSobject alloc]澄港,運(yùn)行時(shí)系統(tǒng)是通過(guò)元類實(shí)現(xiàn)的椒涯。
    元類是一種特殊的類對(duì)象,運(yùn)行時(shí)系統(tǒng)使用其中含有的信息能夠找到并調(diào)用類方法回梧。每個(gè)類都有一個(gè)獨(dú)一無(wú)二的元類废岂,元類中存放著類的類方法列表(類的實(shí)例方法存放在類里,類方法列表存放在元類那里)狱意。
    Objective-C的對(duì)象的isa指針指向類湖苞,類的isa指向元類。
    綜上所述運(yùn)行時(shí)系統(tǒng)通過(guò)下列方式對(duì)實(shí)例和類方法執(zhí)行消息傳遞操作详囤。
    • 當(dāng)向?qū)ο蟀l(fā)送消息時(shí)财骨,會(huì)通過(guò)類的緩存和虛函數(shù)表,獲得相應(yīng)的方法次慢。
    • 當(dāng)向類發(fā)送消息時(shí)重窟, 會(huì)通過(guò)元類的緩存和虛函數(shù)表朗徊,獲得相應(yīng)的方法报慕。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蝠嘉,一起剝皮案震驚了整個(gè)濱河市奴烙,隨后出現(xiàn)的幾起案子石蔗,更是在濱河造成了極大的恐慌艾栋,老刑警劉巖娃属,帶你破解...
    沈念sama閱讀 218,607評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件六荒,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡矾端,警方通過(guò)查閱死者的電腦和手機(jī)掏击,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,239評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)秩铆,“玉大人砚亭,你說(shuō)我怎么就攤上這事∨孤辏” “怎么了捅膘?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,960評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)滚粟。 經(jīng)常有香客問(wèn)我寻仗,道長(zhǎng),這世上最難降的妖魔是什么凡壤? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,750評(píng)論 1 294
  • 正文 為了忘掉前任署尤,我火速辦了婚禮,結(jié)果婚禮上亚侠,老公的妹妹穿的比我還像新娘曹体。我一直安慰自己,他們只是感情好硝烂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,764評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布箕别。 她就那樣靜靜地躺著,像睡著了一般钢坦。 火紅的嫁衣襯著肌膚如雪究孕。 梳的紋絲不亂的頭發(fā)上啥酱,一...
    開(kāi)封第一講書(shū)人閱讀 51,604評(píng)論 1 305
  • 那天爹凹,我揣著相機(jī)與錄音,去河邊找鬼镶殷。 笑死禾酱,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播颤陶,決...
    沈念sama閱讀 40,347評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼颗管,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了滓走?” 一聲冷哼從身側(cè)響起垦江,我...
    開(kāi)封第一講書(shū)人閱讀 39,253評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎搅方,沒(méi)想到半個(gè)月后比吭,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,702評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡姨涡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,893評(píng)論 3 336
  • 正文 我和宋清朗相戀三年衩藤,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片涛漂。...
    茶點(diǎn)故事閱讀 40,015評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡赏表,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出匈仗,到底是詐尸還是另有隱情瓢剿,我是刑警寧澤,帶...
    沈念sama閱讀 35,734評(píng)論 5 346
  • 正文 年R本政府宣布悠轩,位于F島的核電站跋选,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏哗蜈。R本人自食惡果不足惜前标,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,352評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望距潘。 院中可真熱鬧炼列,春花似錦、人聲如沸音比。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,934評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)洞翩。三九已至稽犁,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間骚亿,已是汗流浹背已亥。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,052評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留来屠,地道東北人虑椎。 一個(gè)月前我還...
    沈念sama閱讀 48,216評(píng)論 3 371
  • 正文 我出身青樓震鹉,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親捆姜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子传趾,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,969評(píng)論 2 355

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