runtime的理解(一)

主要内容

  • runtime 是什么
  • runtime 的消息机制

是什么

  OC 是在C语言的基础上进行的扩展和封装。对于C语言,调用未实现的函数在编译阶段就会报错,而对于OC语言,调用的只要是声明过的函数,在编译阶段不会报错,在运行阶段才会报错。这就是OC的动态性的一个体现,并且C语言是面向过程的语言,而OC是面向对象的编程语言,OC对C语言的这些扩展的核心就是 runtime。
  runtime 简称运行时,主要由C和汇编编写,是OC面向对象和动态机制的基础,其的核心是 消息机制。在使用的过程中,我们可以参考官方Api。

消息机制

objc_class

  Cocoa Touch 中几乎所有的类中都继承自 NSObject 类(另一个是 NSProxy 类),在 NSObjcet 中存在一个 Class 的 isa 指针。

typedef struct objc_class *Class;
@interface NSObject  {
    Class isa  OBJC_ISA_AVAILABILITY;
}

  Objective-C 的类也是由 Class 类型来表示的,它实际上是一个指向 objc_class 结构体的指针。查看 objc/runtime.h 中 objc_class 结构体的定义如下:

struct objc_class {
  Class isa; // 指向metaclass
  
  Class super_class ; // 指向其父类
  const char *name ; // 类名
  long version ; // 类的版本信息,初始化默认为0,可以通过runtime函数class_setVersion和class_getVersion进行修改、读取
  long info; // 一些标识信息,如CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含对象方法和成员变量;CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
  long instance_size ; // 该类的实例变量大小(包括从父类继承下来的实例变量);
  struct objc_ivar_list *ivars; // 用于存储每个成员变量的地址
  struct objc_method_list **methodLists ; // 与 info 的一些标志位有关,如CLS_CLASS (0x1L),则存储对象方法,如CLS_META (0x2L),则存储类方法;
  struct objc_cache *cache; // 指向最近使用的方法的指针,用于提升效率;
  struct objc_protocol_list *protocols; // 存储该类遵守的协议
}

方法选择器

  @selector (SEL):是一个SEL方法选择器。SEL 其主要作用是快速的通过方法名字查找到对应方法的函数指针,然后调用其函数。SEL其本身是一个Int类型的地址,地址中存放着方法的名字。一个方法对应着一个SEL,所以一个类中不能存在2个名称相同的方法。

消息传递

  例如执行代码: [person eat]; ,底层就是调用 [person performSelector:@selector(eat)]; 方法,然后编译器再将代码转化为objc_msgSend(person, @selector(eat));

  1. 在 objc_msgSend 函数中。首先通过 person 的isa指针找到 person 对应的 Class。在 class 中先去 cache 中通过 SEL 查找对应函数 method,如果找到则通过 method 中的函数指针跳转到对应的函数中去执行。
  2. 若 cache 中未找到。再去 methodList 中查找。若能找到,则将method加入到cache中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。
  3. 若 methodlist 中未找到,则去 superClass 中查找。若能找到,则将 method 加入到 cache 中,以方便下次查找,并通过method中的函数指针跳转到对应的函数中去执行。若依然未找到,就要消息转发。
runtime的理解(一)_第1张图片
消息传递

消息转发

  消息转发包含动态方法解析、备用接收者、完整消息转发三部分,也是程序报错前最后的三次机会。

  1. 动态方法解析
      在消息传递到根类仍然找不到方法的时候,消息转发,调用 +resolveInstanceMethod: 或者 +resolveClassMethod: 方法。重写此方法,并利用 class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 方法来添加新的方法接受消息。
- (void)testDynamicMethod
{
    [self performSelector:@selector(justTest:) withObject:@"testStr"];
//    [DITRuntimeViewController performSelector:@selector(justClassTest:) withObject:@"classTestStr"];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(justTest:))
    {
        class_addMethod([self class], sel, (IMP)justTestMethod, "v@:@");
        return YES;
    }
    
    return [super resolveInstanceMethod:sel];
}

void justTestMethod(id self, SEL _cmd, id str) {
    NSLog(@"%@的%@方法动态实现了,参数为:%@", self, NSStringFromSelector(_cmd), str);
}

  关于 class_addMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, const char * _Nullable types) 方法,四个参数代表的意思,cls : 表示给哪个类添加方法,这里是指当前控制器;SEL name: 表示要添加方法的 sel,即是传进来的参数 sel。IMP imp : 表示要添加的方法的实现,方法名可与未找到的方法的名字(sel)相同,方法的实现默认带有两个隐式参数, self : 方法调用者 _cmd : 调用方法的 SEL。types : 表示方法类型,需要用特定符号,系统提供的例子中使用的是"v@:",各符号所代表的类型如下图:

runtime的理解(一)_第2张图片
methodTypes.png

  1. 备用接收者
      在调用 +resolveInstanceMethod: 或者 +resolveClassMethod: 方法返回NO的时候,就会调用 - forwardingTargetForSelector: 方法,重写此方法,返回备用接收者即可。
#pragma mark - 备用接受者
- (void)testForwardingReceiver
{
    [self performSelector:@selector(justTestForwarding:) withObject:@"testStr"];
}

- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(justTestForwarding:))
    {
        return [[DITForwardingTest alloc] init];
    }
    
    return [super forwardingTargetForSelector:aSelector];
}
  1. 完整消息转发
      在调用 - forwardingTargetForSelector: 方法返回 nil 的时候,就会启用完整的消息转发机制。首先它会发送 -methodSignatureForSelector: 消息获得函数的参数和返回值类型。如果 -methodSignatureForSelector: 返回nil ,runtime 则会发出 -doesNotRecognizeSelector: 消息,程序报 unrecognized selector 错。如果返回了一个函数签名,runtime 就会创建一个 NSInvocation 对象并发送 -forwardInvocation: 消息给目标对象。
#pragma mark - 完整消息转发
- (void)testMethodSignature
{
    [self performSelector:@selector(justTestMethodSignature:) withObject:@"testMethodSignatureStr"];
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
    if (aSelector == @selector(justTestMethodSignature:))
    {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation
{
    SEL sel = anInvocation.selector;
    DITForwardingTest *methodSignatureTest = [[DITForwardingTest alloc] init];
    
    if ([methodSignatureTest respondsToSelector:sel])
    {
        [anInvocation invokeWithTarget:methodSignatureTest];
    }
    else
    {
        [anInvocation doesNotRecognizeSelector:sel];
//        [super forwardInvocation:anInvocation];
    }
}
  1. 消息转发流程


    runtime的理解(一)_第3张图片
    image.png

Demo地址:Github

你可能感兴趣的:(runtime的理解(一))