本人是渣渣一枚,最近淺淺的接觸了一下runtime,給大家分享一些我的理解,高手勿噴
runtime簡稱運行時,采用C和匯編寫的,是蘋果為了動態(tài)系統(tǒng)的高效而做出的努力. OC從三個不同的層級上與runtime進行交互,分別為:
1.Object-C源代碼
2.Foundation框架和NSObject類定義的方法
3.Runtime函數的直接使用
在平時的開發(fā)中我們只需要寫OC代碼,而runtime會自動在幕后運作
1.RunTime簡稱運行時,就是系統(tǒng)在運行的時候的一些機制,其中最主要的是消息機制。
2.對于C語言,函數的調用在編譯的時候會決定調用哪個函數,編譯完成之后直接順序執(zhí)行,無任何二義性。
3.OC的函數調用成為消息發(fā)送。屬于動態(tài)調用過程。在編譯的時候并不能決定真正調用哪個函數(事實證明,在編譯階段,OC可以調用任何函數,即使這個函數并未實現,只要申明過就不會報錯。而C語言在編譯階段就會報錯)。
只有在真正運行的時候才會根據函數的名稱找到對應的函數來調用。
相信很多人聽了概念,只有個大概的印象,runtime,其實最多的運用于model,還有交換/增加/跟蹤方法,我寫了幾個demo助于各位理解
Model實現NSCoding的自動歸檔和解檔
按照我之前比較入門級的寫法,我們需要對model的每一個屬性都實現一遍encodeObject和decodeObjectForKey方法,那如果有多個屬性,我們則必須一個個的寫一遍,在這里我們就可以采用runtime來漸變實現
這里我先看一下runtime的一個獲取變量列表的方法
#pragma --- 這里先介紹兩個需要涉及到的runtime的兩個知識 ---
// * 獲取屬性列表
/**
這里count可以通過 class_copyPropertyList 這個方法得到屬性的數量
[self class] 指的是你想要獲取的那個類,如果想獲取BeeEncodeModel的屬性則可以改成[BeeEncodeModel class]
這時候我們propertylist能夠獲取到類中的所有成員屬性,然后根據count就可以取到相對應的屬性名稱
*/
unsigned int count = 0;
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (unsigned int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"類中包含屬性:%@", [NSString stringWithUTF8String:propertyName]);
}
// * 獲取成員變量
/**
這個方法跟上面的大同小異,就是方法改為 class_copyPropertyList
class_copyIvarList:獲取類中的所有成員屬性
Ivar:成員屬性的意思
然后這個方法是可以獲取得到成員變量,我們通過打印就可以很清楚的看到
*/
Ivar *ivarList = class_copyIvarList([self class], &count);
for (unsigned int i=0; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"類中包含成員變量%@", [NSString stringWithUTF8String:ivarName]);
}
這里要注意一下,獲取成員變量的方法因為是用指針指向元素的方法,所以得到的變量名字前面會有一個"",屬性列表的方法就沒有"",這里我們可以在控制臺清楚的看出來
有了獲取成員變量這個方法,我們可以動態(tài)的控制類的屬性,方法如下:
#pragma mark - 通過動態(tài)變量控制修改BeeEncodeModel的age屬性,把age的值改成20
- (void)change_variable{
// _beeModel = [[BeeEncodeModel alloc]init];
unsigned int count = 0;
Ivar *ivar = class_copyIvarList([_beeModel class], &count);
for (int i = 0; i<count; i++) {
Ivar var = ivar[i];
const char *varName = ivar_getName(var);
NSString *name = [NSString stringWithUTF8String:varName];
if ([name isEqualToString:@"_age"]) {
// * 在這里也可以把Garlic.h的age為nsnumber類型,則:
object_setIvar(_beeModel, var, @20);
// * 如果是int等基本數據類型,則可以使用強轉
// object_setIvar(beeVC, var, (__bridge id)((void *)20));
break;
}
}
NSLog(@"beeVC age is %@",_beeModel.age);
}
接下來我們創(chuàng)建一個BeeEncodeModel,我們是用runtime的方法對這個model進行NSCoding的自動歸檔和解檔,同樣的,也是用到了encodeObject和decodeObjectForKey方法
#import <Foundation/Foundation.h>
@interface BeeEncodeModel : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *address;
@property (nonatomic, assign) NSNumber *age;
// model 的成員變量不需要全部都存在于字典中
@property (nonatomic, copy) NSString *name1;
@property (nonatomic, copy) NSString *address1;
@property (nonatomic, assign) NSNumber *age1;
@end
#import "BeeEncodeModel.h"
#import <objc/runtime.h>
@implementation BeeEncodeModel
/**
我們通過獲取runtime的方法來獲取BeeEncodeModel的成員屬性,可以得到這個類所有的成員屬性,然后遍歷一下進行歸檔,我們就不用一個個去寫了
這里使用的是獲取的成員變量方法,所以會有 "_" ,因此我們通過 key = [key substringFromIndex:1] 來刪除 "_",使得這個key能跟成員屬性匹配從而進行歸檔
所以我們只需要在.h文件中寫上成員屬性,而.m的代碼是不用動的.就已經能實現自動歸檔和解檔
*/
- (void)encodeWithCorder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([BeeEncodeModel class], &count);
for (int i = 0; i < count; i++) {
// 取出i對應的成員變量
Ivar ivar = ivars[i];
// 查看成員變量
const char *name = ivar_getName(ivar);
// 歸檔
NSString *key = [NSString stringWithUTF8String:name];
id value = [self valueForKey:key];
key = [key substringFromIndex:1];
// 設置到成員變量上
[self setValue:value forKey:key];
}
free(ivars);
}
- (id)initWithCorder:(NSCoder *)decoder
{
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([BeeEncodeModel class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置對應的成員變量
Ivar ivar = ivars[i];
// 查看成員變量
const char *name = ivar_getName(ivar);
// 歸檔
NSString *key = [NSString stringWithUTF8String:name];
key = [key substringFromIndex:1];
id value = [decoder decodeObjectForKey:key];
// 設置到成員變量身上
[self setValue:value forKey:key];
}
free(ivars);
}
return self;
}
@end
這就是運用了runtime的方法把屬性都遍歷了出來然后一個個進行歸檔和解檔,就不用我們一個個去寫了.
當然,我覺得真正強大的并不是在這里,我們可以在model自定義一個方法
可能我們訪問后臺拿到的json數據,就是一個大字典,但是可能大字典里有數組或者有小字典,這時我們可以用runtime的方式,自定義一個方法可以讓這些子字典給弄出來,即一個字典里面如果包含字典或者數組,也可以提取里面相對應的key和value直接使用,即讓成員屬性可以跟每個key相對應,話不多說,上代碼
#import "GarlicModel.h"
#import <objc/runtime.h>
@implementation GarlicModel
/**
這個model不需要歸檔和解檔,直接寫一個類方法
這個model可以讓一個字典里面如果包含字典或者數組,也可以提取里面相對應的key和value直接使用,即讓成員屬性可以跟每個key相對應
*/
+ (instancetype)modelWithDict:(NSDictionary *)dict
{
// 創(chuàng)建對應模型對象
id objc = [[self alloc] init];
unsigned int count = 0;
// 1.獲取成員屬性數組
Ivar *ivarList = class_copyIvarList(self, &count);
// 2.遍歷所有的成員屬性名,一個一個去字典中取出對應的value給模型屬性賦值
for (int i = 0; i < count; i++) {
// 2.1 獲取成員屬性
Ivar ivar = ivarList[i];
// 2.2 獲取成員屬性名 C -> OC 字符串
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
// 2.3 _成員屬性名 => 字典key
NSString *key = [ivarName substringFromIndex:1];
// 2.4 去字典中取出對應value給模型屬性賦值
id value = dict[key];
// 獲取成員屬性類型
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// 這里要注意判斷value,因為model的成員屬性不一定存在于字典中,若不存在,value為null
if (value) {
NSLog(@"value = %@,key = %@",value,key);
[objc setValue:value forKey:key];
}
// * 當字典里嵌套字典的時候
//判斷如果model里有字典
if ([value isKindOfClass:[NSDictionary class]] && [ivarType containsString:@"NS"]) {
// 是字典對象,并且屬性名對應類型是自定義類型
// 處理類型字符串 @\"User\" -> User
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
// 自定義對象,并且值是字典
// value:user字典 -> User模型
// 獲取模型(user)類對象
Class modalClass = NSClassFromString(ivarType);
// 字典轉模型
if (modalClass) {
[objc objectWithDict:value and:objc];
}
}
//* 當字典里嵌套數組的時候
//判斷如果model有數組
if ([value isKindOfClass:[NSArray class]] && [ivarType containsString:@"NS"]){
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
Class modalClass = NSClassFromString(ivarType);
if (modalClass) {
[objc objectWithArray:value and:objc];
}
}
}
return objc;
}
- (void)objectWithDict:(NSDictionary *)dict and:(id)objc
{
unsigned int count = 0;
// 1.獲取成員屬性數組
Ivar *ivarList = class_copyIvarList([GarlicModel class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置對應的成員變量
Ivar ivar = ivarList[i];
// 查看成員變量
const char *name = ivar_getName(ivar);
// 歸檔
NSString *key = [NSString stringWithUTF8String:name];
key = [key substringFromIndex:1];
id value = dict[key];
// 設置到成員變量身上
if (value) {
[objc setValue:value forKey:key];
}
}
}
- (void)objectWithArray:(NSArray *)array and:(id)objc
{
for (int a = 0; a < array.count; a ++) {
unsigned int count = 0;
NSDictionary *dict = array[a];
// 1.獲取成員屬性數組
Ivar *ivarList = class_copyIvarList([GarlicModel class], &count);
for (int i = 0; i<count; i++) {
// 取出i位置對應的成員變量
Ivar ivar = ivarList[i];
// 查看成員變量
const char *name = ivar_getName(ivar);
// 歸檔
NSString *key = [NSString stringWithUTF8String:name];
key = [key substringFromIndex:1];
id value = dict[key];
// 設置到成員變量身上
if (value) {
[objc setValue:value forKey:key];
}
}
}
}
@end
這樣子每一個數據里每一個key都會和屬性對應起來,無論是存在字典中的數組還是字典中的字典.
便于大家理解,我也寫了個小demo網上
https://github.com/iOSJYF/Garlic_runtime/tree/master/Garlic_WithRuntime
第一次寫文章,大家笑納哈~~