RunTime的介紹和基本使用

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
    

{
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:來獲取圖片

Snip20160416_4.png
使用RunTime動態(tài)添加方法
  • 為什么我們需要動態(tài)添加方法?

      1. OC提倡的是懶加載思想,等到用到的時候才去調(diào)用
      1. 舉個列子:比如我們的微博有一個會員的機制,會員會帶有特有的方法,如果一個不是會員(比如說窮B的我)的用戶,那么這個用戶就沒必要去添加會員的方法了.
  • 如何來動態(tài)添加方法? 還是以一個例子來說明

      1. 添加一個DKPerson類,聲明一個run對象方法.
      1. 在外界,創(chuàng)建一個DKPerson對象,調(diào)用run方法.當我們動態(tài)添加方法的時候需要通過performSelector方法來調(diào)用
     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>,其次通過方法添加屬性
    //動態(tài)添加屬性
    /*
    參數(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");
    ```
使用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.實例化模型對象
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;
}
```

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 轉(zhuǎn)至元數(shù)據(jù)結(jié)尾創(chuàng)建: 董瀟偉,最新修改于: 十二月 23, 2016 轉(zhuǎn)至元數(shù)據(jù)起始第一章:isa和Class一....
    40c0490e5268閱讀 2,058評論 0 9
  • 對于從事 iOS 開發(fā)人員來說,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,812評論 7 64
  • runtime 和 runloop 作為一個程序員進階是必須的,也是非常重要的, 在面試過程中是經(jīng)常會被問到的, ...
    made_China閱讀 1,273評論 0 7
  • 我們常常會聽說 Objective-C 是一門動態(tài)語言,那么這個「動態(tài)」表現(xiàn)在哪呢?我想最主要的表現(xiàn)就是 Obje...
    Ethan_Struggle閱讀 2,339評論 0 7
  • 《錯晨》 穿著白紗的清晨 剛跳醒的紅綠燈 擦拭著朦朧的眼 路燈低著頭 閉上了沉重的眼 柔柔的天亮 驅(qū)趕走沒精打采的...
    吟風(fēng)閱讀 233評論 0 1

友情鏈接更多精彩內(nèi)容