012:类的加载上:mapImage-[readClass->UnfixedSelectors->readProtocol]->realizeClassWithoutSwift->[methodizeClass->prepareMethodLists->fixupMethodList]->attachCategories)、loadImage(加载load方法),

问题

readImage->(readClass->UnfixedSelectors->readProtocol)->realizeClassWithoutSwift->methodizeClass->attachCategories)

1: readImage

 

 2:readClass->UnfixedSelectors->readProtocol

 3:realizeClassWithoutSwift:(ro和rw数据、继承和isa关系)realize:实现

 4:methodizeClass,排序,优化,将ro数据放入rw

 5:attachCategories

 6:loadImage

目录

 

预备

 

正文

一:map_images和load_images

我们理解了dyldobjc是如何关联的,本文的主要目的是理解类的相关信息是如何加载内存的,其中重点关注map_imagesload_images

  • map_images:主要是管理文件中和动态库中的所有符号,即class、protocol、selector、category

  • load_images:加载执行load方法

其中代码通过编译,读取到Mach-O可执行文件中,再从Mach-O中读取到内存,如下图所示

二:map_images源码流程

1:map_images方法的主要作用是将Mach-O中的类信息加载到内存

管理文件和动态库中所有的符号,如classProtocolselectorcategory

从系统库 libSystem的 Runtime入口函数 _objc_init跳转到 map_images

/***********************************************************************
* map_images
* Process the given images which are being mapped in by dyld.
* Calls ABI-agnostic code after taking ABI-specific locks.
*
* Locking: write-locks runtimeLock
**********************************************************************/
void
map_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);
}

2:map_images_nolock 分析

void
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    //...省略

    // Find all images with Objective-C metadata.查找所有带有Objective-C元数据的映像
    hCount = 0;

    // Count classes. Size various table based on the total.计算类的个数
    int totalClasses = 0;
    int unoptimizedTotalClasses = 0;
    //代码块:作用域,进行局部处理,即局部处理一些事件
    {
        //...省略
    }
    
    //...省略

    if (hCount > 0) {
        //加载镜像文件
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }

    firstTime = NO;
    
    // Call image load funcs after everything is set up.一切设置完成后,调用镜像加载功能。
    for (auto func : loadImageFuncs) {
        for (uint32_t i = 0; i < mhCount; i++) {
            func(mhdrs[i]);
        }
    }
}

_read_images主要是主要是加载类信息,即类、分类、协议等,进入_read_images源码实现,主要分为以下几部分:

  • 1、条件控制进行的一次加载
  • 2、修复预编译阶段的@selector的混乱问题
  • 3、错误混乱的类处理
  • 4、修复重映射一些没有被镜像文件加载进来的类
  • 5、修复一些消息
  • 6、当类里面有协议时:readProtocol 读取协议
  • 7、修复没有被加载的协议
  • 8、分类处理
  • 9、类的加载处理
  • 10、没有被处理的类,优化那些被侵犯的类

1、条件控制进行的一次加载

doneOnce流程中通过NXCreateMapTable创建表,存放类信息,即创建一张类的哈希表``gdb_objc_realized_classes,其目的是为了类查找方便、快捷

if (!doneOnce) {
     
    //...省略
    
    // namedClasses
    // Preoptimized classes don't go in this table.
    // 4/3 is NXMapTable's load factor
    int namedClassesSize = 
        (isPreoptimized() ? unoptimizedTotalClasses : totalClasses) * 4 / 3;
//创建表(哈希表key-value),目的是查找快
    gdb_objc_realized_classes =
        NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);

    ts.log("IMAGE TIMES: first time tasks");
}
  • 这里拓展一下这张表的类型:gdb_objc_realized_classes的类型是NXMapTable
  • 可以简单理解 NXMapTable== NSMapTable,也就是对应的我们常用的 NSDictionary,并且额外提供了 weak 指针来使用垃圾回收机制
  • NSDictionary底层实现也是使用了 NSMapTable(散列表),(备注:苹果官网并没有这些类的实现,想要查看 NSDictionary和 NSArray的实现源码可以去 GNUstep官网下载
  • 使用 NSMapTable是因为它更强大 NSMapTable 相对于 NSDictionary 的优势

2. 修复预编译阶段的 @selector的混乱问题

// Fix up @selector references 修复@selector引用
//sel 不是简单的字符串,而是带地址的字符串
static size_t UnfixedSelectors;
{
    mutex_locker_t lock(selLock);
    for (EACH_HEADER) {
        if (hi->hasPreoptimizedSelectors()) continue;

        bool isBundle = hi->isBundle();
        //通过_getObjc2SelectorRefs拿到Mach-O中的静态段__objc_selrefs
        SEL *sels = _getObjc2SelectorRefs(hi, &count);
        UnfixedSelectors += count;
        for (i = 0; i < count; i++) { //列表遍历
            const char *name = sel_cname(sels[i]);
            //注册sel操作,即将sel添加到
            SEL sel = sel_registerNameNoLock(name, isBundle);
            if (sels[i] != sel) {//当sel与sels[i]地址不一致时,需要调整为一致的
                sels[i] = sel;
            }
        }
    }
}
主要是通过通过_getObjc2SelectorRefs拿到Mach_O中的静态段__objc_selrefs,遍历列表调用sel_registerNameNoLockSEL添加到namedSelectors哈希表中
其中_getObjc2SelectorRefs的源码如下,表示获取Mach-O中的静态段__objc_selrefs,后续通过_getObjc2开头的Mach-O静态段获取,都对应不同的section name
//      function name                 content type     section name
GETSECT(_getObjc2SelectorRefs,        SEL,             "__objc_selrefs"); 
GETSECT(_getObjc2MessageRefs,         message_ref_t,   "__objc_msgrefs"); 
GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");
GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
GETSECT(_getObjc2ClassList,           classref_t const,      "__objc_classlist");
GETSECT(_getObjc2NonlazyClassList,    classref_t const,      "__objc_nlclslist");
GETSECT(_getObjc2CategoryList,        category_t * const,    "__objc_catlist");
GETSECT(_getObjc2CategoryList2,       category_t * const,    "__objc_catlist2");
GETSECT(_getObjc2NonlazyCategoryList, category_t * const,    "__objc_nlcatlist");
GETSECT(_getObjc2ProtocolList,        protocol_t * const,    "__objc_protolist");
GETSECT(_getObjc2ProtocolRefs,        protocol_t *,    "__objc_protorefs");
GETSECT(getLibobjcInitializers,       UnsignedInitializer, "__objc_init_func");
sel_registerNameNoLock源码路径如下:sel_registerNameNoLock -> __sel_registerName,如下所示,其关键代码是auto it = namedSelectors.get().insert(name);,即将sel插入namedSelectors哈希表
SEL sel_registerNameNoLock(const char *name, bool copy) {
    return __sel_registerName(name, 0, copy);  // NO lock, maybe copy
}


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;

    result = search_builtins(name);
    if (result) return result;
    
    conditional_mutex_locker_t lock(selLock, shouldLock);
    auto it = namedSelectors.get().insert(name);//sel插入表
    if (it.second) {
        // No match. Insert.
        *it.first = (const char *)sel_alloc(name, copy);
    }
    return (SEL)*it.first;
}

其中selector --> sel并不是简单的字符串,是带地址的字符串
如下所示,sels[i]sel字符串一致,但是地址不一致,所以需要调整为一致的

3: 错误混乱的类处理

主要是从Mach-O中取出所有类,在遍历进行处理

//3、错误混乱的类处理
// Discover classes. Fix up unresolved future classes. Mark bundle classes.
bool hasDyldRoots = dyld_shared_cache_some_image_overridden();
//读取类:readClass
for (EACH_HEADER) {
    if (! mustReadClasses(hi, hasDyldRoots)) {
        // Image is sufficiently optimized that we need not call readClass()
        continue;
    }
    //从编译后的类列表中取出所有类,即从Mach-O中获取静态段__objc_classlist,是一个classref_t类型的指针
    classref_t const *classlist = _getObjc2ClassList(hi, &count);

    bool headerIsBundle = hi->isBundle();
    bool headerIsPreoptimized = hi->hasPreoptimizedClasses();

    for (i = 0; i < count; i++) {
        Class cls = (Class)classlist[i];//此时获取的cls只是一个地址
        Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized); //读取类,经过这步后,cls获取的值才是一个名字
        //经过调试,并未执行if里面的流程
        //初始化所有懒加载的类需要的内存空间,但是懒加载类的数据现在是没有加载到的,连类都没有初始化
        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");

通过代码调试,知道了在未执行readClass方法前,cls只是一个地址

执行readClass后,cls获取的值才是一个名字

可以看到在 readClass方法调用之后,对 cls进行了类名的赋值操作。此时类的信息目前仅存储了地址和名称,我们再进去看一下 readClass的源码

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();
    
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(getClassExceptSomeSwift(mangledName));
    } else {
        // 添加类名
        addNamedClass(cls, mangledName, replacing);
        // 插入哈希表中,即从 mach-o 中把类读取到内存当中
        addClassTableEntry(cls);
    }
    
    return cls;
}

再进去查看 addNamedClass的源码

static void addNamedClass(Class cls, const char *name, Class replacing = nil)
{
    Class old;
    if ((old = getClassExceptSomeSwift(name))  &&  old != replacing) {
        inform_duplicate(name, old, cls);

        // getMaybeUnrealizedNonMetaClass uses name lookups.
        // Classes not found by name lookup must be in the
        // secondary meta->nonmeta table.
        addNonMetaClass(cls);
    } else {
        // 将 name 与 cls 的地址进行映射,并插入到内存当中
        NXMapInsert(gdb_objc_realized_classes, name, cls);
    }
}

查看 mangledName方法的源码

const char *mangledName() { 
    // fixme can't assert locks here
    ASSERT(this);
    // 这个初始化判断在我们前面分析的 lookuoImp 也有出现过
    if (isRealized()  ||  isFuture()) {
        // 如果类已经初始化过,则从 ro 中直接获取 name
        return data()->ro()->name;
    } else {
        // 否则从 mach-o 中读取 data 里面的 name
        return ((const class_ro_t *)data())->name;
    }
}

综上可得:readClass的主要作用就是将 mach-o的类读取到内存当中,当前的类中仅有两个信息,即地址和名称,data数据会在步骤九中读取出来并赋值到类中

4: 修复重映射一些没有被镜像文件加载进来的类

//4、修复重映射一些没有被镜像文件加载进来的类
// Fix up remapped classes 修正重新映射的类
// Class list and nonlazy class list remain unremapped.类列表和非惰性类列表保持未映射
// Class refs and super refs are remapped for message dispatching.类引用和超级引用将重新映射以进行消息分发
//经过调试,并未执行if里面的流程
//将未映射的Class 和 Super Class重映射,被remap的类都是懒加载的类
if (!noClassesRemapped()) {
    for (EACH_HEADER) {
        Class *classrefs = _getObjc2ClassRefs(hi, &count);//Mach-O的静态段 __objc_classrefs
        for (i = 0; i < count; i++) {
            remapClassRef(&classrefs[i]);
        }
        // fixme why doesn't test future1 catch the absence of this?
        classrefs = _getObjc2SuperRefs(hi, &count);//Mach_O中的静态段 __objc_superrefs
        for (i = 0; i < count; i++) {
            remapClassRef(&classrefs[i]);
        }
    }
}

ts.log("IMAGE TIMES: remap classes");

主要是将未映射的ClassSuper Class进行重映射,其中

  • _getObjc2ClassRefs是获取Mach-O中的静态段__objc_classrefs类的引用

  • _getObjc2SuperRefs是获取Mach-O中的静态段__objc_superrefs父类的引用

  • 通过注释可以得知,被remapClassRef的类都是懒加载的类,所以最初经过调试时,这部分代码是没有执行的

5: 修复一些消息

#if SUPPORT_FIXUP
//5、修复一些消息
    // Fix up old objc_msgSend_fixup call sites
    for (EACH_HEADER) {
        // _getObjc2MessageRefs 获取Mach-O的静态段 __objc_msgrefs
        message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
        if (count == 0) continue;

        if (PrintVtables) {
            _objc_inform("VTABLES: repairing %zu unsupported vtable dispatch "
                         "call sites in %s", count, hi->fname());
        }
        //经过调试,并未执行for里面的流程
        //遍历将函数指针进行注册,并fix为新的函数指针
        for (i = 0; i < count; i++) {
            fixupMessageRef(refs+i);
        }
    }

    ts.log("IMAGE TIMES: fix up objc_msgSend_fixup");
#endif

6:当类里面有协议时:readProtocol 读取协议

//6、当类里面有协议时:readProtocol 读取协议
// Discover protocols. Fix up protocol refs. 发现协议。修正协议参考
//遍历所有协议列表,并且将协议列表加载到Protocol的哈希表中
for (EACH_HEADER) {
    extern objc_class OBJC_CLASS_$_Protocol;
    //cls = Protocol类,所有协议和对象的结构体都类似,isa都对应Protocol类
    Class cls = (Class)&OBJC_CLASS_$_Protocol;
    ASSERT(cls);
    //获取protocol哈希表 -- protocol_map
    NXMapTable *protocol_map = protocols();
    bool isPreoptimized = hi->hasPreoptimizedProtocols();

    // Skip reading protocols if this is an image from the shared cache
    // and we support roots
    // Note, after launch we do need to walk the protocol as the protocol
    // in the shared cache is marked with isCanonical() and that may not
    // be true if some non-shared cache binary was chosen as the canonical
    // definition
    if (launchTime && isPreoptimized && cacheSupportsProtocolRoots) {
        if (PrintProtocols) {
            _objc_inform("PROTOCOLS: Skipping reading protocols in image: %s",
                         hi->fname());
        }
        continue;
    }

    bool isBundle = hi->isBundle();
    //通过_getObjc2ProtocolList 获取到Mach-O中的静态段__objc_protolist协议列表,
    //即从编译器中读取并初始化protocol
    protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);
    for (i = 0; i < count; i++) {
        //通过添加protocol到protocol_map哈希表中
        readProtocol(protolist[i], cls, protocol_map, 
                     isPreoptimized, isBundle);
    }
}

ts.log("IMAGE TIMES: discover protocols");

通过NXMapTable *protocol_map = protocols();创建protocol哈希表,表的名称为protocol_map

/***********************************************************************
* protocols
* Returns the protocol name => protocol map for protocols.
* Locking: runtimeLock must read- or write-locked by the caller
**********************************************************************/
static NXMapTable *protocols(void)
{
    static NXMapTable *protocol_map = nil;
    
    runtimeLock.assertLocked();

    INIT_ONCE_PTR(protocol_map, 
                  NXCreateMapTable(NXStrValueMapPrototype, 16), 
                  NXFreeMapTable(v) );

    return protocol_map;
}

通过_getObjc2ProtocolList 获取到Mach-O中的静态段__objc_protolist协议列表,即从编译器中读取并初始化protocol

protocol_t * const *protolist = _getObjc2ProtocolList(hi, &count);

循环遍历协议列表,通过readProtocol方法将协议添加到protocol_map哈希表中

readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);

7、修复没有被加载的协议

主要是通过 _getObjc2ProtocolRefs获取到Mach-O的静态段 __objc_protorefs(与6中的__objc_protolist并不是同一个东西),然后遍历需要修复的协议,通过remapProtocolRef比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换
//7、修复没有被加载的协议
// Fix up @protocol references
// Preoptimized images may have the right 
// answer already but we don't know for sure.
for (EACH_HEADER) {
    // At launch time, we know preoptimized image refs are pointing at the
    // shared cache definition of a protocol.  We can skip the check on
    // launch, but have to visit @protocol refs for shared cache images
    // loaded later.
    if (launchTime && cacheSupportsProtocolRoots && hi->isPreoptimized())
        continue;
    //_getObjc2ProtocolRefs 获取到Mach-O的静态段 __objc_protorefs
    protocol_t **protolist = _getObjc2ProtocolRefs(hi, &count);
    for (i = 0; i < count; i++) {//遍历
        //比较当前协议和协议列表中的同一个内存地址的协议是否相同,如果不同则替换
        remapProtocolRef(&protolist[i]);//经过代码调试,并未执行
    }
}

ts.log("IMAGE TIMES: fix up @protocol references");

其中remapProtocolRef的源码实现如下

/***********************************************************************
* remapProtocolRef
* Fix up a protocol ref, in case the protocol referenced has been reallocated.
* Locking: runtimeLock must be read- or write-locked by the caller
**********************************************************************/
static size_t UnfixedProtocolReferences;
static void remapProtocolRef(protocol_t **protoref)
{
    runtimeLock.assertLocked();
    //获取协议列表中统一内存地址的协议
    protocol_t *newproto = remapProtocol((protocol_ref_t)*protoref);
    if (*protoref != newproto) {//如果当前协议 与 同一内存地址协议不同,则替换
        *protoref = newproto;
        UnfixedProtocolReferences++;
    }
}

8、分类处理

主要是处理分类,需要在分类初始化并将数据加载到类后才执行,对于运行时出现的分类,将分类的发现推迟推迟到对_dyld_objc_notify_register的调用完成后的第一个load_images调用为止

//8、分类处理
// Discover categories. Only do this after the initial category 发现分类
// attachment has been done. For categories present at startup,
// discovery is deferred until the first load_images call after
// the call to _dyld_objc_notify_register completes. rdar://problem/53119145
if (didInitialAttachCategories) {
    for (EACH_HEADER) {
        load_categories_nolock(hi);
    }
}

ts.log("IMAGE TIMES: discover categories");

9、类的加载处理

// Realize non-lazy classes (for +load methods and static instances)
for (EACH_HEADER) {
    classref_t const *classlist = 
        _getObjc2NonlazyClassList(hi, &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);
    }
}

通过注释,我们可以得知,当前只有 类为非懒加载类的时候,才会执行。

1. 从 mach-o的静态段 __objc_nlclslist中获取非懒加载类表。

2. 通过 addClassTableEntry将非懒加载的类插入到列表中,然后存储到内存当中,其中如果当前类已经被添加,就不会再次进行添加了。

3. 通过 realizeClassWithoutSwift实现类的 data的加载,因为前面我们的类的信息目前仅存储了地址和名称。

接下来研究此步骤中的重点部分 realizeClassWithoutSwift方法

realizeClassWithoutSwift方法主要作用是将类的 data信息加载到内存当中,还有对 ro和 rw进行了相关操作。主要有以下几个步骤。

 1. 读取 data数据,并设置 rorw

    • 读取 data数据,并将其强转为 ro,以及初始化 rw,然后将 ro拷贝一份到 rw的 ro
    • ro表示 read only,其在编译期的时候就已经确定了内存,包含了类的名称、方法列表、协议列表、属性列表和成员变量列表的信息。由于它是只读的,确定了之后就不会发生变化,所以属于 干净的内存(Clean Memory)
    • rw表示 read Write,由于 OC的动态性,所以可能会在运行时动态往类中添加属性、方法和协议。
    • rwe表示 read Write ext,在2020的WWDC上,对 内存优化做了进一步的改进。其中说到 rw中只有 10% 左右的类真正更改了它们的方法、属性等,所以新增加了 rwe,即是类的额外信息,rw和 rwe都属于 脏内存(dirty memory)

 

// fixme verify class is not in an un-dlopened part of the shared cache?
// 读取 cls 的 data,并将其强转为 ro,以及初始化 rw,然后将 ro 拷贝一份到 rw 的 ro。
// 读取类结构的 bits 属性。
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
    rw = objc::zalloc<class_rw_t>();
    // rw 赋值 ro
    rw->set_ro(ro);
    rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
    // 将 rw 赋值给 cls 的 data
    cls->setData(rw);
}

2:递归调用 realizeClassWithoutSwift完成类的继承链关系。

    • 递归调用 realizeClassWithoutSwift,设置父类和元类的 data信息加载到内存当中。
    • 分别将父类和元类赋值给 class的 superclass和 classIsa
// 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`,设置父类和元类的 `data` 信息加载到内存当中。
// 当 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` 的 `superclass` 和 `classIsa`。
cls->superclass = supercls;
cls->initClassIsa(metacls);

// Connect this class to its superclass's subclass lists
// 注意:class 是双向链接结构,即父类可以找到子类,子类可以找到父类
if (supercls) {
    // 将 cls 作为 supercls 的子类添加。
    addSubclass(supercls, cls);
} else {
    // 将 cls 添加为新的已实现的根类。
    addRootClass(cls);
}

3:调用 methodizeClass方法,读取方法列表(包括分类)、协议列表、属性列表,然后赋值给 rw,最后返回 cls

// Attach categories - 附加分类 
methodizeClass(cls, previously);

return cls;

10: 没有被处理的类,优化那些被侵犯的类

// Realize newly-resolved future classes, in case CF manipulates them
// 10.实现没有被处理的类,优化那些被侵犯的类
if (resolvedFutureClasses) {
    for (i = 0; i < resolvedFutureClassCount; i++) {
        Class cls = resolvedFutureClasses[i];
        if (cls->isSwiftStable()) {
            _objc_fatal("Swift class is not allowed to be future");
        }
        realizeClassWithoutSwift(cls, nil);
        cls->setInstancesRequireRawIsaRecursively(false/*inherited*/);
    }
    free(resolvedFutureClasses);
}

3: methodizeClass分析对rw进行赋值

methodizeClass->prepareMethodLists->fixupMethodList

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();
    
    // Install methods and properties that the class implements itself.
    // 将方法列表、属性列表和协议列表赋值到 rwe
    // 获取 ro 的 方法列表
    method_list_t *list = ro->baseMethods();
    if (list) {
        // 对方法列表进行排序操作
        prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
        // 把方法赋值给 rwe 的 method
        if (rwe) rwe->methods.attachLists(&list, 1);
    }
    
    // 获取 ro 的 属性列表
    property_list_t *proplist = ro->baseProperties;
    if (rwe && proplist) {
        // 把属性赋值给 rwe 的 properties
        rwe->properties.attachLists(&proplist, 1);
    }
    
    // 获取 ro 的 协议列表
    protocol_list_t *protolist = ro->baseProtocols;
    if (rwe && protolist) {
        // 把协议赋值给 rwe 的 protocols
        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
  • 附加分类方法。

3.1 rwe 的获取

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

在上面的源码中我们可以看到 ext()方法调用了 get_ro_or_rwe()获取 ro或者 rwe()

3.2 rwe 的逻辑

以 方法列表加入到 rwe的逻辑为例,主要有以下步骤

1. 获取 ro的 baseMethods

2. 通过 prepareMethodLists方法进行排序;

3. 通过 attachLists插入。

3.2.1 方法如何排序

查看 prepareMethodLists的源码,发现内部是通过调用 fixupMethodList来进行排序的

static void 
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
                   bool baseMethods, bool methodsFromBundle)
{
    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*/);
        }
    }
}

查看 fixupMethodList的源码,发现内部是根据 SortBySELAddress即根据 sel的地址进行排序的。

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 list
    if (!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);
        }
    }

    // Sort by selector address.
    // 根据 `sel` 的地址进行排序
    if (sort) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(mlist->begin(), mlist->end(), sorter);
    }
    
    // Mark method list as uniqued and sorted
    mlist->setFixedUp();
}

4:主要是将分类添加到主类中

void attachToClass(Class cls, Class previously, int flags)
{
    runtimeLock.assertLocked();
    ASSERT((flags & ATTACH_CLASS) ||
           (flags & ATTACH_METACLASS) ||
           (flags & ATTACH_CLASS_AND_METACLASS));
           
    auto &map = get();
    // 找到一个分类就进来一次,防止混乱
    auto it = map.find(previously);
    
    // 注意:当主类没有实现 load 方法的时候,分类实现了 load 方法,那么会迫使主类进行加载,所以会进入 if 判断里面
    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);
    }
}

2.3.4 attachCategories 方法

主要是准备分类的数据

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order, 
// oldest categories first.
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,因为要往主类添加方法、属性和协议
    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) {
            // 当前 mcount = 0,ATTACH_BUFSIZ= 64,所以不会进入 if 里面
            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);
}

2.3.5 attachLists 方法

void attachLists(List* const * addedLists, uint32_t addedCount) {
    if (addedCount == 0) return;
    
    if (hasArray()) {
        // many lists -> many lists
        // 获取数组中旧 lists 的大小
        uint32_t oldCount = array()->count;
        // 计算新的容量大小 = 旧数据大小 + 新数据大小
        uint32_t newCount = oldCount + addedCount;
        // 根据新的容量大小,开辟一个数组,类型是 array_t,通过 array() 获取
        setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
        // 设置数组的大小
        array()->count = newCount;
        // 旧的数据从 addedCount 数组下标开始存放旧的 lists,大小 = 旧数据大小 * 单个旧 list 大小
        memmove(array()->lists + addedCount, array()->lists, 
                oldCount * sizeof(array()->lists[0]));
        // 新数据从数组 首位置开始存储,存放新的lists,大小 = 新数据大小 * 单个list大小
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
    else if (!list  &&  addedCount == 1) {
        // 0 lists -> 1 list
        // 将 list 加入 mlists 的第一个元素,此时的 list 是一个一维数组
        list = addedLists[0]; 
    } 
    else {
        // 新的 list 就是分类,来自LRU的算法思维,即最近最少使用算法
        // 获取旧的list
        List* oldList = list;
        uint32_t oldCount = oldList ? 1 : 0;
        // 计算容量和 = 旧list个数 + 新lists的个数
        uint32_t newCount = oldCount + addedCount;
        // 开辟一个容量和大小的集合,类型是 array_t,即创建一个数组,放到 array 中,通过 array() 获取
        setArray((array_t *)malloc(array_t::byteSize(newCount)));
        // 设置数组的大小
        array()->count = newCount;
        // 判断old是否存在,如果存在,就将旧的 list 放入到数组的末尾
        if (oldList) array()->lists[addedCount] = oldList;
        // memcpy(起始位置,放什么,放多大) 内存平移,从数组起始位置存入新的list
        //其中 array()->lists 表示首位元素位置
        memcpy(array()->lists, addedLists, 
               addedCount * sizeof(array()->lists[0]));
    }
}

从上可知,插入表可分为以下三种情况

  • 1. 多对多: 如果当前调用 attachLists的 list_array_tt二维数组中有多个一维数组。
    • 1.1 先计算数组中旧 list的大小
    • 1.2 计算新的容量大小 = 旧数据大小 + 新数据大小
    • 1.3 根据新的容量大小,开辟一个数组,类型是 array_t,通过 array()获取
    • 1.4 设置数组大小
    • 1.5 旧的数据从 addedCount数组下标开始 存放旧的 lists,大小 = 旧数据大小 * 单个旧list大小,即指针偏移
    • 1.6 新数据从数组首位置开始存储,存放新的 lists,大小 = 新数据大小 * 单个list大小,可以理解越晚加进来的越在前面,越在前面,调用时则优先调用
  • 2. 零对一: 如果当前调用 attachLists的 list_array_tt二维数组为空且新增大小为1。
    • 2.1 直接赋值给 addedList的第一个 list
  • 3. 一对多: 如果当前调用 attachLists的 list_array_tt二维数组只有一个一维数组。
    • 3.1 获取旧的list
    • 3.2 计算容量和 = 旧list个数 + 新lists的个数
    • 3.3 开辟一个容量和大小的集合,类型是 array_t,即创建一个数组,放到array中,通过 array()获取
    • 3.4 设置数组的大小
    • 3.5 判断 old是否存在,如果 old是存在的,那么就将旧的list放入到数组的末尾
    • 3.6 memcpy(起始位置,放什么,放多大) 内存平移,从数组起始位置存入新的list

在第三种情况中,list指的就是我们的分类,所以这就是为什么我们经常碰到的 子类实现父类方法会把父类方法覆盖、分类实现主类方法会把主类方法覆盖的原因了

总结

  • readClass主要是读取类,即此时的类仅有地址+名称,还没有data数据
  • realizeClassWithoutSwift主要是实现类,即将类的data数据读取到内存中
    • methodizeClass方法中实现类中方法(协议等)的`序列化

    • attachCategories方法中实现类以及分类的数据加载

综上所述,类从Mach-O加载到内存的流程图如下所示

methodizeClass:

5:懒加载类和非懒加载类的数据加载时机

 注意

 

引用

1:iOS-底层原理 17:类的加载(上)

2:十二、类的加载分析(上)

posted on 2020-12-02 13:36  风zk  阅读(224)  评论(0编辑  收藏  举报

导航