runtime的那些事(一)——runtime基础介绍

520 阅读8分钟

一、什么是 runtime?

都说 Objective-C 是一门动态语言。首先,动态与静态语言最大的区别,就是动态语言将数据类型的检查等决策尽可能地从程序编译时推迟到了运行时。只要有可能,runtime 就会动态地完成任务。这意味着 Objective-C 语言不仅需要编译器,还需要 runtime 来执行编译代码。
runtime 是一套用C语言提供的 API,Objective-C 代码最终都会被编译器转化为运行时代码,通过消息机制决定了不同函数调用或转发方式,因此 runtime成为了 Objective-C 作为动态语言使用的基础。

Runtime 概念 及术语

1. Object(objc_object) 实例
/// Represents an instance of a class.
struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
};

/// A pointer to an instance of a class.
typedef struct objc_object *id;

这里我们看到 objc_object 结构体 里面只包含一个 class类型 的isa 指针。
这里也就说明 一个Object (实例)唯一保存的就是他所属Class(类)的地址,当我们对一个 实例进行方法调用时候。
例如 [object message] ,会通过objc_object结构体的 isa指针 去找到到对应的 objec_class 结构体

2. Class(objc_class) 类
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

struct objc_class {
    Class _Nonnull isa;                                          // objc_class 结构体的实例指针

#if !__OBJC2__
    Class _Nullable super_class;                                 // 指向父类的指针
    const char * _Nonnull name;                                  // 类的名字
    long version;                                                // 类的版本信息,默认为 0
    long info;                                                   // 类的信息,供运行期使用的一些位标识
    long instance_size;                                          // 该类的实例变量大小;
    struct objc_ivar_list * _Nullable ivars;                     // 该类的实例变量列表
    struct objc_method_list * _Nullable * _Nullable methodLists; // 方法定义的列表
    struct objc_cache * _Nonnull cache;                          // 方法缓存
    struct objc_protocol_list * _Nullable protocols;             // 遵守的协议列表
#endif


  • 这里我们看到 objc_object 结构体里面定义了很多变量 通过命名不难发现 结构体里面保存了 指向父类的 指针、类的名字、版本、实例大小、实例变量 list 方法 list 缓存 协议列表 等。一个类包含的信息不就正式这些吗?
    objc_class结构体 的第一个成员变量是 isa指针,isa指针 保存的是所属类的结构体的实例指针也就是对象。
    所以Class(类)的本质就是一个对象, 称之为 类对象 。

  • 类对象就是一个结构体 struct objc_class ,这个结构体存放的数据 称之为 元数据 (metadata)

3. Meta Class(元类)

  • 从上面可以看出,对象(objc_object)结构体的 isa指针 指向的是对应 类对象(objc_class)结构体 ,那么类对象 (objc_class)的isa指向什么?答案指向 元类

  • 元类 是类对象(objc_class) 的类 听起来绕口 下面看它的作用就会豁然开朗

  • 在OC中,每当我们创建一个类。在编译时就会创建一个元类,而这个元类的对象 就是我们创建的这个类。(我们创建的类本质也是一个对象objc_class)

  • 那么为什么要有元类?我们看类对象( objc_class结构体) ivars 用来存放属性变量,objc_method_list 用来存放 实例方法 (-方法),那一些静态变量 和 类(+)方法 哪里去了? 没错,他们就是存放在 元类的methodLists和ivars里面。

4. Method(objc_method)

/// An opaque type that represents a method in a class definition.
/// 代表类定义中一个方法的不透明类型
typedef struct objc_method *Method;

struct objc_method {
    SEL _Nonnull method_name;                    /// 方法名 
    char * _Nullable method_types;               /// 方法类型
    IMP _Nonnull method_imp;                     ///方法实现
};
  • SEL _method_name (方法名)释义

    /// An opaque type that represents a method selector. typedef struct objc_selector *SEL;

EL: 是一个指向 objc_selecter 结构体的指针,但是在runtime相关头文件中 并没有找到明确的定义,经过测试打印 得出 猜测 结论 SEL 只是一个保存方法名的字符串。

  • char *_Nullable method_types(方法类型)释义

方法类型 method_types 是个字符串,用来存储方法的参数类型和返回值类型。

  • IMP method_imp(方法实现)释义

    /// A pointer to the function of a method implementation. #if !OBJC_OLD_DISPATCH_PROTOTYPES typedef void (IMP)(void / id, SEL, ... */ ); #else typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); #endif

IMP 的实质是一个函数指针,所指向的就是方法的实现。IMP用来找到函数地址,然后执行函数。

  • objc_class (类对象)结构体中的 methodLists(方法列表)中存放的元素就是Method(方法)

  • 由此看出Method将 SEL(方法名)和IMP(函数指针)相关联。当对一个对象 发送消息的时候 会通过SEL(方法名)去找到IMP(函数指针)进行执行。

消息机制的基本原理

根据上面分析,大家脑海中应该会有一个方法调用,也就是消息发送的全过程。下面总结一下

///实例方法调用
Person * p = [[Person alloc]init];
[p eat];

///类方法调用
 [Person sleep];




#import "Person.h"

@implementation Person
-(void)eat;
{
    NSLog(@"吃饭");
}

+(void)sleep;
{
    NSLog(@"睡觉");
}
@end


对象方法调用过程

  1. 通过 实例(p) 的isa指针 找到 实例( p)的 Class(objc_class类对象);

  2. 在Class(objc_class类对象)的结构体中找到 cache(方法缓存)散列表 中根据method_name 看里面有没有对应的IMP(方法实现);

  3. 如果没有找到 就继续在 Class(objc_class类对象)的methodLists(方法列表中)寻找对应的selector ,如果找到,填充到cache(方法缓存)中,并返回 selector;

  4. 如果Class(类)中没有找到这个selector ,就继续在他的superClass中寻找;

  5. 一旦找到对应的selector,就直接执行对应selector方法实现的IMP(方法实现);

  6. 若找不到对应的selector,即将进入 runtime的 消息转发机制。消息转发不做处理 程序发生崩溃。

类方法的调用

  1. 上面我们有说过 我们创建的类 在编译时会创建一个元类,而这个元类的对象 就是我们创建的这个类

  2. 通过Class(objc_class类对象) 的isa指针找到所属元类;

  3. 在进行上述后续操作。

消息转发机制

例:

1.首先创建Person 类 在.h里声明一个实例方法(这里以-(void)instanceMethod)为例子。然后.m不进行实现。在main 函数里初始化并且调用该方法

//这是 Person.h
 #import <Foundation/Foundation.h>
   
   @interface Person : NSObject


 -(void)instanceMethod;
 
 @end

///这是main.m
#import "Person.h"
int main(int argc, const char * argv[]) {
   @autoreleasepool {
       
       
        Person * person = [[Person alloc]init];
       [person instanceMethod];
       
   }
   return 0;

这时运行 由于方法没有实现 所以一定会触发消息转发机制

. 首先进入第一道防线Person.m文件会提供运行时的转发接应,实现 resolveInstanceMethod 为找不到的方法(-(void)instanceMethod)的实现进行补救。

#import "Person.h"
#import <objc/runtime.h>
@implementation Person
//被动态添加的实例方法
void insMethod(id self,SEL _cmd)
{
   NSLog(@"我收到消息了动态补加的");
}
//动态补加方法
+ (BOOL)resolveInstanceMethod:(SEL)sel
{
   if (sel == @selector(instanceMethod)) {
       class_addMethod(self, sel, (IMP)insMethod, "v@:");
       return YES;
   }
   return [super resolveInstanceMethod:sel];
}

运行:

2019-10-25 11:28:23.777657+0800 OC 动态性[17788:1898041] 我收到消息了动态补加的
Program ended with exit code: 0

如果没有实现 + (BOOL)resolveInstanceMethod:(SEL)sel 或者返回了NO 那么进入第二道防线 实现-(id)forwardingTargetForSelector:(SEL)aSelector 方法返回一个实例对象,让对象代替原对象处理这个消息

#import "Person.h"
#import "Person2.h"
#import <objc/runtime.h>
@implementation Person
-(id)forwardingTargetForSelector:(SEL)aSelector
{
   //返回消息转发的实例 注意消息转发实例要和当前实例未实现的方法名一模一样。
   if (aSelector==@selector(instanceMethod)) {
       return [[Person2 alloc]init];
   }
   return nil;
}

///Person2.h 我是头文件
 #import <Foundation/Foundation.h>
 @interface Person2 : NSObject

 -(void)instanceMethod;

 @end

 ///Person2.m 我是实现文件
 #import "Person2.h"
 @implementation Person2

 -(void)instanceMethod;
 {
    NSLog(@"我是替Person完成的消息,我收到了");
 }
 @end
 

运行

2019-10-25 11:36:58.230856+0800 OC 动态性[17884:1901837] 我是替Person完成的消息,我收到了
Program ended with exit code: 0

如果上面的两个补救方法都没有实现 或者forwardingTargetForSelector 返回了nil 那么进入最后一道防线,需要手动生成方法签名并实现forwardInvocation方法将消息转发给另一个对象类似第二道防线 下面看代码

-(NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
{
   //为指定的方法手动生成签名
   NSString * selName = NSStringFromSelector(aSelector);
   if ([selName isEqualToString:@"instanceMethod"]) {
       return [NSMethodSignature signatureWithObjCTypes:"v@:"];
   }
   return [super methodSignatureForSelector:aSelector];
}
-(void)forwardInvocation:(NSInvocation *)anInvocation
{
   //如果另外一个对象可以响应该消息,那么将消息转发给它
   Person2 * person2 = [[Person2 alloc]init];
   if ( [person2 respondsToSelector:[anInvocation selector]]) {
       [anInvocation invokeWithTarget:person2];
   }
   

///Person2.h 我是头文件
#import <Foundation/Foundation.h>
@interface Person2 : NSObject

-(void)instanceMethod;

@end

///Person2.m 我是实现文件
#import "Person2.h"
@implementation Person2

-(void)instanceMethod;
{
   NSLog(@"我是替Person完成的消息,我收到了");
}
@end


运行:

 2019-10-25 11:56:04.493146+0800 OC 动态性[18067:1910152] 我是替Person完成的消息,我收到了
Program ended with exit code: 0

消息转发机制小结

///当调用一个不存在的类方法时调用
+ (BOOL)resolveClassMethod:(SEL)sel;

///当调用一个不存在的实例方法时调用
+ (BOOL)resolveInstanceMethod:(SEL)sel;

///将这个不存在的方法重定向到其他类进行处理,返回一个类的实例
- (id)forwardingTargetForSelector:(SEL)aSelector;

///将这个不存在的方法打包成NSInvocation丢进来,需要调用invokeWithTarget:给某个能执行方法的实例
- (void)forwardInvocation:(NSInvocation *)anInvocation;