1. 回顾

在上篇博文中,已经从dyld_objc_init再到read_images整个流程串联起来了,最后定位到了类的初始化是在realizeClassWithoutSwift中,本篇博文将深入分析类的加载,请搬好板凳坐下认真往下看。

iOS底层探索之类的加载(一):read_images分析

2.realizeClassWithoutSwift

read_images流程中,会对类进行一些修复工作,同时会将类的名称与类进行关联,插入对照表中,并更新到内存中的类表。
rwro的处理我们还不得而知。那么编译生成的MachO文件中类的相关信息,是何时插入到内存对应的cls中的呢?

// Category discovery MUST BE Late to avoid potential races// when other threads call the new category code before// this thread finishes its fixups.// +load handled by prepare_load_methods()// Realize non-lazy classes (for +load methods and static instances)for (EACH_HEADER) {classref_t const *classlist = hi->nlclslist(&count);for (i = 0; i < count; i++) {Class cls = remapClass(classlist[i]);if (!cls) continue;addClassTableEntry(cls);if (cls->isSwiftStable()) {if (cls->swiftMetadataInitializer()) {_objc_fatal("Swift class %s with a metadata initializer ""is not allowed to be non-lazy",cls->nameForLogging());}// fixme also disallow relocatable classes// We can't disallow all Swift classes because of// classes like Swift.__EmptyArrayStorage}realizeClassWithoutSwift(cls, nil);}}

read_images源码中的部分注释可以知道,这是针对非懒加载的类进行初始化操作。那么什么叫做非懒加载的类呢?就是实现了+load方法

  • 非懒加载的类实现了+load方法
  • 通过nlclslist函数获取非懒加载类列表。
  • 对类进行递归处理,完成非懒加载类的初始化工作。
  • addClassTableEntry把类添加到内存表中。
  • realizeClassWithoutSwift初始化类。

2.1 realizeClassWithoutSwift源码

/***********************************************************************
* realizeClassWithoutSwift
* Performs first-time initialization on class cls,
* including allocating its read-write data.
* Does not perform any Swift-side initialization.
* Returns the real class structure for the class.
* Locking: runtimeLock must be write-locked by the caller
**********************************************************************/
static Class realizeClassWithoutSwift(Class cls, Class previously)
{runtimeLock.assertLocked();class_rw_t *rw;Class supercls;Class metacls;if (!cls) return nil;if (cls->isRealized()) {validateAlreadyRealizedClass(cls);return cls;}ASSERT(cls == remapClass(cls));// fixme verify class is not in an un-dlopened part of the shared cache?const char * className = "JPStudent";if (strcmp(class_getName(cls), className) == 0){printf("hello JPStudent...");}auto ro = (const class_ro_t *)cls->data();auto isMeta = ro->flags & RO_META;if (ro->flags & RO_FUTURE) {// This was a future class. rw data is already allocated.rw = cls->data();ro = cls->data()->ro();ASSERT(!isMeta);cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);} else {// Normal class. Allocate writeable class data.rw = objc::zalloc<class_rw_t>();rw->set_ro(ro);rw->flags = RW_REALIZED|RW_REALIZING|isMeta;cls->setData(rw);}cls->cache.initializeToEmptyOrPreoptimizedInDisguise();#if FAST_CACHE_METAif (isMeta) cls->cache.setBit(FAST_CACHE_META);
#endif// Choose an index for this class.// Sets cls->instancesRequireRawIsa if indexes no more indexes are availablecls->chooseClassArrayIndex();if (PrintConnecting) {_objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",cls->nameForLogging(), isMeta ? " (meta)" : "", (void*)cls, ro, cls->classArrayIndex(),cls->isSwiftStable() ? "(swift)" : "",cls->isSwiftLegacy() ? "(pre-stable swift)" : "");}// Realize superclass and metaclass, if they aren't already.// This needs to be done after RW_REALIZED is set above, for root classes.// This needs to be done after class index is chosen, for root metaclasses.// This assumes that none of those classes have Swift contents,//   or that Swift's initializers have already been called.//   fixme that assumption will be wrong if we add support//   for ObjC subclasses of Swift classes.supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);#if SUPPORT_NONPOINTER_ISAif (isMeta) {// Metaclasses do not need any features from non pointer ISA// This allows for a faspath for classes in objc_retain/objc_release.cls->setInstancesRequireRawIsa();} else {// Disable non-pointer isa for some classes and/or platforms.// Set instancesRequireRawIsa.bool instancesRequireRawIsa = cls->instancesRequireRawIsa();bool rawIsaIsInherited = false;static bool hackedDispatch = false;if (DisableNonpointerIsa) {// Non-pointer isa disabled by environment or app SDK versioninstancesRequireRawIsa = true;}else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object")){// hack for libdispatch et al - isa also acts as vtable pointerhackedDispatch = true;instancesRequireRawIsa = true;}else if (supercls  &&  supercls->getSuperclass()  &&supercls->instancesRequireRawIsa()){// This is also propagated by addSubclass()// but nonpointer isa setup needs it earlier.// Special case: instancesRequireRawIsa does not propagate// from root class to root metaclassinstancesRequireRawIsa = true;rawIsaIsInherited = true;}if (instancesRequireRawIsa) {cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);}}
// SUPPORT_NONPOINTER_ISA
#endif// Update superclass and metaclass in case of remappingcls->setSuperclass(supercls);cls->initClassIsa(metacls);// Reconcile instance variable offsets / layout.// This may reallocate class_ro_t, updating our ro variable.if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);// Set fastInstanceSize if it wasn't set already.cls->setInstanceSize(ro->instanceSize);// Copy some flags from ro to rwif (ro->flags & RO_HAS_CXX_STRUCTORS) {cls->setHasCxxDtor();if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {cls->setHasCxxCtor();}}// Propagate the associated objects forbidden flag from ro or from// the superclass.if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||(supercls && supercls->forbidsAssociatedObjects())){rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;}// Connect this class to its superclass's subclass listsif (supercls) {addSubclass(supercls, cls);} else {addRootClass(cls);}// Attach categoriesmethodizeClass(cls, previously);return cls;
}

2.2 ro、rw的处理

machO中获取的数据地址,根据class_ro_t格式进行强转,同时初始化rw的空间,并复制一份ro的数据放入rw中。

auto ro = (const class_ro_t *)cls->data();
auto isMeta = ro->flags & RO_META;
// 判断是否为元类
if (ro->flags & RO_FUTURE) {// This was a future class. rw data is already allocated.rw = cls->data();ro = cls->data()->ro();ASSERT(!isMeta);cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
} else {// Normal class. Allocate writeable class data.rw = objc::zalloc<class_rw_t>();rw->set_ro(ro);rw->flags = RW_REALIZED|RW_REALIZING|isMeta;cls->setData(rw);
}
  • ro属于clean memory,在编辑时即确定的内存空间,只读,加载后不会发生改变的内存空间,包括类名称、方法、协议和实例变量的信息;
  • rw的数据空间属于dirty memoryrw是运行时的结构,可读可写,由于其动态性,可以往类中添加属性、方法、协议。在运行时会发生变更的内存。

具体可以去看看wwdc2020里面做了很详细的说明和分析。

2.3 类的处理

父类和元类的处理

    // 递归,加载父类、元类的实现supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

对于是否支持NONPOINTER_ISA的类进行处理,指针优化是指Isa的末尾位是1
对于元类以及特殊情况下的场景的一些类,无需开启指针优化的类,使用Raw Isa,Isa的末尾位是0

#if SUPPORT_NONPOINTER_ISAif (isMeta) {//元类isa是纯指针。cls->setInstancesRequireRawIsa();} else {//isa是否纯指针, flags中第13位bool instancesRequireRawIsa = cls->instancesRequireRawIsa();bool rawIsaIsInherited = false;static bool hackedDispatch = false;//这就是环境变量中配置的 OBJC_DISABLE_NONPOINTER_ISAif (DisableNonpointerIsa) {// Non-pointer isa disabled by environment or app SDK version//配置环境变量为YES后,isa是一个纯指针。instancesRequireRawIsa = true;}//OS_object类时纯指针else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object")){// hack for libdispatch et al - isa also acts as vtable pointerhackedDispatch = true;instancesRequireRawIsa = true;}//父类是纯指针,并且父类还有父类。那么自己也要是纯指针。rawIsaIsInherited 表示继承的是纯指针else if (supercls  &&  supercls->getSuperclass()  &&supercls->instancesRequireRawIsa()){instancesRequireRawIsa = true;rawIsaIsInherited = true;}//递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。rawIsaIsInherited只是控制打印。if (instancesRequireRawIsa) {cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);}}
// SUPPORT_NONPOINTER_ISA
#endif
  • 递归实例化父类和元类。

  • 判断设置isa是否纯指针。

  • 元类isa是纯指针。

  • 类的isa是否纯指针取值flags第13位。

  • 父类是纯指针,并且父类还有父类。那么自己也要是纯指针。rawIsaIsInherited (只是控制打印)表示继承的是纯指针。

  • 递归设置isa为纯指针,子类也设置为纯指针。(父类为纯指针,子类也为纯指针)。

关联父类与元类

//关联父类与元类。也就是继承链与isa走位。cls->setSuperclass(supercls);cls->initClassIsa(metacls);

2.3 代码调式

添加如下代码,定位调式

    const char * className = "JPStudent";if (strcmp(class_getName(cls), className) == 0){printf("hello JPStudent...");}

设置断点,分别判断在ro初始化前、后,进行lldb调式打印ro的数据结构的变化。

  • ro 赋值前打印:
hello JPStudent...(lldb) p cls
(Class) $3 = 0x00000001000084e0
(lldb) p cls
(Class) $4 = JPStudent
(lldb) p ro
(const class_ro_t *) $5 = 0x00007ffeefbff190
(lldb) p *$5
(const class_ro_t) $6 = {flags = 4022333872instanceStart = 32766instanceSize = 0reserved = 0= {ivarLayout = 0x0000000100008508 "\xe0\x84"nonMetaclass = JPStudent}name = {std::__1::atomic<const char *> = "\xe0\x84" {Value = 0x0000000100008508 "\xe0\x84"}}baseMethodList = 0x00007ffeefbff1e0baseProtocols = 0x00000001003269a0ivars = 0x00000001000084e0weakIvarLayout = 0x0000000100008508 "\xe0\x84"baseProperties = 0x000000010036d080_swiftMetadataInitializer_NEVER_USE = {}
}
  • ro 赋值后打印:
(lldb) p ro
(const class_ro_t *) $9 = 0x00000001000081b8
(lldb) p *$9
(const class_ro_t) $10 = {flags = 0instanceStart = 8instanceSize = 24reserved = 0= {ivarLayout = 0x0000000000000000nonMetaclass = nil}name = {std::__1::atomic<const char *> = "JPStudent" {Value = 0x0000000100003d2d "JPStudent"}}baseMethodList = 0x0000000100008200baseProtocols = nilivars = 0x0000000100008340weakIvarLayout = 0x0000000000000000baseProperties = 0x0000000100008388_swiftMetadataInitializer_NEVER_USE = {}
}
(lldb) p $10.baseMethodList
(void *const) $11 = 0x0000000100008200
(lldb) p *$11
(lldb)

通过调式打印,并没有打印出方法列表,但是我JPStudent是有方法的,说明此时,只是有了ro的数据结构,只是个空的结构的地址,selimp还没有绑定起来。

realizeClassWithoutSwift中最后调用了如下代码,根据注释可以看到应该是对分类的处理,参数cls没问题,previously是从_read_images中传过来的为nil

// Attach categories
methodizeClass(cls, previously);

那么现在就对methodizeClass分析

3. methodizeClass分析

3.1 methodizeClass源码

/***********************************************************************
* methodizeClass
* Fixes up cls's method list, protocol list, and property list.
* Attaches any outstanding categories.
* Locking: runtimeLock must be held by the caller
**********************************************************************/
static void methodizeClass(Class cls, Class previously)
{runtimeLock.assertLocked();bool isMeta = cls->isMetaClass();auto rw = cls->data();auto ro = rw->ro();auto rwe = rw->ext();// Methodizing for the first timeif (PrintConnecting) {_objc_inform("CLASS: methodizing class '%s' %s", cls->nameForLogging(), isMeta ? "(meta)" : "");}// Install methods and properties that the class implements itself.method_list_t *list = ro->baseMethods();if (list) {prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls), nullptr);if (rwe) rwe->methods.attachLists(&list, 1);}property_list_t *proplist = ro->baseProperties;if (rwe && proplist) {rwe->properties.attachLists(&proplist, 1);}protocol_list_t *protolist = ro->baseProtocols;if (rwe && protolist) {rwe->protocols.attachLists(&protolist, 1);}// Root classes get bonus method implementations if they don't have // them already. These apply before category replacements.if (cls->isRootMetaclass()) {// root metaclassaddMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);}// Attach categories.if (previously) {if (isMeta) {objc::unattachedCategories.attachToClass(cls, previously,ATTACH_METACLASS);} else {// When a class relocates, categories with class methods// may be registered on the class itself rather than on// the metaclass. Tell attachToClass to look for those.objc::unattachedCategories.attachToClass(cls, previously,ATTACH_CLASS_AND_METACLASS);}}objc::unattachedCategories.attachToClass(cls, cls,isMeta ? ATTACH_METACLASS : ATTACH_CLASS);#if DEBUG// Debug: sanity-check all SELs; log method list contentsfor (const auto& meth : rw->methods()) {if (PrintConnecting) {_objc_inform("METHOD %c[%s %s]", isMeta ? '+' : '-', cls->nameForLogging(), sel_getName(meth.name()));}ASSERT(sel_registerName(sel_getName(meth.name())) == meth.name());}
#endif
}
  • ro 、rw、rew初始化,元类判断标记
  • 获取方法列表
  • 获取属性列表
  • 获取协议列表
  • 是否根元类,根元类加了initialize方法
  • 分类的处理

methodizeClass方法进行断点调试,发现rwe为空,说明此时还没有对类进行相关的扩展操作,所以rwe还没有被创建初始化。那么此时我们对方法、属性、协议的添加操作也是无效的。

3.2 prepareMethodLists

method_list_t处理时调用prepareMethodLists了方法,核心代码如下:

// Add method lists to array.// Reallocate un-fixed method lists.// The new methods are PREPENDED to the method list array.for (int i = 0; i < addedCount; i++) {method_list_t *mlist = addedLists[i];ASSERT(mlist);// Fixup selectors if necessaryif (!mlist->isFixedUp()) {fixupMethodList(mlist, methodsFromBundle, true/*sort*/);}}
  • addedCount值为1addedLists**类型,就是指针的指针类型。那么mlist就是rolist

  • 如果没有排序则修正,再排序romethodLists

  • fixupMethodList

methodname也就是SEL进行了修正,也就与慢速消息查找的二分查找对应上了。

  • 排序验证

通过以下代码对 排序前 和排序后 进行方法的打印

for (auto& meth : *mlist) {const char *name = sel_cname(meth.name());printf("排序后:%s---%p\n",name,meth.name());}

奇怪的是排序前和排序后的顺序是一样的,我猜测有可能是顺序已经排序好了,无需要再排序,或者是添加的时候就已经是排好了。

4.分类的探索

prepareMethodLists执行完成后是没有rwe数据,所以后续的attachLists相关操作都不会执行。根据之前WWDC的介绍rwe在有分类的情况下会出现,那么就去建立一个分类看看。

4.1 分类底层结构

通过clang 查看分类结构

    struct _category_t {const char *name;struct _class_t *cls;const struct _method_list_t *instance_methods;const struct _method_list_t *class_methods;const struct _protocol_list_t *protocols;const struct _prop_list_t *properties;
};
  • 分类也是一个结构体类型。
  • name的名字应该是分类名称。
  • cls指向类。
  • 在类中只有一个methods,在分类中有了instance_methodsclass_methods。因为分类没有元类(也就是没有分元类)。
  • 分类中是有properties的。

通过xcode帮助文档也可以查看

再去objc源码里面搜索objc_category验证一下

还真有哦!所以也验证了分类的底层是结构体!

4.2 分类源码探索

通过对methodizeClass源码的分析,分类的处理核心逻辑在attachListsattachToClass中。rwe的赋值是来源于rw->ext()代码如下:

auto rwe = rw->ext();
  • ext() 代码如下:
class_rw_ext_t *ext() const {return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
}class_rw_ext_t *extAllocIfNeeded() {//获取rweauto v = get_ro_or_rwe();if (fastpath(v.is<class_rw_ext_t *>())) {return v.get<class_rw_ext_t *>(&ro_or_rw_ext);} else {//创建rwereturn extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));}
}

extAllocIfNeeded中进行了rwe的创建。通过在objc源码里面搜索extAllocIfNeeded的调用,发现了在attachCategories方法里面的调用比较符合。而attachCategories的调用逻辑在attachToClassload_categories_nolock中。

由此所以分类的加载就有了两条线路:

  • methodizeClass --> attachToClass --> attachCategories
  • load_images --> loadAllCategories --> load_categories_nolock --> attachCategories

5.总结

  • 懒加载类与⾮懒加载类: 指当前类是否实现load⽅法
  • 非懒加载类情况 map_images的时候 加载所有类数据_getObjc2NonlazyClassList --> readClass -- > realizeClassWithoutSwift --> methodizeClass
  • 懒加载类情况是数据加载推迟到第⼀次消息的时候,lookUpImpOrForward --> realizeClassMaybeSwiftMaybeRelock -- > realizeClassWithoutSwift --> methodizeClass

更多内容持续更新

iOS底层探索之类的加载(二): realizeClassWithoutSwift分析相关推荐

  1. iOS底层探索(二) - 写给小白看的Clang编译过程原理

    iOS底层探索(一) - 从零开始认识Clang与LLVM 写在前面 编译器是属于底层知识,在日常开发中少有涉及,但在我的印象中,越接近底层是越需要编程基本功,也是越复杂的.但要想提升技术却始终绕不开 ...

  2. iOS底层探索二(OC 中 alloc 方法 初探)

    前言 相关文章: iOS底层探索一(底层探索方法) iOS底层探索三(内存对齐与calloc分析) iOS底层探索四(isa初探-联合体,位域,内存优化) iOS底层探索五(isa与类的关系) iOS ...

  3. iOS 底层探索 - 消息转发

    一.动态方法解析流程分析 我们在上一章<消息查找>分析到了动态方法解析,为了更好的掌握具体的流程,我们接下来直接进行源码追踪. 我们先来到 _class_resolveMethod 方法, ...

  4. iOS开发UI篇—懒加载

    iOS开发UI篇-懒加载 1.懒加载基本 懒加载--也称为延迟加载,即在需要的时候才加载(效率低,占用内存小).所谓懒加载,写的是其get方法. 注意:如果是懒加载的话则一定要注意先判断是否已经有了, ...

  5. [html] iOS下页面如何启动加载时显示画面图片?如何设置大小?它有什么好处?

    [html] iOS下页面如何启动加载时显示画面图片?如何设置大小?它有什么好处? <link rel="apple-touch-startup-image" href=&q ...

  6. 微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题

    微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题 参考文章: (1)微信JSSDK多图片上传并且解决IOS系统上传一直加载的问题 (2)https://www.cnblogs.com/co ...

  7. 基于Vue框架开发的页面加载二维地图以及交互

    一.在Vue项目中引入二维地图 1.切换到公司的仓库下载地图插件 npm config set registry http://nexus.toops.club/repository/npm-zui/ ...

  8. 图片加载 二维码 解析

    图片加载 二维码 解析 1. layout布局文件 (1)activity_category.xml <?xml version="1.0" encoding="u ...

  9. iOS 底层探索篇 —— KVC 底层原理

    iOS 底层探索篇 -- KVC 底层原理 1. Method Swizzling的坑与应用 1.1 method-swizzling 是什么? 1.2 坑点 坑点1:method-swizzling ...

  10. android加载二维码带中间logo

    android加载二维码带中间logo 很简单的,我也是先看了很多博客,然后总结了一下,感谢万能的网友 1导入依赖 //二维码加载依赖 implementation 'com.google.zxing ...

最新文章

  1. [每天进步一点 -- 流水账]第3周
  2. 使用cpanel后台的“时钟守护作业”功能完成空间的定时全备份
  3. 鸡啄米:模态、非模态对话框
  4. Vue.js 状态过渡
  5. html js 动态表格数据,HTML+JS动态表格
  6. 洛谷10月月赛Round.1| P3399 丝绸之路 [DP]
  7. Node.js 应用故障排查手册 —— 冗余配置传递引发的内存溢出
  8. 一次完整的数据分析实战!仅用4步,效率吊打Excel和Python
  9. vim 基础命令大全
  10. 网络流24题(更新中
  11. 编程语言和javascript
  12. HiveQL(三):修改表ALTER TABLE
  13. AirDisk创建网盘
  14. 联想笔记本linux无线网卡,科学网—配置lenovo E430 + Ubuntu 13.04无线网卡 - 彭友松的博文...
  15. Not authorized , ReasonPhrase:Unauthorized
  16. iOS 性能、架构、socket 小结
  17. 鹏业安装算量软件识别电气系统图(表格式)
  18. vue项目 - Mockjs 模拟后台接口数据
  19. 初体验myeclipse+Axis2 开发web service (一)转
  20. 海量数据:快速查找一个数字是否出现在40亿个数字中

热门文章

  1. Python 06 编码
  2. [SCOI2016]背单词
  3. windows安装composer总结
  4. AngularJs(Part 3)--注册服务
  5. PHP漏洞全解(三)-客户端脚本植入
  6. CSS学习笔记(二)选择器
  7. 简述Flash 事件机制?
  8. CSS3中很容易混淆的transform,translate,transition。如何去区分,以及综合写法。
  9. vs2013使用remote debug
  10. MFC下debug改成release版本出现问题及解决办法