iOS - Runtime基础(上)

简介: Runtime合集iOS - isa、superclass指针,元类superclass指向基类本身

1. 什么是Runtime


Runtime是一个库,位于usr/include/objc, 经常用的api位于该库下的runtime.h文件中,在使用时需要引用头文件#import <objc/runtime.h>


2. Runtime 做什么用


通过Runtime,我们可以在App运行期动态的创建对象、检查对象、修改类、对象的方法,可以说Runtime是Objective-C的运行时机制的基础


3. 消息机制的基本原理


声明一个Person类, 类包含两个对象方法(此处为了编译后查找代码方便,我把函数名命为personSleep,此处不符合代码命名规范请忽略)

@implementation Person
- (void)eatFood:(NSString *)foodName {
    NSLog(@"person eat food : %@", foodName);
}
- (void)personSleep {
    NSLog(@"person is sleeping...");
}
@end


我们在外界调用eatFood,编译成cpp查看

Person *person = [[Person alloc] init];
    [person eatFood:@"baozi"];
    [person personSleep];


cpp

Person *person = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
    ((void (*)(id, SEL, NSString * _Nonnull))(void *)objc_msgSend)((id)person, sel_registerName("eatFood:"), (NSString *)&__NSConstantStringImpl__var_folders_44_1ht3l6g55dv59_5s62wsv_bm0000gn_T_ViewController_88ee85_mi_0);
    ((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personSleep"));


把代码简化一下,我们可知编译后的[person eatFood]变成了


objc_msgSend(reciver, Selector)

objc_msgSend(reciver, Selector, org1, org2, ...)


运行期阶段:消息接收者reciver寻找Selector去执行


  1. 通过reciverisa指针找到revicerClass
  2. Classcache(方法缓存)的散列表寻找对应的IMP(方法实现)
  3. 如果2.没找到,就继续在Classmethod list(方法列表)中找对应的selector,如果找到了,填充到Class的cache中并返回selector
  4. 如果3.没找到,就继续在其父类中找
  5. 一旦找到对应的selector,直接执行reciverselectorIMP(方法实现)
  6. 若找不到对应的selector,需要消息被转发或临时向这个reciver添加selector,否则会发生崩溃


4.Runtime中的概念


4.1 objc_msgSend


所有的Objective-C方法编译后都会变成对objc_msgSend的调用,


4.2 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_class 结构体 定义了很多变量:自身的所有实例变量(ivars)、所有方法定义(methodLists)、遵守的协议列表(protocols)等。objc_class 结构体 存放的数据称为 元数据(metadata)


objc_class结构体的第一个成员变量是isa指针,isa指针保存的是所属类的结构体的实例的指针,这里保存的就是objc_class结构体的实例指针,换个名字就是对象,也就是说,Class的本质就是一个对象,我们称为 类对象


4.3 Object


在objc.h中, Object被定义成了objc_class结构体

/// Represents an instance of a class.
typedef struct objc_class *Class;
/// Represents an instance of a class.
struct objc_object {
    Class _Nonnull isa;       //objc_object 结构体的实例指针
};
/// A pointer to an instance of a class.
typedef struct objc_object *id;


从中可以看出,objc_object结构体只包含了一个Class类型的isa指针,也就是说一个Object(对象)唯一保存的就是它所属Class(类)的地址,当我们对一个对象进行方法调用时,比如[receiver selector], 它会通过objc_object结构体的isa指针去找到对应的object_class结构体,然后在object_class结构体的methodLists中找到我们调用的方法,然后执行


4.4 Meta Class


从上边我们能看出,对象的(objc_object结构体)的isa指针指向对应类对象(object_class结构体),那么类对象(object_class结构体)的isa指针又指向什么呢

object_class结构体的isa指针实际上指向的是类对象自身的meta-class(元类)


元类就是一个类对象所属的类。一个对象所属的类叫做类对象,一个类对象所属的类就是元类


Runtime中把类对象所属类型叫做meta-class(元类),用于描述类对象本身所具有的特征,而在元类的methodLists中,保存了类的方法列表,即所谓的类方法,并且类对象中的isa指针指向的就是元类,每个类对象有且仅有一个与之相关的元类


3. 消息机制的基本原理中讲到,对象的调用过程,是通过对象的isa指针找到类对象,在类对象的methodLists中找到对应的selector


而类方法的调用过程与对象的调用差不多,流程如下:


  1. 通过类对象isa指针找到所属的meta-class(元类)
  2. meta-classmethodLists中找到对应的selector
  3. 执行对应的selector


下面看一个示例:

NSString *str = [NSString stringWithFormat:@"%d,%s", 3. @"test"];


上边的示例中,stringWithFormat被发送给了NSString类,NSString类通过isa指针找到NSString的元类,然后在该元类的方法列表中找到对应的stringWithFormat:方法,然后执行该方法


4.5 实例对象、类、元类的关系


iOS - isa、superclass指针,元类superclass指向基类本身


image.png

0c1e95adfbc84b06aa9f28f61bd2b4dc~tplv-k3u1fbpfcp-watermark.image.png


4.6 Method


object_class结构体中的methodLists(方法列表)中存放的元素就是Method(方法)


objc/runtime.h中,表示Method的‘objc_method结构体’数据结构如下

struct objc_method {
    SEL _Nonnull method_name;       //方法名
    char * _Nullable method_types;  //方法类型
    IMP _Nonnull method_imp;        //方法实现
}


1. SEL method_name 方法名


SEL的定义在objc/objc.h

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


SEL是一个指向objc_selector的指针,但是在runtime相关头文件中并没有找到明确的定义。不过,通过测试我们可以得出:SEL只是一个保存方法名的字符串

SEL sel = @selector(viewDidLoad);
    NSLog(@"%s", sel);
    SEL sel1 = @selector(test);
    NSLog(@"%s", sel1);


输出为:

2021-05-10 21:58:24.705590+0800 RuntimeDemo[2266:67998] viewDidLoad
2021-05-10 21:58:24.705746+0800 RuntimeDemo[2266:67998] test


2. IMP _Nonnull method_imp 方法实现


IMP的定义同样在objc/objc.h

/// 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用来找到函数的地址,然后执行函数


3. char * _Nullable method_types; 方法类型


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


到这里,Method的结构就已经很清楚了,MethodSEL(方法名)IMP(函数指针)关联起来,当对一个对象发送消息时,会通过给出的SEL(方法名)去找到IMP(函数指针),然后执行


相关文章
|
API iOS开发
iOS面试关于runtime
iOS面试关于runtime
124 0
15-iOS之Runtime常用API以及使用
15-iOS之Runtime常用API以及使用
109 0
|
编译器 iOS开发
iOS Runtime详细介绍及实战使用(二)
iOS Runtime详细介绍及实战使用
 iOS Runtime详细介绍及实战使用(二)
|
API iOS开发
iOS Runtime详细介绍及实战使用(一)
iOS Runtime详细介绍及实战使用
|
iOS开发
iOS开发- runtime基本用法解析和用runtime给键盘添加工具栏和按钮响应事件
iOS开发- runtime基本用法解析和用runtime给键盘添加工具栏和按钮响应事件
144 0
|
前端开发 API 开发工具
基础篇必看,史上最全的iOS开发教程集锦,没有之一
基础篇必看,史上最全的iOS开发教程集锦,没有之一
|
程序员 API iOS开发
iOS开发:个人对于textView基础用法的总结(其一)
从事了这么久ios开发,对于textView的使用并不陌生,它和textfield有相似的地方,也有不同的地方,这里只对textView的一些基础用法进行描述,textfield不在这里描述。
341 0
|
JSON 前端开发 JavaScript
iOS Principle:Runtime(下)
iOS Principle:Runtime(下)
97 0
iOS Principle:Runtime(下)
|
缓存 编译器 Swift
iOS Principle:Runtime(中)
iOS Principle:Runtime(中)
173 0
iOS Principle:Runtime(中)
|
存储 缓存 编译器
iOS Principle:Runtime(上)
iOS Principle:Runtime(上)
114 0
iOS Principle:Runtime(上)