一.什么是 runtime ?
rumtime是运行时库,基于c语言的api接口,
作用是动态的创建一个类 动态的添加属性和方法 遍历属性和方法名 动态修改属性和方法等等
1.能动态产生一个类,一个成员变量,一个方法
2.能动态修改一个类,一个成员变量,一个方法
3.能动态删除一个类,一个成员变量,一个方法
//类在runtime中的表示
struct objc_class {
Class isa;//指针,顾名思义,表示是一个什么,
//实例的isa指向类对象,类对象的isa指向元类
#if !__OBJC2__
Class super_class; //指向父类
const char *name; //类名
long version;
long info;
long instance_size
struct objc_ivar_list *ivars //成员变量列表
struct objc_method_list **methodLists; //方法列表
struct objc_cache *cache;//缓存
//一种优化,调用过的方法存入缓存列表,下次调用先找缓存
struct objc_protocol_list *protocols //协议列表
#endif
} OBJC2_UNAVAILABLE;
/* Use `Class` instead of `struct objc_class *` */
二.runtime的头文件
#import <objc/runtime.h> 包含对类、成员变量、属性、方法的操作
#import <objc> 包含消息机制
三.消息发送步骤
1)受限检测这个 selector 是不是要忽略。比如Mac OS X 开发,有了垃圾回收就不理会retain , release这些函数。
2)检测这个 selector 的 target 是不是 nil , Objt 允许我们对一个 nil 对象执行任何方法不会 Crash ,因为运行时会被忽略掉。
3)如果上面两步都通过了,那么就开始查找这个类的实现 IMP , 先从 cache 哩查找,如果找到了就运行对应的函数去执行相应的代码。
4)如果 cache 找不到就找类的方法列表中是否有对应的方法。
5)如果类的方法列表找不到就到父类的方法列表中查找,一直找到 NSObjiect 类为止。
6)如果还找不到,进入动态方法解析。
四.常用方法
//动态拦截调用
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
//遍历相关
class_copyMethodList(返回一个指向类的方法数组的指针)
class_copyIvarList (返回一个指向类的成员变量数组的指针)
class_copyPropertyList(返回一个指向类的属性数组的指针)
//修改属性
objc_getAssociatedObject
objc_setAssocaitedObject
//交换
class_getInstanceMethod
method_exchangeImplementation(systemMethod, swizzMethod)
五.应用
1)动态的遍历一个类的所有成员变量,用于字典转模型,归档解档操作
- (void)viewDidLoad {
[super viewDidLoad];
/** 利用runtime遍历一个类的全部成员变量
1.导入头文件<objc/runtime.h> */
unsigned int count = 0;
/** Ivar:表示成员变量类型 */
Ivar *ivars = class_copyIvarList([BDPerson class], &count);//获得一个指向该类成员变量的指针
for (int i =0; i < count; i ++) {
//获得Ivar
Ivar ivar = ivars[i]; //根据ivar获得其成员变量的名称--->C语言的字符串
const char *name = ivar_getName(ivar);
NSString *key = [NSString stringWithUTF8String:name];
NSLog(@"%d----%@",i,key);
}
}
2)可以利用遍历类的属性,来快速的进行归档操作;将从网络上下载的json数据进行字典转模型。
注意:归档解档需要遵守<NSCoding>协议,实现以下两个方法
- (void)encodeWithCoder:(NSCoder *)encoder{
//归档存储自定义对象
unsigned int count = 0;
//获得指向该类所有属性的指针
objc_property_t *properties = class_copyPropertyList([BDPerson class], &count);
for (int i =0; i < count; i ++) {
//获得
objc_property_t property = properties[i]; //根据objc_property_t获得其属性的名称--->C语言的字符串
const char *name = property_getName(property);
NSString *key = [NSString stringWithUTF8String:name];
// 编码每个属性,利用kVC取出每个属性对应的数值
[encoder encodeObject:[self valueForKeyPath:key] forKey:key];
}}
- (instancetype)initWithCoder:(NSCoder *)decoder{
//归档存储自定义对象
unsigned int count = 0;
//获得指向该类所有属性的指针
objc_property_t *properties = class_copyPropertyList([BDPerson class], &count);
for (int i =0; i < count; i ++) {
objc_property_t property = properties[i]; //根据objc_property_t获得其属性的名称--->C语言的字符串
const char *name = property_getName(property);
NSString *key = [NSString stringWithUTF8String:name]; //解码每个属性,利用kVC取出每个属性对应的数值
[self setValue:[decoder decodeObjectForKey:key] forKeyPath:key];
}
return self;
}
3)交换方法
一.例如数组越界问题,防止系统崩溃(NSMutableArray 添加空值会出现崩溃)
① 新建一个分类,分类中引入头文件,实现下列方法
+ (void)load{
Method orginalMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(addObject:));
Method newMethod = class_getInstanceMethod(NSClassFromString(@"__NSArrayM"), @selector(Mn_addObject:));
method_exchangeImplementations(orginalMethod, newMethod);
}
- (void)Mn_addObject:(id)object{
if (object) {
[self Mn_addObject:object];
}
}
②在项目文件中,正常使用,若添加空值,不会崩溃只会出现报警信息
NSMutableArray *arr = [NSMutableArray array];
[arr addObject:nil];
二.生命周期
①创建分类
//load方法会在类第一次加载的时候被调用
//调用的时间比较靠前,适合在这个方法里做方法交换
+ (void)load{
//方法交换应该被保证,在程序中只会执行一次
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
//获得viewController的生命周期方法的selector
SEL systemSel = @selector(viewWillAppear:);
//自己实现的将要被交换的方法的selector
SEL swizzSel = @selector(swiz_viewWillAppear:);
//两个方法的Method
Method systemMethod = class_getInstanceMethod([self class], systemSel);
Method swizzMethod = class_getInstanceMethod([self class], swizzSel);
//首先动态添加方法,实现是被交换的方法,返回值表示添加成功还是失败
BOOL isAdd = class_addMethod(self, systemSel, method_getImplementation(swizzMethod), method_getTypeEncoding(swizzMethod));
if (isAdd) {
//如果成功,说明类中不存在这个方法的实现
//将被交换方法的实现替换到这个并不存在的实现
class_replaceMethod(self, swizzSel, method_getImplementation(systemMethod), method_getTypeEncoding(systemMethod));
}else{
//否则,交换两个方法的实现
method_exchangeImplementations(systemMethod, swizzMethod);
}
});
}
- (void)swiz_viewWillAppear:(BOOL)animated{
//这时候调用自己,看起来像是死循环
//但是其实自己的实现已经被替换了
[self swiz_viewWillAppear:animated];
NSLog(@"swizzle");
}
②在一个自己定义的viewController中重写viewWillAppear,Run起来看看输出吧!
- (void)viewWillAppear:(BOOL)animated{
[super viewWillAppear:animated];
NSLog(@"viewWillAppear");
}
4)关联属性
typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
OBJC_ASSOCIATION_ASSIGN = 0, //相当于属性中的assign
OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, //retain,monatomic
OBJC_ASSOCIATION_COPY_NONATOMIC = 3, //copy,nonatomic
OBJC_ASSOCIATION_RETAIN = 01401, //retain
OBJC_ASSOCIATION_COPY = 01403 //copy
};
//添加关联对象
- (void)addAssociatedObject:(id)object{
objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//获取关联对象
- (id)getAssociatedObject{
return objc_getAssociatedObject(self, _cmd);
}
//样例
- (void)viewDidLoad {
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
UIButton *btn = [UIButton buttonWithType:UIButtonTypeSystem];
btn.backgroundColor = [UIColor blackColor];
btn.frame = CGRectMake(100, 100, 60, 30);
[self.view addSubview:btn];
objc_setAssociatedObject(btn, myBtnKey, @"mybtn", OBJC_ASSOCIATION_RETAIN_NONATOMIC);
[btn addTarget:self action:@selector(btnClick:) forControlEvents:UIControlEventTouchUpInside];
}
- (void)btnClick:(id)sender {
NSString *str = objc_getAssociatedObject(sender, myBtnKey);
/**
* CODE
*/
}
5)方法拦截
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resolveInstanceMethod:(SEL)sel;
_objc_msgForward是 IMP类型,用于消息转发的:当向一个对象发送一条消息,但它并没有实现的时候,_objc_msgForward会尝试做消息转发。
IMP msgForward = _objc_msgForward;
如果手动调用objc_msgForward,将跳过查找IMP的过程,而是直接出发“消息转发”,进入如下流程:
1)+ (BOOL)resolveInstanceMethodL:(SEL)sel 实现方法,指定是否动态添加方法。若返回NO,则进入下一步,若返回YES,则通过class_addMethod函数动态地添加方法,消息得到处理,此流程完毕。
2)在第一步返回的是NO时,就会进入 -(id)forwardTargetForSelector:(SEL)aSelector 方法,这是运行时给我们的第二次机会,用于指定哪个对象响应这个selector。不能指定为self。若返回nil,表示没有响应者,则会进入第三不。若返回某个对象,则会调用该对象的方法。
3)若第二部返回的是nil,则我们首先要通过 -(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector 指定方法签名,若返回nil,则表示不处理。若返回方法签名,则会进入下一步。
4)当第三步放回方法签名后,就会调用 -(void)forwardInvocation:(NSInvocation *)anInvocation 方法,我们可以通过anInvocation对象做很多处理,比如修改实现方法,修改响应对象等。
5)若没有实现 -(void)forwardInvocation:(NSInvocation *)anInvocation 方法,那么会进入 -(void)doesNotRecognizeSelector:(SEL)aSelector方法。若我们没有实现这个方法,那么就会crash,然后提示打不到响应的方法。到此,动态解析的流程就结束了。
6)runtime如何实现weak变量的自动置nil?
runtime对注册的类会进行布局,对于weak对象会放入一个hash表中。用weak指向的对象内存地址作为key,当此对象的引用计数为0的时候会dealloc。假如weak指向的对象内存地址是a,那么就会以a为键,在这个weak表中搜索,找到所有以a为键的weak对象,从而设置为nil。
weak修饰的指针默认值是nil(在Objective-C中向nil发送消息是安全的)