iOS篇-RunTime篇-在实战项目中的应用
TZ : 假如孤独的时候会,我们应该庆幸至少自己还是在这个地球上,没有被遗落在黑暗的宇宙里
一 : 科普一分钟
runtime
大家总能听到,或者在框架中看到,但是在开发项目的时候,似乎没有用到过,读代码的时候也是匆匆略过,但是它的好处确实很多,能帮助我们解决一些曾经绞尽脑汁,但却无功而返的问题,和一些项目需求上的复杂问题,下面一一介绍在实战中的开发技巧.
二 : 项目开发中的实战应用
1. 简介
RunTime
简称运行时,OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的就是消息机制.
相对于C
语言函数的调用,在编译的时候会决定调用哪个函数,而对于OC
的函数,属于动态
调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用.
事实证明,在编译阶段:OC
可以调用任何函数,即使这个函数并未实现,只要声明就不会报错.
而对于C
语言,在编译阶段,调用未实现的函数就会报错.
2.消息发送
任何方法调用本质:发送一个消息,用runtime
发送消息,OC
底层实现通过runtime
实现
示例代码:一个对象如果创建,开辟空间的
//xcode6苹果不推荐使用runtime
//找到build setting -> 搜索msg NO
//id:谁发送消息
//SEL : 发送什么消息
// id objc = [NSObject alloc];
id objc = objc_msgSend([NSObject class], @selector(alloc));
// objc = [objc init];
objc = objc_msgSend(objc, @selector(init));
最终生成消息机制,编译器做的事情,最终代码,需要把当前代码重新编译,用xcode编译器 ,最终生成代码-转换成c++代码
3.对象发送消息
首先创建一个对象,里面有几个我们实现的方法
@interface TZperson : NSObject
-(void)eat;
-(void)TZeat:(NSString*)food;
实现消息发送
TZperson *p = objc_msgSend(objc_getClass("TZperson"),sel_registerName("alloc"));
// p = [p init];
p = objc_msgSend(p, sel_registerName("init"));
//调用
// [p TZeat:@"一块巧克力"];
objc_msgSend(p, @selector(TZeat:),@"一块巧克力");
注意 objc_getClass("TZperson)"
和 [TZperson Class]
同意
过程分析 : 如何调用 TZeat:
方法的
1.通过isa去对应的类中查找,
2.注册方法编号(把方法名转换成方法编号)
3.根据方法编号去查找对应的方法
4.找到的只是最终函数实现的地址,根据地址去方法区调用对应的函数
图解分析 :

4.Runtime交换方法 : 只想修改系统的方法实现
情景 : 当有一项目的一个系统方法 我们以 [UIImage imageNamed:@"1.jpeg"];
为例,为这个方法添加一个功能,判断图片是否读取成功,假如这个项目有200个地方使用了系统的这个方法,我们有什么好的办法,来解决上述需求吗?
有人会想到自定义方法.这个方法倒是可以,但是这么做未免工作量太大了.所以我们想到用runtime
交换方法来实现上述需求
代码示例 :
外部 : 我们要给下面这个 系统方法添加功能
UIImage *image = [UIImage imageNamed:@"1.jpeg"];
内部 : 所以要写一个分类,来完成方法交换
@interface UIImage (image)
+(UIImage*)TZ_imageNamed:(NSString *)name;
@end
//把类加载进内存的时候调用,只会调用一次
+(void)load{
//交换方法 runtime 交换方法
//获取imageNamed
//获取哪个方法
//SEL:获取哪个方法
Method imageNamedMethod = class_getClassMethod(self, @selector(imageNamed:));
//获取TZ_imageNamed
Method TZimageNamedMethod = class_getClassMethod(self, @selector(TZ_imageNamed:));
//交换方法: runtime
method_exchangeImplementations(imageNamedMethod, TZimageNamedMethod);
//调用imageNamed
}
+(UIImage*)TZ_imageNamed:(NSString *)name{
UIImage *image = [UIImage TZ_imageNamed:name];
if (image) {
NSLog(@"加载成功");
}else{
NSLog(@"加载失败");
}
return image;
}
原理 : 与对象发送消息相似,只不过在指向方法区的时候 交换了两个函数的方法实现.
5. Runtime添加方法
需求分析 : 某个对象没有实现某个方法,但是我们却想用如何实现
外部 :
-(void)TZaddMethod{
TZperson *person = [[TZperson alloc]init];
//执行为实现方法
[person performSelector:@selector(TZplay:) withObject:@"人鱼表演"];
}
内部
//任何方法默认都有两个隐式参数,self,_cmd
//什么时候调用:只要一个对象调用了一个未实现的方法就会调用这个方法,进行处理
//作用 : 动态添加方法,处理未实现
+(BOOL)resolveInstanceMethod:(SEL)sel{
if (sel == NSSelectorFromString(@"TZplay:")) {
//TZdrink
//Class : 给哪个类添加方法
//SEL : 添加哪个方法
//IMP : 方法实现 ==>函数 ==>函数入口==>函数名
//type : 方法类型
class_addMethod(self, sel, (IMP)tzaaa, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
void tzaaa(id self,SEL _cmd,NSString *play){
NSLog(@"观赏了%@",play);
}
官方文档 :

6.RunTime动态添加属性
需求分析 : 当我们想给系统扩充一个属性的时候,大家首先做的 是 建立类别,但是类别中的 属性 是没有set
和 get
的 如何实现呢.用runtime
来实现看看.
示例代码 :
// @property分类:只会生成get ,set方法生明,不会生成实现,也不会生成下划线成员属性
@property NSString *name;
-(void)setName:(NSString *)name{
// _name = name;
// object:给哪个对象添加属性
//key : 属性名称
//value : 属性值
//policy : 保存策略
objc_setAssociatedObject(self, @"name", name, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name{
// return _name;
return objc_getAssociatedObject(self, @"name");
}
原理分析 : 动态添加属性,就是让某个属性与对象产生关联,一般都是针对系统的类
7.runtime字典转模型
需求分析 : 自动根据模型来解析字典,对模型和子模型进行赋值
外部
TZStatesItem *item = [TZStatesItem modelWithDic:dict];
内部
@interface NSObject (Model)
//字典转模型
+(instancetype)modelWithDic:(NSDictionary*)dic;
@end
//本质:创建谁的对象
+(instancetype)modelWithDic:(NSDictionary*)dic{
id objc = [[self alloc]init];
//Ivar:成员变量 以下划线开头
//property:属性
//runtime : 根据模型属性,去字典中取出对应的value给模型属性赋值
//1.获取模型中所有成员变量 key
// 获取哪个类的成员变量
//count : 成员变量个数
unsigned int count = 0;
//获取成员变量数组
Ivar *ivarList = class_copyIvarList(self, &count);
//遍历所有成员变量名字
for (int i = 0; i < count; i++) {
//获取成员变量
Ivar ivar = ivarList[i];
//获取成员变量名字
NSString *ivarName = [NSString stringWithUTF8String:ivar_getName(ivar)];
NSString *ivarType = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
// @\"user\" -> user
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"\"" withString:@""];
ivarType = [ivarType stringByReplacingOccurrencesOfString:@"@" withString:@""];
//获取key
NSString *key = [ivarName substringFromIndex:1];
//去字典中查找对应的value
id value = dic[key];
//二级转换 : 判断下value 是否是字典,如果是,字典转换成对应的模型,并且是自定义对象才转换
if ([value isKindOfClass:[NSDictionary class]] && ![ivarType hasPrefix:@"NS"]) {
//获取类
Class modelClass = NSClassFromString(ivarType);
value = [modelClass modelWithDic:value];
}
//给模型中属性赋值
if (value) {
[objc setValue:value forKey:key];
}
}
//2.根据属性名去字典中查找value
//3.给模型中属性赋值 KVC
return objc;
}
延展 : 上述获取属性列表和成员列表功能也可以用于,归档和反归档的需求中,减少了大量冗余代码.
三 : 总结
总体来说,runtime
在我们的实际开发中运用的不多,尽量不要为了运用而运用,在使用中,解决一些棘手和难处理的问题.活学活用.