您的位置:首页 > 移动开发 > IOS开发

iOS 消息机制与运行时 运用runtime字典转模型

2016-10-24 02:41 423 查看
文章中的Demo地址https://github.com/InnerMongoliaZorro/runtime

开始学习iOS开发的时候,对Objective-C的消息转发机制和运行时十分不解,感觉很高深的样子,就一直没有下功夫去研究这些问题.最近看了一些资料,对这部分知识做了一些总结,分享出来供大家参考学习,若有错误请联系我更正,大家共同进步.

1.动态绑定与静态绑定

绑定是一个把过程调用和响应调用所需要执行的代码加以结合的过程.(即是一个方法的调用与方法所在的类(方法主体)关联起来)

静态绑定:在程序执行前方法已经被绑定,此时由编译器或其他连接程序实现.

动态绑定:在运行时根据对象的类型进行绑定.若一种语言实现了动态绑定,同时必须提供一些机制,可在运行期间判断对象的类型,并分别调用对象的方法.也就是说,编译器此时依然不知道对象的类型,但方法调用机制能自己去查,找到正确的方法主体.不同的语言对动态绑定的实现方法是有区别的.(Objective-C中为runtime).

2.动态创建对象(摘自唐巧的

- (void)viewDidLoad {
[super viewDidLoad];
//创建新的类
Class newClass = objc_allocateClassPair([UIView class], "CustomView", 0);
//给类增加新的方法
class_addMethod(newClass, @selector(report), (IMP)ReportFuntion, "v@:");
//注册该类
objc_registerClassPair(newClass);
//创建实例
id instanceOfNewClass = [newClass new];
//调用方法
[instanceOfNewClass performSelector:@selector(report)];
}
void ReportFuntion(id self , SEL _cmd){
NSLog(@"This object is %p" , self);
NSLog(@"Class is %@ , and super is %@",[self class] , [self superclass]);
Class currentClass = [self class];

for (int i = 0; i < 10 ; i++) {
NSLog(@"Following the isa pointer %d times gives %p" , i , currentClass);
currentClass = object_getClass(currentClass);
}

NSLog(@"NSObject's class is %p" , [NSObject class]);
NSLog(@"NSObject's meta class is %p" , object_getClass([NSObject class]));

}


运行结果

2016-10-23 22:14:14.094 Rumtime[36087:424763] This object is 0x7f8329d169e0
2016-10-23 22:14:14.095 Rumtime[36087:424763] Class is CustomView , and super is UIView
2016-10-23 22:14:14.095 Rumtime[36087:424763] Following the isa pointer 0 times gives 0x600000254100
2016-10-23 22:14:14.096 Rumtime[36087:424763] Following the isa pointer 1 times gives 0x600000253cb0
2016-10-23 22:14:14.096 Rumtime[36087:424763] Following the isa pointer 2 times gives 0x107de2e08
2016-10-23 22:14:14.096 Rumtime[36087:424763] Following the isa pointer 3 times gives 0x107de2e08
2016-10-23 22:14:14.096 Rumtime[36087:424763] Following the isa pointer 4 times gives 0x107de2e08
2016-10-23 22:14:14.096 Rumtime[36087:424763] Following the isa pointer 5 times gives 0x107de2e08
2016-10-23 22:14:14.097 Rumtime[36087:424763] Following the isa pointer 6 times gives 0x107de2e08
2016-10-23 22:14:14.097 Rumtime[36087:424763] Following the isa pointer 7 times gives 0x107de2e08
2016-10-23 22:14:14.097 Rumtime[36087:424763] Following the isa pointer 8 times gives 0x107de2e08
2016-10-23 22:14:14.098 Rumtime[36087:424763] Following the isa pointer 9 times gives 0x107de2e08
2016-10-23 22:14:14.098 Rumtime[36087:424763] NSObject's class is 0x107de2e58
2016-10-23 22:14:14.098 Rumtime[36087:424763] NSObject's meta class is 0x107de2e08


Objective-C是一门面向对象的语言,每一个对象都是一个类的实例.在Objective-C语言内部,每一个对象都有一个名为isa的指针,指向该对象的类.每一个类描述了一系列它的对象的特点,包括成员变量的列表\成员函数的列表等.每一个对象都可以接受消息,而对象能够接收的消息列表保存在它对应的类中.在Objective-C中,每一个类实际上也是一个对象.每一个类也有一个名为isa的指针.每一个类也可以接收消息.因为类也是一个对象,所以它也必须是另一个类的实例,这个类就是元类(meatclass).元类类方法的列表.当一个类方法被调用时,元类会首先查找它本身是否有该类方法的实现,如果没有,则该元类会向它的父类查找该方法,这样可以一直找到继承链的头.为了设计上的完整,所有的元类的isa指针都会指向一个根元类(root meatclass).根元类本身的isa指针指向自己,这样就形成了一个闭环.

3.消息转发机制(objc_msgSend)

参考链接:http://www.cnblogs.com/Jenaral/p/4916255.html

http://blog.csdn.net/tskyfree/article/details/7984887

在Objective-C中,message与方法的真正实现是在执行阶段绑定的,而非编译阶段。编译器会将消息发送转换成对objc_msgSend方法的调用。

在Objective-C中,消息是直到运行的时候才和方法实现绑定的。编译器会把一个消息表达式,

[receiver message]

转换成一个对消息函数objc_msgSend的调用。该函数有两个主要参数:消息接收者和消息对应的方法名字——也就是方法选标:

objc_msgSend(receiver, selector)

同时接收消息中的任意数目的参数:

objc_msgSend(receiver, selector, arg1, arg2, …)

该消息函数做了动态绑定所需要的一切:

它首先找到选标所对应的方法实现。因为不同的类对同一方法可能会有不同的实现,所以找到的方法实现依赖于消息接收者的类型。

然后将消息接收者对象(指向消息接收者对象的指针)以及方法中指定的参数传给找到的方法实现。

最后,将方法实现的返回值作为该函数的返回值返回。

当向一个对象发送消息时,objc_msgSend方法根据对象的isa指针找到对象的类,然后在类的调度表(dispatch table)中查找selector。如果无法找到selector,objc_msgSend通过指向父类的指针找到父类,并在父类的调度表(dispatch table)中查找selector,以此类推直到NSObject类。一旦查找到selector,objc_msgSend方法根据调度表的内存地址调用该实现。 通过这种方式,message与方法的真正实现在执行阶段才绑定。为了保证消息发送与执行的效率,系统会将全部selector和使用过的方法的内存地址缓存起来。每个类都有一个独立的缓存,缓存包含有当前类自己的 selector以及继承自父类的selector。查找调度表(dispatch table)前,消息发送系统首先检查receiver对象的缓存。

结合以上三部分,我们可以很清楚的理解Objective-C的消息机制,接下来我就重点讲解几个runtime运用的Demo

4.运用runtime获取一个类的所有成员变量

unsigned int count = 0;
//返回一个指针数组,里面装有一个类的所有成员变量,这个数组必须手动释放
Ivar *ivars = class_copyIvarList([Girlfriend class], &count);
for (int i = 0; i < count;  i++) {
Ivar ivar = ivars[i];
//获取成员变量的名字 , 返回的为C语言字符串
const char *name = ivar_getName(ivar);
//转为oc的字符串
NSString *key = [NSString stringWithUTF8String:name];
//获取成员变量的类型
NSString *type = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
NSLog(@"type:%@ , key : %@",type,key);
}
//手动释放数组
free(ivars);


5.运用runtime实现数组可以添加nil

创建一个可变数组分类NSMutableArray+Extension

//类加载的时候调用
+(void)load{
//系统的方法
Method systemMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:));
//自己的方法
Method myMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(my_addObject:));
//替换系统的方法
method_exchangeImplementations(systemMethod, myMethod);
}
- (void)my_addObject:(id)object{
if (object != nil) {
[self my_addObject:object];
}
}

//测试代码
NSMutableArray *testArray = [NSMutableArray new];
[testArray addObject:@"Object-C"];
[testArray addObject:@"swift"];
[testArray addObject:nil];
NSLog(@"%@",testArray);


6.简单的字典转模型

kvc字转模型的时候如果字典中某个key对应的属性不存在会崩溃,通常我们可以通过重写- (void)setValue:(id)value forUndefinedKey:(NSString *)key方法来覆盖掉系统的处理.但是我们也可以运用运行时的只是来解决这个问题,代码如下:

- (void)setValuesForKeysWithDictionary:(NSDictionary<NSString *,id> *)keyedValues{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0 ; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);

NSMutableString *proName = [NSMutableString stringWithUTF8String:name];
//去掉成员变量名前面的_
[proName replaceCharactersInRange:NSMakeRange(0, 1) withString:@""];
if (keyedValues[proName] != nil) {
[self setValue:keyedValues[proName] forKey:proName];
}
}
free(ivars);

}


7.利用runtime实现模型的归档解档

//归档时调用此方法
- (void)encodeWithCoder:(NSCoder *)coder
{
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0 ; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);

NSString *proName = [NSString stringWithUTF8String:name];

id value = [self valueForKey:proName];

[coder encodeObject:value forKey:proName];
}
free(ivars);
}
//解归档的时候调用
- (instancetype)initWithCoder:(NSCoder *)aDecoder{
if (self = [super init]) {
unsigned int count = 0;
Ivar *ivars = class_copyIvarList([self class], &count);
for (int i = 0 ; i < count; i++) {
Ivar ivar = ivars[i];
const char *name = ivar_getName(ivar);

NSString *proName = [NSString stringWithUTF8String:name];

id value = [aDecoder decodeObjectForKey:proName];
[self setValue:value forKey:proName];
}
free(ivars);
}
return self;
}


8.利用runtime实现多层字典转模型

+ (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 arrayContainModelClass] != nil) {

// 转换成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;

}else{
NSAssert(YES, @"%@",key);
}
}

if (value) { // 有值,才需要给模型的属性赋值
// 利用KVC给模型中的属性赋值
[objc setValue:value forKey:key];
}

}

return objc;
}
//子类重写这个方法,返回数组中模型的类名,实现数组属性里的模型转换
+ (NSDictionary *)arrayContainModelClass{
return nil;
}


MJExtension是一套比较完善的字典转模型的第三方框架,大家在项目中可以直接使用,githup上也有比较好的相似框架,大家只要明白原理就好.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: