一、什么是RunTime?
RunTime简称运行时,OC就是运行时机制,也就是在运行时候的一些机制,其中最主要的就是消息机制。
对于C语言来说,函数的调用在编译的时候回决定调用哪个函数。
对于OC来说,属于动态调用过程,在编译的时候并不能决定真正调用哪个函数,只有在真正运行的时候才会根据函数的名称找到对应的函数来调用。
事实证明:
在编译阶段,OC可以调用任何函数,即使这个函数并未实现,只要生命过就不会报错
在编译阶段,C语言调用来实现的函数,就会报错
二、RunTime作用
1.发送消息
任何方法调用的本质,就是发送消息
objc_msgSend,只有对象才能发送消息,因此以objc开头
使用消息机制前提,必须导入#import <objc/message.h>
RunTime都有一个前缀,说的事情使用谁
[[NSObject alloc]init];
//最终生成消息机制,编译器做的事情
//最终代码:需要把当前代码重新编译,用Xcode编译器,clang
//clang -rewrite-objc main.m 查看最终生成代码
//[NSObject alloc]转化为消息
//id 谁发送消息
//SEL: 发送什么消息
id objc = ((NSObject *(*)(id, SEL))(void *)objc_mesSend)([NSObject class], @selector(alloc));
//开发中
//找到build setting-->搜索msg
id objc = objc_msgSend([NSObject class], @selector(alloc));
等价于:
id objc = objc_msgSend(objc_getClass("NSObject"), sel_registerName("alloc"));
//[objc init]转消息
objc = objc_msgSend(objc, @selector(init));
可以帮我们调用私有的方法,OC中没有真正私有的东西,因为有消息机制
调用带参数的方法
objc_msgSend(objc, @selector(run:), 20); //方法后面是需要传的参数
方法调用的流程:
怎么去调用一个方法?
如果是调用一个对象方法的时候:对象方法保存到类对象中,通过指针找到对应的对象,然后通过对象的isa指针找到对应的类对象,然后就可以调用类对象中的方法了。
如果是调用一个类方法:类方法保存在元类对象中,通过类对象的isa指针可以找到元类对象,然后在元类对象中可以找到方法对应的方法编号。
2.交换方法
需求:每次让UIImage加载图片的时候,告诉我是否加载成功
1.自定义UIImage
弊端:每次使用,都需要导入
项目大了,没办法实现
2.runtime
给系统的imageNamed添加工能,只能使用runTime
-给系统的方法添加分类
-自己实现一个带有扩展功能的方法
-交换方法,只需要交换一次
#import "UIImage+Image.h"
#import <objc/message>
@implementation UIImage (Image)
+(void)load
{
//把类加载进内存的时候调用,只会调用一次
//获取类方法
Method imageNamedMethod = class_getClassMethod(self,@selector(imageNamed:));
Method imageNamedMethod = class_getClassMethod(self,@selector(wly_imageNamed:));
method_exchangeImplementations();
}
+(void)initialize
{
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
}
+(UIImage *)wly_imageNamed:(NSString *)name
{
UIImage *image = [UIImage wly_imageNamed:name]; //注意这里也要改,否则会造成死循环
if(image){
NSLog(@"加载成功");
}else{
NSLog(@"加载失败");
}
return image;
}
@end
3.动态添加方法
RunTime:OC都是懒加载机制,只要一个方法实现了,它就会马上添加到方法列表中
适用于一些不常用的功能
@implementation Person
///任何方法都有两个隐式参数self,_cmd
//_cmd:当前方法的编号
+(BOOL)resolveInstanceMethod:(SEL)sel
{
NSLog(@"NSStringFromSelector(sel)");
//if([NSStringFromSelector(sel) isEqualToString:@"eat"])
if(sel == NSSelectorFromString(@"eat")){
//给哪个类添加方法
//SEL:添加哪个方法
//IMP:方法的实现==函数名
//type:方法的类型
class_addMethod(self, sel, (IMP)method, "v@:"); //查文档
}else{
}
//动态添加方法,处理未实现的方法
return [super resolveInstanceMethod:sel];
}
//函数,没有返回值
void method(id self, SEL _cmd){
}
@end
4.动态添加属性
需求:让一个NSObject类保存一个字符串
NSObject *objc = [[NSObject alloc] init];
//添加一个分类
@interface NSObject (Property)
//只会生成get,set方法的声明,不会生成实现,也不会生成下划线成员属性
@property NSString *name;
@end
@implementation NSObject (Property)
static NSString *_name;
-(void)setName:(NSString *)name
{
//让这个字符串与当前对象产生联系
_name = name;
}
-(NSString *)name
{
return _name;
}
@end
使用runtime
//添加一个分类
@interface NSObject (Property)
//只会生成get,set方法的声明,不会生成实现,也不会生成下划线成员属性
@property NSString *name;
@end
@implementation NSObject (Property)
//static NSString *_name;
-(void)setName:(NSString *)name
{
//让这个字符串与当前对象产生联系
//object:给哪个对象添加属性
//key:属性名称
//value:属性值
//policy:保存策略
objc_setAssociatedObject(self,@"name",name,OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
-(NSString *)name
{
return objc_getAssociatedObject(self,@"name");
}
@end
5.字典转模型
NSString *filePath = [NSBundle mainBundle] pathFoResource:@"123.plist" ofType:nil];
NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:filePath];
WLyDataModel *data = [WLyDataModel itemWithDict:dict];
//根据dictionary中的key自动生成属性代码
//调用[dict createProperty];
-(void)createProperty
{ NSMutableString *codes = [NSMutableString string];
[self enumerateKeysAndObjectsUsingBlock:^(id _Nullable key, id _Nullable value, BOOL _Nullable stop){
if([value isKindOfClass:[NSString class]]){
//判断是否是当前类或者是子类
NSString *code = [NSString stringWithFormat:@"@property (nonatomic, strong) NSString *%@", key];
}else if(){
//基本数据类型注意修改修饰符
}
[codes appendString:code];
[codes appendString:@"\n"];
}];
NSLog(@"%@", codes);
}
利用KVC字典转模型
[item setValuesForKeysWithDictionary:dict]; //把字典中所有值给模型的属性赋值
[dict enumerateKeyAndObjectUsingBlock:^(id _Nullable key, id _Nullable value, BOOL * _Nullable){
//要去模型中查找有没有对应的key
[item setValue:value forKey:key];
//去模型中查找有没有对应的属性的set方法,有的话就把value传给set方法,调用赋值
//找不到的话,去模型中查找有没有对应的属性,有的话直接访问属性赋值
//去模型中查找有没有_source属性,有的话直接访问属性赋值
//找不到,就会直接报错
}];
但是往往模型只保存最重要的是数据,模型中的数据和字典中的数据并不是一一对应的,因此,这种情况下,我们就不能使用KVC了
如果要处理找不到Key的问题,可以重写setValue:forUndefinedKey:(NSString *)key
//系统找不到就会调用这个方法报错
-(void)setValue:(id)value forUndefinedKey:(NSString *)key
{
}
利用RunTime字典转模型
//NSObject + model.h
#import <objc/message.h>
@implementation NSobject (Model)
+(instancetype)modelWithDict:(NSDictionary *)dict
{
id objc = [[self alloc] init];
//runTime:根据模型中属性,去字典中取出对应的value给模型赋值
//获取模型中所有属性
//根据属性名去字典中查找value
//给模型中属性赋值 KVC
//class_copyPropertyList(); 获取属性
/*
获取那个类的成员变量
成员变量的总数
*/
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)]; //拿到成员变量的名字
id value = dict[ivarName];
//把字典转换成模型
NSString *str = [NSString stringWithUTF8String:ivar_getTypeEncoding(ivar)];
//获取类
[str stringByReplaceingOccurrencesOfString:@"\" withString:@""];
[str stringByReplaceingOccurrencesOfString:@"@" withString:@""];
if(value isKindOfClass:[NSDictionary class] && [str hasPrefix:@"NS"]){
Class modelClass = NSClassFromString(str);
value = [modelClass modelWithDict:value];
}
if(value){
[objc setValue:dict[ivarName] forKey:ivarName];
}
}
return objc;
}
@end