美文网首页
18.Runtime总结

18.Runtime总结

作者: 牛牛大王奥利给 | 来源:发表于2021-11-02 16:54 被阅读0次

本篇文章主要讲iOS常见的几个问题:
1、load和initialize方法的调用原则和顺序。
2、runtime是什么?
3、方法的本质,sel是什么?IMP是什么?两者之间的关系是什么?
4、能否向编译后的得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?
5、[self class]和[super class]的区别以及原理分析?

以上问题,会分为个人回答,以及搜索资料的回答两个方面,主要想记录下学习和理解过程。

load和initialize方法的调用原则和顺序

我的回答:

  • load方法是在main函数之前调用的,通过_dyld_objc_notify_register方法调用load_images->call_load_methods->call_class_loads,通过load_method_t表,调用所有的的load方法。只会调用一次。
  • initialize在main函数之后,在类第一次接收到消息的时候调用,调用过就不会再调用了。

参考资料:

  • load是在dyld回调load_images中进行调用的,这个回调是在_objc_init的过程中进行注册的。会最先调用父类的+load,然后是本类的+load,最后加载分类的+load,images的默认顺序是按照compile sources的顺序);如果有多个分类都有load方法,其调用顺序也是根据编译的顺序调用;
  • initialize方法是在第一次objc_msgSend的时候调用的;

Runtime在一个程序中每一个类的一个程序中发送一个初始化一次,或是从它继承的任何类中,都是在程序中发送第一条消息。(因此,当该类不使用时,该方法可能永远不会被调用。)运行时发送一个线程安全的方式初始化消息。父类的调用一定在子类之前。

initialize在类或者其子类的第一个方法被调用前调用。即使类文件被引用进项目,但是没有使用,initialize不会被调用。由于是系统自动调用,也不需要再调用 [super initialize] ,否则父类的initialize会被多次执行。假如这个类放到代码中,而这段代码并没有被执行,这个函数是不会被执行的;父类的initialize方法会比子类先执行;当子类不实现initialize方法,会把父类的实现继承过来调用一遍。在此之前,父类的方法会被优先调用一次;当有多个Category都实现了initialize方法,会覆盖类中的方法,只执行一个(会执行Compile Sources 列表中最后一个Category 的initialize方法)

总结:initialize方法是在第一次objc_msgSend的时候调用的,(❌不是在main之后貌似,我回答的有问题);

load和initialize的共同点:如果父类和子类都被调用,父类的调用一定在子类前边;

load和initialize的主要不同点:load方法只要类被引用进项目就会执行,并且是在main函数之前,与这个类是否被用到无关;而initialize是在类第一次接到消息的时候,就算被加载进来,但是类没有收到消息的时候,initialize不会被调用。

关于类有了分类:+load会先去调用父类的,然后是本类的,最后分类的调用顺序是Compile Sources的分类顺序;而initialize分类的initialize会覆盖掉主类的,只执行一个分类的,是Compile Sources中最后一个分类的initialize方法。

参考:https://www.jianshu.com/p/c52d0b6ee5e9

Runtime是什么

我的回答:runtime是由c++,c以及汇编语言实现的一套系统api,主要用来处理运行时的一些状况。根据苹果官方文档,运行时现在有两个版本。

参考资料:Runtime是一套由C、C++、汇编实现的一套API,所有的方法调用都叫发送消息。根据Apple官方文档描述,目前OC运行时分为两个版本,Modern和Legacy。二者的区别在与Legacy在实例变量发生改变后,需要重新编译其子类。Modern在实例变量发生改变后,不需要重新编译其子类。

我上网查了资料,觉着这一份讲Runtime的最全面,很具体,推荐给大家。
参考:https://www.jianshu.com/p/ce97c66027cd

方法的本质,sel是什么?IMP是什么?两者之间的关系是什么?

我的回答:方法的本质是消息发送,是objc_msgSend。sel是方法编号,imp是方法实现,两者之间的关系就好比是一本书的目录和内容,sel就是书的索引,imp是索引指向的最终内容。

参考资料:方法的本质是消息发送,即objc_msgSend,它的流程是
1、快速查找,去查找cache_t中的buckets,也就是缓存查找;
2、慢速查找,递归自己的方法列表或者父类的,lookUpImpOrForward;
3、查不到消息,动态方法解析,resolveInstanceMethod;
4、快速消息转发,forwardingTargetForSelector;
5、慢速消息转发,methodSignatureForSelector和forwardInvocation;
6、还查不到doesNotRecognizeSelector;
SEL是方法编号,在read_images期间就加载到了内存。他实际是objc_selector结构体。
IMP是函数实现的指针,保存了方法实现的地址
IMP和SEL关系:每一个继承于NSObject的类都能自动获得runtime的支持。在这样的一个类中,有一个isa指针,指向该类定义的数据结构体,这个结构体是由编译器编译时为类(需继承于NSObject)创建的.在这个结构体中有包括了指向其父类类定义的指针以及 Dispatch table. Dispatch table是一张SEL和IMP的对应表。也就是说方法编号SEL最后还是要通过Dispatch table表寻找到对应的IMP,IMP就是一个函数指针,然后执行这个方法。

这里我也有疑问,为啥不直接装着函数实现,还绕一圈儿通过SEL对应到IMP去查找到最终的实现方法?

有了SEL这个中间过程,我们可以对一个编号和什么方法映射做些操作,也就是说我们可以一个SEL指向不同的函数指针,这样就可以完成一个方法名在不同时候执行不同的函数体。另外可以将SEL作为参数传递给不同的类执行。也就是说我们某些业务我们只知道方法名但需要根据不同的情况让不同类执行的时候,SEL可以帮助我们。

通过搜索答案查阅资料,我理解到,这个就好比是网络请求,域名和域名下面真正访问的ip地址是一样的,便于随时应对于ip转换等突发情况,比如服务器宕机,然后可以切换到另外一个地址访问备用服务器之类的。SEL和IMP的这个设计,应该和这个想要实现的效果是一样的。

SEL和IMP的关系参考链接:https://www.jianshu.com/p/df9a199c8a4b?utm_campaign=maleskine&utm_content=note&utm_medium=seo_notes&utm_source=recommendation

能否向编译后的得到的类中增加实例变量?能否向运行时创建的类中添加实例变量?

我的回答:不能像编译后的类中增加实例变量,编译之后是ro,是只读的clean memory,这个时候内存的大小已经确定和固定了;可以像运行时创建的类中增加实例变量,通过Runtime的Api添加,这个时候是rw,是读写状态,可以对之前编译后的内容进行额外的补充。

参考资料:因为编译后的类已经注册在runtime中,类结构中的objc_ivar_list 实例变量的链表和instance_size实例变量的内存大小已经确定,同时runtime会调用class_setIvarLayout 或者class_setWeakIvarLayout来处理strong weak引用,所以不能向存在的类中添加实例变量;运行时创建的的类可以添加实例变量,调用class_addIvar函数,但是得在调用objc_allocateClassPair之后,objc_registerClassPair之前。(仅供参考,网络答案大致相同)

[self class]和[super class]的区别以及原理分析?

我的回答:说实话,这题我不会😭。往常的回答就是[self Class]是当前类❌,[super class]是调用父类❌,然鹅看过答案以后才知道,就根本不是表面这么单纯的事情,是我太单纯。

参考资料:

  • [self class]就是发送消息objc_msgSend,消息接受者是self,方法编号是class。
  • [super class]本质就是objc_msgSendSuper(其实调用的是objc_msgSendSuper2),消息的接受者还是 self,方法编号class。objc_msgSendSuper的objc_super构造是receiver:self,superClass:superClass,而objc_msgSendSuper2的的objc_super构造是receiver:self,superClass:currentClass。
  • 在这里objc_msgSendSuper查找会更快(对于class),直接跳过 self 查找。
  • self本质上是形参名 ,super是编译器关键字。

参考:https://www.jianshu.com/p/da17d28dbacf

虽然这个题我不会,但是我可以学,看看这两个到底是个什么鬼,纸上得来终觉浅,得治此事要躬行,要自己试着分析一下才能更好的去理解本来不太熟悉的东西。

我创建了LGTeacher类,继承自LGPerson,然后代码和打印如下:

image.png
通过打印信息可以看到,两个打印的都是LGTeacher。
通过调试,[self class]调用的是object_getClass,而object_getClass中的操作是:
Class object_getClass(id obj)
{
    if (obj)
        return obj->getIsa();
//    if (obj){
//        id test= obj->getIsa();
//    printf("test======%p\n",&test);
//    return test;
//    }
        
    else return Nil;
}

通过isa找到类,[self class]最终找到self就是本类LGTeacher,还比较好理解,那么[super class]到底是肿么一回事儿。打开符号断点进行调试:

image.png
所以最终调用的是方法objc_msgSendSuper2,我们通过源码找一下这个方法:
#if __OBJC2__
// objc_msgSendSuper2() takes the current search class, not its superclass.
OBJC_EXPORT id _Nullable
objc_msgSendSuper2(struct objc_super * _Nonnull super, SEL _Nonnull op, ...)
    OBJC_AVAILABLE(10.6, 2.0, 9.0, 1.0, 2.0);

在objc2版本下,有注释,会获取当前搜索到的类,不是他的父类,所以返回的还是当前的类!所以这两个返回的都是当前类!

相关文章

网友评论

      本文标题:18.Runtime总结

      本文链接:https://www.haomeiwen.com/subject/bdlliltx.html