聊一聊RunTime那些事

世上只有兩種編程語言:一種是總是被人罵的,一種是從來沒人用的突倍。 — Bjarne Stroustrup

因為項目的原因,又加上沒有好的題材萨醒,所以最近一段時間都沒有寫一些東西出來拼坎。難得的是這幾天一期需求剛結(jié)束,乘著研究新需求的機(jī)會媚污,又重新去了解了一下RunTime,感覺受益還是挺大的廷雅。
RunTime:簡單的翻譯過來也就是運行時的意思耗美,主要是以c和匯編語言編寫出來的一套方便程序員調(diào)用函數(shù)的庫,其本質(zhì)意義也就是方便消息的傳遞航缀。

一商架、消息傳遞的過程

在object-c中,消息直到運行的時候才會綁定對應(yīng)的實現(xiàn),也就是說一開始方法和方法的實現(xiàn)是互相拆分開來的芥玉,并且object-c中也是允許我們來對其進(jìn)行自由組合蛇摸。

objc_msgSend(class, selector)//or objc_msgSend(class, selector, arg1, arg2, ...)

**SEL : **類成員方法的指針,但不同于C語言中的函數(shù)指針灿巧,函數(shù)指針直接保存了方法的地址赶袄,但SEL只是方法編號揽涮。

IMP:一個函數(shù)指針,保存了方法的地址

函數(shù)指針:一個函數(shù)總是占用一段連續(xù)的內(nèi)存區(qū)域,函數(shù)名在表達(dá)式中有時也會被轉(zhuǎn)換為該函數(shù)所在內(nèi)存區(qū)域的首地址饿肺,這和數(shù)組名非常類似蒋困。我們可以把函數(shù)的這個首地址(或稱入口地址)賦予一個指針變量,使指針變量指向函數(shù)所在的內(nèi)存區(qū)域唬格,然后通過指針變量就可以找到并調(diào)用該函數(shù)家破。

這里有必要再提一下,要實現(xiàn)消息的傳遞關(guān)鍵在于每個繼承于NSObject的類都能自動獲得runtime的支持购岗。每個類中都存在兩個重要的元素:

  • isa指針汰聋,指向該類定義的數(shù)據(jù)結(jié)構(gòu)體,這個結(jié)構(gòu)體是由編譯器編譯時為類(需繼承于NSObject)創(chuàng)建的.在這個結(jié)構(gòu)體中有包括了指向其父類類定義的指針

  • 類的分發(fā)表( dispatch table),該表包含selector的名稱及對應(yīng)實現(xiàn)函數(shù)的地址

Objc Message模型圖
  1. 消息執(zhí)行的時候喊积,首先根據(jù)傳遞對象的isa指針找到類的結(jié)構(gòu)烹困,每個類中都存在一個單獨的cache或methodlist,它可以緩存繼承或自定義的方法乾吻。根據(jù)selector名字會首先檢查class類的cache是否已經(jīng)緩存對應(yīng)的selector髓梅,如果有就直接調(diào)用對應(yīng)的實現(xiàn)IMP

  2. 如果在cache中找不到就會在其分發(fā)表中尋找對應(yīng)的selector,找到的話就調(diào)用對應(yīng)的實現(xiàn)绎签;找不到則會根據(jù)super class指針去其父類尋找枯饿,如果父類還找不到,會接著去父類的父類中尋找诡必,直到NSObject類為止

  3. 如果沒有找到奢方,并且實現(xiàn)了動態(tài)方法決議機(jī)制就會決議:resolveInstanceMethod和resolveClassMethod

  4. 如果沒有實現(xiàn)動態(tài)決議機(jī)制或者決議失敗且實現(xiàn)了消息轉(zhuǎn)發(fā)機(jī)制。就會進(jìn)入消息轉(zhuǎn)發(fā)流程爸舒。否則程序Crash(如果同時實現(xiàn)了動態(tài)決議和消息轉(zhuǎn)發(fā)蟋字。那么動態(tài)決議先于消息轉(zhuǎn)發(fā)。只有當(dāng)動態(tài)決議無法決議selector的實現(xiàn)扭勉,才會嘗試進(jìn)行消息轉(zhuǎn)發(fā)鹊奖。)

二、 獲取函數(shù)地址

通過直接獲取函數(shù)指針來繞過消息的綁定實現(xiàn)對函數(shù)的直接調(diào)用涂炎,可以節(jié)約消息傳遞的時間忠聚。
methodForSelector:方法你可以獲取selector對應(yīng)實現(xiàn)的指針,該指針必須轉(zhuǎn)換成合適的函數(shù)類型:

SEL aSelector = @selector(setFilled:);//設(shè)置對應(yīng)的函數(shù)指針類型
/*
使用methodForSelector:繞開動態(tài)綁定節(jié)約了消息傳遞時間
*/
IMP aIMP = [self methodForSelector:aSelector];
void (*setter)(id, SEL, NSString *) = (void(*)(id, SEL, NSString *))aIMP;
setter(self, aSelector, @"哈哈");//通過函數(shù)指針調(diào)用對應(yīng)實現(xiàn)   

三唱捣、 消息的動態(tài)決議

通過resolveInstanceMethod:resolveClassMethod:動態(tài)的為selector提供實現(xiàn)方法两蟀,在這里你可以通過方法名來對消息的傳遞進(jìn)行攔截來實現(xiàn)你的一些特殊需求:比如對無法處理消息的crash攔截處理或者替換系統(tǒng)API。
objective-c方法本質(zhì)上就是一個帶有至少兩個參數(shù)(_self和_cmd)的c函數(shù)爷光,你可以通過 class_addMethod為類添加一個函數(shù)

#import "SomeClass.h"
#import <objc/runtime.h>

static char * ObjectTagKey = "";
@interface SomeClass ()

@end

@implementation SomeClass{
}
//@dynamic objectTag;

#pragma mark - 動態(tài)決議

/**
 添加setter實現(xiàn)
 */
void dynamicSetMethod(id self, SEL _cmd, float w) {
    
    printf("dynamicSetMeghod-%s\n",[NSStringFromSelector(_cmd) cStringUsingEncoding:NSUTF8StringEncoding]);
    
    printf("%f\n",w);
    objc_setAssociatedObject(self, ObjectTagKey, [NSNumber numberWithFloat:w], OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}

void dynamicMethodIMP(id self, SEL _cmd) {
    NSLog(@" >> dynamicMethodIMP");
}

/**
 添加getter實現(xiàn)
 */
void dynamicGetMethod(id self, SEL _cmd) {
    printf("dynamicMethod-%s\n",[NSStringFromSelector(_cmd)
                                 cStringUsingEncoding:NSUTF8StringEncoding]);
    objc_getAssociatedObject(self, ObjectTagKey);
}

/**
 解析selector方法(動態(tài)方法決議,對象方法)
 */
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    
    NSString *methodName = NSStringFromSelector(sel);
    BOOL result = NO;
    /*
     動態(tài)的添加setter和getter方法
     */
    if ([methodName isEqualToString:@"setObjectTag:"]) {
        class_addMethod([self class], sel, (IMP)dynamicSetMethod, "v@:f");
        result = YES;
    }else if ([methodName isEqualToString:@"objectTag"]){
        class_addMethod([self class], sel, (IMP)dynamicGetMethod, "v@:f");
        result = YES;
    }
    return result;
}

/**
 解析selector方法(動態(tài)方法決議,類方法)
 */
+ (BOOL)resolveClassMethod:(SEL)sel {
    return [super resolveClassMethod:sel];
}

/**
 動態(tài)決議調(diào)用
 */
- (void)resolveBindMethod {
    
    SomeClass *objec = [[SomeClass alloc]init];
    objec.objectTag = 10.0f;
    float tag = objec.objectTag;
}

四、 消息的轉(zhuǎn)發(fā)

每一個對象都從NSObject類繼承了forwardInvocation:方法澎粟,但在NSObject中蛀序,該方法只是簡單的調(diào)用doesNotRecognizeSelector:欢瞪,通過重寫該方法你就可以利用forwardInvocation:將消息轉(zhuǎn)發(fā)給其它對象。

  1. 先調(diào)用methodSignatureForSelector:獲取指定selector的方法簽名
  2. 重寫forwardInvocation方法徐裸,調(diào)用invokeWithTarget來確定消息轉(zhuǎn)發(fā)的對象和對應(yīng)的原始參數(shù)
#import "SomeClass.h"
#import <objc/runtime.h>
#import "ForwardClass.h"

@implementation SomeClass{
    id forwardClass;
}

- (id)init {
    if (self = [super init]) {
        forwardClass = [ForwardClass new];
    }
    return self;
}

#pragma mark - 消息轉(zhuǎn)發(fā)

/**
 消息轉(zhuǎn)發(fā)
 */
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    
    if (!forwardClass) {
        [self doesNotRecognizeSelector:[anInvocation selector]];
    }
    [anInvocation invokeWithTarget:forwardClass];
}

/**
 消息轉(zhuǎn)發(fā)前先執(zhí)行此方法
 */
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    NSMethodSignature *signature = [super methodSignatureForSelector:aSelector];
    if (!signature) {
        //生成方法簽名
        signature = [forwardClass methodSignatureForSelector:aSelector];
    }
    return signature;
}
/**
 消息轉(zhuǎn)發(fā)調(diào)用
 */
- (void)forwardMethod {
    SomeClass *objec = [SomeClass new];

    SEL aselector = @selector(doSomethingElse);
    IMP aimp = [objec methodForSelector:aselector];
    void (*setter)(id, SEL) = (void (*)(id, SEL))aimp;
    setter(objec, aselector);
}

消息轉(zhuǎn)發(fā)有很多的用途遣鼓,比如:

  • 創(chuàng)建一個對象負(fù)責(zé)把消息轉(zhuǎn)發(fā)給一個由其它對象組成的響應(yīng)鏈,代理對象會在這個有其它對象組成的集合里尋找能夠處理該消息的對象重贺;
  • 把一個對象包在一個logger對象里骑祟,用來攔截或者紀(jì)錄一些有趣的消息調(diào)用;
  • 比如聲明類的屬性為dynamic气笙,使用自定義的方法來截取和取代由系統(tǒng)自動生成的getter和setter方法次企。

五、 Method Swizzling

Method Swizzling:被灌醉的方法
前面也曾提到過潜圃,object-c中是允許我們來對方法和方法的實現(xiàn)進(jìn)行自由組合的缸棵,原理也就是替換掉對應(yīng)方法的函數(shù)指針I(yè)MP,達(dá)到另類的方法實現(xiàn)
通過RunTime的Swizzling我們可以做很多事情:比如目前流行的無埋點統(tǒng)計中的事件圈選以及APM的頁面慢交互分析谭期,底層都是通過改變類的分發(fā)表里selector和實現(xiàn)之間的對應(yīng)關(guān)系堵第,來進(jìn)行信息捕獲的

#import "UIView+Hook.h"
#import <objc/runtime.h>

static NSHashTable *hashTable;

@implementation UIView (Hook)

+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        
        SEL originalSelector = @selector(init);
        SEL swizzledSelector = @selector(customInit);
        
        Method originalMethod = class_getInstanceMethod([self class], originalSelector);
        Method swizzledMethod = class_getInstanceMethod([self class], swizzledSelector);
        
        BOOL didAddMethod = class_addMethod([self class], originalSelector, method_getImplementation(swizzledMethod), method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
            class_replaceMethod([self class], swizzledSelector, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
        }else {
            method_exchangeImplementations(originalMethod, swizzledMethod);
        }
        
    });
}

- (instancetype)customInit {
    if (!hashTable) {
        hashTable = [NSHashTable hashTableWithOptions:NSPointerFunctionsWeakMemory];
    }
    [hashTable addObject:self];
    return [self customInit];
}

- (NSArray *)showRecords {
    NSLog(@"records==>%@",hashTable.allObjects);
    return hashTable.allObjects;
}

以上所說的主要還是針對RunTime的消息傳遞來說的,其實RunTime的用途還有很多隧出,一些基礎(chǔ)的class_copyIvarList踏志、class_copyMethodList、class_copyProtocolList胀瞪、class_copyPropertyList的使用在這里就不在多說了针余,有興趣的同學(xué)可以自行在網(wǎng)上查找。

GitHub上的Demo地址

常用三方庫推薦

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末赏廓,一起剝皮案震驚了整個濱河市涵紊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幔摸,老刑警劉巖摸柄,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異既忆,居然都是意外死亡驱负,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門患雇,熙熙樓的掌柜王于貴愁眉苦臉地迎上來跃脊,“玉大人,你說我怎么就攤上這事苛吱±沂酰” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵,是天一觀的道長绘雁。 經(jīng)常有香客問我橡疼,道長,這世上最難降的妖魔是什么庐舟? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任欣除,我火速辦了婚禮,結(jié)果婚禮上挪略,老公的妹妹穿的比我還像新娘历帚。我一直安慰自己,他們只是感情好杠娱,可當(dāng)我...
    茶點故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布挽牢。 她就那樣靜靜地躺著,像睡著了一般墨辛。 火紅的嫁衣襯著肌膚如雪卓研。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天睹簇,我揣著相機(jī)與錄音奏赘,去河邊找鬼。 笑死太惠,一個胖子當(dāng)著我的面吹牛磨淌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播凿渊,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼梁只,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了埃脏?” 一聲冷哼從身側(cè)響起搪锣,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎彩掐,沒想到半個月后构舟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡堵幽,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年狗超,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片朴下。...
    茶點故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡努咐,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出殴胧,到底是詐尸還是另有隱情渗稍,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站竿屹,受9級特大地震影響音五,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜羔沙,卻給世界環(huán)境...
    茶點故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望厨钻。 院中可真熱鬧扼雏,春花似錦、人聲如沸夯膀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽诱建。三九已至蝴蜓,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間俺猿,已是汗流浹背茎匠。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留押袍,地道東北人诵冒。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓,卻偏偏與公主長得像谊惭,于是被迫代替她去往敵國和親汽馋。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,916評論 2 344

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

  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,544評論 33 466
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉圈盔,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,682評論 0 9
  • 轉(zhuǎn)載:http://yulingtianxia.com/blog/2014/11/05/objective-c-r...
    F麥子閱讀 728評論 0 2
  • 參考鏈接: http://www.cnblogs.com/ioshe/p/5489086.html 簡介 Runt...
    樂樂的簡書閱讀 2,131評論 0 9
  • 打包資源文件 默認(rèn)資源文件不會被打包到j(luò)ar豹芯,需要在pom.xml中配置 打包源碼 查看jar包源碼時亂碼 ecl...
    瘋狂豬寶寶閱讀 196評論 1 1