iOS 底层探索篇 ——类的加载原理(下)

1. 为什么要有ro,rw,rwe

  • ro属于cleanmemory,在编译即确定的内存空间,只读,加载后不会改变内容的空间
  • rw属于dirtymemory,rw是运行时结构,可读可写,可以向类中添加属性、方法等,在运行时会改变的内存;
  • rwe相当于类的额外信息,因为在实际使用过程中,只有很少的类会真正的改变他们的内容,所以为避免资源的消耗就有了rwe;

ro被复制一份到rw里面,这是因为在运行时分类可以添加方法,而程序员也可以动态添加方法或者属性到类里面,而ro是只读的,所以需要在rw里面来追踪这些东西。
不是每一个类都会动态添加,所以如果这片内存写在rw里面,那么就会对脏内存有影响,所以把这些东西放在rwe里面。

2. cls->data()

realizeClassWithoutSwift中的ro时怎么来的呢?查找到赋值的地方。
在这里插入图片描述
在这里插入图片描述
发现返回的是一个地址指针,而指针可以进行取值和强转。这里是从macho里面获取地址指针,然后对class_ro_t的数据进行赋值。

3. extAllocIfNeeded & attachCategories

回到methodizeClass里面查看,看到rwe则是等于rw->ext()
在这里插入图片描述
在这里插入图片描述
点进去查看实现,发现下面一个方法extAllocIfNeeded,如果有这个方法就会有rwe
在这里插入图片描述
在源码中查找extAllocIfNeeded,发现在attachCategories里面有调用。也就是在添加分类的时候,rwe是必然有值的。同时在class_setVersionaddMethods_finishclass_addProtocol_class_addPropertyobjc_duplicateClass的时候也是有调用的。这说明在运行时进行处理的时候,才会进行rwe的创建。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
现在来看attachCategories,因为需要研究分类时如何写到类里面的。在源码中搜索attachCategories,发现在attachToClassload_categories_nolock里面有调用。
在这里插入图片描述
在这里插入图片描述
load_categories_nolock比较陌生,而attachToClass在methodizeClass里面有看到过,所以来看一下attachToClass。
看到这里有一个地方判断previously来看是否进入调用attachToClass。
在这里插入图片描述看一下previously是哪里来的。发现是methodizeClass的第二个参数。
在这里插入图片描述
接下来搜索methodizeClass的调用。发现在realizeClassWithoutSwift里面。
在这里插入图片描述
又看到previously是realizeClassWithoutSwift的第二个参数。
在这里插入图片描述
接下来寻找realizeClassWithoutSwift的调用。发现传的第二个参数全是nil,那么就代表previously是一个备用参数,为了方便内部进行相关的调节。
既然previously是nil,那么上面点函数就不会进去,也就是只会调用下面点attachToClass。
在这里插入图片描述

接下来看attachCategories里面的attachLists。先看中间最短的地方。这里表示如果没有list而且attachLists里面就一个元素,那么list就是一个只有这个元素的一位数组。
在这里插入图片描述
再来看下面的else 的情况,这里做的是重新创建一个count为新list.count + 1 的数组(有oldlist的情况下,否则为0),将之前的一整个数组放到新数组的最后一位,然后将要添加的数组的元素从新数组的头部开始依次添加进去。
在这里插入图片描述
在这里插入图片描述
再来看最后一个部分,这里做的就是重新创建一个count为oldList的count个数加addedList的count个数的数组,先将oldList的元素依次从i+addedCount位添加进去,然后将addedList的依次从头开始添加进去。
在这里插入图片描述
在这里插入图片描述

4.分类的懒加载

之前说到类有懒加载并且由load方法来控制,那么分类是否也有懒加载呢?并且分类的懒加载和类的懒加载是否有关系呢?来探索一下。

类和分类都实现load

先来看类和分类都实现load的情况,在attachCategories打下断点并运行。
在这里插入图片描述
运行后发现会来到_read_images的非懒加载区域
在这里插入图片描述
然后来到realizeClassWithoutSwift。
在这里插入图片描述
然后来到methodizeClass里面调用attachToClass。
在这里插入图片描述
在往下就来到了attachCategories
在这里插入图片描述
所以调用流程如下:
map_images -> map_images_nolock -> _read_images -> readClass -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> load_images -> loadAllCategories -> load_categories_nolock -> load_categories_nolock -> attachCategories -> attachLists

类实现load和分类不实现load

把分类的load方法去掉后重新运行,发现不会实现attachCategories方法了。
在这里插入图片描述

类不实现load和分类实现load

把分类的load方法去掉后重新运行,发现和上面的情况一样不会实现attachCategories方法了。
在这里插入图片描述

类和分类都不实现load

运行后发现直接到main函数里面了。

5. 4种不同情况下的分类加载

类和分类都实现load

实现类和分类的load方法后运行,进入realizeClassWithoutSwift后输入ro查看分类数据是否被加载进去,如果有的话那么就说明在realizeClassWithoutSwift分类就已经被加载进去了,否则就在realizeClassWithoutSwift之后。
输出所有的baseMehtods之后发现,没有分类的方法,那么说明分类还没有被加载进去。
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
接下来看load_categories_nolock里面,输出cat看一下,确实是我们创建的分类。
在这里插入图片描述

在输出cls 看一下发现确实是LGPerson。
在这里插入图片描述
接下来往下走发现调用attachCategories的地方,这里因为cls已经在read_images时候加载过了所以cls->isRealized()为true。
在这里插入图片描述
看到attachCategories里面,看到mlist的数据结构,且里面count为2,也就是分类里面的2个方法。
在这里插入图片描述
在往下走,发现把mlist放到了mlists里的最后一位。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
往下走,跳过中间的属性和协议,就来到了这里。看到这里调用prepareMethodLists进行了排序,然后往rwe的methods调用attachLists添加了分类方法。
在这里插入图片描述
进去看attachLists。这里看到addedLists是个二级指针。
在这里插入图片描述
然后往下走,看到进入了else。在这里插入图片描述
打印输出发现存入的都是指针。

在这里插入图片描述
输出看一下发现这里的addedList[0]存的是分类
在这里插入图片描述

类不实现load,分类实现load

没有进入attachCategories,也就是没有加载分类,但是加载类。
在这里插入图片描述
这里类被迫营业了。分类是针对主类实现的,所以分类要实现,就要先实现类的。这里在realizeClassWithoutSwift里面输出ro的baseMethods,发现分类的方法也都加载了进去了,说明类和分类已经合到一起了,编译器已经将分类的数据放在 cls->data()。
在realizeClassWithoutSwift输出ro的baseMethods,发现不仅类的方法存在,分类的方法也都加载进去了。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

类实现load,分类不实现load

没有进入attachCategories,也就是没有加载分类,但是加载类。
在这里插入图片描述
这里在realizeClassWithoutSwift里面输出ro的baseMethods,发现分类的方法也都加载了进去了,说明类和分类已经合到一起了,编译器已经将分类的数据放在 cls->data(),
在这里插入图片描述

类和分类都不实现load

运行后发现直接到main函数里面了,两者都懒加载。
在这里插入图片描述
这里在realizeClassWithoutSwift里面输出ro的baseMethods,发现分类的方法也都加载了进去了,说明类和分类已经合到一起了,编译器已经将分类的数据放在 cls->data(),
在这里插入图片描述
也就是说,当类和分类都不实现load的时候,两者都懒加载,在类第一次收到消息的时候初始化。但是类和分类加载都已经在data里面了。

6.多个分类的加载情况

分类全都实现load

在这里插入图片描述

添加一个分类,然后运行。看到两个分类都加载了进去。
在这里插入图片描述
在这里插入图片描述
接下来的流程和单个分类的加载情况一样,但是attachLists却不一样了。这里就加载了分类。
在这里插入图片描述

分类不全都实现load

运行后还是全都加载了。
在这里插入图片描述
processCatlist是block,在下面调用。

在这里插入图片描述
load_categories_nolock是由loadAllCategories调用,loadAllCategories则是由load_images里面调用,并且由didInitialAttachCategories和didCallDyldNotifyRegister决定调不调用,didInitialAttachCategories默认为false,而当loadAllCategories调用后,didInitialAttachCategories就为true,所以就只调用一次。
在这里插入图片描述
在这里插入图片描述
所以能加载到分类的原因是通过catList从macho里面读取到的。

一般来说,mach会加载整个数据结构,当调用load的时候,就会打乱算法来进行计算,所以说load耗时。正常来说,类和分类都会在macho里面加载好的了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值