Runtime簡(jiǎn)介以及常見(jiàn)的使用場(chǎng)景
? ?Runtime簡(jiǎn)稱運(yùn)行時(shí),是一套比較底層的純C語(yǔ)言的API,作為OC的核心宛乃,運(yùn)行時(shí)是一種面向?qū)ο蟮木幊陶Z(yǔ)言的運(yùn)行環(huán)境归形,其中最主要的是消息機(jī)制,Objective-C就是基于運(yùn)行時(shí)的嘁圈。
? ?所謂運(yùn)行時(shí),是指盡可能地把決定從編譯期推遲到運(yùn)行期,就是盡可能地做到動(dòng)態(tài).只是在運(yùn)行的時(shí)候才會(huì)去確定對(duì)象的類型和方法的.因此利用Runtime機(jī)制可以在程序運(yùn)行時(shí)動(dòng)態(tài)地修改類和對(duì)象中的所有屬性和方法殉农。
? ?對(duì)于C語(yǔ)言刀脏,函數(shù)的調(diào)用在編譯的時(shí)候會(huì)決定調(diào)用哪個(gè)函數(shù)。對(duì)于OC的函數(shù)超凳,屬于動(dòng)態(tài)調(diào)用過(guò)程愈污,在編譯的時(shí)候并不能決定真正調(diào)用哪個(gè)函數(shù)耀态,只有在真正運(yùn)行的時(shí)候才會(huì)根據(jù)函數(shù)的名稱找到對(duì)應(yīng)的函數(shù)來(lái)調(diào)用。
Objective-C從三種不同的層級(jí)上與Runtime系統(tǒng)進(jìn)行交互暂雹,分別是
①通過(guò)Objective-C源代碼首装;
②通過(guò)Foundation框架的NSObject類定義的方法;
③通過(guò)對(duì)Runtime函數(shù)的直接調(diào)用(需要導(dǎo)入#import )杭跪;
? ?大部分情況下只管寫OC代碼就行仙逻,因?yàn)镺C底層默認(rèn)實(shí)現(xiàn)Runtime,每一個(gè)OC的方法涧尿,底層必然有一個(gè)與之對(duì)應(yīng)的Runtime方法系奉。。
以下是Runtime的一些使用場(chǎng)景:
1.發(fā)送消息
方法調(diào)用的本質(zhì)姑廉,就是讓對(duì)象發(fā)送消息缺亮。objc_msgSend,只有對(duì)象才能發(fā)送消息
舉個(gè)簡(jiǎn)單的例子:如下
//(1).調(diào)用對(duì)象方法:
//創(chuàng)建Dog對(duì)象
Dog *dog = [[Dog alloc] init];
//調(diào)用對(duì)象方法
[dog run];
//調(diào)用對(duì)象本質(zhì):讓對(duì)象發(fā)送消息
objc_msgSend(dog, @selector(run));
//(2).調(diào)用類方法方式:
//有兩種
//第一種通過(guò)類名調(diào)用
[Dog run];
//第二種通過(guò)類對(duì)象調(diào)用
[[Dog class] run];
//用類名調(diào)用類方法,底層會(huì)自動(dòng)把類名轉(zhuǎn)換成類對(duì)象調(diào)用
//調(diào)用類方法本質(zhì):讓類對(duì)象發(fā)送消息
objc_msgSend([Dog class],@selector(run));
消息機(jī)制原理:對(duì)象根據(jù)方法編號(hào)SEL去映射表查找對(duì)應(yīng)的方法實(shí)現(xiàn)
2.動(dòng)態(tài)添加方法
開(kāi)發(fā)使用場(chǎng)景:如果一個(gè)類方法非常多桥言,加載類到內(nèi)存的時(shí)候也比較耗費(fèi)資源瞬内,需要給每個(gè)方法生成映射表,可以使用動(dòng)態(tài)給某個(gè)類限书,添加方法解決。經(jīng)典面試題:有沒(méi)有使用performSelector章咧,其實(shí)主要想問(wèn)你有沒(méi)有動(dòng)態(tài)添加過(guò)方法倦西。簡(jiǎn)單使用
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
Person *p = [[Person alloc] init];
//默認(rèn)person,沒(méi)有實(shí)現(xiàn)eat方法赁严,可以通過(guò)performSelector調(diào)用扰柠,但是會(huì)報(bào)錯(cuò)。
//動(dòng)態(tài)添加方法就不會(huì)報(bào)錯(cuò)
[p performSelector:@selector(eat)];
}
@end
@implementation Person
//void(*)()
//默認(rèn)方法都有兩個(gè)隱式參數(shù)疼约,
void eat(id self,SEL sel)
{
NSLog(@"%@%@",self,NSStringFromSelector(sel));
}
//當(dāng)一個(gè)對(duì)象調(diào)用未實(shí)現(xiàn)的方法卤档,會(huì)調(diào)用這個(gè)方法處理,并且會(huì)把對(duì)應(yīng)的方法列表傳過(guò)來(lái).
//剛好可以用來(lái)判斷,未實(shí)現(xiàn)的方法是不是我們想要?jiǎng)討B(tài)添加的方法
+(BOOL)resolveInstanceMethod:(SEL)sel
{ ?if (sel == @selector(eat)) {
//動(dòng)態(tài)添加eat方法
/**第一個(gè)參數(shù):給哪個(gè)類添加方法程剥,第二個(gè)參數(shù):添加方法的方法編號(hào)劝枣,第三個(gè)參數(shù):添加方法的函數(shù)實(shí)現(xiàn)(函數(shù)地址),第四個(gè)參數(shù):函數(shù)的類型织鲸,(返回值+參數(shù)類型) v:void @:對(duì)象->self :表示SEL->_cmd*/
class_addMethod(self, @selector(eat),eat, "v@:"); ?}
return [super resolveInstanceMethod:sel];
}
@end
注意:
? ?+ (BOOL)resolveInstanceMethod:(SEL)aSEL這個(gè)函數(shù)與forwardingTargetForSelector類似舔腾,都會(huì)在對(duì)象不能接受某個(gè)selector時(shí)觸發(fā),執(zhí)行起來(lái)略有差別搂擦。前者的目的主要在于給用戶一個(gè)機(jī)會(huì)來(lái)向該對(duì)象添加所需的selector稳诚,后者的目的在于允許用戶將selector轉(zhuǎn)發(fā)給另一個(gè)對(duì)象。另外觸發(fā)時(shí)機(jī)也不完全一樣瀑踢,該函數(shù)是個(gè)類函數(shù)扳还,在程序剛啟動(dòng)才避,界面尚未顯示出時(shí),就會(huì)被調(diào)用氨距。
? ?在類不能處理某個(gè)selector的情況下桑逝,如果類重載了該函數(shù),并使用class_addMethod添加了相應(yīng)的selector衔蹲,并返回YES肢娘,那么后面forwardingTargetForSelector就不會(huì)被調(diào)用,如果在該函數(shù)中沒(méi)有添加相應(yīng)的selector舆驶,那么不管返回什么橱健,后面都會(huì)繼續(xù)調(diào)用forwardingTargetForSelector,如果在forwardingTargetForSelector并未返回能接受該selector的對(duì)象沙廉,那么resolveInstanceMethod會(huì)再次被觸發(fā)拘荡,這一次,如果仍然不添加selector撬陵,程序就會(huì)報(bào)異常
3.運(yùn)行時(shí)關(guān)聯(lián)對(duì)象提高效率珊皿,給分類添加屬性。
使用的時(shí)候與懶加載的特點(diǎn)相似巨税,從`關(guān)聯(lián)對(duì)象`中獲取對(duì)象屬性蟋定,如果有,直接返回草添。
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loadingthe view, typically from a nib.
//給系統(tǒng)NSObject類動(dòng)態(tài)添加屬性name
NSObject *objc = [[NSObject alloc] init];
objc.name = @"旺財(cái)";
NSLog(@"%@",objc.name);
}
@end
//定義關(guān)聯(lián)的key
staticconst char *key = "name";
@implementation NSObject (Property)
-(NSString *)name
{
//根據(jù)關(guān)聯(lián)的key驶兜,獲取關(guān)聯(lián)的值。
NSString*name =objc_getAssociatedObject(self, key);
if(name! = nil) {
return name;
}}
-(void)setName:(NSString *)name
{//第一個(gè)參數(shù):給哪個(gè)對(duì)象添加關(guān)聯(lián)
//第二個(gè)參數(shù):關(guān)聯(lián)的key远寸,通過(guò)這個(gè)key獲取
//第三個(gè)參數(shù):關(guān)聯(lián)的value
//第四個(gè)參數(shù):關(guān)聯(lián)的策略
objc_setAssociatedObject(self, key, name,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end
4.使用運(yùn)行時(shí)字典轉(zhuǎn)模型
大體思路:利用運(yùn)行時(shí)抄淑,遍歷模型中所有屬性,根據(jù)模型的屬性名驰后,去字典中查找key肆资,取出對(duì)應(yīng)的值,給模型的屬性賦值灶芝。步驟:提供一個(gè)NSObject分類郑原,專門字典轉(zhuǎn)模型,以后所有模型都可以通過(guò)這個(gè)分類轉(zhuǎn)夜涕。(所有字典轉(zhuǎn)模型框架的核心算法)
創(chuàng)建NSObject的分類Runtime:
在.h中的類方法如下:
#import
@interface NSObject (Runtime)
///給定一個(gè)字典颤专,創(chuàng)建self類對(duì)應(yīng)的對(duì)象
///@param dict字典
///@return對(duì)象
+ (instancetype)hd_objWithDict:(NSDictionary*)dict;
///獲取類的屬性列表數(shù)組
///@return類的屬性列表數(shù)組
+ (NSArray*)hd_objProperties;
@end
在.m中的類方法如下:
//所有字典轉(zhuǎn)模型框架的核心算法
+ (instancetype)hd_objWithDict:(NSDictionary*)dict {
//實(shí)例化對(duì)象
id object = [[self alloc]init];
//使用字典,設(shè)置對(duì)象信息
1>獲得self的屬性列表
NSArray *proList = [self hd_objProperties];
2>遍歷字典
[dictenumerateKeysAndObjectsUsingBlock:^(id_Nonnullkey,id_Nonnullobj,BOOL*_Nonnullstop) {
NSLog(@"key %@ --- value %@", key,obj);
//
3>判斷key是否在proList中
if([proListcontainsObject:key]) {
//說(shuō)明屬性存在钠乏,可以使用`KVC`設(shè)置數(shù)值
[objectsetValue:objforKey:key];
}}];
return object;
}
constchar* kPropertiesListKey ="CZPropertiesListKey";
+ (NSArray*)hd_objProperties{
//從`關(guān)聯(lián)對(duì)象`中獲取對(duì)象屬性栖秕,如果有,直接返回晓避!
//獲取關(guān)聯(lián)對(duì)象-動(dòng)態(tài)添加的屬性
NSArray*ptyList =objc_getAssociatedObject(self,kPropertiesListKey);
if(ptyList != nil) {
return ptyList;
}
//調(diào)用運(yùn)行時(shí)方法簇捍,取得類的屬性列表
//Ivar成員變量
//Method方法
//Property屬性
//Protocol協(xié)議
//所有屬性的`數(shù)組`只壳,C語(yǔ)言中,數(shù)組的名字暑塑,就是指向第一個(gè)元素的地址
//retain/create/copy需要release吼句,最好option + click
unsigned int count =0;
objc_property_t *proList =class_copyPropertyList([self class], &count);
NSLog(@"屬性的數(shù)量%d", count);
//創(chuàng)建數(shù)組
NSMutableArray *arrayM = [NSMutableArrayarray];
//遍歷所有的屬性
for(unsignedinti = 0; i < count; i++) {
// 1.從數(shù)組中取得屬性
//C語(yǔ)言的結(jié)構(gòu)體指針,通常不需要`*`
objc_property_t ?pty = proList[i];
// 2.從pty中獲得屬性的名稱
const char *cName =property_getName(pty);
NSString *name = [NSStringstringWithCString:cNameencoding:NSUTF8StringEncoding];
//NSLog(@"%@",name);
// 3.屬性名稱添加到數(shù)組
[arrayM addObject:name];
}
//釋放數(shù)組
free(proList);
// ---
2.對(duì)象的屬性數(shù)組已經(jīng)獲取完畢事格,利用關(guān)聯(lián)對(duì)象惕艳,動(dòng)態(tài)添加屬性
/**
參數(shù)
1.對(duì)象self [OC中class也是一個(gè)特殊的對(duì)象]
2.動(dòng)態(tài)添加屬性的key,獲取值的時(shí)候使用
3.動(dòng)態(tài)添加的屬性值
4.對(duì)象的引用關(guān)系
*/
objc_setAssociatedObject(self,kPropertiesListKey, arrayM.copy,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return arrayM.copy;
}
注意:必須保證驹愚,模型中的屬性和字典中的key一一對(duì)應(yīng)远搪。如果不一致,就會(huì)調(diào)用[ setValue:forUndefinedKey:]逢捺,報(bào)key找不到的錯(cuò)谁鳍。
分析:模型中的屬性和字典的key不一一對(duì)應(yīng),系統(tǒng)就會(huì)調(diào)用setValue:forUndefinedKey:報(bào)錯(cuò)劫瞳。
解決:重寫對(duì)象的setValue:forUndefinedKey:,把系統(tǒng)的方法覆蓋倘潜,
就能繼續(xù)使用KVC,字典轉(zhuǎn)模型了志于。
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
? ?通過(guò)運(yùn)行時(shí)字典轉(zhuǎn)模型的好處在于寫在NSObject的分類中涮因,和類的關(guān)聯(lián)性不強(qiáng)對(duì)類解耦,以后再做字典轉(zhuǎn)模型的時(shí)候只需要把這個(gè)分類往任何一個(gè)程序中一拖伺绽,程序中的對(duì)象就都具備了這個(gè)字典轉(zhuǎn)模型的方法蕊退。
5.交叉方法(黑魔法)
? ?開(kāi)發(fā)使用場(chǎng)景:系統(tǒng)自帶的方法功能不夠,給系統(tǒng)自帶的方法擴(kuò)展一些功能憔恳,并且保持原有的功能。方式一:繼承系統(tǒng)的類净蚤,重寫方法.方式二:使用runtime,交換方法.
? ?Runtime在AFN中的使用細(xì)節(jié):在AFN的NSURLSessionMangerM方法里面第363行寫了一個(gè)靜態(tài)的內(nèi)聯(lián)函數(shù)钥组,做了一個(gè)交叉方法,交叉的是af_resume和resume方法今瀑,這樣的話程梦,可以在發(fā)送網(wǎng)絡(luò)之前發(fā)起一個(gè)通知,能接受到任何一個(gè)網(wǎng)絡(luò)請(qǐng)求的事件的變化橘荠。
@implementation ViewController
-(void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loadingthe view, typically from a nib.
//需求:給imageNamed方法提供功能屿附,每次加載圖片就判斷下圖片是否加載成功。
//步驟一:先搞個(gè)分類哥童,定義一個(gè)能加載圖片并且能打印的方法+ (instancetype)imageWithName:(NSString *)name;
//步驟二:交換imageNamed和imageWithName的實(shí)現(xiàn)挺份,就能調(diào)用imageWithName,間接調(diào)用imageWithName的實(shí)現(xiàn)贮懈。
UIImage *image = [UIImageimageNamed:@"123"];
}
@end
@implementation UIImage (Image)
//加載分類到內(nèi)存的時(shí)候調(diào)用
+(void)load
{
//交換方法
//獲取imageWithName方法地址
Method imageWithName =class_getClassMethod(self, @selector(imageWithName:));
//獲取imageWithName方法地址
Method imageName =class_getClassMethod(self, @selector(imageNamed:));
//交換方法地址匀泊,相當(dāng)于交換實(shí)現(xiàn)方式
method_exchangeImplementations(imageWithName,imageName);
}
//既能加載圖片又能打印
+(instancetype)imageWithName:(NSString *)name
{//這里調(diào)用imageWithName优训,相當(dāng)于調(diào)用imageName
UIImage *image = [self imageWithName:name];
if (image == nil) {
NSLog(@"加載空的圖片");}
return image;
}
@end