1.什么是runtime
runtime是苹果封装的底层C语言api,包含许多功能强大的C语言数据结构和函数.实际上我们平时所编写的OC代码,底层都是基于runtime实现的.也就是说我们平时编写的代码,最终都转换成了底层的runtime代码.
runtime有什么作用呢?
1> 能动态产生一个类、一个成员变量、一个方法
2> 能动态修改一个类、一个成员变量、一个方法
3> 能动态删除一个类、一个成员变量、一个方法
2.如何使用runtime
在我们实际工作中也许很少直接使用runtime,但我却时时刻刻间接的和runtime打交道.那么在什么情况下我们需要直接使用runtime呢,以下是我列举的一些常用方式.
1.枚举一个类里面的所有方法,变量和属性.不管是自定义的还是系统自带的都可以获取.
2.交换类或者对象中的方法名.
3.常见的函数、头文件
#import <objc/runtime.h> : 成员变量、类、方法
Ivar * class_copyIvarList : 获得某个类内部的所有成员变量
Method * class_copyMethodList : 获得某个类内部的所有方法
Method class_getInstanceMethod : 获得某个实例方法(对象方法,减号-开头)
Method class_getClassMethod : 获得某个类方法(加号+开头)
method_exchangeImplementations : 交换2个方法的具体实现
#import<objc/message.h> : 消息机制
objc_msgSend(….)
3.runtime实例分析
自定义一个类MRPerson
,该类中含有两个属性,一个方法:
//1.导入头文件 #import<objc/message.h> //2.属性 @property (strong, nonatomic) NSString *name; @property (assign, nonatomic) NSInteger age; //3.方法 -(void)run; //- (void)run{ // NSLog(@"-------run--------"); //}
1.使用objc_msgSend(…)发消息
我们平时常常说调用OC类中的某个方法,严格来说这样并不正确.而应该说成向某个对象发送消息.
-(void)useObjcSendMessage { MRPerson* person = [[MRPerson alloc]init]; //[person setAge:10]; objc_msgSend(person, @selector(setAge:), 10); printf("%d",(int)objc_msgSend(person, @selector(age))); }
在这个方法中,objc_msgSend(person, @selector(setAge:), 10)
就相当于
[person setAge:10]
.用通俗的话说,就是向对象person
,发送一个消息setAge:
,该消息中含有一个参数10
.
其实我们平时调用OC方法底层都是通过objc_msgSend
进行消息发送的.
2.获取类中的成员变量
1.Ivar *class_copyIvarList(Class cls, unsigned int *outCount)
这个方法就是用来获取一个类中所有的成员变量的:
返回值:一个指向Ivar数组的指针,里面保存的就是类中所有的变量
第一个参数:要从哪个类中遍历所有的变量.
第二个参数:该类中变量的个数
2.const char *ivar_getName(Ivar v)
返回值:变量的名称,一个常量字符串
参数:对应的变量
3.const char *ivar_getTypeEncoding(Ivar v)
返回值:变量的类型
参数:对应的变量
-(void)getIvar { unsigned int outCount= 0; Ivar* ivar = class_copyIvarList([MRPerson class], &outCount); for (int i = 0 ; i < outCount; i++) { const char* name = ivar_getName(ivar[i]); const char* type = ivar_getTypeEncoding(ivar[i]); printf("%d %s %s\n",i,name,type); } }
输出:
0 _name @"NSString" 1 _age i
3.交换方法Swizzing
交换方法我们需要使用以下三个函数:
- Method class_getInstanceMethod : 获得某个实例方法(对象方法,减号-开头)
- Method class_getClassMethod : 获得某个类方法(加号+开头)
- method_exchangeImplementations : 交换2个方法的具体实现
在iOS中通过下标取出NSArray中的成员,如果下标越界,程序就会崩溃.
现在我想自定义一个方法- (id)mr_objectAtIndex:(NSUInteger)index
来进行下标访问,当越界的时候会有提示,并且返回nil.
给NSArray写一个分类,分类实现如下:
#import <Foundation/Foundation.h> #import <objc/runtime.h> @implementation NSArray (MR) //__NSArrayI objectAtIndex: /** * 只要类被装载到内存中,就会调用1次 */ +(void)load { Method one = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(objectAtIndex:)); Method two = class_getInstanceMethod(NSClassFromString(@"__NSArrayI"), @selector(mr_objectAtIndex:)); method_exchangeImplementations(one, two); } - (id)mr_objectAtIndex:(NSUInteger)index { if (index >= self.count) { NSLog(@"数组越界"); return nil; } else{ //注意这里:由于方法已经被交换了,这里相当于[self objectAtIndex:index] 并不会死循环. return [self mr_objectAtIndex:index]; } } @end
在ViewController.m文件中实现以下方法,并且调用:
-(void)swizzlingMethod { NSArray *array = @[@(1),@(2),@(3)]; NSLog(@"%@",array[3]); }
输出结果:
2015-09-17 19:59:54.331 runtime机制[21741:1450284] 数组越界 2015-09-17 19:59:54.332 runtime机制[21741:1450284] (null)
下面的程序是我写的NSObject的分类,大家可以通过调用下面两个方法来分别实现类方法的交换,对象方法的交换.
//类方法 + (void)swizzleClassMethod:(Class)class originSelector:(SEL)originSelector otherSelector:(SEL)otherSelector //对象方法 + (void)swizzleInstanceMethod:(Class)class originSelector:(SEL)originSelector otherSelector:(SEL)otherSelector
#import <objc/runtime.h> @implementation NSObject(Extension) + (void)swizzleClassMethod:(Class)class originSelector:(SEL)originSelector otherSelector:(SEL)otherSelector { Method otherMehtod = class_getClassMethod(class, otherSelector); Method originMehtod = class_getClassMethod(class, originSelector); // 交换2个方法的实现 method_exchangeImplementations(otherMehtod, originMehtod); } + (void)swizzleInstanceMethod:(Class)class originSelector:(SEL)originSelector otherSelector:(SEL)otherSelector { Method otherMehtod = class_getInstanceMethod(class, otherSelector); Method originMehtod = class_getInstanceMethod(class, originSelector); // 交换2个方法的实现 method_exchangeImplementations(otherMehtod, originMehtod); } @end @implementation NSArray(Extension) + (void)load { [self swizzleInstanceMethod:NSClassFromString(@"__NSArrayI") originSelector:@selector(objectAtIndex:) otherSelector:@selector(hm_objectAtIndex:)]; } - (id)hm_objectAtIndex:(NSUInteger)index { if (index < self.count) { return [self hm_objectAtIndex:index]; } else { return nil; } } @end @implementation NSMutableArray(Extension) + (void)load { [self swizzleInstanceMethod:NSClassFromString(@"__NSArrayM") originSelector:@selector(addObject:) otherSelector:@selector(hm_addObject:)]; [self swizzleInstanceMethod:NSClassFromString(@"__NSArrayM") originSelector:@selector(objectAtIndex:) otherSelector:@selector(hm_objectAtIndex:)]; } - (void)mr_addObject:(id)object { if (object != nil) { [self hm_addObject:object]; } } - (id)mr_objectAtIndex:(NSUInteger)index { if (index < self.count) { return [self hm_objectAtIndex:index]; } else { return nil; } } @end