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