iOS 类的加载

577 阅读7分钟

1、懒加载类与非懒加载类

官方在对类进行处理的时候, 为了提高对类处理的效率以及性能, 就对类进行了识别, 当类需要使用的时候, 系统才会对类进行实现. 如果没有使用就不会实现. 当需要实现才进行加载的类就被称为懒加载类. 反之无论是否使用到这个类, 都对这个类进行加载的类就被称为非懒加载类. 在 _read_images() 中, 当对类进行处理的时候, 通过 _getObjc2NonlazyClassList() 获取到的类中并没有我们自己创建的类. 这就说明我们平常通过XCode创建的类默认为是懒加载类. 我们知道 +(void)load 方法会在 main() 之前调用. 我们在自己的类中实现 +(void)load 方法:

+ (void)load{
    NSLog(@"load");
}

复制代码再来 _getObjc2NonlazyClassList() 中就可以找到自己的类. 这样的话意味着当一个类实现了+(void)load方法后, 它就会由 懒加载类 变成 非懒加载类.

2. iOS 类的加载

由于 objc_init 是 runtime 入口(runtime还没有处理类的元数据)。

首先简单看一个runtime初始化的流程。

  • ①.objc_init

  • ②. read_images 读取所有镜像文件信息

      1. 初始化全局 class map,读取 macho 的 __objc_classlist 初始化全局 class map, __objc_classrefs
      1. 读取 __objc_selrefs等段读取相关。进行 sel fix up
      1. 读取 protocol
      1. 读取所有 非懒加载 类,并调用 realizeClass 进行初始化,进一步调用 methodizeClass(Attach categories)
      1. 读取 categories,把读取出来的 categories 放到全局的 map 里,如果 cat 关联的 cls 已经被 realize 则进行remethodizeClass
  • ③. 对所有非懒加载的类 和 category 执行 +load

类的可读写元数据的初始话主要发生在 realizeClass 和 methodizeClass

dyld和ObjC的关联中我们知道当 dyld 加载到开始链接主程序的时候,最终会走到类的加载方法methodizeClass

  • 通过 mangledName 筛选出我们所定义的类的实现,定位到当前类
  • 在methodizeClass中,从ro中读取方法列表(包括分类中的方法)、属性列表、协议列表赋值给rw
/***********************************************************************
* 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();
    
    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();
        printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);

//        if (!kc_isMeta) {
//            printf("%s: 这个是我要研究的 %s \n",__func__,LGPersonName);
//        }
    }

    // Methodizing for the first time
    if (PrintConnecting) {
        _objc_inform("CLASS: methodizing class '%s' %s", 
                     cls->nameForLogging(), isMeta ? "(meta)" : "");
    }

    // 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()) {
        // 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);

#if DEBUG
    // Debug: sanity-check all SELs; log method list contents
    for (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
}

attachLists 是如何插入数据的呢?方法属性协议都可以直接通过 attachLists 插入吗?

struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
    bool isUniqued() const;
    bool isFixedUp() const;
    void setFixedUp();

    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t i = 
            (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
        ASSERT(i < count);
        return i;
    }
};

struct property_list_t : entsize_list_tt<property_t, property_list_t, 0> {
};

struct protocol_list_t {
    // count is pointer-sized by accident.
    uintptr_t count;
    protocol_ref_t list[0]; // variable-size

    size_t byteSize() const {
        return sizeof(*this) + count*sizeof(list[0]);
    }

    protocol_list_t *duplicate() const {
        return (protocol_list_t *)memdup(this, this->byteSize());
    }

    typedef protocol_ref_t* iterator;
    typedef const protocol_ref_t* const_iterator;

    const_iterator begin() const {
        return list;
    }
    iterator begin() {
        return list;
    }
    const_iterator end() const {
        return list + count;
    }
    iterator end() {
        return list + count;
    }
};

方法、属性继承于 entsize_list_tt ,协议则是类似 entsize_list_tt 实现,都是二维数组 方法、属性、协议

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

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

attachLists 的源码实现中可以得出:

  • (多对多)如果当前调用 attachListslist_array_tt 二维数组中有多个一维数组

    • 通过 realloc 对容器进行重新分配大小为原来的大小加上新增的大小
    • 通过 memmove 把原来的数据移动到容器的末尾
    • 把新的数据 memcpy 拷贝到容器的起始位置
  • (0对一)如果调用 attachListslist_array_tt 二维数组为空且新增大小数目为 1

    • 直接赋值 addedList 的第一个 list
  • (一对多)如果当前调用 attachListslist_array_tt 二维数组只有一个一维数组

    • 通过 realloc 对容器进行重新分配大小为原来的大小加上新增的大小
    • 由于只有一个一维数组,所以直接赋值到新 Array 的最后一个位置
    • 把新的数据 memcpy 拷贝到容器的起始位置

memmovememcpy 的区别在于:

  • 在不知道需要平移的内存大小时,需要memmove进行内存平移,保证安全
  • memcpy从原内存地址的起始位置开始拷贝若干个字节到目标内存地址中,速度快

同样在 methodizeClass 方法中也会执行到 attachToClass 方法,在 attachToClass 方法中也会执行 attachCategories 方法

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);
    auto rwe = cls->data()->extAllocIfNeeded();

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

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

一、主类实现 load 方法, 其中一个分类实现 load 方法,一个没有实现 load 方法 :非懒加载类、懒加载分类

只要有一个分类是 非懒加载分类, 所有都会是非懒加载分类

主类实现 load 方法, 其中一个分类没有实现 load 方法

二、主类实现 load 方法, 分类没有实现 load 方法 : 非懒加载类、懒加载分类

主类实现 load 方法,分类的方法来自于主类的 data() -> (const class_ro_t *)cls->data(),编译时期完成 data()

当非懒加载类与懒加载分类,此时非懒加载类也会在程序启动时加载进内存。

主类实现 load 方法, 分类没有实现 load 方法

三、主类没有实现 load 方法, 分类没有实现 load 方法 :懒加载类、懒加载分类

第一次消息的时候,分类的方法也会来自于主类的 data() -> (const class_ro_t *)cls->data(),编译时期完成 data()

  • 当懒加载类发送消息时,才会断点成功。在 lookupimporforward 里面就做了一次判断是否为懒加载类
  • realizeClassMaybeSwiftMaybeRelock 中区分是否为 Swift
  • realizeClassWithoutSwift 跳转到 methodizeClass 将类加载到内存中
  • 当分类没有实现 load 方法的时候,都是编译时处理, 直接存放进 ro

整体流程为:msgSend -> lookupimporforward -> realizeClassWithoutSwift -> methodlizeClass

主类没有实现 load 方法, 分类没有实现 load 方法

四、主类没有实现 load 方法, 分类实现 load 方法 懒加载类、非懒加载分类

迫使类成为非懒加载类样式来提前加载数据

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

主类没有实现 load 方法, 分类实现 load 方法

类和分类搭配加载