runtime简称运行时,就是系统在运行的时候的一些机制(主要是消息机制)。对于C语言,函数在编辑的时候已经决定调用哪个函数,编辑完成后直接顺序执行。OC的函数调用为消息发送,只有在真正运行的时候才会根据函数的名称查找对应的函数进行调用。
我们写的代码在程序运行过程中都会被转化成runtime的C代码执行。例如:[target doSomething];会被转化成obj_msgSend(large,@selector(doSomething));
OC中一切都被设计成了对象,当一个类被初始化成一个实例后,这个实例就是一个对象;实际上一个类在OC中也是一个对象,在runtime中用结构体表示。相关的定义:
///描述类中的一个方法
typedef struct objc_method *Method;
///实例变量
typedef struct objc_ivar *Ivar;
///类别Category
typedef struct objc_category *Category;
///类中声明的属性
typedef struct objc_property *objc_property_t;
类在runtime中的表现
//类在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;
之前有一个分享问runtime是什么?他说是指c的方法可以获取类的一些信息(包括属性列表,方法列表,成员变量列表,和遵循的协议列表)。先看代码:
#import <objc/runtime.h>
unsigned int count;
//获取属性列表
objc_property_t *propertyList = class_copyPropertyList([self class], &count);
for (int i=0; i<count; i++) {
const char *propertyName = property_getName(propertyList[i]);
NSLog(@"property -->>%@",[NSString stringWithUTF8String:propertyName]);
}
//获取方法列表
Method *methodList = class_copyMethodList([self class], &count);
for (int i=0; i<count; i++) {
Method method = methodList[i];
NSLog(@"method-->>%@",NSStringFromSelector(method_getName(method)));
}
//获取成员变量列表
Ivar *ivarList = class_copyIvarList([self class], &count);
for (int i=0; i<count; i++) {
Ivar myIvar = ivarList[i];
const char *ivarName = ivar_getName(myIvar);
NSLog(@"Ivar---->>%@",[NSString stringWithUTF8String:ivarName]);
}
///获取需要实现协议列表
__unsafe_unretained Protocol **protocolList = class_copyProtocolList([self class], &count);
for (int i=0; i<count; i++) {
Protocol *myProtocol = protocolList[i];
const char *protocolName = protocol_getName(myProtocol);
NSLog(@"protocol--->>%@",[NSString stringWithUTF8String:protocolName]);
}
实际除了可以通过C的函数获取一些类的信息外(包括方法交换),runtime不仅是这样,还有更多的功能,如果拦截调用,动态添加方法,添加属性。实现这个几方法可以看出,当oc在运行的时候,我们可以动态的为类或对象干更多的事情。 先了解一下在运行过程中方法调用的过程。
关于重写父类的方法,只是在当前类对象中找到了这个方法后,就不会在父类中查找了。如果想调用父类的方法,需要使用super这个编译器标识,它会在运行时跳过当前的类对象直接去父类中查找。
下面这个题和上面的解释有点绕:
@implementation Son : Father
- (id)init
{
self = [super init];
if (self)
{
NSLog(@"%@", NSStringFromClass([self class]));
NSLog(@"%@", NSStringFromClass([super class]));
}
return self;
}
@end
输入结果是两个Son;原理请看http://www.cocoachina.com/ios/20141224/10740.html
实际还有一个有意思的题:
1. NSMutableArray *ary = [[NSString alloc] initWithString:@"test"];
2. [ary addObject:@"error"];
上面的说到了,如果ary没有找到addObject的方法,就会转向拦截调用,拦截调用是指在找不到调用的方法程序崩溃之前,我们可以重写NSObject的四个方法来处理:
+ (BOOL)resolveClassMethod:(SEL)sel;
+ (BOOL)resloveInstanceMethod:(SEL)sel;
///后两个方法需要转发到其他的类处理
- (id)forwardingTargetForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
这个是基于上面的拦截调用来实现的,当前对象调用的方法无法在方法列表中找到时,就会调用 + (BOOL)resloveInstanceMethod:(SEL)sel; 而这时我们可以在里面直接加一个方法
void dynamicMethodIMP(id self, SEL _cmd) { //定义一个新方法
NSLog(@" >> dynamicMethodIMP");
}
///当前对象的方法没有被找到时,会调用 resolveInstanceMethod,我们里面把没有找到的方法替换为了dynamicMethodIMP。
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
class_addMethod([self class], sel, (IMP)dynamicMethodIMP, "v@:");
return [super resolveClassMethod:sel];
}
这个我们直接 在NSObject里面的扩展加了一个方法
//添加关联对象
- (void)addAssociatedObject:(id)object{
objc_setAssociatedObject(self, @selector(getAssociatedObject), object, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//获取关联对象
- (id)getAssociatedObject{
return objc_getAssociatedObject(self, _cmd);
}
注意:这里不用声明一个全局变量(用它的地址作为关联对象的key),而是用getAssociatedObject的方法地址作为唯一的key,_cmd代码当前调用方法的地址 在runtime中,我们还有方法交换的姿势没有说。