手撕iOS底层20 -- 类的加载【中卷】

1,801 阅读10分钟

接上篇继续分析_read_images方法, 上篇探讨了readClass,只有了类的地址和名字,类的内部数据还并未实现,本章分析实现类方法realizeClassWithoutSwift

0x00 -- realizeClassWithoutSwift分析

realizeClassWithoutSwift部分关键代码, 包含了rw ro的数据操作,

这个初始化方法在之前的方法的慢速查找流程也有涉及, 路径是

lookUpImpOrForward

realizeClassMaybeSwiftAndLeaveLocked

realizeClassMaybeSwiftMaybeRelock

realizeClassWithoutSwift

也就是说在发送消息的时候会去判断类是否实现, 如果类本身没有准备好,没有实现,没有ro, rw, rw

这个方法的主要作用是实现类,将实现完的类数据放到内存中,程序去内存中读取数据,操作主要分为以下几步:

  • 读取Mach-O 的data数据, 设置ro rw
  • 递归调用realizeClassWithoutSwift实现继承链的完善
  • 通过methodizeClass方法类

0x01 - 读取data数据

class_rw_t *rw;
Class supercls;
Class metacls;
	// fixme verify class is not in an un-dlopened part of the shared cache?
	//读取class的data(),以及ro/rw创建
	auto ro = (const class_ro_t *)cls->data(); //读取类结构的bits属性、//ro -- clean memory,在编译时就已经确定了内存
	auto isMeta = ro->flags & RO_META; //判断元类
	if (ro->flags & RO_FUTURE) {
	// This was a future class. rw data is already allocated.
	rw = cls->data(); //dirty memory 进行赋值
	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>(); //申请开辟zalloc -- rw
	rw->set_ro(ro);//rw中的ro设置为临时变量ro
	rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
	cls->setData(rw);//将cls的data赋值为rw形式
	}

⚠️cls是从classref_t const *classlist = _getObjc2NonlazyClassList(hi, &count); 这个classlsit列表获取的, 这个列表是从Mach-O里拿到的, 所以cls->data()拿到的数据也是Mach-O的,

  • ro表示read only的意思, 即只读, 包含名称, 方法, 协议以及实例变量信息, 因为是只读的,所以也就是WWDC说的clean memory,而clean memory在加载到内存后是不会发生改变的。

  • rw表示read write,就是可读可写, 由于runtime的动态性, 可能会给类中添加属性 、方法、协议等一些内容, 需要改变这个类的数据结构, 在新的WWDCAdvancements in the Objective-C Runtime中,提到了rw,其实在rw中只有10%的类真正的更改了它们的方法,所以有了rwe,即类的额外信息。对于那些确实需要额外信息的类,可以分配rwe扩展记录中的一个,并将其滑入类中供其使用。其中rw就属于dirty memory,而 dirty memory是指在进程运行时会发生更改的内存类结构一经使用就会变成 ditry memory,因为运行时会向它写入新数据,例如 创建一个新的方法缓存,并从类中指向它

    addMethodaddProperty以及addProtocol都会通过extAllocIfNeeded()方法生成或者获取ext的空间。

2.png

3.jpg

4.png

0x02 - 递归完善继承链

👇继续看realizeClassWithoutSwift部分代码

 // 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. --
    //递归调用realizeClassWithoutSwift完善继承链,并处理当前类的父类、元类
    //递归实现 设置当前类、父类、元类的 rw,主要目的是确定继承链 (类继承链、元类继承链)
    //实现元类、父类
    //当isa找到根元类之后,根元类的isa是指向自己的,不会返回nil从而导致死循环——remapClass中对类在表中进行查找的操作,如果表中已有该类,则返回一个空值;如果没有则返回当前类,这样保证了类只加载一次并结束递归
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
...

// Update superclass and metaclass in case of remapping -- class 是 双向链表结构 即父子关系都确认了
// 将父类和元类给我们的类 分别是isa和父类的对应值
		cls->superclass = supercls;
		cls->initClassIsa(metacls);

...

		// Connect this class to its superclass's subclass lists
		//双向链表指向关系 父类中可以找到子类 子类中也可以找到父类
		//通过addSubclass把当前类放到父类的子类列表中去
		if (supercls) {
    		addSubclass(supercls, cls);
		} else {
   		 addRootClass(cls);
		}

递归调用realizeClassWithoutSwift方法来完善父类以及元类,也设置了父类和元类的rw,通过setInstancesRequireRawIsaRecursively设置了父类和元类的isa对应的值

通过addSubclassaddRootClass设置父子双向链表的关系,也就是 父类可以找到子类,子类中也可以找到父类

⚠️在realizeClassWithoutSwift元类递归时, 由于根元类指向本身, 所以不会返回nil,所以有以下的终止条件,保证每个类只初始化一次,

    if (!cls) return nil;
    if (cls->isRealized()) return cls;
    ASSERT(cls == remapClass(cls));
  • 如果类不存在,则返回nil
  • 如果类已经实现,则直接返回cls

remapClass中,如果cls不存在,也会直接返回nil

/***********************************************************************
* remapClass
* Returns the live class pointer for cls, which may be pointing to 
* a class struct that has been reallocated.
* Returns nil if cls is ignored because of weak linking.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static Class remapClass(Class cls)
{
    runtimeLock.assertLocked();
    if (!cls) return nil;//如果cls不存在,则返回nil
    auto *map = remappedClasses(NO);
    if (!map)
        return cls;
    auto iterator = map->find(cls);
    if (iterator == map->end())
        return cls;
    return std::get<1>(*iterator);
}

0x03 - methodizeClass方法化类

⚠️⚠️⚠️ 想要从_read_images走进realizeClassWithoutSwift,需要在自定义的类Person中实现+ load方法,然后才能在realizeClassWithoutSwift中走调用methodizeClass方法。因为懒加载类需要提前初始化,load方法是在load_images的时候调用,所以需要提前准备好。

  • 懒加载类

懒加载类 , 第一次发送消息, 迫使这个类去实现它。,

  • 非懒加载类

有load方法就是非懒加载类, 因为有load方法是在load_images的时候会调用,如果类没有实现,怎么调用load方法

来到到realizeClassWithoutSwift类的最底部,看到methodizeClass方法化类的代码:

		// 贴进类别 -- 疑问:ro中也有方法列表 rw中也有方法列表,下面这个方法可以说明
		//将ro数据写入到rw
    // Attach categories
    methodizeClass(cls, previously);
    return cls;

来到methodizeClass方法,也可以通过LLDB指令通过之前学习的内存偏移来调试cls 的内部数据。

static void methodizeClass(Class cls, Class previously)
{
    runtimeLock.assertLocked();

    bool isMeta = cls->isMetaClass();
    auto rw = cls->data(); // 初始化一个rw
    auto ro = rw->ro();
    auto rwe = rw->ext();
    
    ...

    // Install methods and properties that the class implements itself.
    //将属性列表、方法列表、协议列表等贴到rw中
    // 将ro中的方法列表加入到rw中
    method_list_t *list = ro->baseMethods();//获取ro的baseMethods
    if (list) {
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));//methods进行排序
        if (rwe) rwe->methods.attachLists(&list, 1);//对rwe进行处理
    }
    // 加入属性
    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 metaclass
        addMethod(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);

    ....
}
  • 主要是将方法列表属性列表以及协议列表放到rwe
  • 通过rw获取rorwe
  • 通过ro->baseMethods();获取方法列表
  • 通过attachLists插入对应的数据表

prepareMethodLists方法内

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                  bool baseMethods, bool methodsFromBundle)
{
   ...

   // 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 necessary
       if (!mlist->isFixedUp()) {
           fixupMethodList(mlist, methodsFromBundle, true/*sort*/);//排序
       }
   }
   
   ...
}


prepareMethodLists方法内是对方法列表按sel地址有排序,在lookUpImpForward的流程里,查找方法列表是按二分查找的, 所以这里有对方法列表的排序。

6.jpg

Sort by selector address.

通过sel地址来排序。

attachToClass分析

void attachToClass(Class cls, Class previously, int flags)
{
    runtimeLock.assertLocked();
    ASSERT((flags & ATTACH_CLASS) ||
           (flags & ATTACH_METACLASS) ||
           (flags & ATTACH_CLASS_AND_METACLASS));

    
    const 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);
        }
    }
    
    auto &map = get();
    auto it = map.find(previously);//找到一个分类进来一次,即一个个加载分类,不要混乱

    if (it != map.end()) {//这里会走进来:当主类没有实现load,分类开始加载,迫使主类加载,会走到if流程里面
        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
            attachCategories(cls, list.array(), list.count(), flags);
        }
        map.erase(it);
    }
}

在这里有一个iterator迭代器 循环调用attachCategories,找到一个调用一次

attachCategories分析

static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                 int flags)
{
    if (slowpath(PrintReplacedMethods)) {
        printReplacements(cls, cats_list, cats_count);
    }
    if (slowpath(PrintConnecting)) {
        _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                     cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                     cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
    }

    /*
     * Only a few classes have more than 64 categories during launch.
     * This uses a little stack, and avoids malloc.
     *
     * Categories must be added in the proper order, which is back
     * to front. To do that with the chunking, we iterate cats_list
     * from front to back, build up the local buffers backwards,
     * and call attachLists on the chunks. attachLists prepends the
     * lists, so the final result is in the expected order.
     */
    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的创建,
     那么为什么要在这里进行`rwe的初始化`?因为我们现在要做一件事:往`本类`中`添加属性、方法、协议`等
     */
    auto rwe = cls->data()->extAllocIfNeeded();
        
    //mlists 是一个二维数组
    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) {//mcount = 0,ATTACH_BUFSIZ= 64,不会走到if里面的流程
                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);//mlists + ATTACH_BUFSIZ - mcount 为内存平移
        if (flags & ATTACH_EXISTING) flushCaches(cls);
    }

    rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);

    rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
}
  • 走到这里使用添加分类里,所以会通过extAllocIfNeeded();开辟rwe的空间,即rwe有值了, 对原来的clean memory要进行处理了。
  • mlists[ATTACH_BUFSIZ - ++mcount] = mlist;拆分来写
	mcount += 1
  mlists[ATTACH_BUFSIZ - mcount] == mlists[64-1] // 也就是将数据从尾部开始插入
   
  • 这里的ATTACH_BUFSIZ=64,即可以理解为 倒序插入,64的原因是允许容纳64个(最多64个分类)

addMethodaddProperty以及addProtocol都会通过extAllocIfNeeded()方法生成或者获取ext的空间。

// 781版本
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 *>());
    }
}
// 818.2版本
    class_rw_ext_t *extAllocIfNeeded() {
        auto v = get_ro_or_rwe();
        if (fastpath(v.is<class_rw_ext_t *>())) {//判断rwe是否存在
            return v.get<class_rw_ext_t *>(&ro_or_rw_ext);//如果存在,则直接获取
        } else {
            return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));//如果不存在则进行开辟
        }
    }
👇//extAlloc源码实现
class_rw_ext_t *
class_rw_t::extAlloc(const class_ro_t *ro, bool deepCopy)
{
    runtimeLock.assertLocked();
    //此时只有rw,需要对rwe进行数据添加,即0-1的过程
    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);
    }

    // See comments in objc_duplicateClass
    // property lists and protocol lists historically
    // have not been deep-copied
    //
    // This is probably wrong and ought to be fixed some day
    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;
}
  • extAllocIfNeeded的内部,首先开辟内存, 接着把ro的数据初始化给rwe,也就是本类的数据加载到了rwe.

总结: 修改本类数据clean memory的地方需要开辟rwe,只要给本类添加方法,协议,属性,都会涉及rwe的开辟,也就是dirty memory

0x04 - 分类的本质

通过clang -rewrite-objc main.m 命令转成c++文件查看

7.jpg

通过转换的c++文件查看分类是一个category_t的结构,有名字cls, 实例方法列表, 类方法列表, 协议列表, 属性列表,

8.jpg

通过初始化看到,在编译时,分类的方法名字还是本类的名字,在运行时会替换成分类的名字。

11.jpg

分类中的属性并不会生成方法在instance_methodsclass_meethods,也没有生成下划线的成员变量,通过runtime关联对象技术 来实现

欢迎大佬留言指正😄,码字不易,觉得好给个赞👍 有任何表达或者理解失误请留言交流;共同进步;