明確幾個(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 Calls
為YES
,否則會(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í)候記得來嘲諷我=η薄!薪伏!