Runtime

C语言中,在编译期,函数的调用就会决定调用哪个函数,而OC的函数,属于动态调用过程,在编译期并不能决定真正调用哪个函数,只有在真正运行时才会根据函数的名称找到对应的函数来调用,我们写的代码在程序运行过程中都会被转化成runtime的C代码执行,例如[target doSomething];会被转化成objc_msgSend(target, @selector(doSomething)),调用方法其实就是给对象发送消息。

一、对象、类、元类

OC中一切都被设计成了对象,一个类被初始化成一个实例,这个实例是一个对象。objc_class继承于objc_object,所以类也是一个对象。

typedef  struct  objc_class    *Class;

typedef  struct  objc_object  *id;

@interface Object{

Class isa;

}

@interface NSObject {

Class isa OBJC_ISA_AVAILABILITY;

}

structobjc_object {

private:

isa_t isa;

}

struct  objc_class : objc_object {

// Class ISA;

Class superclass;

cache_t cache;//

formerly cache pointer and vtable

class_data_bits_t bits;// class_rw_t * plus custom

rr/alloc flags

}

unionisa_t

{

isa_t() { }

isa_t(uintptr_t value) : bits(value) { }

Class cls;

uintptr_t bits;

}

当一个对象的实例方法被调用的时候,会通过isa找到相应的类,然后在该类的class_data_bits_t中去查找方法。class_data_bits_t是指向了类对象的数据区域。在该数据区域内查找相应方法的对应实现

类对象的类方法调用时,通过类的isa在元类中获取方法的实现。

meta-class存储着一个类的所有类方法。每个类都会有一个单独的meta-class,因为每个类的类方法基本不可能完全相同。

Runtime_第1张图片
类、对象、元类之间的关系

实线是super_class指针,虚线是isa指针

Root class (class)其实就是NSObject,NSObject是没有超类的,所以Root class(class)的superclass指向nil。

每个Class都有一个isa指针指向唯一的Meta class

Root class(meta)的superclass指向Root class(class),也就是NSObject,形成一个回路

每个Meta class的isa指针都指向Root class (meta)

二、获取类的信息

//获取属性列表class_copyPropertyList([self class], &count);

//获取方法列表

class_copyMethodList([self class], &count);

//获取成员变量列表

class_copyIvarList([self class], &count);

//获取协议列表

class_copyProtocolList([self class], &count);

1、自动归档解档

获取类的成员变量列表,把变量名称当做key值进行归档解档

2、字典转模型

获取模型类的属性列表,根据属性字符串去字典里边取值,利用KVC赋值

三、关联对象

分类不能添加属性,因为添加了@property之后并不会自动帮我们生成实例变量以及存取方法,我们可以通过关联对象来给分类添加属性

- (void)setAssociatedObject:(id)associatedObject {

objc_setAssociatedObject(self,AssociatedKey, associatedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

}

- (id)associatedObject {

returnobjc_getAssociatedObject(self, AssociatedKey);

}

objc_setAssociatedObject(id object, const void *key,id value, objc_AssociationPolicy policy)

id object给谁设置关联对象

const void *key关联对象唯一的key,获取时会用到

id value关联对象

objc_AssociationPolicy关联策略

四、动态加载方法

我们知道发送消息是通过objc_msgSend(id, SEL, ...)来实现的,首先通过对象的isa指针找到对象所在的类,在Class中先去cache中通过SEL查找对应函数method,若cache中未找到,再去methodList中查找,若methodlist中未找到,则取superClass中查找。若能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。若是一直查找到根类还是没有找到,就会执行resolveInstanceMethod方法。在这个方法里面,我们可以动态的给类对象或者实例对象动态的增加方法。

1、动态添加实例方法

void dynamicAddressMethod(id self,SEL _cmd) {

NSLog(@"Address --->百子湾");

}

+ (BOOL)resolveInstanceMethod:(SEL)sel{

if(sel ==@selector(address)){

class_addMethod([self class], sel, (IMP)dynamicAddressMethod,"v@:");

returnYES;

}

return[super resolveInstanceMethod:sel];

}

2、动态添加类方法

void dynamicNameMethod(id self,SEL sel,NSString *n) {

NSLog(@"Name ---> %@",n);

}

+ (BOOL)resolveClassMethod:(SEL)sel {

//方法1添加到当前类的元类

ClassmetaClass = objc_getMetaClass(class_getName(self));

if (sel ==@selector(name)) {

class_addMethod(metaClass,sel, (IMP)dynamicNameMethod, "v@:");

returnYES;

}

//方法2添加到NSObject类的元类

//ClassmetaClass = objc_getMetaClass(object_getClassName((id)[NSObject class]));

//if (sel== @selector(name)) {

//class_addMethod(metaClass,sel, (IMP)dynamicNameMethod, "v@:@");

//return YES;

//}

////方法3添加到NSObject

//if (sel== @selector(name)) {

//class_addMethod([NSObject class], sel, (IMP)dynamicNameMethod,"v@:@");

//return YES;

//}

//

return[super resolveClassMethod:sel];

}

六、消息转发

Runtime_第2张图片
消息转发

向一个对象发送其无法处理的消息,如果我们在resolve...Method方法里边添加方法,就会执行消息转发

forwardingTargetForSelector可以返回一个可以执行方法的对象

- (id)forwardingTargetForSelector:(SEL)aSelector {

if(aSelector == @selector(run)){

return[RunMethod new];

}

return[super forwardingTargetForSelector:aSelector];

}

如果我们不实现forwardingTargetForSelector,就会调用methodSignatureForSelector和forwardInvocation

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector{

NSLog(@"对象方法方法签名");

if(aSelector == @selector(run)) {

return[NSMethodSignature signatureWithObjCTypes:"v@:"];

}

return[super methodSignatureForSelector:aSelector];

}

关于生成签名的类型"v@:",每一个方法会默认隐藏两个参数,self、_cmd,self代表方法调用者,_cmd代表这个方法的SEL,签名类型就是用来描述这个方法的返回值、参数的,v代表返回值为void,@表示self,:表示_cmd。

- (void)forwardInvocation:(NSInvocation*)anInvocation {

//自定义转发逻辑1

SEL sel =anInvocation.selector;

RunMethod*runM = [RunMethod new];

if ([runM respondsToSelector:sel]){

[anInvocation invokeWithTarget:runM];

}else {

[superforwardInvocation:anInvocation];

}

////自定义转发逻辑2

////在这里可以改变方法选择器

//[anInvocation setSelector:@selector(unknown)];

////改变方法选择器后,需要指定消息的接收者

//[anInvocation invokeWithTarget:self];

//[anInvocation setSelector:@selector(run)];

//[anInvocation invokeWithTarget:[RunMethod new]];

}

七、方法交换

Method one = class_getInstanceMethod([Method_1

class], @selector(eat));

Method two = class_getInstanceMethod([Mehtod_2class], @selector(eat));

method_exchangeImplementations(one, two);

Method_1 *onem = [[Method_1 alloc]init];

[onem eat];

Mehtod_2 *twom = [[Mehtod_2 alloc]init];

[twom eat];

页面统计:

@implementation UIViewController (statistics)

+load会在类初始加载时调用,+initialize方法是以懒加载的方式被调用的,如果程序一直没有给某个类或它的子类发送消息,那么这个类的+initialize方法是永远不会被调用的

+ (void)load {

MethodoriginalMethod = class_getInstanceMethod([self class],@selector(viewWillAppear:));

Method swizzledMethod= class_getInstanceMethod([self class], @selector(statistics_viewWillAppear:));

method_exchangeImplementations(originalMethod, swizzledMethod);

}

- (void)statistics_viewWillAppear:(BOOL)animated {

NSString*pageName = NSStringFromClass([self class]);

NSLog(@"pageName = %@",pageName);

[selfstatistics_viewWillAppear:animated];

}

- (void)statistics_viewWillDisappear:(BOOL)animated {

[selfstatistics_viewWillDisappear:animated];

}

@end

你可能感兴趣的:(Runtime)