iOS RunTime的原理和使用

RunTime顧名思義運(yùn)行時(shí)穷劈,就是系統(tǒng)在運(yùn)行的時(shí)候的一些機(jī)制薇正,最主要的是消息機(jī)制片酝。對(duì)于C語(yǔ)言,函數(shù)的調(diào)用會(huì)在編譯時(shí)決定調(diào)用哪個(gè)函數(shù)挖腰,編譯完成后,會(huì)按順序執(zhí)行练湿,無(wú)二義性猴仑。OC的調(diào)用成為消息轉(zhuǎn)發(fā),編譯時(shí)不能決定調(diào)哪個(gè)函數(shù)肥哎,只有在真正運(yùn)行的時(shí)候通過(guò)函數(shù)名找到對(duì)應(yīng)的函數(shù)調(diào)用辽俗,屬于動(dòng)態(tài)調(diào)用的過(guò)程。

  • 什么是runtime篡诽?

我們寫(xiě)的oc代碼在運(yùn)行的時(shí)候崖飘,會(huì)被轉(zhuǎn)化為runtime的C代碼執(zhí)行。

e.g.:[obj doSomething];方法被轉(zhuǎn)化為objc_msgSend(obj, @selector(doSomething));

OC中的obj都繼承自NSObject杈女,類(lèi)的實(shí)例在Runtime中的結(jié)構(gòu)如下:

/// 描述類(lèi)中的一個(gè)方法

typedef struct objc_method *Method;

/// 實(shí)例變量

typedef struct objc_ivar *Ivar;

/// 類(lèi)別

typedef struct objc_category *Category;

/// 描述類(lèi)中的屬性

typedef struct objc_property *objc_property_t;

類(lèi)在runtime中的表示:

//類(lèi)在runtime中的表示
struct objc_class {
    Class isa; //指針
    //實(shí)例的isa指向類(lèi)對(duì)象朱浴,類(lèi)對(duì)象的isa指向元類(lèi)
    
#if !__OBJC2__
    Class super_class;    //指向父類(lèi)
    const char *name;     //類(lèi)名
    long version;         //當(dāng)前版本
    long info;
    long instance_size;
    struct objc_ivar_list *ivars;             //成員變量列表
    struct objc_method_list **methodLists;    //方法列表
    struct objc_cache *cache;                 //緩存
    struct objc_protocol_list *protocols;     //協(xié)議列表
#endif
    
} OBJC2_UNAVAILABLE;
  • runtime的使用:
1、獲取類(lèi)中的屬性方法列表等

我們可以通過(guò)runtime的一些方法獲取屬性列表达椰、成員變量列表翰蠢、方法列表、遵循的協(xié)議列表

unsigned int count;
//獲取屬性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
    const char *propertyName = property_getName(propertyList[i]);
    NSLog(@"property---->%@", [NSString stringWithUTF8String:propertyName]);
}
//獲取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (unsigned int i; i<count; i++) {
    Method method = methodList[i];
    NSLog(@"method---->%@", NSStringFromSelector(method_getName(method)));
}
//獲取成員變量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i; i<count; i++) {
    Ivar myIvar = ivarList[i];
    const char *ivarName = ivar_getName(myIvar);
    NSLog(@"Ivar---->%@", [NSString stringWithUTF8String:ivarName]);
}
//獲取協(xié)議列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (unsigned int i; i<count; i++) {
    Protocol *myProtocal = protocolList[i];
    const char *protocolName = protocol_getName(myProtocal);
    NSLog(@"protocol---->%@", [NSString stringWithUTF8String:protocolName]);
}
2啰劲、方法調(diào)用過(guò)程(在運(yùn)行時(shí))
  1. 如果是實(shí)例對(duì)象調(diào)用實(shí)例方法梁沧,會(huì)在實(shí)例的isa指針指向的對(duì)象(類(lèi)對(duì)象)操作。
  2. 如果是類(lèi)對(duì)象調(diào)用類(lèi)方法蝇裤,會(huì)在類(lèi)對(duì)象的isa指針指向的對(duì)象(元類(lèi)/基類(lèi))操作廷支。
    具體操作:
  • 首先,在相應(yīng)操作對(duì)象的緩存方法列表中查找要調(diào)用的方法栓辜,如果找到蚜迅,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)蛤肌,并執(zhí)行。
  • 如果未找到,在相應(yīng)操作對(duì)象的方法列表中查找质和,如果找到,轉(zhuǎn)向相應(yīng)實(shí)現(xiàn)喳魏,并執(zhí)行卖毁。
  • 如果未找到,在操作對(duì)象的父類(lèi)指針?biāo)赶虻膶?duì)象中操作1贩毕,2步悯许。
  • 以此類(lèi)推,如果到根類(lèi)還未找到辉阶,轉(zhuǎn)向攔截調(diào)用方法先壕。
  • 如果未實(shí)現(xiàn)攔截調(diào)用瘩扼,程序報(bào)錯(cuò)。
3垃僚、攔截調(diào)用

上面說(shuō)到了集绰,如果未找到方法,則轉(zhuǎn)向攔截調(diào)用谆棺。
那什么是攔截調(diào)用呢栽燕?
攔截調(diào)用就是,在找不到調(diào)用的方法程序崩潰之前改淑,你有機(jī)會(huì)重寫(xiě)NSObject類(lèi)中的四個(gè)方法來(lái)做處理:

+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//后兩個(gè)方法需要轉(zhuǎn)發(fā)到其他的類(lèi)處理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
  • 第一個(gè)方法是當(dāng)你調(diào)用一個(gè)不存在的類(lèi)方法的時(shí)候碍岔,會(huì)調(diào)用這個(gè)方法,默認(rèn)返回NO朵夏,你可以加上自己的處理然后返回YES蔼啦。
  • 第二個(gè)方法和第一個(gè)方法相似,只不過(guò)處理的是實(shí)例方法仰猖。
  • 第三個(gè)方法是將你調(diào)用的不存在的方法重定向到一個(gè)其他聲明了這個(gè)方法的類(lèi)捏肢,只需要你返回一個(gè)有這個(gè)方法的target。
  • 第四個(gè)方法是將你調(diào)用的不存在的方法打包成NSInvocation傳給你亮元。做完你自己的處理后猛计,調(diào)用invokeWithTarget:方法讓某個(gè)target觸發(fā)這個(gè)方法。
4爆捞、動(dòng)態(tài)添加方法

重寫(xiě)了攔截方法并返回YES奉瘤,我們要采取什么措施補(bǔ)救呢?
可以通過(guò)傳遞的SEL類(lèi)型的selector動(dòng)態(tài)添加方法煮甥。

舉個(gè)栗子:
obj 對(duì)象隱式調(diào)用一個(gè)不存在的方法doSomething
[obj performSelector:@selector(doSomething) withObject:nil];

然后在obj內(nèi)部的攔截方法動(dòng)態(tài)添加方法

void runAddMethod(id obj, SEL _cmd, NSString *str) {
    NSLog(@"add C IMP");
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if ([NSStringFromSelector(sel) isEqualToString:@"doSomething"]) {
        class_addMethod(self, sel, (IMP)runAddMethod, "v@:*");
    }
    return YES;
}

class_addMethod的四個(gè)參數(shù)解釋

  • Class cls 給哪個(gè)類(lèi)添加方法盗温,本例中是self。
  • SEL name 添加的方法成肘,本例中是重寫(xiě)的攔截調(diào)用傳進(jìn)來(lái)的selector卖局。
  • IMP imp 方法的實(shí)現(xiàn),C方法的方法實(shí)現(xiàn)可以直接獲得双霍。如果是OC方法砚偶,可以用+ (IMP)instanceMethodForSelector:(SEL)aSelector;獲得方法的實(shí)現(xiàn)。
  • “v@:*”方法的簽名洒闸,代表有一個(gè)參數(shù)的方法染坯,下面還會(huì)講到。
5.關(guān)聯(lián)對(duì)象

如果你想用系統(tǒng)類(lèi)丘逸,在不滿足需求情況下单鹿,你需要額外添加屬性,多數(shù)會(huì)使用繼承深纲,然后定義屬性仲锄【⒚睿總是繼承不是太麻煩,有了runtime儒喊,你不用費(fèi)那大勁了镣奋。接下來(lái),就來(lái)看看runtime是如何動(dòng)態(tài)添加屬性怀愧,獲取屬性的唆途。

//首先定義一個(gè)全局變量,用它的地址作為關(guān)聯(lián)對(duì)象的key
static char associatedObjectKey;
//設(shè)置關(guān)聯(lián)對(duì)象
objc_setAssociatedObject(target, &associatedObjectKey, @"添加的字符串屬性", OBJC_ASSOCIATION_RETAIN_NONATOMIC); 
//獲取關(guān)聯(lián)對(duì)象
NSString *string = objc_getAssociatedObject(target, &associatedObjectKey);
NSLog(@"AssociatedObject = %@", string);

objc_setAssociatedObject的四個(gè)參數(shù):

  • id object給誰(shuí)設(shè)置關(guān)聯(lián)對(duì)象掸驱。
  • const void *key關(guān)聯(lián)對(duì)象唯一的key,獲取時(shí)會(huì)用到没佑。
  • id value關(guān)聯(lián)對(duì)象毕贼。
  • objc_AssociationPolicy關(guān)聯(lián)策略,有以下幾種策略:
    OBJC_ASSOCIATION_ASSIGN = 0,
    OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, 
    OBJC_ASSOCIATION_COPY_NONATOMIC = 3,
    OBJC_ASSOCIATION_RETAIN = 01401,
    OBJC_ASSOCIATION_COPY = 01403 
}```

objc_getAssociatedObject的兩個(gè)參數(shù):

- id object獲取誰(shuí)的關(guān)聯(lián)對(duì)象蛤奢。
- const void *key根據(jù)這個(gè)唯一的key獲取關(guān)聯(lián)對(duì)象鬼癣。

可以將上述兩個(gè)方法寫(xiě)在當(dāng)前類(lèi)的類(lèi)別的方法中,方便調(diào)用啤贩。

//添加關(guān)聯(lián)對(duì)象

  • (void)addAssociatedObject:(id)object{
    objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    }
    //獲取關(guān)聯(lián)對(duì)象
  • (id)getAssociatedObject{
    return objc_getAssociatedObject(self, _cmd);//這里面我們把getAssociatedObject方法的地址作為唯一的key待秃,_cmd代表當(dāng)前調(diào)用方法的地址。
    }```

看到這里估計(jì)大家有些疲累了痹屹,別急章郁,馬上就結(jié)束了!

6.交換方法

就是將兩個(gè)方法的實(shí)現(xiàn)交換志衍。例如暖庄,將A方法和B方法交換,調(diào)用A方法的時(shí)候楼肪,就會(huì)執(zhí)行B方法中的代碼培廓,反之亦然。
我們來(lái)試一下 定義一個(gè)UIViewController的category

/**
 load方法會(huì)在類(lèi)第一次加載的時(shí)候被調(diào)用
 調(diào)用的時(shí)間比較靠前春叫,適合在這個(gè)方法里做方法交換
 */
+(void)load{
    //方法交換應(yīng)該被保證肩钠,在程序中只會(huì)執(zhí)行一次
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        //獲得viewController的生命周期方法的selector
        SEL systemSel = @selector(viewWillAppear:);
        //自己實(shí)現(xiàn)的將要被交換的方法的selector
        SEL customeSel = @selector(custome_viewWillAppear:);
        //兩個(gè)方法的Method
        Method systemMethod = class_getInstanceMethod([self class], systemSel);
        Method customeMethod = class_getInstanceMethod([self class], customeSel);
        
        //首先動(dòng)態(tài)添加方法,實(shí)現(xiàn)是被交換的方法暂殖,返回值表示添加成功還是失敗
        BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(customeMethod), method_getTypeEncoding(customeMethod));
        if (isAdd) {
            //如果成功价匠,說(shuō)明類(lèi)中不存在這個(gè)方法的實(shí)現(xiàn)
            //將被交換方法的實(shí)現(xiàn)替換到這個(gè)并不存在的實(shí)現(xiàn)
            class_replaceMethod(self, customeSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
        }else{
            //否則,交換兩個(gè)方法的實(shí)現(xiàn)
            method_exchangeImplementations(systemMethod, customeMethod);
        }
    });
}
- (void)custome_viewWillAppear:(BOOL)animated{
    //這時(shí)候調(diào)用自己央星,看起來(lái)像是死循環(huán)
    //但是其實(shí)自己的實(shí)現(xiàn)已經(jīng)被替換了
    [self custome_viewWillAppear:animated];//這里 是去執(zhí)行系統(tǒng)的viewWillApper:方法
    NSLog(@"custome");
}```

寫(xiě)個(gè)通用的方法供外部調(diào)
  • (void)changeMethodWithTarget:(id)obj fromMethod:(SEL)fromMethod toMethod:(SEL)toMethod
    {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
    Method m1 = class_getInstanceMethod([obj class], fromMethod);
    Method m2 = class_getInstanceMethod([obj class], toMethod);
    BOOL isAdd = class_addMethod([obj class], fromMethod, method_getImplementation(m2), method_getTypeEncoding(m2));
    if (isAdd) {
    class_replaceMethod([obj class], fromMethod, method_getImplementation(m2), method_getTypeEncoding(m2));
    }else{
    method_exchangeImplementations(m1, m2);
    }
    });
    }```

以上是runtime的基本原理何使用講解霞怀,有什么不對(duì)或不足的地方,希望大家多多指教莉给!
下一章:RunLoop與多線程的原理和使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末毙石,一起剝皮案震驚了整個(gè)濱河市廉沮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌徐矩,老刑警劉巖滞时,帶你破解...
    沈念sama閱讀 211,290評(píng)論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異滤灯,居然都是意外死亡坪稽,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,107評(píng)論 2 385
  • 文/潘曉璐 我一進(jìn)店門(mén)鳞骤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)窒百,“玉大人,你說(shuō)我怎么就攤上這事豫尽「萆遥” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 156,872評(píng)論 0 347
  • 文/不壞的土叔 我叫張陵美旧,是天一觀的道長(zhǎng)渤滞。 經(jīng)常有香客問(wèn)我,道長(zhǎng)榴嗅,這世上最難降的妖魔是什么妄呕? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,415評(píng)論 1 283
  • 正文 為了忘掉前任,我火速辦了婚禮嗽测,結(jié)果婚禮上绪励,老公的妹妹穿的比我還像新娘。我一直安慰自己论咏,他們只是感情好优炬,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,453評(píng)論 6 385
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著厅贪,像睡著了一般蠢护。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上养涮,一...
    開(kāi)封第一講書(shū)人閱讀 49,784評(píng)論 1 290
  • 那天葵硕,我揣著相機(jī)與錄音,去河邊找鬼贯吓。 笑死懈凹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的悄谐。 我是一名探鬼主播介评,決...
    沈念sama閱讀 38,927評(píng)論 3 406
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了们陆?” 一聲冷哼從身側(cè)響起寒瓦,我...
    開(kāi)封第一講書(shū)人閱讀 37,691評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎坪仇,沒(méi)想到半個(gè)月后杂腰,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,137評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡椅文,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,472評(píng)論 2 326
  • 正文 我和宋清朗相戀三年喂很,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片皆刺。...
    茶點(diǎn)故事閱讀 38,622評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡少辣,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出羡蛾,到底是詐尸還是另有隱情毒坛,我是刑警寧澤,帶...
    沈念sama閱讀 34,289評(píng)論 4 329
  • 正文 年R本政府宣布林说,位于F島的核電站,受9級(jí)特大地震影響屯伞,放射性物質(zhì)發(fā)生泄漏腿箩。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,887評(píng)論 3 312
  • 文/蒙蒙 一劣摇、第九天 我趴在偏房一處隱蔽的房頂上張望珠移。 院中可真熱鬧,春花似錦末融、人聲如沸钧惧。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,741評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)浓瞪。三九已至,卻和暖如春巧婶,著一層夾襖步出監(jiān)牢的瞬間乾颁,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,977評(píng)論 1 265
  • 我被黑心中介騙來(lái)泰國(guó)打工艺栈, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留英岭,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,316評(píng)論 2 360
  • 正文 我出身青樓湿右,卻偏偏與公主長(zhǎng)得像诅妹,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子毅人,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,490評(píng)論 2 348

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

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉吭狡,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,686評(píng)論 0 9
  • objc_getAssociatedObject返回與給定鍵的特定對(duì)象關(guān)聯(lián)的值尖殃。ID objc_getAssoci...
    有一種再見(jiàn)叫青春閱讀 1,567評(píng)論 0 7
  • 參數(shù)自一個(gè)指針,指向類(lèi)的要接收消息的實(shí)例赵刑。 OP在處理該信息的方法的選擇分衫。 ......可變參數(shù)列表包含參數(shù)的方法...
    reallychao閱讀 796評(píng)論 0 0
  • 一整天不在狀態(tài)晚上突發(fā)奇想去跑步,結(jié)果跑著跑著跑去新垵般此,路上肚子餓的咕咕叫蚪战,然后。铐懊。就去吃了碗沙茶面邀桑,然后,跟同事...
    Shadowsnow閱讀 81評(píng)論 0 0
  • 天下難事必作于易科乎,天下大事必作于細(xì)
    于魚(yú)于魚(yú)閱讀 103評(píng)論 0 0