探索之前
用MachOView分析Build后的文件
之前提到过,类的创建是在编译期,也就是build之后的文件中就已经创建了这个类,说白了就是这个类有地址了。项目结构如下,然后将build后的可执行文件拖到MachOView软件中查看。

在MachOView中能看到这三个类分别的起始地址。接下来在XCode中通过LLDB打印来验证一下,这三个类的地址和MachOView上是否一致。
那么,问题来了。鄙人夜观objc_class的结构:
struct objc_class : objc_object {// Class ISA;Class superclass;cache_t cache; // formerly cache pointer and vtableclass_data_bits_t bits; // class_rw_t * plus custom rr/alloc flags// 省略部分代码}
ISA:8字节;superClass:8字节;cache:16字节;bits:8字节。总计40字节。(之前文章提到过,点击属性的类型可以看出来)
回过头来再看上边的地址(16进制表示):
Person 地址是0x100003708
Student 地址是0x100003730 (和Person的地址刚好差40字节)
Teacher地址是0x1000037a8 (和Student的地址相差120字节)
思考:难道Teacher和Student之间穿插了别的类了吗?
手动计算一下内存偏移40个字节应该是0x100003758,再偏移40字节为0x100003780,之后再偏移40字节就是0x1000037a8了,所以这里需要看的就是0x100003758和0x100003780究竟是什么东西?用LLDB打印看一下:
惊天地泣鬼神了么?居然Student之后还是Student。Teacher之前还有个Teacher。什么鬼?迷茫之中,脑海中有一个灵石传音告诉我,元类。用Student类通过isa指针来验证一下我们的猜想吧:
0x00007ffffffffff8为isa的mask掩码,在之前isa相关博客多次提到过
验证通过,打印的元类地址0x100003758,和上边通过内存偏移得出来的一模一样。元类也是在编译时创建的,属性也是需要40字节地址的。根据lldb打印猜想:元类和类在地址内存中是没有先后顺序的但是应该是挨着的。在Person地址0x100003708向前做40个字节的偏移0x1000036d0,如果猜想准确,它应该指向的是Person的元类:lldb打印验证一下:
写这么多,就一个目的,确定类在编译的时候就已经创建了,有了地址。空有一些地址和内存空间,它这里边的属性还没有赋值,比如ISA,superClass,而类的加载要将这些属性,这些空间给着实添上数据。
从何处开始研究
在APP启动到main函数之间的那些事儿博客中提到过,在main函数之前,dyld库之后,会回调到objc中的_objc_init函数,开始进行各种初始化的操作,如下图。其中我们这篇文章开始研究的就是这个方法及其参数(回调函数)_dyld_objc_notify_register(&map_images, load_images, unmap_image);上边的几个函数的作用参考后边的备注。接下来开始分析map_images和load_images。(unmap_image,就是从内存中移除已经映射的文件研究意义不大)
map_images
map_images:简单来说就是镜像文件的映射,将数据从编译出来的Mach-O文件中映射到内存段中。这里边的代码操作就是将数据读取出俩,然后插入到一个个表中。源码:
voidmap_images(unsigned count, const char * const paths[],const struct mach_header * const mhdrs[]){mutex_locker_t lock(runtimeLock);return map_images_nolock(count, paths, mhdrs);}voidmap_images_nolock(unsigned mhCount, const char * const mhPaths[],const struct mach_header * const mhdrs[]){// 省略部分代码// Find all images with Objective-C metadata.hCount = 0;// 省略部分代码if (hCount > 0) {_read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);}}
map_images源码最终走到的根本点就是_read_images。_read_images 就是在Mach-O文件中,拿到数据(比如所有的Class、SEL等),映射到对应的表中。下面分析_read_images。
_read_images
1、创建两个表
if (!doneOnce) {doneOnce = YES;// namedClasses// Preoptimized classes don't go in this table.// 4/3 is NXMapTable's load factorint namedClassesSize =(isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;gdb_objc_realized_classes =NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);ts.log("IMAGE TIMES: first time tasks");}
通过doneOnce来控制这个模块的代码只执行一次。保证gdb_objc_realized_classes和allocatedClasses表只创建一次。看一下这两个表官方给的解释:
// This is a misnomer: gdb_objc_realized_classes is actually a list of// named classes not in the dyld shared cache, whether realized or not.NXMapTable *gdb_objc_realized_classes; // exported for debuggers in objc-gdb.h
gdb_objc_realized_classes:无论是否实现,只要不在dyld共享缓存中并且已经命名的类的列表。
/************************************************************************ allocatedClasses* A table of all classes (and metaclasses) which have been allocated* with objc_allocateClassPair.**********************************************************************/static NXHashTable *allocatedClasses = nil;
allocatedClasses:通过objc_allocateClassPair已经分配的所有类(包括元类)的列表。
开局先创建两个关于类的表,可以看成一个大的集合,一个小的集合。之后在Mach-O文件中关于类的数据就要根据条件填充到这两个表中。
2、将所有类映射到内存
for (EACH_HEADER) {classref_t *classlist = _getObjc2ClassList(hi, &count);for (i = 0; i < count; i++) {Class cls = (Class)classlist[i];Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);if (newCls != cls && newCls) {// Class was moved but not deleted. Currently this occurs// only when the new class resolved a future class.// Non-lazily realize the class below.resolvedFutureClasses = (Class *)realloc(resolvedFutureClasses,(resolvedFutureClassCount+1) * sizeof(Class));resolvedFutureClasses[resolvedFutureClassCount++] = newCls;}}}ts.log("IMAGE TIMES: discover classes");
_getObjc2ClassList 拿到每个头文件下所有的类,这里有一点需要注意,断点在Class cls = (Class)classlist[i];时,cls还是是一个地址,并不会显示出类的名字。在执行完Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);newCls就有了类的名字。readClass源码:
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized){const char *mangledName = cls->mangledName();Class replacing = nil;if (Class newCls = popFutureNamedClass(mangledName)) {class_rw_t *rw = newCls->data();const class_ro_t *old_ro = rw->ro;memcpy(newCls, cls, sizeof(objc_class));rw->ro = (class_ro_t *)newCls->data();newCls->setData(rw);freeIfMutable((char *)old_ro->name);free((void *)old_ro);addRemappedClass(cls, newCls);replacing = cls;cls = newCls;}if (headerIsPreoptimized && !replacing) {assert(getClass(mangledName));} else {addNamedClass(cls, mangledName, replacing);addClassTableEntry(cls);}return cls;}
先解释上边提到的关于类的名字的问题,const char *mangledName = cls->mangledName();拿到了类的名字,然后在addNamedClass(cls, mangledName, replacing);时候为cls赋予了名字。
然后说一个这个地方面试的坑点,if (Class newCls = popFutureNamedClass(mangledName))判断句里边的内容,看似像是只要进入这个条件,就会初始化rw、为rw做一些赋值的操作。但是注意,这个条件正常情况下不满足,进不来,直接将断点打在if里边,发现无论系统类还是自己创建的类都进不来,所以rw并不是在这个时候创建和赋值的,这是个坑点。至于提到的Future Class我也不清楚到底是什么,平时用到的类和系统类可以说都是Normal Class,这里注意一下就行。
所以,这里返回的cls还是传进来的cls,然后,readClass里边的操作就是将cls命名,并添加到对应的表中。addNamedClass(cls, mangledName, replacing);将已命名的类添加到gdb_objc_realized_classes表中;
static void addNamedClass(Class cls, const char *name, Class replacing = nil){Class old;if ((old = getClass(name)) && old != replacing) {inform_duplicate(name, old, cls);addNonMetaClass(cls);} else {// 插入到gdb_objc_realized_classes表中NXMapInsert(gdb_objc_realized_classes, name, cls);}}
addClassTableEntry(cls);将已经分配完成的类添加到allocatedClasses表中。
static void addClassTableEntry(Class cls, bool addMeta = true) {if (!isKnownClass(cls))// 插入到allocatedClasses表中NXHashInsert(allocatedClasses, cls);if (addMeta)addClassTableEntry(cls->ISA(), false);}
至此,所有的类都实实在在的已经映射到我们内存表中,当然只是把类写进去,类里边的属性还没有赋值,还不到时候。继续阅读_read_images源码。
3、将所有SEL都注册到另外一张哈希表
for (EACH_HEADER) {SEL *sels = _getObjc2SelectorRefs(hi, &count);UnfixedSelectors += count;for (i = 0; i < count; i++) {const char *name = sel_cname(sels[i]);// 注册SEL的操作sels[i] = sel_registerNameNoLock(name, isBundle);}}
4、修复函数指针
修复旧的函数指针调用遗留,一般进不来,if后直接continue。
for (EACH_HEADER) {message_ref_t *refs = _getObjc2MessageRefs(hi, &count);if (count == 0) continue;for (i = 0; i < count; i++) {fixupMessageRef(refs+i);}}ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
5、将协议列表加载到Protocol的哈希表中
for (EACH_HEADER) {extern objc_class OBJC_CLASS_$_Protocol;Class cls = (Class)&OBJC_CLASS_$_Protocol;assert(cls);NXMapTable *protocol_map = protocols();bool isPreoptimized = hi->isPreoptimized();bool isBundle = hi->isBundle();protocol_t **protolist = _getObjc2ProtocolList(hi, &count);for (i = 0; i < count; i++) {readProtocol(protolist[i], cls, protocol_map,isPreoptimized, isBundle);}}
6、对所有Protocol重映射
for (EACH_HEADER) {// 需要注意到是,下面的函数是_getObjc2ProtocolRefs,和上面的_getObjc2ProtocolList不一样protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);for (i = 0; i < count; i++) {remapProtocolRef(&protolist[i]);}}
7、实现非懒加载类
至于什么是懒加载类和非懒加载类后面再说。这里拿到非懒加载类之后,就对类信息进行实现。
for (EACH_HEADER) {classref_t *classlist =_getObjc2NonlazyClassList(hi, &count);for (i = 0; i < count; i++) {Class cls = remapClass(classlist[i]);// 重新映射?if (!cls) continue;// 添加到allocatedClasses表中addClassTableEntry(cls);// 实现非懒加载的类(实例化类对象的一些信息,例如rw)realizeClass(cls);}}ts.log("IMAGE TIMES: realize non-lazy classes");
8、对未来的类进行初始化
这里上边解释过 Future Class正常遇不到,所以这个if 也进不来,不需要考虑。
if (resolvedFutureClasses) {for (i = 0; i < resolvedFutureClassCount; i++) {realizeClass(resolvedFutureClasses[i]);resolvedFutureClasses[i]->setInstancesRequireRawIsa(false/*inherited*/);}free(resolvedFutureClasses);}ts.log("IMAGE TIMES: realize future classes");
9、处理分类Category
// Discover categories.for (EACH_HEADER) {category_t **catlist =_getObjc2CategoryList(hi, &count);bool hasClassProperties = hi->info()->hasCategoryClassProperties();for (i = 0; i < count; i++) {// 循环遍历当前类的所有Categorycategory_t *cat = catlist[i];Class cls = remapClass(cat->cls);// Process this category.// First, register the category with its target class.// Then, rebuild the class's method lists (etc) if// the class is realized.// 首先,通过其所属的类注册Category。如果这个类已经被实现,则重新构造类的方法列表。bool classExists = NO;if (cat->instanceMethods || cat->protocols|| cat->instanceProperties){// 将Category添加到对应Class的value中,value是Class对应的所有category数组addUnattachedCategoryForClass(cat, cls, hi);// 如果已经实现,将Category的method、protocol、property添加到Classif (cls->isRealized()) {remethodizeClass(cls);classExists = YES;}}// 这块和上面逻辑一样,区别在于这块是对Meta Class做操作,而上面则是对Class做操作// 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的if (cat->classMethods || cat->protocols|| (hasClassProperties && cat->_classProperties)){addUnattachedCategoryForClass(cat, cls->ISA(), hi);if (cls->ISA()->isRealized()) {remethodizeClass(cls->ISA());}}}}ts.log("IMAGE TIMES: discover categories");
以上,map_images、_read_images中主要功能就是将类及其分类等信息从Mach-O文件中映射到内存中,处理分类并且如果类已实现的话,将分类的方法、属性、协议添加到类中。另外重点注意的就是非懒加载类,在这个时候就将类进行初始化了——realizeClass(cls)。
类的加载
类的加载实际上就是对类结构体的ISA、superClass、cache(这个例外,对象调用方法时候填充)、bits属性进行初始化赋值。
realizeClass类的初始化
在_read_images中对非懒加载类进行映射的时候,就已经调用了realizeClass(cls)函数。这里先分析这个函数中的赋值操作,之后再分析懒加载类和非懒加载类的加载相关。源码主要有以下操作:
1、读取data(),初始化rw并拷贝ro到rw->ro
ro = (const class_ro_t *)cls->data();// 这个就是之前提到的future class,一般不会进来if (ro->flags & RO_FUTURE) {// This was a future class. rw data is already allocated.rw = cls->data();ro = cls->data()->ro;cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);} else {// 走normal class// Normal class. Allocate writeable class data.rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);rw->ro = ro;rw->flags = RW_REALIZED|RW_REALIZING;cls->setData(rw);}
直接编译相关的数据在data()中,直接强转为class_ro_t类型。条件判断走进Normal class中创建rw,然后对rw->ro进行赋值,注意最后的cls->setData(rw);这也是为什么类加载之后我们读取的data()会强转为class_rw_t而不再是class_ro_t了。这里仅仅对rw中的ro进行赋值,查看rw的结构:
struct class_rw_t {// Be warned that Symbolication knows the layout of this structure.uint32_t flags;uint32_t version;const class_ro_t *ro;method_array_t methods;property_array_t properties;protocol_array_t protocols;Class firstSubclass;Class nextSiblingClass;char *demangledName;};
可以看到除了ro以外还有其他很多属性没有赋值,这里注意一下就行,具体哪里赋值,后边讲。
2、递归映射并实现父类和元类
supercls = realizeClass(remapClass(cls->superclass));metacls = realizeClass(remapClass(cls->ISA()));
类结构中有ISA和superClass属性需要赋值。这里递归实现应该没什么问题,问题是这个递归的终止在哪儿?realizeClass(cls)函数入口处有判断if (!cls) return nil;和if (cls->isRealized()) return cls;父类向上最终都会找到nil,而元类向上最终一直是NSObject元类,已经实现就返回cls。这就是递归终止点。
// Update superclass and metaclass in case of remappingcls->superclass = supercls;cls->initClassIsa(metacls);
3、将此类连接到其超类的子类列表
// Connect this class to its superclass's subclass listsif (supercls) {addSubclass(supercls, cls);} else {addRootClass(cls);}
4、将ro的数据写入到rw中
最后的最后,调用了methodizeClass(cls);方法。之前创建rw的时候,只对rw的ro进行赋值了,其他的属性,就是在这个方法中赋值的。展开分析。
methodizeClass
static void methodizeClass(Class cls){bool isMeta = cls->isMetaClass();auto rw = cls->data();auto ro = rw->ro;// Install methods and properties that the class implements itself.method_list_t *list = ro->baseMethods();if (list) {prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));rw->methods.attachLists(&list, 1);}property_list_t *proplist = ro->baseProperties;if (proplist) {rw->properties.attachLists(&proplist, 1);}protocol_list_t *protolist = ro->baseProtocols;if (protolist) {rw->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, SEL_initialize, (IMP)&objc_noop_imp, "", NO);}// Attach categories.category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);attachCategories(cls, cats, false /*don't flush caches*/);}
在这里分别拿到ro中的baseMethods()、baseProperties、baseProtocols对rw中的method_array_t methods;、 property_array_t properties;、protocol_array_t protocols;进行赋值。之后还进行attachCategories处理分类的方法、属性、协议列表,这个点进去看下,先记住这个,后边分析分类的时候会提到。这里重点看attachLists这个方法,看看是如何将ro中的各个列表加进去的。
attachLists
void attachLists(List* const * addedLists, uint32_t addedCount) {if (addedCount == 0) return;if (hasArray()) {// many lists -> many listsuint32_t oldCount = array()->count;uint32_t newCount = oldCount + addedCount;setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));array()->count = newCount;memmove(array()->lists + addedCount, array()->lists,oldCount * sizeof(array()->lists[0]));memcpy(array()->lists, addedLists,addedCount * sizeof(array()->lists[0]));}else if (!list && addedCount == 1) {// 0 lists -> 1 listlist = addedLists[0];}else {// 1 list -> many listsList* oldList = list;uint32_t oldCount = oldList ? 1 : 0;uint32_t newCount = oldCount + addedCount;setArray((array_t *)malloc(array_t::byteSize(newCount)));array()->count = newCount;if (oldList) array()->lists[addedCount] = oldList;memcpy(array()->lists, addedLists,addedCount * sizeof(array()->lists[0]));}}
源码中分了多对多、0对一、一对多三种情况,rw中的methods、properties、protocols都是二维数组的形式,新添加进来的列表其实都是插在前边,上边的算法就是关于字节移动和拷贝的逻辑。
思考:都什么时候会用到attachLists这个方法? 1、类的加载,如上methodizeClass方法中; 2、添加方法:addMethods; 3、添加属性:_class_addProperty; 4、添加协议:class_addProtocol; 5、分类的加载:attachCategories;
至此,大致知道了类的初始化都做了哪些处理。下边具体分析一下类的家在时机。首先需要了解的什么是懒加载类和非懒加载类。
懒加载类和非懒加载类
在_read_images方法中,非懒加载类操作_getObjc2NonlazyClassList的上边有一句注释:// Realize non-lazy classes (for +load methods and static instances)意思就是实现了+load方法的就被认为是非懒加载类。
反之,未实现+load方法的类就是懒加载类。接下来分析加载时机,此时不考虑分类的情况(分类同样也会有懒加载分类和非懒加载分类)。
加载时机
非懒加载类
先说非懒加载类,在分析_read_images方法的时候,在非懒加载类循环中就调用了realizeClass``(cls);即对这个类进行了实现。(realizeClass后边做展开分析)。所以,非懒加载类的加载时机在_read_images时已经实现。
懒加载类
那么懒加载类是什么时候实现的。还记得之前消息查找的方法吗?
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,bool initialize, bool cache, bool resolver){// 省略代码 ...if (!cls->isRealized()) {realizeClass(cls);}// 省略代码 ...}
类在第一次使用(消息发送的时候)就会判断类是否已经实现,如果没实现,就先实现,然后再去查找方法。懒加载类的加载时机在消息发送时。可以在if (!cls->isRealized())打个断点,自己创建一个类尝试实现和不实现+load方法来调试看是都在发送消息的时候走进去realizeClass(cls)。
分类的加载
类有懒加载和非懒加载,那么分类也有懒加载分类和非懒加载分类。那么这和类来组合,就出现如下四种,这里我根据debug调试总结出四种情况的加载流程,具体的debug自行总结。
懒加载分类
和类一样,未实现+load方法的可以视为懒加载分类。首先明确一点,懒加载分类里边的数据是直接编译进类的ro中。这个点做一步验证:假设场景,懒加载类和懒加载分类(均未实现+load方法),懒加载类在发送消息的时候会初始化。Teacher类及其分类方法:

分享一个调试小技巧,因为进入realizeClass函数可能会有很多系统类,在芸芸众生之中找到我的类:
class_ro_t *lgro = (const class_ro_t *)cls->data();const char *lgname = lgro->name;const char *lgnameTest = "Teacher";if (strcmp(lgname, lgnameTest) == 0) {printf("是我们的人");}
1、懒加载类+懒加载分类
懒加载类的加载是在消息发送的时候,而懒加载分类的数据直接被编译到ro中,所以在将ro中的数据attachLists到rw的时候就已经处理了,这里可以直接总结出流程即:lookUpImpOrForward -> realizeClass(cls) -> methodizeClass(cls) -> attachLists
2、非懒加载类+懒加载分类
非懒加载类实现了+load方法:_read_images -> _getObjc2NonlazyClassList -> realizeClass(cls) -> methodizeClass(cls)-> attachLists
非懒加载分类
实现了+load方法的分类视为非懒加载分类。非懒加载分类的数据不会直接编译到类的ro中,需要在类初始化的时候添加到rw中。
3、懒加载类+非懒加载分类
有种类似子类实现了,连带父类会一起实现的感觉。既然分类实现了+load方法,分类提前了,那么类虽然是懒加载类,按理说是要消息发送的时候实现,但是由于类提前了,类也就提前初始化了。_read_images -> _getObjc2CategoryList -> addUnattachedCategoryForClass ->由于类没有实现,至此``_read_images 即 ``map_images执行完毕。在load_images中prepare_load_methods -> _getObjc2NonlazyCategoryList -> realizeClass(cls) -> methodizeClass(cls)-> attachLists
4、非懒加载类+非懒加载分类
_read_images -> _getObjc2NonlazyClassList -> realizeClass(cls)-> 至此,非懒加载类已经实现 -> _getObjc2CategoryList -> addUnattachedCategoryForClass -> 因为非懒加载类已经实现 -> remethodizeClass -> attachCategories -> attachLists
load_images
在上边懒加载类+非懒加载分类模块已经初步提到了load_images。再或者说,map_images做的主要就是一些表映射,还有非懒加载类的初始化等工作,那么多次提到的+load方法是什么时候调用的呢?看源码:
voidload_images(const char *path __unused, const struct mach_header *mh){// Return without taking locks if there are no +load methods here.if (!hasLoadMethods((const headerType *)mh)) return;recursive_mutex_locker_t lock(loadMethodLock);// Discover load methods{mutex_locker_t lock2(runtimeLock);prepare_load_methods((const headerType *)mh);}// Call +load methods (without runtimeLock - re-entrant)call_load_methods();}
先看注释,直接就看到了// Call +load methods。字面意思,回调+load方法。回调之前,需要先准备一下,或者说需要统计一下都有哪些类和分类实现了+load方法。往上看// Discover load methods发现load方法,逻辑很完美。下面看看在上边懒加载类+非懒加载分类提到的prepare_load_methods:
void prepare_load_methods(const headerType *mhdr){size_t count, i;runtimeLock.assertLocked();classref_t *classlist =_getObjc2NonlazyClassList(mhdr, &count);for (i = 0; i < count; i++) {schedule_class_load(remapClass(classlist[i]));}category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);for (i = 0; i < count; i++) {category_t *cat = categorylist[i];Class cls = remapClass(cat->cls);if (!cls) continue; // category for ignored weak-linked classrealizeClass(cls);assert(cls->ISA()->isRealized());add_category_to_loadable_list(cat);}}
找到所有的非懒加载类(实现+load方法)
在schedule_class_load -> add_class_to_loadable_list 最根本的就是记录下实现+load方法的cls和method:
loadable_classes[loadable_classes_used].cls = cls;loadable_classes[loadable_classes_used].method = method;
找到所有的非懒加载分类
这里边发现一个很熟悉的方法realizeClass(cls);。之前提过,非懒加载分类会带动懒加载的类提前初始化(而不是等到消息发送的时候才初始化)就是在这个地方。add_category_to_loadable_list 这个最根本的也是记录下分类cat和method:
loadable_categories[loadable_categories_used].cat = cat;loadable_categories[loadable_categories_used].method = method;
找到了cls、cat也找到了method,准备工作完成,调用call_load_methods();完成+load方法回调。
类和分类中相同方法的调用顺序
load方法的调用顺序
如果类和分类都实现了+load方法,那么系统回调的顺序是什么样子?打印看一下:
打印结果:load方法的回调,先回调类再回调分类;
除+load方法外相同方法,对象会调用哪个?
这里在Teacher类和分类中都写上-(void)saySomething;实例化一个对象调用这个方法,打印开一下:
打印结果证明:其余相同方法会调用分类的。为什么?
还记得上边提到的attachLists吗?这个的插入是前插,也就是分类的方法列表会插入到类原有的列表的前边。方法查找的时候必然优先找到分类的imp,然后直接响应了。
类拓展
什么是类拓展?这个其实在开发中经常用到,比如我们在Person类想定义一些属性或者方法,不让外界直接调用,一般就会在Person.m上边这样写:
@interface Person ()@property (nonatomic, strong) NSString *personName;-(void)person;@end
这个其实就是类拓展,也可以称之为匿名分类,Person ()括号中没有名字,分类的括号中还有名字。那么类拓展的数据是什么时候加载的?Person没有实现+load方法是懒加载类,在消息发送的时候调用,我们直接在消息发送断点,查看ro:
总结
至此,本文大致分析了从编译期类的创建,到映射到内存,然后分为四种情况简单总结了类和分类的加载整个流程,最后提到了+load方法是在什么时候回调的。大致就这些,有新的理解随时补充。
