Runtime奇技淫巧之objc_msgSend

明確幾個(gè)概念:
  • 方法調(diào)用的本質(zhì)烤送,就是讓對(duì)象發(fā)送消息。
  • NSNumber *isEnough = [person eatEnough:@(20)];該方法的調(diào)用時(shí)會(huì)轉(zhuǎn)化為objc_msgSend進(jìn)行調(diào)用,eatEnough:以及后面的參數(shù)形成了一個(gè)的消息。
  • 看一下objc_msgSend這個(gè)方法的系統(tǒng)定義,void objc_msgSend(void /* id self, SEL op, ... */ )凄硼,消息發(fā)送最少包括兩個(gè)參數(shù),一個(gè)id類型捷沸,一個(gè)SEL類型摊沉。
  • 當(dāng)對(duì)象對(duì)應(yīng)方法列表中查詢不到這個(gè)方法時(shí),并且不做任何處理的情況下痒给,系統(tǒng)會(huì)拋出異常说墨。(??:可以轉(zhuǎn)發(fā)這個(gè)消息,來消除異常)
(對(duì)于基礎(chǔ)類型不了解的同志們可以查看 這里

消息發(fā)送到最后對(duì)應(yīng)到方法實(shí)現(xiàn)的查找過程:SEL -> Method -> IMP苍柏,最后利用IMP函數(shù)指針調(diào)用方法尼斧,流程圖如下:


之前說過關(guān)于元類的知識(shí),上圖也同樣反映出试吁,類方法和實(shí)例方法處理起來有些許的差別棺棵。

下面我們來說一下具體的用法

  • 應(yīng)用場(chǎng)景:隨意調(diào)用所有的方法,并且為實(shí)現(xiàn)不會(huì)崩潰。(是不是超牛逼)
    在我們開發(fā)中烛恤,有些方法我們不愿在.h中公開母怜,導(dǎo)入某些頭文件一不注意就會(huì)循環(huán)引用。并且很多時(shí)候會(huì)造成代碼的耦合性上升缚柏,任何功能的SDK特性才是一個(gè)程序員代碼實(shí)力的象征苹熏。

?? 使用objc_msgSend這個(gè)方法的前提是你需要在BuildSettings中設(shè)置Enable Strict Checking of objc_msgSend CallsYES,否則會(huì)報(bào)錯(cuò)船惨。
?? 不要以為導(dǎo)入頭文件#import <objc/runtime.h>就萬事大吉了柜裸,同時(shí)記得導(dǎo)入#import <objc/message.h>

首先粱锐,創(chuàng)建一個(gè)Person類,.h中不公開任何方法 扛邑,在.m中實(shí)現(xiàn)如下方法:

/**
 *吃飯實(shí)例方法  無參數(shù) 無返回值
 */
-(void)eat{
    NSLog(@"eat_person");
}
/**
 *吃飯類方法  無參數(shù)  無返回值
 */
+(void)eat{
    NSLog(@"eat_class");
}

/**
 *睡覺實(shí)例方法  有參數(shù) 無返回值
 */
-(void)sleepOfHour:(NSNumber*)hour{
    NSLog(@"sleep_person_%@",hour);
}
/**
 *睡覺類方法  有參數(shù) 無返回值
 */
+(void)sleepOfHour:(NSNumber*)hour{
    NSLog(@"sleep_class_%@",hour);
}


/**
 *是否吃飽實(shí)例方法 有參數(shù) 有返回值
 */
-(NSNumber*)eatEnough:(NSNumber*)breadCount{
    NSLog(@"breadCount_person_%@",breadCount);
    return @(1);
}

/**
 *是否吃飽類方法 有參數(shù) 有返回值
 */
+(NSNumber*)eatEnough:(NSNumber*)breadCount{
    NSLog(@"breadCount_class_%@",breadCount);
    return @(0);
}

runtime方法封裝:

.h
#pragma mark - objc_msgSend (限制五個(gè)個(gè)參數(shù)及以內(nèi))
/**
 *實(shí)例方法
 */
+(id)msgSendToObj:(id)obj Selector:(SEL)selector Prarms:(NSArray*)params NeedReturn:(BOOL)needReturn;
/**
 *類方法
 */
+(id)msgSendToClass:(Class)YSClass Selector:(SEL)selector Prarms:(NSArray*)params NeedReturn:(BOOL)needReturn;
-----------------------------------------------------------
.m
#pragma mark - objc_msgSend

+(id)msgSendToObj:(id)obj Selector:(SEL)selector Prarms:(NSArray*)params NeedReturn:(BOOL)needReturn{
    id returnValue = nil;
    NSInteger paramsCount = params.count;
    NSMutableArray *params_M = [NSMutableArray arrayWithArray:params];
    //
    while (params_M.count < 5) {
        [params_M addObject:@""];
    }
    params = params_M;
    //
    if (obj && selector && [obj respondsToSelector:selector] && paramsCount <= 5) {
        if (needReturn) {
            returnValue = ((id (*) (id, SEL, id, id , id, id, id)) (void *)objc_msgSend) (obj, selector, params[0], params[1], params[2], params[3], params[4]);
        }else{
            ((void (*) (id, SEL, id, id , id, id, id)) (void *)objc_msgSend)(obj, selector,  params[0], params[1], params[2], params[3], params[4]);
        }
    }
    return returnValue;
}

+(id)msgSendToClass:(Class)YSClass Selector:(SEL)selector Prarms:(NSArray*)params NeedReturn:(BOOL)needReturn{
    id returnValue = nil;
    NSInteger paramsCount = params.count;
    NSMutableArray *params_M = [NSMutableArray arrayWithArray:params];
    //
    while (params_M.count < 5) {
        [params_M addObject:@""];
    }
    params = params_M;
    //
    Method method = class_getClassMethod(YSClass, selector);
    //
    if (YSClass && selector && (int)method != 0 && paramsCount <= 5) {
        if (needReturn) {
            returnValue = ((id (*) (id, SEL, id, id , id, id, id)) (void *)objc_msgSend) (YSClass, selector, params[0], params[1], params[2], params[3], params[4]);
        }else{
            ((void (*) (id, SEL, id, id , id, id, id)) (void *)objc_msgSend)(YSClass, selector,  params[0], params[1], params[2], params[3], params[4]);
        }
    }
    return returnValue;
}

在工程中調(diào)用方法如下:

Person* person = [[Person alloc] init];
[NSObject msgSendToObj:person Selector:NSSelectorFromString(@"eat") Prarms:nil NeedReturn:NO];
[NSObject msgSendToClass:NSClassFromString(@"Person") Selector:NSSelectorFromString(@"eat") Prarms:nil NeedReturn:NO];
//
[NSObject msgSendToObj:person Selector:NSSelectorFromString(@"sleepOfHour:") Prarms:@[@(10)] NeedReturn:NO];
[NSObject msgSendToClass:NSClassFromString(@"Person") Selector:NSSelectorFromString(@"sleepOfHour:") Prarms:@[@(8)] NeedReturn:NO];
//
id objR = [NSObject msgSendToObj:person Selector:NSSelectorFromString(@"eatEnough:") Prarms:@[@(100)] NeedReturn:YES];
id classR = [NSObject msgSendToClass:NSClassFromString(@"Person") Selector:NSSelectorFromString(@"eatEnough:") Prarms:@[@(100)] NeedReturn:YES];
NSLog(@"objR_%@ classR_%@", objR, classR);

打印結(jié)果:

RuntimeSkill[2126:215490] eat_person
RuntimeSkill[2126:215490] eat_class
RuntimeSkill[2126:215490] sleep_person_10
RuntimeSkill[2126:215490] sleep_class_8
RuntimeSkill[2126:215490] breadCount_person_100
RuntimeSkill[2126:215490] breadCount_class_100
RuntimeSkill[2126:215490] objR_1 classR_0

通過上面的講解和代碼怜浅,我姑且認(rèn)為你了解了消息發(fā)送的原理以及實(shí)用技巧,但是有限個(gè)參數(shù)的限制真的是頭疼蔬崩,后來發(fā)現(xiàn)了一個(gè)東西NSInvocation恶座。于是擴(kuò)展出了下面一套不限參數(shù)的方法調(diào)用機(jī)制:

.h
#pragma mark - NSInvocation (不限參數(shù))
/**
 *實(shí)例方法
 */
+(id)msgSendToObj_invocation:(id)obj Selector:(SEL)selector Prarms:(NSArray*)params NeedReturn:(BOOL)needReturn;
/**
 *類方法
 */
+(id)msgSendToClass_invocation:(Class)YSClass Selector:(SEL)selector Prarms:(NSArray*)params NeedReturn:(BOOL)needReturn;
------------------------------------------------------------------
.m
#pragma mark - NSInvocation (不限參數(shù))
+(id)msgSendToObj_invocation:(id)obj Selector:(SEL)selector Prarms:(NSArray*)params NeedReturn:(BOOL)needReturn{
    id value = nil;
    if (obj && selector) {
        if ([obj respondsToSelector:selector]) {
            NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[[obj class] instanceMethodSignatureForSelector:selector]];
            [invocation setSelector:selector];
            [invocation setTarget:obj];
            for (int i=0; i < params.count; i++) {
                id ref = params[i];
                [invocation setArgument:&ref atIndex:2+i];
            }
            [invocation invoke];//perform 的傳參表達(dá)方式
            if(needReturn){//獲得返回值
                void *vvl = nil;
                [invocation getReturnValue:&vvl];
                value = (__bridge id)vvl;
            }
        }else{
#ifdef _YSDebugLog
            NSLog(@"msgToTarget unRespondsToSelector -->>> %@ %@",obj,menthed);
#endif
        }
    }
    return value;
}
+(id)msgSendToClass_invocation:(Class)YSClass Selector:(SEL)selector Prarms:(NSArray*)params NeedReturn:(BOOL)needReturn{
    id value = nil;
    Method method = class_getClassMethod(YSClass, selector);
    if((int)method != 0){
        NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[YSClass methodSignatureForSelector:selector]];
        [invocation setSelector:selector];
        [invocation setTarget:YSClass];
        for (int i=0; i < params.count; i++) {
            id ref = params[i];
            [invocation setArgument:&ref atIndex:2+i];
        }
        [invocation invoke];//perform 的傳參表達(dá)方式
        if(needReturn){//獲得返回值
            void *vvl = nil;
            [invocation getReturnValue:&vvl];
            value = (__bridge id)vvl;
        }
    }else{
#ifdef _YSDebugLog
        NSLog(@"msgToClass unRespondsToSelector -->>> %@ %@",YSClass,menthed);
#endif
    }
    return value;
}

調(diào)用方法:

[NSObject msgSendToObj_invocation:person Selector:NSSelectorFromString(@"eat") Prarms:nil NeedReturn:NO];
[NSObject msgSendToClass_invocation:NSClassFromString(@"Person") Selector:NSSelectorFromString(@"eat") Prarms:nil NeedReturn:NO];
//
[NSObject msgSendToObj_invocation:person Selector:NSSelectorFromString(@"sleepOfHour:") Prarms:@[@(10)] NeedReturn:NO];
[NSObject msgSendToClass_invocation:NSClassFromString(@"Person") Selector:NSSelectorFromString(@"sleepOfHour:") Prarms:@[@(8)] NeedReturn:NO];
//
id objR_in = [NSObject msgSendToObj_invocation:person Selector:NSSelectorFromString(@"eatEnough:") Prarms:@[@(100)] NeedReturn:YES];
id classR_in = [NSObject msgSendToClass_invocation:NSClassFromString(@"Person") Selector:NSSelectorFromString(@"eatEnough:") Prarms:@[@(100)] NeedReturn:YES];
NSLog(@"objR_in_%@ classR_in_%@", objR_in, classR_in);

打印結(jié)果:

RuntimeSkill[2150:221988] eat_person
RuntimeSkill[2150:221988] eat_class
RuntimeSkill[2150:221988] sleep_person_10
RuntimeSkill[2150:221988] sleep_class_8
RuntimeSkill[2150:221988] breadCount_person_100
RuntimeSkill[2150:221988] breadCount_class_100
RuntimeSkill[2150:221988] objR_in_1 classR_in_0

有了這個(gè)原則,你就可以肆無忌憚的調(diào)用管他公布公不公開的方法沥阳,換一個(gè)角度跨琳,也就是可以通過get方法獲取.m中未公開屬性值。方法是這個(gè)方法桐罕,但每個(gè)人可能都有不同的見解脉让,拋磚引玉,你可能看了之后能找到更牛逼的實(shí)用領(lǐng)域功炮,到時(shí)候記得來嘲諷我=η薄!薪伏!

傳送門 : Runtime實(shí)用技巧(不扯淡其馏,不套路)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末锋叨,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌儿倒,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件盐须,死亡現(xiàn)場(chǎng)離奇詭異反症,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)朴爬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門即寒,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事母赵∫菥簦” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵凹嘲,是天一觀的道長(zhǎng)师倔。 經(jīng)常有香客問我,道長(zhǎng)周蹭,這世上最難降的妖魔是什么趋艘? 我笑而不...
    開封第一講書人閱讀 55,252評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮凶朗,結(jié)果婚禮上瓷胧,老公的妹妹穿的比我還像新娘。我一直安慰自己棚愤,他們只是感情好搓萧,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,253評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宛畦,像睡著了一般瘸洛。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上次和,一...
    開封第一講書人閱讀 49,031評(píng)論 1 285
  • 那天反肋,我揣著相機(jī)與錄音,去河邊找鬼踏施。 笑死石蔗,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的读规。 我是一名探鬼主播抓督,決...
    沈念sama閱讀 38,340評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼束亏!你這毒婦竟也來了铃在?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤碍遍,失蹤者是張志新(化名)和其女友劉穎定铜,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怕敬,經(jīng)...
    沈念sama閱讀 43,466評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡揣炕,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,937評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了东跪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片畸陡。...
    茶點(diǎn)故事閱讀 38,039評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鹰溜,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出丁恭,到底是詐尸還是另有隱情曹动,我是刑警寧澤,帶...
    沈念sama閱讀 33,701評(píng)論 4 323
  • 正文 年R本政府宣布牲览,位于F島的核電站墓陈,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏第献。R本人自食惡果不足惜贡必,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,254評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望庸毫。 院中可真熱鬧仔拟,春花似錦、人聲如沸飒赃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽盒揉。三九已至,卻和暖如春兑徘,著一層夾襖步出監(jiān)牢的瞬間刚盈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工挂脑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留藕漱,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,497評(píng)論 2 354
  • 正文 我出身青樓崭闲,卻偏偏與公主長(zhǎng)得像肋联,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子刁俭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,786評(píng)論 2 345

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

  • objc_getAssociatedObject返回與給定鍵的特定對(duì)象關(guān)聯(lián)的值橄仍。ID objc_getAssoci...
    有一種再見叫青春閱讀 1,564評(píng)論 0 7
  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 1,679評(píng)論 0 9
  • 參數(shù)自一個(gè)指針牍戚,指向類的要接收消息的實(shí)例侮繁。 OP在處理該信息的方法的選擇。 ......可變參數(shù)列表包含參數(shù)的方法...
    reallychao閱讀 788評(píng)論 0 0
  • 2017.6.1六一兒童節(jié)如孝,我們?cè)诎耸程镒x書慶祝宪哩。2016.12.9開始學(xué)習(xí)《業(yè)力管理》,昨天第晰,學(xué)習(xí)圓滿了锁孟,踐行不...
    孫蕾蔣楊閱讀 241評(píng)論 0 1
  • 文/南陌花開 《江山夢(mèng)之謀妃天下》目錄 上一章 江山夢(mèng)之謀妃天下13 第十四章 春日融融 自那日與顧琛出門一游后...
    南陌花開閱讀 230評(píng)論 0 2