二十二、app加载流程(六)分类的实现和加载(上)

本文由快学吧个人写作,以任何形式转载请表明原文出处

一、资料准备

objc4-818.2

对应mac的版本是11.1。可根据自己的系统版本挑选可以进行调试的源码。

二、思路

  1. 类的加载(上)类的加载(下)中知道了非懒加载类和懒加载类是如何从镜像加载到内存中并实现的。
  2. methodizeClass源码中有关分类的处理还没有看过。那么分类的本质是什么?
  3. 分类是如何和主类联系上的,分类又是怎么实现的?

三、分类的本质

设置main.m中的代码,创建一个JDMan的分类 :

通过clang编译main.m文件,生成编译文件查看分类的本质。一定要进入到main.m所在文件夹下,再执行clang命令。

clang -rewrite-objc main.m -o main.cpp

1. 本质是结构体category_t

在main.cpp中可以看到分类的本质是结构体category_t

在818源码中可以搜索一下:struct category_t

name : 分类的名字

cls : 哪个类的分类

instanceMethods : 分类的实例方法列表,类型为method_list_t

classMethods : 分类的类方法列表,类型为method_list_t

protocols : 分类的协议列表,类型为protocol_list_t

instanceProperties : 分类中的属性,类型为property_list_t

官方注释 :下面的不常见,不说了。

2. 分类的特点

  1. 在分类中通过通过@property添加的属性是没有setter和getter方法的,和类不一样。
  2. 分类的属性添加setter和getter方法可以通过关联对象来设置。后面再说。

在上面编译的main.cpp文件中也可以看出来,分类中的方法列表中不自带setter和getter方法 :

四、探索分类加载的准备

1. 分类加载的总体思路

分类的加载就要分成4种情况了,因为分类是依附于类的。

删除掉main.m中创建的JDPerson分类,以文件的形式来创建一下。用来测试分类的加载。创建结果如下图 :

main.m如下图 :

2. 分类的加载是从哪里进行的

  1. 思路 :
  1. 类的实现都是通过realizeClassWithoutSwift,无论是非懒加载类还是懒加载类。
  2. realizeClassWithoutSwift中的methodizeClass中发现了官方注释Attach categories.
  3. 在官方注释下面的methodizeClass源码中的attachToClass源码中发现了attachCategories,通过命名猜测是和分类相关。attachCategories源码中有对rwe的操作。而rwe是运行时动态修改类才会出现的结构。
  1. 全局搜索attachCategories,找到load_categories_nolock出现了对它调用。

  2. 全局搜索load_categories_nolock,找到了loadAllCategories

  1. 全局搜索loadAllCategories,找到了load_images
  1. load_images是在之前的objc_init中注册回调dyld,使其加载镜像的函数。

  2. map_images一章中见过load_images。是通过dyld的注册回调,用来加载镜像。

  3. 从搜索的流程看,是符合map_images这章中的read_images中对分类的处理的官方注释的。

  4. 那么实现类的realizeClassWithoutSwift中的methodizeClass,它的attachToClass是没有对类的分类进行操作吗?

3. 创建分类进行测试

1. JDMan和main.m中的代码

main.m :

这里删除掉了上面探索分类本质的时候在main.m中创建的分类代码。

JDMan :

2. 创建分类
3. 测试用的自己写的代码段 :

代码段1 :

arduino 复制代码
            const char *qudaodemingzi = cls->mangledName();
            const char *JDManName = "JDMan";
            if (strcmp(qudaodemingzi, JDManName) == 0) {
                printf("找到自己定义的类了 : %s 从函数 : %s 拿到的\n",qudaodemingzi,__func__);
            }

代码段2 :

ini 复制代码
    const char *qudaodemingzi = cls->mangledName();
    const char *JDManName = "JDMan";
    if (strcmp(qudaodemingzi, JDManName) == 0) {
        auto jd_ro = (const class_ro_t *)cls->data();
        auto jd_isMeta = jd_ro->flags & RO_META;
        if (!jd_isMeta) {
            printf("找到自己定义的类了:%s 从函数 : %s 拿到的\n",qudaodemingzi,__func__);
        }
    }

代码段3 :

ini 复制代码
    const char *qudaodemingzi = cls->mangledName();
    const char *JDManName = "JDMan";
    if (strcmp(qudaodemingzi, JDManName) == 0) {
        auto jd_isMeta = cls->isMetaClass();
        if (!jd_isMeta) {
            printf("找到自己定义的类了 :%s 从函数 : %s 拿到的\n",qudaodemingzi,__func__);
        }
    }
4. 将代码段贴到源码中

用到的代码段不一样,也是根据类的实现情况来确定的。可以自行调整。

  1. read_images中,找到非懒加载类的实现,用代码段1 :
  1. realizeClassWithoutSwift中,用代码段2 :
  1. methodizeClass中,用代码段3 :
  1. attachToClass中,在if判断中分别加了代码段3 :

(1). attachToClass的if外部 :

(2). attachToClass的if内部 :

  1. 进入load_categories_nolock,加入代码段3 ,稍作修改,将printf里面直接改成load_categories_nolock,因为这是个迭代器,所以__func__只会显示operator(),改成如下图 :
  1. 进入attachCategories,加入代码段3 :

五、只有一个分类,分类的加载情况

就如上面创建的,JDMan只有一个分类JDMan(LA)。

1. 类非懒加载+分类非懒加载

read_imagesrealizeClassWithoutSwiftload_categories_nolockattachCategories中自定义的代码段中加入断点,:

运行项目,结果如下 :

  1. 先进入了read_images的断点 :
  1. 再进入了realizeClassWithoutSwift的断点 :
  1. 然后进入了load_categories_nolock的断点 :
  1. 最后进入了attachCategories的断点 :
  1. 再运行,就会完成程序的运行,得到了流程如下 :
  1. 可以发现,是没有进入attachToClassif判断的。

小结 :

非懒加载类 + 非懒加载分类的加载 :

(1). 非懒加载类通过: read_images--->realizeClassWithoutSwift--->methodizeClass--->attachToClass先进行加载和实现。

(2). 非懒加载分类通过 :

load_images--->loadAllCategories--->load_categories_nolock--->attachCategories再进行加载和实现。并没有进入attachToClassif判断的流程。

2. 类非懒加载+分类懒加载

首先注释掉JDMan(LA).m这个分类中的+(void)load方法。分类变成懒加载。

运行项目,结果如下 :

  1. 先进入了read_images的断点 :
  1. 然后进入了realizeClassWithoutSwift的断点 :
  1. 项目直接运行结束,看自定义代码段打印的结果 :
  1. 并没有进入load_images的流程。那么分类是否是和类一起加载的?

  2. 重新运行项目,将断点走到realizeClassWithoutSwift里面。然后向下执行断点,到auto isMeta = ro->flags & RO_META;这一行 :

  1. 利用lldb查看ro的数据 :

小结 :

非懒加载类 + 懒加载分类 :

懒加载分类的数据会在编译的时候就和非懒加载类一起被存入了非懒加载类的ro中。在非懒加载类加载的时候,一起加载到内存。不需要经过attachCategories等步骤再进行加载。

3. 类懒加载+分类非懒加载

注释掉JDMan类中的+(void)load。打开JDMan(LA)分类中的+(void)load

运行项目,结果如下 :

  1. 先进入了read_images的断点 :

2.然后进入了realizeClassWithoutSwift的断点 :

  1. 项目直接运行结束,看自定义代码段打印的结果 :
  1. 并没有进入load_images的流程。那么分类非懒加载是否会使类也成为非懒加载?在read_images非懒加载类的循环那里,加入如下测试代码,如图 :
  1. 再次运行818.2项目。
  1. JDMan出现在了非懒加载类中,证明非懒加载的分类使类也变成了非懒加载的。那么分类的数据是否也已经存储到了类中?重新运行818.2,然后使断点执行到realizeClassWithoutSwift中,移动断点到ro下面,查看ro中的数据。

小结 :

懒加载类 + 非懒加载分类 :

非懒加载的分类会使懒加载的类变成非懒加载,分类数据和类的数据在编译期一起被写入ro。在类加载的时候,一起加载到内存。不需要经过attachCategories等步骤再进行加载。

4. 类懒加载+分类懒加载

打开类和分类的+(void)load方法。在main.m中让类被调用一次,例如在main.m加入JDMan *aMan = [[JDMan alloc] init];

运行项目,结果如下 :

  1. 进入了realizeClassWithoutSwift的断点,只不过是通过lookUpImpOrForward进入的 :
  1. 然后就直接运行完成,不会走其他的断点。

  2. 那么分类是否也和上面一样,直接和类一起完成了加载?重新运行项目,看ro的数据 :

小结 :

懒加载类+懒加载分类 : 类会在第一次被调用的时候完成实现和加载,分类的实现和加载与类一起完成。分类的数据存储在ro中。

六、总结

  1. 分类本质是结构体category_t
  2. 分类中添加的属性不会自动生成setter和getter方法,需要用到关联对象。
  3. 如果只有一个分类的情况下,分类的实现和加载情况如下 :

(1). 非懒加载类 + 非懒加载分类 :

非懒加载类的加载 : map_images--->read_images--->realizeClassWithoutSwift--->methodizeClass--->attachToClass

非懒加载分类的加载 : load_images--->loadAllCategories--->load_categories_nolock--->attachCategories

只有一个分类,非懒加载分类不会进入类的methodizeClass--->attachToClass--->attachCategories流程中进行加载。

(2). 非懒加载类 + 懒加载分类 :

分类的数据会在编译期就被写入类的ro中。伴随着类的加载完成加载。不进入attachCategories

(3). 懒加载类 + 非懒加载分类 :

非懒加载分类会使懒加载的类变成非懒加载的类,分类的数据也会在编译期就被写入到类的ro中。分类会伴随着类的加载完成加载。不进入attachCategories

(4). 懒加载类 + 懒加载分类 :

分类的数据也会在编译期就被写入类的ro中。当类第一次被调用,通过lookUpImpOrForward进行实现和加载的时候,分类伴随着一起完成。不进入attachCategories

相关推荐
missmisslulu21 小时前
电容笔值得买吗?2024精选盘点推荐五大惊艳平替电容笔!
学习·ios·电脑·平板
GEEKVIP1 天前
手机使用技巧:8 个 Android 锁屏移除工具 [解锁 Android]
android·macos·ios·智能手机·电脑·手机·iphone
GEEKVIP1 天前
如何在 Windows 10 上恢复未保存/删除的 Word 文档
macos·ios·智能手机·电脑·word·笔记本电脑·iphone
奇客软件1 天前
iPhone使用技巧:如何恢复变砖的 iPhone 或 iPad
数码相机·macos·ios·电脑·笔记本电脑·iphone·ipad
奇客软件2 天前
如何从相机的记忆棒(存储卡)中恢复丢失照片
深度学习·数码相机·ios·智能手机·电脑·笔记本电脑·iphone
GEEKVIP2 天前
如何修复变砖的手机并恢复丢失的数据
macos·ios·智能手机·word·手机·笔记本电脑·iphone
一丝晨光2 天前
继承、Lambda、Objective-C和Swift
开发语言·macos·ios·objective-c·swift·继承·lambda
GEEKVIP3 天前
iPhone/iPad技巧:如何解锁锁定的 iPhone 或 iPad
windows·macos·ios·智能手机·笔记本电脑·iphone·ipad
KWMax3 天前
RxSwift系列(二)操作符
ios·swift·rxswift
Mamong3 天前
Swift并发笔记
开发语言·ios·swift