在上一篇文章中,我们了解了objc
和dyld
的关联,那么关联之后怎么对类进行加载的呢, 本篇将对类的加载进行探索和分析。
在上一篇文章中我们通过objc_init
里面的_dyld_objc_notify_register
来到了_read_images
我们点到_read_images
里面去,把项目跑起来,然后打断点调试,看看走哪些代码
来到3597
行的时候,我们在里面判断一下读取自己的类。
因为这里还会读取很多系统的类,而系统的类我们又不好操控,所以在readClass
里面,我们写上这么一段代码,读取到我们自己的类,,让自己创建的类进来。
来到我们自己类之后接着往下走。通过在判断条件里面打断点的方式,判断每个if
下面有没有走。
接着来到realizeClassWithoutSwift
实现当前的类,我们在自己研究的类这里打个断点,发现没有进来,这是啥情况,一般人又玩不进来了。 其实苹果也给了注释 Realize non-lazy classes (for +load methods and static instances)
大概意思是需要实现非懒加载的类才可以进去,用+load
方法来实现。那么我们在LGPerson
里面加一个load
方法试试
加完之后我们再来运行一下看看进没进来
果然进来了,从而得出加了load
方法后让类提前实现成为非懒加载类,那么懒加载类在什么时候呢,我们把load方法注释来到realizeClassWithoutSwiftbt
打个断点,然后打印一下堆栈
在我们[LGPerson alloc];
其实是进行了一次消息的发送_objc_msgSend_uncached
,也就是当你要用到这个类的时候才会去实现这个类,这样大大减少了程序启动前的内存加载。
我们用一张PPT来总结下懒加载和非懒加载的区别:
类的加载
明白了懒加载和非懒加载的区别之后,回到_read_images
进来的断点里面,断点进来后我们接着进到realizeClassWithoutSwift
里面去,这个方法也是本篇文章研究的重点,因为在类相关的判断里面都有realizeClass
实现类的方法。
从而能看出来类就是在realizeClassWithoutSwift
这个里面实现的,其实之前在lookupimp
慢速查找的时候也提到过,那我们点进去看一下到底是怎么实现的
首先在开始前还是加一句只研究本类的代码, 排除其它类和元类,同时在自己类里面加了一些方法和属性,方便研究。
加好之后,我们回到realizeClassWithoutSwift
,接下来再断点定到自己的类,接着往下看
在这个地方从我们的MachO
里面读取到了我们的data
,按照一定的格式转换成class_ro_t
赋值给了临时变量ro
,
拿到ro
之后判断是否为元类,显然不是,所以来到else
里面,来到里面之后第一句代码rw = objc::zalloc
申请和开辟class_rw_t
,此时rw
还只是刚创建,是空的,然后set_ro
把ro
放到了rw
里面,再setData
把rw
给了LGPerson
,设置完了之后LGPerson
还是空的,接着往下走
走到这两句就意味着把我们类信息确定好之后,再确定父类和元类,从LGPerson 类信息 -> 父类 -> 元类
就是为了确定继承链关系,实现完了之后就会来到isa
的设置,完了之后就把整个继承链设置好了。
接着往下走,设置一些其它信息,比如setHasCxxDtor
,设置Cxx
,这就是为什么类里面有这么个Cxx
的方法,这是系统默认设置的。
设置完了之后来到methodizeClass
这句重点,点进去,然后在methodizeClass
继续做一个判断,只研究当前自己的类,因为在realizeClassWithoutSwift
递归了父类和元类,所以methodizeClass
会进来父类和元类,写这个判断就是为了排除别的类。
来到自己的类之后,把rw
,ro
,rwe
拿了出来
auto rw = cls->data();
auto ro = rw->ro();
auto rwe = rw->ext();
然后把baseMethods
拿出来
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
if (rwe) rwe->methods.attachLists(&list, 1);
}
然后走到prepareMethodLists
,看下里面做了什么操作
看到下面有一句fixupMethodList
,注释的意思是修正方法list
,应该是对方法排序的意思,我们点进去看一下
果然,把这里把方法的名字进行一系列的处理之后开始sort
排序
根据sel
的名字来进行排序,排序前和排序后分别来打印一下
注意,这里是根据名字的地址大小进行排序的,而不是文字的字符串大小排序的。
把顺序排好之后,来到rwe
,我断点走完之后发现rwe始终为NULL,这是啥情况,这个地方让很多人百思不得其解,在后面我会为大家揭晓。我们先来看一下methodizeClass
这个方法上面的注释Attach categories
附加分类,说明跟分类有关系,既然说到分类那就看看分类里面到底有些什么东西,可以通过Clang来看,我这里就直接搜索category_t
来看
看到category_t
有classMethods
,protocols
等等,那么这里面的这些东西怎么加到我们的类里面去的呢,带着这个问题继续来探索:
我们在methodizeClass
往下翻会看到分类相关的东西
在1480
行有一个attachToClass
,非常好,我们点进去看到底是怎么加到类里面去的
老规矩,在前面还是加上只研究本类的判断, 看到category_list &list = it->second;
,然后在下面进行实现分类的判断调用了attachCategories
, 如果if (flags & ATTACH_CLASS_AND_METACLASS)
满足,则说明类和分类都实现了,如果不满足,则说明主类没有实现load
方法,而分类实现了load
方法需要进行加载了,就会来到else
下面。
我们点到attachCategories
里面去看看
来到这里面之后我们意外的发现 auto rwe = cls->data()->extAllocIfNeeded();
,rwe
原来就在这里进行创建或者获取,看来要对本类进行添加分类信息的时候才会处理这个rwe
。那么还有哪些地方有用到吗,我们全局搜索
通过全局搜索能够看出rwe
除了在添加分类以外,还在除了系统外的addMethod
,addProtocol
,addProperty
这些地方用到过,说白了正如WWDC
里面所说的只有对原始的内存进行处理和修改的时候我们才会用到rwe
。
明白了rwe
之后,我们再回attachCategories
接着打一个断点进入LGPerson
当前要研究的类,然后开始遍历cats_count
可以看到这里来到的是LGPerson
的分类LGA
,然后拿到分类的method_list
拿到分类的方法列表之后,判断mcount
是否等于ATTACH_BUFSIZ
,表示最大修改次数为64次, 如果不等于则mlists[64 - 1] = mlist
进行倒序插入
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
倒序插入完了之后会来到
if (mcount > 0) {
prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount, NO, fromBundle);
rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
if (flags & ATTACH_EXISTING) flushCaches(cls);
}
到这一步,我们数据已经准备好了,那么到底是怎么加载到我们类里面去的呢,还有在什么时机加进去的,这将在下篇文章进行展开分析。 本来这篇文章只讲类的加载的,结果摸到分类来了,也算是提前给下一篇文章做个预告
未完待续。。。。
iOS 底层原理 文章汇总