Runtime

使用運行時方法需要引入runtime.h文件。

一纹冤、基礎知識

runtime簡稱運行時洒宝,OC就是運行時機制,也就是在運行時候的一些機制萌京,其中最主要的是消息機制雁歌。
對于C語言,函數(shù)的調(diào)用是在編譯的時候會決定調(diào)用哪個函數(shù)知残。
對于OC的函數(shù)靠瞎,屬于動態(tài)調(diào)用過程,在編譯的時候并不能決定真正調(diào)用哪個函數(shù)求妹,只有在真正運行的時候才會根據(jù)函數(shù)的名稱找到對應的函數(shù)來調(diào)用乏盐。
事實證明:
-- 在編譯階段,OC可以調(diào)用任何函數(shù)制恍,即使這個函數(shù)并未實現(xiàn)父能,只要聲明過就不會報錯。
-- 在編譯階段净神,C語言調(diào)用未實現(xiàn)的函數(shù)就會報錯何吝。
Method :成員方法
Ivar : 成員變量

二、常用方法

class_copyPropertyList : 獲取屬性列表
  class_copyMethodList : 獲取成員方法列表
  class_copyIvarList:獲取成員變量列表
  ivar_getName:獲取變量名
  property_getName:獲取屬性名

使用示例

2.1.獲取成員變量list

        unsigned int ivarCount = 0; //成員變量數(shù)
        Ivar *ivarList = class_copyIvarList([self class], &ivarCount);//ivar數(shù)組
        
        for (int i = 0; i < ivarCount; i++) {//遍歷
            Ivar ivar = ivarList[i]; //獲取ivar
            const char *name = ivar_getName(ivar);//獲取變量名
            NSString *key = [NSString stringWithUTF8String:name];
            NSLog(@"%@", key);

        }

      free(ivarList);

2.2.獲取屬性列表

unsigned int count = 0;
    objc_property_t *propertList = class_copyPropertyList([self class], &count);
    for (int i = 0; i < count; i++) {
        objc_property_t property = propertList[i];
        const char *name = property_getName(property);
        const char *attrs = property_getAttributes(property);
//        property_copyAttributeValue(,) 第一個參數(shù)為objc_property_t,第二個參數(shù)"V"獲取變量名,"T"獲取類型
        const char *value = property_copyAttributeValue(property, "V");
        NSLog(@"name = %s, attrs = %s, value = %s", name, attrs, value);
  /*
         各種符號對應類型鹃唯,部分類型在新版SDK中有所變化爱榕,如long 和long long
         c char         C unsigned char
         i int          I unsigned int
         l long         L unsigned long
         s short        S unsigned short
         d double       D unsigned double
         f float        F unsigned float
         q long long    Q unsigned long long
         B BOOL
         @ 對象類型 //指針 對象類型 如NSString 是@“NSString”
         propertyType,你可以打印出來俯渤,看看它是什么呆细。
         要判斷某個屬性的類型型宝,只需要[propertyType hasPrefix:@"T@\"NSString\""]
          這代表它是NSString 類型八匠。
         */
    }
    free(propertList);

2.3.獲取方法列表

unsigned int count = 0;
    Method *methodList = class_copyMethodList([self class], &count);
    for (int i = 0 ; i < count; i++) {
        Method method = methodList[i];
        SEL selector = method_getName(method);//方法入口
        const char *sel_name = sel_getName(selector);
        NSLog(@"方法名 %s", sel_name);
    }
    free(methodList);

2.4.Runtime-動態(tài)創(chuàng)建類添加屬性和方法

- (void)createClass
{
    Class MyClass = objc_allocateClassPair([NSObject class], "myclass", 0);
    //添加一個NSString的變量,第四個參數(shù)是對其方式趴酣,第五個參數(shù)是參數(shù)類型
    if (class_addIvar(MyClass, "itest", sizeof(NSString *), 0, "@")) {
        NSLog(@"add ivar success");
    }
    //myclasstest是已經(jīng)實現(xiàn)的函數(shù)梨树,"v@:"這種寫法見參數(shù)類型連接
    class_addMethod(MyClass, @selector(myclasstest:), (IMP)myclasstest, "v@:");
    //注冊這個類到runtime系統(tǒng)中就可以使用他了
    objc_registerClassPair(MyClass);
    //生成了一個實例化對象
    id myobj = [[MyClass alloc] init];
    NSString *str = @"asdb";
    //給剛剛添加的變量賦值
    //  object_setInstanceVariable(myobj, "itest", (void *)&str);在ARC下不允許使用
    [myobj setValue:str forKey:@"itest"];
    //調(diào)用myclasstest方法,也就是給myobj這個接受者發(fā)送myclasstest這個消息
    [myobj myclasstest:10];

}
//這個方法實際上沒有被調(diào)用,但是必須實現(xiàn)否則不會調(diào)用下面的方法
- (void)myclasstest:(int)a
{
    
}
//調(diào)用的是這個方法
static void myclasstest(id self, SEL _cmd, int a) //self和_cmd是必須的岖寞,在之后可以隨意添加其他參數(shù)
{
    
    Ivar v = class_getInstanceVariable([self class], "itest");
    //返回名為itest的ivar的變量的值
    id o = object_getIvar(self, v);
    //成功打印出結(jié)果
    NSLog(@"%@", o);
    NSLog(@"int a is %d", a);
}

詳解Objective-C的meta-class:http://www.reibang.com/p/a5ab8d998b9e

三抡四、使用方向:歸檔、字典<---->模型、框架封裝等

3.1. 實現(xiàn)歸檔

encode:編碼  decode:解碼
#define WKCodingImplementing 
- (void)encodeWithCoder:(NSCoder *)aCoder 
{ 
    unsigned int ivarCount = 0; 
    Ivar *ivarList = class_copyIvarList([self class], &ivarCount); 
    for (int i = 0; i < ivarCount; i++) { 
        Ivar ivar = ivarList[i]; 
        const char *name = ivar_getName(ivar); 
        const char *type = ivar_getTypeEncoding(ivar); 
        NSLog(@"%s-----%s", name, type); 
        NSString *key = [NSString stringWithUTF8String:name]; 
        id value = [self valueForKey:key]; 
        [aCoder encodeObject:value forKey:key]; 
    } 
    free(ivarList); 
} 
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder 
{ 
    if (self = [super init]) { 
        unsigned int ivarCount = 0; 
        Ivar *ivarList = class_copyIvarList([self class], &ivarCount); 
        for (int i = 0; i < ivarCount; i++) { 
            Ivar ivar = ivarList[i]; 
            const char *name = ivar_getName(ivar); 
            NSString *key = [NSString stringWithUTF8String:name]; 
            id value = [aDecoder decodeObjectForKey:key]; \
            NSLog(@"%@ %@", key, value); 
            [self setValue:value forKey:key]; 
        } 
    } 
    return self; 
}

3.2.如何快速生成Plist文件屬性名

實現(xiàn)原理:通過遍歷字典,判斷類型,拼接字符串

  // 拼接屬性字符串代碼
  NSMutableString *strM = [NSMutableString string];

  // 1.遍歷字典指巡,把字典中的所有key取出來淑履,生成對應的屬性代碼
  [dict enumerateKeysAndObjectsUsingBlock:^(id  _Nonnull key, id  _Nonnull obj, BOOL * _Nonnull stop) {

  NSString *type;

      if ([obj isKindOfClass:NSClassFromString(@"__NSCFString")]) {
          type = @"NSString";
      }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFArray")]){
          type = @"NSArray";
      }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFNumber")]){
          type = @"int";
      }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFDictionary")]){
          type = @"NSDictionary";
      }else if ([obj isKindOfClass:NSClassFromString(@"__NSCFBoolean")]){
          type = @"BooL";
      }
      // 屬性字符串
      NSString *str;
      if ([type containsString:@"NS"]) {
          str = [NSString stringWithFormat:@"@property (nonatomic, strong) %@ *%@;",type,key];
      }else{
          str = [NSString stringWithFormat:@"@property (nonatomic, assign) %@ %@;",type,key];
      }
      // 每生成屬性字符串,就自動換行藻雪。
      [strM appendFormat:@"\n%@\n",str];  
  }];
  // 把拼接好的字符串打印出來秘噪,就好了。
  NSLog(@"%@",strM);

打印結(jié)果

874828-7eb4ad3d25184d7a.png

3.3.KVC實現(xiàn)字典轉(zhuǎn)模型

KVC弊端
模型中屬性必須和字典的key一致,否則就報錯
如果不一致,系統(tǒng)會調(diào)用setValue: forUndefinedKey:
解決辦法,只需要重寫setValue: forUndefinedKey:即可

- (void)setValue:(id)value forUndefinedKey:(NSString *)key
{

}

3.4. RunTime實現(xiàn)字典轉(zhuǎn)模型

實現(xiàn)思路:遍歷模型中所有屬性,根據(jù)模型的屬性名去字典中查找key,取出對應的的值,給模型的屬性賦值

3.4.1.一級轉(zhuǎn)換

class_copyIvarList(self, &count)該方法第一個參數(shù)是要獲取哪個類中的成員屬性,第二個參數(shù)是這個類中有多少成員屬性,需要傳入地址,返回值Ivar是個數(shù)組,會將所有成員屬性放入這個數(shù)組中

@implementation NSObject (Model)

+ (instancetype)modelWithDict:(NSDictionary *)dict
{
    // 思路:遍歷模型中所有屬性-》使用運行時

    // 0.創(chuàng)建對應的對象
    id objc = [[self alloc] init];

    // 1.利用runtime給對象中的成員屬性賦值

    // class_copyIvarList:獲取類中的所有成員屬性
    // Ivar:成員屬性的意思
    // 第一個參數(shù):表示獲取哪個類中的成員屬性
    // 第二個參數(shù):表示這個類有多少成員屬性勉耀,傳入一個Int變量地址指煎,會自動給這個變量賦值
    // 返回值Ivar *:指的是一個ivar數(shù)組,會把所有成員屬性放在一個數(shù)組中便斥,通過返回的數(shù)組就能全部獲取到至壤。
    /* 類似下面這種寫法

     Ivar ivar;
     Ivar ivar1;
     Ivar ivar2;
     // 定義一個ivar的數(shù)組a
     Ivar a[] = {ivar,ivar1,ivar2};

     // 用一個Ivar *指針指向數(shù)組第一個元素
     Ivar *ivarList = a;

     // 根據(jù)指針訪問數(shù)組第一個元素
     ivarList[0];

     */
    unsigned int count;

    // 獲取類中的所有成員屬性
    Ivar *ivarList = class_copyIvarList(self, &count);

    for (int i = 0; i < count; i++) {
        Ivar ivar = ivarList[i];

        // 獲取成員屬性名
        NSString *name = [NSString stringWithUTF8String:ivar_getName(ivar)];

        // 處理成員屬性名->字典中的key 
        // (去掉 _ ,從第一個角標開始截取)
        NSString *key = [name substringFromIndex:1];

        // 根據(jù)成員屬性名去字典中查找對應的value
        id value = dict[key];

3.4.2.二級轉(zhuǎn)換

判斷字典中是否存在字典,如果存在,轉(zhuǎn)為模型
字典屬性生成的是@"@"xxxx""類型,需要裁減為@"xxxx"

    if ([value isKindOfClass:[NSDictionary class]]) {
            // 獲取成員屬性類型
            NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
            // 生成的是這種@"@\"User\"" 類型 -》 @"User"  在OC字符串中 \" -> ",\是轉(zhuǎn)義的意思枢纠,不占用字符
           type = [type stringByReplacingOccurrencesOfString:@"\"" withString:@""];
    NSLog(@"type-----------%@",type);
    
            type = [type stringByReplacingOccurrencesOfString:@"@"  withString:@""];
    NSLog(@"type-----------%@",type);
            // 根據(jù)字符串類名生成類對象
            Class modelClass = NSClassFromString(type); 
            if (modelClass) {
                value  =  [modelClass modelWithDict:value];
            }
        }

3.4.3.三級轉(zhuǎn)換

通過給分類添加一個協(xié)議,來實現(xiàn)將數(shù)組中的字典轉(zhuǎn)為模型

       // 三級轉(zhuǎn)換:NSArray中也是字典像街,把數(shù)組中的字典轉(zhuǎn)換成模型.
        // 判斷值是否是數(shù)組
        if ([value isKindOfClass:[NSArray class]]) {
            // 判斷對應類有沒有實現(xiàn)字典數(shù)組轉(zhuǎn)模型數(shù)組的協(xié)議
            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {

                // 轉(zhuǎn)換成id類型,就能調(diào)用任何對象的方法
                id idSelf = self;

                // 獲取數(shù)組中字典對應的模型
                NSString *type =  [idSelf arrayContainModelClass][key];

                // 生成模型
               Class classModel = NSClassFromString(type);
                NSMutableArray *arrM = [NSMutableArray array];
                // 遍歷字典數(shù)組晋渺,生成模型數(shù)組
                for (NSDictionary *dict in value) {
                    // 字典轉(zhuǎn)模型
                  id model =  [classModel modelWithDict:dict];
                    [arrM addObject:model];
                }

                // 把模型數(shù)組賦值給value
                value = arrM;

            }
        }


        if (value) { // 有值宅广,才需要給模型的屬性賦值
            // 利用KVC給模型中的屬性賦值
            [objc setValue:value forKey:key];
        }

    }

    return objc;
}

@end

3.5. 使用runtime實現(xiàn)方法的交換

3.5.1 前言

在開發(fā)過程中,我們常常會遇到一種問題: 即當我們使用系統(tǒng)自帶的方法時,發(fā)現(xiàn)系統(tǒng)方法的功能不能滿足我們的需求,這時候需要我們給系統(tǒng)的方法添加額外的功能.
下面以一個例子來實現(xiàn)給系統(tǒng)自帶的方法添加額外的功能.

項目背景 : 公司有個開發(fā)了很久的項目,之前使用添加圖片的方法用的是:imageName:方法, 現(xiàn)在的項目需求是給系統(tǒng)自帶的imageName:添加一個功能:如果下載的圖片不存在,那么就提示我們當前下載的圖片為空.

思路 : 給系統(tǒng)自帶的方法添加功能的幾種方法

  1. 自定義類, 重寫系統(tǒng)自帶的imageName:方法,這種方法雖然可以實現(xiàn),但是它的弊端就是必須要使用自己的類,依賴性強.
  2. 給UIImage添加一個分類, 改變系統(tǒng)類的實現(xiàn),給系統(tǒng)的類添加方法的時候調(diào)用(強烈不推薦)
  3. 使用runtime的交互方法,給系統(tǒng)的方法添加功能. 具體實現(xiàn) : 添加一個分類 --> 在分類中提供一個需要添加的功能的方法 --> 將這個方法的實現(xiàn)和系統(tǒng)自帶的方法的實現(xiàn)交互.

3.5.2.步驟

3.5.2.1. 新建一個繼承自UIImage的分類,定義一個方法實現(xiàn)給系統(tǒng)的自帶的方法添加功能.

#import <UIKit/UIKit.h>
@interface UIImage (WGImage)
// 聲明方法
// 如果跟系統(tǒng)方法差不多功能,可以采取添加前綴,與系統(tǒng)方法區(qū)分
+ (UIImage *)wg_imageWithName:(NSString *)imageName;
@end

3.5.2.2. 實現(xiàn)方法的交互

定義完畢新方法后,需要弄清楚什么時候?qū)崿F(xiàn)與系統(tǒng)的方法交互?
答 : 既然是給系統(tǒng)的方法添加額外的功能,換句話說,我們以后在開發(fā)中都是使用自己定義的方法,取代系統(tǒng)的方法,所以,當程序一啟動,就要求能使用自己定義的功能方法.說道這里:我們必須要弄明白一下兩個方法 :
+(void)initialize(當類第一次被調(diào)用的時候就會調(diào)用該方法,整個程序運行中只會調(diào)用一次)

  • (void)load(當程序啟動的時候就會調(diào)用該方法,換句話說,只要程序一啟動就會調(diào)用load方法,整個程序運行中只會調(diào)用一次)
+ (void)load {
/*
     self:UIImage
     誰的事情,誰開頭 1.發(fā)送消息(對象:objc) 2.注冊方法(方法編號:sel) 3.交互方法(方法:method) 4.獲取方法(類:class)
     Method:方法名
     獲取方法,方法保存到類
     Class:獲取哪個類方法
     SEL:獲取哪個方法
     imageName
*/
    // 獲取imageName:方法的地址
    Method imageNameMethod = class_getClassMethod(self, @selector(imageNamed:));
    // 獲取wg_imageWithName:方法的地址
    Method wg_imageWithNameMethod = class_getClassMethod(self, @selector(wg_imageWithName:));
    // 交換方法地址,相當于交換實現(xiàn)方式
    method_exchangeImplementations(imageNameMethod, wg_imageWithNameMethod);
}

3.5.2.3. 加載圖片, 判斷當前圖片是否為空

// 加載圖片, 判斷是否為空
+ (UIImage *)wg_imageWithName:(NSString *)imageName
{
    // 這里調(diào)用imageWithName些举,相當于調(diào)用imageName
    UIImage *image = [UIImage wg_imageWithName:imageName];
    if (!image) {
        NSLog(@"Alex : 圖片不存在");
    }
    return image;
}

3.5.2.4. 使用交互后的方法

#import "ViewController.h"
#import "WGStudent.h"
#import "UIImage+WGImage.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

  //  wg_imageWithName ---> imageNamed(底層的操作 : 1, 下載圖片. 2, 判斷圖片是否存在 )
    [UIImage imageNamed:@"WilliamAlex.png"];
}

@end

3.5.2.5. 打印結(jié)果

2016-03-08 17:29:50.372 fdsfsdf[1545:96854] Alex : 圖片不存在
知識拓展
不能在分類中重寫系統(tǒng)方法imageNamed跟狱,因為會把系統(tǒng)的功能給覆蓋掉,而且分類中不能調(diào)用super.

runtime的那點事兒(一)消息機制
http://blog.csdn.net/jq2530469200/article/details/51880836
runtime的那點事兒(二)消息機制
http://blog.csdn.net/jq2530469200/article/details/51886532
runtime的那點事兒(三)消息機制
http://blog.csdn.net/jq2530469200/article/details/51886578
runtime簡單使用之方法的交換:
http://www.reibang.com/p/d28abb706add
使用RunTime實現(xiàn)字典轉(zhuǎn)模型:
http://www.reibang.com/p/cecfe78e9cd8

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末户魏,一起剝皮案震驚了整個濱河市驶臊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌叼丑,老刑警劉巖关翎,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異鸠信,居然都是意外死亡纵寝,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門星立,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爽茴,“玉大人,你說我怎么就攤上這事绰垂∈易啵” “怎么了?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵劲装,是天一觀的道長胧沫。 經(jīng)常有香客問我昌简,道長,這世上最難降的妖魔是什么绒怨? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任纯赎,我火速辦了婚禮,結(jié)果婚禮上南蹂,老公的妹妹穿的比我還像新娘址否。我一直安慰自己,他們只是感情好碎紊,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布佑附。 她就那樣靜靜地躺著,像睡著了一般仗考。 火紅的嫁衣襯著肌膚如雪音同。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天秃嗜,我揣著相機與錄音权均,去河邊找鬼。 笑死锅锨,一個胖子當著我的面吹牛叽赊,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播必搞,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼必指,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了恕洲?” 一聲冷哼從身側(cè)響起塔橡,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎霜第,沒想到半個月后葛家,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡泌类,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年癞谒,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刃榨。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡弹砚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出喇澡,到底是詐尸還是另有隱情迅栅,我是刑警寧澤殊校,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布晴玖,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏呕屎。R本人自食惡果不足惜让簿,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望秀睛。 院中可真熱鬧尔当,春花似錦、人聲如沸蹂安。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽田盈。三九已至畜号,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間允瞧,已是汗流浹背简软。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留述暂,地道東北人痹升。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像畦韭,于是被迫代替她去往敵國和親疼蛾。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

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

  • 對于從事 iOS 開發(fā)人員來說艺配,所有的人都會答出【runtime 是運行時】什么情況下用runtime?大部分人能...
    夢夜繁星閱讀 3,721評論 7 64
  • 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的轉(zhuǎn)載 這篇文章完全是基于南峰子老師博客的...
    西木閱讀 30,554評論 33 466
  • 一. RunTime簡介 RunTime簡稱運行時妒挎。OC就是運行時機制绳锅,也就是在運行時候的一些機制,其中最主要的是...
    xx_cc閱讀 13,672評論 43 170
  • 目錄 Objective-C Runtime到底是什么 Objective-C的元素認知 Runtime詳解 應用...
    Ryan___閱讀 1,939評論 1 3
  • 他給自己煎了兩個雞蛋酝掩,火大了有點老鳞芙,還熬了一碗稀飯,時間久了熬的有點干期虾。 將就著吃下去原朝,習慣性的不刷碗,在...
    773aa6d27d76閱讀 527評論 1 2