Runtime理解

weixin_33806914發表於2019-02-15

Runtime簡稱執行時。OC就是執行時機制,也就是在執行時候的一些機制,其中最主要的是訊息機制。

對於C語言,函式的呼叫在編譯的時候會決定呼叫哪個函式。

對於OC的函式,屬於動態呼叫過程,在編譯的時候並不能決定真正呼叫哪個函式,只有在真正執行的時候才會根據函式的名稱找到對應的函式來呼叫。

在編譯階段,OC可以呼叫任何函式,即使這個函式並未實現,只要宣告過就不會報錯。
在編譯階段,C語言呼叫未實現的函式就會報錯。

runtime作用

1.傳送訊息

方法呼叫的本質,就是讓物件傳送訊息。

objc_msgSend,只有物件才能傳送訊息,因此以objc開頭.

使用訊息機制前提,必須匯入#import <objc/message.h>
訊息機制原理:物件根據方法編號SEL去對映表查詢對應的方法實現


3446786-7710ae8cff0fbfe3.png

2.交換方法

開發使用場景:系統自帶的方法功能不夠,給系統自帶的方法擴充套件一些功能,並且保持原有的功能。

方式一:繼承系統的類,重寫方法.

方式二:使用runtime,交換方法.

3.動態新增方法

開發使用場景:如果一個類方法非常多,載入類到記憶體的時候也比較耗費資源,需要給每個方法生成對映表,可以使用動態給某個類,新增方法解決。

經典面試題:有沒有使用performSelector,其實主要想問你有沒有動態新增過方法。

4.給分類新增屬性

  • 原理:給一個類宣告屬性,其實本質就是給這個類新增關聯,並不是直接把這個值的記憶體空間新增到類存空間。
@implementation ViewController
- (void)viewDidLoad {    
[super viewDidLoad];    
// Do any additional setup after loading the view, typically from a nib.    // 給系統NSObject類動態新增屬性name   
 NSObject *objc = [[NSObject alloc] init];    
objc.name = @"小碼哥";    
NSLog(@"%@",objc.name);
}
@end
// 定義關聯的keystatic const char *key = "name";
@implementation NSObject (Property)
- (NSString *)name{    
// 根據關聯的key,獲取關聯的值。    
return objc_getAssociatedObject(self, key);
}
- (void)setName:(NSString *)name{    
// 第一個引數:給哪個物件新增關聯   
 // 第二個引數:關聯的key,通過這個key獲取   
 // 第三個引數:關聯的value   
 // 第四個引數:關聯的策略   
 objc_setAssociatedObject(self, key, name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);}
@end

5.字典轉模型

設計模型:字典轉模型的第一步

模型屬性,通常需要跟字典中的key一一對應

問題:一個一個的生成模型屬性,很慢?

需求:能不能自動根據一個字典,生成對應的屬性。

解決:提供一個分類,專門根據字典生成對應的屬性字串。

@implementation NSObject (Log)// 自動列印屬性字串+ (void)resolveDict:(NSDictionary *)dict{    // 拼接屬性字串程式碼    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";        }        // 屬性字串        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);}@end

字典轉模型的方式一:KVC

@implementation Status+ (instancetype)statusWithDict:(NSDictionary *)dict{    Status *status = [[self alloc] init];    [status setValuesForKeysWithDictionary:dict];    return status;}@end

KVC字典轉模型弊端:必須保證,模型中的屬性和字典中的key一一對應。

如果不一致,就會呼叫[<Status 0x7fa74b545d60> setValue:forUndefinedKey:]報key找不到的錯。

分析:模型中的屬性和字典的key不一一對應,系統就會呼叫setValue:forUndefinedKey:報錯。

解決:重寫物件的setValue:forUndefinedKey:,把系統的方法覆蓋,就能繼續使用KVC,字典轉模型了。

- (void)setValue:(id)value forUndefinedKey:(NSString *)key{}
3446786-e3491bdc7dcda6a0
image

字典轉模型的方式二:Runtime

思路:利用執行時,遍歷模型中所有屬性,根據模型的屬性名,去字典中查詢key,取出對應的值,給模型的屬性賦值。

步驟:提供一個NSObject分類,專門字典轉模型,以後所有模型都可以通過這個分類轉。

@implementation ViewController- (void)viewDidLoad {    [super viewDidLoad];    // Do any additional setup after loading the view, typically from a nib.    // 解析Plist檔案    NSString *filePath = [[NSBundle mainBundle] pathForResource:@"status.plist" ofType:nil];    NSDictionary *statusDict = [NSDictionary dictionaryWithContentsOfFile:filePath];    // 獲取字典陣列    NSArray *dictArr = statusDict[@"statuses"];    // 自動生成模型的屬性字串//    [NSObject resolveDict:dictArr[0][@"user"]];    _statuses = [NSMutableArray array];    // 遍歷字典陣列    for (NSDictionary *dict in dictArr) {        Status *status = [Status modelWithDict:dict];        [_statuses addObject:status];    }    // 測試資料    NSLog(@"%@ %@",_statuses,[_statuses[0] user]);}@end@implementation NSObject (Model)+ (instancetype)modelWithDict:(NSDictionary *)dict{    // 思路:遍歷模型中所有屬性-》使用執行時    // 0.建立對應的物件    id objc = [[self alloc] init];    // 1.利用runtime給物件中的成員屬性賦值    // class_copyIvarList:獲取類中的所有成員屬性    // Ivar:成員屬性的意思    // 第一個引數:表示獲取哪個類中的成員屬性    // 第二個引數:表示這個類有多少成員屬性,傳入一個Int變數地址,會自動給這個變數賦值    // 返回值Ivar *:指的是一個ivar陣列,會把所有成員屬性放在一個陣列中,通過返回的陣列就能全部獲取到。    /* 類似下面這種寫法     Ivar ivar;     Ivar ivar1;     Ivar ivar2;     // 定義一個ivar的陣列a     Ivar a[] = {ivar,ivar1,ivar2};     // 用一個Ivar *指標指向陣列第一個元素     Ivar *ivarList = a;     // 根據指標訪問陣列第一個元素     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];        // 根據成員屬性名去字典中查詢對應的value        id value = dict[key];        // 二級轉換:如果字典中還有字典,也需要把對應的字典轉換成模型        // 判斷下value是否是字典        if ([value isKindOfClass:[NSDictionary class]]) {            // 字典轉模型            // 獲取模型的類物件,呼叫modelWithDict            // 模型的類名已知,就是成員屬性的型別            // 獲取成員屬性型別           NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];          // 生成的是這種@"@\"User\"" 型別 -》 @"User"  在OC字串中 \" -> ",\是轉義的意思,不佔用字元            // 裁剪型別字串            NSRange range = [type rangeOfString:@"\""];           type = [type substringFromIndex:range.location + range.length];            range = [type rangeOfString:@"\""];            // 裁剪到哪個角標,不包括當前角標          type = [type substringToIndex:range.location];            // 根據字串類名生成類物件            Class modelClass = NSClassFromString(type);            if (modelClass) { // 有對應的模型才需要轉                // 把字典轉模型                value  =  [modelClass modelWithDict:value];            }        }        // 三級轉換:NSArray中也是字典,把陣列中的字典轉換成模型.        // 判斷值是否是陣列        if ([value isKindOfClass:[NSArray class]]) {            // 判斷對應類有沒有實現字典陣列轉模型陣列的協議            if ([self respondsToSelector:@selector(arrayContainModelClass)]) {                // 轉換成id型別,就能呼叫任何物件的方法                id idSelf = self;                // 獲取陣列中字典對應的模型                NSString *type =  [idSelf arrayContainModelClass][key];                // 生成模型               Class classModel = NSClassFromString(type);                NSMutableArray *arrM = [NSMutableArray array];                // 遍歷字典陣列,生成模型陣列                for (NSDictionary *dict in value) {                    // 字典轉模型                  id model =  [classModel modelWithDict:dict];                    [arrM addObject:model];                }                // 把模型陣列賦值給value                value = arrM;            }        }        if (value) { // 有值,才需要給模型的屬性賦值            // 利用KVC給模型中的屬性賦值            [objc setValue:value forKey:key];        }    }    return objc;}@end