RunTime
- RunTime:簡稱運行時,OC就是
運行時機制,其中最主要的就是消息機制 - 對于C語言,
函數(shù)的調(diào)用在編譯階段就會決定調(diào)用哪個函數(shù) - 對于OC,屬于
動態(tài)調(diào)用語言,在編譯階段并不會決定真正調(diào)用哪個函數(shù),只有在運行的時候才會根據(jù)函數(shù)的名稱取找到對應(yīng)的函數(shù)來調(diào)用 - 比如說,在OC中,我們只是聲明一個方法,在調(diào)用時并不會報錯,而是在運行時才會報錯
-在c語言中,我們調(diào)用未實現(xiàn)的函數(shù)就會報錯
RunTime的作用
- 方法調(diào)用的本質(zhì)讓對象發(fā)送消息
-消息機制的簡單使用
- 1.先自定義一個DKPerson類,定義一個方法,在main.m中調(diào)用此方法
DKPerson *p = [DKPerson alloc]; p = [p init]; [p run];
- 2.通過終端將main.m文件編譯成c++語言,在終端中輸入clang -rewrite-objc main.m會生成一個main.cpp文件,在文件中查找
DKPerson *p = ((DKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("DKPerson"), sel_registerName("alloc")); p = ((DKPerson *(*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("init")); ((void (*)(id, SEL))(void *)objc_msgSend)((id)p, sel_registerName("run"));
- 3.如果我們想自己來使用RunTime來發(fā)送消息,那么我們需要導(dǎo)入頭文件<objc/message.h>,但是在Xcode6之后蘋果不希望我們在使用底層的方法,所以當我們使用objc_msgSend()來發(fā)送消息時不再會提示參數(shù),但是我們可以通過項目中的BuildSettings搜索msg來設(shè)置不需要嚴肅檢查消息機制調(diào)用,那么這樣就會有參數(shù)的提示
//DKPerson *p = [DKPerson alloc]; DKPerson *p = objc_msgSend(objc_getClass("DKPerson"), sel_registerName("alloc")); //p = [p init]; p = objc_msgSend(p, sel_registerName("init")); //[p run]; objc_msgSend(p, sel_registerName("run"));
發(fā)送消息的本質(zhì)
- 先介紹一下OC的基本知識:
- 1.OC中的對象方法都保存在類對象的方法列表中
- 2.OC中的類方法都保存在源對象(meta)的方法列表中
- 3.OC中所有的方法都會轉(zhuǎn)化成函數(shù),保存在內(nèi)存的方法區(qū)中
- OC方法的調(diào)用流程
- 1.當我們給一個對象發(fā)送消息,那么會根據(jù)對象中的isa指針找到對應(yīng)類對象.
- 2.根據(jù)SEL方法編號在類對象的方法列表中查找對應(yīng)的方法
- 3.實現(xiàn)方法
RunTime的應(yīng)用場景
交換方法
- 應(yīng)用場景:當系統(tǒng)方法不能滿足我們的需求時,需要我們給系統(tǒng)的方法添加一些特有的功能時,我們就可以使用RunTime來解決.
- 舉個栗子: 需要給imageNamed:方法添加一個提示功能,提示開發(fā)人員圖片是否加載成功
- 解決的思路:
- 1.首先我們會想到自定義一個繼承自
UIImage的類,自定義一個方法,在該方法中來加載圖片,然后實現(xiàn)提示的功能,但是,這樣做我們需要在每次使用的時候取導(dǎo)入頭文件,每次調(diào)用該方法都會先去加載系統(tǒng)的方法,在做判斷.所以不理想. - 2.既然這樣,那我就會去給
UIimage添加一個分類,需要注意的是,在分類中不能重寫系統(tǒng)的imageNamed:方法,會覆蓋系統(tǒng)原有的方法,所以這里我們自定義一個dk_imageNamed:方法,在該方法中來實現(xiàn)加載圖片,添加功能
+(UIImage *)dk_imageNamed:(NSString *)name - 1.首先我們會想到自定義一個繼承自
{
UIImage *image = [UIImage imageNamed:name];//注意,如果交換了方法這里不能再使用系統(tǒng)的方法來獲取圖片,否則會造成死循環(huán)
if (image) {
NSLog(@"圖片存在");
}else{
NSLog(@"圖片不存在");
}
return image;
}
- 3.但是這樣還是會每次需要導(dǎo)入分類的頭文件,每次調(diào)用自定義方法都會先去加載系統(tǒng)的圖片,添加功能,所以還是不理想,這里就需要使用RunTime來交換方法 - 那么我們在什么時候來交換方法呢?難道是在每次調(diào)用方法的時候來交換?肯定不現(xiàn)實,所以這里需要在第一次把方法加載到內(nèi)存的時候來交換方法 - 4.在`+(void)load`方法中交換方法,`load`方法的調(diào)用比`main`還早,是當加載類到內(nèi)存的時候就會調(diào)用,只會調(diào)用一次,所以我們選擇在這里交換方法
+(void)load
{
//1.獲取系統(tǒng)的方法
Method imageNamedMethod = class_getClassMethod(self, sel_registerName("imageNamed:"));
//2.獲取自定義的方法
Method dk_imageNamedMethod = class_getClassMethod(self, sel_registerName("dk_imageNamed:"));
//3.交換方法
method_exchangeImplementations(imageNamedMethod, dk_imageNamedMethod);
}
```
- 5.程序?qū)懙竭@里,那么我們在外界調(diào)用系統(tǒng)的imageNamed:方法就會調(diào)用我們自定義的dk_imageNamed:方法中的實現(xiàn),注意當前我們自定義方法中使用過imageNamed:來獲取圖片的,而我們已經(jīng)交換了方法,所以這里不能再使用imageNamed:來獲取圖片,而是需要使用dk_imageNamed:來獲取圖片

使用RunTime動態(tài)添加方法
-
為什么我們需要動態(tài)添加方法?
- OC提倡的是懶加載思想,等到用到的時候才去調(diào)用
- 舉個列子:比如我們的微博有一個會員的機制,會員會帶有特有的方法,如果一個不是會員(比如說窮B的我)的用戶,那么這個用戶就沒必要去添加會員的方法了.
-
如何來動態(tài)添加方法? 還是以一個例子來說明
- 添加一個
DKPerson類,聲明一個run對象方法.
- 添加一個
- 在外界,創(chuàng)建一個
DKPerson對象,調(diào)用run方法.當我們動態(tài)添加方法的時候需要通過performSelector方法來調(diào)用
- 在外界,創(chuàng)建一個
DKPerson *p = [[DKPerson alloc]init]; //當我們動態(tài)添加方法的時候使用performSelector...調(diào)用方法 [p performSelector:@selector(run)];3.我們在
DKPerson類中只是聲明了一個run方法.在外界調(diào)用了一個只有聲明,沒有實現(xiàn)的方法,就會調(diào)用resolveInstanceMethod(對應(yīng)對象方法),resolveClassMethod(對應(yīng)類方法),這兩個方法就是當我們調(diào)用了一個只有聲明沒有實現(xiàn)的方法時,系統(tǒng)就通過此方法來處理,并且把方法列表傳遞過來4.實現(xiàn)
resolveInstanceMethod方法,判斷當前傳遞進來的方法是否是我們動態(tài)添加的方法
+(BOOL)resolveInstanceMethod:(SEL)sel
{
if (sel == @selector(run)) {//如果當前方法是我們動態(tài)添加的方法
//添加動態(tài)方法
/*
參數(shù)一:__unsafe_unretained Class cls 給誰添加方法
參數(shù)二:SEL name 需要添加的方法
參數(shù)三:IMP imp 函數(shù)入口
參數(shù)四: const char *types 函數(shù)的類型 ,可以去文檔中查找,這里偷懶直接使用NULL
*/
class_addMethod(self, @selector(run), text, NULL);
}
return [super resolveClassMethod:sel];
}
- 5.定義函數(shù)
//每個方法都有2個隱士參數(shù)
void text(id self, SEL sel){
NSLog(@"動態(tài)添加方法");
}
```
使用RunTime動態(tài)添加屬性
- 應(yīng)用場景:當我們需要給系統(tǒng)的類添加屬性的時候,就需要通過動態(tài)添加屬性的方式來添加
- 思路:
- 1.當需要給系統(tǒng)的類添加屬性,那么我首先就會想到給系統(tǒng)類添加一個分類,在分類中聲明一個屬性,假設(shè)聲明一個name屬性
@property NSString *name; - 2.在分類中使用
@property添加屬性,只會生成對應(yīng)的setter和getter方法的聲明,并不會生成set和get方法的實現(xiàn)和帶_成員變量 - 3.所以我們需要重寫set和get方法,但是我們無法通過_成員變量來保存數(shù)據(jù).所以這里我們就定義一個全局的變量
static NSString *_name;,通過它來保存數(shù)據(jù) - 4.如果僅僅做到這里,我們并沒有使用到動態(tài)添加屬性?那么我們需要來分析下這個全局變量了,我們定義了這個全局變量僅僅是做到了數(shù)據(jù)的保存和傳遞,和我們的分類沒有半毛錢的關(guān)系.所以通過這種方式是無法做到屬性和分類之間的關(guān)聯(lián)的
- 5.這個時候我們就需要使用到動態(tài)添加屬性
- 1.在set方法中,我們需要給分類設(shè)置屬性,首先需要導(dǎo)入
<objc/message.h>,其次通過方法添加屬性
- 1.在set方法中,我們需要給分類設(shè)置屬性,首先需要導(dǎo)入
/*
參數(shù)一:object 給誰添加屬性
參數(shù)二:key 讓對象和屬性通過key產(chǎn)生關(guān)聯(lián)
參數(shù)三:value 添加的屬性
參數(shù)四:policy 策略
*/
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
- 2.在get方法中,獲取到添加的屬性
//通過鍵值,獲取和對象產(chǎn)生關(guān)聯(lián)的屬性
return objc_getAssociatedObject(self, @"name");
``` - 1.當需要給系統(tǒng)的類添加屬性,那么我首先就會想到給系統(tǒng)類添加一個分類,在分類中聲明一個屬性,假設(shè)聲明一個name屬性
使用RunTime來實現(xiàn)字典轉(zhuǎn)模型
-
介紹:
- 字典轉(zhuǎn)模型,最先想到的是利用KVC來實現(xiàn),KVC來字典轉(zhuǎn)模型,需要模型的屬性與字典的鍵值一一對應(yīng),當讓也不是絕對的,可以有解決的辦法.
- RunTime來字典轉(zhuǎn)模型,是根據(jù)模型的屬性去字典中查找有沒有與之對應(yīng)的鍵值,正好與KVC相反
-
RunTime來實現(xiàn)字典轉(zhuǎn)模型
- 1.首先,考慮到復(fù)用將此功能單獨的抽取出來,所以給NSObject添加一個分類,定義一個字典轉(zhuǎn)模型的方法
+(instancetype)dk_objectWithDic:(NSDictionary *)dic - 2.實例化模型對象
id obj = [[self alloc]init]; - 3.獲取到模型中的成員變量列表
/* 參數(shù)一:獲取誰的成員變量列表 參數(shù)二:返回的成員變量個數(shù) */ unsigned int count = 0; Ivar *ivarList = class_copyIvarList(self, &count); //返回了一個指向成員變量列表數(shù)組首元素- 4.遍歷獲取成員變量
Ivar ivar = ivarList[i]; //獲取成員變量名,返回值是一個C語言的字符串,這里需要轉(zhuǎn)換成OC字符串 NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];- 5.根據(jù)打印的值發(fā)現(xiàn),這里獲得的字符串是帶下劃線的,這里需要裁剪字符串,不然獲取準確的key
- 6.根據(jù)鍵值獲取字典中的值
id value = [dic objectForKey:propertyName];- 7.有了key 有了值,那么就只需要賦值即可,但是如果字典中嵌套字典呢?那么需要做二次字典轉(zhuǎn)模型
- 8.判斷類型,如果是字典,那么繼續(xù)轉(zhuǎn)換模型
if ([value isKindOfClass:[NSDictionary class]]){ //繼續(xù)轉(zhuǎn)換模型 }- 9.獲取成員變量的類型
//獲取成員變量的類型 NSString *className = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];- 10.根據(jù)打印的結(jié)果發(fā)現(xiàn)需要對這里的字符串進行處理
className = [className stringByReplacingOccurrencesOfString:@"@" withString:@""]; className = [className stringByReplacingOccurrencesOfString:@"\"" withString:@""];- 11.將字符串轉(zhuǎn)換成類型,字典轉(zhuǎn)化成子模型
Class subitem = NSClassFromString(className); value = [subitem dk_objectWithDic:value];- 12.最后直接復(fù)制
[obj setValue:value forKey:propertyName];+(instancetype)dk_objectWithDic:(NSDictionary *)dic - 1.首先,考慮到復(fù)用將此功能單獨的抽取出來,所以給NSObject添加一個分類,定義一個字典轉(zhuǎn)模型的方法
{
//1.實例化模型對象
id obj = [[self alloc]init];
//2.獲取模型的成員變量列表
/*
參數(shù)一:獲取誰的成員變量列表
參數(shù)二:返回的成員變量個數(shù)
*/
unsigned int count = 0;
Ivar *ivarList = class_copyIvarList(self, &count);
//遍歷賦值
for (NSInteger i = 0; i < count; i ++) {
//3.獲取成員變量
Ivar ivar = ivarList[i];
//4.獲取成員變量名,返回值是一個C語言的字符串,這里需要轉(zhuǎn)換成OC字符串
NSString *propertyName = [NSString stringWithUTF8String:ivar_getName(ivar)];
//獲取成員變量的類型
NSString *className = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
//這里返回的是帶下劃線的成員變量名,所以需要裁剪,獲取key
propertyName = [propertyName substringFromIndex:1];
//5.根據(jù)成員變量名取字典找尋找對應(yīng)的鍵值
id value = [dic objectForKey:propertyName];
//6.判斷當前value的類型,如果是字典類型,那么需要再次轉(zhuǎn)換模型
if ([value isKindOfClass:[NSDictionary class]]) {
//截取字符串
className = [className stringByReplacingOccurrencesOfString:@"@" withString:@""];
className = [className stringByReplacingOccurrencesOfString:@""" withString:@""];
//轉(zhuǎn)化
Class subitem = NSClassFromString(className);
value = [subitem dk_objectWithDic:value];
}
//賦值
[obj setValue:value forKey:propertyName];
}
return obj;
}
```