阅读 28

类的加载

前言

  作为一名iOS开发人员,我们总是要不停的和类打交道,我们可能已经习惯创建类、实例化类、为类添加属性、添加方法等等一系列的相关操作。那么类是怎么加载到内存中的呢?类里面的数据又是什么时候确定的呢?
  当启动一个App的时候,系统需要把代码加载到内存中,编译成可执行文件,在经历系统初始化了libSystem、libdispatch、os_objc_init之后,系统就要开始初始化跟程序更加相关的东西。

void _objc_init(void)
{
    static bool initialized = false;
    if (initialized) return;
    initialized = true;
    
    // fixme defer initialization until an objc-using image is found?
    // 读取影响运行时的环境变量
    environ_init();
    // 设置线程的key
    tls_init();
    // 运行系统级别的c++ 静态构造函数
    static_init();
    lock_init();
    // 初始化异常监听的回调函数
    exception_init();
    
    _dyld_objc_notify_register(&map_images, load_images, unmap_image);
}
复制代码

  从以上源码可以看出,_objc_init会先执行一些配置相关的初始化:

  • 环境变量的初始化
  • 设置线程的key
  • 运行系统级别的c++ 静态构造函数
  • 初始化异常监听的回调函数

之后就会进行image的映射和加载。

映射image

  在这个环节,会将类的相关数据进行处理。首先,我们需要获取image中的相关信息。

void
map_images(unsigned count, const char * const paths[],
           const struct mach_header * const mhdrs[])
{
    recursive_mutex_locker_t lock(loadMethodLock);
    map_images_nolock(count, paths, mhdrs);
}

void 
map_images_nolock(unsigned mhCount, const char * const mhPaths[],
                  const struct mach_header * const mhdrs[])
{
    ......
    if (hCount > 0) {
        _read_images(hList, hCount, totalClasses, unoptimizedTotalClasses);
    }
    ......
}
复制代码

追踪代码流程,我们会进入_read_images这个方法:

// 只展示重要的代码
void _read_images(header_info **hList, uint32_t hCount, int totalClasses, int unoptimizedTotalClasses)
{
    ......
    
    // 1. 实例化存储类的hash表
    if (!doneOnce) {
        doneOnce = YES;
        gdb_objc_realized_classes =
            NXCreateMapTable(NXStrValueMapPrototype, namedClassesSize);
        
        allocatedClasses = NXCreateHashTable(NXPtrPrototype, 0, nil);
    }
    
    // 2. 类处理
    for (EACH_HEADER) {
        classref_t *classlist = _getObjc2ClassList(hi, &count);
        
        if (! mustReadClasses(hi)) {
            // Image is sufficiently optimized that we need not call readClass()
            continue;
        }

        for (i = 0; i < count; i++) {
            Class cls = (Class)classlist[i];
            Class newCls = readClass(cls, headerIsBundle, headerIsPreoptimized);
        }
    }
    
    // 3. 方法处理
    static size_t UnfixedSelectors;
    {
        mutex_locker_t lock(selLock);
        for (EACH_HEADER) {
            SEL *sels = _getObjc2SelectorRefs(hi, &count);
            for (i = 0; i < count; i++) {
                const char *name = sel_cname(sels[i]);
                sels[i] = sel_registerNameNoLock(name, isBundle);
            }
        }
    }
    
    // 4. 协议的处理
    for (EACH_HEADER) {
        extern objc_class OBJC_CLASS_$_Protocol;
        Class cls = (Class)&OBJC_CLASS_$_Protocol;
        assert(cls);

        protocol_t **protolist = _getObjc2ProtocolList(hi, &count);
        for (i = 0; i < count; i++) {
            readProtocol(protolist[i], cls, protocol_map, 
                         isPreoptimized, isBundle);
        }
    }
    
    // 5. 非懒加载类的处理
       for (EACH_HEADER) {
        classref_t *classlist = 
            _getObjc2NonlazyClassList(hi, &count);
        for (i = 0; i < count; i++) {
            Class cls = remapClass(classlist[i]);
            if (!cls) continue;
            
            addClassTableEntry(cls);
            realizeClassWithoutSwift(cls);
        }
    }
    
    // 6. future类处理
    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);
        cls->setInstancesRequireRawIsa(false/*inherited*/);
    } 
    
    // 7. 分类的处理
    for (EACH_HEADER) {
        category_t **catlist = 
            _getObjc2CategoryList(hi, &count);

        for (i = 0; i < count; i++) {
            category_t *cat = catlist[i];
            Class cls = remapClass(cat->cls);
        }
    }
    
    ......
}
复制代码

分析上述方法:

1. 如果是第一次,创建两张hash表,用来存储类:

static NXHashTable *allocatedClasses = nil;
复制代码

allocatedClasses:所有通过objc_allocateClassPairallocated的类或者元类

NXMapTable *gdb_objc_realized_classes;  
复制代码

gdb_objc_realized_classes:不在共享缓存中且已经命名的类,不管实现与否

2. 读取类;修复未解决的future类;标记bundle类。

Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    const char *mangledName = cls->mangledName();
    
    if (missingWeakSuperclass(cls)) {
        addRemappedClass(cls, nil);
        cls->superclass = nil;
        return nil;
    }

    .......
    // 如果是后期要处理的类,设置rw 
    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);
        
        addRemappedClass(cls, newCls);
        
        replacing = cls;
        cls = newCls;
    }
    .......
    addNamedClass(cls, mangledName, replacing);
    addClassTableEntry(cls);
    
    return cls;
}
复制代码

3. 对没有映射的引用类和其父类做重映射。

if (!noClassesRemapped()) {
        for (EACH_HEADER) {
            // 重映射Class,注意是从_getObjc2ClassRefs函数中取出类的引用
            Class *classrefs = _getObjc2ClassRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
            // fixme why doesn't test future1 catch the absence of this?
            classrefs = _getObjc2SuperRefs(hi, &count);
            for (i = 0; i < count; i++) {
                remapClassRef(&classrefs[i]);
            }
        }
    }
复制代码

4. 将所有的SEL都添加到一张名为namedSelectorsNXMapTable表中。

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

static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) 
{
    // 如果存在namedSelectors,就先在namedSelectors中进行寻找, 匹配的上的话,就直接返回
    if (namedSelectors) {
        result = (SEL)NXMapGet(namedSelectors, name);
    }
    if (result) return result;

    // 不存在namedSelectors,先创建一张namedSelectors表
    if (!namedSelectors) {
        namedSelectors = NXCreateMapTable(NXStrValueMapPrototype,
                                          (unsigned)SelrefCount);
    }
    // 在表namedSelectors中插入
    if (!result) {
        result = sel_alloc(name, copy);
        NXMapInsert(namedSelectors, sel_getName(result), result);
    }
    return result;
}
复制代码

5. 如果支持SUPPORT_FIXUP,获取所有的objc_msgSend引用,然后对其进行修复

for (EACH_HEADER) {
    message_ref_t *refs = _getObjc2MessageRefs(hi, &count);
    if (count == 0) continue;
        for (i = 0; i < count; i++) {
             // 内部将常用的alloc、objc_msgSend等函数指针进行注册,并fix为新的函数指针
            fixupMessageRef(refs+i);
        }
}
复制代码

6. 将所有的protocols都添加到protocol_map这张NXMapTable表中。

static void
readProtocol(protocol_t *newproto, Class protocol_class,
             NXMapTable *protocol_map, 
             bool headerIsPreoptimized, bool headerIsBundle)
{
    ......
    auto insertFn = headerIsBundle ? NXMapKeyCopyingInsert : NXMapInsert;

    // 先从protocol_map表中获取protocol_t
    protocol_t *oldproto = (protocol_t *)getProtocol(newproto->mangledName);
    
    // 取到就不做操作
    // 没有就插入到protocol_map表中
    if (oldproto) {
        
    }
    else if (headerIsPreoptimized) {
        // 共享缓存会初始化协议对象本身,但是为了允许缓存外替换,我们现在需要将其添加到协议表中。
        insertFn(protocol_map, installedproto->mangledName, 
                 installedproto);
    }
    else if (newproto->size >= sizeof(protocol_t)) {
        // 从未预先优化的image获取的协议
        newproto->initIsa(protocol_class);  // fixme pinned
        insertFn(protocol_map, newproto->mangledName, newproto);
    }
    else {
        // 重新实例化
        size_t size = max(sizeof(protocol_t), (size_t)newproto->size);
        protocol_t *installedproto = (protocol_t *)calloc(size, 1);
        memcpy(installedproto, newproto, newproto->size);
        installedproto->size = (typeof(installedproto->size))size;
        
        installedproto->initIsa(protocol_class);  // fixme pinned
        insertFn(protocol_map, installedproto->mangledName, installedproto);
    }
}
复制代码

7. 初始化所有非懒加载类

注意:并不会实例化任何swift端的类

  • 初始化类,将类实例化rw结构,但是不对rw赋值
  • 对当前类的父类、元类进行相关的初始化操作
  • 对父类、元类、根类、子类进行相关的绑定操作
  • 处理类的method list、protocol list、property list;绑定categories
// 只展示重要代码

// 将类插入hash表中
static void addClassTableEntry(Class cls, bool addMeta = true) {
    runtimeLock.assertLocked();
    
    if (!isKnownClass(cls))
        NXHashInsert(allocatedClasses, cls);
    if (addMeta)
        addClassTableEntry(cls->ISA(), false);
}

// 初始化所有非懒加载类,实例化rw数据
static Class realizeClassWithoutSwift(Class cls)
{
    ......

    // rw创建
    ro = (const class_ro_t *)cls->data();
    
    // 如果是future类 rw已经被实例化,直接更新
    // 注意,此处不对rw赋值
    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 {
        // 普通类,先开辟内存,创建rw结构
        rw = (class_rw_t *)calloc(sizeof(class_rw_t), 1);
        rw->ro = ro;
        rw->flags = RW_REALIZED|RW_REALIZING;
        cls->setData(rw);
    }

    // 初始化父类或者元类,实例化其数据
    supercls = realizeClassWithoutSwift(remapClass(cls->superclass));
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()));
    
    // 在重映射的情况下更新父类和元类
    cls->superclass = supercls;
    cls->initClassIsa(metacls);
    
    // 将当前类链接到其父类的子类
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    // 处理类的method list、protocol list、and property list 
    // 绑定 categories
    methodizeClass(cls);

    return cls;
}

复制代码

ro的值赋给rw,比如method_list_t、property_list_t、protocol_list_t,并且把分类的method_list_t、property_list_t、protocol_list_t绑定到类上。

// 只展示重要代码
static void methodizeClass(Class cls)
{
    runtimeLock.assertLocked();

    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);
    }

    if (cls->isRootMetaclass()) {
        // root metaclass
        addMethod(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*/);
    
    ......
}
复制代码
列表绑定:
  • 多对多:先将原来的列表进行扩容,然后将旧数据移动到列表的尾部位置,再把新的数据copy到列表头部位置。
  • 0对1:直接将新数据赋值给列表
  • 1对多或者1对多:
    • 对列表进行扩容,如果存在旧数据,将原来的数据移动到列表的尾部,再把新的数据copy到列表头部位置。
    • 对列表进行扩容,如果不存在旧数据,直接把新的数据copy到列表头部位置。
void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;//10
            uint32_t newCount = oldCount + addedCount;//4
            setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
            array()->count = newCount;// 10+4
            
            // 将lists的数据往扩容后的copy,从原来oldCount的位置开始
            memmove(array()->lists + addedCount, array()->lists,
                    oldCount * sizeof(array()->lists[0]));
            
            // 将addedLists
            memcpy(array()->lists, addedLists, 
                   addedCount * sizeof(array()->lists[0]));
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
        } 
        else {
            // 1 list -> many lists
            List* 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]));
        }
    }
复制代码

8. 遍历future类,并对其进行初始化

if (resolvedFutureClasses) {
    for (i = 0; i < resolvedFutureClassCount; i++) {
        Class cls = resolvedFutureClasses[i];
        realizeClassWithoutSwift(cls);
        cls->setInstancesRequireRawIsa(false/*inherited*/);
    }
    free(resolvedFutureClasses);
}    
复制代码

9. 处理所有的分类,对未绑定的分类进行绑定操作。将分类的的method、protocol、property添加到类。

for (EACH_HEADER) {
    // 外部循环遍历找到当前类,查找类对应的Category数组
    category_t **catlist = 
            _getObjc2CategoryList(hi, &count);
    bool hasClassProperties = hi->info()->hasCategoryClassProperties();

    for (i = 0; i < count; i++) {
        // 内部循环遍历当前类的所有Category
        category_t *cat = catlist[i];
        Class cls = remapClass(cat->cls);

        // 首先,通过其所属的类注册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添加到Class
            if (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());
            }
        }
    }
}
复制代码

总结:

  加载类的时候,首先会在创建两张表gdb_objc_realized_classesallocatedClasses用来存储类;接着从image里读取类,并将类的SEL存到namedSelectors的表里,将protocols存到protocol_map表;然后实现非懒加载类的一些信息相关的操作,如给类创建rw结构、对父类、元类也进行相关初始化、对父类、元类、根类、子类进行相关的绑定、处理类的method list、protocol list、property list、将ro的值赋给rw;然后再绑定分类,对分类的数据进行相关的操作。这样整个类就算是加载完成了。

Tips:

环境变量配置

当我们在scheme中进行配置之后,重新编译程序的时候,就会在此时被系统获知,从而生效。

memmove && memcpy

void *memmove(void *s1, const void *s2, size_t n);
void *memcpy(void *restrict s1, const void *restrict s2, size_t n);
复制代码

这两个函数都是将s2指向位置的n字节数据拷贝到s1指向的位置,区别就在于关键字restrict, memcpy假定两块内存区域没有数据重叠,而memmove没有这个前提条件。 我们可以看一下这两个方法实现的源码来加深印象:

#include <stddef.h> /* size_t */
void *memcpy(void *dest, const void *src, size_t n)
{
    char *dp = dest;
    const char *sp = src;
    while (n--)
        *dp++ = *sp++;
    return dest;
}

void *memmove(void *dest, const void *src, size_t n)
{
    unsigned char *pd = dest;
    const unsigned char *ps = src;
    if (__np_anyptrlt(ps, pd))
        for (pd += n, ps += n; n--;)
            *--pd = *--ps;
    else
        while(n--)
            *pd++ = *ps++;
    return dest;
}
复制代码

__np_anyptrlt是一个宏,用于结合拷贝的长度检测destsrc的位置,如果destsrc指向同样的对象,且srcdest地址小,就需要从尾部开始拷贝 由此可见memcpy的速度比memmove快一点,如果使用者可以确定内存不会重叠,则可以选用memcpy,否则memmove更安全一些。

关于懒加载类的加载流程

  • 非懒加载类:调用了load方法,在加载数据的时候就会提前加载。
  • 懒加载类:在使用的第一次才会加载。当我们调用这个类的方法的时候,如果是第一次,在消息查找的过程中就会加载这个类。
IMP lookUpImpOrForward(Class cls, SEL sel, id inst, 
                       bool initialize, bool cache, bool resolver)
{
    ......

    if (!cls->isRealized()) {
        cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
    }
    
    ......
}

static Class
realizeClassMaybeSwiftAndLeaveLocked(Class cls, mutex_t& lock)
{
    return realizeClassMaybeSwiftMaybeRelock(cls, lock, true);
}

static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
    if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
        realizeClassWithoutSwift(cls);
    } else {
        cls = realizeSwiftClass(cls);
    }

    return cls;
}
复制代码
关注下面的标签,发现更多相似文章
评论