iOS底层探索(十二)类的加载(中)

书接上文,在iOS底层探索(十一) 类的加载(上)中,我们了解到在realizeClassWithoutSwift方法中进行了类的加载,我们继续研究这个方法。

realizeClassWithoutSwift方法探究

源码如下:

static Class realizeClassWithoutSwift(Class cls, Class previously)
{runtimeLock.assertLocked();class_rw_t *rw;Class supercls;Class metacls;const char *mangledName  = cls->mangledName();const char *LGPersonName = "LGPerson";if (strcmp(mangledName, LGPersonName) == 0) {auto kc_ro = (const class_ro_t *)cls->data();auto kc_isMeta = kc_ro->flags & RO_META;if (!kc_isMeta) {printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);}}if (!cls) return nil;if (cls->isRealized()) return cls;ASSERT(cls == remapClass(cls));// fixme verify class is not in an un-dlopened part of the shared cache?// 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);}#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)" : "");}// 递归循环父类与元类supercls = realizeClassWithoutSwift(remapClass(cls->superclass), 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->name, "OS_object")){// hack for libdispatch et al - isa also acts as vtable pointerhackedDispatch = true;instancesRequireRawIsa = true;}else if (supercls  &&  supercls->superclass  &&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->superclass = 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 categories - 分类 methodizeClass(cls, previously);return cls;
}

对当前方法进行判断类名LGPerson并进行断点调试。

怎么获取ro数据

获取ro的源码,如下:

    const class_ro_t *ro() const {auto v = get_ro_or_rwe();if (slowpath(v.is<class_rw_ext_t *>())) {return v.get<class_rw_ext_t *>()->ro;}return v.get<const class_ro_t *>();}

获取ro数据时,会对runtime进行判断,将会分别从rwero中获取。

怎么获取rw数据

由上方代码可知rw->set_ro(ro)方法为获取rw数据的方法。源码如下:

    void set_ro(const class_ro_t *ro) {auto v = get_ro_or_rwe();if (v.is<class_rw_ext_t *>()) {v.get<class_rw_ext_t *>()->ro = ro;} else {set_ro_or_rwe(ro);}}

创建可读空间时,或根据是否有rwe进行判断,如果有rwe则使用rwe,如果没有,则使用rorwe将会在后面讲解到。

递归获取类

由上方代码可知supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);是递归调用当前方法的位置,目的是确定继承链关系。

设置isa

源码如下:

#if SUPPORT_NONPOINTER_ISAif (isMeta) {cls->setInstancesRequireRawIsa();} else {bool instancesRequireRawIsa = cls->instancesRequireRawIsa();bool rawIsaIsInherited = false;static bool hackedDispatch = false;if (DisableNonpointerIsa) {instancesRequireRawIsa = true;}else if (!hackedDispatch  &&  0 == strcmp(ro->name, "OS_object")){hackedDispatch = true;instancesRequireRawIsa = true;}else if (supercls  &&  supercls->superclass  &&supercls->instancesRequireRawIsa()){instancesRequireRawIsa = true;rawIsaIsInherited = true;}if (instancesRequireRawIsa) {cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);}}
// SUPPORT_NONPOINTER_ISA
#endif

当前方法为,根据类的不同,如当前类父类元类等的不同进行isa的设置。

methodizeClass方法

分类方法调用,当有分类时,会调用该方法,方法源码如下:

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();// 自定义代码,为了判断自己写的类进行// startconst char *mangledName  = cls->mangledName();const char *LGPersonName = "LGPerson";if (strcmp(mangledName, LGPersonName) == 0) {bool kc_isMeta = cls->isMetaClass();auto kc_rw = cls->data();auto kc_ro = kc_rw->ro();if (!kc_isMeta) {printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);}}// end // 第一次方法化if (PrintConnecting) {...}// Install methods and properties that the class implements itself.method_list_t *list = ro->baseMethods();if (list) {// 将方法进行排序prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));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()) {// 根元类addMethod(cls, @selector(initialize), (IMP)&objc_noop_imp, "", NO);}// Attach categories.if (previously) {if (isMeta) {objc::unattachedCategories.attachToClass(cls, previously,ATTACH_METACLASS);} else {//  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模式下的操作。// 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
}

rw->ext()

在这个方法中我们可以看到,如何获取rwe,即rw->ext(),查看ext()方法。源码如下:

class_rw_ext_t *ext() const {return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>();}

由上方源码中我们可以看到ext()方法是调用get_ro_or_rwe()即获取ro或者rwe,该方法的作用上方已经讲过了。

prepareMethodLists 方法排序

该方法是对类中的方法进行排序操作。方便方法查找中的二分查找使用。源码如下:

static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,bool baseMethods, bool methodsFromBundle)
{runtimeLock.assertLocked();// 自定义方法,只研究自己的方法// startconst char *mangledName  = cls->mangledName();const char *LGPersonName = "LGPerson";if (strcmp(mangledName, LGPersonName) == 0) {bool kc_isMeta = cls->isMetaClass();auto kc_rw = cls->data();auto kc_ro = kc_rw->ro();if (!kc_isMeta) {printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);}}// endif (addedCount == 0) return;// 屏蔽基本方法if (baseMethods) {...}// 将方法插入到数组中,新方法在方法列表数组之前。for (int i = 0; i < addedCount; i++) {method_list_t *mlist = addedLists[i];ASSERT(mlist);// 方法排序,如果方法列表比之前的多,则需要进行方法排序if (!mlist->isFixedUp()) {// 修正方法列表fixupMethodList(mlist, methodsFromBundle, true/*sort*/);}}// 判断是否初始化if (cls->isInitialized()) {objc::AWZScanner::scanAddedMethodLists(cls, addedLists, addedCount);objc::RRScanner::scanAddedMethodLists(cls, addedLists, addedCount);objc::CoreScanner::scanAddedMethodLists(cls, addedLists, addedCount);}
}

由上方方法可知,fixupMethodList为该方法的核心内容,为修正方法列表。查看该方法源码如下:

static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{runtimeLock.assertLocked();ASSERT(!mlist->isFixedUp());// fixme lock less in attachMethodLists ?// dyld3 may have already uniqued, but not sorted, the listif (!mlist->isUniqued()) {mutex_locker_t lock(selLock);// Unique selectors in list.for (auto& meth : *mlist) {const char *name = sel_cname(meth.name);// 方法名字的赋予meth.name = sel_registerNameNoLock(name, bundleCopy);}}// 根据方法地址进行排序if (sort) {method_t::SortBySELAddress sorter;std::stable_sort(mlist->begin(), mlist->end(), sorter);}// Mark method list as uniqued and sortedmlist->setFixedUp();
}

查看SortBySELAddress方法,看他具体是怎么进行排序的

 struct SortBySELAddress :public std::binary_function<const method_t&,const method_t&, bool>{bool operator() (const method_t& lhs,const method_t& rhs){ return lhs.name < rhs.name; }};

由此可知,正常的方法排序是根据方法名字的字符串地址进行排序的。

那么当前的地址是一致的永远不变的吗?
从上面的代码可知sel_registerNameNoLock方法为给方法赋值name,查看该方法源码。

SEL sel_registerNameNoLock(const char *name, bool copy) {return __sel_registerName(name, 0, copy);  // NO lock, maybe copy
}

查看__sel_registerName方法,源码如下:

static SEL __sel_registerName(const char *name, bool shouldLock, bool copy)
{SEL result = 0;if (shouldLock) selLock.assertUnlocked();else selLock.assertLocked();if (!name) return (SEL)0;// 由代码可知,下面的方法是获取name地址的方法,而非最下面。result = search_builtins(name);if (result) return result;// 这里并不会运行。conditional_mutex_locker_t lock(selLock, shouldLock);auto it = namedSelectors.get().insert(name);if (it.second) {// No match. Insert.*it.first = (const char *)sel_alloc(name, copy);}return (SEL)*it.first;
}

断点可知该方法运行到search_builtins方法。并不会运行到该代码的下面。查看search_builtins的源码,如下:

static SEL search_builtins(const char *name)
{#if SUPPORT_PREOPTif (builtins) {SEL result = 0;if ((result = (SEL)builtins->get(name)))return result;if ((result = (SEL)_dyld_get_objc_selector(name)))return result;} else if (useDyldSelectorLookup) {if (SEL result = (SEL)_dyld_get_objc_selector(name))return result;}
#endifreturn nil;
}

从源码中可以看出,如果在第一次执行,或者没有获取到name时,方法会执行_dyld_get_objc_selector方法,该方法为dyld方法,查看该方法。源码如下:

const char* _dyld_get_objc_selector(const char* selName)
{// 如果在共享缓存中有该方法直接返回,没有则执行下面的ifif ( gObjCOpt != nullptr ) {if ( const objc_opt::objc_selopt_t* selopt = gObjCOpt->selopt() ) {const char* name = selopt->get(selName);if (name != nullptr)return name;}}if ( gUseDyld3 )return dyld3::_dyld_get_objc_selector(selName);return nullptr;
}

查看dyld3中的_dyld_get_objc_selector方法,源码如下:

const char* _dyld_get_objc_selector(const char* selName)
{log_apis("dyld_get_objc_selector()\n");return gAllImages.getObjCSelector(selName);
}

查看getObjCSelector方法源码,如下:

const char* AllImages::getObjCSelector(const char *selName) const {if ( _objcSelectorHashTable == nullptr )return nullptr;return _objcSelectorHashTable->getString(selName, _objcSelectorHashTableImages.array());
}

从上面的源码可知,使用hash表调用getString方法,全局搜索该方法,源码如下:

const char* ObjCStringTable::getString(const char* selName, const Array<uintptr_t>& baseAddresses) const {StringTarget target = getPotentialTarget(selName);// 判断是否为空地址。if (target == sentinelTarget)return nullptr;dyld3::closure::Image::ObjCImageOffset imageAndOffset;imageAndOffset.raw = target;uintptr_t sectionBaseAddress = baseAddresses[imageAndOffset.imageIndex];// 这里才是真正的赋值的位置,即方法名的获取。 基本地址 + 偏移量const char* value = (const char*)(sectionBaseAddress + imageAndOffset.imageOffset);if (!strcmp(selName, value))return value;return nullptr;
}

至此可知,在动态编译的过程中,会有动态库的编入,方法相对于当前库的地址是固定的,即sectionBaseAddress,当对于当前库来说,库的位置是改变的,即imageAndOffset.imageOffset,因此当前方法名的地址也是变化的,但相对于库内部是不变且有序的。

总结
由上方代码可知,methodizeClass方法为获取rwe,并且进行分类中的方法添加,然后进行方法排序。

分类 category

由上方的代码可知,当创建分类,并且在分类中有方法的时候,会执行methodizeClass方法里面的内容,接下来我们来探索分类category.
什么是分类?
我们在日常的开发中经常会遇到分类,那么什么是分类呢?它的底层是怎么实现的呢?

  • main.m文件中添加我们自定义类的分类。代码如下:
@interface LGPerson (LG)@property (nonatomic, copy) NSString *cate_name;
@property (nonatomic, assign) int cate_age;- (void)cate_instanceMethod1;
- (void)cate_instanceMethod3;
- (void)cate_instanceMethod2;
+ (void)cate_sayClassMethod;@end@implementation LGPerson (LG)- (void)cate_instanceMethod1{NSLog(@"%s",__func__);
}- (void)cate_instanceMethod3{NSLog(@"%s",__func__);
}- (void)cate_instanceMethod2{NSLog(@"%s",__func__);
}+ (void)cate_sayClassMethod{NSLog(@"%s",__func__);
}
@end
  • 使用clang语句生成相应的.cpp文件进行查看。
  • 进入.cpp文件,搜索相应的分类的位置。代码如下:
static struct _category_t _OBJC_$_CATEGORY_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) =
{"LGPerson",0, // &OBJC_CLASS_$_LGPerson,(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_LG,(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_LGPerson_$_LG,0,(const struct _prop_list_t *)&_OBJC_$_PROP_LIST_LGPerson_$_LG,};
  • 由上方的代码可知,_category_t 结构体是定义分类category的解释。查看.cpp文件中对_category_t结构体的定义。
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;
};
  • 值得注意的是,在.cpp文件中对_category_t的赋值,分类的name字段为LGPerson而非自己的LG,原因就是分类的方法是插入到LGPerson这个类中,而非其他位置,因此它的名字与当前类的名字相同。是因为在iOS系统中是没有分元类的,那么在分类中的操作即为将分类中的内容attachToClass即复制到中.
  • 继续搜索.cpp文件中的内容,查看方法列表内容_method_list,其中__attribute__后面的内容即为method_t中的定义内容。
static struct /*_method_list_t*/ {unsigned int entsize;  // sizeof(struct _objc_method)unsigned int method_count;struct _objc_method method_list[3];
} _OBJC_$_CATEGORY_INSTANCE_METHODS_LGPerson_$_LG __attribute__ ((used, section ("__DATA,__objc_const"))) = {sizeof(_objc_method),3,{{(struct objc_selector *)"cate_instanceMethod1", "v16@0:8", (void *)_I_LGPerson_LG_cate_instanceMethod1},{(struct objc_selector *)"cate_instanceMethod3", "v16@0:8", (void *)_I_LGPerson_LG_cate_instanceMethod3},{(struct objc_selector *)"cate_instanceMethod2", "v16@0:8", (void *)_I_LGPerson_LG_cate_instanceMethod2}}
};
  • 查看method_t源码进行确认,源码如下
struct method_t {SEL name; // 方法名const char *types; // 方法类型MethodListIMP imp; // 方法地址struct SortBySELAddress :public std::binary_function<const method_t&,const method_t&, bool>{bool operator() (const method_t& lhs,const method_t& rhs){ return lhs.name < rhs.name; }};
};
  • .cpp文件中没有实现属性的settergetter的方法的,如果想定义,则需要使用runtime方法进行关联。
  • 查看objc文件中对category_t的定义。主要目的是查看是否有注释解释等内容。为什么查看category_t而不是_category_t,前文有相应的解释。
struct category_t {const char *name;classref_t cls;struct method_list_t *instanceMethods;struct method_list_t *classMethods;struct protocol_list_t *protocols;struct property_list_t *instanceProperties;// Fields below this point are not always present on disk.struct property_list_t *_classProperties;method_list_t *methodsForMeta(bool isMeta) {if (isMeta) return classMethods;else return instanceMethods;}property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);protocol_list_t *protocolsForMeta(bool isMeta) {if (isMeta) return nullptr;else return protocols;}
};
  • 由上可知,在objc源码中,对于category_t并没有详细的注释解释,那么接下来就需要在官方文档中搜索查看了。
  • 由此可知,该文档需要更新了,那么只能自己去探索了。

创建分类进行调试

  • LGPerson类添加LGA分类。代码内容如下:
@interface LGPerson (LGA)
- (void)cateA_1;
- (void)cateA_2;
- (void)cateA_3;
@end@implementation LGPerson (LGA)
+ (void)load{}
- (void)kc_instanceMethod1{NSLog(@"%s",__func__);
}- (void)cateA_2{NSLog(@"%s",__func__);
}
- (void)cateA_1{NSLog(@"%s",__func__);
}
- (void)cateA_3{NSLog(@"%s",__func__);
}
@end
  • LGPerson类添加LGB分类,代码如下:
@interface LGPerson (LGB)
- (void)cateB_1;
- (void)cateB_2;
- (void)cateB_3;
@end@implementation LGPerson (LGB)+ (void)load{}- (void)kc_instanceMethod1{NSLog(@"%s",__func__);
}- (void)cateB_2{NSLog(@"%s",__func__);
}
- (void)cateB_1{NSLog(@"%s",__func__);
}
- (void)cateB_3{NSLog(@"%s",__func__);
}@end

分类的探索

因为就目前所知,只有在methodizeClass这个函数入手了,因为只有这个地方有个Attach categories注释的位置,并且在methodizeClass也有关于类的方法列表、属性列表、协议列表的加载等等。

attachToClass 添加到类

从断点调试中可以看到,当执行分类时,会执行objc::unattachedCategories.attachToClass(cls, cls, isMeta ? ATTACH_METACLASS : ATTACH_CLASS);方法,查看该方法源码如下:

void attachToClass(Class cls, Class previously, int flags)
{runtimeLock.assertLocked();ASSERT(...);// 自定义代码// startconst char *mangledName  = cls->mangledName();const char *LGPersonName = "LGPerson";if (strcmp(mangledName, LGPersonName) == 0) {bool kc_isMeta = cls->isMetaClass();auto kc_rw = cls->data();auto kc_ro = kc_rw->ro();if (!kc_isMeta) {printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);}}// end// 分类迫使主类变为非懒加载类的样式来提前加载数据auto &map = get();auto it = map.find(previously);if (it != map.end()) {category_list &list = it->second;if (flags & ATTACH_CLASS_AND_METACLASS) {int otherFlags = flags & ~ATTACH_CLASS_AND_METACLASS;// 添加对象方法attachCategories(cls, list.array(), list.count(), otherFlags | ATTACH_CLASS);// 添加类方法attachCategories(cls->ISA(), list.array(), list.count(), otherFlags | ATTACH_METACLASS);} else {attachCategories(cls, list.array(), list.count(), flags);}map.erase(it);}
}

类的强制加载
在iOS系统中,分类的加载是的,其中

    auto &map = get();auto it = map.find(previously);if (it != map.end()) {...}

这段代码的意义在于如果分类中实现了+load方法,而本类中没有实现该方法,这时执行上方的代码,将类强行变为非加载类,然后在将分类一个一个的进行加载。

attachCategories 加载分类

查看attachCategories方法源码,源码如下

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,int flags)
{if (slowpath(PrintReplacedMethods)) {...}if (slowpath(PrintConnecting)) {...}// 发布期间,只有少数几个类具有超过64个类别。这将使用少量堆栈,并避免使用malloc。必须按正确的顺序添加类别,这是从前到后的,为此,我们从前到后迭代cats_list,向后建立本地缓冲区,然后在块上调用attachLists。attachLists在列表之前,因此最终结果按预期顺序排列constexpr uint32_t ATTACH_BUFSIZ = 64;method_list_t   *mlists[ATTACH_BUFSIZ];property_list_t *proplists[ATTACH_BUFSIZ];protocol_list_t *protolists[ATTACH_BUFSIZ];uint32_t mcount = 0;uint32_t propcount = 0;uint32_t protocount = 0;bool fromBundle = NO;bool isMeta = (flags & ATTACH_METACLASS);// rwe 初始化auto rwe = cls->data()->extAllocIfNeeded();// 自定义代码,为了调试// startconst char *mangledName  = cls->mangledName();const char *LGPersonName = "LGPerson";if (strcmp(mangledName, LGPersonName) == 0) {bool kc_isMeta = cls->isMetaClass();auto kc_rw = cls->data();auto kc_ro = kc_rw->ro();if (!kc_isMeta) {printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);}}// end// 循环插入方法列表for (uint32_t i = 0; i < cats_count; i++) {auto& entry = cats_list[i];method_list_t *mlist = entry.cat->methodsForMeta(isMeta);if (mlist) {if (mcount == ATTACH_BUFSIZ) {prepareMethodLists(cls, mlists, mcount, NO, fromBundle);rwe->methods.attachLists(mlists, mcount);mcount = 0;}// 倒叙插入mlists[ATTACH_BUFSIZ - ++mcount] = mlist;fromBundle |= entry.hi->isBundle();}// 循环插入属性列表property_list_t *proplist =entry.cat->propertiesForMeta(isMeta, entry.hi);if (proplist) {if (propcount == ATTACH_BUFSIZ) {rwe->properties.attachLists(proplists, propcount);propcount = 0;}// 倒叙插入proplists[ATTACH_BUFSIZ - ++propcount] = proplist;}// 循环插入协议列表protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);if (protolist) {if (protocount == ATTACH_BUFSIZ) {rwe->protocols.attachLists(protolists, protocount);protocount = 0;}// 倒叙插入protolists[ATTACH_BUFSIZ - ++protocount] = protolist;}}// 方法排序,用于二分法查找使用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);}rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}

对该方法进行断点调试。查看mlist内容

继续断点,分别查看mlistmlists的内容。


由上部分可以看出,在第63个位置插入的数据与mlist的数据相同。
使用鼠标查看rwe,此时rwe是有值的,那么它是什么时候有值得呢,向上翻看代码,可知在auto rwe = cls->data()->extAllocIfNeeded();方法执行时给rwe赋值的。查看extAllocIfNeeded方法。源码如下

class_rw_ext_t *extAllocIfNeeded() {auto v = get_ro_or_rwe();if (fastpath(v.is<class_rw_ext_t *>())) {// 如果存在,则直接获取return v.get<class_rw_ext_t *>();} else {// 如果不存在,则进行创建。return extAlloc(v.get<const class_ro_t *>());}}

查看extAlloc方法源码,如下:

class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{runtimeLock.assertLocked();auto rwe = objc::zalloc<class_rw_ext_t>();rwe->version = (ro->flags & RO_META) ? 7 : 0;method_list_t *list = ro->baseMethods();if (list) {if (deepCopy) list = list->duplicate();rwe->methods.attachLists(&list, 1);}property_list_t *proplist = ro->baseProperties;if (proplist) {rwe->properties.attachLists(&proplist, 1);}protocol_list_t *protolist = ro->baseProtocols;if (protolist) {rwe->protocols.attachLists(&protolist, 1);}set_ro_or_rwe(rwe, ro);return rwe;
}

在这里进行了rwe的创建。

那么为什么rwe会在这里创建呢?

  • 首先rwe的创建是在attachCategories方法中才执行
  • attachCategories方法只有在存在分类category的时候才执行
  • 那么rwecategory相关联,即当存在category时,才会创建rwe,用于动态插入方法、属性、协议等内容。

哪里还会创建rwe

既然我们已经知道extAllocIfNeeded方为创建rwe的方法,那么我们进行全局搜索,查看时候还有其他位置存在。搜索结果如下:

搜索查看可知:

  • demangledName方法中存在rwe的创建。即在修改名字的时候需要创建rwe.
  • class_setVersion方法,版本设置
  • addMethod添加方法时
  • addMethods添加方法列表时,与上部分基本相同
  • class_addProtocol添加协议
  • _class_addProperty添加属性
  • objc_duplicateClass
    总结
    对原始内存进行处理的时候需要创建并使用rwe

iOS底层探索(十二)类的加载(中)相关推荐

  1. 一步步手动实现热修复(二)-类的加载机制简要介绍

    *本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布 本节课程主要分为3块: 1.一步步手动实现热修复(一)-dex文件的生成与加载 2.一步步手动实现热修复(二)-类的加载机制简要介 ...

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

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

  3. 一个简单的页面加载管理类(包含加载中,加载失败,数据为空,加载成功)

    在最近公布的比赛框架中,发现了页面加载管理类,觉得挺有用的,所以做个简单的笔记. 什么是页面加载管理类呢?(大佬可直接跳过翻看实现过程) 如果能有这个问题,那么很好,哈哈哈,你和我一样,刚开始都挺疑惑 ...

  4. iOS底层原理之dyld应用程序加载

    前言 众所周知,main作为程序的入口,但是在它之前发生了什么?有点好奇,让我们来瞅一瞅: 一.准备工作 1.代码 __attribute__((constructor)) void Func(){p ...

  5. ArcGIS应用(二十二)Arcmap加载激光雷达las格式数据

    1.使用数据 使用Icesat-2星载激光雷达ATL03数据,数据格式为las格式. 2.使用工具 Argis工具箱中的LAS Dataset工具模块,位于Data Management Tools里 ...

  6. OpenGL(二十二) gluBuild2DMipmaps 加载Mip纹理贴图

    当纹理被用于渲染一个面积比它本身小很多的对象时,会由于纹理图像的降采样率不足而导致混叠现象,主要的表现特征是纹理图像的闪烁,出现纹理躁动.特别是在场景远近移动变换时,这种闪烁情况更为明显,严重可能会影 ...

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

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

  8. iOS之深入解析类加载的底层原理:类如何加载到内存中

    一.App 启动与 dylb 加载 App 启动会由 libdyld.dylib 库先于 main 函数调用 start,执行 _dyld_start 方法,然后运用汇编实现调用 dyldbootst ...

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

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

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

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

最新文章

  1. 为什么很多程序员工作时都戴耳机?
  2. legnano卡片 里怎么添加成员,设置标签,添加检查项?
  3. strlen 与 sizeof 的区别详解
  4. warning: implicit declaration of function ‘sleep’(添加头文件: #include <unistd.h>)
  5. 设计模式--建造者模式--简记
  6. 将基于 .NET Framework 的 WPF 项目迁移到基于 .NET Core 3
  7. python 删除尾部0_python之List常见操作
  8. 数字ab写成c语言表达式,《c语言程序设计》复习题.pdf
  9. SqlServer还原数据库出现“无法在服务器上访问指定的路径或文件”提示的解决办法
  10. Android高手进阶:Adapter深入理解与优化
  11. 【工作感悟】linuxdocker运行windows镜像
  12. 如何使用电力电子仿真软件Plecs写程序脚本?(欢迎交流学习)
  13. UDS知识整理(二):UDS诊断服务简介
  14. 计算机控制技术复试面试(一)
  15. 开源开放 | Gowild 狗尾草“七律”知识图谱进入 OpenKG,开放 8000 万中文百科知识三元组数据...
  16. 形容谣言的四字词语_四字词语加解释大全
  17. Gmail大改版,36岁的电子邮箱为何未像BBS一样消亡?
  18. PT100铂电阻温度传感器详解
  19. matlab 多个向量的余弦角_matlab中怎么求一个行向量的反余弦值
  20. 2022美国大学生数学建模竞赛C题摘要及总结

热门文章

  1. Python猜数字游戏(包含异常处理,可自定义随机数产生范围、最大猜测次数,如果用户猜错的话可根据输入情况缩小猜测范围)
  2. 'rm' 不是内部或外部命令,也不是可运行的程序 或批处理文件。
  3. mysql 时间加五分钟_mysql当前时间增加5分钟的实现方法
  4. python:等间距分割pdf文件
  5. 宽带连接工具[bat]
  6. 图纸上标注的是实际尺寸吗_尺寸数字应该标注图纸上所画实际长度。
  7. 我想加入阿里,我该怎么做
  8. php中文网灭绝师太照片,灭绝师太照片欣赏
  9. 尾行4攻略女仆计算机密码,【尾行4攻略女仆不动】尾行2,攻略(共10篇)
  10. YIT-CTF—隐写术