五点之后写bBug

runtime

2021-05-20  本文已影响0人  GFan

一、简介

OC 是一门动态的语言,而 Runtime 是使用 OC 开发 iOS 应用的一个核心技术。OC 中很多动态的特性都是通过 Runtime 来实现的。

当一个类进行方法的调用时,本质是利用 Runtime 的消息机制发送一条消息,从而达到调用方法的目的,其底层就是调用了  objc_msgSend 函数。

想要发送一条消息给某个对象,最多会经历三个阶段,即消息发送动态方法解析消息转发。如果经历这三个阶段都没有找到该消息,那么程序在运行时就会抛出一个错误 :unrecognized selector sent to instance。

二、消息发送

当我们给某个对象发送消息时,通过该对象的 isa 找到该对象的类对象。在类对象中的方法缓存中查找有没有该方法,如果有即进行调用。

如果在类对象的缓存中没有该方法,那就在该类对象存储方法的位置查找方法,如果有该方法,先将该方法加入类对象的方法缓存中去,然后调用该方法。

如果没有找到该方法,通过 superclass 指针找其父类,先查找父类的方法缓存中有没有该方法,如果有该方法那么就进行调用,如果缓存中没有该方法,那么就从父类的方法列表中查找方法,如果找到该方法,先缓存到类对象的方法缓存中,再进行调用

如果元类对象的父类还是没找到该方法,就找父类的父类依次类推,重复步骤3。如果最终都没有找到该方法就进行第二个阶段即动态方法解析。

三、动态方法解析

当消息发送阶段完成后仍然没有找到对应的方法,那么就会来到动态方法解析阶段。动态方法解析可以帮助我们拦截到消息发送阶段未实现的方法。在动态方法解析提供的方法中,利用 runtime 动态的添加某个方法。当消息发送阶段调用的方法没有找到的时候,来调用我们动态添加的方法。

下面以实例方法为例进行说明:

我们定义一个类,该类中只有方法的声明而没有实现。

@interface SomeClass : NSObject

- (void)foo;

@end

因为是实例方法,所以我们在 SomeClass 的实现文件中需要重写 resolveInstanceMethod 方法,在该方法中来实现动态方法解析。

假设当我们调用 foo 方法时候,因为 foo 方法未被实现,来到动态方法解析阶段,我们让其调用 bar 方法的实现。

+ (BOOL)resolveInstanceMethod:(SEL)sel {    // 判断方法名是否为 foo   

 if (sel == @selector(foo)) { // 获取被添加的方法        

Method method = class_getInstanceMethod(self, @selector(bar));// 获取方法实现//        

IMP imp = class_getMethodImplementation(self, @selector(bar));     

IMP imp = method_getImplementation(method); // 获取 TypeEncoding        

const char *types = method_getTypeEncoding(method);// 动态添加方法        

class_addMethod(self, sel, imp, types);                

return YES;    

}     

   return [super resolveInstanceMethod:sel];

}

利用 runtime 的 class_addMethod 函数动态的添加方法

第1个参数:如果是添加实例方法,传入类对象,如果是添加类方法传入元类对象

第2个参数:被动态添加方法的方法名称

第3个参数:动态添加方法的方法实现也就是函数地址

第4个参数:动态添加的方法的参数和返回值的 typeEncoding

动态解析阶段添加完方法后,会重新执行一遍消息发送的流程。

如果没有实现动态方法解析,那么就会来到第三个阶段消息转发阶段

四、消息转发

当前2个阶段都没有找到对应的消息,最终就会来到消息转发阶段,我们还是以实例方法进行举例说明:

首先会调用 - (id)forwardingTargetForSelector:(SEL)aSelector 方法,在该方法中进行消息的转发,将该消息转发到其他类,让其他类处理该消息。

- (id)forwardingTargetForSelector:(SEL)aSelector {    

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

return [[OtherClass alloc] init];    

}   

 return [super forwardingTargetForSelector:aSelector];

}

// OtherClass@interface OtherClass : NSObject

- (void)foo;@end  @implementation OtherClass

- (void)foo {    NSLog(@"%s", __func__);

}@end

创建一个 OtherClass 的类,该类中声明并实现了 foo 方法

forwardingTargetForSelector 方法中返回了 OtherClass 的实例对象,意味着我们会将消息交给 OtherClass 去处理,此时就会调用该类中的 foo 方法。

如果 forwardingTargetForSelector 方法返回 nil 或者压根就没有实现,消息转发阶段会调用其他方法来进行处理。

首先调用的是 methodSignatureForSelector 方法,该方法返回一个 NSMethodSignature 对象,如果返回值不为 nil,那么他将会调用 forwardInvocation 方法,并且传入一个 NSInvocation 对象,该对象封装了方法调用所必须的条件。

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

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

Method method = class_getInstanceMethod([OtherClass class], aSelector);

const char *types = method_getTypeEncoding(method);   

NSMethodSignature *signature = [NSMethodSignature signatureWithObjCTypes:types];        

return signature;    

}        

return [super methodSignatureForSelector:aSelector];

}

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

[anInvocation invokeWithTarget:[[OtherClass alloc] init]];    

// 假如 types 随便传的,anInvocation 封装的内容就毫无意义 

 // 我们可以随意进行任何操作    

OtherClass *oc = [[OtherClass alloc] init]; 

   [oc bar];

}

如果我们想要用 anInvocation 来处理消息,那么在 methodSignatureForSelector 方法中 types 一定要传对,关于 types 如何表示的我们可以查看苹果的官方文档Type Encoding

如果我们不想利用 anInvocation 处理消息,types 其实就可以随便传.

上一篇 下一篇

猜你喜欢

热点阅读